1、状态机

1.1、理论

FPGA不同于CPU的一点特点就是CPU是顺序执行的,而FPGA是同步执行(并行)的。那么FPGA如何处理明显具有时间上先后顺序的事件呢?这个时候我们就需要使用到状态机了。

状态机简写为 FSM(Finite State Machine),也称为同步有限状态机,我们一般简称为状态机,之所以说“同步”是因为状态机中所有的状态跳转都是在时钟的作用下进行的,而“有限”则是说状态的个数是有限的。状态机的每一个状态代表一个事件,从执行当前事件到执行另一事件我们称之为状态的跳转或状态的转移,我们需要做的就是执行该事件然后跳转到一下时间,这样我们的系统就“活”了,可以正常的运转起来了。状态机通过控制各个状态的跳转来控制 流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显。

1.2、分类

根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒(Mealy) 型状态机。

  • Mealy 状态机:输出不仅取决于当前状态,还取决于输入状态。

米勒状态机的模型如下图所示,模型中第一个方框是指产生下一状态的组合逻辑 F,F 是当前状态和输 入信号的函数,状态是否改变、如何改变,取决于组合逻辑 F 的输出;第二框图是指状态寄存器,其由一 组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳边沿;第三个框图是指产生输出的组合逻辑 G,状态机的输出是由输出组合逻辑 G 提供的,G 也是当前状态和输入信号的函数。

  •  Moore 状态机:组合逻辑的输出只取决于当前状态,而与输入状态无关。

摩尔状态机的模型如下图所示,对比米勒状态机的模型可以发现,其区别在于米勒状态机的输出由当 前状态和输入条件决定的,而摩尔状态机的输出只取决于当前状态。

1.3、写法

根据状态机的实际写法,状态机可以分为一段式、二段式和三段式状态机。

  • 一段式状态机:整个状态机写到一个 always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。 
  • 二段式状态机:用两个 always 模块来描述状态机,其中一个 always 模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要定义两个状态,现态和次态,然后通过现态和次态的转换来实现时序逻辑。
  •  三段式状态机:在两个 always 模块描述方法基础上,使用三个always 模块,一个always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出。

2、状态机实例分析

接下来对一个简单的可乐售卖系统使用状态机的思想进行分析。

可乐售卖系统:可乐机每次只能投入 1 枚 1 元硬币,且每瓶可乐卖 3 元钱,即投入 3 个硬币就可以让可乐机出可乐,如果投币不够 3 元想放弃投币需要按复位键,否则之前投入的钱不能退回。

首先分析会有哪些输入、输出信号:

输入信号:

sys_clk_n:既然是同步状态机,那么时钟是肯定少不了的,这里设定时钟是50MHz;

sys_rst_n:一个稳健的系统肯定需要一个复位,这里设定位低电平有效;

money:投币输入,高电平表示投入一元,低电平表示没有投币;

输出信号:

cola:可乐输出,高电平表示掉落一瓶可乐,低电平表示没有可乐掉落;

根据以上输入、输出可以画出状态机模块的示意框图:

接下来需要想一想这个状态机是怎么运作的,也就是要绘制这个系统的状态转移图。前面提到,状态机的状态转移有和输入挂钩的Mealy 状态机,也有和输入无关的Moore 状态机。所以接下来分别用Moore 状态机、Mealy 状态机的思想来绘制状态转移图:

Moore 状态机(输出和输入无关):

  • IDLE:首先是系统复位后的默认状态,这个状态下售卖机里没有钱,没有可乐输出;接下来的状态有两种情况:投了1元硬币则跳转状态ONE、没有投硬币则保持IDLE状态;
  • ONE:这个状态下售卖机里有1元硬币,所以也没有可乐输出;接下来的状态有两种情况:投了1元硬币则跳转状态TWO、没有投硬币则保持ONE状态;
  • TWO:这个状态下售卖机里有2元硬币,所以也没有可乐输出;接下来的状态有两种情况:投了1元硬币则跳转状态THREE、没有投硬币则保持TWO状态;
  • THREE:这个状态下售卖机里有3元硬币,但是因为是使用的时序逻辑,所以在这个时钟周期,是不会有可乐输出的,可乐会在状态跳转后(下一个时钟周期输出);接下来的状态有两种情况:投了1元硬币则跳转状态ONE、没有投硬币则跳转状态IDLE状态;而且状态跳转后会输出一瓶可乐(实际上可以理解为THREE状态来自于TWO状态投的一元硬币,也就是这个时钟周期如果输出发生了变化,则输出是和输入有关的了,那就不是Moore 状态机了);

根据上面列出的这些状态可以绘制出如下的状态转移图(1/0:前面的1代表输入,后面的0代表输出):

Mealy 状态机(输出和输入相关):

  • IDLE:首先是系统复位后的默认状态,没有可乐输出(分两种情况,投币和不投币);接下来的状态有两种情况:投了1元硬币则跳转状态ONE且没有可乐输出、没有投硬币则保持IDLE状态且没有可乐输出;
  • ONE:这个状态下售卖机里有1元硬币;接下来的状态有两种情况:投了1元硬币则跳转状态TWO且没有可乐输出、没有投硬币则保持ONE状态且没有可乐输出;
  • TWO:这个状态下售卖机里有2元硬币;接下来的状态有两种情况:投了1元硬币则跳转状态IDLE且输出可乐(根据和输入相关要求,此时输入一元,加上原来的2元,一共有三元,满足输出可乐的条件)、没有投硬币则保持TWO状态且没有可乐输出;

