跳转到内容
新建笔记

08. 函数、任务、编译指令与 testbench

写出 RTL 只完成了一半工作。你还要用 testbench 主动制造输入、观察输出,并让仿真自动判定 PASS/FAIL。纯 Verilog-2001 的 $finish 通常不保证返回非零进程状态,因此 CI 还应检查日志中是否存在唯一的 PASS;支持 SystemVerilog 的仿真器可在失败时使用 $fatal。

function:有一个返回值的组合计算

跳转到“function:有一个返回值的组合计算”

Verilog 函数不能消耗仿真时间,不能包含 #、@ 或 wait 等时序控制。它适合表达可复用的组合计算。

function [3:0] count_ones8;
input [7:0] value;
integer i;
begin
count_ones8 = 4'd0;
for (i = 0; i < 8; i = i + 1)
count_ones8 = count_ones8 + value[i];
end
endfunction
wire [3:0] ones = count_ones8(data);

函数名本身是返回值。综合后函数通常被内联为调用位置的组合硬件,不是软件中的共享处理器。

task:多个输入输出的过程复用

跳转到“task:多个输入输出的过程复用”

任务可以有 input/output/inout 参数,也可以在 testbench 中包含时间控制:

task drive_and_check;
input [7:0] test_a;
input [7:0] test_b;
input [8:0] expected;
begin
a = test_a;
b = test_b;
#1;
if (sum !== expected) begin
$display("ERROR a=%0d b=%0d sum=%0d expected=%0d",
a, b, sum, expected);
errors = errors + 1;
end
end
endtask

带 #1 的任务只能用于仿真。可综合 task 必须遵守综合工具支持的 RTL 子集,但即使可综合,调用也表示逻辑展开,不代表硬件复用。

`timescale 1ns/1ps

前一个值是时间单位,后一个值是时间精度。此设置下:

  • #10 表示 10 ns;
  • 仿真时间可量化到 1 ps。

不同文件的时间尺度不一致会导致难以发现的仿真问题。工程应统一设置,并了解工具的全局覆盖规则。

`define DATA_WIDTH 8
wire [`DATA_WIDTH-1:0] data;

宏是预处理文本替换,没有模块参数那样的类型和层次语义。可配置模块优先使用 parameter;宏适合全局编译开关或少量公共定义。

`ifdef SIMULATION
initial $display("simulation build");
`endif

不要让大量条件编译把设计拆成难以验证的多个隐形版本。

待测设计 DUT(design under test)输入由 testbench 的 reg 驱动,DUT 输出接 testbench 的 wire:

`timescale 1ns/1ps
module adder_tb;
reg [7:0] a;
reg [7:0] b;
wire [8:0] sum;
adder #(
.WIDTH(8)
) dut (
.a (a),
.b (b),
.sum (sum)
);
initial begin
a = 8'd0;
b = 8'd0;
#10 a = 8'd5; b = 8'd9;
#10 a = 8'd255; b = 8'd1;
#10 $finish;
end
endmodule

testbench 通常没有端口,因为它是仿真世界的顶层。

initial begin
clk = 1'b0;
forever #5 clk = ~clk; // 10 ns 周期,100 MHz
end
initial begin
rst_n = 1'b0;
repeat (3) @(posedge clk);
rst_n = 1'b1;
end

复位释放靠近时钟沿可能制造 testbench 竞态。更严谨的激励可在负边沿改变同步输入,让 DUT 在下一个正边沿稳定采样:

@(negedge clk);
enable = 1'b1;

自动检查,而不是只看波形

跳转到“自动检查,而不是只看波形”
integer errors;
initial begin
errors = 0;
a = 0;
b = 0;
drive_and_check(8'd5, 8'd9, 9'd14);
drive_and_check(8'd255, 8'd1, 9'd256);
if (errors == 0)
$display("PASS");
else
$display("FAIL: %0d errors", errors);
$finish;
end

波形适合调查原因,自动检查适合稳定回归。两者不是二选一。

  • $display:立即打印一行;
  • $monitor:参数变化时自动打印;
  • $time:当前仿真时间;
  • $finish:正常结束仿真;
  • $stop:暂停并进入交互环境;
  • $dumpfile/$dumpvars:某些模拟器中生成 VCD 波形。

这些系统任务大多只用于仿真。

至少包含:

  1. 复位期间输出;
  2. 复位释放后的第一个有效周期;
  3. 正常输入;
  4. 最小值、最大值和临界值;
  5. 同时发生的条件及其优先级;
  6. 连续多周期行为;
  7. 非法输入或默认分支;
  8. 最终自动 PASS/FAIL。

仿真正常仍可能有问题

跳转到“仿真正常仍可能有问题”

RTL 仿真通常不会自动发现:

  • 目标频率下的建立/保持时间违例;
  • 异步输入带来的亚稳态;
  • 错误的引脚约束和 I/O 电气标准;
  • 电源、时钟质量和板级连接问题。

正确流程是:RTL 仿真验证功能,综合与实现报告检查资源和时序,上板验证真实系统。

为第 5 篇的 4 路 MUX 写自检查 testbench:给四个数据端口不同固定值,遍历 sel=0..3,每次用 if (y !== expected) 统计错误,最后打印 PASS/FAIL。

上一篇:条件、循环与 generate · 返回目录 · 下一篇:状态机与模块化