写在前面:验证工程师的困境

“验证覆盖率到92%了,但流片回来还是出问题”——这是许多验证工程师的痛。问题的根源往往不是测试用例不够多,而是验证架构本身缺乏系统性和可复用性

本文将以一个工业级AXI Stream数据通路为例,手把手带你构建一套完整的UVM验证平台。你将学到:

  • 如何设计符合UVM规约的Agent架构

  • Sequence与Sequencer的解耦策略

  • Scoreboard的自检机制实现

  • 覆盖率收集的最佳实践

前置要求:具备SystemVerilog基础,了解UVM基础概念(组件层级、phase机制)。


一、验证架构设计:从需求到蓝图

1.1 DUT(被测设计)分析

我们的测试目标是AXI Stream数据通路模块,其接口定义如下:

module axi_stream_fifo #(
    parameter int DATA_WIDTH = 32,
    parameter int DEPTH      = 16
)(
    input  logic                    aclk,
    input  logic                    aresetn,
    
    // 输入AXI-Stream slave接口
    input  logic [DATA_WIDTH-1:0]   s_axis_tdata,
    input  logic                    s_axis_tvalid,
    output logic                    s_axis_tready,
    input  logic                    s_axis_tlast,  // 包结束标记
    
    // 输出AXI-Stream master接口  
    output logic [DATA_WIDTH-1:0]   m_axis_tdata,
    output logic                    m_axis_tvalid,
    input  logic                    m_axis_tready,
    output logic                    m_axis_tlast
);
    // FIFO实现...
endmodule

验证要点

  1. 数据完整性:输入数据与输出数据完全一致

  2. 协议合规性:AXI Stream握手时序约束

  3. 边界场景:FIFO满/空状态、背压(backpressure)处理

  4. 性能指标:吞吐率、延迟分布

1.2 UVM Testbench架构

[UVM Testbench拓扑]
                    ┌─────────────────────────┐
                    │     uvm_top            │
                    └──────────┬──────────────┘
                               │
                    ┌──────────▼──────────────┐
                    │   test_base (uvm_test)  │
                    └──────────┬──────────────┘
                               │
           ┌───────────────────┼───────────────────┐
           │                   │                   │
    ┌──────▼──────┐   ┌────────▼────────┐   ┌──────▼──────┐
    │   env       │   │      vseqr      │   │  coverage   │
    │  (uvm_env)  │   │ (virtual seqr)  │   │ collector   │
    └──────┬──────┘   └─────────────────┘   └─────────────┘
           │
     ┌─────┴─────┐
     │           │
┌────▼───┐  ┌────▼───┐
│input   │  │output  │
│agent   │  │agent   │
└────┬───┘  └────┬───┘
     │           │
┌────▼───┐  ┌────▼───┐  ┌─────────────┐
│driver  │  │monitor │  │ scoreboard  │
│        │  │        │  │             │
└────┬───┘  └────┬───┘  └─────────────┘
     │           │           ▲
┌────▼───┐       │           │
│sequencer│      └───────────┘
└─────────┘    (TLM analysis port)

二、构建输入Agent:从Transaction到Driver

2.1 Transaction定义

Transaction是UVM中的基本数据单元,必须继承uvm_sequence_item

// axi_stream_transaction.sv
class axi_stream_transaction extends uvm_sequence_item;
    `uvm_object_utils(axi_stream_transaction)
    
    // 数据字段
    rand logic [31:0] data;
    rand logic        tlast;      // 包结束标记
    rand int          delay_cycles; // 发送前等待周期(模拟真实场景)
    
    // 约束
    constraint c_delay { delay_cycles inside {[0:10]}; }
    
    // 常用函数
    function new(string name = "axi_stream_transaction");
        super.new(name);
    endfunction
    
    function string convert2string();
        return $sformatf("data=0x%0h tlast=%0b delay=%0d", 
            data, tlast, delay_cycles);
    endfunction
    
    function void do_copy(uvm_object rhs);
        axi_stream_transaction rhs_;
        if(!$cast(rhs_, rhs)) `uvm_fatal(get_name(), "Cast failed")
        super.do_copy(rhs);
        data = rhs_.data;
        tlast = rhs_.tlast;
        delay_cycles = rhs_.delay_cycles;
    endfunction
    
    function bit do_compare(uvm_object rhs, uvm_comparer comparer);
        axi_stream_transaction rhs_;
        if(!$cast(rhs_, rhs)) return 0;
        return (super.do_compare(rhs, comparer) &&
                data == rhs_.data &&
                tlast == rhs_.tlast);
    endfunction