根据上面列出的这些状态可以绘制出如下的状态转移图(1/0:前面的1代表输入,后面的0代表输出):

从上面的分析可以得到以下结论:

  • Mealy 状态机比Moore状态机的状态个数要少
  • Mealy 状态机比Moore状态机的输出要早一个时钟周期

接下来就各种写法对状态机进行仿真分析。

3、一段式状态机

一段式状态机是将整个状态机写到一个 always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。不推荐采用这种状态机,因为从代码风格方面来讲,一般都会要求把组合逻辑和时序逻辑分开;从代 码维护和升级来说,组合逻辑和时序逻辑混合在一起不利于代码维护和修改,也不利于约束。

3.1、Moore型(摩尔型)一段式状态机

Verillog代码如下:

//==================================================================
//--    1段式状态机(Moore)
//==================================================================//------------<模块及端口声明>----------------------------------------
module FSM_Moore_1(input        sys_clk     ,       //输入系统时钟、50Minput       sys_rst_n   ,       //复位信号、低电平有效input       money       ,       //投币输入,高电平有效output reg   cola                //可乐输出,高电平有效
);//------------<状态机参数定义>------------------------------------------
//这里使用独热码编码节省组合逻辑资源
//此外还可以使用格雷码 、二进制码
localparam  IDLE  = 4'b0001,ONE   = 4'b0010,TWO   = 4'b0100,THREE = 4'b1000;//------------<reg定义>-------------------------------------------------
reg [3:0]   state;                  //定义状态寄存器//-----------------------------------------------------------------------
//--    1段式状态机(Moore)
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begincola <= 1'b0;                //复位初始状态state <= IDLE;endelsecase(state)                    //根据当前状态、输入进行状态转换判断//根据当前状态进行输出IDLE:begincola <= 1'b0;     //初始状态无可乐输出if(money)                state <= ONE;   //投币1元则状态跳转到ONEelsestate <= IDLE;   //否则保持原有状态end               ONE:begincola <= 1'b0;     //该状态只有1元,无可乐输出if(money)state <= TWO;    //投币1元则状态跳转到TWOelsestate <= ONE;    //否则保持原有状态endTWO:begincola <= 1'b0;        //该状态只有2元,无可乐输出if(money)state <= THREE;  //投币1元则状态跳转到THREEelsestate <= TWO;  //否则保持原有状态end THREE:begincola <= 1'b1;     //该状态有3元,有可乐输出//但是时序逻辑输出会落后一个时钟周期if(money)state <= ONE;  //投币1元则状态跳转到ONEelsestate <= IDLE;   //否则状态跳转到IDLEend            default:begin           //默认状态同IDLEcola <= 1'b0;if(money)state <= ONE;elsestate <= IDLE;end    endcase
endendmodule

使用QuartusII编码生成的状态机视图如下:

可以看到,这和我们之前绘制的状态转移图一致。

状态机的编码方式一般有三种,各有优劣,独热码算是用的比较多的:

  • 独热码
  • 格雷码
  • 二进制码

编写Testbench文件进行仿真,文件如下:

