verilog仿真波形图里,非阻塞赋值的输入和输出同时的原因 非阻塞赋值输入比输出延迟一拍但是 在同步电路里输入和它打一拍的输出在波形上常常因时钟对齐而看着同步。真正的延迟要看跨多级的累积和结果出现的时刻。always (posedge clk) begin y x; end在某个 clk 上升沿 y 采样的是这个上升沿到来之前的 x时钟沿x 当前值y 更新后第 1 拍x0x0第 2 拍x1x1第 3 拍x2x2看起来 y 在每个时钟沿跟随 x 变化但是实际上除了被赋值的 y所有参与计算的 y 都用的是旧值 x[n-1]也就是y[n] x[n-1]波形图上看着同步是因为x 和 y 都是贴着时钟沿跳变的所以视觉上是同时变化的判断延迟不能只看跳变是不是都对齐时钟沿而要看某个数据从进入寄存器开始经过几拍对应的结果出现在输出端为什么仿真的旧值和新值会同一时刻对齐它俩的变化都发生在同一个时钟上升沿的那一瞬间同一个仿真时刻 t只是在那一瞬间内部的不同阶段。而波形显示时同一个时刻 t 的最终值画在同一条竖线上。Verilog 仿真器在每个时间步内按固定顺序执行Active 区 — 阻塞赋值()、右值采样、always/initial 语句立即执行NBA Non-Blocking Assignment区 — 非阻塞赋值() 的右侧已经算好现在统一写入左侧Postponed 区 — $monitor/strobe 等的效果是右侧在当前时间步算左侧在本时间步结束时才更新(posedge clk); a_row01;用就不一样(posedge clk); a_row0 1; // 立刻生效DUT 同一个 posedge 就能采到 1右值采样a b c右值采样就是把等号右边表达式b c此刻的值计算出来、读取下来、锁在一个临时的地方存着。完整定义右值采样非阻塞赋值在时钟边沿执行时第一步先把等号右边表达式此刻的值计算出来并锁住拍快照此时左边还未更新。第二步才把锁住的值赋给左边。因为采样发生在所有更新之前所以右边引用的其他寄存器采到的都是它们更新前的旧值。阻塞赋值的右值采样非阻塞赋值把一次赋值拆成了两个分开的动作发生在同一时刻的不同阶段动作1在 Active 区——右值采样计算右边b c此刻的值用的是 b 和 c 在这个上升沿时刻的当前值把这个算出来的结果锁进一个临时存储。注意——此时左边的 a 还没有变a 还是旧值。仅仅是把右边的结果算好、存起来待用。动作2在 NBA 区稍后——左值更新把动作1锁住的那个临时结果真正赋给左边的 a。到这里 a 才变。非阻塞赋值是 先采样再更新阻塞赋值是 顺序执行、立即生效always (posedge clk) begin b a; // 立即b变成a c b; // 立即c变成b——但b刚刚已经变成a了 endb a执行完b 立刻变成 a_old。c b执行时b 已经是 a_old 了所以 c 也变成 a_old。为什么时序逻辑必须用非阻塞因为非阻塞赋值的 右值采样机制竞争tb 用赋值dut 用引起 clock edge race tb 用阻塞赋值在上升沿喂数据和 DUT 在同一上升沿采样发生了竞争导致 DUT 采到了 tb 刚更新的新值。同一个时钟边沿既驱动信号tba0、又采样信号DUTba仿真器无法保证谁先谁后于是结果可能是采到旧值也可能是采到新值——行为不确定依赖仿真器实现。怎么避免 racetb 驱动 DUT 输入时不要在 DUT 采样的同一个边沿驱动要错开标准做法有两种做法1tb 用非阻塞赋值喂数据改为。(posedge clk); a_row0 1; // 用 而不是 这样 tb 的赋值也进 NBA 区和 DUT 的采样在调度上有了确定的先后采样在前、更新在后DUT 就稳定采到旧值了。做法2更标准tb 在时钟下降沿、或边沿后延迟一点驱动。(posedge clk); #1 a_row0 1; // 上升沿后延迟1个单位再驱动明确错开采样时刻或者用(negedge clk)在下降沿喂数据。这样驱动和采样在时间上明确分开绝不竞争。工业级 testbench / UVM 的解决方法用 clocking block时钟块系统性解决——它精确定义信号在边沿前多久采样、边沿后多久驱动从根上消除 race。这是 SystemVerilog 验证的核心机制之一