4. 仿真验证基础:行为仿真、后仿真、Testbench编写、Vivado Simulator使用

仿真验证,说白了就是咱们在把代码烧到板子之前,先在电脑上跑一遍看看对不对。我刚开始做FPGA那会儿,总觉得仿真浪费时间,恨不得写完代码直接上板。结果呢?板子冒烟了都不知道是哪儿的问题。后来我学乖了——仿真不是可选项,是必选项。

这一章,咱们就聊聊Vivado里的仿真那点事。包括行为仿真、后仿真、怎么写Testbench,以及Vivado自带的仿真器怎么用。

4.1 行为仿真 vs 后仿真

先搞清楚两个概念:行为仿真和后仿真。很多人混着叫,其实差别挺大。

对比项 行为仿真 后仿真
仿真对象 RTL代码(未综合) 综合/实现后的网表
延时信息 无延时或单位延时 包含实际门级延时、布线延时
仿真速度 快(几秒到几分钟) 慢(可能几十分钟甚至几小时)
主要用途 验证逻辑功能正确性 验证时序是否满足、有无毛刺
是否需要SDF 不需要 需要(标准延时格式文件)

我个人习惯是:先做行为仿真,把功能调通。等综合实现之后,再做后仿真,看看时序有没有问题。你想想看,如果行为仿真都跑不通,后仿真就更别指望了。

核心观点:行为仿真验证的是“逻辑对不对”,后仿真验证的是“能不能跑得起来”。两者缺一不可。

4.2 Testbench编写——从零开始

Testbench,就是咱们给设计喂的“测试用例”。它本身不是可综合的代码,只是用来模拟外部激励。

我见过很多新手,Testbench写得比设计代码还复杂,结果仿真跑半天出不来。其实Testbench的核心就三件事:

  1. 例化设计模块——把咱们写的模块“装”进Testbench里
  2. 生成时钟和复位——给模块提供最基本的驱动
  3. 施加激励——模拟外部输入信号的变化

来,看个最简单的例子。假设咱们有一个计数器模块:

// 设计代码: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 图形界面操作

  1. 在Flow Navigator里点击 SIMULATION → Run Simulation
  2. 选择 Run Behavioral Simulation(行为仿真)或 Run Post-Implementation Timing Simulation(后仿真)
  3. Vivado会自动编译、加载Testbench,然后弹出波形窗口
  4. 在波形窗口里,可以添加信号、设置光标、测量时间等

我记得有一次,一个同事跑后仿真,等了半小时还没出来。后来发现他选了“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 激励生成与检查 RTL代码 + 无延时 网表 + SDF文件 时钟/复位/激励 Vivado Simulator (XSim) 波形 / 日志 / 断言结果

这张图把咱们刚才讲的内容串起来了。从行为仿真到后仿真,再到Testbench的编写,最终都通过Vivado Simulator来执行,输出波形和日志。你想想看,是不是这个流程?

4.6 一点个人建议

最后,说点掏心窝子的话。仿真这件事,别指望一次搞定。我做了十几年FPGA,每次仿真还是会发现新问题。关键是养成好习惯:

  • 每写完一个模块,立刻写Testbench跑行为仿真
  • 综合实现后,至少跑一次后仿真,看看时序
  • Testbench里加一些断言(assertion),自动检查结果,别全靠肉眼盯波形

嗯,这一章就到这儿。仿真验证是FPGA设计的“安”,千万别省。下一章咱们聊聊更深入的话题。

可可精品资料库