always 的顺序逻辑
上一篇文章展示了使用 always
的不同示例 块来实现组合逻辑。一个 always
block 也主要用于实现 sequential 具有存储元件的逻辑,例如可以保存值的触发器。
JK 人字拖
JK 触发器是用于存储值的多种触发器之一,它有两个数据输入 j 和 k,一个用于复位 rstn,另一个用于时钟 clk。 JK 触发器的真值表如下所示,通常使用 NAND 门实现。
rstn | j | k | q | 评论 |
---|---|---|---|---|
0 | 0 | 0 | 0 | 置位复位时,输出始终为零 |
1 | 0 | 0 | 保持值 | 当j和k都为0时,输出和之前一样 |
1 | 0 | 1 | 1 | 当k=1时,输出变为1 |
1 | 1 | 0 | 0 | 当k=0时,输出变为0 |
1 | 1 | 1 | 切换值 | 当 j=1,k=1 输出切换当前值 |
JK触发器的行为Verilog代码可以写成如下所示
module jk_ff ( input j, // Input J
input k, // Input K
input rstn, // Active-low async reset
input clk, // Input clk
output reg q); // Output Q
always @ (posedge clk or negedge rstn) begin
if (!rstn) begin
q <= 0;
end else begin
q <= (j & ~q) | (~k & q);
end
end
endmodule
测试台
首先声明测试台中使用的所有变量并使用简单的 always
启动时钟 可以驱动到设计的块。然后实例化设计并将其端口与相应的测试平台变量连接起来。注意 q 的类型是 wire
因为它连接到将主动驱动它的设计输出。设计的所有其他输入都是类型 reg
以便它们可以在诸如 initial
之类的程序块内驱动 .
激励首先将设计的所有输入初始化为零,然后在一段时间后取消断言复位。 for
循环用于将不同的值驱动到随机时间驱动的 j 和 k。循环完成后,再等待一段时间并完成模拟。
module tb;
// Declare testbench variables
reg j, k, rstn, clk;
wire q;
integer i;
reg [2:0] dly;
// Start the clock
always #10 clk = ~clk;
// Instantiate the design
jk_ff u0 ( .j(j), .k(k), .clk(clk), .rstn(rstn), .q(q));
// Write the stimulus
initial begin
{j, k, rstn, clk} <= 0;
#10 rstn <= 1;
for (i = 0; i < 10; i = i+1) begin
dly = $random;
#(dly) j <= $random;
#(dly) k <= $random;
end
#20 $finish;
end
endmodule
从仿真波形中可以看出,在时钟的位姿下,输出 q 会根据输入 j 和 k 的状态而改变值,如真值表所示。
<无脚本>模 10 计数器
模数 (MOD) 计数器在回滚到零之前简单地计数到某个数字。 MOD-N 计数器将从 0 计数到 N-1,然后回滚到零并重新开始计数。这样的计数器通常需要 log2N 个触发器来保存计数值。下面显示的是一个 MOD-10 计数器的 Verilog 代码,只要复位 rstn 被取消断言,它就会在每个时钟 clk 保持计数。
Verilog 参数可用于制作更具扩展性的 MOD-N 计数器。
module mod10_counter ( input clk,
input rstn,
output reg[3:0] out);
always @ (posedge clk) begin
if (!rstn) begin
out <= 0;
end else begin
if (out == 10)
out <= 0;
else
out <= out + 1;
end
end
endmodule
测试台
测试台首先声明了一些变量,这些变量可以分配一些值并驱动到设计输入。然后计数器模块被实例化并与测试台信号连接,这些信号随后被激励中的一些值驱动。由于计数器还需要时钟,因此测试台时钟使用 always
建模 堵塞。激励只是在时间 0ns 设置默认值,然后在 10ns 后取消置位复位,并允许设计运行一段时间。
module tb;
reg clk, rstn;
reg [3:0] out;
mod10_counter u0 ( .clk(clk), .rstn(rstn), .out(out));
always #10 clk = ~clk;
initial begin
{clk, rstn} <= 0;
#10 rstn <= 1;
#450 $finish;
end
endmodule
看到计数器模块从零计数到九,翻转到零,重新开始计数。
<无脚本>4位左移寄存器
下图是一个 4 位左移寄存器,它接受一个输入 d 到 LSB,所有其他位将左移 1。例如,如果 d 等于 0 并且寄存器的初始值为 0011,它将变为 0110 在时钟 clk 的下一个边沿。
module lshift_4b_reg ( input d,
input clk,
input rstn,
output reg [3:0] out
);
always @ (posedge clk) begin
if (!rstn) begin
out <= 0;
end else begin
out <= {out[2:0], d};
end
end
endmodule
测试台
测试平台遵循与之前所示类似的模板,其中声明了一些变量,设计模块被实例化并与测试平台信号连接。然后启动时钟并使用 initial
将激励驱动到设计中 堵塞。在这个测试台示例中,必须执行不同的 d 值,因此 for
循环用于迭代 20 次并将随机值应用于设计。
module tb;
reg clk, rstn, d;
wire [3:0] out;
integer i;
lshift_4b_reg u0 ( .d(d), .clk(clk), .rstn(rstn), .out(out));
always #10 clk = ~clk;
initial begin
{clk, rstn, d} <= 0;
#10 rstn <= 1;
for (i = 0; i < 20; i=i+1) begin
@(posedge clk) d <= $random;
end
#10 $finish;
end
endmodule
请注意,每个位都向左移动 1,并将 d 的新值应用于 LSB。
<无脚本>Verilog