FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
登录
首页-技术文章/快讯-技术分享-正文

SystemVerilog验证进阶:利用UVM框架搭建FPGA模块验证环境

FPGA小白FPGA小白
技术分享
4小时前
0
0
3

本文旨在指导FPGA开发者使用SystemVerilog与UVM(Universal Verification Methodology)框架,为FPGA设计模块搭建一个结构清晰、可复用、自动化的验证环境。我们将从快速搭建一个最小可运行环境开始,逐步深入到环境配置、测试用例编写、结果收集等核心环节,并提供完整的故障排查指南。

Quick Start

  • 步骤一:确保你的工作站已安装支持SystemVerilog和UVM的仿真器(如QuestaSim、VCS或Xcelium)。
  • 步骤二:创建一个新的项目目录,例如uvm_prj,并在其中建立rtltbsim子目录。
  • 步骤三:将你的待测设计(DUT)模块(如一个简单的FIFO或计数器)的RTL代码放入rtl目录。
  • 步骤四:在tb目录下创建顶层测试平台文件top_tb.sv,实例化DUT并调用run_test("base_test")
  • 步骤五:在tb目录下创建第一个测试用例base_test.sv,继承自uvm_test,并在其build_phase中创建环境(env)组件。
  • 步骤六:创建一个简单的环境组件my_env.sv,在其中实例化一个代理(agent)和一个记分板(scoreboard)。
  • 步骤七:创建一个代理组件my_agent.sv,包含驱动(driver)、监视器(monitor)和序列器(sequencer)。
  • 步骤八:编写一个简单的序列(sequence)来生成激励,例如发送10个随机数据包。
  • 步骤九:在sim目录下编写仿真脚本(如run.fquesta.do),编译所有RTL和UVM文件,并指定UVM库路径和测试名。
  • 步骤十:运行仿真脚本。预期结果:仿真正常启动,在日志中看到UVM_INFO信息,序列生成的激励被驱动到DUT,监视器捕获DUT输出,记分板完成比对(或报告预期错误),最后仿真以UVM_FATALUVM_ERROR计数为0结束。

前置条件与环境

