FPGA开发学习篇-Moore状态机和Mealy状态机

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. //==================================================================
  2. //-- 1段式状态机(Moore)
  3. //==================================================================
  4. //------------<模块及端口声明>----------------------------------------
  5. module FSM_Moore_1(
  6. input sys_clk , //输入系统时钟、50M
  7. input sys_rst_n , //复位信号、低电平有效
  8. input money , //投币输入,高电平有效
  9. output reg cola //可乐输出,高电平有效
  10. );
  11. //------------<状态机参数定义>------------------------------------------
  12. //这里使用独热码编码节省组合逻辑资源
  13. //此外还可以使用格雷码 、二进制码
  14. localparam IDLE = 4 'b0001,
  15. ONE = 4 'b0010,
  16. TWO = 4 'b0100,
  17. THREE = 4 'b1000;
  18. //------------<reg定义>-------------------------------------------------
  19. reg [ 3: 0] state; //定义状态寄存器
  20. //-----------------------------------------------------------------------
  21. //-- 1段式状态机(Moore)
  22. //-----------------------------------------------------------------------
  23. always@(posedge sys_clk or negedge sys_rst_n)begin
  24. if(!sys_rst_n)begin
  25. cola <= 1 'b0; //复位初始状态
  26. state <= IDLE;
  27. end
  28. else
  29. case(state) //根据当前状态、输入进行状态转换判断
  30. //根据当前状态进行输出
  31. IDLE:begin
  32. cola <= 1 'b0; //初始状态无可乐输出
  33. if(money)
  34. state <= ONE; //投币1元则状态跳转到ONE
  35. else
  36. state <= IDLE; //否则保持原有状态
  37. end
  38. ONE:begin
  39. cola <= 1 'b0; //该状态只有1元,无可乐输出
  40. if(money)
  41. state <= TWO; //投币1元则状态跳转到TWO
  42. else
  43. state <= ONE; //否则保持原有状态
  44. end
  45. TWO:begin
  46. cola <= 1 'b0; //该状态只有2元,无可乐输出
  47. if(money)
  48. state <= THREE; //投币1元则状态跳转到THREE
  49. else
  50. state <= TWO; //否则保持原有状态
  51. end
  52. THREE:begin
  53. cola <= 1 'b1; //该状态有3元,有可乐输出
  54. //但是时序逻辑输出会落后一个时钟周期
  55. if(money)
  56. state <= ONE; //投币1元则状态跳转到ONE
  57. else
  58. state <= IDLE; //否则状态跳转到IDLE
  59. end
  60. default:begin //默认状态同IDLE
  61. cola <= 1 'b0;
  62. if(money)
  63. state <= ONE;
  64. else
  65. state <= IDLE;
  66. end
  67. endcase
  68. end
  69. endmodule

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

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

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

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

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


 
 
  1. //-------------------------------------------------------------------
  2. //-- 1段式状态机(Moore)
  3. //-------------------------------------------------------------------
  4. `timescale 1ns/ 1ns
  5. //------------<模块及端口声明>----------------------------------------
  6. module tb_FSM_Moore_1();
  7. reg sys_clk;
  8. reg sys_rst_n;
  9. reg money;
  10. wire cola;
  11. //------------<例化被测试模块>----------------------------------------
  12. FSM_Moore_1 FSM_Moore_1_inst(
  13. . sys_clk (sys_clk) ,
  14. . sys_rst_n (sys_rst_n) ,
  15. . money (money) ,
  16. . cola (cola)
  17. );
  18. //------------<设置初始测试条件>----------------------------------------
  19. initial begin
  20. sys_clk = 1 'b0; //初始时钟为0
  21. sys_rst_n <= 1 'b0; //初始复位
  22. money <= 1 'b0; //投币初始化为0
  23. # 5 //5个时钟周期后
  24. sys_rst_n <= 1 'b1; //拉高复位,系统进入工作状态
  25. # 25 //25个时钟周期后
  26. money <= 1 'b1; //拉高投币信号
  27. # 40 //40个时钟周期后
  28. money <= 1 'b0; //拉低投币信号
  29. # 20 //25个时钟周期后
  30. money <= 1 'b1; //拉高投币信号
  31. # 80 //25个时钟周期后
  32. money <= 1 'b0; //拉低投币信号
  33. end
  34. //------------<设置时钟>----------------------------------------------
  35. always # 10 sys_clk = ~sys_clk; //系统时钟周期20ns
  36. //------------<状态机名称查看器>----------------------------------------
  37. reg [ 39: 0] state_name; //每字符8位宽,这里最多5个字符40位宽
  38. always @(*) begin
  39. case(FSM_Moore_1_inst.state)
  40. 4 'b0001: state_name = "IDLE";
  41. 4 'b0010: state_name = "ONE";
  42. 4 'b0100: state_name = "TWO";
  43. 4 'b1000: state_name = "THREE";
  44. default: state_name = "IDLE";
  45. endcase
  46. end
  47. endmodule

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

 可以看到:

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

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

Verillog代码如下:


 
 
  1. //==================================================================
  2. //-- 1段式状态机(Mealy)
  3. //==================================================================
  4. //------------<模块及端口声明>----------------------------------------
  5. module FSM_Mealy_1(
  6. input sys_clk , //输入系统时钟、50M
  7. input sys_rst_n , //复位信号、低电平有效
  8. input money , //投币输入,高电平有效
  9. output reg cola //可乐输出,高电平有效
  10. );
  11. //------------<状态机参数定义>------------------------------------------
  12. //这里使用独热码编码节省组合逻辑资源
  13. //此外还可以使用格雷码 、二进制码
  14. localparam IDLE = 3 'b001,
  15. ONE = 3 'b010,
  16. TWO = 3 'b100;
  17. //------------<reg定义>------------------------------------------------
  18. reg [ 2: 0] state; //定义状态寄存器
  19. //-----------------------------------------------------------------------
  20. //-- 1段式状态机(Mealy)
  21. //-----------------------------------------------------------------------
  22. always@(posedge sys_clk or negedge sys_rst_n)begin
  23. if(!sys_rst_n)begin
  24. cola <= 1 'b0; //复位初始状态
  25. state <= IDLE; //复位初始状态
  26. end
  27. else
  28. case(state) //根据当前状态、输入进行状态转换判断
  29. //根据当前状态、输入进行输出
  30. IDLE:begin
  31. if(money)begin //投入1元
  32. state <= ONE; //状态跳转到ONE
  33. cola <= 1 'b0; //一共1元 ,没有可乐输出
  34. end
  35. else begin //没有投入
  36. state <= IDLE; //保持原有状态
  37. cola <= 1 'b0; //一共0元 ,没有可乐输出
  38. end
  39. end
  40. ONE:begin
  41. if(money)begin //投入1元
  42. state <= TWO; //状态跳转到TWO
  43. cola <= 1 'b0; //一共2元 ,没有可乐输出
  44. end
  45. else begin //没有投入
  46. state <= ONE; //保持原有状态
  47. cola <= 1 'b0; //一共1元 ,没有可乐输出
  48. end
  49. end
  50. TWO:begin
  51. if(money)begin //投入1元
  52. state <= IDLE; //状态跳转到IDLE(一共3元了,需要输出可乐)
  53. cola <= 1 'b1; //一共3元 ,输出可乐
  54. end
  55. else begin //没有投入
  56. state <= TWO; //保持原有状态
  57. cola <= 1 'b0; //一共2元 ,没有可乐输出
  58. end
  59. end
  60. default:begin //默认状态同初始状态
  61. if(money)begin
  62. state <= ONE;
  63. cola <= 1 'b0;
  64. end
  65. else begin
  66. state <= IDLE;
  67. cola <= 1 'b0;
  68. end
  69. end
  70. endcase
  71. end
  72. endmodule

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

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

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


 
 
  1. //-------------------------------------------------------------------
  2. //-- 1段式状态机(Mealy)
  3. //-------------------------------------------------------------------
  4. `timescale 1ns/ 1ns
  5. //------------<模块及端口声明>----------------------------------------
  6. module tb_FSM_Mealy_1();
  7. reg sys_clk;
  8. reg sys_rst_n;
  9. reg money;
  10. wire cola;
  11. //------------<例化被测试模块>----------------------------------------
  12. FSM_Mealy_1 FSM_Mealy_1_inst(
  13. . sys_clk (sys_clk) ,
  14. . sys_rst_n (sys_rst_n) ,
  15. . money (money) ,
  16. . cola (cola)
  17. );
  18. //------------<设置初始测试条件>----------------------------------------
  19. initial begin
  20. sys_clk = 1 'b0; //初始时钟为0
  21. sys_rst_n <= 1 'b0; //初始复位
  22. money <= 1 'b0; //投币初始化为0
  23. # 5 //5个时钟周期后
  24. sys_rst_n <= 1 'b1; //拉高复位,系统进入工作状态
  25. # 25 //25个时钟周期后
  26. money <= 1 'b1; //拉高投币信号
  27. # 40 //40个时钟周期后
  28. money <= 1 'b0; //拉低投币信号
  29. # 20 //25个时钟周期后
  30. money <= 1 'b1; //拉高投币信号
  31. # 80 //25个时钟周期后
  32. money <= 1 'b0; //拉低投币信号
  33. end
  34. //------------<设置时钟>----------------------------------------------
  35. always # 10 sys_clk = ~sys_clk; //系统时钟周期20ns
  36. //------------<状态机名称查看器>----------------------------------------
  37. reg [ 31: 0] state_name; //每字符8位宽,这里最多4个字符32位宽
  38. always @(*) begin
  39. case(FSM_Mealy_1_inst.state)
  40. 3 'b001: state_name = "IDLE";
  41. 3 'b010: state_name = "ONE";
  42. 3 'b100: state_name = "TWO";
  43. default: state_name = "IDLE";
  44. endcase
  45. end
  46. endmodule

 使用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代码如下:


 
 
  1. //==================================================================
  2. //-- 2段式状态机(Moore)
  3. //==================================================================
  4. //------------<模块及端口声明>----------------------------------------
  5. module FSM_Moore_2(
  6. input sys_clk , //输入系统时钟、50M
  7. input sys_rst_n , //复位信号、低电平有效
  8. input money , //投币输入,高电平有效
  9. output reg cola //可乐输出,高电平有效
  10. );
  11. //------------<状态机参数定义>------------------------------------------
  12. localparam IDLE = 4 'b0001,
  13. ONE = 4 'b0010,
  14. TWO = 4 'b0100,
  15. THREE = 4 'b1000;
  16. //------------<reg定义>-------------------------------------------------
  17. reg [ 3: 0] cur_state; //定义现态寄存器
  18. reg [ 3: 0] next_state; //定义次态寄存器
  19. //-----------------------------------------------------------------------
  20. //--状态机第一段:同步时序描述状态转移
  21. //-----------------------------------------------------------------------
  22. always@(posedge sys_clk or negedge sys_rst_n)begin
  23. if(!sys_rst_n)
  24. cur_state <= IDLE; //复位初始状态
  25. else
  26. cur_state <= next_state; //次态转移到现态
  27. end
  28. //-----------------------------------------------------------------------
  29. //--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
  30. //-----------------------------------------------------------------------
  31. always@(*)begin //组合逻辑
  32. case(cur_state) //根据当前状态、输入进行状态转换判断
  33. //根据当前状态进行输出
  34. IDLE:begin
  35. cola = 1 'b0; //初始状态无可乐输出
  36. if(money) //投币1元
  37. next_state = ONE; //次态(下个状态)为ONE
  38. else
  39. next_state = IDLE; //次态为现态
  40. end
  41. ONE:begin
  42. cola = 1 'b0; //无可乐输出
  43. if(money) //投币1元
  44. next_state = TWO; //次态(下个状态)为TWO
  45. else
  46. next_state = ONE; //次态为现态
  47. end
  48. TWO:begin
  49. cola = 1 'b0; //无可乐输出
  50. if(money) //投币1元
  51. next_state = THREE; //次态(下个状态)为THREE
  52. else
  53. next_state = TWO; //次态为现态
  54. end
  55. THREE:begin
  56. cola = 1 'b1; //输出可乐输出
  57. if(money) //投币1元
  58. next_state = ONE; //次态(下个状态)为ONE
  59. else
  60. next_state = IDLE; //次态为IDLE
  61. end
  62. default:begin //默认状态同IDLE
  63. cola = 1 'b0;
  64. if(money)
  65. next_state = ONE;
  66. else
  67. next_state = IDLE;
  68. end
  69. endcase
  70. end
  71. endmodule

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

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

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


 
 
  1. //------------------------------------------------
  2. //-- 2段式状态机(Moore)
  3. //------------------------------------------------
  4. `timescale 1ns/ 1ns
  5. //------------<模块及端口声明>----------------------------------------
  6. module tb_FSM_Moore_2();
  7. reg sys_clk;
  8. reg sys_rst_n;
  9. reg money;
  10. wire cola;
  11. //------------<例化被测试模块>----------------------------------------
  12. FSM_Moore_2 FSM_Moore_2_inst(
  13. . sys_clk (sys_clk) ,
  14. . sys_rst_n (sys_rst_n) ,
  15. . money (money) ,
  16. . cola (cola)
  17. );
  18. //------------<设置初始测试条件>----------------------------------------
  19. initial begin
  20. sys_clk = 1 'b0; //初始时钟为0
  21. sys_rst_n <= 1 'b0; //初始复位
  22. money <= 1 'b0; //投币初始化为0
  23. # 5 //5个时钟周期后
  24. sys_rst_n <= 1 'b1; //拉高复位,系统进入工作状态
  25. # 25 //25个时钟周期后
  26. money <= 1 'b1; //拉高投币信号
  27. # 40 //40个时钟周期后
  28. money <= 1 'b0; //拉低投币信号
  29. # 20 //25个时钟周期后
  30. money <= 1 'b1; //拉高投币信号
  31. # 80 //25个时钟周期后
  32. money <= 1 'b0; //拉低投币信号
  33. end
  34. //------------<设置时钟>----------------------------------------------
  35. always # 10 sys_clk = ~sys_clk; //系统时钟周期20ns
  36. //------------------------------------------------
  37. //-- 状态机名称查看器
  38. //------------------------------------------------
  39. reg [ 39: 0] state_name_cur; //每字符8位宽,这里最多5个字符40位宽
  40. reg [ 39: 0] state_name_next; //每字符8位宽,这里最多5个字符40位宽
  41. always @(*) begin
  42. case(FSM_Moore_2_inst.cur_state)
  43. 4 'b0001: state_name_cur = "IDLE";
  44. 4 'b0010: state_name_cur = "ONE";
  45. 4 'b0100: state_name_cur = "TWO";
  46. 4 'b1000: state_name_cur = "THREE";
  47. default: state_name_cur = "IDLE";
  48. endcase
  49. end
  50. always @(*) begin
  51. case(FSM_Moore_2_inst.next_state)
  52. 4 'b0001: state_name_next = "IDLE";
  53. 4 'b0010: state_name_next = "ONE";
  54. 4 'b0100: state_name_next = "TWO";
  55. 4 'b1000: state_name_next = "THREE";
  56. default: state_name_next = "IDLE";
  57. endcase
  58. end
  59. endmodule

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

从仿真结果可以看到:

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

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

Verillog代码如下:


 
 
  1. //------------------------------------------------
  2. //-- 2段式状态机(Mealy )
  3. //------------------------------------------------
  4. //------------<模块及端口声明>----------------------------------------
  5. module FSM_Mealy_2(
  6. input sys_clk , //输入系统时钟、50M
  7. input sys_rst_n , //复位信号、低电平有效
  8. input money , //投币输入,高电平有效
  9. output reg cola //可乐输出,高电平有效
  10. );
  11. //------------<状态机参数定义>------------------------------------------
  12. localparam IDLE = 3'b001,
  13. ONE = 3'b010,
  14. TWO = 3'b100;
  15. //------------<reg定义>------------------------------------------------
  16. reg [ 2: 0] cur_state; //定义现态
  17. reg [ 2: 0] next_state; //定义次态
  18. //-----------------------------------------------------------------------
  19. //--状态机第一段:同步时序描述状态转移
  20. //-----------------------------------------------------------------------
  21. always@(posedge sys_clk or negedge sys_rst_n)begin
  22. if(!sys_rst_n)
  23. cur_state <= IDLE; //复位初始状态
  24. else
  25. cur_state <= next_state; //次态转移到现态
  26. end
  27. //-----------------------------------------------------------------------
  28. //--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
  29. //-----------------------------------------------------------------------
  30. always@(*)begin
  31. case(cur_state) //组合逻辑
  32. IDLE:begin //根据当前状态、输入进行状态转换判断
  33. //根据当前状态、输入进行输出
  34. if(money)begin //当前输入为1
  35. next_state = ONE; //次态为ONE
  36. cola = 1'b0; //一共1元 ,没有可乐输出
  37. end
  38. else begin //当前输入为0
  39. next_state = IDLE; //次态为IDLE
  40. cola = 1'b0; //一共0元 ,没有可乐输出
  41. end
  42. end
  43. ONE:begin
  44. if(money)begin //当前输入为1
  45. next_state = TWO; //次态为TWO
  46. cola = 1'b0; //一共2元 ,没有可乐输出
  47. end
  48. else begin //当前输入为0
  49. next_state = ONE; //次态为ONE
  50. cola = 1'b0; //一共1元 ,没有可乐输出
  51. end
  52. end
  53. TWO:begin
  54. if(money)begin //当前输入为1
  55. next_state = IDLE; //次态为IDLE
  56. cola = 1'b1; //一共3元 ,输出可乐
  57. end
  58. else begin //当前输入为0
  59. next_state = TWO; //次态为TWO
  60. cola = 1'b0; //一共2元 ,没有可乐输出
  61. end
  62. end
  63. default:begin //默认状态同初始状态
  64. if(money)begin
  65. next_state = ONE;
  66. cola = 1'b0;
  67. end
  68. else begin
  69. next_state = IDLE;
  70. cola = 1'b0;
  71. end
  72. end
  73. endcase
  74. end
  75. endmodule

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

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

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


 
 
  1. //------------------------------------------------
  2. //-- 2段式状态机(Mealy)
  3. //------------------------------------------------
  4. `timescale 1ns/ 1ns
  5. //------------<模块及端口声明>----------------------------------------
  6. module tb_FSM_Mealy_2();
  7. reg sys_clk;
  8. reg sys_rst_n;
  9. reg money;
  10. wire cola;
  11. //------------<例化被测试模块>----------------------------------------
  12. FSM_Mealy_2 FSM_Mealy_2_inst(
  13. . sys_clk (sys_clk),
  14. . sys_rst_n (sys_rst_n),
  15. . money (money),
  16. . cola (cola)
  17. );
  18. //------------<设置初始测试条件>----------------------------------------
  19. initial begin
  20. sys_clk = 1 'b0; //初始时钟为0
  21. sys_rst_n <= 1 'b0; //初始复位
  22. money <= 1 'b0; //投币初始化为0
  23. # 5 //5个时钟周期后
  24. sys_rst_n <= 1 'b1; //拉高复位,系统进入工作状态
  25. # 25 //25个时钟周期后
  26. money <= 1 'b1; //拉高投币信号
  27. # 40 //40个时钟周期后
  28. money <= 1 'b0; //拉低投币信号
  29. # 20 //25个时钟周期后
  30. money <= 1 'b1; //拉高投币信号
  31. # 80 //25个时钟周期后
  32. money <= 1 'b0; //拉低投币信号
  33. end
  34. //------------<设置时钟>----------------------------------------------
  35. always # 10 sys_clk = ~sys_clk; //系统时钟周期20ns
  36. //------------------------------------------------
  37. //-- 状态机名称查看器
  38. //------------------------------------------------
  39. //1字符8位宽
  40. reg [ 31: 0] state_name_cur; //每字符8位宽,这里最多4个字符32位宽
  41. reg [ 31: 0] state_name_next; //每字符8位宽,这里最多4个字符32位宽
  42. always @(*) begin
  43. case(FSM_Mealy_2_inst.cur_state)
  44. 3 'b001: state_name_cur = "IDLE";
  45. 3 'b010: state_name_cur = "ONE";
  46. 3 'b100: state_name_cur = "TWO";
  47. default: state_name_cur = "IDLE";
  48. endcase
  49. end
  50. always @(*) begin
  51. case(FSM_Mealy_2_inst.next_state)
  52. 3 'b001: state_name_next = "IDLE";
  53. 3 'b010: state_name_next = "ONE";
  54. 3 'b100: state_name_next = "TWO";
  55. default: state_name_next = "IDLE";
  56. endcase
  57. end
  58. endmodule

