最近在搭UVM验证环境,sequence通过TLM端口往driver里发transaction,跑仿真的时候经常卡死在put/get的阻塞调用上。我检查了driver的run_phase和sequence的body,感觉握手逻辑没问题,但就是会死锁。想问问各位大佬,这种阻塞卡死一般怎么定位?是TLM端口没连接对,还是时序上有什么讲究?有没有什么debug技巧能快速定位是sequence的问题还是driver的问题?
2026年了,FPGA工程师做UVM验证平台,sequence和driver之间用TLM端口通信,put和get阻塞卡死,怎么定位是时序问题还是握手逻辑问题?
10 个回答

先别急着怀疑握手逻辑,我见过太多人一卡死就去看sequence和driver的代码,结果问题在更前面。你这种情况,第一步应该先确认TLM端口到底连上没有。UVM里最常见的坑是:在connect阶段用了绝对路径,比如uvm_top.agent.driver.tlm_fifo,但环境里agent被例化在某个sub_env下面,路径写错的时候UVM不会报错,只是connect了个寂寞,put/get就永远卡在阻塞调用上。你可以在build_phase结束后打印driver和sequence里tlm端口是否存在,或者在connect之后用uvm_info打印一下port和export是否has_connection。第二个常见原因,是run_phase没有同时启动。sequence的body如果是在某个phase里调用的,而driver的run_phase被raise_objection阻塞了,或者sequence的body根本没被start,那put和get就是死等。你可以分别在put和get前后加打印,带上时间戳,看是put这边没等到get,还是get那边没等到put,这样能快速定位是哪一侧没被调度。第三个隐藏点,transaction句柄没有new。如果你在sequence里直接声明了一个transaction类型的变量,没用create方法,就传给put,那这个句柄可能是null,driver那边get到的也是空,但阻塞调用本身不会立刻报错,容易让人误以为是时序问题。我建议你在sequence的body里用req = my_transaction::type_id::create("req"),然后put(req),这样至少能排除空句柄。至于时序问题,UVM里run_phase是并行执行的,你确认一下driver的run_phase有没有被fork起来,或者是不是用了task而不是function。如果以上都查过还卡死,那就在put和get之间加一个超时保护,用wait_for_nb_put或者fork+timeout,看是卡在sequence还是driver,这一步能直接区分是调度问题还是数据没产生。最后说一句,别迷信仿真器自带的debug视图,打印时间戳和transaction内容比什么波形都直观。你现在的环境是UVM 1.2还是UVM 1.1?这俩在TLM宏定义上有细微差别,有时候也影响连接行为。

我碰到类似卡死时,一般只用一招就能快速二分定位:在put调用前后各加一句uvm_info,在get调用前后也各加一句,都带上realtime和transaction的print。跑一次仿真,如果看到put前有打印但put后没有,说明put发出去了但get没收到,问题在driver侧或者两者之间的fifo;如果put前都没打印,那说明sequence的body根本没执行到put,可能是sequence没启动或者body里先卡在了别的阻塞调用上。这个办法比看波形快得多,因为阻塞卡死时波形上往往看不出明显异常。另外,如果你用uvm_tlm_fifo,可以检查一下它的size,默认是1还是0,如果size为0且两边同时阻塞,也会死锁。你可以先设一个较大的size,比如16,排除fifo容量问题。等确认不是容量问题后,再回头看connect和phase同步。你的driver有没有在build_phase里new这个fifo?有时候忘了new,connect的时候用的是空句柄,UVM不报错但实际没连上。

先确认你用的是uvm_put_port还是uvm_blocking_put_port,这俩名字差一个blocking,但行为完全不同。前者是非阻塞的,put调用会立刻返回,返回值和状态码能告诉你到底有没有送出去;后者才是阻塞的,卡死只可能发生在blocking版本上。如果你确实用了blocking版本,那问题大概率在connect阶段,或者run_phase启动顺序。一个很实用的调试技巧是:在build_phase里打印driver和sequence的hierarchy路径,然后手动检查这两个路径是否都能找到对方。UVM的connect是单向的,sequence这边put,driver那边get,你必须在driver侧声明一个uvm_blocking_get_port,然后在connect阶段把sequence的put_port连到driver的get_port上。很多人只连了put_port,忘了get_port那边也要连,或者连反了方向,结果两边都以为自己有端口,但实际没握手。这种错位用打印看has_connection最直观。另外,你提到检查了run_phase和body,但sequence的body是在sequence的start里被调用的,你需要确认你是在test里调用了seq.start(sequencer),而不是只声明了seq对象没启动。如果sequence没启动,body里的put永远不执行,driver那边get就会一直等。你可以在test的run_phase里,用seq.start(env.agent.sequencer)这种方式,并且确保sequencer的run_phase没有被raise_objection挡掉。如果这些都查过还卡死,那就用超时机制,比如在sequence里fork一个timer,5微秒后打印"timeout"并退出,这样至少能知道是sequence卡住了还是driver卡住了。

