验证方法论

第 5 章 · 共 15 章

Code Coverage:FSM

State coverage 与 transition coverage,给出精确的定义,直接建立在 axil_regfile 的 aw_have/w_have 锁存逻辑之上——其中一个状态在设计上就结构性地不可能到达,还有一个第 4 章 toggle 报告已经找到的真实缺口,这次用第三种方式再次现身。

第 1 章那份模拟报告里写的是 FSM Coverage: 50.0% (2/4 states, 3/6 transitions)。这一章把这一对数字讲精确——把这个项目已经很熟悉的逻辑——axil_regfileaw_have/w_have 锁存行为(uvm-advanced 第 1 章,也正是第 4 章发现从来没有翻转过的那两个信号)——明确地重新表述成一个小型有限状态机,给出命名的状态和它们之间命名的弧线。

给状态命名

aw_havew_have 是两个独立的 bit,但合在一起定义了一个状态:一次写操作里,如果有哪一边已经先到了,正在等另一边——是哪一边。理论上存在四种组合:

  • IDLEaw_have=0, w_have=0)——什么都没锁存;在等 AW、W,或者两者一起。
  • AW_ONLYaw_have=1, w_have=0)——AW 到了并被锁存了;还在等 W。
  • W_ONLYaw_have=0, w_have=1)——W 到了并被锁存了;还在等 AW。
  • BOTH_LATCHEDaw_have=1, w_have=1)——两边同时被锁存。

有意义的转移一共五个:IDLE→AW_ONLY(AW 单独到达)、IDLE→W_ONLY(W 单独到达)、IDLE→IDLE(两者一起到达,write_fire 直接触发,什么都不需要锁存)、AW_ONLY→IDLE(等待中的 W 终于到了)、W_ONLY→IDLE(等待中的 AW 终于到了)。

一个根本不可能存在的状态

BOTH_LATCHED 不在上面这份转移清单里,这不是漏写——RTL 让它在结构上根本不可能到达,这一点可以直接从它自己的逻辑(uvm-advanced 第 1 章)里证明出来:

assign write_fire = (aw_have || aw_fire) && (w_have || w_fire) && !s_axi_bvalid;
...
if (aw_fire && !write_fire) begin aw_addr_q <= s_axi_awaddr; aw_have <= 1'b1; end
if (w_fire  && !write_fire) begin w_data_q <= s_axi_wdata;   w_have <= 1'b1; end

假设 w_have 已经是 1(处在 W_ONLY 状态),然后 aw_fire 到达了。write_fire 的第二项 (w_have || w_fire) 这时已经是真的了——所以 write_fire 在同一个周期里、纯组合逻辑地就变成了高电平。这就让 aw_fire && !write_fire 变成假,于是 aw_have <= 1'b1 根本不会执行。反过来的情况(aw_have 已经是 1,然后 w_fire 到达)也是同样的道理被挡住。不存在任何一种事件顺序能让这两个 bit 同时都变成 1——一次写操作的第二边一出现,write_fire 就立刻触发,整件事在那一个周期里就解决了,根本不会走到两边都被锁存的地步。

这正是第 2 章 illegal_bins 概念在 FSM coverage 里的对应物:一个真实的覆盖率工具会把它整个从分母里排除掉,或者哪怕真的观察到它也会报一个错误——而不是当成一个需要补上的缺口,因为补上它就意味着真的有什么东西坏了。

State Coverage

FSM State Coverage Report -- axil_regfile.sv (aw_have/w_have)
================================================================
State           (aw_have, w_have)   Hits   Status
--------------  ------------------  ----  --------------------------
IDLE            (0, 0)                 7  covered
AW_ONLY         (1, 0)                 0  NOT COVERED
W_ONLY          (0, 1)                 0  NOT COVERED
BOTH_LATCHED    (1, 1)                 -  excluded -- structurally unreachable

IDLE 在复位时被访问一次,之后第 1 章那六次写操作每完成一次也会回到这个状态。AW_ONLYW_ONLY 都停在零——原因跟第 4 章发现 aw_have/w_have 从来没有翻转过一模一样:这个项目的测试历史里,从来没有哪一次让 AW 和 W 分别在不同的周期到达过。

Transition Coverage

单看 state coverage 分不清"访问了 IDLE 七次,每次都走同一条路"和"访问了 IDLE 七次,走了好几条不同的路"这两种情况。Transition coverage 能分清:

