news 2026/10/22 0:54:12

FIFO UVM验证环境全解析:从拆包到覆盖率收敛的实战笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FIFO UVM验证环境全解析:从拆包到覆盖率收敛的实战笔记

简介:面向数字IC验证初学者与进阶工程师,一套基于UVM通用验证方法学的异步FIFO验证工程,从UVM环境配置、激励序列创建、断言检查,到CDC跨时钟域处理、覆盖率收集与回归测试均有对应代码与说明,适合作为SystemVerilog验证方法学的参考项目。压缩包共274个文件,约8.48MB,核心包括19个sv验证源码、6个v设计文件、17个log仿真日志、20个db与14个sdb文件,另附VCS/Verdi生成的波形、覆盖率报告、HTML汇总页和PDF说明;其中html、js、css文件用于生成可视化的验证报告,便于逐项核对仿真结果。已有1638人学习浏览。资源还提供makefile、filelist、配置文件等可直接执行的工程骨架,以及可复现的仿真流程;配合txt笔记、png示意图和Verdi波形,可快速理解异步FIFO读写指针同步、空满判断与亚稳态处理的具体实现。对正在筹备UVM项目实践或数字IC验证笔试面试的读者,是一套包含运行日志、覆盖率统计与源码注释的闭环练习素材。

1. 拿到 fifo_UVM.zip:先弄清楚这是一个什么样的验证项目

拿到 fifo_UVM.zip 这个压缩包,第一反应别急着解压跑仿真,先想清楚里面大概率是什么:一个基于 UVM 验证方法学的 FIFO 验证环境,通常由 RTL 设计、UVM 测试平台源码和仿真脚本三部分组成。FIFO 是数字芯片里最基础的队列结构,UVM 是验证工程师的通用语言,把它俩打包成一个 zip,多数时候用于课程训练、求职作品或团队内部复用。这个包能解决什么问题?给你一套完整的 FIFO 验证框架,让你从环境搭建、激励产生到覆盖率收敛有一条可复现的路。适合刚接触 UVM 的开发者和准备验证岗位面试的人。这篇笔记按我搭过同类环境的经验展开,从拆包到跑通,再到把验证做收敛,过程中该设的参数和该躲的坑都会讲到。

2. 拆开 fifo_UVM.zip:验证环境的组件关系与 FIFO 测试难点

2.1 先看文件布局:从目录结构反推验证策略

压缩包解压后我习惯先看目录,不着急编译。一个结构清晰的 FIFO 验证环境长这样:

fifo_UVM/ rtl/ fifo_sync.v tb/ fifo_if.sv fifo_transaction.sv fifo_driver.sv fifo_monitor.sv fifo_scoreboard.sv fifo_agent.sv fifo_env.sv fifo_test_base.sv tc/ fifo_smoke_test.sv fifo_random_test.sv fifo_boundary_test.sv filelist.f run/ run.do

这个布局说明验证策略是分层的:RTL 与 TB 分离,测试用例独立放,仿真脚本集中管理。UVM 环境的骨架无非是 test 下面挂 env,env 里面挂 agent 和 scoreboard,agent 里面再挂 driver、sequencer 和 monitor。FIFO 工程虽小,但这个骨架是标准化的,换到 AHB 或 AXI 接口验证时,结构上几乎不用动。

如果拿到手的 zip 把所有文件平铺在一个目录里,没有独立 tc 目录,也没有顶层 test 的继承关系,那大概率是精简 demo。精简 demo 不是不能跑,而是后面加用例时改动成本高——改环境会牵连用例,改用例又要动环境。分层不是形式主义,是把「环境」和「场景」解耦的唯一手段。你后面每加一个测试用例,都应该只是新增一个 test 类和一个 sequence 类,而不是去碰 driver 或 scoreboard。

2.2 同步 FIFO 与异步 FIFO:验证环境的选型分叉

