---
title: 手把手教你用SystemVerilog，为FPGA验证搭个智能裁判（记分板）
url: "https://www.fpgalearn.com/29354.html"
kind: article
id: 29354
excerpt: 在FPGA开发的世界里，功能验证就像是给设计做“全面体检”，是确保一切运行如意的关键。当设计越来越复杂，靠人工一个个去核对输出？那简直是大海捞针，效率低还容易看…
published_at: 2026-04-07
author: FPGA小白
tags: FPGA验证, 记分板
word_count: 4486
source: "https://www.fpgalearn.com"
---

# 手把手教你用SystemVerilog，为FPGA验证搭个智能裁判（记分板）

> 标签：FPGA验证、记分板
> 在FPGA开发的世界里，功能验证就像是给设计做“全面体检”，是确保一切运行如意的关键。当设计越来越复杂，靠人工一个个去核对输出？那简直是大海捞针，效率低还容易看…

> 在FPGA开发的世界里，功能验证就像是给设计做“全面体检”，是确保一切运行如意的关键。当设计越来越复杂，靠人工一个个去核对输出？那简直是大海捞针，效率低还容易看…

在FPGA开发的世界里，功能验证就像是给设计做“全面体检”，是确保一切运行如意的关键。当设计越来越复杂，靠人工一个个去核对输出？那简直是大海捞针，效率低还容易看花眼。这时候，基于约束的随机验证（CRV）就成了我们的主力军，而记分板（Scoreboard），就是这支军队里最公正的“智能裁判”。
它能够自动比对设计的实际输出和预期结果，让验证效率飙升，可靠性也大大增强。今天，我们就一起用SystemVerilog，动手在FPGA验证环境中搭建一个既简单又实用的记分板，感受一下自动化验证的魅力。
### 一、为什么我们需要一个“记分板”？
想象一下，你在验证一个数据通路（比如FIFO、DMA或者图像处理流水线）。测试平台（Testbench）会像连珠炮一样，向待测设计（DUT）发送海量的随机数据。如果每个输出都得你瞪大眼睛看波形，或者在一堆打印信息里手动查找，那得多累啊，而且保不准就会出错。
记分板就是来解放你的！它的核心价值有三点：
- **自动化比较：**实时或事后自动对比“实际输出”和“预期模型输出”，完全不用你操心。
- **错误精确定位：**一旦发现不匹配，立刻报告错误发生的时间、具体数据和上下文，调试效率直接拉满。
- **覆盖率驱动：**它可以和功能覆盖率模型联手，指导随机测试的生成，确保你的验证能覆盖到各种边边角角。
### 二、用SystemVerilog打造记分板，需要哪些“零件”？
一个典型的记分板，我们可以利用SystemVerilog面向对象（OOP）的特性，像搭积木一样把它构建出来。主要包含三部分：
- **预测模型（Reference Model）：**你可以把它理解为一个“黄金标准”或“参考答案”。它接收和DUT一样的输入，然后用更高抽象级别的方式，计算出DUT**应该**输出的结果。
- **数据容器（Mailbox或Queue）：**用来临时存放预测模型算好的“预期数据”。SystemVerilog提供的mailbox或者queue特别好用，能轻松搞定线程之间的数据传递和同步问题。
- **比较器（Comparator）：**这是记分板的核心“大脑”。它从容器里取出“预期数据”，再和从DUT那里监测到的“实际数据”进行比对，然后给出“对”或“错”的判决。
### 三、实战开始：给一个简易AXI-Stream FIFO配个裁判
理论说再多，不如动手敲一敲。假设我们有一个带延迟的AXI-Stream接口FIFO模块，现在就来为它量身定制一个记分板，验证数据传得对不对。
#### 步骤1：定义“事务”类——数据的基本单元
在验证环境中，数据通常被打包成“事务”来处理。我们先定义好它长什么样：
```
class axi_stream_transaction;
    rand bit [31:0] tdata;  // 数据本身
    rand bit [3:0]  tkeep;  // 字节有效指示
    rand bit        tlast;  // 包结束标志
    int             id;     // 给事务加个ID，方便调试跟踪

    function void display(string prefix="");
        $display("%sTime=%0t, ID=%0d, DATA=0x%h, LAST=%b",
                 prefix, $time, id, tdata, tlast);
    endfunction
endclass
```
#### 步骤2：构建“记分板”类——我们的智能裁判
重头戏来了，我们来搭建裁判本体：
```
class scoreboard;
    // 用两个“邮箱”分别存预期和实际的事务。参数‘0’表示邮箱无限大。
    mailbox #(axi_stream_transaction) exp_mbx;
    mailbox #(axi_stream_transaction) act_mbx;
    int match_count, mismatch_count; // 记录对比结果

    // 构造函数，初始化一切
    function new();
        exp_mbx = new();
        act_mbx = new();
        match_count = 0;
        mismatch_count = 0;
    endfunction

    // 任务：把预测模型算好的事务，放进“预期邮箱”
    task put_expected(axi_stream_transaction exp_tr);
        exp_mbx.put(exp_tr);
    endtask

    // 任务：把从DUT监测到的事务，放进“实际邮箱”
    task put_actual(axi_stream_transaction act_tr);
        act_mbx.put(act_tr);
    endtask

    // 核心比较任务！通常它会作为一个独立的线程一直运行
    task run();
        axi_stream_transaction exp_tr, act_tr;
        forever begin
            // 等待两个邮箱里都有数据（这里假设数据顺序一致）
            // 注意：如果DUT会乱序输出，就需要更复杂的匹配机制，比如用ID来配对。
            exp_mbx.get(exp_tr);
            act_mbx.get(act_tr);
            compare(exp_tr, act_tr);
        end
    endtask

    // 具体的比较函数
    function void compare(axi_stream_transaction exp, act);
        if (exp.tdata === act.tdata && exp.tlast === act.tlast) begin
            match_count++;
            `uvm_info("SCOREBOARD", $sformatf("Match! ID=%0d, EXP_DATA=0x%h, ACT_DATA=0x%h",
                                               exp.id, exp.tdata, act.tdata), UVM_LOW)
        end else begin
            mismatch_count++;
            `uvm_error("SCOREBOARD",
                       $sformatf("Mismatch! Time=%0t, ID=%0dn", $time, exp.id) +
                       $sformatf("  Expected: DATA=0x%h, LAST=%bn", exp.tdata, exp.tlast) +
                       $sformatf("  Actual:   DATA=0x%h, LAST=%b", act.tdata, act.tlast))
        end
    endfunction

    // 测试结束后，打印一份漂亮的最终报告
    function void report();
        $display("n=== SCOREBOARD FINAL REPORT ===");
        $display("Total Comparisons: %0d", match_count + mismatch_count);
        $display("Matches: %0d", match_count);
        $display("Mismatches: %0d", mismatch_count);
        if (mismatch_count == 0) begin
            $display("*** TEST PASSED ***");
        end else begin
            $display("*** TEST FAILED ***");
        end
        $display("============================n");
    endfunction
endclass
```
#### 步骤3：在测试平台中集成——让裁判开始工作
最后，我们需要在顶层的Testbench里实例化记分板，并把它和监测器（Monitor）连接起来：
```
module tb_axi_fifo();
    // ... 这里省略DUT实例、接口声明、时钟生成等代码 ...
    scoreboard sb; // 声明我们的记分板裁判

    initial begin
        sb = new(); // 创建裁判实例

        fork
            sb.run(); // 启动比较线程（裁判开始工作）

            // 启动驱动、监测器等线程
            begin
                input_monitor_task(); // 输入监测器，会调用 sb.put_expected(...)
                output_monitor_task(); // 输出监测器，会调用 sb.put_actual(...)
            end

            // 主测试序列
            run_test_sequence();
        join

        // 等待测试结束
        #100ns;
        sb.report(); // 打印最终裁决报告
        $finish;
    end

    // 输入监测器任务示例
    task input_monitor_task();
        axi_stream_transaction tr;
        forever begin
            // 捕捉DUT输入接口上的事务...
            @(posedge vif.clk iff (vif.tvalid && vif.tready));
            tr = new();
            tr.tdata = vif.tdata;
            tr.tlast = vif.tlast;
            tr.id = some_id++; // ID自增
            // 将事务送入记分板的预期队列
            sb.put_expected(tr);
        end
    endtask
    // 输出监测器任务类似，调用 sb.put_actual(tr)
    // ...
endmodule
```
### 四、还想更厉害？这些进阶技巧了解一下
- **处理乱序与延迟：**上面的简易记分板假设数据顺序不变。如果你的DUT可能会打乱顺序输出，就需要在事务类里加入唯一ID（比如序列号），然后记分板内部使用关联数组，按照ID来存储和匹配预期数据。
- **拥抱UVM框架：**对于大型项目，强烈推荐使用UVM。它内置了强大的`uvm_scoreboard`、`uvm_in_order_comparator`等组件，以及`uvm_tlm_analysis_fifo`用于通信，能让你搭记分板事半功倍，复用性也极高。
- **FPGA环境特别考量：**在FPGA原型验证时，你可能会想把部分验证组件（比如比较器）用可综合的代码实现，并放到FPGA里做实时检查。这时候，可一定要注意你的代码风格必须是可综合的哦！
- **关注性能与内存：**如果是长时间、大数据量的测试，要注意管理邮箱或队列的深度，别把内存撑爆了。可以考虑把比较结果实时记录到文件里，而不是全部缓存在内存中。
### 五、总结一下
看，记分板其实就是构建自动化、自检型验证环境的基石。通过今天的学习，我们利用SystemVerilog的OOP特性，从零开始搭建了一个用于AXI-Stream FIFO的简易记分板，涵盖了事务定义、数据传递、比较逻辑和结果报告的全流程。
掌握了这个基础框架，你就可以举一反三，根据具体设计（比如要处理乱序、多通道或者更复杂的协议）去扩展和优化你的“智能裁判”了。
在成电国芯的FPGA高级验证课程中，我们会带你更深入地探索如何基于UVM搭建工业级的验证平台，涵盖覆盖率收集、断言（SVA）、寄存器模型（RAL）等高级主题。我们的目标是帮助你从一名FPGA设计者，成长为能应对复杂芯片验证挑战的验证专家。期待与你一起，在验证的领域里玩出更多花样！