FSM Transition Coverage Report -- axil_regfile.sv (aw_have/w_have)
======================================================================
Transition            Hits   Status
---------------------  ----  ---------------------------------------
IDLE -> IDLE              6  covered  (AW and W arrive together)
IDLE -> AW_ONLY            0  NOT COVERED
IDLE -> W_ONLY             0  NOT COVERED
AW_ONLY -> IDLE            0  NOT COVERED
W_ONLY -> IDLE             0  NOT COVERED

这个项目的六次写操作,走的都是完全同一条弧线:IDLE→IDLE,一个自环——aw_firew_fire 同一个周期一起落地的瞬间,write_fire 就触发了,两个 bit 都不需要锁存。AW_ONLY/W_ONLY 的 state 命中数为零已经说明了问题,但 transition 报告说得更具体:不只是这两个状态没被访问过,真正跟它们有关的全部四条弧线——不管是进入还是离开——也都没被走过。一个设计理论上完全可能把每个状态都至少访问一次,却依然从来没有走过两个状态之间某一条特定的弧线;这个 DUT 的报告恰好同时暴露了两种失败,原因是同一个,但它们本来问的是两个不同的问题。

同一个缺口,被三种不同的方式找到

aw_have/w_have 的 toggle coverage 是 0%(第 4 章),AW_ONLY/W_ONLY 的 state 命中数是 0,五条转移里有四条从没走过——这是三份独立的覆盖率报告,指向同一个底层事实:这个项目发出过的每一次写,AW 和 W 都是一起到达的,所以专门为处理它们分开到达而写的那部分逻辑,一次都没有真正跑过。这种殊途同归不是冗余——toggle coverage 问的是一个 bit 的值有没有动过,state coverage 问的是一个命名的条件有没有为真过,transition coverage 问的是两个命名条件之间某一次具体的变化有没有发生过。三个不同的问题,被同一个真实的缺口都回答了"没有",这是单独任何一个指标都说不清楚的三个角度。

关于验证可行性的提醒

跟这个模块其他章节一样的限制:Icarus Verilog 完全没有 FSM coverage 统计功能(第 1 章说过,这本来就是商业工具才有的特性),所以上面这些报告是示意性的——是靠追踪 write_fire 真实的逻辑和这个项目真实的写操作历史推导出来的,不是工具跑出来的。一个商业级仿真器会直接从 aw_have/w_have 的用法里推断出这个状态机,并自动针对它生成报告。

小结

  • FSM coverage 把一个设计的控制状态重新表述成命名的状态、以及它们之间命名的转移——这里,aw_have/w_have 变成了一个有 4 个状态(IDLEAW_ONLYW_ONLYBOTH_LATCHED)、5 条真实转移的状态机。
  • 有些状态在结构上就不可达,不只是没测过而已。BOTH_LATCHEDwrite_fire 自己的定义就能证明是不可能的——这是第 2 章 illegal_bins 概念在 FSM coverage 里的对应物:从目标里排除掉,而不是一个需要补上的缺口。
  • State coverage(这个状态有没有被进入过)和 transition coverage(这条具体的弧线有没有被走过)问的是两个不同的问题——一个设计完全可能访问了每一个状态,却从来没走过它们之间某一条特定的弧线。
  • AW_ONLY/W_ONLY 以及跟它们相关的四条转移全都是零,原因跟第 4 章的 toggle 报告已经找到的一模一样:这个项目发出过的每一次写,AW 和 W 都在同一个周期到达。
  • 同一个真实的缺口在 toggle、state、transition coverage 里都现身,这不是冗余——每一个指标问的都是真正不同的问题,三个独立的角度都给出同样的答案,比单独任何一个指标都更有说服力。

一个设计在一次测试中把 FSM 里的每一个状态都至少访问了一次。这能保证 100% 的 transition coverage 吗?

为什么 BOTH_LATCHED(aw_have=1, w_have=1)会被从 state coverage 的目标里排除掉,而不是显示成一个需要补上的缺口?

AW_ONLY 和 W_ONLY 的 state 命中数都是 0,跟它们相关的四条转移也都是 0。根本原因是什么?

Toggle coverage(第 4 章)、state coverage、transition coverage 在 aw_have/w_have 上都报出了同一个底层缺口。这是不是说明其中两个指标是多余的?