//-------------------------------------------------------------------
//--    1段式状态机(Moore)
//-------------------------------------------------------------------
`timescale 1ns/1ns//------------<模块及端口声明>----------------------------------------
module tb_FSM_Moore_1();
reg     sys_clk;
reg     sys_rst_n;
reg     money;
wire    cola;//------------<例化被测试模块>----------------------------------------
FSM_Moore_1 FSM_Moore_1_inst(.sys_clk   (sys_clk)   ,.sys_rst_n (sys_rst_n) ,.money     (money)     ,.cola       (cola)
);
//------------<设置初始测试条件>----------------------------------------
initial beginsys_clk = 1'b0;                  //初始时钟为0sys_rst_n <= 1'b0;             //初始复位money <= 1'b0;                   //投币初始化为0#5                             //5个时钟周期后sys_rst_n <= 1'b1;                //拉高复位,系统进入工作状态#25                               //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #40                             //40个时钟周期后money <= 1'b0;                   //拉低投币信号    #20                             //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #80                             //25个时钟周期后money <= 1'b0;                   //拉低投币信号
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;     //系统时钟周期20ns//------------<状态机名称查看器>----------------------------------------
reg [39:0]  state_name;             //每字符8位宽,这里最多5个字符40位宽always @(*) begincase(FSM_Moore_1_inst.state)4'b0001: state_name = "IDLE";4'b0010:    state_name = "ONE";4'b0100:     state_name = "TWO";4'b1000:     state_name = "THREE";default:    state_name = "IDLE";endcase
endendmodule

使用ModelSim执行仿真,仿真出来的波形如下所示:

可以看到:

  • 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
  • 第1次输出可乐滞后THREE状态一个时钟周期,且当前的输入为1;第2次输出可乐滞后THREE状态一个时钟周期,且当前的输入为0;这说明输出会之后当前状态一个时钟周期,且与输入无关(输入不管是0还是1都有输出);
  • 状态的跳转符合我们绘制的状态转移图。

3.2、Mealy型(米勒型)一段式状态机

Verillog代码如下:

//==================================================================
//--    1段式状态机(Mealy)
//==================================================================//------------<模块及端口声明>----------------------------------------
module FSM_Mealy_1(input        sys_clk     ,           //输入系统时钟、50Minput       sys_rst_n   ,           //复位信号、低电平有效input       money       ,           //投币输入,高电平有效output reg   cola                    //可乐输出,高电平有效
);//------------<状态机参数定义>------------------------------------------
//这里使用独热码编码节省组合逻辑资源
//此外还可以使用格雷码 、二进制码
localparam  IDLE  = 3'b001,ONE   = 3'b010,TWO   = 3'b100;//------------<reg定义>------------------------------------------------
reg [2:0]   state;                      //定义状态寄存器//-----------------------------------------------------------------------
//--    1段式状态机(Mealy)
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begincola <= 1'b0;                    //复位初始状态state <= IDLE;                  //复位初始状态endelsecase(state)                      //根据当前状态、输入进行状态转换判断//根据当前状态、输入进行输出IDLE:begin                if(money)begin          //投入1元state <= ONE;     //状态跳转到ONEcola <= 1'b0;        //一共1元 ,没有可乐输出end    else begin              //没有投入state <= IDLE;        //保持原有状态cola <= 1'b0;      //一共0元 ,没有可乐输出end    end             ONE:begin               if(money)begin          //投入1元state <= TWO;     //状态跳转到TWOcola <= 1'b0;       //一共2元 ,没有可乐输出end                     else begin              //没有投入state <= ONE;       //保持原有状态cola <= 1'b0;       //一共1元 ,没有可乐输出endendTWO:begin                 if(money)begin          //投入1元state <= IDLE;      //状态跳转到IDLE(一共3元了,需要输出可乐)cola <= 1'b1;       //一共3元 ,输出可乐end                     else begin              //没有投入state <= TWO;       //保持原有状态cola <= 1'b0;       //一共2元 ,没有可乐输出endend      default:begin               //默认状态同初始状态if(money)beginstate <= ONE;cola <= 1'b0;endelse beginstate <= IDLE;cola <= 1'b0;end    end     endcase
endendmodule

使用QuartusII编码生成的状态机视图如下:

可以看到,这和我们之前绘制的状态转移图一致。

编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:

//-------------------------------------------------------------------
//--    1段式状态机(Mealy)
//-------------------------------------------------------------------
`timescale 1ns/1ns//------------<模块及端口声明>----------------------------------------
module tb_FSM_Mealy_1();
reg     sys_clk;
reg     sys_rst_n;
reg     money;
wire    cola;//------------<例化被测试模块>----------------------------------------
FSM_Mealy_1 FSM_Mealy_1_inst(.sys_clk   (sys_clk)   ,.sys_rst_n (sys_rst_n) ,.money     (money)     ,.cola       (cola)
);
//------------<设置初始测试条件>----------------------------------------
initial beginsys_clk = 1'b0;                  //初始时钟为0sys_rst_n <= 1'b0;             //初始复位money <= 1'b0;                   //投币初始化为0#5                             //5个时钟周期后sys_rst_n <= 1'b1;                //拉高复位,系统进入工作状态#25                               //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #40                             //40个时钟周期后money <= 1'b0;                   //拉低投币信号    #20                             //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #80                             //25个时钟周期后money <= 1'b0;                   //拉低投币信号
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;     //系统时钟周期20ns//------------<状态机名称查看器>----------------------------------------
reg [31:0]  state_name;             //每字符8位宽,这里最多4个字符32位宽always @(*) begincase(FSM_Mealy_1_inst.state)3'b001:      state_name = "IDLE";3'b010:     state_name = "ONE";3'b100:  state_name = "TWO";default:  state_name = "IDLE";endcase
endendmodule

使用ModelSim执行仿真,仿真出来的波形如下所示:

从仿真结果可以看到:

  • 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
  • 第1次输出可乐滞后TWO状态一个时钟周期,且当前的输入为1;第2次输出可乐滞后TWO状态一个时钟周期,且当前的输入也为1;这说明输出会之后当前状态一个时钟周期,且与输入相关(只有输入为 1才有输出,在第4个时钟是,输入为0,所以没有输出);
  • 状态的跳转符合我们绘制的状态转移图。

通过以上,针对一段式状态机可以得出如下小结:

  • Moore型状态机输出滞后Mealy型状态机一个时钟周期
  • 一段式状态机将所有状态转移与输出全写在一个always块里,如果状态多的话就会看起来十分臃肿,且不利于维护

4、二段式状态机

二段式状态机用两个 always 模块来描述状态机,其中一个 always 模块采用同步时序描述状态转移;另一个 模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要定 义两个状态,现态和次态,然后通过现态和次态的转换来实现时序逻辑。

4.1、Moore型(摩尔型)二段式状态机

Verillog代码如下:

//==================================================================
//--    2段式状态机(Moore)
//==================================================================//------------<模块及端口声明>----------------------------------------
module FSM_Moore_2(input        sys_clk     ,       //输入系统时钟、50Minput       sys_rst_n   ,       //复位信号、低电平有效input       money       ,       //投币输入,高电平有效output reg   cola                //可乐输出,高电平有效
);//------------<状态机参数定义>------------------------------------------
localparam  IDLE  = 4'b0001,ONE   = 4'b0010,TWO   = 4'b0100,THREE = 4'b1000;
//------------<reg定义>-------------------------------------------------
reg [3:0]   cur_state;              //定义现态寄存器
reg [3:0]   next_state;             //定义次态寄存器//-----------------------------------------------------------------------
//--状态机第一段:同步时序描述状态转移
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)cur_state <= IDLE;         //复位初始状态elsecur_state <= next_state;    //次态转移到现态
end//-----------------------------------------------------------------------
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
//-----------------------------------------------------------------------
always@(*)begin                        //组合逻辑case(cur_state)                   //根据当前状态、输入进行状态转换判断//根据当前状态进行输出IDLE:begincola = 1'b0;         //初始状态无可乐输出if(money)                //投币1元next_state = ONE;    //次态(下个状态)为ONE        else next_state = IDLE;    //次态为现态 end                 ONE:begincola = 1'b0;         //无可乐输出if(money)                //投币1元next_state = TWO;    //次态(下个状态)为TWOelse next_state = ONE; //次态为现态         endTWO:begin                    cola = 1'b0;          //无可乐输出if(money)               //投币1元next_state = THREE; //次态(下个状态)为THREEelse                                next_state = TWO;  //次态为现态end  THREE:begin cola = 1'b1;          //输出可乐输出if(money)               //投币1元next_state = ONE;    //次态(下个状态)为ONE        else                                next_state = IDLE;  //次态为IDLEenddefault:begin              //默认状态同IDLEcola = 1'b0;       if(money)next_state = ONE;else next_state = IDLE;end  endcase
endendmodule