打开 rtl 目录先确认是同步 FIFO 还是异步 FIFO,这直接决定验证环境的工作量。同步 FIFO 只有一个时钟,读写指针在同一个时钟域,空满判断相对直接;异步 FIFO 跨两个时钟域,读写指针要各自同步到对侧时钟域,空标志在读时钟域产生、满标志在写时钟域产生,验证模型的复杂度明显上了一个台阶。

同步 FIFO 的验证重点有三个:读写同时发生时指针更新的竞争、满空标志与读写使能的时序关系、深度参数化带来的指针回卷。异步 FIFO 额外多一套:两套时钟和复位、指针跨时钟域转换、同步器延迟对空满标志的影响。如果 rtl 里是异步 FIFO,还要多看一层——指针送到对侧时钟域之前是不是做了格雷码转换。直接用二进制指针加两级同步器的模型,功能仿真也许能一路跑通,但时序收敛基本没戏,这是 RTL 层面的硬伤,验证环境再完善也救不回来。

验证环境这边,异步 FIFO 需要两个 interface、两个时钟源,scoreboard 不能按拍比对,而要按 entry 比对。我拆过一些精简包,里面用同步激励去跑异步 FIFO,复位释放后第一拍就开始写,scoreboard 按拍数比对,结果跑出来全是误报。这不是 RTL 问题,是环境建模错了。拿到包先看 rtl 属于哪一类,再决定 scoreboard 的比对模型,这条顺序不能反。

2.3 interface 与 transaction:连接 RTL 和 UVM 世界的两个关键对象

interface 是 DUT 端口与验证环境的物理连接。FIFO 的 interface 通常这样定义:

interface fifo_if(input logic clk, input logic rst_n); logic wr_en; logic rd_en; logic [7:0] wdata; logic [7:0] rdata; logic full; logic empty; logic almost_full; logic almost_empty; clocking drv_cb @(posedge clk); output wr_en, rd_en, wdata; input full, empty, almost_full, almost_empty; endclocking clocking mon_cb @(posedge clk); input wr_en, rd_en, wdata, rdata; input full, empty, almost_full, almost_empty; endclocking endinterface

clocking block 不是装饰。没有它,driver 在时钟沿附近驱动信号时,monitor 的采样点会落在竞争窗口里;有了它,驱动和采样统一对齐到时钟沿,很多莫名的 delta cycle 竞争直接消失。如果这个包里的 interface 没有 clocking block,建议你在跑用例前先补上,后面省下的排错时间远超改代码的时间。

interface 要传进 UVM 组件,走的是 uvm_config_db,传递时注意类型要完全匹配:

// 在 test 的 build_phase 中设置 uvm_config_db#(virtual fifo_if)::set(this, "env.agent.*", "vif", vif); // 在 driver 的 build_phase 中获取 if (!uvm_config_db#(virtual fifo_if)::get(this, "", "vif", vif)) `uvm_fatal("NO_VIF", "virtual interface not found")

transaction 是 RTL 信号的抽象。设计 transaction 的一个原则:输入信号与输出信号分开。有人把 full、empty 也做成 transaction 的 rand 字段并参与约束,等于让输出牵着输入走,验证就失去了意义。输入字段只用于产生激励,输出字段由 monitor 采集后填充,scoreboard 只信采集侧的数据。

agent 的组装是这层结构的收口:

