3、仿真与测试平台(Testbench)编写

仿真,说白了就是让代码先跑一遍。你想想看,写了几百行Verilog,直接上板子?我年轻时干过这事,结果板子冒烟了。从那以后,我养成了先仿真的习惯。仿真不花钱,烧芯片才心疼。

3.1 仿真概念:你到底在验证什么?

仿真就是给设计喂数据,看它吐出来的对不对。我们验证的是逻辑行为,不是物理特性。说白了,就是检查你的代码有没有按你想的干活。

我个人习惯把仿真分成两层:

  • 功能仿真:只看逻辑对不对,不考虑延迟。跑得快,适合早期调试。
  • 时序仿真:带上门延迟和布线延迟。慢,但更接近真实芯片。

我在项目中遇到过一件事:功能仿真全过,时序仿真全挂。原因是组合逻辑路径太长,信号没在时钟沿前稳定下来。嗯,这就是为什么我建议你两种仿真都要做。

核心原则:仿真通过 ≠ 上板没问题,但仿真不通过 = 上板一定有问题。

3.2 $display 和 $monitor:仿真界的printf

这两个系统函数,是调试的利器。$display 就像C语言的printf,执行一次打印一次。$monitor 则不同,只要监控的信号变了,它就自动打印。

看个例子:

initial begin
  // 只打印一次
  $display("仿真开始,时间 = %0t", $time);
  
  // 持续监控
  $monitor("时间 = %0t, clk = %b, data = %h", $time, clk, data);
end

我曾经犯过一个低级错误:在always块里放$monitor。结果每次触发都重新注册监控器,打印信息爆炸了。记住,$monitor一般只放在initial块里,调用一次就够了。

小技巧:用 %0t 打印时间,比 %t 更紧凑。用 %h 看十六进制,比 %b 省眼睛。

3.3 initial/always块在仿真中的使用

仿真里,initial块是老大。它只执行一次,适合做初始化、生成激励。always块则反复执行,适合生成时钟这类周期信号。

我常用的时钟生成模板:

// 时钟生成
initial begin
  clk = 0;
end

always #5 clk = ~clk;  // 周期10ns,100MHz

// 复位信号
initial begin
  rst_n = 0;
  #20;
  rst_n = 1;
  #100;
  rst_n = 0;
  #20;
  rst_n = 1;
end

注意,仿真里的initial块是顺序执行的。你写#10延时,它就真等10个时间单位。这和硬件里的行为完全不同,别搞混了。

避坑指南:我曾经在initial块里写了一个死循环,忘了加延时。仿真器直接卡死,我还以为是软件坏了。后来发现,是循环里没有#延时,仿真时间不推进,永远跑不完。

3.4 波形查看:Vivado Simulator vs ModelSim

波形是仿真的可视化结果。我个人偏爱ModelSim,但Vivado Simulator也够用。两者核心操作差不多:

操作 Vivado Simulator ModelSim
添加信号 右键 → Add to Wave 右键 → Add Wave
运行仿真 Run All / Run For run / run 100ns
查看内部信号 需要设置mark_debug 直接添加即可
波形导出 File → Export File → Save Format

我个人习惯:先用$display快速定位问题,再用波形细看时序。别一上来就盯着波形看,那太费眼了。

3.5 常见仿真错误:我踩过的坑

仿真报错,别慌。大部分错误就那几种:

  • 时间精度不匹配:`timescale 1ns/1ps 和 `timescale 1ns/100ps 混用,仿真结果对不上。统一用 1ns/1ps 最省事。
  • 信号未初始化:仿真默认值是'X'。你看到波形全是红色,八成是没给初值。在initial块里赋个0就好。
  • 组合逻辑环路:always @(*) 里写了 a = a + 1,仿真直接崩。组合逻辑不能有反馈,这是常识。
  • 仿真时间不够:跑10ns就停了,复位还没结束。我建议至少跑够100个时钟周期。

我的经验:仿真报错时,先看第一条错误。后面的错误往往是连锁反应,第一条修好了,后面的自动消失。

3.6 本章知识体系

下面这张图,帮你理清仿真测试的脉络:

仿真与测试平台知识体系 Testbench 编写 仿真概念 功能仿真 vs 时序仿真 $display / $monitor 一次性打印 vs 持续监控 initial / always 激励生成 / 时钟产生 波形查看 Vivado / ModelSim 常见仿真错误 时间精度 / 未初始化 / 环路 避坑指南 经验总结 / 实战技巧 仿真通过 ≠ 上板没问题,但仿真不通过 = 上板一定有问题

这张图把仿真测试的核心内容串起来了。你写Testbench时,就照着这个框架来:先搞清楚要验证什么,再用$display和波形看结果,最后避开那些常见坑。

最后说一句:仿真是个熟能生巧的活。你多写几个Testbench,自然就找到感觉了。别怕报错,报错是好事,总比上板烧芯片强。

专注资料整理