从近似0基础开始FPGA开发 -- part.2 仿真

Testbench的编写

在了解了时钟之后,为了方便时序概念的建立,本章介绍fpga开发过程中的仿真部分。在时钟介绍章节,通过将编译完成的bit下载到开发板上观察流水灯闪烁可以判断工程功能的正确性,但在复杂的工程中,采用硬件调试并不能实现快速定位,因此通过仿真工具进行rtl代码功能正确性的验证,提升开发效率。

Vivado添加仿真文件

以时钟章节的工程为例,提供一段仿真代码参考

复制代码
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2026/08/20 15:00:36
// Design Name: 
// Module Name: clk_wizard_TB
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////
module clk_wizard_TB;
reg sysclk_p; //时钟信号
reg sysclk_n;
wire [1:0] up_led; //仿真的 LED 信号

clk_wizard_ip u_clk_wizard_ip(
.I_sysclk_p(sysclk_p),
.I_sysclk_n(sysclk_n),
.O_up_led(up_led)
);
initial
begin
sysclk_p = 1'b0;
sysclk_n = 1'b1;
end
///////////////////////////////////////////////////////////////////
//
// Clock generation
//
always #20 sysclk_p = ~sysclk_p;  //duty cycle 50%, period 20 ns;
always #20 sysclk_n = ~sysclk_n;
Endmodule

一个完整的 TB 通常包含以下 5 个核心部分:

**模块声明:**TB 的模块没有输入输出端口(上述代码中的module clk_wizard_TB);

信号声明: 定义与 DUT (被测试的设计)连接的信号(用 reg 给 DUT 提供输入,用 wire 接收 DUT 的输出);

**DUT 实例化:**将你的顶层设计像元器件一样例化进来并连线(上述代码中例化了clk_wizard_ip这个模块)

**产生激励:**使用 initial 或 always 过程块生成时钟、复位和测试数据(上述代码中定义sysclk)。

检查与监控:使用 monitor、display 打印信息,或使用 assert 进行自动比对(高级用法)

完成正确的TB编写后可以看到被测模块在层级上归属到TB文件下一级,即被TB调用。

通用模板*

这里给出一份通用的仿真文件编写模板供参考

