第八章 仿真与调试:Testbench编写、ModelSim/PangoSim联合仿真、Signal Tap逻辑分析仪使用、VIO虚拟IO调试

各位同学,欢迎来到第八章。这一章我们聊聊仿真和调试。说实话,这是FPGA开发中最磨人、但也最见功底的部分。我见过太多同学,代码写得飞快,一上板就跑飞,然后对着示波器发呆半天。嗯,其实很多问题,在仿真阶段就能发现。

我个人习惯是:写代码之前,先想好怎么测。你想想看,如果连测试方案都没想清楚,你怎么知道你的设计对不对?这一章,我们就来把这套流程走一遍。

8.1 Testbench编写——给设计搭个“测试台”

Testbench,说白了就是一段用来测试你设计的Verilog代码。它没有端口,也不需要综合。它的任务就是:产生激励、施加到你的设计上、然后检查输出。

我刚开始学的时候,总觉得Testbench随便写写就行。直到有一次,我写了一个UART接收模块,仿真时一切正常,上板后却死活收不到数据。查了两天,最后发现是Testbench里时钟周期写错了,比实际慢了10%。从那以后,我再也不敢小看Testbench了。

8.1.1 基本结构

一个标准的Testbench长这样:

module tb_led_controller();

// 1. 信号声明
reg        clk;
reg        rst_n;
wire [3:0] led;

// 2. 实例化待测模块
led_controller u_dut (
    .clk    (clk),
    .rst_n  (rst_n),
    .led    (led)
);

// 3. 时钟生成
initial begin
    clk = 0;
    forever #10 clk = ~clk;  // 50MHz时钟
end

// 4. 复位与激励
initial begin
    rst_n = 0;
    #100;
    rst_n = 1;
    #2000;
    $finish;
end

// 5. 波形输出
initial begin
    $dumpfile("wave.vcd");
    $dumpvars(0, tb_led_controller);
end

endmodule

这里有几个要点:

  • 时钟生成:用forever配合延时,简单高效。我个人习惯用always #10 clk = ~clk,效果一样。
  • 复位时序:先拉低复位,等几个时钟周期再释放。模拟真实上电过程。
  • 仿真结束:别忘了$finish,否则仿真会一直跑下去。

8.1.2 自检式Testbench

光看波形太累了。我建议你写自检式Testbench,让仿真自己报错:

