我在一个UVM验证环境里,sequence和driver之间用TLM端口通信,现在put和get阻塞卡死了,仿真一直停在那不动。我用Verdi看了波形,感觉像是握手信号没拉起来,但又不敢确定是时序问题还是我握手逻辑写错了。各位大佬,遇到这种阻塞卡死的情况,一般怎么快速定位?是先查sequence的body执行到哪一步,还是直接看driver那边的TLM端口有没有收到请求?有没有什么调试技巧或者打印信息推荐的?
2026年了,FPGA工程师做UVM验证,sequence和driver之间用TLM端口通信,put和get阻塞卡死,怎么定位是时序问题还是握手逻辑问题?
7 个回答

先别急着怀疑握手逻辑,第一步永远是确认 sequence 的 body 到底执行到哪一行。最简单粗暴的办法:在 body 里 put 调用前后各打一条 $display,在 driver 的 get 前后也打,跑一下看打印顺序。如果 sequence 那边打印了『准备 put』但 driver 那边没有『收到 get』,说明问题在 TLM 连接或者 driver 的 run_phase 根本没启动;如果两边都打印了但卡住,那才是握手信号的问题。另外,UVM 里 put/get 阻塞卡死最常见的坑不是时序,而是你忘了在 connect 阶段把 port 和 export 连起来,或者连反了。Verdi 里看 TLM 端口有没有 transaction 是次要的,先看打印,打印能告诉你代码执行流,波形只能告诉你信号状态。还有一个容易忽略的点:如果你的 sequence 是在某个 phase 里启动的,而 driver 的 run_phase 里用了 forever 循环等待,但 sequence 的 body 执行完就结束了,那 driver 会一直等一个永远不会来的 put——这种死锁在仿真里表现就是卡住不动。排查顺序建议:先打印确认执行流,再查连接关系,最后才看波形。顺便说一句,如果 sequence 用了 raise_objection 但没 drop,也会导致仿真不结束,但那个一般不是卡死而是停在 phase 结束,注意区分。你现在的 driver 是用的 uvm_driver 基类还是自己写的类?这个会影响 run_phase 的写法。

个人感觉这类问题九成是『两个端口互相等待』,但多数人一开始都以为是时序。你想想,sequence 的 put 是阻塞的,它要等 driver 把上一次的 item 取走才返回;driver 的 get 也是阻塞的,它要等 sequence 发新 item。如果两边都只调用了一次,那第一次握手应该能成功,卡死往往发生在第二次或之后的迭代里——比如 driver 处理完第一个 item 后,因为某个条件没满足(比如用了 while 循环但条件写反了),它没有回到 get 等待,而是卡在别的地方;这时候 sequence 还在傻傻地等 put 返回,于是死锁。这种问题看波形是看不出来的,因为波形上 TLM 端口可能确实有 transaction 过去,但 driver 的代码根本没执行到 get。所以我的建议是:不要一开始就打开 Verdi 找握手信号,而是先在 sequence 和 driver 的每个关键节点打时间戳打印。具体来说,sequence 的 body 里打 put 前后的时间,driver 的 run_phase 里打 get 前后以及 item 处理完成后的时间。跑一次仿真,如果发现 sequence 打了『put 完成』但 driver 没有打『收到 item』,那说明 item 根本没送到 driver——这时候去查 connect 是否在 build_phase 之后正确执行;如果 driver 打了『收到 item』但没有打『处理完成』,那问题在 driver 内部的业务逻辑,跟 TLM 无关。另外,一个非常实用的技巧:在 sequence 里给 item 加一个递增的序号字段,打印时带上序号,这样你能清楚地看到是第几个 item 卡住的。如果卡在第一个 item,多半是连接或初始化问题;如果卡在第三个、第五个,那大概率是 driver 的处理逻辑里有条件分支没覆盖到。你用的是 UVM 1.2 还是 IEEE 1800.2?不同版本的 TLM 端口实现细节略有差异,有些老版本对 put/get 的时序要求更严格。想再深入的话,可以考虑在 driver 的 get 调用处加一个超时保护,比如用 fork/join_any 加 delay,超时后打印当前状态再 fail,这样以后定位会快很多。你现在是 sequence 和 driver 都是自己写的,还是用了现有的 sequence 库?