使用QuartusII编码生成的状态机视图如下:

可以看到,这和我们之前绘制的状态转移图一致。

编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:

//------------------------------------------------
//--    2段式状态机(Moore)
//------------------------------------------------
`timescale 1ns/1ns//------------<模块及端口声明>----------------------------------------
module tb_FSM_Moore_2();reg     sys_clk;
reg     sys_rst_n;
reg     money;wire  cola;//------------<例化被测试模块>----------------------------------------
FSM_Moore_2 FSM_Moore_2_inst(.sys_clk   (sys_clk)   ,.sys_rst_n (sys_rst_n) ,.money     (money)     ,.cola       (cola)
);//------------<设置初始测试条件>----------------------------------------
initial beginsys_clk = 1'b0;                  //初始时钟为0sys_rst_n <= 1'b0;             //初始复位money <= 1'b0;                   //投币初始化为0#5                             //5个时钟周期后sys_rst_n <= 1'b1;                //拉高复位,系统进入工作状态#25                               //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #40                             //40个时钟周期后money <= 1'b0;                   //拉低投币信号    #20                             //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #80                             //25个时钟周期后money <= 1'b0;                   //拉低投币信号
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;     //系统时钟周期20ns//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
reg [39:0]  state_name_cur;         //每字符8位宽,这里最多5个字符40位宽
reg [39:0]  state_name_next;        //每字符8位宽,这里最多5个字符40位宽always @(*) begincase(FSM_Moore_2_inst.cur_state)4'b0001:     state_name_cur = "IDLE";4'b0010:    state_name_cur = "ONE";4'b0100:     state_name_cur = "TWO";4'b1000:     state_name_cur = "THREE"; default:       state_name_cur = "IDLE";endcase
endalways @(*) begincase(FSM_Moore_2_inst.next_state)4'b0001:     state_name_next = "IDLE";4'b0010:       state_name_next = "ONE";4'b0100:    state_name_next = "TWO";4'b1000:        state_name_next = "THREE"; default:      state_name_next = "IDLE";endcase
endendmodule

使用ModelSim执行仿真,仿真出来的波形如下所示:

从仿真结果可以看到:

  • 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
  • 现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
  • 与一段式状态机不同,可乐的输出不会滞后一个时钟周期,这是因为采用了组合逻辑来描述输出;
  • 第1次输出可乐的输入为1,第2次输出可乐的输入为0;这说明输出与输入无关(输入不管是0还是1都有输出);
  • 状态的跳转符合我们绘制的状态转移图;

4.2、Mealy型(米勒型)二段式状态机

Verillog代码如下:

//------------------------------------------------
//--    2段式状态机(Mealy )
//------------------------------------------------//------------<模块及端口声明>----------------------------------------
module FSM_Mealy_2(input        sys_clk     ,           //输入系统时钟、50Minput       sys_rst_n   ,           //复位信号、低电平有效input       money       ,           //投币输入,高电平有效output reg   cola                    //可乐输出,高电平有效
);//------------<状态机参数定义>------------------------------------------
localparam  IDLE  = 3'b001,ONE   = 3'b010,TWO   = 3'b100;//------------<reg定义>------------------------------------------------
reg [2:0]   cur_state;                  //定义现态
reg [2:0]   next_state;                 //定义次态//-----------------------------------------------------------------------
//--状态机第一段:同步时序描述状态转移
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)cur_state <= IDLE;             //复位初始状态else                                cur_state <= next_state;        //次态转移到现态
end//-----------------------------------------------------------------------
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
//-----------------------------------------------------------------------
always@(*)begincase(cur_state)                     //组合逻辑IDLE:begin                        //根据当前状态、输入进行状态转换判断//根据当前状态、输入进行输出if(money)begin                //当前输入为1next_state = ONE;      //次态为ONEcola = 1'b0;          //一共1元 ,没有可乐输出endelse begin                  //当前输入为0next_state = IDLE;     //次态为IDLEcola = 1'b0;         //一共0元 ,没有可乐输出end    end                 ONE:begin               if(money)begin              //当前输入为1next_state = TWO;       //次态为TWOcola = 1'b0;            //一共2元 ,没有可乐输出end                         else begin                  //当前输入为0next_state = ONE;       //次态为ONEcola = 1'b0;            //一共1元 ,没有可乐输出end endTWO:begin                if(money)begin              //当前输入为1next_state = IDLE;     //次态为IDLEcola = 1'b1;            //一共3元 ,输出可乐end                         else begin                  //当前输入为0next_state = TWO;       //次态为TWOcola = 1'b0;            //一共2元 ,没有可乐输出end   end     default:begin                   //默认状态同初始状态if(money)beginnext_state = ONE;cola = 1'b0;endelse beginnext_state = IDLE;cola = 1'b0;end  endendcase
endendmodule

使用QuartusII编码生成的状态机视图如下:

可以看到,这和我们之前绘制的状态转移图一致。

编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:

//------------------------------------------------
//--    2段式状态机(Mealy)
//------------------------------------------------
`timescale 1ns/1ns//------------<模块及端口声明>----------------------------------------
module tb_FSM_Mealy_2();reg     sys_clk;
reg     sys_rst_n;
reg     money;
wire    cola;//------------<例化被测试模块>----------------------------------------
FSM_Mealy_2     FSM_Mealy_2_inst(.sys_clk   (sys_clk),.sys_rst_n    (sys_rst_n),.money      (money),.cola       (cola)
);//------------<设置初始测试条件>----------------------------------------
initial beginsys_clk = 1'b0;                  //初始时钟为0sys_rst_n <= 1'b0;             //初始复位money <= 1'b0;                   //投币初始化为0#5                             //5个时钟周期后sys_rst_n <= 1'b1;                //拉高复位,系统进入工作状态#25                               //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #40                             //40个时钟周期后money <= 1'b0;                   //拉低投币信号    #20                             //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #80                             //25个时钟周期后money <= 1'b0;                   //拉低投币信号
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;     //系统时钟周期20ns//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
//1字符8位宽
reg [31:0]  state_name_cur;         //每字符8位宽,这里最多4个字符32位宽
reg [31:0]  state_name_next;        //每字符8位宽,这里最多4个字符32位宽always @(*) begincase(FSM_Mealy_2_inst.cur_state)3'b001:      state_name_cur = "IDLE";3'b010:     state_name_cur = "ONE";3'b100:      state_name_cur = "TWO";default:  state_name_cur = "IDLE";endcase
endalways @(*) begincase(FSM_Mealy_2_inst.next_state)3'b001:      state_name_next = "IDLE";3'b010:    state_name_next = "ONE";3'b100:     state_name_next = "TWO";default: state_name_next = "IDLE";endcase
endendmodule