initial begin
    // 等待复位完成
    @(posedge rst_n);
    repeat(10) @(posedge clk);
    
    // 发送测试数据
    data_in = 8'hA5;
    @(posedge clk);
    data_in = 8'h5A;
    
    // 检查结果
    #100;
    if (data_out !== 8'hA5) begin
        $display("ERROR: data mismatch at time %t", $time);
        $stop;
    end else begin
        $display("PASS: test case 1 passed");
    end
    
    #500;
    $finish;
end
我的小技巧:在Testbench里多用$display打印关键信息。仿真跑完,看一眼打印日志就知道对错,不用去翻波形。

8.2 ModelSim与PangoSim联合仿真

紫光同创的Pango Design Suite自带PangoSim仿真器。但说实话,ModelSim用起来更顺手,功能也更全。我个人习惯是:日常调试用ModelSim,最终验证用PangoSim。

8.2.1 ModelSim仿真流程

步骤很简单:

  1. 编译库文件:先把紫光同创的器件库编译到ModelSim里。这一步只需要做一次。
  2. 创建工程:把设计文件和Testbench加进去。
  3. 编译:检查语法错误。
  4. 仿真:设置运行时间,看波形。

这里有个坑:紫光同创的IP核会生成一些加密文件。直接用ModelSim编译会报错。我曾经在这上面浪费了半天。解决办法是:用PangoSim先跑一次,它会自动生成一个simlib文件夹,里面是解密后的库文件。把这个路径加到ModelSim的库映射里就行了。

8.2.2 PangoSim的使用

PangoSim是集成在Pango Design Suite里的。操作上更傻瓜化:

  • 在工程里右键,选择“Simulation”
  • 选择仿真顶层(就是你的Testbench)
  • 设置仿真时间,点击运行

PangoSim的波形界面和ModelSim很像。但有一点要注意:PangoSim的仿真速度比ModelSim慢不少。所以复杂设计我建议先用ModelSim调通,最后再用PangoSim做一次回归验证。

重要提醒:紫光同创官方要求,最终仿真结果必须来自PangoSim。因为只有它才能准确模拟器件内部的延时和时序。ModelSim的结果只能作为参考。

8.3 Signal Tap逻辑分析仪使用

仿真跑通了,上板后还是出问题?这时候就需要Signal Tap了。它相当于一个嵌入在FPGA内部的逻辑分析仪,可以实时抓取芯片内部的信号。

我记得第一次用Signal Tap时,觉得这东西太神奇了。不用接探头,不用焊线,直接在软件里选信号就能看波形。但后来发现,它也有自己的脾气。

8.3.1 基本配置

在Pango Design Suite里,添加Signal Tap的步骤:

  1. 打开工程,点击“Tools” -> “Signal Tap Logic Analyzer”
  2. 选择要观察的时钟信号(采样时钟)
  3. 设置采样深度(我一般设1024,够用)
  4. 添加要观察的信号
  5. 设置触发条件
  6. 下载配置,开始采集

8.3.2 避坑指南

我曾经犯过一个低级错误:把内部寄存器的信号加到Signal Tap里,结果发现波形全是乱的。查了半天,原来是采样时钟频率太低,发生了亚稳态。从那以后,我总结了几条经验:

  • 采样时钟要够快:至少是被测信号频率的2倍,最好5倍以上。
  • 不要贪多:一次抓太多信号,会占用大量BRAM资源。我一般一次抓8-16个信号。
  • 触发条件要精准:别设“always trigger”,否则抓到的都是无用数据。设一个你关心的条件,比如“当计数器等于100时触发”。

核心要点:Signal Tap不是万能的。它只能抓同步信号,异步信号抓出来可能不准。而且它本身会占用芯片资源,可能影响你的设计时序。所以,能用仿真解决的问题,尽量别上Signal Tap。

8.4 VIO虚拟IO调试

VIO(Virtual Input/Output)是Signal Tap的好搭档。它允许你在运行时,通过JTAG接口往FPGA内部写值,或者读取内部信号的状态。

你想想看,有时候你想手动改一个参数,看看系统怎么响应。如果每次都重新综合、重新下载,那得等到猴年马月。VIO就是干这个的。

8.4.1 VIO的使用场景

  • 手动控制:比如你想手动拉高某个使能信号,看看后续逻辑是否正常。
  • 参数调整:比如滤波器的系数,你可以通过VIO在线调整,找到最优值。
  • 状态监控:实时查看内部状态机的当前状态。

8.4.2 配置与使用

在Pango Design Suite里添加VIO:

  1. 在IP核列表里找到“VIO”
  2. 配置输入输出端口数量和位宽
  3. 在代码里实例化VIO模块
  4. 综合、下载后,在调试界面里操作

代码里实例化VIO大概是这样:

vio_0 u_vio (
    .clk        (clk),
    .probe_in0  (status_reg),   // 输入:监控内部状态
    .probe_out0 (ctrl_signal)   // 输出:手动控制信号
);
我的经验:VIO和Signal Tap配合使用效果最好。用VIO手动触发某个条件,然后用Signal Tap抓取触发后的波形。这样调试效率能提高一倍。

8.5 调试实战:一个完整的调试流程

说了这么多,我们来走一遍完整的调试流程。假设你写了一个PWM发生器,上板后灯不亮。

  1. 先检查仿真:跑一遍Testbench,看PWM波形是否正常。如果仿真都不对,别上板。
  2. 检查管脚约束:有时候灯不亮,只是因为管脚约束写错了。用Signal Tap抓一下输出管脚的电平。
  3. 检查时钟:用Signal Tap抓内部时钟,看有没有起振。我遇到过PLL锁不住的情况,折腾了好久。
  4. 检查复位:用VIO手动拉高复位,再释放,看系统能否正常启动。
  5. 逐级排查:从输出往回查,一级一级看信号。用Signal Tap抓PWM输出、计数器、分频器...

嗯,这套流程我用了好几年,基本能解决90%的问题。剩下的10%,要么是硬件问题,要么是...你该休息了。

最后提醒:调试时一定要有耐心。我曾经为了找一个时序问题,连续调了三天。最后发现是PCB布线导致信号反射。所以,当软件层面都查遍了还找不到问题,不妨想想硬件层面。

好了,这一章的内容就到这里。仿真和调试是FPGA开发的必修课,也是区分新手和老手的分水岭。多练、多踩坑、多总结,你也能成为调试高手。下一章我们聊聊时序约束,那是另一个让人又爱又恨的话题。