打印比看波形快多了。sequence 里 put 前打一条,driver 里 get 后打一条,看谁没执行。谁没执行就查谁,别在 Verdi 里瞎找。

既然你已经在用 Verdi 看波形了,那我猜你大概率是刚接触 UVM 没多久。这个问题其实不用上升到时序层面,UVM 的 TLM 端口阻塞通信本身是事件驱动的,不依赖时钟边沿,所以『时序问题』这个方向基本可以排除——除非你是在 driver 内部用了 @(posedge clk) 之类的等待,把 TLM 握手和时钟同步混在一起了。举个例子,很多新手会在 driver 的 run_phase 里写:get(item); @(posedge vif.clk); drive_item(item); 然后 sequence 那边 put 完就等下一次。如果 driver 在 get 之后、@(posedge clk) 之前有某种阻塞操作,或者时钟没有正常翻转,那 sequence 的 put 虽然已经返回,但 driver 永远走不到下一次 get,于是 sequence 第二次 put 就卡死了。这种问题波形上能看到 TLM 端口有第一个 transaction,但第二个 transaction 的请求信号一直没拉高。所以排查的时候,先确认你的 driver 里有没有把 TLM 调用和时钟等待混在一个 always 块里,如果有,建议把 get 和驱动逻辑拆开:get 放在一个独立的循环里,拿到 item 后通过 mailbox 或者简单变量传给驱动线程。另外,打印信息里一定要带 $time,这样你能精确知道卡死发生的时间点,再回去看那个时间点附近的波形,比从头到尾翻波形高效得多。还有个小技巧:在 sequence 的 body 里用 fork 包一个超时检查,比如 1000 个时钟周期内 put 没返回就打印『timeout』并 $finish,这样仿真不会无限卡下去,能直接暴露问题。你现在的 driver 是继承 uvm_driver 还是完全自定义的?如果是自定义的,确认一下 run_phase 里有没有 super.run_phase(task) 的调用,这个也经常导致 TLM 握手失效。

说实话,这种卡死十有八九不是时序,是逻辑。你想想,put 和 get 都是阻塞的,如果两边都只执行一次,那第一次肯定能通。卡死说明至少有一方没有回到等待状态。建议你直接在 sequence 的 put 前面加一个 $display 显示当前时间和 item 序号,driver 的 get 后面也加,跑一次看打印顺序。如果 sequence 打了第一次 put 但 driver 没打第一次 get,那问题在连接或 phase 启动;如果 driver 打了 get 但 sequence 没打第二次 put,那问题在 sequence 的循环逻辑。用波形看握手信号是最后一步,不是第一步。另外,检查一下你的 driver 是不是在 get 之后用了 while 循环等待某个信号,那个信号可能永远不满足。你用的是 UVM 哪个版本?不同版本对 TLM 端口的实现有细微差别,有些老版本在 connect 没配对时不会报错,只是静默卡死。

先花三十秒做一件事:把 sequence 的 body 和 driver 的 run_phase 里所有 TLM 调用前后都加上 $display,带上 $time 和 item 的 ID。然后跑仿真,看打印顺序。如果 sequence 的 put 打印了但 driver 的 get 没打印,说明 item 没送到,去查 connect 和 phase 执行;如果 driver 的 get 打印了但 sequence 的 put 没返回,说明 driver 没完成上一次握手,去看 driver 内部逻辑。波形是辅助,打印是主线索。不要一上来就怀疑时序,UVM 的 TLM 是事件驱动,跟时钟没直接关系——除非你 driver 里用了 @(posedge clk) 之类的同步,那才是时序问题。我见过一个案例,driver 在 get 之后用了一个 while(!vif.ready) 等待外部信号,结果 ready 永远没拉高,整个仿真就卡死了。这种问题波形上能看到 TLM 有 transaction,但 driver 的驱动逻辑永远在等。你现在 driver 的 get 调用是放在 run_phase 的什么位置?如果是放在某个条件分支里,检查一下那个分支是否覆盖了所有情况。

看打印,别看波形。sequence 的 put 前后打一下,driver 的 get 前后打一下,谁没打就查谁。如果两边都打了但卡住,那八成是 driver 内部有 while 循环在等一个永远不满足的条件。你查一下 driver 里 get 之后有没有 while 等待信号,比如 wait(vif.ready) 之类的,那才是卡死的根源。
发表回答
登录后可在本页底部提交回答