endclass

紫霄提示:重写do_copydo_compare是实现Transaction深拷贝和比较的关键,pack/unpack方法用于序列化场景。

2.2 Sequencer实现

Sequencer是Sequence与Driver之间的协调器,UVM提供了标准基类:

// axi_stream_sequencer.sv
class axi_stream_sequencer extends uvm_sequencer #(axi_stream_transaction);
    `uvm_component_utils(axi_stream_sequencer)
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    // 可在此添加自定义方法,如配置参数
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        `uvm_info(get_name(), "Sequencer build phase done", UVM_MEDIUM)
    endfunction
endclass

2.3 Driver实现

Driver负责将Transaction转换为DUT接口信号:

// axi_stream_driver.sv
class axi_stream_driver extends uvm_driver #(axi_stream_transaction);
    `uvm_component_utils(axi_stream_driver)
    
    // 虚拟接口
    virtual interface axi_stream_if vif;
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // 从配置数据库获取接口
        if(!uvm_config_db#(virtual axi_stream_if)::get(
            this, "", "vif", vif)) begin
            `uvm_fatal(get_name(), "Virtual interface not found")
        end
    endfunction
    
    task run_phase(uvm_phase phase);
        forever begin
            seq_item_port.get_next_item(req);  // 从sequencer获取事务
            drive_item(req);
            seq_item_port.item_done();         // 通知完成
        end
    endtask
    
    task drive_item(axi_stream_transaction tr);
        // 模拟发送前延迟
        repeat(tr.delay_cycles) @(posedge vif.aclk);
        
        // 驱动信号
        @(posedge vif.aclk);
        vif.s_axis_tdata  <= tr.data;
        vif.s_axis_tvalid <= 1'b1;
        vif.s_axis_tlast  <= tr.tlast;
        
        // 等待握手完成(tvalid && tready)
        do begin
            @(posedge vif.aclk);
        end while(!(vif.s_axis_tvalid && vif.s_axis_tready));
        
        // 解复位
        vif.s_axis_tvalid <= 1'b0;
        
        `uvm_info(get_name(), $sformatf("Driven: %s", tr.convert2string()), UVM_HIGH)
    endtask
endclass

2.4 Monitor实现

Monitor从DUT接口采样数据,不驱动信号,独立于Driver运行:

// axi_stream_monitor.sv
class axi_stream_monitor extends uvm_monitor;
    `uvm_component_utils(axi_stream_monitor)
    
    virtual interface axi_stream_if vif;
    
    // Analysis Port:向Scoreboard广播采样到的Transaction
    uvm_analysis_port #(axi_stream_transaction) item_collected_port;
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
        item_collected_port = new("item_collected_port", this);
    endfunction
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if(!uvm_config_db#(virtual axi_stream_if)::get(
            this, "", "vif", vif))
            `uvm_fatal(get_name(), "Virtual interface not found")
    endfunction
    
    task run_phase(uvm_phase phase);
        axi_stream_transaction tr;
        forever begin
            @(posedge vif.aclk);
            // 检测有效传输(valid && ready)
            if(vif.s_axis_tvalid && vif.s_axis_tready) begin
                tr = axi_stream_transaction::type_id::create("tr");
                tr.data = vif.s_axis_tdata;
                tr.tlast = vif.s_axis_tlast;
                item_collected_port.write(tr);  // 广播
                `uvm_info(get_name(), $sformatf("Monitored: %s", 
                    tr.convert2string()), UVM_HIGH)
            end
        end
    endtask
