状态机
对于FPGA而言,它的编程语言是Verilog,这是一款硬件描述语言硬件电路是并行执行的,当需要按照流程或者步骤来完成某个功能时,代码中通常会使用很多个if嵌套语句来实现,这样就增加了代码的复杂度,以及降低了代码的可读性,这个时候就可以使用状态机来编写代码。
状态机相当于一个控制器,它将一项功能的完成分解为若干步,每一步对应于二进制的一个状态,通过预先设计的顺序在各状态之间进行转换,状态转换的过程就是实现逻辑功能的过程。
有限状态机(Finite State Machine/FSM)
在有限个状态之间按一定规律转化的时序电路
状态机的种类
根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒(Mealy)型状态机。
Mealy状态机
组合逻辑的输出不仅取决于当前状态,还取决于输入状态。
Mealy状态机的模型如下图所示,模型中第一个方框是指产生下一状态的组合逻辑F,F是当前状态和输入信号的函数,状态是否改变、如何改变,取决于组合逻辑F的输出;第二框图是指状态寄存器,其由一组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳边沿;第三个框图是指产生输出的组合逻辑G,状态机的输出是由输出组合逻辑G提供的,G也是当前状态和输入信号的函数。
Moore状态机
组合逻辑的输出只取决于当前状态。
摩尔状态机的模型如下图所示,对比米勒状态机的模型可以发现,其区别在于米勒状态机的输出由当前状态和输入条件决定的,而摩尔状态机的输出只取决于当前状态。
三段式状态机
使用三个always模块,一个always模块采用同步时序描述状态转移,一个always采用组合逻辑判断状态转移条件,描述状态转移规律,另一个always模块描述状态输出(可以用组合电路输出,也可以时序电路输出)。
设计状态机有这么四个步骤
1.状态空间的定义
状态空间:即是各个状态的集合
在定义状态空间的时候,还需要额外定义两个寄存器,即current_state(过去的状态)以及next_state(下一个状态)。
并且在定义状态空间的时候,也可以使用独热码(即每个状态只有一个寄存器位置)类似于FreeRTOS中的信号组,这种定义方式译码更简单。
parameter S0 = 7'b0000001; //独热码定义方式
parameter S1 = 7'b0000010;
parameter S2 = 7'b0000100;
parameter S3 = 7'b0001000;
parameter S4 = 7'b0010000;
parameter S5 = 7'b0100000;
parameter S6 = 7'b1000000;
//由于是7位的位宽,所以用于存储状态的寄存器也要定义为7位的位宽
reg [6:0] curr_st ; //当前状态
reg [6:0] next_st ; //下一个状态
2. 状态的跳转(时序逻辑)
即满足了当前条件后用来控制当前状态的变化,主要是控制当前状态的变化,并且检测但满足复位条件时将当前状态复位为初始状态。(下一个状态是什么由“下个状态的判断模块来实现”)
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
curr_st <= S0;
else
curr_st <= next_st;
end
3. 下个状态的判断
用来根据当前的状态判断下个状态是什么
//状态机的第二段采用组合逻辑判断状态转移条件
always @(*) begin
case (curr_st)
S0: next_st = S1;
S1: next_st = S2;
S2: next_st = S3;
S3: next_st = S4;
S4: next_st = S5;
S5: next_st = S6;
S6: next_st = S0;
default: next_st = S0;
endcase
end
4. 各个状态下的工作
这里举的例子是时序逻辑电路,但实际上,各个状态下的工作也可以用组合逻辑电路来实现
状态机的第三段可以使用组合逻辑电路输出,也可以使用时序逻辑电路输出,一般推荐使用时序电路输出,因为状态机的设计和其它设计一样,最好使用同步时序方式设计,以提高设计的稳定性,消除毛刺。
//状态机的第三段描述状态输出(这里采用时序电路输出)
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_divide_7 <= 1'b0;
else if ((curr_st == S0) | (curr_st == S1) | (curr_st == S2) | (curr_st == S3))
clk_divide_7 <= 1'b0;
else if ((curr_st == S4) | (curr_st == S5) | (curr_st == S6))
clk_divide_7 <= 1'b1;
else
;
end
状态机采用时序逻辑输出的状态机模型如下图所示:
采用这种描述方法虽然代码结构复杂了一些,但是这样做的好处是可以有效地滤去组合逻辑输出的毛刺,同时也可以更好的进行时序计算与约束,另外对于总线形式的输出信号来说,容易使总线数据对齐,减小总线数据间的偏移,从而降低接收端数据采样出错的频率。
小结
事实上状态机不仅仅在FPGA上适用,利用状态机的思想也可用于STM32等嵌入式的程序编写。特别适用于多级菜单这类人机交互界面。