4. 测试用例开发:SystemVerilog/UVM基础回顾、GEM测试用例模板、断言与检查器编写

好,咱们进入第四章。这一章是实战的起点,说白了就是教你如何写出第一条能跑的GEM测试用例。很多新人一上来就盯着复杂的验证环境看,结果越看越懵。我的建议是——先把手弄脏,写一个最简单的用例,跑通了再说。

我会带着你过一遍SystemVerilog和UVM里最常用的几个概念,然后直接套到GEM的测试模板里。最后再聊聊断言和检查器,这东西在GEM验证里特别重要,我吃过不少亏。

4.1 SystemVerilog/UVM核心回顾

咱们不搞教科书式的复习,只挑GEM验证里真正用得上的东西讲。

4.1.1 接口与时钟块

GEM的接口信号很多,用interface来封装是最合理的做法。我个人习惯把时钟和复位单独拎出来,放在一个clocking block里,这样驱动和采样的时序关系一目了然。

interface gem_if (input bit clk, input bit rst_n);
    logic [31:0] addr;
    logic [63:0] data;
    logic       req;
    logic       ack;

    clocking cb @(posedge clk);
        default input #1ns output #1ns;
        output addr, data, req;
        input  ack;
    endclocking

    modport TB (clocking cb, input rst_n);
endinterface

你看,这样写之后,测试用例里驱动信号就变成了if.cb.addr <= value,干净利落。

4.1.2 UVM中的sequence与sequencer

UVM里最常用的就是sequence。GEM的测试用例本质上就是一个sequence。你只需要记住三件事:

  • body():你的测试逻辑写在这里
  • `uvm_do:发送transaction的宏
  • sequencer:负责把transaction发给driver

我在项目中见过有人把几百行代码全塞进body()里,结果调试的时候根本找不到问题在哪。我的建议是——把不同的操作拆成独立的sequence,然后用p_sequencer来组合它们。

4.1.3 工厂覆盖与配置机制

GEM验证里经常需要替换某个组件的行为。比如你想让某个agent在异常模式下工作,用set_type_override_by_type就能搞定,不用改一行环境代码。

小技巧: 用uvm_config_db来传递配置对象,比用全局变量安全得多。我习惯在build_phase里统一读取配置,这样后期维护起来特别省心。

4.2 GEM测试用例模板

好,咱们直接看GEM的测试用例长什么样。下面这个模板是我在多个项目中打磨出来的,你可以直接拿来用。

class gem_base_test extends uvm_test;
    `uvm_component_utils(gem_base_test)

    gem_env       m_env;
    gem_vseqr     m_vseqr;

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        m_env   = gem_env::type_id::create("m_env", this);
        m_vseqr = gem_vseqr::type_id::create("m_vseqr", this);
    endfunction

    task run_phase(uvm_phase phase);
        gem_base_vseq vseq;
        phase.raise_objection(this);
        vseq = gem_base_vseq::type_id::create("vseq");
        vseq.start(m_vseqr);
        phase.drop_objection(this);
    endtask
endclass

这个模板有几个关键点:

  • base test:只做环境创建和sequence启动,不写具体测试逻辑
  • virtual sequencer:用来协调多个agent的sequence
  • objection机制:确保仿真不会提前结束

你想想看,如果每个测试用例都从零开始写,那得重复多少代码?所以我的做法是——所有公共的东西都放在base test里,子类只覆盖需要改的部分。

4.2.1 一个具体的GEM测试用例

假设我们要测试GEM的寄存器写操作,可以这样写:

class gem_reg_write_test extends gem_base_test;
    `uvm_component_utils(gem_reg_write_test)

    function void build_phase(uvm_phase phase);
        // 覆盖配置,比如让寄存器模型工作在非阻塞模式
        uvm_config_db#(bit)::set(this, "m_env.*", "blocking_mode", 0);
        super.build_phase(phase);
    endfunction

    task run_phase(uvm_phase phase);
        gem_reg_write_vseq vseq;
        phase.raise_objection(this);
        vseq = gem_reg_write_vseq::type_id::create("vseq");
        // 设置寄存器地址和数据
        vseq.reg_addr = 32'hA000_0000;
        vseq.reg_data = 64'hDEAD_BEEF;
        vseq.start(m_vseqr);
        phase.drop_objection(this);
    endtask
endclass

你看,只需要几十行代码,一个完整的测试用例就出来了。这就是模板化的好处。

4.3 断言与检查器编写

断言这东西,说白了就是给仿真装上一双眼睛。GEM的协议比较复杂,靠人工看波形根本盯不过来。我建议你从一开始就把断言写进去,别等到发现问题了再补。

4.3.1 立即断言 vs 并发断言

类型 使用场景 示例
立即断言 检查组合逻辑、状态机跳转 assert (state == IDLE);
并发断言 检查时序关系、协议握手 assert property (@(posedge clk) req |=> ack);

我个人更偏爱并发断言,因为它能描述时序关系。比如GEM的请求-应答协议,用一条|=> 就能搞定。

4.3.2 GEM协议断言示例

咱们写一个最常用的——请求后必须在5个时钟内收到应答:

property req_ack_timing;
    @(posedge gem_if.clk)
    disable iff (!gem_if.rst_n)
    gem_if.req |=> ##[1:5] gem_if.ack;
endproperty

assert_req_ack: assert property (req_ack_timing)
    else $error("Request not acknowledged within 5 cycles");
注意: 我曾经在项目里吃过一次亏——断言写得太松,结果漏掉了一个关键bug。后来我养成了一个习惯:每个断言都加上else分支,哪怕只是打印一条信息。这样至少能知道断言被触发了。

4.3.3 检查器编写

断言适合检查协议层面的东西,但有些功能检查需要更复杂的逻辑。这时候就要写检查器(checker)了。

检查器本质上是一个独立的验证组件,它监听接口上的信号,然后做数据比对。比如GEM的DMA传输,你需要检查传输的数据是否正确:

class gem_data_checker extends uvm_component;
    `uvm_component_utils(gem_data_checker)

    uvm_analysis_imp #(gem_transaction, gem_data_checker) m_imp;

    function void write(gem_transaction t);
        if (t.data !== t.expected_data) begin
            `uvm_error("DATA_CHECK", $sformatf(
                "Data mismatch: addr=%0h, got=%0h, exp=%0h",
                t.addr, t.data, t.expected_data))
        end
    endfunction
endclass

你看,检查器的核心就是一个write函数,每次收到transaction就做一次比对。简单、直接、有效。

4.4 本章知识体系

下面这张图把本章的核心内容串起来了,你可以对照着看看自己掌握了多少。

第4章 知识体系 SV/UVM基础 GEM测试模板 断言与检查器 接口与时钟块 Sequence机制 Base Test模板 用例继承 并发断言 检查器 核心思路:模板化 + 断言前置 + 检查器兜底 先写断言再写逻辑,让bug无处可藏

本章要点:

  • 用interface封装GEM接口信号,用clocking block控制时序
  • 测试用例模板化,公共逻辑放在base test里
  • 断言要写早、写全,每个断言都加上else分支
  • 检查器负责数据比对,和断言互补