小言_互联网的博客

【状态机设计】Moore、Mealy状态机、三段式、二段式、一段式状态机书写规范

533人阅读  评论(0)

目录

状态机介绍

状态机类型

Moore 型状态机

Mealy 型状态机

状态机设计流程

自动售卖机

状态机设计:3 段式(推荐)

实例

实例

状态机修改:2 段式

实例

状态机修改:1 段式(慎用)

实例

状态机修改:Moore 型

实例

实例


状态机介绍

有限状态机(Finite-State Machine,FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。状态机不仅是一种电路的描述工具,而且也是一种思想方法,在电路设计的系统级和 RTL 级有着广泛的应用。

都说状态机是 FPGA 设计的灵魂,可见其重要之处,在 Verilog 的设计中,状态机其实可以等同于 if 语句和 case 语句,但是由于在某些情况下,状态的种类多且复杂,各种状态跳转起来非常麻烦,所以 一般利用状态机设计是一种可靠便捷的方法。

规范的状态机代码可以极大地提高设计效率, 在减少状态出错可能的同时缩短调试时间, 从而设计出稳健的系统。

在设计状态机时,最好能够满足以下要求:

  • 通用的设计方法, 针对简单或复杂的状态机设计都能满足;
  • 步骤清晰易懂, 每步只考虑一个问题;
  • 状态机代码严谨规范, 不容易出错;
  • 设计的状态机结构简单且稳定。

状态机类型

Verilog 中状态机主要用于同步时序逻辑的设计,能够在有限个状态之间按要求和规律切换时序电路的状态。状态的切换方向不但取决于各个输入值,还取决于当前所在状态。 状态机可分为两类:

  • Moore 状态机
  • Mealy 状态机

Moore 型状态机

Moore 型状态机的输出只与当前状态有关,与当前输入无关。

输出会在一个完整的时钟周期内保持稳定,即使此时输入信号有变化,输出也不会变化。输入对输出的影响要到下一个时钟周期才能反映出来。这也是 Moore 型状态机的一个重要特点:输入与输出是隔离开来的。

Mealy 型状态机

Mealy 型状态机的输出,不仅与当前状态有关,还取决于当前的输入信号。

Mealy 型状态机的输出是在输入信号变化以后立刻发生变化,且输入变化可能出现在任何状态的时钟周期内。因此,同种逻辑下,Mealy 型状态机输出对输入的响应会比 Moore 型状态机早一个时钟周期。

状态机设计流程

根据设计需求画出状态转移图,确定使用状态机类型,并标注出各种输入输出信号,更有助于编程。一般使用最多的是 Mealy 型 3 段式状态机,下面用通过设计一个自动售卖机的具体实例来说明状态机的设计过程。

自动售卖机

自动售卖机的功能描述如下:

饮料单价 2 元,该售卖机只能接受 0.5 元、1 元的硬币。考虑找零和出货。投币和出货过程都是一次一次的进行,不会出现一次性投入多币或一次性出货多瓶饮料的现象。每一轮售卖机接受投币、出货、找零完成后,才能进入到新的自动售卖状态。

该售卖机的工作状态转移图如下所示,包含了输入、输出信号状态。

其中,coin = 1 代表投入了 0.5 元硬币,coin = 2 代表投入了 1 元硬币。

状态机设计:3 段式(推荐)

状态机设计如下:

  • (0) 首先,根据状态机的个数确定状态机编码。利用编码给状态寄存器赋值,代码可读性更好。
  • (1) 状态机第一段,时序逻辑,非阻塞赋值,传递寄存器的状态。
  • (2) 状态机第二段,组合逻辑,阻塞赋值,根据当前状态和当前输入,确定下一个状态机的状态。
  • (3) 状态机第三段,时序逻辑,非阻塞赋值,因为是 Mealy 型状态机,根据当前状态和当前输入,确定输出信号。

实例


  
  1. module   vending_machine_p3  (
  2.     input           clk ,
  3.     input           rstn ,
  4.     input [ 1: 0]     coin ,     
  5. output [ 1: 0]    change , //找零
  6.     output          sell     //输出饮料
  7.     );
  8.      //machine state decode
  9.     parameter           IDLE   =  3 'd0 ;
  10.     parameter           GET05  =  3 'd1 ;
  11.     parameter           GET10  =  3 'd2 ;
  12.     parameter           GET15  =  3 'd3 ;
  13.    
  14.  
  15.     reg  [ 2: 0]          st_next ;
  16.     reg  [ 2: 0]          st_cur ;
  17.     reg  [ 1: 0]   change_r ;
  18.     reg           sell_r ;
  19.      //第一段状态机,时序逻辑 非阻塞赋值
  20.     always @(posedge clk or negedge rstn) begin
  21.          if (!rstn) begin
  22.             st_cur <=  'b0 ;
  23.         end
  24.          else begin
  25.             st_cur <= st_next ;
  26.         end
  27.     end
  28.      //第二段状态机 ,组合逻辑 阻塞赋值  
  29.     always @(*) begin
  30.         st_next = st_cur ; //如果条件选项考虑不全,可以赋初值消除latch
  31.          case(st_cur)
  32.             IDLE:
  33.                  case (coin)
  34.                      2 'b01:     st_next = GET05 ;
  35.                      2 'b10:     st_next = GET10 ;
  36.                     default:   st_next = IDLE ;
  37.                 endcase
  38.             GET05:
  39.                  case (coin)
  40.                      2 'b01:     st_next = GET10 ;
  41.                      2 'b10:     st_next = GET15 ;
  42.                     default:   st_next = GET05 ;
  43.                 endcase
  44.             GET10:
  45.                  case (coin)
  46.                      2 'b01:     st_next = GET15 ;
  47.                      2 'b10:     st_next = IDLE ;
  48.                     default:   st_next = GET10 ;
  49.                 endcase
  50.             GET15:
  51.                  case (coin)
  52.                      2 'b01, 2 'b10:
  53.                                st_next = IDLE ;
  54.                     default:   st_next = GET15 ;
  55.                 endcase
  56.             default:  st_next = IDLE ;
  57.         endcase
  58.     end
  59.      //第三段状态机,时序逻辑 非阻塞赋值
  60. always @(posedge clk or negedge rstn) begin
  61. if (!rstn) begin
  62.             change_r <=  2 'b0 ;
  63.             sell_r   <=  1 'b0 ;
  64. end
  65. else begin
  66. case (st_cur)
  67. IDLE:
  68. begin
  69. change_r <=  2 'b0 ;
  70. sell_r   <=  1 'b0 ;
  71. end
  72. GET05:
  73. begin
  74. change_r <=  2 'b0 ;
  75. sell_r   <=  1 'b0 ;
  76. end
  77. GET10:
  78. begin
  79. if (coin == 2 'd2) begin
  80. change_r <=  2 'b0 ;
  81. sell_r   <=  1 'b1 ;
  82. end
  83. else begin
  84. change_r <=  2 'b0 ;
  85. sell_r   <=  1 'b0 ;
  86. end
  87. end
  88. GET15:
  89. begin
  90. if (coin == 2 'h1) begin
  91. change_r <=  2 'b0 ;
  92. sell_r   <=  1 'b1 ;
  93. end
  94. else if (coin ==  2 'h2) begin
  95. change_r <=  2 'b1 ;
  96. sell_r   <=  1 'b1 ;
  97. end
  98. else begin
  99. change_r <=  2 'b0 ;
  100. sell_r   <=  1 'b0 ;
  101. end
  102. end
  103. default:
  104. begin
  105. change_r <=  2 'b0 ;
  106. sell_r   <=  1 'b0 ;
  107. end
  108. endcase
  109. end
  110. end
  111.     assign  sell = sell_r ;
  112.     assign  change = change_r ;
  113. endmodule

testbench 设计如下。仿真中模拟了 4 种情景,分别是:

case1 对应连续输入 4 个 5 角硬币;case2 对应 1 元 - 5 角 - 1 元的投币顺序;case3 对应 5 角 - 1 元 - 5 角的投币顺序;case4 对应连续 3 个 5 角然后一个 1 元的投币顺序。

实例


  
  1. `timescale  1ns / 1ps
  2. module test ;
  3.     reg          clk;
  4.     reg          rstn ;
  5.     reg [ 1: 0]    coin ;
  6.     wire [ 1: 0]   change ;
  7.     wire         sell ;
  8.      / /clock generating
  9.      parameter    CYCLE_200MHz  =  10 ; 
  10.     always  begin
  11.         clk  =  0 ; #(CYCLE_200MHz / 2) ;
  12.         clk  =  1 ; #(CYCLE_200MHz / 2) ;
  13.      end
  14.      / /motivation generating
  15.     reg [ 9: 0]    buy_oper ;
  16.      initial  begin
  17.         buy_oper   =  'h0 ;
  18.         coin      = 2'h0 ;
  19.         rstn       =  1 'b0 ;
  20.         #8 rstn   = 1'b1 ;
  21.         @(negedge clk) ;
  22.          / / case( 1) 0.5 - > 0.5 - > 0.5 - > 0.5
  23.         # 16 ;
  24.         buy_oper   =  10 'b00_0101_0101 ;
  25.         repeat(5) begin
  26.             @(negedge clk) ;
  27.             coin      = buy_oper[1:0] ;
  28.             buy_oper  = buy_oper >> 2 ;
  29.         end
  30.         //case(2) 1 -> 0.5 -> 1, taking change
  31.         #16 ;
  32.         buy_oper  = 10'b00_0010_0110 ;
  33.         repeat( 5begin
  34.             @(negedge clk) ;
  35.             coin       = buy_oper[ 1: 0] ;
  36.             buy_oper   = buy_oper  >>  2 ;
  37.          end
  38.          / / case( 3) 0.5 - > 1 - > 0.5
  39.         # 16 ;
  40.         buy_oper   =  10 'b00_0001_1001 ;
  41.         repeat(5) begin
  42.             @(negedge clk) ;
  43.             coin      = buy_oper[1:0] ;
  44.             buy_oper  = buy_oper >> 2 ;
  45.         end
  46.         //case(4) 0.5 -> 0.5 -> 0.5 -> 1, taking change
  47.         #16 ;
  48.         buy_oper  = 10'b00_1001_0101 ;
  49.         repeat( 5begin
  50.             @(negedge clk) ;
  51.             coin       = buy_oper[ 1: 0] ;
  52.             buy_oper   = buy_oper  >>  2 ;
  53.          end
  54.      end
  55.     / /( 1) mealy state with 3 -stage
  56.     vending_machine_p3    u_mealy_p3     (
  57.         .clk              (clk),
  58.         .rstn             (rstn),
  59.         .coin             (coin),
  60.         .change           (change),
  61.         .sell             (sell)
  62.         );
  63.     / /simulation finish
  64.    always  begin
  65.       # 100;
  66.       if ($ time  >=  10000)  $finish ;
  67.     end
  68. endmodule 

仿真结果如下:

由图可知,代表出货动作的信号 sell 都能在投币完毕后正常的拉高,而代表找零动作的信号 change 也都能根据输入的硬币场景输出正确的是否找零信号。

状态机修改:2 段式

将 3 段式状态机 2、3 段描述合并,其他部分保持不变,状态机就变成了 2 段式描述。

修改部分如下:

实例


  
  1. reg  [ 1: 0]   change_r ;
  2. reg          sell_r ;
  3. always @(*) begin 
  4.      case(st_cur)
  5.         IDLE: begin
  6.             change_r     =  2 'b0 ;
  7.             sell_r       =  1 'b0 ;
  8.              case (coin)
  9.                  2 'b01:     st_next = GET05 ;
  10.                  2 'b10:     st_next = GET10 ;
  11.                  default:   st_next = IDLE ;
  12.             endcase 
  13.          end
  14.         GET05: begin
  15.             change_r     =  2 'b0 ;
  16.             sell_r       =  1 'b0 ;
  17.              case (coin)
  18.                  2 'b01:     st_next = GET10 ;
  19.                  2 'b10:     st_next = GET15 ;
  20.                  default:   st_next = GET05 ;
  21.             endcase 
  22.          end
  23.         GET10:
  24.              case (coin)
  25.                  2 'b01:     begin
  26.                     st_next      = GET15 ;
  27.                     change_r     =  2 'b0 ;
  28.                     sell_r       =  1 'b0 ;
  29.                  end
  30.                  2 'b10:     begin
  31.                     st_next      = IDLE ;
  32.                     change_r     =  2 'b0 ;
  33.                     sell_r       =  1 'b1 ;
  34.                  end
  35.                  default:   begin
  36.                     st_next      = GET10 ;
  37.                     change_r     =  2 'b0 ;
  38.                     sell_r       =  1 'b0 ;
  39.                  end
  40.             endcase 
  41.         GET15:
  42.              case (coin)
  43.                  2 'b01: begin
  44.                     st_next     = IDLE ;
  45.                     change_r    =  2 'b0 ;
  46.                     sell_r      =  1 'b1 ;
  47.                  end
  48.                  2 'b10:     begin
  49.                     st_next     = IDLE ;
  50.                     change_r    =  2 'b1 ;
  51.                     sell_r      =  1 'b1 ;
  52.                  end
  53.                  default:   begin
  54.                     st_next     = GET15 ;
  55.                     change_r    =  2 'b0 ;
  56.                     sell_r      =  1 'b0 ;
  57.                  end
  58.             endcase
  59.          default:  begin
  60.             st_next     = IDLE ;
  61.             change_r    =  2 'b0 ;
  62.             sell_r      =  1 'b0 ;
  63.          end
  64.     endcase
  65. end

将上述修改的新模块例化到 3 段式的 testbench 中即可进行仿真,结果如下:

由图可知,出货信号 sell 和 找零信号 change 相对于 3 段式状态机输出提前了一个时钟周期,这是因为输出信号都是阻塞赋值导致的。

如图中红色圆圈部分,输出信号都出现了干扰脉冲,这是因为输入信号都是异步的,而且输出信号是组合逻辑输出,没有时钟驱动。

实际中,如果输入信号都是与时钟同步的,这种干扰脉冲是不会出现的。如果是异步输入信号,首先应当对信号进行同步。

状态机修改:1 段式(慎用)

将 3 段式状态机 1、 2、3 段描述合并,状态机就变成了 1 段式描述。

修改部分如下:

实例


  
  1.     reg  [ 1: 0]   change_r ;
  2.     reg          sell_r ;
  3.     always @(posedge clk or negedge rstn)  begin
  4.          if (!rstn)  begin
  5.             st_cur     <= 'b0 ;
  6.             change_r   <=  2'b0 ;
  7.             sell_r     <=  1'b0 ;
  8.          end
  9.         else  begin
  10.              case(st_cur)
  11.             IDLE:  begin
  12.                 change_r  <=  2'b0 ;
  13.                 sell_r    <=  1'b0 ;
  14.                  case (coin)
  15.                      2'b01:     st_cur <= GET05 ;
  16.                      2'b10:     st_cur <= GET10 ;
  17.                 endcase
  18.              end
  19.             GET05:  begin
  20.                  case (coin)
  21.                      2'b01:     st_cur <= GET10 ;
  22.                      2'b10:     st_cur <= GET15 ;
  23.                 endcase
  24.              end
  25.             GET10:
  26.                  case (coin)
  27.                      2'b01:     st_cur   <=  GET15 ;
  28.                      2'b10:      begin
  29.                         st_cur   <= IDLE ;
  30.                         sell_r   <=  1'b1 ;
  31.                      end
  32.                 endcase
  33.             GET15:
  34.                  case (coin)
  35.                      2'b01:      begin
  36.                         st_cur   <= IDLE ;
  37.                         sell_r   <=  1'b1 ;
  38.                      end
  39.                      2'b10:      begin
  40.                         st_cur   <= IDLE ;
  41.                         change_r <=  2'b1 ;
  42.                         sell_r   <=  1'b1 ;
  43.                      end
  44.                 endcase
  45.             default:   begin
  46.                   st_cur    <= IDLE ;
  47.              end
  48.             endcase // case (st_cur)
  49.          end 
  50.      end

将上述修改的新模块例化到 3 段式的 testbench 中即可进行仿真,结果如下:

由图可知,输出信号与 3 段式状态机完全一致。

1 段式状态机的缺点就是许多种逻辑糅合在一起,不易后期的维护。当状态机和输出信号较少时,可以尝试此种描述方式。

状态机修改:Moore 型

如果使用 Moore 型状态机描述售卖机的工作流程,那么还需要再增加 2 个状态编码,用以描述 Mealy 状态机输出时的输入信号和状态机状态。

3 段式 Moore 型状态机描述的自动售卖机 Verilog 代码如下:

实例


  
  1. module   vending_machine_moore    (
  2.     input           clk ,
  3.     input           rstn ,
  4.     input [ 1: 0]     coin ,     
  5.     output [ 1: 0]    change ,
  6.     output          sell    
  7.     );
  8.     parameter            IDLE   =  3 'd0 ;
  9.     parameter            GET05  =  3 'd1 ;
  10.     parameter            GET10  =  3 'd2 ;
  11.     parameter            GET15  =  3 'd3 ;
  12.     parameter            GET20  =  3 'd4 ;
  13.     parameter            GET25  =  3 'd5 ;
  14.     reg [ 2: 0]            st_next ;
  15.     reg [ 2: 0]            st_cur ;
  16.      //(1) state transfer
  17.     always @(posedge clk or negedge rstn) begin
  18.          if (!rstn) begin
  19.             st_cur      <=  'b0 ;
  20.         end
  21.          else begin
  22.             st_cur      <= st_next ;
  23.         end
  24.     end
  25.     always @(*) begin  //all case items need to be displayed completely
  26.          case(st_cur)
  27.             IDLE:
  28.                  case (coin)
  29.                      2 'b01:     st_next = GET05 ;
  30.                      2 'b10:     st_next = GET10 ;
  31.                     default:   st_next = IDLE ;
  32.                 endcase
  33.             GET05:
  34.                  case (coin)
  35.                      2 'b01:     st_next = GET10 ;
  36.                      2 'b10:     st_next = GET15 ;
  37.                     default:   st_next = GET05 ;
  38.                 endcase
  39.             GET10:
  40.                  case (coin)
  41.                      2 'b01:     st_next = GET15 ;
  42.                      2 'b10:     st_next = GET20 ;
  43.                     default:   st_next = GET10 ;
  44.                 endcase
  45.             GET15:
  46.                  case (coin)
  47.                      2 'b01:     st_next = GET20 ;
  48.                      2 'b10:     st_next = GET25 ;
  49.                     default:   st_next = GET15 ;
  50.                 endcase
  51.             GET20:         st_next = IDLE ;
  52.             GET25:         st_next = IDLE ;
  53.             default:       st_next = IDLE ;
  54.         endcase
  55.     end 
  56.     reg  [ 1: 0]   change_r ;
  57.     reg          sell_r ;
  58.     always @(posedge clk or negedge rstn) begin
  59.          if (!rstn) begin
  60.             change_r       <=  2 'b0 ;
  61.             sell_r         <=  1 'b0 ;
  62.         end
  63.          else  if (st_cur == GET20 ) begin
  64.             sell_r         <=  1 'b1 ;
  65.         end
  66.          else  if (st_cur == GET25) begin
  67.             change_r       <=  2 'b1 ;
  68.             sell_r         <=  1 'b1 ;
  69.         end
  70.          else begin
  71.             change_r       <=  2 'b0 ;
  72.             sell_r         <=  1 'b0 ;
  73.         end
  74.     end
  75.     assign       sell    = sell_r ;
  76.     assign       change  = change_r ;
  77. endmodule

将上述修改的 Moore 状态机例化到 3 段式的 testbench 中即可进行仿真,结果如下:

由图可知,输出信号与 Mealy 型 3 段式状态机相比延迟了一个时钟周期,这是因为进入到新增加的编码状态机时需要一个时钟周期的时延。此时,输出再用非阻塞赋值就会导致最终的输出信号延迟一个时钟周期。这也属于 Moore 型状态机的特点。

输出信号赋值时,用阻塞赋值,则可以提前一个时钟周期。

输出逻辑修改如下。

实例


  
  1.     reg  [ 1: 0]   change_r ;
  2.     reg          sell_r ;
  3.     always @(*) begin
  4.         change_r  =  'b0 ;
  5.         sell_r    =  'b0 ; //not list all condition, initializing them
  6.          if (st_cur == GET20 ) begin
  7.             sell_r         =  1 'b1 ;
  8.          end
  9.          else  if (st_cur == GET25) begin
  10.             change_r       =  2 'b1 ;
  11.             sell_r         =  1 'b1 ;
  12.          end
  13.      end

输出信号阻塞赋值的仿真结果如下:

由图可知,输出信号已经和 3 段式 Mealy 型状态机一致。


转载:https://blog.csdn.net/m0_61298445/article/details/125470553
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场