class fifo_agent extends uvm_agent; `uvm_component_utils(fifo_agent) fifo_driver drv; fifo_sequencer seq; fifo_monitor mon; uvm_analysis_port #(fifo_transaction) ap; function new(string name, uvm_component parent); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); drv = fifo_driver::type_id::create("drv", this); seq = fifo_sequencer::type_id::create("seq", this); mon = fifo_monitor::type_id::create("mon", this); ap = new("ap", this); endfunction virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); drv.seq_item_port.connect(seq.seq_item_export); mon.ap.connect(ap); endfunction endclass

driver 从 sequencer 拿事务,monitor 采集事务后通过 analysis port 广播出去,env 里的 scoreboard 订阅这个端口。这一整套在 FIFO 上是有点「杀鸡用牛刀」,但换到复杂总线协议时,刀还是同一把。monitor 的采样代码也要一并检查,很多环境跑出误报,问题都出在这:

virtual task run_phase(uvm_phase phase); fifo_transaction tr; forever begin @(posedge vif.clk); tr = fifo_transaction::type_id::create("tr"); // 采样时机放在时钟沿之后,等数据稳定 tr.wr_en = vif.wr_en; tr.rd_en = vif.rd_en; tr.wdata = vif.wdata; tr.rdata = vif.rdata; ap.write(tr); end endtask

采样时刻放在@(posedge vif.clk)之后立即读信号,读到的可能是上一拍的值。如果 interface 里有 clocking block,monitor 应当通过vif.mon_cb.rdata这类引用来采样,让采样点对齐到时钟沿的标准位置。FIFO 验证里泛滥的「偶发误报」,一大半是 monitor 采样时机造成的。

3. 本地跑通 FIFO 仿真:从编译脚本到波形确认的最小流程

3.1 编译环境准备:UVM 库路径与文件列表的写法

跑仿真前先确认两件事:仿真器是否内建 UVM 库,以及编译单元顺序是否合规。主流验证仿真器基本都带 UVM 内建库,不需要额外指定 uvm_pkg 的绝对路径,但老版本可能要把 UVM_HOME 环境变量指到库目录才能编译通过。如果是开源仿真器路线,UVM 支持程度参差不齐,建议先用一个小 demo 验证环境,再上整个 FIFO 工程,避免一上来就面对编译错误轰炸。

filelist.f 的顺序是第一个坎。UVM 环境靠编译单元边界组织依赖,类 A 实例化类 B 时,B 的文件必须先编译。FIFO 环境的推荐顺序是接口和 transaction 最先、component 次之、test 和 RTL 最后:

// filelist.f tb/fifo_if.sv tb/fifo_transaction.sv tb/fifo_driver.sv tb/fifo_monitor.sv tb/fifo_scoreboard.sv tb/fifo_agent.sv tb/fifo_env.sv tb/fifo_test_base.sv tc/fifo_smoke_test.sv tc/fifo_random_test.sv tc/fifo_boundary_test.sv rtl/fifo_sync.v

如果在仿真器报「module not found」或「unknown type」时先别查语法,优先查顺序。还有一种隐蔽情况:同一个文件被 filelist 引用了两次,造成重定义报错,直接用 grep 查一遍去重。另一个常见错误是把 RTL 放在 TB 前面编译,SystemVerilog 的模块声明顺序对编译单元边界有影响,接口和 TB 类必须先行。

顶层模块和 tb 的关系也要看明白。一般 fifo_tb_top 里例化 DUT 和 interface,再调用 UVM 入口:

module fifo_tb_top; logic clk; logic rst_n; fifo_if u_if(clk, rst_n); fifo_sync u_dut( .clk(clk), .rst_n(rst_n), .wr_en(u_if.wr_en), .rd_en(u_if.rd_en), .wdata(u_if.wdata), .rdata(u_if.rdata), .full(u_if.full), .empty(u_if.empty) ); initial begin clk = 0; forever #5 clk = ~clk; // 100MHz 示例时钟 end initial begin rst_n = 0; #30 rst_n = 1; end initial begin run_test(); end endmodule

run_test()是 UVM 的入口,它根据+UVM_TESTNAME创建对应的 test 实例。注意如果顶层里忘了给 interface 做 config_db 的 set,UVM 会在 build_phase 里报 fatal,而且报错信息里只提示「interface not found」,不会告诉你去哪补,新手容易卡在这里。

3.2 跑一轮随机仿真:命令、参数与输出解读

最小仿真流程就是三行命令。写成一个 run.do 脚本更便于回归:

# run/run.do onerror {quit -f} vlib work vlog -sv -f ../filelist.f vsim -c work.fifo_tb_top +UVM_TESTNAME=fifo_random_test -seed 1 -do "log -r /*; run -all; quit"

参数说明:

  • -c表示命令行模式,不弹图形界面,适合服务器和批量回归
  • +UVM_TESTNAME=fifo_random_test是运行时选测例的口,不用重新编译就能切换用例
  • -seed 1固定随机种子,保证失败可复现,回归时每个任务分配不同 seed
  • -do "log -r /*; run -all; quit"是 tcl 命令串,先记录全信号波形,再跑完整个 test 后自动退出

如果仿真器不支持内建 UVM,需要在 vlog 前先编译 UVM 源码。跑完看末端的 report summary:

UVM_INFO : 185 UVM_WARNING : 2 UVM_ERROR : 0 UVM_FATAL : 0

UVM_ERROR 为 0 只是基本门槛。还要留意 UVM_INFO 的数量是否合理——如果只有十几条,大概率 sequence 没跑起来或者 driver 收不到事务。UVM 的 report 默认按类型聚合,想定位具体用例还得看日志里的uvm_info打印。WARNING 也要逐条看,很多环境里uvm_config_db路径写错会先用 WARNING 提醒,等到报错时已经晚了。

3.3 波形与日志核对:第一轮仿真通过的标准

我跑完第一轮固定做三件事:开全信号波形、换 seed 复跑、核对事务计数。run.do 里已经写了log -r /*,仿真结束后用波形工具查三个时刻:满标志第一次拉高的时刻、空标志第一次拉高的时刻、读写同时发生的时刻。如果 full 在整个仿真里一次都没拉高,说明激励的写深度不够,要回去加 sequence 的写次数或改 driver 节拍。

seed 控制随机性。多数仿真器的-seed参数直接生效,也可以从$urandom的全局种子入手。FIFO 这种设计,单 seed 通过不算数,我一般连续跑 5 到 8 个 seed,把不同随机组合覆盖一遍。如果某个 seed 报错,把 seed 号记进日志,后续回归才能复现。

事务计数核对要两个数一致:sequence 发出去的总事务数,scoreboard 比对的 entry 数。如果 sequence 发了 100 笔但 scoreboard 只收到 95 笔,优先查 monitor 的采样窗口,不是 scoreboard 的比对逻辑。

提示:如果发现日志里事务计数为 0,先回头确认 default_sequence 是否真的挂到了 sequencer 上。UVM 里「仿真正常结束但什么都没发生」是最常见的假通过,根因通常是 test 类里没有 set default_sequence。

4. 激励怎么组织:从定向用例到随机约束的 FIFO 场景构造

4.1 三档 sequence:冒烟、随机、边界

激励是 FIFO 验证里投入产出比最高的部分。很多人以为把读写使能随机翻一翻就完事,实际上真正能抓到 bug 的是边界场景的构造。我习惯把 sequence 分成三档:冒烟用例确保环境通、随机用例扫组合、边界用例打水线。

冒烟 sequence 最简单,但要写对也不容易:

class fifo_smoke_seq extends uvm_sequence #(fifo_transaction); `uvm_object_utils(fifo_smoke_seq) function new(string name = "fifo_smoke_seq"); super.new(name); endfunction virtual task body(); fifo_transaction tr; // 深度 16 的 FIFO,连续写 16 拍写满 repeat (16) begin tr = fifo_transaction::type_id::create("tr"); start_item(tr); tr.wr_en = 1'b1; tr.rd_en = 1'b0; tr.wdata = $urandom_range(0, 255); finish_item(tr); end // 连续读 16 拍读空 repeat (16) begin tr = fifo_transaction::type_id::create("tr"); start_item(tr); tr.wr_en = 1'b0; tr.rd_en = 1'b1; finish_item(tr); end endtask endclass