先对齐一个前提:你说的卡死是仿真时间完全不动,还是波形还在走但 transaction 一直没被消费?这两个现象指向的原因很不一样。如果是前者,十有八九是 TLM 端口根本没连上,阻塞 put 挂在 semaphore 上等 get,仿真时间自然冻结。如果是后者,那更像是 driver 的 run_phase 在忙别的事,比如 while 循环里先等了一个永远不会来的信号,或者 get 被放在了某个条件判断后面。我个人的排查顺序是:第一,在 sequence 的 body 里 put 之前打印一次 transaction 的完整字段,确认句柄不是 null 且数据有值;第二,在 driver 的 run_phase 第一行就打印『进入 run_phase』,排除 phase 没启动的可能;第三,重点检查 connect 阶段是不是用了绝对路径,比如 uvm_top.env.agent.driver.tlm_fifo,但实际 agent 被例化在别的层级下面。UVM 对 connect 失败基本不报错,只会静默地连不上。还有一个容易忽略的点:如果你在 sequence 里用的是 uvm_put_port,但 driver 侧声明的是 uvm_blocking_get_port,这两个虽然名字像,但端口类型不匹配时 connect 不会报错,只是数据永远到不了。你可以把 sequence 的 port 和 driver 的 port 都打印出来看看 is_connected 返回值。另外提一个替代做法:如果调试时间紧,可以先临时把 put 换成 try_put,把 get 换成 try_get,配合轮询打印,虽然不优雅,但能立刻看出是发送侧卡住还是接收侧卡住。你用的是 Questa 还是 VCS?不同工具的 UVM 库版本对端口连接检查的严格程度略有差异,这个信息对进一步定位会有帮助。

先查一件事:sequence 的 body 里 put 之前有没有等过别的东西,比如 `#0` 或者 `wait_for_grant`。很多时候不是 TLM 的问题,是 sequence 卡在 arbitration 上根本没走到 put。

这个问题我前两年刚转验证时也踩过,后来想明白一个核心点:UVM 的 TLM 阻塞调用本质上是两个进程之间的握手,卡死只说明握手没完成,但『没完成』的原因可以分三层。第一层是连接层,端口没连上或者连错了对象,这是最常见也最容易被忽略的。很多人写完 connect 就默认连上了,但 UVM 的 connect 机制是纯软件层面的引用传递,不会检查对方端口是否存在,路径写错或者端口类型不匹配(比如 put 对 get,而不是 put 对 blocking_get)都不会报错,只是运行时空等。第二层是调度层,sequence 的 body 和 driver 的 run_phase 必须同时处于可调度状态。如果你用了 `phase.raise_objection` 但没在 sequence 里也 raise,driver 的 run_phase 可能已经结束了,get 永远不会被调用。这个可以用 `uvm_top.print_topology()` 在 end_of_elaboration 阶段打印整个拓扑,看看 sequence 和 driver 是否都在 active 状态。第三层才是你问的时序问题,比如 driver 里在 get 之前先做了 `forever begin @(posedge vif.clk)` 这种等待,而 sequence 这边 put 是立刻发的,如果时钟还没跑起来,driver 就永远等不到第一个时钟沿。我自己调试时有个习惯:在 put 和 get 的调用处各加一个 `uvm_info` 带 `UVM_HIGH` 级别,然后跑一个极短的测试用例,只发一个 transaction,看打印顺序。如果 put 打印了但 get 没打印,问题在 driver 侧;如果 put 都没打印,问题在 sequence 启动或 objection 上。还有一个更省事的办法:用 `uvm_tlm_fifo` 替代直接端口连接,fifo 天然带缓冲,即使两侧速度不匹配也不会立刻卡死,等调通了再换回直接连接。你目前 sequence 是挂在哪个 sequencer 上的?如果是自己 new 的 sequence 而不是通过 sequencer 启动,那 phase 同步问题基本可以确定是罪魁祸首。

