4. 仿真验证基础:行为仿真、后仿真、Testbench编写、Vivado Simulator使用
仿真验证,说白了就是咱们在把代码烧到板子之前,先在电脑上跑一遍看看对不对。我刚开始做FPGA那会儿,总觉得仿真浪费时间,恨不得写完代码直接上板。结果呢?板子冒烟了都不知道是哪儿的问题。后来我学乖了——仿真不是可选项,是必选项。
这一章,咱们就聊聊Vivado里的仿真那点事。包括行为仿真、后仿真、怎么写Testbench,以及Vivado自带的仿真器怎么用。
4.1 行为仿真 vs 后仿真
先搞清楚两个概念:行为仿真和后仿真。很多人混着叫,其实差别挺大。
| 对比项 | 行为仿真 | 后仿真 |
|---|---|---|
| 仿真对象 | RTL代码(未综合) | 综合/实现后的网表 |
| 延时信息 | 无延时或单位延时 | 包含实际门级延时、布线延时 |
| 仿真速度 | 快(几秒到几分钟) | 慢(可能几十分钟甚至几小时) |
| 主要用途 | 验证逻辑功能正确性 | 验证时序是否满足、有无毛刺 |
| 是否需要SDF | 不需要 | 需要(标准延时格式文件) |
我个人习惯是:先做行为仿真,把功能调通。等综合实现之后,再做后仿真,看看时序有没有问题。你想想看,如果行为仿真都跑不通,后仿真就更别指望了。
核心观点:行为仿真验证的是“逻辑对不对”,后仿真验证的是“能不能跑得起来”。两者缺一不可。
4.2 Testbench编写——从零开始
Testbench,就是咱们给设计喂的“测试用例”。它本身不是可综合的代码,只是用来模拟外部激励。
我见过很多新手,Testbench写得比设计代码还复杂,结果仿真跑半天出不来。其实Testbench的核心就三件事:
- 例化设计模块——把咱们写的模块“装”进Testbench里
- 生成时钟和复位——给模块提供最基本的驱动
- 施加激励——模拟外部输入信号的变化
来,看个最简单的例子。假设咱们有一个计数器模块:
// 设计代码:counter.v
module counter (
input wire clk,
input wire rst_n,
output reg [7:0] cnt
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 8'd0;
else
cnt <= cnt + 1'b1;
end
endmodule
对应的Testbench怎么写?
// Testbench:tb_counter.v
`timescale 1ns / 1ps
module tb_counter;
// 信号声明
reg clk;
reg rst_n;
wire [7:0] cnt;
// 例化设计模块
counter u_counter (
.clk (clk),
.rst_n (rst_n),
.cnt (cnt)
);
// 生成时钟:周期10ns,即100MHz
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 生成复位:先拉低,再拉高
initial begin
rst_n = 0;
#100;
rst_n = 1;
end
// 观察波形
initial begin
#1000;
$finish;
end
endmodule
嗯,这里要注意几个细节:
`timescale一定要写,不然仿真器不知道时间单位forever循环里记得加延时,不然仿真器会死循环$finish用来结束仿真,不然它会一直跑下去
小技巧:我习惯在Testbench里加一个 $display 打印关键信息,比如复位完成、数据到达等。这样看仿真日志就能快速定位问题,不用每次都开波形窗口。
4.3 Vivado Simulator 使用指南
Vivado自带的仿真器叫XSim,功能其实挺全的。很多人一上来就装ModelSim或者Questa,其实对于中小型设计,XSim完全够用。
怎么用?两种方式:
4.3.1 图形界面操作
- 在Flow Navigator里点击 SIMULATION → Run Simulation
- 选择 Run Behavioral Simulation(行为仿真)或 Run Post-Implementation Timing Simulation(后仿真)
- Vivado会自动编译、加载Testbench,然后弹出波形窗口
- 在波形窗口里,可以添加信号、设置光标、测量时间等
我记得有一次,一个同事跑后仿真,等了半小时还没出来。后来发现他选了“Run Post-Implementation Functional Simulation”,那个是不带时序信息的,跑得很快。所以一定要看清楚选项。
4.3.2 Tcl命令行方式
如果你跟我一样,喜欢用命令行控制一切,那Tcl脚本是你的好帮手:
# 启动仿真
launch_simulation -mode behavioral -type behavioral
# 添加波形信号
add_wave /tb_counter/clk
add_wave /tb_counter/rst_n
add_wave /tb_counter/cnt
# 运行仿真
run 1000 ns
# 查看波形
open_wave_window
用Tcl的好处是,可以写成一个脚本,每次跑仿真直接source一下,省得点来点去。
4.4 仿真中的常见坑
做仿真这么多年,踩过的坑不少。挑几个典型的说说:
坑一:仿真通过,上板失败
我曾经遇到一个情况,行为仿真跑得完美,但上板后就是不对。后来发现是Testbench里用了 #0 延时,导致仿真器把信号当成“立即赋值”,但实际电路里没有这种操作。所以,Testbench里尽量用 #1、#5 这样的非零延时,模拟真实情况。
坑二:后仿真出现X态
后仿真时,如果看到波形里出现红色的X(未知态),别慌。先检查复位信号是否正常,再检查有没有未初始化的寄存器。我一般会在Testbench里加一句 initial $readmemh 来初始化RAM,避免X态传播。
坑三:仿真速度太慢
后仿真确实慢,但如果你发现行为仿真也慢,那可能是Testbench里写了太多 $display 或者 $monitor。这些打印语句会拖慢仿真速度。我建议只在关键节点打印,或者用 $fwrite 写到文件里,别往终端刷屏。
4.5 知识体系总览
说了这么多,咱们用一张图来总结一下仿真验证的核心逻辑:
这张图把咱们刚才讲的内容串起来了。从行为仿真到后仿真,再到Testbench的编写,最终都通过Vivado Simulator来执行,输出波形和日志。你想想看,是不是这个流程?
4.6 一点个人建议
最后,说点掏心窝子的话。仿真这件事,别指望一次搞定。我做了十几年FPGA,每次仿真还是会发现新问题。关键是养成好习惯:
- 每写完一个模块,立刻写Testbench跑行为仿真
- 综合实现后,至少跑一次后仿真,看看时序
- Testbench里加一些断言(assertion),自动检查结果,别全靠肉眼盯波形
嗯,这一章就到这儿。仿真验证是FPGA设计的“安”,千万别省。下一章咱们聊聊更深入的话题。
可可精品资料库