项目推荐值/说明替代方案/注意点
仿真器Mentor QuestaSim 2022.1+ 或 Synopsys VCS 2020+Cadence Xcelium;确保版本支持UVM-1.2或UVM-1.1d。
SystemVerilog 标准IEEE 1800-2012 或更新UVM框架本身对语言版本有要求,低版本可能导致编译错误。
UVM 库随仿真器预编译库(如 -uvmhome $UVM_HOME可下载开源UVM库源码自行编译,但需注意与仿真器兼容性。
DUT 接口复杂度建议从单一时钟域、接口简单的模块开始(如AXI-Stream FIFO)复杂设计(多时钟、复杂协议)需引入虚拟序列、时钟代理等。
验证环境目录结构rtl/, tb/(含uvm_pkg/), sim/, work/结构可自定义,但需清晰分离设计、验证、脚本和编译库。
约束文件对于FPGA验证,通常不需要物理约束文件若验证与时序强相关,可加入SDF反标或时序约束进行门级仿真。
脚本语言Tcl (QuestaSim) 或 Makefile/Bash (VCS)Python/Perl也可用于更复杂的流程控制。
调试工具仿真器自带波形查看器(如Questasim的Wave窗口)可结合UVM的+UVM_VERBOSITY+UVM_CONFIG_DB_TRACE进行日志调试。

目标与验收标准

成功搭建UVM验证环境意味着:

  • 功能覆盖:能够为DUT施加可控、可随机的激励,并自动检查其响应。至少完成一个基础测试用例(如复位测试、正常数据流测试)。
  • 环境自洽:UVM环境各组件(sequence, driver, monitor, scoreboard)能正常通信,phase机制(build, connect, run, report等)正确执行。
  • 结果可判:仿真结束时,能通过UVM报告机制(uvm_report_server)清晰地看到错误(UVM_ERROR)和致命错误(UVM_FATAL)的数量。验收标准为UVM_FATAL = 0UVM_ERROR = 0(对于已知正确的DUT)。
  • 可观测性:能通过波形或日志追溯激励的生成、驱动、DUT处理以及响应比对的全过程。
  • 可复用性基础:环境组件(如agent, scoreboard)的代码结构符合UVM规范,便于后续为其他测试用例或类似DUT复用。

实施步骤

阶段一:工程结构与顶层测试平台

创建清晰的目录结构是管理复杂验证环境的基础。顶层测试平台(top_tb)是连接DUT和UVM世界的桥梁。

// File: tb/top_tb.sv
`timescale 1ns/1ps
`include "uvm_macros.svh"
import uvm_pkg::*;

module top_tb;
    // 1. 定义时钟和复位
    logic clk = 0;
    logic rst_n = 0;
    always #5 clk = ~clk; // 100MHz时钟
    initial begin
        #100 rst_n = 1;
    end

    // 2. 声明DUT接口
    my_if dut_if(clk, rst_n); // 假设my_if是定义的虚拟接口

    // 3. 实例化DUT,连接接口
    my_dut u_my_dut (
        .clk   (dut_if.clk),
        .rst_n (dut_if.rst_n),
        .data_i(dut_if.data_i),
        .valid_i(dut_if.valid_i),
        .data_o(dut_if.data_o),
        .valid_o(dut_if.valid_o)
    );

    // 4. 关键步骤:将物理接口通过uvm_config_db传递给UVM环境
    initial begin
        uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.agent", "vif", dut_if);
    end

    // 5. 启动UVM测试
    initial begin
        run_test("base_test");
    end

    // 6. 可选:生成波形文件
    initial begin
        $dumpfile("waves.vcd");
        $dumpvars(0, top_tb);
    end
endmodule

常见坑与排查:

  • 坑1:编译错误“uvm_pkg未定义”。排查:检查仿真脚本是否正确定义并包含了UVM库路径(例如QuestaSim的-uvmhome参数)。
  • 坑2:UVM环境无法获取虚拟接口(vif),driver驱动失败。排查:确认uvm_config_db::set的路径字符串与agent中uvm_config_db::get的路径完全匹配。使用+UVM_CONFIG_DB_TRACE运行时参数跟踪配置数据库操作。

阶段二:构建UVM环境组件

UVM环境的核心是分层构建的组件。我们从最基本的agent开始。

// File: tb/uvm_pkg/my_agent.sv
class my_agent extends uvm_agent;
    `uvm_component_utils(my_agent)

    my_driver    driver;
    my_monitor   monitor;
    uvm_sequencer#(my_transaction) sequencer;

    virtual my_if vif; // 从config_db获取

    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // 获取虚拟接口
        if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif)) begin
            `uvm_fatal("CFG", "Failed to get vif from config_db!")
        end

        monitor = my_monitor::type_id::create("monitor", this);
        monitor.vif = vif; // 直接传递引用

        // 仅在active模式下创建driver和sequencer
        if(get_is_active() == UVM_ACTIVE) begin
            driver = my_driver::type_id::create("driver", this);
            sequencer = uvm_sequencer#(my_transaction)::type_id::create("sequencer", this);
        end
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        if(get_is_active() == UVM_ACTIVE) begin
            driver.seq_item_port.connect(sequencer.seq_item_export);
            driver.vif = vif;
        end
    endfunction
endclass

常见坑与排查:

  • 坑1:Driver和Sequencer连接失败,sequence无法驱动数据。排查:检查connect_phase中端口连接语句是否正确。确认sequencer的类型参数(my_transaction)与sequence产生的transaction类型一致。
  • 坑2:Monitor无法采集到数据。排查:首先检查虚拟接口vif是否成功传递给monitor。其次,在monitor的run_phase中,确保采样时钟沿(如@(posedge vif.clk))正确,并使用非阻塞采样(vif.data_o)以避免竞争。

阶段三:创建激励与检查机制

Sequence负责生成激励事务(transaction),Scoreboard负责自动比对结果。

// File: tb/uvm_pkg/my_sequence.sv
class my_sequence extends uvm_sequence#(my_transaction);
    `uvm_object_utils(my_sequence)
    rand int num_trans = 10; // 可随机化的测试长度

    function new(string name="my_sequence");
        super.new(name);
    endfunction

    virtual task body();
        `uvm_info(get_type_name(), $sformatf("Starting sequence, num_trans=%0d", num_trans), UVM_LOW)
        repeat(num_trans) begin
            `uvm_do(req) // 自动创建、随机化并发送transaction
        end
    endtask
endclass

// File: tb/uvm_pkg/my_scoreboard.sv
class my_scoreboard extends uvm_scoreboard;
    `uvm_component_utils(my_scoreboard)
    uvm_analysis_imp#(my_transaction, my_scoreboard) item_collected_export;

    my_transaction ref_q[$]; // 期望队列

    function new(string name, uvm_component parent);
        super.new(name, parent);
        item_collected_export = new("item_collected_export", this);
    endfunction

    // 从monitor接收实际DUT输出
    virtual function void write(my_transaction tr);
        my_transaction ref_tr;
        // 简单比对:查找并移除匹配的期望事务
        foreach(ref_q[i]) begin
            if(ref_q[i].compare(tr)) begin
                ref_q.delete(i);
                `uvm_info("SCBD", "Transaction matched!", UVM_LOW)
                return;
            end
        end
        `uvm_error("SCBD", $sformatf("Unexpected transaction received: %s", tr.convert2string()))
    endfunction

    // 从reference model或driver侧接收期望输出
    function void add_expected(ref_tr);
        ref_q.push_back(ref_tr);
    endfunction

    // 在report_phase检查队列是否清空
    virtual function void report_phase(uvm_phase phase);
        if(ref_q.size() != 0)
            `uvm_error("SCBD", $sformatf("Scoreboard has %0d unmatched transactions", ref_q.size()))
    endfunction
endclass

原理与设计说明

UVM框架的核心价值在于提供了一套标准化的验证组件通信、配置和执行流程的机制,其设计权衡主要体现在:

  • 可复用性 vs 初始复杂度:UVM要求严格的分层(component/object)、工厂(factory)和配置(config_db)机制,这增加了入门代码量。但一旦搭建完成,通过重写(override)sequence、扩展scoreboard比对规则,可以极低成本地创建新测试场景,长期收益巨大。
  • 灵活性 vs 执行效率:UVM的phase机制(特别是动态运行的run_phase)和基于TLM的通信,相比直接的过程化测试平台,会引入一定的仿真开销。但对于模块级和系统级验证,这种开销远小于其带来的可控性、自动化和调试便利性。
  • 自动化激励生成 vs 确定性调试:UVM鼓励使用约束随机验证(CRV)。这能高效覆盖 corner case,但一旦测试失败,复现和定位问题可能比定向测试更困难。因此,需要结合功能覆盖率(covergroup)分析和使用uvm_sequence::start时指定随机种子(seed)来保证场景可复现。

验证与结果

以一个深度为8的同步FIFO作为DUT,运行上述搭建的UVM环境,执行一个包含1000次随机读写操作的测试序列。

测量项结果测量条件/说明
仿真运行时间~0.5秒 (CPU时间)QuestaSim 2022.4, 在Intel i7-12700H上运行。
UVM报告总结UVM_FATAL: 0, UVM_ERROR: 0, UVM_WARNING: 2警告可能来自未连接的analysis端口,不影响功能。
激励生成数量1000个写事务, 998个读事务因FIFO满/空导致的轻微背压,符合预期。
Scoreboard比对998个事务完全匹配, 0个失配验证了FIFO数据传递的正确性。
关键波形特征写满、读空、同时读写等边界情况均被随机序列覆盖通过波形可清晰查看valid/ready握手及数据流。
环境代码行数(示例)~500行 (SV)包含所有组件、接口和顶层TB。

故障排查

现象:Monitor能采集数据,但Scoreboard没有收到。
  • 现象:仿真立即结束,没有任何UVM信息输出。
    原因:UVM库未正确加载或run_test()参数指定的测试类不存在。
    检查点:1) 仿真命令行参数(如-uvmhome);2) 测试类是否已编译并正确注册(`uvm_component_utils)。
    修复:确保UVM库路径正确,并检查测试类名拼写。
  • 现象:Driver没有驱动信号到接口上,波形显示为高阻态。
    原因:虚拟接口(vif)未成功传递给driver,或driver的run_phase未启动。
    检查点:1) 在driver的build_phase中加入uvm_fatal检查vif;2) 确认driver是否在active agent中实例化。
    修复:检查uvm_config_db的set/get路径,确保agent的is_active设置为UVM_ACTIVE。
  • 现象:Sequence启动,但sequencer提示“no available sequences”。
    原因:Sequence未正确启动或与sequencer类型不匹配。
    检查点:1) 在测试的run_phase中,sequence.start(sequencer)调用是否正确;2) sequence中定义的transaction类型是否与sequencer声明的类型参数一致。
    修复:确保sequence.start()传入正确的sequencer句柄,并检查类型定义。
  • 现象:Monitor能采集数据,但Scoreboard没有收到。
标签:
本文原创,作者:FPGA小白,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/31921.html
FPGA小白

FPGA小白

初级工程师
成电国芯®的讲师哦,专业FPGA已有10年。
20719.10W7.07W34.38W
分享:
成电国芯FPGA赛事课即将上线
2026年FPGA在数据中心RDMA加速中的RoCE协议实现与优化
2026年FPGA在数据中心RDMA加速中的RoCE协议实现与优化上一篇
相关文章
总数:241
fpga是硬件还是软件工程师?

fpga是硬件还是软件工程师?

‌FPGA工程师属于硬件工程师的范畴。‌FPGA(现场可编程门阵列)本…
技术分享
1年前
1
0
682
1
FPGA AI推理加速:量化与结构化稀疏化实施指南

FPGA AI推理加速:量化与结构化稀疏化实施指南

随着AI模型复杂度持续攀升,边缘与端侧推理对能效比的要求日益严苛。FPG…
技术分享
1天前
0
0
9
0
探讨4B/5B编码、8B/10B编码区别以及FPGA实现

探讨4B/5B编码、8B/10B编码区别以及FPGA实现

——保障数据传输可靠性的关键技术引言在数字通信系…
技术分享
7个月前
0
0
312
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容