电子说
在 Verilog的江湖里,流传着一段,两段,三段式状态机的传说。它们各有优劣,本文就书写三段式状态机的错误原因进行探寻。
本文会涉及到Verilog的基本语法,需要一定的Verilog基础,包括对reg,always,状态机的一些认识,数字电路基础。
三段式状态机,将状态机分为三段,功能为状态转移,状态转移条件判断,输出逻辑等。
存在问题的“三段式状态机”代码如下:
程序比较简单,于是直接上板验证。
经过上板调试,发现问题存在于当empty为低时,rd_en并没有拉高。于是,我首先怀疑整个系统是不是一直处于复位状态。
经过查找,发现rst一直保持低电平,整个程序不在复位状态。这就很神奇了。
后来,经过提醒和仔细查找原因,才发现这个状态机写的其实存在问题。状态转移条件判断和状态转移都使用时序逻辑,两者并行运行,问题就凸显出来了。
举例说明,假设当前状态state
, next_state
都处于IDLE
状态;
事实上,这么写,造成的问题是state相对其他信号可能会晚一节拍。状态机晚一节拍,可能会导致时序错乱。
而事实上,三段式状态机当然不是这么写的。
首先,三段式状态机由三部分(段)组成,前面也提到了,是状态转移,状态转移条件判断,输出逻辑等。
首先谈到状态转移,三段式状态机有state, next_state信号作为指示。两者在位宽,信号类型上一模一样,都是reg型变量。
状态转移很简单,大致如下:
其中,IDLE
是预先定义的常量,使用localparam
,parameter
定义即可,或者可以使用`define定义宏变量参数,然后放在一个通用的参数模块文件里,供项目里所有文件调用,后续再详细讲解这块吧。
这块的内容,仅仅是将next_state的值打一拍,通过了一个寄存器,意义何在呢,要结合状态转移条件判断一起看。
状态转移条件判断代码大致如下:
这块的内容主要是做next_state值变更的条件判断,结合功能来说,就是做状态值的更新。
那可能有人会问:
简单来说,always @ ( ) 意为每时每刻都在运行,也就是组合逻辑,你也可以使用always @ (state, a, b)方式来代替always @ ( )。但是,假如always列表里的信号不全,可能造成的影响又是什么呢?
reg型变量最后综合的电路也可能是组合逻辑,也不是说always块一定综合出时序逻辑,主要还是跟敏感列表有关,其次always里只能使用reg型变量
对于时序逻辑来说,没有补全else,default等不会生成锁存器;对于组合逻辑来说,没有else,default确实可能生成锁存器,但是锁存器生成的原因是因为设计代码中要求组合逻辑去保持或者说记忆住某个值,这本该是时序逻辑该干的事,组合逻辑干不了,所以生成锁存器;而本段代码,第一行代码就相当于已经为所有无法进入if条件的情况增加了默认情况,就是赋值为state的状态。
这两块内容都跟状态转移有关,第二块进行条件判断,而判断不仅要根据外部信号,也要根据自身所在的状态,所以第一块进行状态值的更新。
那么这么做的好处是什么呢?跟一段式状态机相比,将状态转移与判断和输出逻辑分开写,方便后续的调试。
因为在调试阶段,查找问题时会去查找问题所在的状态,然后再比对代码中该状态的判断条件,没有错误再比对该条件下问题信号的输出逻辑,调试更为方便。
那么第三段的输出逻辑,代码大致如下:
首先,输出逻辑这段并不是要求只用一个always写完所有的输出逻辑;其次,使用时序逻辑输出,更利于信号的时序分析和时序收敛。
当然,一段式状态机也并不是一无是处,很多时候,在一些逻辑简单,功能简单的场景下,我也愿意使用一段式状态机。两段式状态机用的比较少,感觉像是一段式和三段式状态机的过渡状态,有点鸡肋的感觉。
全部0条评论
快来发表一下你的评论吧 !