You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不同仿真器与架构的信号门控差异及仿真异常问题咨询

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将每个时间步划分为多个执行区域,关键顺序为:

  1. 活跃区域:执行阻塞赋值、always块触发、连续赋值等核心逻辑;
  2. 非阻塞赋值更新区域:统一执行所有非阻塞赋值的更新操作;
  3. 停滞区域:执行#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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 23:32:15