我补充一个工程上常见的坑:很多人把 TLM 端口直接连在 sequence 和 driver 之间,但 sequence 的 lifecycle 是受 sequencer 控制的,如果 sequence 没有被正确挂到 sequencer 上,比如用了 `start()` 但没传 `this` 作为 parent,那 body 可能根本不会被调用。检查一下你的 sequence 是不是在 test 的 run_phase 里直接 `seq.start(seqr)` 启动的,如果是,看看 seqr 是不是 null。另一个快速定位手段:在 driver 的 run_phase 里加一个 `#10ns` 的延时再进入 get 循环,排除仿真时间 0 时刻的竞争。如果加了延时就不卡了,那基本是 phase 同步或 0 时刻调度问题,不是握手逻辑问题。

从面试官角度说一句:这种问题考察的其实是你能不能把『阻塞』这个词理解透。阻塞 put 的意思是调用方必须等到对方 get 完成才返回,所以卡死必然意味着对方没在 get。那你要回答的就不是『时序还是握手』,而是『对方为什么没在 get』。常见三个原因:没连上、没启动、被别的阻塞调用挡住。我面试时一般会追问一句:你怎么证明端口连上了?能答出 `port.is_connected()` 或者打印 `has_connection` 的,说明真跑过环境。还有一点很多人忽略:如果 driver 的 run_phase 里有 `while(1)` 循环,但循环体里第一个语句是 `get`,而 sequence 只发了一个 transaction 就结束了,那 driver 会永远等第二个 transaction,这不是卡死,是正常行为,但看起来很像死锁。你的测试用例是发一个就结束,还是持续发?这个信息对判断很关键。如果持续发还卡死,那大概率是 fifo 满了或者 get 侧消费速度跟不上,可以临时把 fifo size 调大验证。

我觉得你可以换个思路,别一上来就怀疑 TLM 机制。UVM 的 TLM 阻塞调用本身很可靠,除非你用了自定义的端口子类或者改了 `put` 的实现。先做最基础的检查:在 sequence 的 `body` 里 `put` 之前打印 `this.get_full_name()`,在 driver 的 `run_phase` 里打印 `this.get_full_name()`,确认这两个对象确实是同一个 testbench 层级下的。有时候你 sequence 是在 sub_test 里启动的,但 driver 挂在 top 层,connect 的时候用相对路径 `../driver` 可能指错对象。另一个隐蔽问题是 transaction 句柄:如果你在 sequence 里 `create` 了一个 transaction,但 `put` 之前不小心把它 `null` 了,或者 driver 那边 `get` 之后没有 `clone` 就改了字段,也可能导致后续行为异常。不过这种一般不会卡死,更多是数据错乱。回到你的问题,我建议先加打印,把 put 和 get 的调用点都标出来,跑一个 1us 的仿真,看打印顺序。如果 put 打印了但 get 没打印,再去看 driver 的 run_phase 是不是被 `phase.phase_done` 提前杀掉了。你用的是 default sequence 还是手动 start?如果是 default sequence,检查 config_db 里有没有设置 `default_sequence` 的路径,路径写错的话 sequence 根本没启动,body 不会执行。

给一个最直接的调试命令吧:在仿真跑起来之前,用 `uvm_top.print_topology()` 打印整个环境树,重点看 sequence 和 driver 之间的连接线。如果看到 `put_port` 和 `get_port` 之间有一条虚线连接,说明 connect 成功了;如果只是两个孤立的端口,那问题就在 connect 阶段。这个命令比任何代码审查都快。另外,你可以把 `UVM_VERBOSITY` 调到 `UVM_DEBUG`,UVM 自带的 TLM 端口在 debug 级别会打印 put/get 的调用时间和 transaction 内容,这样不用自己加打印就能看到卡在哪个环节。如果 debug 级别没有输出,说明端口根本没连上。你用的是 `uvm_put_port` 还是 `uvm_blocking_put_port`?这个区别很关键,名字差一个 blocking 行为完全不同,前者非阻塞,后者才会卡死。
发表回答
登录后可在本页底部提交回答