简介:OVL(Open Verification Library)开放验证库源代码包,面向基于ARM核的SoC设计与验证工程师,用于在硬件验证流程中快速搭建断言检查与功能覆盖环境。压缩包共314个文件,以Verilog(131个)、SystemVerilog(65个)、VHDL(40个)等设计源码为主,辅以vlib/psl断言描述文件、头文件及4份PDF快速参考手册,整体仅1.46MB,目录结构清晰,便于直接集成到现有验证平台。包内std_ovl组件可检测时序违例、数据竞争、信号同步等常见设计错误,支持参数配置和自定义扩展,并能与UVM等主流验证框架协同工作,帮助工程师减少手写断言代码的工作量,提升验证完备性。附带PDF手册覆盖常见断言用法与调试要点。已有779人学习下载,适合具备一定硬件验证基础、希望提高ARM核项目验证效率的工程师参考。
1. OVL库到底是什么:先搞清它在 ARM 核项目里解决什么问题
做 ARM 核 SoC 验证的人,大概率都遇到过这种场景:软件跑到一半,某个外设行为不对,打开波形找半天,发现一个使能信号高电平比协议规定多撑了三个时钟周期,仿真波形里不放大根本看不出来;或者复位释放后,状态机进了一个代码里根本不存在的状态。这类问题靠功能测试用例很难触发,靠人眼盯波形又太费劲。OVL 库(Open Verification Library)解决的就是这件事:它是一套用纯 HDL 写成的标准断言组件库,把“信号永远为真”“信号不得为真”“高电平宽度不超过 N 拍”“状态编码必须 one-hot”这类时序与逻辑检查封装成可以直接例化的模块,插进 RTL 里,仿真器一跑,违规就按设定级别打印报告。OVL 的可综合性还让它在 ARM 核项目里有一个特殊价值:不仅能用于仿真,还能综合进 FPGA 原型,把断言变成片上硬件监视器,软件跑着跑着触发了断言,直接通过中断或 GPIO 反馈出来。这篇文章按“原理 → 源码结构 → 最小集成 → ARM 核典型用法 → 避坑 → 进阶技巧”的顺序,把 OVL 库的落地路径完整讲清楚。
2. OVL 的设计逻辑:为什么 ARM 核场景里值得加这一层监视
2.1 断言组件拆解:从 assert_always 到 assert_change,一张表看懂
OVL 库的核心不是某一个模块,而是一整套按行为语义分类的断言组件。理解了这套分类,拿到源码时就不会觉得目录里的文件是一堆黑匣子。常见做法是按检查行为分成以下几类:状态保持类、反向状态类、宽度窗口类、变化检测类、编码约束类、协议握手类。每一类对应一个或几个标准组件,组件的命名本身就把语义写清楚了。
| 类别 | 代表组件 | 检查行为 | ARM 核典型场景 |
|---|---|---|---|
| 状态保持 | assert_always | 指定信号在每个时钟周期都必须为真 | 总线桥使能信号、时钟门控条件 |
| 反向状态 | assert_never | 指定信号在任何时候都不得为真 | 非法状态、读写冲突、只读寄存器被写 |
| 宽度窗口 | assert_width | 信号高电平宽度不超过指定周期数 | 中断脉冲宽度、复位释放后的稳定窗口 |
| 变化检测 | assert_change / assert_no_transition | 信号在窗口内必须变化或禁止变化 | 配置寄存器锁定后不得翻转 |
| 编码约束 | assert_one_hot / assert_range / assert_parity | 状态编码、数值范围、奇偶校验 | 中断状态寄存器编码、地址偏移范围 |
| 协议握手 | assert_handshake / assert_frame | 请求应答时序、帧内信号次序 | APB/AHB 传输、外设读写握手 |
每个组件都是一段可综合的 RTL,内部用计数器、状态寄存器和比较逻辑实现检查。相比手写 SVA,OVL 的好处是接口统一、参数统一、报错格式统一。你不需要为每个检查点重新设计一套检查逻辑,只选组件、配参数、连信号。而且 OVL 是跨仿真器的,换仿真器环境时断言代码不用重写。ARM 核 SoC 里外设多、总线上挂的设备杂,这套“插桩式”断言对快速定位问题特别有用。
2.2 参数体系:severity_level、property_type、fire_time 怎么配合
OVL 组件的参数设计很克制,核心就几个,但组合起来能覆盖绝大多数检查需求。width是检查位宽或计数宽度,severity_level决定违规时打印的报告级别,property_type决定这个断言的行为模式,fire_time控制违规信号出现后延迟几个周期再报告,msg是自定义消息文本,coverage_level控制是否收集覆盖率。
ovl_width #( .width(16), .severity_level(2), .property_type(1), .fire_time(3), .msg("APB bridge count_en width check") ) u_ovl_width_check ( .clock(clk), .reset_n(rst_n), .enable(1'b1), .start_event(count_en), .test_expr(count_en), .fire(fire_sig) );这段例化展示了参数配合的典型方式:severity_level = 2在 OVL 标准里对应 ERROR 级报告,仿真器收到后会在 transcript 里打印明显的错误标识,但不会立刻停止仿真;property_type = 1表示把它当断言使用,违规即报;fire_time = 3表示信号连续违规 3 个时钟周期后才触发报告,这样能过滤掉单周期抖动造成的假报警。fire输出信号可以接出来,在仿真里看波形,在 FPGA 原型里接到中断或 GPIO。
参数调整时最容易忘的是severity_level和property_type的搭配。property_type有四种值:0 表示禁用该断言,1 表示断言模式,2 表示只报告不计数,3 表示作为 formal 验证的 assume 约束。如果用 0,整个检查器等于不存在,仿真器和综合工具都会把它优化掉;如果用 3 但后端只跑仿真,那这个组件不会主动报错,它只是给形式化验证工具提供约束。建议在 RTL 里统一用宏定义管理这套参数,方便后期整体切换模式。
2.3 仿真与综合两条使用路径:ARM 核 SoC 为什么特别适合 OVL
OVL 库一个容易被忽略的特性是它可以综合。SVA 断言在仿真里很强大,但它是仿真器层面的构造,综合工具不认识它。OVL 不一样,它本身就是 RTL,描述的是实实在在的硬件逻辑,因此能够跟着设计一起综合进 FPGA 或流片。ARM 核项目几乎都有 FPGA 原型验证阶段,OVL 是少数能从仿真一路带到硬件原型的断言方案。
在仿真路径里,OVL 组件输出报告、记录违规次数;在综合路径里,OVL 组件变成真实的监视电路,fire信号是现实存在的电平。把这个电平引到 ARM 核的中断控制器或 GPIO,软件就可以在运行时读到断言结果。比如某个外设的时序在真实硬件上偶发违规,软件捕获中断后打印日志,就能把“波形里很难复现的问题”变成“寄存器里的一个事件”。这条路径对 ARM 核软硬件协同验证特别适合,因为它把验证工程师的断言和嵌入式工程师的调试手段打通了。
综合路径要注意一点:OVL 组件综合后是有面积代价的,单个监控器通常只需要几十个 LUT 或等效门,几十个监控器对一颗 FPGA 原型来说完全可接受,但如果打算流片时把 OVL 留在 RTL 里,需要提前评估面积和功耗。常见做法是流片版本里用property_type = 0或宏开关把断言全部禁用,综合工具会自动优化掉这些逻辑。
3. 拿到 OVL 源码并跑通最小工程:从目录结构到第一个可用的断言
3.1 源码目录与组件映射:先认清库结构再写代码
下载到的 OVL 发布包通常按语言分两大块:Verilog 实现和 VHDL 实现,二者行为完全等价。以 Verilog 实现为例,目录里最常见的是每个组件一个独立文件,文件名和组件名一一对应,类似ovl_always、ovl_never、ovl_width这样的命名方式。除组件文件外,还有一个公共宏定义头文件,所有组件都依赖它,里面定义了报告格式、消息打印函数和内部参数映射。这个头文件是整个库最基础的部分,编译时必须最先处理。
组件本身不是孤立的。OVL 内部有一套完整的宏体系,用来统一处理时钟、复位、报告输出和违规计数。使用 OVL 时不需要理解每个宏的实现细节,但要知道组件文件之间的依赖关系。有些组件比如assert_handshake内部可能调用更底层的辅助宏,发布包的编译脚本里会体现这种顺序。常见做法是直接用发布包自带的编译脚本,或者在自己的工程里把整个 OVL 源码目录作为一个编译单元整体加入,而不是只挑个别文件。如果只编译用到的组件文件,可能漏掉宏定义而产生诡异报错。
源码结构这一块不需要背,关键是建立“组件名 + 行为语义 + 参数接口”的映射关系。拿到一个组件文件,先看文件头部的参数声明和端口列表,再看例化示例,比直接通读内部实现效率高得多。OVL 的接口风格高度统一,一组接口熟悉了,整个库就通了。
3.2 最小断言实例:给 ARM 核外设总线加一个宽度检查
现在实际动手例化一个组件。以监控 ARM 核总线下某个外设的计数器使能信号为例,要求是count_en高电平宽度最多允许 8 个时钟周期,超过就报错。这个检查用ovl_width最直观:start_event给出窗口起始脉冲,test_expr是被检查信号,width是允许的最大宽度。
// 顶层模块内例化 OVL 宽度检查器 // 检查对象:外设计数使能信号 count_en // 约束:高电平宽度不超过 8 个时钟周期 ovl_width #( .width(8), // 最大允许宽度,超过即违规 .severity_level(2), // ERROR 级报告 .property_type(1), // 断言模式 .fire_time(1), // 违规后延迟 1 拍报告,错开 start_event 采样沿 .msg("count_en width exceed 8 cycles") ) u_width_count_en ( .clock(clk), // 与 ARM 核外设总线同源时钟 .reset_n(rst_n), // 低有效复位,与系统复位同步 .enable(1'b1), // 常开,不做条件过滤 .start_event(count_en), // 窗口起点:count_en 拉高 .test_expr(count_en), // 被检查信号:count_en 本身 .fire(fire_width) // 违规时拉高一个周期 );逻辑上,ovl_width内部在start_event有效时开始计数,只要test_expr保持为高,计数器每个周期加一;当计数值超过width设定值时,fire输出拉高并打印报告。参数说明里值得注意两点:start_event和test_expr在示例里都是count_en,这意味着计数器使能信号拉高的同一个周期内开始计数,检查的是连续高电平宽度;fire_time = 1让报告延迟一拍输出,避免在start_event本身的采样沿产生竞争。若把fire_time设成 0,不同实现版本语义有差异,有的立即报,有的直接禁用报告,建议统一用 1 以上。
这个模块在仿真环境里就能工作。把 RTL 和 OVL 源码一起编译进仿真工程,跑一段软件激励,如果count_en真的超过 8 拍,transcript 里会出现带msg文本的 ERROR 报告,时间戳精确到违规周期。到了 FPGA 原型阶段,fire_width可以接出来做硬件指示。
3.3 仿真编译与运行:主流仿真器都适用的一套流程
OVL 源码跟厂商无关,主流仿真器都能编译。以下是一套通用流程,仿真器命令不同但思路一致:
# 1) 先编译 OVL 公共宏定义文件 # 注意:宏文件必须最先编译,否则组件文件会报未定义错误 vlog -work ovl \ +define+OVL_VERILOG \ ../ovl/src/ovl_macros.vh # 2) 编译整个 OVL 组件源码目录 # 用通配符批量加入,组件间依赖由编译顺序保证 vlog -work ovl ../ovl/src/ovl_assert_*.v # 3) 编译你的 RTL、OVL 例化层和 testbench # -incdir 指向宏文件所在目录 vlog -work work \ +incdir+../ovl/src \ ../rtl/apb_slave.v \ ../rtl/top_ovl_wrapper.sv # 4) 运行仿真,跑完自动退出 vsim -c work.tb_top -do "run -all; quit"这段流程的核心逻辑:先用独立 work 库收集 OVL 源码,把库结构和你自己的 RTL 分开,这样增量编译时不用反复重新编译 OVL;再用+incdir把宏文件目录加入搜索路径,让 RTL 里的例化代码能引用到宏定义。如果用的是 VCS,把vlog换成vcs -sverilog,把-work ovl换成-y ../ovl/src +libext+.v;用 Xcelium 则把vlog换成xrun -sv,思路完全一样。
编译失败时优先看两类错误:一是找不到宏定义,二是组件端口不匹配。前者检查编译顺序和+incdir路径;后者检查端口名和位宽,OVL 组件端口名在不同版本里略有差异,以你实际拿到的源码为准。把 OVL 当黑盒用没问题,但端口名还是建议打开组件文件确认一遍,这个动作能省掉不少排查时间。
3.4 综合到 FPGA 原型:keep 属性与综合约束
OVL 可综合是它区别于 SVA 的最大亮点,但代价是综合工具经常把断言逻辑当冗余优化掉。工具的逻辑是:fire信号不扇出到任何功能性路径,属于“死逻辑”,剪掉它不影响设计功能。这在纯仿真里无所谓,但 FPGA 原型里要读fire信号,必须阻止优化。
// 方式一:Vivado / 通用 Verilog 工具用 keep 属性 (* keep = "true" *) wire fire_width; // 方式二:Design Compiler 风格用 syn_preserve // (* syn_preserve = "true" *) wire fire_width; // 方式三:把 fire 引到 SoC 顶层 GPIO 或中断输入 ovl_width #(...) u_width_count_en ( ... .fire(fire_width) ); assign soc_gpio_irq[0] = fire_width;这里的关键不只是给fire加属性,还要给 OVL 内部的关键计数寄存器加类似的 preserve 约束。常见做法是直接对 OVL 组件实例加综合属性,有些工具支持在不改源码的前提下通过综合属性文件指定:DC 里可以用set_preserve true [get_cells u_width_count_en/*],Vivado 里可以用set_property KEEP true [get_nets fire_width]。最省事的办法是把fire接到真实端口,制造合法的扇出,大部分工具就会保留整条逻辑链。
综合路径的另两个注意点是复位和时钟。OVL 组件内部有状态寄存器和计数器,复位必须真实存在,不能悬空;如果设计里有多个时钟域,OVL 组件只能采样单一时钟,跨时钟域信号要先同步再送入断言。FPGA 原型阶段建议把复位释放后前几拍的断言通过enable关掉,避免上电瞬间误报,这个习惯能省很多排查时间。
4. ARM 核协同验证的三种典型用法:总线、外设与固件接口
4.1 总线级:APB 访问时序的协议约束
ARM 核 SoC 里大量外设挂在 APB 总线上,APB 协议的特点是写操作时PSEL和PENABLE有严格时序:PENABLE拉高期间PSEL必须保持有效,传输结束前二者都不能提前释放。这个约束用assert_never检查最合适,把违规条件组合成一个信号,违规时断言报告。
// 检查 APB 写传输:PENABLE 高电平期间 PSEL 不得拉低 wire apb_psel_drop_violation; assign apb_psel_drop_violation = PENABLE && !PSEL; ovl_never #( .width(1), .severity_level(2), .property_type(1), .fire_time(1), .msg("APB: PSEL dropped while PENABLE high") ) u_apb_psel_drop ( .clock(pclk), .reset_n(preset_n), .enable(apb_transfer_active), .test_expr(apb_psel_drop_violation), .fire(apb_fire) );assert_never的语义是test_expr在任意时钟周期都不得为真,一旦为真就触发报告。这里把违规条件定义为PENABLE && !PSEL,只要这个组合出现,就是协议违规。enable用apb_transfer_active控制,只在总线使能时检查,避免空闲状态下的无效计数。这种总线级断言的价值在于:ARM 核侧的 DMA 或 CPU 访问模式一旦写出非法时序,仿真日志直接指明违规接口和报文,而不是让你在波形里从头追。
总线级还可以扩展检查写保护:对只读寄存器地址段,把“写选通有效且地址落在只读段”组合成违规条件,一个assert_never就能挡住固件误写。这类断言对早期 bring-up 尤其有用,固件里一个错误的指针解引用,总线断言比硬件看门狗更快暴露问题。
4.2 外设级:看门狗与中断控制器的运行时监控
ARM 核的中断控制器和看门狗是软件时序依赖很重的模块。中断线拉高后,软件要在中断服务程序里清标志,如果响应时间异常,高电平宽度就会超标。这个检查用ovl_width正合适,width设成软件承诺的最大响应周期数,超过即报。
// 检查:外设中断线高电平宽度不超过 16 个时钟周期 // 超过说明 CPU 中断响应或清标志逻辑异常 ovl_width #( .width(16), .severity_level(2), .property_type(1), .fire_time(1), .msg("periph IRQ line high too long") ) u_irq_width_check ( .clock(clk), .reset_n(rst_n), .enable(1'b1), .start_event(periph_irq), .test_expr(periph_irq), .fire(irq_fire) );这个例子把 OVL 从“验证代码”变成了“运行时监控器”。仿真阶段它检查软件行为,到了 FPGA 原型阶段,irq_fire直接驱动板上 LED 或 ARM 核的中断输入,嵌入式工程师调试驱动时能实时看到“中断响应超标”事件。
外设级另一类典型检查是状态编码。中断控制器内部有一套中断状态寄存器,合法状态通常要求 one-hot 编码。assert_one_hot组件能检查 8 位或更宽的状态向量,非法编码立刻报告。ARM 核的中断优先级仲裁状态机一旦进入非 one-hot 态,后续行为完全不可预测,这类断言是防止“偶发死机查不出原因”的有效手段。
4.3 固件级:寄存器访问边界与值域约束
ARM 核软件访问外设寄存器时,最容易踩的坑是写入了超出硬件支持范围的值。硬件对超范围值的行为往往是截断或未定义,问题会延迟到后续模块才显现。OVL 在固件级检查上的思路是:在寄存器写数据通路上加值域断言。
// 检查:写 DMA 描述符地址偏移时,值域不得超过 0x1FFF // 超过说明固件配置了非法描述符,后续 DMA 搬运必然出错 ovl_range #( .width(32), .min(32'h0000_0000), .max(32'h0000_1FFF), .severity_level(2), .property_type(1), .fire_time(0), .msg("DMA desc offset out of range") ) u_dma_offset_range ( .clock(clk), .reset_n(rst_n), .enable(dma_cfg_wr_valid), .test_expr(dma_cfg_wr_data), .fire(dma_range_fire) );ovl_range检查test_expr是否落在[min, max]闭区间内,enable用寄存器写有效信号控制,只有真正发生写操作时才检查,避免在test_expr为任意值时误报。这个断言的典型价值是:DMA 描述符地址越界这类问题,在纯仿真里可能表现为“数据写错了地方但功能还能跑”,加了值域断言后,固件一执行非法写,验证日志立刻定位到写事务的源和值,而不是等后续模块表现异常再反向排查。
固件级还有一种更细的断言:寄存器位段只读检查。ARM 核外设寄存器经常有保留位或只读位,固件写这些位时硬件行为未定义。把“写选通 && 写入位非零 && 对应位是只读位”组合成违规条件,用assert_never监控,可以把固件里不小心写保留位的操作全部抓出来。这类断言在验证阶段跑全量回归时非常有效,能积累一份“固件非法访问清单”,交给软件团队逐条修复。
5. OVL 实战避坑:五个高频翻车点的现象、原因与处理
5.1 fire_time = 0 的语义在不同实现里不一样
现象:把fire_time设成 0,以为违规信号出现的当拍就会报错,实际行为却是断言完全沉默,或者报错时间点总在下一拍,跟预期完全对不上。
原因:OVL 组件里fire_time为 0 的含义在不同发布版本里并不统一,有的实现把 0 当作“禁用报告”,有的当作“立即报告”,还有一种当作“非法参数”并打印警告。这类语义差异最容易在换版本时坑人。
解决:不要依赖fire_time = 0的语义。统一设成 1 或更大的正整数,表示“违规稳定持续 N 拍后报告”。如果想过滤抖动就设大一些,如果想尽早报就设 1。设置后打开组件源文件确认一下实现,看内部比较逻辑是大于还是大于等于,这个确认动作能避免后续排查白费功夫。
5.2 复位释放那一拍断言乱报
现象:上电复位释放的瞬间,断言组件的fire信号莫名其妙拉高,波形上看被检查信号明明是正常状态。这类误报在多次上电测试中并不稳定,很玄学。
原因:复位释放后,芯片内部时钟树上各触发器的复位释放存在轻微偏斜,OVL 组件和被检查信号来自不同触发器时,第一个采样沿可能采到未稳定的中间态。另一个常见诱因是复位释放后立即出现短暂毛刺,恰好被断言当作有效电平。
解决:对复位后早期时段做屏蔽。把断言组件的enable连到一个“复位释放后延迟 N 拍才有效”的信号上,常用的写法是把复位信号打两拍,取第二拍的反相作为enable。如果必须从复位释放后的第一拍就开始检查,就给被检查信号加一级同步寄存器,用同步后的信号作为test_expr,代价是断言时间点后移一拍,但可靠性高得多。
5.3 综合后断言被优化掉
现象:仿真里断言报错没问题,综合进 FPGA 原型后fire信号永远为 0,软件读状态寄存器也读不到任何断言事件。检查属性设置又看不出问题。
原因:综合工具把不扇出到功能路径的逻辑判定为死逻辑,OVL 组件整个被优化。fire信号如果不接出去,工具的理由很充分:这个信号没人用,留着浪费面积。
解决:三层手段叠加。第一层给fire相关网络加综合保留属性,Vivado 用keep,DC 用syn_preserve;第二层把fire接到 SoC 顶层的中断输入或 GPIO 输出,制造真实扇出;第三层在综合脚本里对 OVL 实例设置 don_touch 属性。做完这三件事后,在综合报告里搜索器件类型为ovl_的模块,确认存在它们。这条检查是最容易遗漏的,因为综合工具不会报错,它只是安静地把东西删了。
5.4 跨时钟域信号直接接入断言
现象:ARM 核 SoC 里总线桥前后是两个时钟域,桥内部中间信号直接接到 OVL 组件,仿真出现大量随机误报,波形看不出来任何违规,重新跑一次结果还不一样。
原因:OVL 组件是单时钟采样逻辑,跨时钟域信号存在亚稳态窗口,采样到的值是不确定的。断言建立在不确定的值上,自然随机报错。这属于 CDC 问题,不是 OVL 本身的问题。
解决:跨时钟域信号必须先经过两级同步器,再送入 OVL 组件。如果检查的对象是跨时钟域协议时序,把断言放在目的时钟域同步链之后,检查同步后的信号;如果一定要检查源时钟域的原始时序,就把 OVL 组件的时钟改成源时钟,test_expr保持原始信号,不要在同一个组件里混两个时钟域。这条规则和普通 RTL 设计的 CDC 规则完全一致,OVL 不会因为它是断言就豁免亚稳态。
5.5 severity_level 设成 FATAL,回归测试变成灾难
现象:把关键断言的severity_level设成 3(FATAL 级),仿真时一个断言违规,仿真器立刻全局退出,波形没保存,日志只有一条报错。多次上电回归测试反复被中断,没人愿意跑。
原因:FATAL 级让仿真器在违规点立即停止并结束会话,这在 ARM 核 bring-up 早期是灾难。上电初始化阶段固件对总线的访问本来就可能有大量非关键违例,比如初始化顺序和硬件预期不一致,这些违例在后续执行中会被修正,但 FATAL 级别不给修正的机会。
解决:前几轮回归用severity_level = 1或2,让仿真跑完并收集完整违规列表,逐一分析后再决定哪些断言需要升级为 FATAL。同时配置仿真器在 FATAL 触发前自动保存波形快照,不同仿真器都有类似assertFatal -file snapshot的选项,提前设置好能保留现场。血泪经验是:FATAL 留在最终冒烟回归里把关,不要在开发初期就打开。
6. 进阶:把 OVL 当成片上调试探针,事件滤波与状态快照联动
OVL 用到后期,价值早就超出“验证”本身。在 ARM 核 FPGA 原型验证阶段,我习惯把 OVL 的fire信号统一接入中断控制器的备用中断线,每个监控器占一条。嵌入式软件里维护一张“断言事件表”,中断触发后读硬件寄存器确定是哪个监控器报的,再把这个事件和时间戳写进软件日志。这样一来,OVL 就从被动检查变成了主动的片上调试探针。
事件滤波是这层用法里的关键参数。真实硬件上的偶发抖动和毛刺会让断言过度敏感,fire_time可以作为滤波参数使用:设成 4,表示信号连续违规 4 拍才上报,软件侧读到的都是稳定违规,不会为单周期毛刺反复进中断。另一个实用技巧是把断言组件的enable端口接到软件可写的控制寄存器,由固件决定哪些监控使能、哪些关闭。系统休眠或低功耗模式下自动关闭非关键断言,跑性能测试时只留总线协议断言,这样监控不影响测试结果。
// 软件可控制监控使能:由 pinmux 控制寄存器 bit0 决定 wire sw_mon_en = pinmux_ctrl_reg[0]; ovl_never #( .width(1), .severity_level(1), .property_type(1), .fire_time(2), .msg("SW-controlled: illegal state detected") ) u_sw_mon_never ( .clock(clk), .reset_n(rst_n), .enable(sw_mon_en), .test_expr(illegal_state), .fire(sw_mon_fire) );这个例子里severity_level = 1对应 WARNING 级,不会打断主流程,fire_time = 2滤波,enable由软件寄存器控制。硬件上最值得监控的那些状态,比如中断状态机的非法编码、总线桥的读写冲突、电源域切换时的信号时序,都放进这张表里。我曾经在某个项目的 ARM 核上追一个偶发中断丢失,波形看了三天没有结论,后来就是把 IRQ 状态机加了一个assert_never,fire接到板上 LED,带测试固件跑了一整夜,第二天早晨看到 LED 亮着,顺着时间戳一次就定位到了问题。那种“它明明违规了但完全没留下现场”的痛,经历一次就会养成一个习惯:任何关键的硬件状态,都留一个 OVL 探针把fire引出来。希望帮到你。
本文还有配套的精品资源,点击获取