复制代码
//////////////////////////////////////////////////////////////////////////////
// Testbench 模板
//////////////////////////////////////////////////////////////////////////////
`timescale 1ns / 1ps          // 时间单位/精度定义

module tb_example();          // 1. 空模块(无端口)

    // 2. 信号声明
    reg         clk;          // 给 DUT 的输入用 reg
    reg         rst_n;        // 给 DUT 的输入用 reg
    reg         data_in;
    wire        data_out;     // 接收 DUT 的输出用 wire

    // -------------------------------------------------------------
    // 3. 实例化被测试模块 (DUT)
    // -------------------------------------------------------------
    your_top_module u_dut (
        .clk        (clk),
        .rst_n      (rst_n),
        .data_in    (data_in),
        .data_out   (data_out)
    );

    // -------------------------------------------------------------
    // 4. 产生时钟 (Clock Generation)
    // -------------------------------------------------------------
    // 方法:使用 always 反转,周期为 10ns (即 100MHz)
    always #5 clk = ~clk;     // 每 5ns 反转一次 -> 周期 10ns

    // -------------------------------------------------------------
    // 5. 产生激励 (Stimulus)
    // -------------------------------------------------------------
    initial begin
        // 5.1 初始化所有输入为 0,防止仿真出现 X 态
        clk = 0;
        rst_n = 0;
        data_in = 0;

        // 5.2 等待时钟稳定,释放复位 (通常等 5~10 个周期)
        repeat(5) @(posedge clk); // 等待 5 个上升沿
        rst_n = 1;               // 释放复位 (低有效)
        
        // 5.3 注入测试数据
        repeat(10) @(posedge clk);
        data_in = 1'b1;
        
        repeat(5) @(posedge clk);
        data_in = 1'b0;

        // 5.4 等待一段时间后结束仿真
        repeat(20) @(posedge clk);
        $display("Testbench finished successfully.");
        $finish;                 // 结束仿真
    end

    // -------------------------------------------------------------
    // 6. (可选) 波形转储与实时打印
    // -------------------------------------------------------------
    initial begin
        $dumpfile("waveform.vcd"); // 生成 VCD 波形文件
        $dumpvars(0, tb_example);  // 记录所有层级信号
        $monitor("Time=%0t, clk=%b, rst=%b, out=%b", 
                  $time, clk, rst_n, data_out);
    end

endmodule

运行仿真

在完成仿真工程的构建后,通过侧边栏,点击 Run Behavioral Simulation,即可进行rtl功能仿真。

附上不同仿真模式的行为差异,正常按照行为仿真 -> 综合后功能仿真 -> 实现后时序仿真的顺序进行;当然考虑效率也可以行为仿真完成,确认功能无误后直接编bit上板卡调试。

Vivado内置的Vivado Simulator界面如下

Obeject下会根据选定的层级展示该层级下的所有数字信号

通过右键即可添加信号到时序界面。在Vivado Simulator下,添加的信号并不会直接展示仿真结果,需要在添加完成后重新运行才能看到对应时序结果。而ModelSim添加信号可以实时显示,如何安装ModelSim以及配置Vivado调用可以自行百度,教程很多。

添加全部想要观察的信号后,通过工具栏运行/暂停仿真,由于TB中没有添加stop语句,因此开始运行仿真后会持续运行。

在工程代码中,led选取的时cnt25:24两位,在实际仿真中会耗费非常长的等待时间,因此可将工程代码中的led赋值改为cnt5:4,可以快速看到目标信号的翻转。

代码中cnt寄存器的累加发生在clk0时钟的上升沿,与时序图相对应

这里放一组实现后时序仿真的时序图作为对比,可以看到信号翻转并不是在基于理想情况下的在时钟上升沿同步翻转,这些布局布线导致的时延在复杂工程下就可能会导致竞争冒险。

Fin

在介绍完了rtl代码的仿真自查后,下一篇文章介绍比特流的上板调试以及ila IP的使用,这也是一般做FPGA项目开发的流程。

相关推荐
吉他英雄一里3 小时前
zynq入门教程
fpga开发·zynq
XMAIPC_Robot5 小时前
RK3576/RK3588+FPGA+AI数据采集系统:实时性与边缘AI算力兼顾
人工智能·fpga开发·机器人·数据采集·rk3588+fpga·rk3576+fpga
156082072191 天前
JFMRFVU3P5G的bit正常,但烧录mcs程序后加载异常
fpga开发
狂奔蜗牛(bradley)1 天前
把 EtherCAT 初始化从 FPGA 搬到 ARM:命令通道的接口设计与11个坑
arm开发·人工智能·fpga开发·架构
女神下凡2 天前
芯参谋(37):EEPROM_电路设计指南
arm开发·单片机·嵌入式硬件·fpga开发·设计规范
szxinmai主板定制专家3 天前
工业视觉新思路:FPGA图像预处理+RK NPU推理,实现高速缺陷检测
人工智能·嵌入式硬件·fpga开发·rk3576+codesys
FPGA小徐3 天前
【硬件实战】立创 EDA 绘制 TF/SD 卡模块电路|SPI 模式原理图 + 阻抗匹配 + PCB 布局布线全教程
单片机·嵌入式硬件·fpga开发
狂奔蜗牛(bradley)3 天前
EtherCAT DC 分布式时钟同步的 ModelSim 仿真:从帧模板到从站行为建模
人工智能·嵌入式硬件·fpga开发·架构
ALINX技术博客4 天前
8 通道同步采样 ADC!AN706 波形显示实验 【FPGA黑金云课堂】
fpga开发
szxinmai主板定制专家4 天前
RK3568+FPGA+CODESYS异构控制方案:赋能半导体设备与工业自动化升级
人工智能·fpga开发·自动化·rk3576·半导体设备