使用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代码如下:


 
 
  1. //==================================================================
  2. //-- 3段式状态机(Moore)
  3. //==================================================================
  4. //------------<模块及端口声明>----------------------------------------
  5. module FSM_Moore_3(
  6. input sys_clk , //输入系统时钟、50M
  7. input sys_rst_n , //复位信号、低电平有效
  8. input money , //投币输入,高电平有效
  9. output reg cola //可乐输出,高电平有效
  10. );
  11. //------------<状态机参数定义>------------------------------------------
  12. localparam IDLE = 4'b0001,
  13. ONE = 4'b0010,
  14. TWO = 4'b0100,
  15. THREE = 4'b1000;
  16. //------------<reg定义>-------------------------------------------------
  17. reg [ 3: 0] cur_state; //定义现态寄存器
  18. reg [ 3: 0] next_state; //定义次态寄存器
  19. //-----------------------------------------------------------------------
  20. //--状态机第一段:同步时序描述状态转移
  21. //-----------------------------------------------------------------------
  22. always@(posedge sys_clk or negedge sys_rst_n)begin
  23. if(!sys_rst_n)
  24. cur_state <= IDLE; //复位初始状态
  25. else
  26. cur_state <= next_state; //次态转移到现态
  27. end
  28. //-----------------------------------------------------------------------
  29. //--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
  30. //-----------------------------------------------------------------------
  31. always@(*)begin
  32. case(cur_state) //组合逻辑
  33. //根据当前状态、输入进行状态转换判断
  34. IDLE:begin
  35. if(money)
  36. next_state = ONE; //投币1元,则状态转移到ONE
  37. else
  38. next_state = IDLE; //没有投币,则状态保持
  39. end
  40. ONE:begin
  41. if(money)
  42. next_state = TWO; //投币1元,则状态转移到TWO
  43. else
  44. next_state = ONE; //没有投币,则状态保持
  45. end
  46. TWO:begin
  47. if(money)
  48. next_state = THREE; //投币1元,则状态转移到THREE
  49. else
  50. next_state = TWO; //没有投币,则状态保持
  51. end
  52. THREE:begin
  53. if(money)
  54. next_state = ONE; //投币1元,则状态转移到ONE
  55. else
  56. next_state = IDLE; //没有投币,则状态保持
  57. end
  58. default:begin //默认状态同IDLE
  59. if(money)
  60. next_state = ONE;
  61. else
  62. next_state = IDLE;
  63. end
  64. endcase
  65. end
  66. //-----------------------------------------------------------------------
  67. //--状态机第三段:时序逻辑描述输出
  68. //-----------------------------------------------------------------------
  69. always@(posedge sys_clk or negedge sys_rst_n)begin
  70. if(!sys_rst_n)
  71. cola <= 1'b0; //复位、初始状态
  72. else
  73. case(cur_state) //根据当前状态进行输出
  74. IDLE: cola <= 1'b0; //无可乐输出
  75. ONE: cola <= 1'b0; //无可乐输出
  76. TWO: cola <= 1'b0; //无可乐输出
  77. THREE: cola <= 1'b1; //输出可乐
  78. default:cola <= 1'b0; //默认无可乐输出
  79. endcase
  80. end
  81. endmodule

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

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

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


 
 
  1. //------------------------------------------------
  2. //-- 3段式状态机(Moore)
  3. //------------------------------------------------
  4. `timescale 1ns/ 1ns
  5. //------------<模块及端口声明>----------------------------------------
  6. module tb_FSM_Moore_3();
  7. reg sys_clk;
  8. reg sys_rst_n;
  9. reg money;
  10. wire cola;
  11. //------------<例化被测试模块>----------------------------------------
  12. FSM_Moore_3 FSM_Moore_3_inst(
  13. . sys_clk (sys_clk),
  14. . sys_rst_n (sys_rst_n),
  15. . money (money),
  16. . cola (cola)
  17. );
  18. //------------<设置初始测试条件>----------------------------------------
  19. initial begin
  20. sys_clk = 1 'b0; //初始时钟为0
  21. sys_rst_n <= 1 'b0; //初始复位
  22. money <= 1 'b0; //投币初始化为0
  23. # 5 //5个时钟周期后
  24. sys_rst_n <= 1 'b1; //拉高复位,系统进入工作状态
  25. # 25 //25个时钟周期后
  26. money <= 1 'b1; //拉高投币信号
  27. # 40 //40个时钟周期后
  28. money <= 1 'b0; //拉低投币信号
  29. # 20 //25个时钟周期后
  30. money <= 1 'b1; //拉高投币信号
  31. # 80 //25个时钟周期后
  32. money <= 1 'b0; //拉低投币信号
  33. end
  34. //------------<设置时钟>----------------------------------------------
  35. always # 10 sys_clk = ~sys_clk; //系统时钟周期20ns
  36. //------------------------------------------------
  37. //-- 状态机名称查看器
  38. //------------------------------------------------
  39. reg [ 39: 0] state_name_cur; //每字符8位宽,这里最多5个字符40位宽
  40. reg [ 39: 0] state_name_next; //每字符8位宽,这里最多5个字符40位宽
  41. always @(*) begin
  42. case(FSM_Moore_3_inst.cur_state)
  43. 4 'b0001: state_name_cur = "IDLE";
  44. 4 'b0010: state_name_cur = "ONE";
  45. 4 'b0100: state_name_cur = "TWO";
  46. 4 'b1000: state_name_cur = "THREE";
  47. default: state_name_cur = "IDLE";
  48. endcase
  49. end
  50. always @(*) begin
  51. case(FSM_Moore_3_inst.next_state)
  52. 4 'b0001: state_name_next = "IDLE";
  53. 4 'b0010: state_name_next = "ONE";
  54. 4 'b0100: state_name_next = "TWO";
  55. 4 'b1000: state_name_next = "THREE";
  56. default: state_name_next = "IDLE";
  57. endcase
  58. end
  59. endmodule

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

从仿真结果可以看到:

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

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

Verilog代码如下:


 
 
  1. //==================================================================
  2. //-- 3段式状态机(Mealy)
  3. //==================================================================
  4. //------------<模块及端口声明>----------------------------------------
  5. module FSM_Mealy_3(
  6. input sys_clk , //输入系统时钟、50M
  7. input sys_rst_n , //复位信号、低电平有效
  8. input money , //投币输入,高电平有效
  9. output reg cola //可乐输出,高电平有效
  10. );
  11. //------------<状态机参数定义>------------------------------------------
  12. localparam IDLE = 3'b0001,
  13. ONE = 3'b0010,
  14. TWO = 3'b0100;
  15. //------------<reg定义>-------------------------------------------------
  16. reg [ 3: 0] cur_state; //定义现态寄存器
  17. reg [ 3: 0] next_state; //定义次态寄存器
  18. //-----------------------------------------------------------------------
  19. //--状态机第一段:同步时序描述状态转移
  20. //-----------------------------------------------------------------------
  21. always@(posedge sys_clk or negedge sys_rst_n)begin
  22. if(!sys_rst_n)
  23. cur_state <= IDLE; //复位初始状态
  24. else
  25. cur_state <= next_state; //次态转移到现态
  26. end
  27. //-----------------------------------------------------------------------
  28. //--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
  29. //-----------------------------------------------------------------------
  30. always@(*)begin
  31. case(cur_state) //组合逻辑
  32. //根据当前状态、输入进行状态转换判断
  33. IDLE:begin
  34. if(money)
  35. next_state = ONE; //投币1元,则状态转移到ONE
  36. else
  37. next_state = IDLE; //没有投币,则状态保持
  38. end
  39. ONE:begin
  40. if(money)
  41. next_state = TWO; //投币1元,则状态转移到TWO
  42. else
  43. next_state = ONE; //没有投币,则状态保持
  44. end
  45. TWO:begin
  46. if(money)
  47. next_state = IDLE; //投币1元,则状态转移到IDLE
  48. else
  49. next_state = TWO; //没有投币,则状态保持
  50. end
  51. default:begin //默认状态同IDLE
  52. if(money)
  53. next_state = ONE;
  54. else
  55. next_state = IDLE;
  56. end
  57. endcase
  58. end
  59. //-----------------------------------------------------------------------
  60. //--状态机第三段:时序逻辑描述输出
  61. //-----------------------------------------------------------------------
  62. always@(posedge sys_clk or negedge sys_rst_n)begin
  63. if(!sys_rst_n)
  64. cola <= 1'b0; //复位、初始状态
  65. else
  66. case(cur_state) //根据当前状态进行输出
  67. IDLE: cola <= 1'b0; //无可乐输出(因为输入不管是0、1都是输出0,所以省略写法)
  68. ONE: cola <= 1'b0; //无可乐输出(因为输入不管是0、1都是输出0,所以省略写法)
  69. TWO:begin
  70. if(money)
  71. cola <= 1'b1; //如果输入1,则输出可乐
  72. else
  73. cola <= 1'b0; //如果输入0,则无可乐输出
  74. end
  75. default:cola <= 1'b0; //默认无可乐输出
  76. endcase
  77. end
  78. endmodule

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

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

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


 
 
  1. //------------------------------------------------
  2. //-- 3段式状态机(Mealy)
  3. //------------------------------------------------
  4. `timescale 1ns/ 1ns
  5. //------------<模块及端口声明>----------------------------------------
  6. module tb_FSM_Mealy_3();
  7. reg sys_clk;
  8. reg sys_rst_n;
  9. reg money;
  10. wire cola;
  11. //------------<例化被测试模块>----------------------------------------
  12. FSM_Mealy_3 FSM_Mealy_3_inst(
  13. . sys_clk (sys_clk),
  14. . sys_rst_n (sys_rst_n),
  15. . money (money),
  16. . cola (cola)
  17. );
  18. //------------<设置初始测试条件>----------------------------------------
  19. initial begin
  20. sys_clk = 1 'b0; //初始时钟为0
  21. sys_rst_n <= 1 'b0; //初始复位
  22. money <= 1 'b0; //投币初始化为0
  23. # 5 //5个时钟周期后
  24. sys_rst_n <= 1 'b1; //拉高复位,系统进入工作状态
  25. # 25 //25个时钟周期后
  26. money <= 1 'b1; //拉高投币信号
  27. # 40 //40个时钟周期后
  28. money <= 1 'b0; //拉低投币信号
  29. # 20 //25个时钟周期后
  30. money <= 1 'b1; //拉高投币信号
  31. # 80 //25个时钟周期后
  32. money <= 1 'b0; //拉低投币信号
  33. end
  34. //------------<设置时钟>----------------------------------------------
  35. always # 10 sys_clk = ~sys_clk; //系统时钟周期20ns
  36. //------------------------------------------------
  37. //-- 状态机名称查看器
  38. //------------------------------------------------
  39. reg [ 39: 0] state_name_cur; //每字符8位宽,这里最多5个字符40位宽
  40. reg [ 39: 0] state_name_next; //每字符8位宽,这里最多5个字符40位宽
  41. always @(*) begin
  42. case(FSM_Mealy_3_inst.cur_state)
  43. 4 'b0001: state_name_cur = "IDLE";
  44. 4 'b0010: state_name_cur = "ONE";
  45. 4 'b0100: state_name_cur = "TWO";
  46. 4 'b1000: state_name_cur = "THREE";
  47. default: state_name_cur = "IDLE";
  48. endcase
  49. end
  50. always @(*) begin
  51. case(FSM_Mealy_3_inst.next_state)
  52. 4 'b0001: state_name_next = "IDLE";
  53. 4 'b0010: state_name_next = "ONE";
  54. 4 'b0100: state_name_next = "TWO";
  55. 4 'b1000: state_name_next = "THREE";
  56. default: state_name_next = "IDLE";
  57. endcase
  58. end
  • 7
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值