状态机

状态机

​ 对于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等嵌入式的程序编写。特别适用于多级菜单这类人机交互界面。