使用ModelSim执行仿真,仿真出来的波形如下所示:

从仿真结果可以看到:

  • 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
  • 现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
  • 与一段式状态机不同,可乐的输出不会滞后一个时钟周期,这是因为采用了组合逻辑来描述输出;
  • 第1次输出可乐的输入为1,第2次输出可乐的输入也为1;这说明输出与输入有关;
  • 状态的跳转符合我们绘制的状态转移图;

通过以上,针对二段式状态机可以得出如下小结:

  • Moore型状态机输出滞后Mealy型状态机一个时钟周期
  • 二段式状态机的输出使用组合逻辑输出,而使用组合逻辑则无法避免的会引入“毛刺”问题

5、三段式状态机

三段式状态机在两个 always 模块描述方法基础上,使用三个always 模块,一个always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出。

5.1、Moore型(摩尔型)三段式状态机

Verilog代码如下:

//==================================================================
//--    3段式状态机(Moore)
//==================================================================//------------<模块及端口声明>----------------------------------------
module FSM_Moore_3(input        sys_clk     ,           //输入系统时钟、50Minput       sys_rst_n   ,           //复位信号、低电平有效input       money       ,           //投币输入,高电平有效output reg   cola                    //可乐输出,高电平有效
);//------------<状态机参数定义>------------------------------------------
localparam  IDLE  = 4'b0001,ONE   = 4'b0010,TWO   = 4'b0100,THREE = 4'b1000;//------------<reg定义>-------------------------------------------------
reg [3:0]   cur_state;                  //定义现态寄存器
reg [3:0]   next_state;                 //定义次态寄存器//-----------------------------------------------------------------------
//--状态机第一段:同步时序描述状态转移
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)cur_state <= IDLE;             //复位初始状态elsecur_state <= next_state;        //次态转移到现态
end//-----------------------------------------------------------------------
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
//-----------------------------------------------------------------------
always@(*)begincase(cur_state)                     //组合逻辑//根据当前状态、输入进行状态转换判断                                       IDLE:begin              if(money)                   next_state = ONE;      //投币1元,则状态转移到ONEelse next_state = IDLE;     //没有投币,则状态保持 end                 ONE:begin               if(money)next_state = TWO;     //投币1元,则状态转移到TWOelse next_state = ONE;      //没有投币,则状态保持endTWO:begin             if(money)next_state = THREE;       //投币1元,则状态转移到THREEelse                        next_state = TWO;       //没有投币,则状态保持end    THREE:begin             if(money)next_state = ONE;     //投币1元,则状态转移到ONEelse                        next_state = IDLE;      //没有投币,则状态保持enddefault:begin                 //默认状态同IDLEif(money)next_state = ONE;else next_state = IDLE;  endendcase
end//-----------------------------------------------------------------------
//--状态机第三段:时序逻辑描述输出
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)cola <= 1'b0;                 //复位、初始状态 elsecase(cur_state)                   //根据当前状态进行输出IDLE:   cola <= 1'b0;      //无可乐输出         ONE:    cola <= 1'b0;      //无可乐输出TWO: cola <= 1'b0;      //无可乐输出THREE:   cola <= 1'b1;      //输出可乐default:cola <= 1'b0;        //默认无可乐输出endcase
endendmodule

使用QuartusII编码生成的状态机视图如下:

可以看到,这和我们之前绘制的状态转移图一致。

编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:

