📌 本篇导读
| 知识点 | 难度 | 实战价值 | 典型场景 |
|---|---|---|---|
initial 过程块 |
⭐ | ⭐⭐⭐⭐⭐ | 测试激励生成、初始化 |
$display / $write |
⭐ | ⭐⭐⭐⭐⭐ | 打印信息、调试 |
$monitor / $strobe |
⭐⭐ | ⭐⭐⭐⭐ | 自动跟踪信号变化 |
时间控制(#、@、repeat) |
⭐⭐ | ⭐⭐⭐⭐⭐ | 延迟、事件同步 |
$finish / $stop |
⭐ | ⭐⭐⭐⭐ | 控制仿真结束 |
| 时钟生成模板 | ⭐⭐ | ⭐⭐⭐⭐⭐ | always + # 生成时钟 |
| 复位生成模板 | ⭐ | ⭐⭐⭐⭐⭐ | 异步/同步复位序列 |
force / release |
⭐⭐ | ⭐⭐⭐⭐ | 强制赋值、故障注入 |
$dumpvars / $dumpfile |
⭐ | ⭐⭐⭐⭐ | 生成波形文件 |
| 简单 Testbench 结构 | ⭐⭐ | ⭐⭐⭐⭐⭐ | 顶层封装、DUT例化 |
💡 常见误区:
#10与@(posedge clk)混用导致时序错位$display与$strobe在非阻塞赋值后的显示差异- 忘记用
$finish导致仿真永不结束 force后忘记release导致后续测试异常- 在多时钟域中时间单位设置不合理
🧭 本文使用 【综合】 标识电路设计相关语法,使用 【验证】 标识测试与仿真专用语法。本章主要面向验证,大量语法不可综合。
1 验证与 Testbench 概述
Testbench 是对设计(DUT)进行功能验证的仿真环境,通常不综合,但会:
- 生成时钟和复位信号
- 产生输入激励数据
- 接收并检查 DUT 输出
- 报告测试结果
最简单的 Testbench 结构:

2 initial 过程块【验证】
initial 块在仿真时间 0 时刻 仅执行一次,是 Testbench 核心激励描述方式。
systemverilog
initial begin
$display("仿真开始 @ %0t", $time);
#10 rst_n = 1; // 10 ns 后释放复位
#100 $display("完成初始序列");
#20 $finish; // 结束仿真
end
多个 initial 块 :并行执行,均从 0 时刻启动。
systemverilog
initial begin
clk = 0;
forever #5 clk = ~clk;
end
initial begin
#100 $display("时钟已运行 100 个时间单位");
end
3.1 $display 与 $write
| 函数 | 行为 | 示例 |
|---|---|---|
$display |
自动换行 | $display("a=%0d", a); |
$write |
不换行 | $write("."); |
$display(带格式) |
支持 %d/%h/%b/%s/%t/%p 等 |
$display("data=0x%h", data); |
常用格式符:
%d:十进制;%0d:去掉前导空格(仿真器扩展)%h/%0h:十六进制%b:二进制%s:字符串%t:仿真时间(配合$time)%m:当前模块层次路径%p:打印聚合数据类型(数组、队列、结构体、联合体、类等),自动展开层次结构,非常适合调试
示例:
systemverilog
module tb_print;
initial begin
// 变量声明
logic [7:0] val = 8'hA5;
int arr[] = '{1, 2, 3};
struct { logic [3:0] a; logic b; } s = '{4'hF, 1'b1};
$display("======== 仿真开始 ========");
// 基本格式符
$display("原始: val=%d, hex=%h, bin=%b", val, val, val);
// 输出: 原始: val=165, hex=a5, bin=10100101
$display("无前导零: %0d, %0h", val, val);
// 输出: 无前导零: 165, a5
// %p 聚合数据类型
$display("arr = %p", arr);
// 输出: arr = '{1, 2, 3}
$display("s = %p", s);
// 输出: s = '{a:15, b:1}
$display("======== 仿真结束 ========");
$finish;
end
endmodule
仿真打印:

3.2 $monitor 自动监视
信号变化时自动打印;全局仅一个活跃 $monitor,后调用覆盖前。
systemverilog
initial begin
$monitor("TIME=%0t, clk=%b, data=%h", $time, clk, data);
end
3.3 $strobe 同步打印
当前时间所有赋值完成后执行,适合观察非阻塞赋值最终值。
systemverilog
always_ff @(posedge clk) begin
q <= d;
$display("display: q=%0d", q); // 显示旧值
$strobe ("strobe: q=%0d", q); // 显示新值
end
3.4 $time / $realtime
$time:按timescale舍入为整数$realtime:保留小数的实数时间
systemverilog
`timescale 1ns/1ps
initial begin
#1.55;
$display("$time = %0d ns", $time); // 输出 2
$display("$realtime = %0t ns", $realtime); // 输出 1.55
end
4 时间控制与等待【验证】
4.1 # 延迟控制
systemverilog
#10; // 延迟 10 个时间单位
#(5.3); // 延迟 5.3(需精度匹配)
#(a + b); // 延迟表达式(验证可用变量)
4.2 @ 边沿敏感
systemverilog
@(posedge clk); // 时钟上升沿
@(negedge rst_n); // 复位下降沿
@(clk); // 任意变化(不推荐)
4.3 repeat 重复等待
systemverilog
repeat(5) @(posedge clk); // 等待 5 个时钟上升沿
4.4 wait 电平敏感
systemverilog
wait (ready == 1); // 等待高电平
wait (busy == 0) @(posedge clk); // 组合使用
4.5 event 自定义事件
systemverilog
event start_sim; // 声明事件
initial begin
-> start_sim; // 触发事件
end
initial begin
@(start_sim); // 等待触发
$display("事件已收到");
end
好的,优化后的内容如下,增加工业界主流的 VCS + FSDB + Verdi 流程,同时保留通用的 VCD 方案作为参考。
5 仿真控制系统函数 【验证】
| 函数 | 行为 |
|---|---|
$finish |
结束仿真,退出仿真器 |
$stop |
暂停仿真,进入交互模式 |
5.1 波形文件生成
方案一:通用 VCD
systemverilog
initial begin
$dumpfile("wave.vcd"); // 指定波形文件名
$dumpvars(0, tb_top); // 记录 tb_top 及其所有子模块信号
#1000 $finish;
end
方案二:VCS + FSDB + Verdi
在 Synopsys VCS 环境中,通常使用 FSDB(Fast Signal Database) 格式配合 Verdi 调试波形,速度更快、压缩比更高、支持信号追溯。
systemverilog
initial begin
$fsdbDumpfile("wave.fsdb"); // 指定 FSDB 文件名
$fsdbDumpvars(0, tb_top); // 记录所有层次信号
#1000 $finish;
end
编译与运行(VCS 命令行):
bash
# 编译时需加 -fsdb 选项,并链接 Verdi 库
vcs -full64 -sverilog -fsdb tb.sv -o simv
# 运行仿真,生成 wave.fsdb
./simv
# 用 Verdi 查看波形
verdi -ssf wave.fsdb &
常用 FSDB 函数扩展:
| 函数 | 行为 |
|---|---|
$fsdbDumpfile("xxx.fsdb") |
指定 FSDB 文件名 |
$fsdbDumpvars(0, top) |
记录所有层次信号 |
$fsdbDumpvars(1, top.sub) |
仅记录指定模块及其子层次 |
$fsdbDumpon / $fsdbDumpoff |
动态开启/关闭波形记录 |
6 时钟与复位生成模板
6.1 时钟生成
systemverilog
`timescale 1ns/1ps
module tb;
logic clk;
// 50% 占空比
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 等价写法
always #5 clk = ~clk;
endmodule
相位偏移时钟:
systemverilog
initial begin
clk = 0;
#2.5; // 偏移 2.5ns
forever #5 clk = ~clk;
end
6.2 复位生成
异步复位:
systemverilog
initial begin
rst_n = 0;
#20;
rst_n = 1;
#100;
rst_n = 0; // 可选
#20 rst_n = 1;
end
同步复位(推荐):
systemverilog
initial begin
rst_n = 0;
repeat(3) @(posedge clk);
rst_n = 1;
end
7 简单 Testbench 完整结构
DUT(8位计数器)
systemverilog
`timescale 1ns/1ps
module counter #(parameter WIDTH=8) (
input logic clk,
input logic rst_n,
input logic en,
output logic [WIDTH-1:0] cnt
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 0;
else if (en)
cnt <= cnt + 1;
end
endmodule
Testbench
systemverilog
`timescale 1ns/1ps
module tb_counter;
logic clk, rst_n, en;
logic [7:0] cnt;
// 例化 DUT
counter #(.WIDTH(8)) dut (
.clk (clk),
.rst_n (rst_n),
.en (en),
.cnt (cnt)
);
// 时钟
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 复位
initial begin
rst_n = 0;
#20;
rst_n = 1;
end
// 激励与检查
initial begin
en = 0;
wait(rst_n == 1);
@(posedge clk);
repeat(2) @(posedge clk);
en = 1;
repeat(10) @(posedge clk);
if (cnt == 10)
$display("[PASS] 计数到 10");
else
$display("[FAIL] 计数错误: %0d", cnt);
en = 0;
repeat(3) @(posedge clk);
if (cnt === 10)
$display("[PASS] 计数保持");
else
$display("[FAIL] 计数改变");
rst_n = 0;
@(negedge clk);
if (cnt === 0)
$display("[PASS] 复位清零");
else
$display("[FAIL] 复位错误");
#50;
$display("=== 仿真完成 ===");
$finish;
end
// 波形
initial begin
$fsdbDumpfile("wave.fsdb");
$fsdbDumpvars(0, tb_counter);
end
// 监视器
initial begin
$monitor("TIME=%0t, en=%b, cnt=%0d", $time, en, cnt);
end
endmodule
仿真输出:

