使用 Verilog always 块实现时序逻辑
前文展示了用 always 块实现组合逻辑的示例。always 块也主要用于实现时序逻辑,包含可保持值的触发器等存储元件。
JK 触发器
JK 触发器是存储值的多种触发器之一,有两个数据输入 j 和 k,以及复位 rstn 和时钟 clk。真值表如下,通常用与非门实现。
| rstn | j | k | q | 注释 |
|---|---|---|---|---|
| 0 | 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=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 等过程块中驱动。
激励先将所有设计输入初始化为 0,延时后释放复位。用 for 循环在随机时间驱动 j 和 k 不同值。循环结束后再等待一段时间结束仿真。
module tb;
reg j, k, rstn, clk;
wire q;
integer i;
reg [2:0] dly;
always #10 clk = ~clk;
jk_ff u0 ( .j(j), .k(k), .clk(clk), .rstn(rstn), .q(q));
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
可见计数器模块从 0 计到 9,归零后重新计数。
4 位左移寄存器
下面是 4 位左移寄存器,接受输入 d 进入 LSB,所有其他位左移 1 位。例如 d 为 0 且寄存器初值 0011,下一时钟沿变为 0110。
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