//------------------------------------------------
//--    3段式状态机(Moore)
//------------------------------------------------
`timescale 1ns/1ns//------------<模块及端口声明>----------------------------------------
module tb_FSM_Moore_3();reg     sys_clk;
reg     sys_rst_n;
reg     money;wire  cola;//------------<例化被测试模块>----------------------------------------
FSM_Moore_3     FSM_Moore_3_inst(.sys_clk   (sys_clk),.sys_rst_n    (sys_rst_n),.money      (money),.cola       (cola)
);//------------<设置初始测试条件>----------------------------------------
initial beginsys_clk = 1'b0;                  //初始时钟为0sys_rst_n <= 1'b0;             //初始复位money <= 1'b0;                   //投币初始化为0#5                             //5个时钟周期后sys_rst_n <= 1'b1;                //拉高复位,系统进入工作状态#25                               //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #40                             //40个时钟周期后money <= 1'b0;                   //拉低投币信号    #20                             //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #80                             //25个时钟周期后money <= 1'b0;                   //拉低投币信号
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;     //系统时钟周期20ns//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
reg [39:0]  state_name_cur;         //每字符8位宽,这里最多5个字符40位宽
reg [39:0]  state_name_next;        //每字符8位宽,这里最多5个字符40位宽always @(*) begincase(FSM_Moore_3_inst.cur_state)4'b0001:     state_name_cur = "IDLE";4'b0010:    state_name_cur = "ONE";4'b0100:     state_name_cur = "TWO";4'b1000:     state_name_cur = "THREE"; default:       state_name_cur = "IDLE";endcase
endalways @(*) begincase(FSM_Moore_3_inst.next_state)4'b0001:     state_name_next = "IDLE";4'b0010:       state_name_next = "ONE";4'b0100:    state_name_next = "TWO";4'b1000:        state_name_next = "THREE"; default:      state_name_next = "IDLE";endcase
endendmodule

使用ModelSim执行仿真,仿真出来的波形如下所示:

从仿真结果可以看到:

  • 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
  • 现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
  • 与二段式状态机不同,可乐的输出会滞后一个时钟周期,这是因为采用了时序逻辑来描述输出;
  • 第1次输出可乐的输入为1,第2次输出可乐的输入为0;这说明输出与输入无关;
  • 状态的跳转符合我们绘制的状态转移图;
  • 波形图除了多了一个次态外,其余与一段式 的Moore状态机完全一致;

5.2、Mealy型(米勒型)三段式状态机

Verilog代码如下:

//==================================================================
//--    3段式状态机(Mealy)
//==================================================================//------------<模块及端口声明>----------------------------------------
module FSM_Mealy_3(input        sys_clk     ,           //输入系统时钟、50Minput       sys_rst_n   ,           //复位信号、低电平有效input       money       ,           //投币输入,高电平有效output reg   cola                    //可乐输出,高电平有效
);//------------<状态机参数定义>------------------------------------------
localparam  IDLE  = 3'b0001,ONE   = 3'b0010,TWO   = 3'b0100;//------------<reg定义>-------------------------------------------------
reg [3:0]   cur_state;                  //定义现态寄存器
reg [3:0]   next_state;                 //定义次态寄存器//-----------------------------------------------------------------------
//--状态机第一段:同步时序描述状态转移
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)cur_state <= IDLE;             //复位初始状态elsecur_state <= next_state;        //次态转移到现态
end//-----------------------------------------------------------------------
//--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
//-----------------------------------------------------------------------
always@(*)begincase(cur_state)                     //组合逻辑//根据当前状态、输入进行状态转换判断                                       IDLE:begin              if(money)                   next_state = ONE;      //投币1元,则状态转移到ONEelse next_state = IDLE;     //没有投币,则状态保持 end                 ONE:begin               if(money)next_state = TWO;     //投币1元,则状态转移到TWOelse next_state = ONE;      //没有投币,则状态保持endTWO:begin             if(money)next_state = IDLE;        //投币1元,则状态转移到IDLEelse                        next_state = TWO;       //没有投币,则状态保持end default:begin                   //默认状态同IDLEif(money)next_state = ONE;else next_state = IDLE;  endendcase
end//-----------------------------------------------------------------------
//--状态机第三段:时序逻辑描述输出
//-----------------------------------------------------------------------
always@(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)cola <= 1'b0;                 //复位、初始状态 elsecase(cur_state)                   //根据当前状态进行输出IDLE:   cola <= 1'b0;      //无可乐输出(因为输入不管是0、1都是输出0,所以省略写法)          ONE:    cola <= 1'b0;      //无可乐输出(因为输入不管是0、1都是输出0,所以省略写法)  TWO:begin                   if(money)cola <= 1'b1;     //如果输入1,则输出可乐elsecola <= 1'b0;      //如果输入0,则无可乐输出enddefault:cola <= 1'b0;      //默认无可乐输出endcase
endendmodule

使用QuartusII编码生成的状态机视图如下:

可以看到,这和我们之前绘制的状态转移图一致。

编写Testbench文件进行仿真,仿真激励设置和Moore型一段式状态机一致,文件如下:

//------------------------------------------------
//--    3段式状态机(Mealy)
//------------------------------------------------
`timescale 1ns/1ns//------------<模块及端口声明>----------------------------------------
module tb_FSM_Mealy_3();reg     sys_clk;
reg     sys_rst_n;
reg     money;wire  cola;//------------<例化被测试模块>----------------------------------------
FSM_Mealy_3     FSM_Mealy_3_inst(.sys_clk   (sys_clk),.sys_rst_n    (sys_rst_n),.money      (money),.cola       (cola)
);//------------<设置初始测试条件>----------------------------------------
initial beginsys_clk = 1'b0;                  //初始时钟为0sys_rst_n <= 1'b0;             //初始复位money <= 1'b0;                   //投币初始化为0#5                             //5个时钟周期后sys_rst_n <= 1'b1;                //拉高复位,系统进入工作状态#25                               //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #40                             //40个时钟周期后money <= 1'b0;                   //拉低投币信号    #20                             //25个时钟周期后money <= 1'b1;                   //拉高投币信号    #80                             //25个时钟周期后money <= 1'b0;                   //拉低投币信号
end
//------------<设置时钟>----------------------------------------------
always #10 sys_clk = ~sys_clk;     //系统时钟周期20ns//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
reg [39:0]  state_name_cur;         //每字符8位宽,这里最多5个字符40位宽
reg [39:0]  state_name_next;        //每字符8位宽,这里最多5个字符40位宽always @(*) begincase(FSM_Mealy_3_inst.cur_state)4'b0001:     state_name_cur = "IDLE";4'b0010:    state_name_cur = "ONE";4'b0100:     state_name_cur = "TWO";4'b1000:     state_name_cur = "THREE"; default:       state_name_cur = "IDLE";endcase
endalways @(*) begincase(FSM_Mealy_3_inst.next_state)4'b0001:     state_name_next = "IDLE";4'b0010:       state_name_next = "ONE";4'b0100:    state_name_next = "TWO";4'b1000:        state_name_next = "THREE"; default:      state_name_next = "IDLE";endcase
endendmodule

