SystemVerilog语法(9)-验证基础与简单Testbench

📌 本篇导读

知识点 难度 实战价值 典型场景
initial 过程块 ⭐⭐⭐⭐⭐ 测试激励生成、初始化
$display / $write ⭐⭐⭐⭐⭐ 打印信息、调试
$monitor / $strobe ⭐⭐ ⭐⭐⭐⭐ 自动跟踪信号变化
时间控制(#@repeat ⭐⭐ ⭐⭐⭐⭐⭐ 延迟、事件同步
$finish / $stop ⭐⭐⭐⭐ 控制仿真结束
时钟生成模板 ⭐⭐ ⭐⭐⭐⭐⭐ always + # 生成时钟
复位生成模板 ⭐⭐⭐⭐⭐ 异步/同步复位序列
force / release ⭐⭐ ⭐⭐⭐⭐ 强制赋值、故障注入
$dumpvars / $dumpfile ⭐⭐⭐⭐ 生成波形文件
简单 Testbench 结构 ⭐⭐ ⭐⭐⭐⭐⭐ 顶层封装、DUT例化

💡 常见误区

  • #10@(posedge clk) 混用导致时序错位
  • $display$strobe 在非阻塞赋值后的显示差异
  • 忘记用 $finish 导致仿真永不结束
  • force 后忘记 release 导致后续测试异常
  • 在多时钟域中时间单位设置不合理

🧭 本文使用 【综合】 标识电路设计相关语法,使用 【验证】 标识测试与仿真专用语法。本章主要面向验证,大量语法不可综合。


1 验证与 Testbench 概述

Testbench 是对设计(DUT)进行功能验证的仿真环境,通常不综合,但会:

  • 生成时钟和复位信号
  • 产生输入激励数据
  • 接收并检查 DUT 输出
  • 报告测试结果

最简单的 Testbench 结构:


2 initial 过程块【验证】

initial 块在仿真时间 0 时刻 仅执行一次,是 Testbench 核心激励描述方式。

systemverilog 复制代码
initial begin
    $display("仿真开始 @ %0t", $time);
    #10  rst_n = 1;      // 10 ns 后释放复位
    #100 $display("完成初始序列");
    #20  $finish;        // 结束仿真
end

多个 initial并行执行,均从 0 时刻启动。

systemverilog 复制代码
initial begin
    clk = 0;
    forever #5 clk = ~clk;
end

initial begin
    #100 $display("时钟已运行 100 个时间单位");
end

3.1 $display$write

函数 行为 示例
$display 自动换行 $display("a=%0d", a);
$write 不换行 $write(".");
$display(带格式) 支持 %d/%h/%b/%s/%t/%p $display("data=0x%h", data);

常用格式符

  • %d:十进制;%0d:去掉前导空格(仿真器扩展)
  • %h/%0h:十六进制
  • %b:二进制
  • %s:字符串
  • %t:仿真时间(配合 $time
  • %m:当前模块层次路径
  • %p打印聚合数据类型(数组、队列、结构体、联合体、类等),自动展开层次结构,非常适合调试

示例

systemverilog 复制代码
module tb_print;
    initial begin
        // 变量声明
        logic [7:0] val = 8'hA5;
        int         arr[] = '{1, 2, 3};
        struct { logic [3:0] a; logic b; } s = '{4'hF, 1'b1};
        
        $display("======== 仿真开始 ========");
        
        // 基本格式符
        $display("原始: val=%d, hex=%h, bin=%b", val, val, val);
        // 输出: 原始: val=165, hex=a5, bin=10100101
        $display("无前导零: %0d, %0h", val, val);
        // 输出: 无前导零: 165, a5

        // %p 聚合数据类型
        $display("arr = %p", arr);
        // 输出: arr = '{1, 2, 3}
        $display("s = %p", s);
        // 输出: s = '{a:15, b:1}

        $display("======== 仿真结束 ========");
        $finish;
    end
endmodule

仿真打印:


3.2 $monitor 自动监视

信号变化时自动打印;全局仅一个活跃 $monitor,后调用覆盖前。

systemverilog 复制代码
initial begin
    $monitor("TIME=%0t, clk=%b, data=%h", $time, clk, data);
end

3.3 $strobe 同步打印

当前时间所有赋值完成后执行,适合观察非阻塞赋值最终值。

systemverilog 复制代码
always_ff @(posedge clk) begin
    q <= d;
    $display("display: q=%0d", q);   // 显示旧值
    $strobe ("strobe: q=%0d", q);    // 显示新值
end

3.4 $time / $realtime

  • $time:按 timescale 舍入为整数
  • $realtime:保留小数的实数时间
systemverilog 复制代码
`timescale 1ns/1ps
initial begin
    #1.55;
    $display("$time = %0d ns", $time);        // 输出 2
    $display("$realtime = %0t ns", $realtime); // 输出 1.55
end

4 时间控制与等待【验证】

4.1 # 延迟控制

systemverilog 复制代码
#10;           // 延迟 10 个时间单位
#(5.3);        // 延迟 5.3(需精度匹配)
#(a + b);      // 延迟表达式(验证可用变量)

4.2 @ 边沿敏感

systemverilog 复制代码
@(posedge clk);    // 时钟上升沿
@(negedge rst_n);  // 复位下降沿
@(clk);             // 任意变化(不推荐)

4.3 repeat 重复等待

systemverilog 复制代码
repeat(5) @(posedge clk); // 等待 5 个时钟上升沿

4.4 wait 电平敏感

systemverilog 复制代码
wait (ready == 1);        // 等待高电平
wait (busy == 0) @(posedge clk); // 组合使用

4.5 event 自定义事件

systemverilog 复制代码
event start_sim;          // 声明事件
initial begin
    -> start_sim;         // 触发事件
end
initial begin
    @(start_sim);         // 等待触发
    $display("事件已收到");
end

好的,优化后的内容如下,增加工业界主流的 VCS + FSDB + Verdi 流程,同时保留通用的 VCD 方案作为参考。


5 仿真控制系统函数 【验证】

函数 行为
$finish 结束仿真,退出仿真器
$stop 暂停仿真,进入交互模式

5.1 波形文件生成

方案一:通用 VCD
systemverilog 复制代码
initial begin
    $dumpfile("wave.vcd");      // 指定波形文件名
    $dumpvars(0, tb_top);       // 记录 tb_top 及其所有子模块信号
    #1000 $finish;
end
方案二:VCS + FSDB + Verdi

在 Synopsys VCS 环境中,通常使用 FSDB(Fast Signal Database) 格式配合 Verdi 调试波形,速度更快、压缩比更高、支持信号追溯。

systemverilog 复制代码
initial begin
    $fsdbDumpfile("wave.fsdb"); // 指定 FSDB 文件名
    $fsdbDumpvars(0, tb_top);   // 记录所有层次信号
    #1000 $finish;
end

编译与运行(VCS 命令行):

bash 复制代码
# 编译时需加 -fsdb 选项,并链接 Verdi 库
vcs -full64 -sverilog -fsdb tb.sv -o simv

# 运行仿真,生成 wave.fsdb
./simv

# 用 Verdi 查看波形
verdi -ssf wave.fsdb &

常用 FSDB 函数扩展

函数 行为
$fsdbDumpfile("xxx.fsdb") 指定 FSDB 文件名
$fsdbDumpvars(0, top) 记录所有层次信号
$fsdbDumpvars(1, top.sub) 仅记录指定模块及其子层次
$fsdbDumpon / $fsdbDumpoff 动态开启/关闭波形记录

6 时钟与复位生成模板

6.1 时钟生成

systemverilog 复制代码
`timescale 1ns/1ps
module tb;
    logic clk;

    // 50% 占空比
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    // 等价写法
    always #5 clk = ~clk;
endmodule

相位偏移时钟

systemverilog 复制代码
initial begin
    clk = 0;
    #2.5; // 偏移 2.5ns
    forever #5 clk = ~clk;
end

6.2 复位生成

异步复位

systemverilog 复制代码
initial begin
    rst_n = 0;
    #20;
    rst_n = 1;
    #100;
    rst_n = 0; // 可选
    #20 rst_n = 1;
end

同步复位(推荐)

systemverilog 复制代码
initial begin
    rst_n = 0;
    repeat(3) @(posedge clk);
    rst_n = 1;
end

7 简单 Testbench 完整结构

DUT(8位计数器)

systemverilog 复制代码
`timescale 1ns/1ps
module counter #(parameter WIDTH=8) (
    input  logic clk,
    input  logic rst_n,
    input  logic en,
    output logic [WIDTH-1:0] cnt
);
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt <= 0;
        else if (en)
            cnt <= cnt + 1;
    end
endmodule

Testbench

systemverilog 复制代码
`timescale 1ns/1ps
module tb_counter;
    logic        clk, rst_n, en;
    logic [7:0]  cnt;

    // 例化 DUT
    counter #(.WIDTH(8)) dut (
        .clk   (clk),
        .rst_n (rst_n),
        .en    (en),
        .cnt   (cnt)
    );

    // 时钟
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

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

    // 激励与检查
    initial begin
        en = 0;
        wait(rst_n == 1);
        @(posedge clk);
        repeat(2) @(posedge clk);

        en = 1;
        repeat(10) @(posedge clk);
        if (cnt == 10)
            $display("[PASS] 计数到 10");
        else
            $display("[FAIL] 计数错误: %0d", cnt);

        en = 0;
        repeat(3) @(posedge clk);
        if (cnt === 10)
            $display("[PASS] 计数保持");
        else
            $display("[FAIL] 计数改变");

        rst_n = 0;
        @(negedge clk);
        if (cnt === 0)
            $display("[PASS] 复位清零");
        else
            $display("[FAIL] 复位错误");

        #50;
        $display("=== 仿真完成 ===");
        $finish;
    end

    // 波形
    initial begin
        $fsdbDumpfile("wave.fsdb");
        $fsdbDumpvars(0, tb_counter);
    end

    // 监视器
    initial begin
        $monitor("TIME=%0t, en=%b, cnt=%0d", $time, en, cnt);
    end
endmodule

仿真输出:


8 force / release【验证】

仅仿真有效,用于故障注入/调试。

systemverilog 复制代码
initial begin
    #100;
    force dut.cnt = 8'hFF;
    #20;
    release dut.cnt;
end

⚠️ :不可综合,仅用于验证


9 timescale 时间尺度【验证】

systemverilog 复制代码
`timescale <单位> / <精度>

常见设置:

  • 1ns/1ps:单位 1ns,精度 1ps
  • 10ns/1ns:单位 10ns,精度 1ns
  • 1ps/1ps:单位 1ps

示例:

systemverilog 复制代码
`timescale 1ns/1ps
initial begin
    #1.55;
    #1.555;
end

10 序列检测器仿真示例

DUT

systemverilog 复制代码
`timescale 1ns/1ps

module seq_detector (
    input  logic clk,
    input  logic rst_n,
    input  logic din,
    output logic dout
);
    typedef enum logic [2:0] { S0=0, S1=1, S2=3, S3=2, S4=6 } state_t;
    state_t cur, nxt;

    always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) cur <= S0;
        else cur <= nxt;

    always_comb begin
        nxt = cur;
        case (cur)
            S0: nxt = din ? S1 : S0;
            S1: nxt = din ? S2 : S0;
            S2: nxt = din ? S2 : S3;
            S3: nxt = din ? S4 : S0;
            S4: nxt = S0;
            default: nxt = S0;
        endcase
    end
    assign dout = (cur == S4);
endmodule

Testbench

systemverilog 复制代码
`timescale 1ns/1ps
module tb_seq;
    logic clk, rst_n, din, dout;
    seq_detector dut (.*);

    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    initial begin
        $display("======== Simulation Start ========");
        rst_n = 0; din = 0;
        $display("[%0t] Reset asserted (rst_n=0)", $time);

        #20 rst_n = 1;
        $display("[%0t] Reset released (rst_n=1)", $time);

        // 发送序列 1,1,0,1
        @(posedge clk);
        din = 1;
        $display("[%0t] Send din = 1", $time);

        @(posedge clk);
        din = 1;
        $display("[%0t] Send din = 1", $time);

        @(posedge clk);
        din = 0;
        $display("[%0t] Send din = 0", $time);

        @(posedge clk);
        din = 1;
        $display("[%0t] Send din = 1", $time);

        @(posedge clk);
        din = 0;   // 停止输入
        $display("[%0t] Input stopped", $time);

        #10;  // 等待输出稳定
        $display("[%0t] Check output: dout = %b", $time, dout);
        if (dout === 1) $display("PASS: 1101 detected");
        else $display("FAIL: 1101 test");

        #50;
        $display("======== Simulation Stop ========");
        $finish;
    end

    //initial begin
    //    $fsdbDumpfile("wave.fsdb");
    //    $fsdbDumpvars(0, tb_seq);
    //end

endmodule

仿真输出:


11 常见错误与编码建议

  1. 忘记 timescale → 所有文件开头统一写 ```timescale 1ns/1ps``
  2. $display/$strobe 混用 → 非阻塞赋值用 $strobe
  3. 时钟无初值initial clk=0; 配合 always #5 clk=~clk
  4. 复位与时钟沿对齐风险 → 同步复位用 @(posedge clk) rst_n=1;
  5. force 不释放 → 必须配对 release
  6. $monitor 多实例覆盖 → 全局仅设一个
  7. 仿真不结束 → 测试完成后调用 $finish


📢 关于文章 :原创分享,转载需注明出处。

🔔 点击关注,第一时间收到后续内容推送(第10篇:面向对象编程基础)。
📚 参考文档IEEE Standard for SystemVerilog

相关推荐
kaizq2 小时前
MuleRun助力MakerChip-FPGA在线编程模拟仿真操练
fpga开发·verilog·龙虾机器人·mulerun·makerchip·在线模拟仿真
c-u-r-ry302 小时前
vivado处理硬件设计差分对布线极性翻转的问题
经验分享·fpga开发
XINVRY-FPGA2 小时前
XC7Z020-2CLG484I Xilinx Zynq-7000 SoC FPGA
嵌入式硬件·fpga开发·云计算·硬件工程·fpga
小眼睛FPGA2 小时前
【紫光HiYou开源入门轻量级PCIE开发板PG2L25G】实验例程2-基于紫光FPGA 的键控流水灯实验例程
fpga开发
XMAIPC_Robot2 小时前
电力设备RK3568/RK3576+FPGA,多系统混合部署Linux+RTOS RT-THREAD,强实时性
linux·运维·fpga开发
XMAIPC_Robot3 小时前
RK3588 PLC AMP 核隔离配置 + RT‑Thread 实时优化 + FPGA 接口定义 + CODESYS 工程
人工智能·嵌入式硬件·深度学习·fpga开发
Darth Nihilus3 小时前
Horizon Journey 5 Evaluation and Development Kit(四)
嵌入式硬件·汽车
yong99903 小时前
STC15W4K32S4系列单片机驱动nRF24L01 2.4G无线接收方案
单片机·嵌入式硬件
淘晶驰AK3 小时前
农业物联网 / 温室:组态屏监控系统搭建教程
嵌入式硬件