Vivado中调用FIFO,用三段式状态机改进
状态机全称是有限状态机(Finite State Machine、FSM),是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
状态机可根据控制信号按照预先设定的状态进行状态转移,这就出现了如何对状态进行有效编码的问题。编码方式,最简单的就是直接使用二进制编码进行表示,除此之外还有使用格雷码、独热码。独热码,每一个状态均使用一个寄存器,在与状态比较时仅仅需要比较一位,相比其他译码电路简单;格雷码,所需寄存器数与二进制码一样,译码复杂,但相邻位只跳动一位,一般用于异步多时钟域多 bit 位的转换,如异步 FIFO;二进制码,最为常见的编码方式,所用寄存器少,译码较复杂。按照 FPGA 厂商给的建议,选择哪一种编码格式是要根据状态机的复杂度、器件类型以 及从非法状态中恢复出来的要求来确定。在使用不同的编码格式生成出来的 RTL 视图中可以看出二进制比独热码使用更少的寄存器。二进制用 7 个寄存器就可以实现 100 个状态的 状态机,但是独热码就需要 100 个寄存器。但是另一方面,虽然独热码使用更多的寄存器但是其组合逻辑相对简单。一般在 CPLD 中,由于提供较多的组合逻辑资源而推荐使用前者, 而在 FPGA 中提供较多的时序逻辑而推荐使用后者。
状态机描述方式,可分为一段式、两段式以及三段式。 一段式,整个状态机写到一个 always 模块里面。在该模块中既描述状态转移,又描述状态的输入和输出。 两段式,用两个 always 模块来描述状态机。其中一个 always 模块采用同步时序描述状态转移,另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律及其输出。 三段式,在两个 always 模块描述方法基础上,使用三个 always 模块。一个 always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出(可以用组合电路输出,也可以时序电路输出)。
编写状态机还应主要注意的事项是,为了避免不必要的锁存器生成,需要穷举所有状态对应的输出动作,或者使用 default 来定义未定义状态动作;在定义状态时,推荐使用参数定义 localparam 或 parameter,这样可以在编写时状态更清晰且不容易出错,也方便修改。 在复位或者跑飞能回到初始态或者预定态,所以在设计中要有异步或者同步复位来确保状态机上电有个初始态。
三段式状态机模板:
//第一个always块,描述现态到次态的状态转移 always@(posedge clk or negedge rstn)begin if(~rstn) current_state <= IDLE; else current_state <= next_state; end
//第二个进程,组合逻辑always模块,描述状态转移条件判断 always@(*)begin next_state = x; //要初始化,使得系统复位后能进入正确的状态 case(current_state) S1: if(...) next_state = S2; //阻塞赋值 S2: if(...) next_state = S3; //阻塞赋值 …… endcase end
//第三个进程,时序逻辑always模块,描述次态寄存器输出 always@(posedge clk or negedge rstn)begin ...//初始化 case(next_state) S1: out1 <= 1'b1; //注意是非阻塞逻辑 S2: out2 <= 1'b1; default:... //default 的作用是免除综合工具综合出锁存器 endcase end
本文对之前的FIFO测试模块用三段式状态机进行修改,使代码思路更加清晰:
利用独热码进行编码,共有三个状态,分别是空闲状态,写状态,读状态。第一段是时序逻辑always块,描述当前状态到下一状态的转移;第二段是组合逻辑always块,判断状态转移条件,描述状态转移规律;第三段是时序逻辑always块,描述状态输出,注意别落了default。