使用ModelSim执行仿真,仿真出来的波形如下所示:

从仿真结果可以看到:

  • 在第2、3、5、6、7、8的6个时钟投入了6个硬币,理论上应该有2个可乐分别输出,实际也有两个可乐输出;
  • 现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
  • 与二段式状态机不同,可乐的输出会滞后一个时钟周期,这是因为采用了时序逻辑来描述输出;
  • 第1次输出可乐的输入为1,第2次输出可乐的输入为0;这说明输出与输入无关;
  • 状态的跳转符合我们绘制的状态转移图;
  • 波形图除了多了一个次态外,其余与一段式的Mealy状态机完全一致;

通过以上,针对三段式状态机可以得出如下小结:

  • Moore型状态机输出滞后Mealy型状态机一个时钟周期
  • 三段式状态机的输出使用时序逻辑输出,避免了二段式状态机使用组合逻辑输出从而无法避免的“毛刺”问题

6、总结与思考

状态机的三种描述方法:

一段式:整个状态机写到一个always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。

二段式:用两个always模块来描述状态机,其中一个always模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。

三段式:在两个always模块描述方法基础上,使用三个always模块,一个always模块采用同步时序描述状态转移,一个always采用组合逻辑判断状态转移条件,描述状态转移规律,另一个always模块描述状态输出。

应该选择哪一种状态机 ?

一段式状态机写法不够模块化 ,且过于臃肿不利于维护,及布局布线;

二段式状态机将同步时序和组合逻辑分别放到不同的always模块中实现,这样做的好处不仅仅是便于阅读、理解、维护,更重要的是利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。但是其当前状态的输出用组合逻辑实现,组合逻辑很容易产生毛刺,而且不利于约束,不利于综合器和布局布线器实现高性能的设计。

三段式状态机与二段式状态机相比,关键在于根据状态转移规律,在上一状态根据输入条件判断出当前状态的输出,从而在不插入额外时钟节拍的前提下,实现了寄存器输出,解决了毛刺问题。实际应用中三段式状态机使用最多,因为三段式状态机将组合逻辑和时序分开,有利于综合器分析优 化以及程序的维护;并且三段式状态机将状态转移与状态输出分开,使代码看上去更加清晰易懂,提高了代码的可读性,推荐大家使用三段式状态机。

三段式状态机的基本格式:

第一个 always 语句实现同步状态跳转;

第二个 always 语句采用组合逻辑判断状态转移条件;

第三个 always 语句采用时序逻辑描述状态输出。

状态机的编码方式:

   独热码:和格雷码相比,虽然独热码多用了触发器,但所用组合电路可以省一些,因而使电路的速度和可靠性有显著提高,而总的单元数并无显著增加。因为独热码只有一位的变化,所以更适用于高速系统。

格雷码:使用了更多的组合逻辑资源,但是比独热码能表示更多的状态。

2进制:使用了更多的组合逻辑资源,但是比独热码能表示更多的状态,稳定性不如格雷码。

三段式状态机的第三段采用next_state还是cur_state:

        第三段使用next_state和cur_state的区别在于,当状态跳转时,基于next_state的输出是立刻变化的,而基于cur_state输出会延迟一个周期,其他情况都一样,应该根据自己的时序要求选择。

modelsim显示状态机名称的方法:

1.在Testbench中添加如下语句:实质上就是在测试文件里添加一个变量来代替你要观察的变量;

2.在modelsim的波形仿真见面右击波形,选择Radix--ASSIC。

//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
reg [39:0]  state_name_cur;             //每字符8位宽,这里最多5个字符40位宽(THREE)
reg [39:0]  state_name_next;            //每字符8位宽,这里最多5个字符40位宽(THREE)always @(*) begincase(FSM_Mealy_3_inst.cur_state)    //这里写你例化的状态机模块里你想查看的参数4'b0001:      state_name_cur = "IDLE";    //编码对应你的状态机的编码4'b0010:      state_name_cur = "ONE";4'b0100:     state_name_cur = "TWO";4'b1000:     state_name_cur = "THREE"; default:       state_name_cur = "IDLE";endcase
endalways @(*) begincase(FSM_Mealy_3_inst.next_state)4'b0001:     state_name_next = "IDLE";4'b0010:       state_name_next = "ONE";4'b0100:    state_name_next = "TWO";4'b1000:        state_name_next = "THREE"; default:      state_name_next = "IDLE";endcase
end

