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


    目录

    状态机介绍

    状态机类型

    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 型状态机一致。

  • 相关阅读:
    VSCode配置 C/C++ 环境
    css之复合选择器
    C++之if-else基本应用
    ArcGIS 10.3软件安装包下载及安装教程!
    m在ISE平台下使用verilog开发基于FPGA的GMSK调制器
    10BGP community属性
    Spring框架(六):SpringTransaction事务的底层原理、xml配置和注解配置
    10分钟设置免费海外远程桌面
    Linux动态链接懒加载
    Java IDEA controller导出CSV,excel
  • 原文地址:https://blog.csdn.net/m0_61298445/article/details/125470553