Verilog 仿真技术指南:构建可靠 RTL 验证环境
Verilog 是硬件描述语言,设计者无需仿真即可将 RTL 转换为逻辑门。那么为什么要仿真?

仿真是在不同时间向设计施加不同输入激励,检查 RTL 代码是否按预期行为工作的技术。本质上,仿真是验证设计鲁棒性的常用手段。这也类似于制造出的芯片在真实世界中的使用方式及其对不同输入的反应。
例如,上图设计表示一个正边沿检测器,输入时钟和信号在周期性间隔求值得到输出 pe。仿真让我们查看相关信号的时序图,理解 Verilog 描述的实际行为。

多家 EDA 公司开发仿真器,能计算设计对各种输入的输出。Verilog 基于离散事件执行模型定义,不同仿真器可使用不同算法提供一致结果。Verilog 代码被分为多个进程和线程,在仿真过程中可能在不同时间求值,稍后详述。
示例
测试平台 tb 是容纳设计模块的容器。本例未使用设计实例。有两个变量或信号可在特定时间赋值。clk 代表时钟,在测试平台内由 always 语句每 5ns 翻转一次产生。initial 块包含一组语句,在不同时间给两信号赋不同值。
module tb; reg clk; reg sig; // 时钟生成 // 进程从 0ns 开始,每 5ns 循环 once always #5 clk = ~clk; // initial 块:进程从 0ns 开始 initial begin // 此系统任务在信号每次变化时打印值 $monitor("Time = %0t clk = %0d sig = %0d", $time, clk, sig); // 即激励,只是在仿真"延迟"后给变量赋不同值 sig = 0; #5 clk = 0; // 5ns 时将 clk 赋 0 #15 sig = 1; // 20ns 时将 sig 赋 1 (#5 + #15) #20 sig = 0; // 40ns 时将 sig 赋 0 (#5 + #15 + #20) #15 sig = 1; // 55ns 时将 sig 赋 1 (#5 + #15 + #20 + #15) #10 sig = 0; // 65ns 时将 sig 赋 0 (#5 + #15 + #20 + #15 + #10) #20 $finish; // 85ns 结束仿真 end endmodule
仿真器执行上述测试平台后输出:
仿真日志
ncsim> run Time = 0 clk = x sig = 0 Time = 5 clk = 0 sig = 0 Time = 10 clk = 1 sig = 0 Time = 15 clk = 0 sig = 0 Time = 20 clk = 1 sig = 1 Time = 25 clk = 0 sig = 1 Time = 30 clk = 1 sig = 1 Time = 35 clk = 0 sig = 1 Time = 40 clk = 1 sig = 0 Time = 45 clk = 0 sig = 0 Time = 50 clk = 1 sig = 0 Time = 55 clk = 0 sig = 1 Time = 60 clk = 1 sig = 1 Time = 65 clk = 0 sig = 0 Time = 70 clk = 1 sig = 0 Time = 75 clk = 0 sig = 0 Time = 80 clk = 1 sig = 0 Simulation complete via $finish(1) at time 85 NS + 0
什么是仿真波形?
仿真可将设计和测试平台信号导出为波形,以图形方式分析和调试 RTL 功能。下图波形来自 EDA 工具,显示各信号随时间变化,与前文时序图一致。

变量或网表值的每次变化称为更新事件。进程对更新事件敏感,当更新事件发生时求值该进程,称为求值事件。因多进程可能被任意顺序求值,变化顺序须在事件队列中跟踪。
事件队列自然按仿真时间排序。将新事件放入队列称为调度。仿真时间指仿真器维护的时间值,用于建模被仿真电路实际所需时间。上例时序图中时间值以纳秒 ns 为单位。
module des; wire abc; wire a, b, c; assign abc = a & b | c; // abc 通过 assign 语句(进程)在 a、b、c 任一变化时更新 -> 更新事件 endmodule

复习 Verilog 并查看示例!
事件队列区域
Verilog 事件队列逻辑上分为五个区域,事件可加入任一区域,但只能从活动区域移除。
| 事件 | 描述 |
|---|---|
| 活动 | 发生在当前仿真时间,可任意顺序处理 |
| 非活动 | 发生在当前仿真时间,但在所有活动事件处理完后处理 |
| 非阻塞赋值 | 在先前某时刻求值,但在当前仿真时间、活动和非活动事件完成后赋值 |
| 监视 | 在所有活动、非活动、非阻塞赋值事件完成后处理 |
| 未来 | 发生在未来某仿真时间 |
一个仿真周期即处理完所有活动事件。标准保证特定调度顺序,仅少数例外。例如 begin-end 块内语句仅按出现顺序执行。
module tb; reg [3:0] a; reg [3:0] b; initial begin // 语句在适当仿真时间按顺序执行 a = 5; // 0ns 时 a 赋 5 b = 2; // 同一仿真步骤(0ns)b 赋 2 #10 a = 7; // 仿真推进到 10ns 时 a 赋 7 end endmodule
事件队列规定赋值 b 应在赋值 a 之后发生。
Verilog
- Verilog 入门:数字电路设计完整教程
- 掌握 Verilog 拼接操作:实用指南
- 深入理解 Verilog 赋值语句:类型、语法与最佳实践
- Verilog阻塞与非阻塞赋值:关键区别详解
- 掌握Verilog函数:高效RTL设计的复用利器
- 理解Verilog任务:函数vs任务、语法与用法全解析
- Verilog `ifdef 条件编译详解:宏控制代码包含与排除
- 理解 Verilog 调度语义:时序、事件与执行顺序
- Verilog 时钟发生器设计与实现指南
- 掌握 Verilog 显示任务:高效调试与日志记录
- 掌握 Verilog 数学函数:高效硬件设计的利器
- 掌握 Verilog $timeformat:精度控制与显示格式化