7、参考资料

达芬奇之 FPGA 开发指南--正点原子

FPGA Verilog 开发实战指南--基于 Intel Cyclone IV--野火电子

FPGA三段式状态机的思维陷阱

FPGA状态机(一段式、二段式、三段式)、摩尔型(Moore)和米勒型(Mealy)相关推荐

  1. (49)FPGA状态机描述(二段式)

    (49)FPGA状态机描述(二段式) 1 文章目录 1)文章目录 2)FPGA入门与提升课程介绍 3)FPGA简介 4)FPGA状态机描述(二段式) 5)技术交流 6)参考资料 2 FPGA入门与提升 ...

  2. html三段式布局,运用三段式框架,教你轻松应对10分钟即兴演讲!

    即兴演讲不宜篇幅过长,一般人在很短的时间也不可能构思出内容复杂的宏篇巨论.因此,即兴演讲宜简短精炼,结构简单,一般为单线条式的结构,或横向的以事物的几个方面为纲,或纵向的以事物的发展过程为目,按逻辑思 ...

  3. 程序员文档写作能力(二)-大三段式构架你的文档

    内容概要 我们今天先从最最常用的平日工作中经常要用到的你的主管.老板让你留文档这种常用技术文档(含设计文档)入手来入门吧. 第一大段-虎头 哎,这个名字起的好.今年是虎年,祝愿各位虎虎生威. 为什么叫 ...

  4. 彻底搞懂状态机(一段式、两段式、三段式)

    转自:https://blog.csdn.net/wordwarwordwar/article/details/78509445 实例:FSM实现10010串的检测 状态转移图:初始状态S0,a = ...

  5. DSP嵌入式C语言状态机,三段式状态机描述及模版

    三段式状态机描述及模版 [复制链接] 本帖最后由 Aguilera 于 2018-11-10 20:23 编辑 时序电路的状态是一个状态变量集合,这些状态变量在任意时刻的值都包含了为确定电路的未来行为 ...

  6. 以爱情规律为例,浅谈三段式描述状态机

    目录 基础概念介绍 状态机的要素 FSM的分类 Verilog描述状态机的方法 一段式描述 两段式描述 三段式描述 结语 基础概念介绍 今夜闲来无事,忽然想到最近准备复习一下Verilog语法,所以就 ...

  7. Verilog有限状态机三段式描述方法【原创*改进】

    1.好的状态机标准 好的状态机的标准很多,最重要的几个方面如下: 第一,状态机要安全,是指FSM不会进入死循环,特别是不会进入非预知的状态,而且由于某些扰动进入非设计状态,也能很快的恢复到正常的状态循 ...

  8. 分布式事务之——两段式、三段式

    一.两段式 1.请求阶段(commit-request phase,或称表决阶段,voting phase) 事务询问.协调者向所有参与者发送事务内容,询问是否可以进行事务提交操作,然后就开始等待参与 ...

  9. 2022-05-18——视频拍摄小记 ——三段式思维让短片更具有氛围感

    分镜三段式拍摄,三段式思维让短片更具有氛围感 每次拍摄一个动作的时候,把它拆分成三个镜头 远景 中景 特写 特写镜头也可以换成风景,或者静物 顺序可以自己来考量,一般按照这个顺序拍摄,后期可以调整顺序 ...

最新文章

  1. AS 4.7安装yum
  2. python3 判断ip类型 ipv4 ipv6
  3. Sublime text 3 汉化教程
  4. JAVA 基本运算符(摘)
  5. 数据库表DML不了 可能是被锁了
  6. 工业领域产品经理的尴尬处境
  7. cocoapods 详尽使用
  8. cuda nvidia安装程序失败_Ubuntu16.04安装nvidia-docker
  9. 网络基石 —— ADSL
  10. springboot项目license_license · 开源的SpringBoot前后端分离项目/framework - Gitee.com
  11. dpl直播源_上千个高清无码直播源,爽歪歪!
  12. C语言练手题(52个小练习)
  13. 推荐一个好看且实用的火狐浏览器新标签页插件【火狐浏览器新标签页自定义美化】
  14. vue项目将localhost改成自己的ip访问
  15. C语言练习题:统计 N 个整数中,大于零或小于零的整数个数(数组)
  16. 推荐一款MD5解密在线网站
  17. 南京湖南路学计算机哪家好,在南京只知道夫子庙湖南路你就OUT了!真正的美食街在此!!...
  18. Hantek6022BE 虚拟示波器 使用心得
  19. php中``反引号的作用
  20. 【嵌入式 C】嵌入式开发神器—SourceInsight的使用教程

热门文章

  1. adb devices显示device unauthorized
  2. python怎么定义整数数组_python中输入整数数组长度python基础语法
  3. Excel:自动隐藏错误结果的显示信息(转)
  4. 关于一场“信任危机”
  5. 前端技术学习第四讲:JavaScript中DOM和BOM
  6. 断舍离:人生从此提效30%
  7. srm采购管理系统中的协同管理
  8. pygame落球游戏优化
  9. 一个项目玩转 Android 自定义 Drawable。
  10. 鸿蒙来啦!有物混成,先天地生