always块只换个等号,时序为何变了?
“=”与“<=”的差别不在符号长短,而在左值什么时候变化;写错位置,级联寄存器就可能失去原本的拍间边界
时钟上升沿到来时,你希望A、B、C三级寄存器同时采样各自的旧值。代码里只把“<=”换成“=”,仿真中后面的语句却可能立即读到前面刚写入的新值。
这不是一条单纯的“语法规则”,而是Verilog对更新时刻的建模方式。在时序逻辑里,我们希望一个时钟沿上所有寄存器都基于沿到来之前的状态计算下一状态。
因此审查赋值符时,先问当前always块在描述寄存器还是组合计算,再决定写法。脱离上下文讨论某个符号“永远正确”,很容易得到错的结论。
阻塞赋值会立即改变后续语句看到的值
在一个过程内,阻塞赋值“=”执行后,左侧变量会立即更新。同一个always块里的下一条语句再读这个变量,拿到的就是新值,所以语句顺序可以直接改变仿真结果。
图1 时钟触发块中的赋值方式会决定后续语句读到旧值还是新值
这种语义很适合组合逻辑的逐步计算。例如先算中间量,后续表达式立即使用它。但组合always块还必须为输出提供完整赋值路径,否则又会推断出不希望的锁存器。
把阻塞赋值放进时钟触发块,不一定每次都会综合出不同电路,但它会让代码行顺序参与仿真行为,尤其在多个过程交互时增加竞争风险。
非阻塞赋值把“采样”与“更新”分开非阻塞赋值“<=”在当前always块执行时先计算右值,但不立即改变左值。所有这类更新会在当前仿真时间步的非阻塞更新阶段一起生效。
这正对应同一时钟沿上多个寄存器并行采样旧状态的概念。A与B的书写顺序互换,只要都是非阻塞赋值,它们在这个时钟沿上看到的仍然是各自右侧的旧值。
图2 两个时钟块用非阻塞赋值时,各自基于时钟沿前的状态计算
当同一个信号被多个时钟always块读取时,非阻塞赋值还能避免结果依赖仿真器先执行哪个块。这是时序RTL可预测的重要基础。
混用最容易让代码看起来像“只差半拍”在时钟触发块中先用阻塞赋值更新A,再用非阻塞赋值更新B,B的右值可能已经读到新A。于是原本想表达两级寄存器的代码,在仿真中却像变成了同一拍内传递。
更麻烦的是,综合工具可能仍然根据输出依赖推导出寄存器。如果验证环境又对某些竞争不敏感,这个问题可能直到复杂交互或仿真顺序变化时才暴露。
图3 时钟块内混用阻塞和非阻塞赋值会让右值采样时刻变得难以判断
因此不要把“某次综合结果一样”当成混用可以放行的证据。编码规范的目标是让RTL、仿真和综合对边沿上的更新具有一致、可审查的含义。
审查RTL,先按意图分类遇到posedge或negedge触发的寄存器逻辑,默认对状态、数据和输出寄存器使用非阻塞赋值。时钟块中若出现阻塞赋值,要求作者说清它只是局部中间计算,还是真在依赖语句顺序。
图4 将运算明确拆到两个时钟周期,才能保留预期寄存器边界
组合always_comb或完整敏感列表中,使用阻塞赋值表达逻辑传播。进入分支之前先赋默认值,并检查if、case的每条路径都覆盖输出,避免由于赋值不完整推断锁存器。
静态检查可以把三类风险直接报出:时钟块中的阻塞赋值、同一信号多过程写入,以及组合块赋值不完整。这些规则应在代码评审和CI中固定,不要等到波形异常再回头寻找。
仿真时给关键寄存器级联加断言,确认每一级只在时钟沿后取得前一级的旧值。对跨多always块的交互,随机化复位释放和输入变化时刻,更容易暴露竞争。
复位逻辑也要遵守同一语义。同步复位分支与正常状态更新都在时钟沿采样,寄存器赋值仍应用非阻塞方式;异步复位虽然可在复位边沿立即进入指定状态,释放时却必须满足器件的恢复与移除要求。把复位写法和赋值符混在一起修改,很容易掩盖真正的时序问题。
测试平台与可综合RTL还应分开约束。测试平台为了构造激励可以使用延时和过程化写法,可综合模块则要让每个信号的硬件归属清晰。团队规范若只写一句“时序用非阻塞”,却不检查多驱动、跨过程读写和组合默认赋值,仍会留下竞争与锁存风险。
代码评审时可以画一张两拍表:每个时钟沿之前各寄存器是什么值,边沿上采样什么右值,边沿后哪些寄存器一起更新。只要阻塞赋值让同一个时钟块里的后续语句提前读到新值,表格就会立刻暴露级联被压缩的问题,比盯着符号争论更直观。
时钟块里的“=”与“<=”,差别在于后续语句是否立即看到新值。时序逻辑用非阻塞赋值,是为了让所有寄存器在同一边沿基于旧状态并行计算。
记住符号只是第一步。真正的审查要看这个always块在描述什么硬件,信号在哪个时钟边沿被采样,下一级应该晚几拍看到结果。只要这三件事能对齐,赋值符就不会再是靠背诵选择。
声明:
本文由凡亿教育整理,转载请注明来源!
投稿/招聘/广告/课程合作/资源置换 请加微信:13237418207

扫码关注








































