SystemVerilog测试平台Monitor类在$finish后仍运行问题求助
核心问题分析
从仿真输出和代码来看,Monitor类的forever循环在$finish后持续运行,本质是**$finish根本没有被执行**,时钟持续驱动Monitor的循环不断运行。
具体原因
事件等待的时序错误
测试平台的fork join_any会在第一个完成的线程(此处为generator的run任务)结束后,立即执行后续的@(done)语句。但generator的执行逻辑是:先触发done事件(->done),再打印DONE,最后任务结束。这意味着当join_any开始执行@(done)时,done事件已经触发过——而SystemVerilog的事件是边沿触发的,只能被捕获一次,后续的@(done)会永久阻塞,导致$finish永远不会被执行。
此时时钟由always #5 vif.clk=~vif.clk持续翻转,Monitor的forever循环无阻塞条件,会不断执行并输出信息。Monitor事务对象重复使用(次要问题)
Monitor类中仅创建了一个transaction对象tr,每次循环仅修改其out字段就放入mailbox,导致每次输出的datain都是最后一次赋值的旧值(此处为0)。
解决方案
方案1:修正事件等待逻辑
将@(done)的等待放在fork join_none之后,确保在generator触发done事件时能及时捕获,避免错过事件触发:
initial begin gtd=new(); gts=new(); mts=new(); g=new(gtd,gts); drv=new(gtd); mn=new(mts); g.drvnext=drvnext; mn.drvnext=drvnext; g.done=done; drv.vif=vif; mn.vif=vif; fork g.run(); drv.run(); mn.run(); join_none // 后台并行运行三个任务,主流程继续执行 @(done); // 等待done事件触发 $finish; end
或者,使用wait(done.triggered)替代@(done),它可以检测事件是否已经触发过,避免永久阻塞:
fork g.run(); drv.run(); mn.run(); join_any wait(done.triggered); // 检测done事件是否已触发 $finish;
方案2:给forever循环添加终止标志
在测试平台中添加全局终止标志,让driver和monitor的循环能够主动退出:
- 在
tb模块中添加终止标志:
bit test_done = 0;
- 修改
driver的run任务:
task run(); forever begin @(posedge vif.clk); @(posedge vif.clk); if(test_done) break; // 检测终止标志,退出循环 gtd.get(t); vif.din=t.in; t.display("DRV"); end endtask
- 修改
monitor的run任务:
task run(); forever begin @(posedge vif.clk); @(posedge vif.clk); if(test_done) break; // 检测终止标志,退出循环 tr.out=vif.dout; mts.put(tr.copy()); // 放入事务拷贝,避免对象复用问题 tr.display("MON"); ->drvnext; end endtask
- 在触发
done事件后设置终止标志:
@(done); test_done = 1; #1; // 给线程留出退出时间 $finish;
额外优化:修复Monitor事务对象复用问题
在Monitor的run任务中,放入mailbox的应该是事务对象的拷贝,而非原对象,避免后续修改覆盖之前的事务数据:
mts.put(tr.copy());
内容的提问来源于stack exchange,提问作者Ankit Kaushik

