Verilog中FSM的case变量是否需要原子递增?
关于Verilog FSM状态遍历中计数器更新的跳步问题分析
首先直接给结论:两种代码实现都不会导致FSM跳步,但片段2的非原子更新方式确实效率更高——每个状态只需要1个时钟周期,而片段1需要2个。下面具体拆解两种实现的行为逻辑:
片段1:双拍式的计数器更新
片段1通过ITERATE信号把“状态执行”和“计数器递增”拆分成了两个时钟周期:
- 当
ITERATE为低时,执行当前i对应的case状态逻辑; - 当
ITERATE为高时,只执行i = i + 1的递增操作; ITERATE在每个时钟沿翻转,所以每两个时钟周期才会推进一次状态(执行一个状态+更新计数器)。
这种“原子更新”的本质是双拍控制,确实能保证状态不会跳步,但代价是效率减半——因为一半的时钟周期都用来做计数器更新,没有执行状态逻辑。它的适用场景是需要在状态执行和计数器更新之间插入额外控制(比如等待外部信号、插入延迟)的情况,但对于单纯的顺序状态遍历来说,这种设计是冗余的。
片段2:单周期的计数器更新
片段2的逻辑是在同一个时钟周期内,先执行当前i对应的case逻辑,再递增i。这里的关键是理解Verilog中always @(posedge clk)块内阻塞赋值(=)的执行顺序:
- 在同一个时钟沿触发时,块内的语句是按代码顺序执行的:先执行
case逻辑,此时i还是上一个周期的旧值,对应当前要执行的状态; - 执行完
case后,才会执行i = i + 1,把i更新为下一个状态的索引; - 更新后的
i会在下一个时钟沿生效,作为下一次case的判断变量。
这种方式下,每个时钟周期对应一个状态的完整执行+计数器更新,完全不会出现跳步——因为当前周期的状态逻辑永远基于更新前的i,下一个周期才会使用新的i。这也是它效率是片段1两倍的原因:每个状态只需要1个时钟周期。
额外注意点
如果把片段2中的阻塞赋值(=)换成非阻塞赋值(<=),结果也是一样安全的。因为非阻塞赋值是在时钟沿结束后才完成变量更新,case逻辑依然会使用i的旧值,不会影响状态的逐次推进。
总结建议
如果你的应用只是需要按顺序遍历状态,没有额外的控制需求,片段2的实现是更优的选择——它既保证了FSM不会跳步,又能把执行效率提升一倍。片段1的双拍设计只适合需要拆分操作步骤的特殊场景。
内容的提问来源于stack exchange,提问作者Magnus Øverbø
相关产品推荐
相关产品推荐