endclass

2.5 Agent封装

Agent将Sequencer、Driver、Monitor封装为可复用单元:

// axi_stream_agent.sv
class axi_stream_agent extends uvm_agent;
    `uvm_component_utils(axi_stream_agent)
    
    axi_stream_sequencer    sqr;
    axi_stream_driver       drv;
    axi_stream_monitor      mon;
    
    // 配置参数
    uvm_active_passive_enum is_active = UVM_ACTIVE;
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mon = axi_stream_monitor::type_id::create("mon", this);
        
        if(is_active == UVM_ACTIVE) begin
            sqr = axi_stream_sequencer::type_id::create("sqr", this);
            drv = axi_stream_driver::type_id::create("drv", this);
        end
    endfunction
    
    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        if(is_active == UVM_ACTIVE) begin
            drv.seq_item_port.connect(sqr.seq_item_export);
        end
    endfunction
endclass

三、Sequence编写:测试向量的艺术

3.1 基础Sequence

// axi_stream_base_sequence.sv
class axi_stream_base_sequence extends uvm_sequence #(axi_stream_transaction);
    `uvm_object_utils(axi_stream_base_sequence)
    
    rand int num_transactions = 100;  // 默认发送100个事务
    
    function new(string name = "axi_stream_base_sequence");
        super.new(name);
    endfunction
    
    task body();
        axi_stream_transaction tr;
        repeat(num_transactions) begin
            tr = axi_stream_transaction::type_id::create("tr");
            start_item(tr);
            // 可在预处理阶段修改约束
            if(!tr.randomize()) `uvm_error(get_name(), "Randomize failed")
            finish_item(tr);
        end
    endtask
endclass

3.2 带TLAST的突发传输Sequence

// axi_stream_burst_sequence.sv
class axi_stream_burst_sequence extends axi_stream_base_sequence;
    `uvm_object_utils(axi_stream_burst_sequence)
    
    rand int burst_length = 8;  // 突发长度
    
    function new(string name = "axi_stream_burst_sequence");
        super.new(name);
    endfunction
    
    task body();
        axi_stream_transaction tr;
        for(int i = 0; i < burst_length; i++) begin
            tr = axi_stream_transaction::type_id::create("tr");
            start_item(tr);
            // 约束:最后一个事务tlast=1
            if(!tr.randomize() with {
                tlast == (i == burst_length - 1);
                data == i;  // 可预测的数据模式
            }) `uvm_error(get_name(), "Randomize failed")
            finish_item(tr);
        end
    endtask
endclass

3.3 性能压力Sequence

// axi_stream_stress_sequence.sv
class axi_stream_stress_sequence extends axi_stream_base_sequence;
    `uvm_object_utils(axi_stream_stress_sequence)
    
    // 零延迟背靠背传输
    constraint c_no_delay { num_transactions == 1000; }
    
    task body();
        axi_stream_transaction tr;
        for(int i = 0; i < num_transactions; i++) begin
            tr = axi_stream_transaction::type_id::create("tr");
            start_item(tr);
            if(!tr.randomize() with {
                delay_cycles == 0;  // 无延迟
                tlast == (i % 10 == 9);  // 每10个一组
            }) `uvm_error(get_name(), "Randomize failed")
            finish_item(tr);
        end
    endtask
endclass

四、Scoreboard自检机制

4.1 参考模型与比较器

Scoreboard接收输入和输出Transaction,进行端到端校验:

// axi_stream_scoreboard.sv
class axi_stream_scoreboard extends uvm_scoreboard;
    `uvm_component_utils(axi_stream_scoreboard)
    
    // TLM Analysis FIFO:缓存Monitor发送的Transaction
    uvm_tlm_analysis_fifo #(axi_stream_transaction) input_fifo;
    uvm_tlm_analysis_fifo #(axi_stream_transaction) output_fifo;
    
    // 统计
    int compare_count = 0;
    int error_count = 0;
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
        input_fifo = new("input_fifo", this);
        output_fifo = new("output_fifo", this);
    endfunction
    
    task run_phase(uvm_phase phase);
        axi_stream_transaction in_tr, out_tr;
        forever begin
            // 阻塞等待两边都有数据
            fork
                input_fifo.get(in_tr);
                output_fifo.get(out_tr);
            join
            
            // 比较
            if(!in_tr.compare(out_tr)) begin
                `uvm_error(get_name(), $sformatf(
                    "Mismatch!\nInput:  %s\nOutput: %s",
                    in_tr.convert2string(), out_tr.convert2string()))
                error_count++;
            end else begin
                `uvm_info(get_name(), $sformatf("Match: %s", 
                    in_tr.convert2string()), UVM_MEDIUM)
            end
            compare_count++;
        end
    endtask
    
    function void report_phase(uvm_phase phase);
        `uvm_info(get_name(), $sformatf(
            "Scoreboard Report:\n  Compared: %0d\n  Errors: %0d\n  Pass Rate: %0.2f%%",
            compare_count, error_count, 
            100.0*(compare_count-error_count)/compare_count), UVM_LOW)
    endfunction
endclass

4.2 异步FIFO模型

如果DUT内部逻辑复杂,可在Scoreboard中嵌入参考模型:

// 简化FIFO模型(仅示意)
class ref_fifo #(int DEPTH = 16) extends uvm_component;
    // 内部队列模拟FIFO行为
    axi_stream_transaction queue[$];
    
    function bit write(axi_stream_transaction tr);
        if(queue.size() >= DEPTH) return 0;  // FIFO满
        queue.push_back(tr);
        return 1;
    endfunction
    
    function bit read(output axi_stream_transaction tr);
        if(queue.size() == 0) return 0;  // FIFO空
        tr = queue.pop_front();
        return 1;
    endfunction
endclass

五、覆盖率收集

5.1 Covergroup定义

// coverage_collector.sv
class coverage_collector extends uvm_component;
    `uvm_component_utils(coverage_collector)
    
    // 接收Monitor的Transaction
    uvm_analysis_imp #(axi_stream_transaction, coverage_collector) analysis_export;
    
    // Covergroup定义
    covergroup axi_stream_cg with function sample(axi_stream_transaction tr);
        // 数据值覆盖(分bins)
        data_cp: coverpoint tr.data {
            bins zeros = {0};
            bins low   = {[1:100]};
            bins mid   = {[101:1000]};
            bins high  = {[1001:$]};
            bins all_ones = {32'hFFFFFFFF};
        }
        
        // 延迟覆盖
        delay_cp: coverpoint tr.delay_cycles {
            bins immediate = {0};
            bins short = {[1:5]};
            bins long  = {[6:10]};
        }
        
        // tlast交叉覆盖
        tlast_cp: coverpoint tr.tlast {
            bins asserted = {1};
            bins deasserted = {0};
        }
        
        // 交叉覆盖:数据类型 x tlast
        data_x_tlast: cross data_cp, tlast_cp;
    endgroup
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
        axi_stream_cg = new();
        analysis_export = new("analysis_export", this);
    endfunction
    
    // Write方法:Monitor调用通知
    function void write(axi_stream_transaction tr);
        axi_stream_cg.sample(tr);
    endfunction
    
    function void report_phase(uvm_phase phase);
        `uvm_info(get_name(), $sformatf("Coverage: %0.2f%%", 
            axi_stream_cg.get_coverage()), UVM_LOW)
    endfunction
endclass

六、Test整合

6.1 Environment封装

// testbench_env.sv
class testbench_env extends uvm_env;
    `uvm_component_utils(testbench_env)
    
    axi_stream_agent input_agent;   // ACTIVE模式(驱动输入)
    axi_stream_agent output_agent;  // PASSIVE模式(仅监控)
    axi_stream_scoreboard scb;
    coverage_collector cov;
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        
        input_agent = axi_stream_agent::type_id::create("input_agent", this);
        input_agent.is_active = UVM_ACTIVE;
        
        output_agent = axi_stream_agent::type_id::create("output_agent", this);
        output_agent.is_active = UVM_PASSIVE;
        
        scb = axi_stream_scoreboard::type_id::create("scb", this);
        cov = coverage_collector::type_id::create("cov", this);
    endfunction
    
    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        // 连接Monitor到Scoreboard
        input_agent.mon.item_collected_port.connect(scb.input_fifo.analysis_export);
        output_agent.mon.item_collected_port.connect(scb.output_fifo.analysis_export);
        // 连接Coverage
        input_agent.mon.item_collected_port.connect(cov.analysis_export);
    endfunction
endclass

6.2 Base Test

// test_base.sv
class test_base extends uvm_test;
    `uvm_component_utils(test_base)
    
    testbench_env env;
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        env = testbench_env::type_id::create("env", this);
    endfunction
    
    task run_phase(uvm_phase phase);
        axi_stream_base_sequence seq;
        phase.raise_objection(this);  // 阻止phase结束
        
        seq = axi_stream_base_sequence::type_id::create("seq");
        seq.start(env.input_agent.sqr);
        
        phase.drop_objection(this);   // 允许phase结束
    endtask
endclass

6.3 特定测试(并发背压场景)

// test_backpressure.sv
class test_backpressure extends test_base;
    `uvm_component_utils(test_backpressure)
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    task run_phase(uvm_phase phase);
        // 并行启动:输入侧全速发送 + 输出侧随机反压
        phase.raise_objection(this);
        
        fork
            begin
                axi_stream_stress_sequence seq;
                seq = axi_stream_stress_sequence::type_id::create("seq");
                seq.start(env.input_agent.sqr);
            end
            begin
                // 随机控制输出tready
                repeat(1000) begin
                    env.output_agent.vif.m_axis_tready <= $urandom_range(0,1);
                    @(posedge env.output_agent.vif.aclk);
                end
            end
        join
        
        phase.drop_objection(this);
    endtask
endclass

七、仿真与调试技巧

7.1 Makefile示例

# UVM Makefile示例
SIMULATOR = vcs  # 或xrun, vsim
UVM_HOME = /path/to/uvm

all: comp run

comp:
	$(SIMULATOR) -full64 -sverilog -ntb_opts uvm \
		+incdir+$(UVM_HOME)/src \
		-f filelist.f \
		-top test_base

run:
	./simv +UVM_TESTNAME=test_backpressure +UVM_VERBOSITY=UVM_MEDIUM

clean:
	rm -rf csrc simv* vc_hdrs.h ucli.key

7.2 调试技巧

  1. 波形优化:使用$wlfdumpvars()按需记录,避免波形过大

  2. Transaction Debug:在Transaction中添加print()函数,日志输出友好格式

  3. Phase调试:使用+UVM_PHASE_TRACE追踪phase执行

  4. 超时保护:在build_phase中设置uvm_top.set_timeout(1ms)防止死锁


总结

本文完整展示了从底层Transaction到顶层Test的UVM平台构建过程。关键要点:

  1. 标准化接口:Agent封装确保复用性,通过is_active参数灵活切换模式

  2. 解耦通信:TLM Analysis Port实现Monitor→Scoreboard/Coverage的无耦合连接

  3. 层次化Sequence:继承体系支持从基础测试到压力测试的快速扩展

  4. 自动化校验:Scoreboard参考模型确保功能正确性,覆盖率收集量化验证完备性

下一步建议

  • 使用UVM Register Model添加寄存器配置验证

  • 引入形式验证(JasperGold)检查AXI协议断言

  • 集成CI/CD(Jenkins + Verilator)实现回归测试自动化

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