3. 断言实战:assert property在UVM中的嵌入、断言覆盖率收集、断言与scoreboard的联动、常见断言编写错误

断言这东西,说白了就是给设计加个「电子警察」。你想想看,仿真跑了几百万个周期,靠人眼去波形里找bug?不现实。断言能帮我们在违规发生的第一时间抓住它,而且精确到哪个周期、哪个信号。

我个人习惯,在项目一开始就把断言框架搭好。别等到验证后期再补,那时候改RTL的成本太高了。好,我们直接进入正题。

3.1 assert property在UVM中的嵌入方式

SystemVerilog的断言有两种:立即断言和并发断言。在UVM里,我们主要用并发断言,也就是assert property。嵌入方式有三种,我一个个说。

方式一:直接在interface里写

这是最常用的做法。把断言写在interface里,跟接口信号绑定在一起。UVM的driver和monitor通过virtual interface来驱动和采样,断言自然就能看到所有信号变化。

interface my_bus_if (input clk, input rst_n);
    logic [31:0] addr;
    logic        valid;
    logic        ready;

    // 断言:valid拉高后,ready必须在3个周期内拉高
    property p_valid_to_ready;
        @(posedge clk) disable iff (!rst_n)
        valid |-> ##[1:3] ready;
    endproperty

    assert property(p_valid_to_ready)
        else $error("valid拉高后ready未在3周期内响应");
endinterface

我在项目中遇到过一个问题:把断言写在module里,结果UVM的virtual interface绑定时出了岔子。后来统一改到interface里,世界清净了。

方式二:在UVM component里用`uvm_assert

UVM也提供了宏来包装断言,比如`uvm_assert。这种方式的好处是能跟UVM的报告机制整合,打印的信息更规范。

class my_monitor extends uvm_monitor;
    virtual my_bus_if vif;

    task run_phase(uvm_phase phase);
        forever begin
            @(posedge vif.clk);
            `uvm_assert(vif.valid & vif.ready, 
                       "valid和ready同时拉高,传输完成")
        end
    endtask
endclass

不过说实话,我个人不太喜欢这种方式。因为断言本身是声明式的,写成过程代码反而失去了「自动检查」的味道。你想想看,用assert property是硬件思维,用`uvm_assert是软件思维,混在一起容易乱。

方式三:用bind语句注入

这是最优雅的方式。不改动原有代码,通过bind把断言模块「贴」到设计上。适合在验证后期追加检查点。

bind dut my_assert_module assert_inst (.clk(clk), .rst_n(rst_n), .*);

嗯,这里要注意:bind的模块里不能有initial块,否则仿真器会报错。我吃过这个亏,排查了半天。

3.2 断言覆盖率收集

断言覆盖率,说白了就是「你的断言被触发了多少次」。这个指标很重要。如果某个断言从来没被触发过,有两种可能:要么设计没问题,要么你的测试根本没跑到那个场景。

UVM里收集断言覆盖率,我一般用两种方法。

方法一:利用仿真器的内置功能

大多数仿真器(VCS、Xcelium、Questa)都支持自动收集断言覆盖率。你只需要在仿真命令里加个选项:

# VCS
vcs -assert coverage -cm assert ...

# Questa
vsim -assertcover ...

仿真结束后,会生成覆盖率数据库。用verdi或simvision打开,能看到每个断言被hit了多少次。

方法二:在UVM里手动收集

有时候我们需要在仿真过程中实时知道断言覆盖率。我会在scoreboard里维护一个覆盖率表。

class coverage_collector extends uvm_component;
    int assert_hit_count[string];

    function void report_phase(uvm_phase phase);
        foreach(assert_hit_count[name]) begin
            `uvm_info(get_type_name(), 
                     $sformatf("断言 %s 被触发 %0d 次", 
                              name, assert_hit_count[name]), 
                     UVM_MEDIUM)
        end
    endfunction
endclass

我曾经在一个项目里发现,某个关键断言覆盖率一直是0。查了半天,原来是测试用例的约束写错了,导致那个场景永远跑不到。所以断言覆盖率真的能帮你发现测试的盲区。

3.3 断言与scoreboard的联动

断言和scoreboard,一个抓时序违规,一个抓数据错误。两者配合,才能全面覆盖。

我常用的联动方式是这样的:断言发现违规后,直接通知scoreboard,让scoreboard标记当前事务为「无效」或「异常」。

class my_scoreboard extends uvm_scoreboard;
    event assert_fail_event;

    task run_phase(uvm_phase phase);
        forever begin
            @(assert_fail_event);
            // 断言失败,标记当前事务
            current_tr.status = FAILED;
            // 可以在这里记录日志或触发额外检查
        end
    endtask

    function void notify_assert_fail();
        -> assert_fail_event;
    endfunction
endclass

然后在断言里调用这个函数:

assert property (p_valid_to_ready)
    else begin
        $error("断言失败");
        scoreboard.notify_assert_fail();
    end

为什么要联动?因为有时候断言报错了,但scoreboard还在傻傻地比对数据。你想想看,时序都错了,数据比对还有意义吗?直接标记异常,避免误报。

3.4 常见断言编写错误

写断言看着简单,坑其实不少。我列几个最常见的,都是血泪教训。

错误类型 错误示例 正确写法 说明
忘记disable iff @(posedge clk) a |-> b @(posedge clk) disable iff (!rst_n) a |-> b 复位期间断言会误报
时序范围写反 ##[3:1] ##[1:3] 范围必须从小到大
组合逻辑里用断言 @(*) a |-> b 改用@(posedge clk) 组合逻辑断言容易产生毛刺误报
$past使用不当 $past(a, 0) $past(a, 1) $past的第二个参数最小是1
我曾经踩过的坑: 在断言里用了$past(a, 2),但没考虑时钟沿对齐问题。结果仿真波形上看a确实满足条件,但断言就是报错。后来发现是$past取的是两个时钟周期前的值,而我的信号是在时钟下降沿变化的。记住,$past默认是相对于采样沿的。

3.5 本章小结

断言这东西,写起来不难,但写好需要经验。我建议你从简单的开始,比如先给接口加上valid-ready握手检查。跑通了再慢慢加复杂的时序约束。

另外,断言覆盖率一定要看。不看覆盖率,你都不知道自己的断言是不是在「自娱自乐」。嗯,今天就到这里,下一章我们聊形式化验证和断言的结合。

核心要点:
  • 断言嵌入推荐用interface或bind方式
  • 断言覆盖率是测试质量的「照妖镜」
  • 断言和scoreboard联动,避免无效比对
  • 常见错误:忘记disable iff、时序范围写反、$past参数错误
小技巧: 写断言时,先画时序图,再写property。时序图画清楚了,断言基本不会错。我每次写复杂断言前,都会在纸上画个波形草图。
断言实战知识体系 断言实战 嵌入方式 ① interface内嵌 ② UVM component宏 ③ bind语句注入 覆盖率收集 ① 仿真器内置功能 ② UVM手动收集 与scoreboard联动 ① 断言失败通知scoreboard ② 标记事务状态为异常 ③ 避免无效数据比对 常见编写错误 ① 忘记disable iff ② 时序范围写反 ③ $past参数错误

可可精品资料库