UVM驱动中非阻塞赋值引发uvm_info打印竞态问题咨询
问题现象
我正在实现一个同步UVM驱动,已将驱动信号对齐时钟边沿,采用非阻塞赋值避免竞态条件。运行驱动后,打印输出为:
myCmd.start: 1
myCmd.start: 0
但预期输出应为:
myCmd.start: 0
myCmd.start: 1
波形查看器中信号工作正常,仅uvm_info()打印结果异常。若在打印前添加#1延迟:
#1; uvm_info(get_type_name(), $sformatf("myCmd.start:%h", my_master_vif.dut_command), UVM_LOW)
即可得到正确打印值。请问仅用非阻塞赋值不足以处理UVM驱动中uvm_info()的竞态问题吗?该如何解决此问题?
附代码
驱动代码
class my_master_driver_c extends uvm_driver#(my_frame_c); virtual my_if my_master_vif; //... task run_phase(uvm_phase phase); while (1) begin begin forever begin @(posedge my_master_vif.dut_clock_i iff (my_master_vif.reset)) seq_item_port.get_next_item(req); drive_transfer(req); #10; seq_item_port.item_done(req); end end end endtask //... task drive_transfer (input my_frame_c trans); @(posedge my_master_vif.dut_clock_i iff (my_master_vif.reset)); my_master_vif.dut_command <= 0; `uvm_info(get_type_name(), $sformatf("myCmd.start:%h", my_master_vif.dut_command), UVM_LOW) @(posedge my_master_vif.dut_clock_i iff (my_master_vif.reset)); my_master_vif.dut_command <= 1; `uvm_info(get_type_name(), $sformatf("myCmd.start:%h", my_master_vif.dut_command), UVM_LOW) //... endtask : drive_transfer //... endclass
接口代码
interface my_if ( input clock_my, reset, inout wire w_dut_command, output reg dut_clock_i, //... ); timeunit 1ps; timeprecision 1ps; assign dut_clock_i = clock_my; logic dut_command = 'bz; assign w_dut_command = dut_command; clocking driver_cb @(posedge clock_my); default input #1 output #1; inout dut_command; endclocking endinterface : my_if
问题分析与解决
根源
非阻塞赋值(<=)的更新发生在NBA(Non-Blocking Assignment)调度区域,而uvm_info()属于立即执行区域。时钟边沿触发后,你先执行非阻塞赋值,紧接着调用uvm_info读取信号值——此时非阻塞赋值的更新还未生效,读取到的是信号的旧值,这就是打印和波形不一致的原因。
波形查看器显示的是NBA区域更新后的最终值,所以看起来正常;而打印语句在立即区域执行,拿到的是更新前的旧值,导致顺序颠倒。
解决方法
不推荐依赖#1这种非结构化延迟,以下是三种更规范的方案:
1. 使用接口中的时钟块(推荐)
你已经在接口中定义了driver_cb时钟块,它本身就处理了信号的时序同步——默认output #1意味着驱动信号会在时钟边沿后1ps更新,读取信号时也会自动对齐时序。修改驱动代码,通过时钟块访问信号:
task drive_transfer (input my_frame_c trans); @(my_master_vif.driver_cb iff (my_master_vif.reset)); my_master_vif.driver_cb.dut_command <= 0; `uvm_info(get_type_name(), $sformatf("myCmd.start:%h", my_master_vif.driver_cb.dut_command), UVM_LOW) @(my_master_vif.driver_cb iff (my_master_vif.reset)); my_master_vif.driver_cb.dut_command <= 1; `uvm_info(get_type_name(), $sformatf("myCmd.start:%h", my_master_vif.driver_cb.dut_command), UVM_LOW) //... endtask : drive_transfer
时钟块会自动处理时序,确保读取到的是更新后的值,彻底避免竞态。
2. 直接打印赋值变量而非读取信号
既然你已经明确要赋值的是0或1,直接打印变量值而非读取接口信号,从根源上避免竞态:
task drive_transfer (input my_frame_c trans); logic cmd_val; @(posedge my_master_vif.dut_clock_i iff (my_master_vif.reset)); cmd_val = 0; my_master_vif.dut_command <= cmd_val; `uvm_info(get_type_name(), $sformatf("myCmd.start:%h", cmd_val), UVM_LOW) @(posedge my_master_vif.dut_clock_i iff (my_master_vif.reset)); cmd_val = 1; my_master_vif.dut_command <= cmd_val; `uvm_info(get_type_name(), $sformatf("myCmd.start:%h", cmd_val), UVM_LOW) //... endtask : drive_transfer
这种方式最直接,不需要依赖任何时序延迟,完全规避读取信号的竞态问题。
3. 延迟到NBA区域之后读取
如果不想用时钟块,可以在打印前显式等待到时钟边沿后的NBA更新完成,比如等待时钟下降沿:
@(posedge my_master_vif.dut_clock_i iff (my_master_vif.reset)); my_master_vif.dut_command <= 0; @(negedge my_master_vif.dut_clock_i); // 等待NBA更新完成 `uvm_info(get_type_name(), $sformatf("myCmd.start:%h", my_master_vif.dut_command), UVM_LOW)
总结
非阻塞赋值本身是正确的驱动方式,但uvm_info的执行时机和非阻塞赋值的更新时机不在同一个调度区域,导致读取旧值。优先使用时钟块或者直接打印赋值变量的方式解决,避免使用#1这种依赖具体时间的延迟,保证代码的可移植性和稳定性。
内容的提问来源于stack exchange,提问作者bu-ral