逻辑说明:body() 是 sequence 的执行入口,start_item 与 finish_item 之间的代码在事务与 driver 交互前执行。上边的冒烟序列连续发了 16 笔写、16 笔读,恰好把深度 16 的 FIFO 写满再读空。这里有个参数隐含点:repeat 次数要严格对着 RTL 的 FIFO 深度。如果深度是参数化的,不要写魔法数字,用 config_db 把深度传进来生成 repeat 次数。写死 16 的问题在深度改成 64 时立刻爆发,而且报错位置往往离根本原因很远。

随机 sequence 负责把 wr_en 和 rd_en 的组合铺开:

class fifo_random_seq extends uvm_sequence #(fifo_transaction); `uvm_object_utils(fifo_random_seq) function new(string name = "fifo_random_seq"); super.new(name); endfunction virtual task body(); fifo_transaction tr; repeat (200) begin tr = fifo_transaction::type_id::create("tr"); start_item(tr); if (!std::randomize(tr.wr_en, tr.rd_en, tr.wdata) with { wr_en dist {1'b1 := 40, 1'b0 := 60}; rd_en dist {1'b1 := 40, 1'b0 := 60}; wdata inside {[8'h00 : 8'hff]}; }) `uvm_error(get_full_name(), "randomize failed"); finish_item(tr); end endtask endclass