8 force / release【验证】
仅仿真有效,用于故障注入/调试。
systemverilog
initial begin
#100;
force dut.cnt = 8'hFF;
#20;
release dut.cnt;
end
⚠️ :不可综合,仅用于验证。
9 timescale 时间尺度【验证】
systemverilog
`timescale <单位> / <精度>
常见设置:
1ns/1ps:单位 1ns,精度 1ps10ns/1ns:单位 10ns,精度 1ns1ps/1ps:单位 1ps
示例:
systemverilog
`timescale 1ns/1ps
initial begin
#1.55;
#1.555;
end
10 序列检测器仿真示例
DUT
systemverilog
`timescale 1ns/1ps
module seq_detector (
input logic clk,
input logic rst_n,
input logic din,
output logic dout
);
typedef enum logic [2:0] { S0=0, S1=1, S2=3, S3=2, S4=6 } state_t;
state_t cur, nxt;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) cur <= S0;
else cur <= nxt;
always_comb begin
nxt = cur;
case (cur)
S0: nxt = din ? S1 : S0;
S1: nxt = din ? S2 : S0;
S2: nxt = din ? S2 : S3;
S3: nxt = din ? S4 : S0;
S4: nxt = S0;
default: nxt = S0;
endcase
end
assign dout = (cur == S4);
endmodule
Testbench
systemverilog
`timescale 1ns/1ps
module tb_seq;
logic clk, rst_n, din, dout;
seq_detector dut (.*);
initial begin
clk = 0;
forever #5 clk = ~clk;
end
initial begin
$display("======== Simulation Start ========");
rst_n = 0; din = 0;
$display("[%0t] Reset asserted (rst_n=0)", $time);
#20 rst_n = 1;
$display("[%0t] Reset released (rst_n=1)", $time);
// 发送序列 1,1,0,1
@(posedge clk);
din = 1;
$display("[%0t] Send din = 1", $time);
@(posedge clk);
din = 1;
$display("[%0t] Send din = 1", $time);
@(posedge clk);
din = 0;
$display("[%0t] Send din = 0", $time);
@(posedge clk);
din = 1;
$display("[%0t] Send din = 1", $time);
@(posedge clk);
din = 0; // 停止输入
$display("[%0t] Input stopped", $time);
#10; // 等待输出稳定
$display("[%0t] Check output: dout = %b", $time, dout);
if (dout === 1) $display("PASS: 1101 detected");
else $display("FAIL: 1101 test");
#50;
$display("======== Simulation Stop ========");
$finish;
end
//initial begin
// $fsdbDumpfile("wave.fsdb");
// $fsdbDumpvars(0, tb_seq);
//end
endmodule
仿真输出:

11 常见错误与编码建议
- 忘记
timescale→ 所有文件开头统一写 ```timescale 1ns/1ps`` $display/$strobe混用 → 非阻塞赋值用$strobe- 时钟无初值 →
initial clk=0;配合always #5 clk=~clk - 复位与时钟沿对齐风险 → 同步复位用
@(posedge clk) rst_n=1; force不释放 → 必须配对release$monitor多实例覆盖 → 全局仅设一个- 仿真不结束 → 测试完成后调用
$finish
📢 关于文章 :原创分享,转载需注明出处。
🔔 点击关注,第一时间收到后续内容推送(第10篇:面向对象编程基础)。
📚 参考文档 :IEEE Standard for SystemVerilog