不同仿真器与架构的信号门控差异及仿真异常问题咨询
SystemVerilog仿真中跨模块信号赋值的时序一致性问题
我开展了一项SystemVerilog仿真:在时钟沿之后修改信号strb,并将其连接至D触发器。遇到以下现象:
- 预期D触发器输出在下一个时钟沿结束时变化,但部分仿真器中输出与输入同步变化。
- 在修改信号前添加
#0语句后,所有仿真器均能得到正确结果。 - 将触发器从外部UUT2模块移至testbench内部
always_ff语句后,所有仿真器也能正常工作。
请问这一现象的原因是什么?
相关代码
testbench.sv
module testbench; logic clk; logic strb; logic rdy_out_sv; UUT2 uut2_instance ( .clk(clk), .strb_in(strb), .rdy_out(rdy_out_sv) ); initial begin $dumpfile("dump.vcd"); $dumpvars(0, testbench); end initial begin clk = 0; forever #(2.5) clk=~clk; end initial begin strb = 0; @(posedge clk); //#0; for (int i=0; i<100; i++) begin strb = 1; @(posedge clk); //#0; strb = 0; @(posedge clk); //#0; end $finish; end endmodule
UUT2.sv
module UUT2( input logic clk, input logic strb_in, output logic rdy_out ); always_ff @(posedge clk) begin rdy_out <= strb_in; //Updated to non-blocking after the suggestion from Im Groot end endmodule
仿真情况
- Aldec和Mentor仿真器下结果正确;
- Cadence、Synopsys及Vivado仿真器需取消
#0;语句注释才能得到正确结果; - 将UUT2移至testbench内部后,所有仿真器结果均正确,对应代码如下:
module testbench; logic clk; logic strb; logic rdy_out_sv; always_ff @(posedge clk) begin rdy_out_sv = strb; end initial begin $dumpfile("dump.vcd"); $dumpvars(0, testbench); end initial begin clk = 0; forever #(2.5) clk=~clk; end initial begin strb = 0; @(posedge clk); //#0; for (int i=0; i<100; i++) begin strb = 1; @(posedge clk); //#0; strb = 0; @(posedge clk); //#0; end $finish; end endmodule
问题原因解析
这一现象的核心是SystemVerilog事件队列的调度规则,以及跨模块与同模块信号交互时的时序区域执行差异。
1. 事件队列的时序区域顺序
SystemVerilog将每个时间步划分为多个执行区域,关键顺序为:
- 活跃区域:执行阻塞赋值、
always块触发、连续赋值等核心逻辑; - 非阻塞赋值更新区域:统一执行所有非阻塞赋值的更新操作;
- 停滞区域:执行
#0延迟语句,属于当前时间步的最后一个活跃阶段。
当你在@(posedge clk)后直接修改strb,这个阻塞赋值发生在当前时钟沿触发的活跃区域;而UUT2中的always_ff @(posedge clk)块也会在同一活跃区域被触发,执行rdy_out <= strb_in的非阻塞赋值。
2. 跨模块的仿真器实现差异
不同仿真器对跨模块信号的采样时机处理存在差异:
- Aldec/Mentor仿真器会严格遵循“先收集所有时钟沿触发的
always块,再统一采样输入信号的时钟沿前值”的规则,因此strb的后续修改不会被采样到,结果符合预期; - Cadence/Synopsys/Vivado仿真器则可能在
always_ff块触发时,直接采样strb_in的当前值(即testbench刚修改后的值),导致输出与输入同步变化。
3. 同模块场景的一致性
当触发器和strb的赋值在同一模块时,仿真器会严格按事件队列顺序处理:@(posedge clk)后的strb赋值在活跃区域执行,但同一模块内的always_ff块已经在时钟沿触发时完成了输入采样,因此不会采到刚修改的strb值,所有仿真器结果一致。
4. #0语句的作用
#0延迟会将strb的赋值推迟到当前时间步的停滞区域,此时所有时钟沿触发的always_ff块已经完成采样和非阻塞赋值调度,无论哪个仿真器都会采样到时钟沿前的strb值,因此结果统一正确。
规范写法建议
为避免仿真器差异,testbench激励信号赋值推荐以下方式:
- 添加
#0将赋值推迟到停滞区域; - 采用
##1延迟后赋值,确保在下一个时间步修改信号; - 或使用非阻塞赋值(testbench中更常用阻塞赋值加
#0)。
内容的提问来源于stack exchange,提问作者G. B.
相关产品推荐
相关产品推荐

