Vivado仿真时间调度

异步FIFO测试代码中,测试代码与模块代码产生竞争,Vivado仿真时间调度后,产生不正确的结果,故此记录。

现象

代码分析

DUT中代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 读地址
always @(posedge clk or negedge w_rst_n_sync)
begin
if(!w_rst_n_sync)
begin
r_r_addr <= 'b0;
end
else if(i_r_en && (!o_r_empty))
begin
r_r_addr <= r_r_addr + 1'b1;
end
else
begin
r_r_addr <= r_r_addr;
end
end

在该代码中,默认(!o_r_empty)恒为1。

仿真文件中代码:

1
2
@(posedge i_r_clk);
i_r_en = 1;

预期现象

时刻 clk i_r_en r_r_addr 发生了什么?
初始 0 0 初值 系统启动
↑ 第1个clk 0 不变 i_r_en=0,所以不加1
posedge执行完以后 →1 不变 testbench 才执行 i_r_en = 1
↑ 第2个clk 1 加1 此时i_r_en为1,r_r_addr <= r_r_addr+1
NBA阶段结束 新值生效 r_r_addr 在此时更新

实际现象

时刻 clk i_r_en r_r_addr 发生了什么?
初始 0 0 初值 系统启动
↑ 第1个clk →1 初值 i_r_en=1,加1
posedge执行完以后 1 加1 此时i_r_en为1,r_r_addr <= r_r_addr+1
↑ 第2个clk 1 加1 此时i_r_en为1,r_r_addr <= r_r_addr+1
NBA阶段结束 新值生效 r_r_addr 在此时更新

即实际波形在第一拍之后,r_r_addr就开始增加。

Vivado 仿真时间调度机制

每个仿真时刻(simulation time)内部包含多个执行阶段(region),依次执行,直到这一时间所有事件都完成才进入下一个时间点。

一个仿真时间点包含这些区域(按执行顺序):

  1. Active Region(活动区)
  2. Inactive Region(非活动区)
  3. NBA Region(Non-Blocking Assignment,非阻塞区)
  4. Monitor / Postponed Region(监控/最终区)
区域名称 作用 示例触发语句
Active Region 立即执行的行为块、阻塞赋值 a = b;always @* begin ... end
Inactive Region #0 延迟的事件 #0 a = b;
NBA Region 非阻塞赋值的执行区 a <= b;
Monitor/Postponed 仿真输出显示、$monitor、断言检查 $display, $monitor

条件判断的执行阶段

语句类型 所在调度区域 if 条件判断发生在?
always @(*) / always @(posedge clk),内部用 =(阻塞赋值) Active 区 在 Active 区立即判断
always @(posedge clk) 内部用 <=(非阻塞赋值) Active 区进入,但赋值在 NBA 区 if 判断仍在 Active 区进行
如果这个 always 是因为 #0 触发 Inactive 区 if 在 Inactive 区判断
initial 块里有 #5 ... 延迟后执行 延迟结束时所在时间的 Active 区 if 在那一刻时间点的 Active 区判断

FIFO仿真异常原因

  • 在 @(posedge clk); i_r_en = 1; 这种写法中,当 posedge 到达时,所有等待该 edge 的进程都会被唤醒并进入 Active 阶段,但唤醒后哪个进程先运行是未定义的。

  • 如果 Vivado 的 scheduler 恰好先运行 testbench 的 resumed 进程,把 i_r_en 设为 1,然后再运行 DUT 的 always @(posedge clk)中的条件判断,那么 DUT 会在同一拍看到 i_r_en == 1 并把 r_r_addr 在本拍(NBA 最终更新)加 1。

  • 如果调度顺序相反(先运行 DUT,再运行 TB),则本拍不会加 1,要等下一拍。两种行为均符合 Verilog 的语义(没有定义全局的唤醒顺序),所以模拟器之间可能表现不同。

解决方案

方案一 (推荐)

1
@(posedge i_r_clk); #0; i_r_en = 1;

方案二:
(需要修改部分逻辑)

1
2
i_r_en = 1;         // 提前设置,保证第一个有意义的 posedge 被采样
@(posedge i_r_clk);

方案三:
(使用非阻塞赋值 <=)

1
2
@(posedge clk);
i_r_en <= 1;
作者

LiXintao

发布于

2025-11-07

更新于

2025-11-13

许可协议

评论