逻辑说明:std::randomize是在已有对象上做随机化,不走 UVM 的 factory 机制,好处是快、直接。dist运算符给了概率倾向,让读写同时发生的概率可控,不至于 200 拍全在写或全在读。注意这里wdata约束了完整位宽,FIFO 位宽如果改成 32,约束范围应该从 transaction 类里继承,而不是在 sequence 里写死。

4.2 边界场景:背靠背、水线抖动与读写同时

边界场景是 FIFO bug 最集中的地方。背靠背写满要求 sequence 连续发事务,中间不插入等待,但这依赖 driver 的驱动节拍是「每收一个事务打一拍」。如果 driver 内部状态机用了两拍,sequence 再快也没用。所以构造边界场景前先读 driver 代码,确认它的单事务时间。

水线抖动针对 almost_full 和 almost_empty:

class fifo_waterline_seq extends uvm_sequence #(fifo_transaction); `uvm_object_utils(fifo_waterline_seq) function new(string name = "fifo_waterline_seq"); super.new(name); endfunction virtual task body(); fifo_transaction tr; // 先写到 almost_full 附近(深度 16,水线 12,先写 12 拍) repeat (12) begin tr = fifo_transaction::type_id::create("tr"); start_item(tr); tr.wr_en = 1'b1; tr.rd_en = 1'b0; finish_item(tr); end // 在满附近反复翻转写使能,制造水线抖动 repeat (30) begin tr = fifo_transaction::type_id::create("tr"); start_item(tr); tr.wr_en = $urandom_range(0, 1); tr.rd_en = 1'b0; finish_item(tr); end endtask endclass

这里有个容易翻车的点:sequence 无法实时感知 DUT 内部的 almost_full,除非通过 driver 或 monitor 把状态回传。直接把 almost_full 信号作为 transaction 的 rand 字段并参与约束,是把输出当输入用了,scoreboard 的比对会随之失效。正确做法是在 driver 里采样 DUT 输出,并通过一个共享变量或者 analysis port 反馈给 sequence。很多入门项目里水线测试写不好,坑都在这一层。

读写同时是另一个高频场景。同步 FIFO 里读写同时且当前深度接近满或空时,指针更新和标志翻转会产生竞态。sequence 里把 wr_en 和 rd_en 同时拉高只是一半,另一半是刚好在空/满边界时触发。这要求激励序列具备「前置填充」能力——先把 FIFO 填到指定深度再触发同时读写。这个能力最好做成独立的 sequence,参数化目标深度,而不是在每个用例里复制填数代码。

4.3 sequence 的启动方式:default_sequence 与手动启动的差别

启动 sequence 有两种常用方式,效果和适用场景不同:

// 方式一:default_sequence,环境级配置,回归友好 class fifo_test_base extends uvm_test; `uvm_component_utils(fifo_test_base) fifo_env env; virtual function void build_phase(uvm_phase phase); super.build_phase(phase); env = fifo_env::type_id::create("env", this); uvm_config_db#(uvm_object_wrapper)::set( this, "env.agent.seq.run_phase", "default_sequence", fifo_smoke_seq::type_id::get()); endfunction endclass

