Vivado仿真时间调度
异步FIFO测试代码中,测试代码与模块代码产生竞争,Vivado仿真时间调度后,产生不正确的结果,故此记录。
现象
代码分析
DUT中代码:
1 | // 读地址 |
在该代码中,默认(!o_r_empty)恒为1。
仿真文件中代码:
1 | @(posedge i_r_clk); |
预期现象
| 时刻 | 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),依次执行,直到这一时间所有事件都完成才进入下一个时间点。
一个仿真时间点包含这些区域(按执行顺序):
- Active Region(活动区)
- Inactive Region(非活动区)
- NBA Region(Non-Blocking Assignment,非阻塞区)
- 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 | i_r_en = 1; // 提前设置,保证第一个有意义的 posedge 被采样 |
方案三:
(使用非阻塞赋值 <=)
1 | @(posedge clk); |