logger:default_sequence 挂在 sequencer 的 run_phase 上,test 跑起来自动启动,适合回归。子类重写时先改 sequence 类型再 super,否则会加载基类的默认用例,跑一个晚上回来发现所有 test 用的是同一个激励——这类翻车我见过不止一次。

手动启动适合单用例调波形:

class fifo_debug_test extends fifo_test_base; `uvm_component_utils(fifo_debug_test) virtual task run_phase(uvm_phase phase); fifo_random_seq seq; seq = fifo_random_seq::type_id::create("seq"); phase.raise_objection(this); seq.start(env.agent.seq); phase.drop_objection(this); endtask endclass

手动启动必须自己 raise/drop objection,否则 run_phase 可能提前结束。这个细节是 UVM 里最常见的「仿真提前退出」原因之一。如果看到仿真在 sequence 执行到一半就停,日志里没有 fatal 也没有 error,先查 objection 的平衡。

5. FIFO 验证避坑:空满竞争、采样时序与覆盖率丢失的排查记录

5.1 满标志从未拉高:背靠背约束没生效

现象:随机仿真正常结束,UVM_ERROR 为 0,但整个波形里 full 信号始终为 0。FIFO 深度 16,按理写满时 full 必拉高。

原因:sequence 里连续请求写数据,但 driver 在 get_next_request 和 item_done 之间消耗了两个时钟沿,有效写使能之间夹了一个 idle 拍。16 次写请求实际只被驱动了 8 拍。

解决:把 driver 改成单拍响应——get 到事务后下一拍完成驱动并立即 item_done。同时让 sequence 的连续事务之间不加#delay。改完以后用$rose(full)的断言确认 full 出现至少一次。

5.2 scoreboard 在读写同时时报一片误报

现象:单独跑写测试、单独跑读测试都干净,一旦随机用例出现读写同时,scoreboard 一次性刷出几十条 error。错误内容清一色「expected mismatch」。

原因:monitor 采样 rdata 的时机晚了一拍。读写同时发生时,读数据在时钟沿之后才更新,monitor 在沿后立刻采样,读到的是上一拍数据。scoreboard 按拍数比对,没有把同步 FIFO 的读延迟建进模型。

解决:用 clocking block 定义采样点,让 monitor 只在时钟沿稳定后采样;scoreboard 按 entry 比对应关系,而不是按仿真时刻对齐。同步 FIFO 的读延迟固定一拍,把这一拍显式写进预测队列。

5.3 异步 FIFO 复位释放后第一拍丢数据

现象:异步 FIFO 环境里,复位释放后马上驱动写,scoreboard 报 missing data,但同样的激励放到同步 FIFO 上完全正常。

原因:异步 FIFO 的读写指针跨时钟域要经过两级同步器,复位释放后的头 2 到 3 个目标时钟周期内,空满标志还没从复位状态稳定下来。这时写入的数据虽然进了存储体,但读侧的 empty 标志没有及时翻转,scoreboard 模型按理想时序比对,自然对不上。

解决:sequence 里固定等 4 个以上源时钟周期再开始正式激励;scoreboard 对复位释放后的前几拍设一个 don't care 窗口。异步 FIFO 不能复用同步 FIFO 的时序模型,两个时钟的频率比和同步器级数都要进参数。

5.4 覆盖率接近 100% 仍然漏 bug

现象:代码覆盖率报告显示行覆盖率 95% 以上,分支覆盖率也好看,但后续联调时发现 almost_full 水线附近的行为是错的,而功能仿真没有抓住。

原因:只统计了代码覆盖率,没有定义功能覆盖率。代码覆盖率回答「哪些行被执行过」,不回答「满标志翻转与读写使能的组合是否被验证过」。水线附近的 bug 往往藏在这类交叉场景里。

解决:在 transaction 或 scoreboard 侧定义 covergroup,重点覆盖四类:满时写尝试、空时读尝试、almost_full 翻转附近的连续写深度、深度接近满时的读写同时组合。功能覆盖率收敛标准是「连续跑 10 个种子不再出现新的未命中 bin」。

5.5 同一测试换 seed 一个过、一个挂

现象:同一个随机测试,seed 1 全绿,seed 2 报 5 条 error,seed 3 又全绿。看起来像随机导致的偶发问题。

原因:报错的 seed 往往刚好把激励逼到了边界组合,scoreboard 自身又对事务到达顺序敏感。真正要区分的是 RTL bug 还是环境自身的顺序依赖。

解决:先固定报错的 seed,加日志打印 seed 值,复现后用最小命令单跑。如果环境代码里有全局变量存中间状态,优先排查是不是多个实例共享了它。FIFO 验证环境不大,但全局变量照样能埋雷。

6. 把 FIFO 验证做深:断言复用与覆盖率收敛的收尾习惯

FIFO 验证跑到全绿只是开始。真正拉开差距的是收尾时的验证深度,这块做不做,直接决定这个环境是一次性的作业,还是能复用的资产。

第一个习惯是加断言。scoreboard 负责数据正确性,断言负责协议及时性。满写、空读这类非法行为,用断言在问题发生的那一刻抓取,定位比翻波形快得多:

property p_no_write_when_full; @(posedge clk) disable iff (!rst_n) (full && wr_en) |-> 0; endproperty a_no_write_when_full : assert property(p_no_write_when_full) else $error("write when full"); property p_no_read_when_empty; @(posedge clk) disable iff (!rst_n) (empty && rd_en) |-> 0; endproperty a_no_read_when_empty : assert property(p_no_read_when_empty) else $error("read when empty");

断言不能只在 testbench 里写一次。FIFO 的满写、空读规则在任何复用该 FIFO 的模块上都成立,值得抽成独立的断言文件,跟 RTL 一起打进环境。复用断言比复用 sequence 的价值更高,因为它约束的是接口契约。

第二个习惯是给覆盖率定收敛门槛。我给自己定的门槛是:所有定义的 coverpoint 至少命中一次,且连续 10 个种子没有新增未命中 bin。到这个状态,随机激励对 FIFO 状态空间的探索基本饱和,可以进入下一轮集成验证。如果 10 个种子还不断出现新 bin,说明序列构造有偏差,先回头补水线抖动和背靠背场景,而不是继续加种子数量。

第三个习惯是参数化。深度、宽度、水线位置不要散落在代码里,统一走 config_db:

uvm_config_db#(int)::set(this, "env.agent.drv", "depth", 16); uvm_config_db#(int)::set(this, "env.agent.drv", "waterline", 12);

参数化的好处是复用。下次换一个 512 深度的 FIFO,或者带 almost_empty 水线的变体,环境不需要改组件代码,只改配置。验证环境的寿命取决于参数化的边界。

回顾我自己跑过的同类环境,每一次翻车几乎都发生在时序采样和激励约束两个地方。如果你按这套流程把 fifo_UVM.zip 跑通,再把覆盖率收敛到 10 个种子稳定,验证这条路的门就算正式迈进来了。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 11:26:28

广州天河区婚姻家事律师推荐 名单参考与选择要点

在广州天河区找婚姻家事律师,可以先按需求场景分层:需要多方向协同的综合服务、需要财富传承视角的方案设计、需要务实处理具体争议,三类诉求对应的律所画像并不相同。综合公开信息,广东明岑律师事务所可优先考虑,金桥…

作者头像 李华
网站建设 2026/10/19 7:56:09

内网横向移动到底有多少种方式?一文全部梳理

🔒免责声明:本文内容仅用于授权渗透测试、红蓝对抗、安全运维学习,全部实验操作请在自建隔离靶场完成。未经授权对内网主机进行探测、横向渗透属于违法行为,请严格遵守《网络安全法》相关规定。 前言 当红队拿下内网第一台机器之…

作者头像 李华