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

【状态机设计】Moore、Mealy状态机、三段式、二段式、一段式状态机书写规范 目录状态机介绍状态机类型Moore 型状态机Mealy 型状态机状态机设计流程自动售卖机状态机设计3 段式推荐实例实例状态机修改2 段式实例状态机修改1 段式慎用实例状态机修改Moore 型实例实例状态机介绍有限状态机Finite-State MachineFSM简称状态机是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。状态机不仅是一种电路的描述工具而且也是一种思想方法在电路设计的系统级和 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 型状态机根据当前状态和当前输入确定输出信号。实例module vending_machine_p3 ( input clk , input rstn , input [1:0] coin , output [1:0] change , //找零 output sell //输出饮料 ); //machine state decode parameter IDLE 3d0 ; parameter GET05 3d1 ; parameter GET10 3d2 ; parameter GET15 3d3 ; reg [2:0] st_next ; reg [2:0] st_cur ; reg [1:0] change_r ; reg sell_r ; //第一段状态机时序逻辑 非阻塞赋值 always (posedge clk or negedge rstn) begin if (!rstn) begin st_cur b0 ; end else begin st_cur st_next ; end end //第二段状态机 组合逻辑 阻塞赋值 always (*) begin st_next st_cur ; //如果条件选项考虑不全可以赋初值消除latch case(st_cur) IDLE: case (coin) 2b01: st_next GET05 ; 2b10: st_next GET10 ; default: st_next IDLE ; endcase GET05: case (coin) 2b01: st_next GET10 ; 2b10: st_next GET15 ; default: st_next GET05 ; endcase GET10: case (coin) 2b01: st_next GET15 ; 2b10: st_next IDLE ; default: st_next GET10 ; endcase GET15: case (coin) 2b01,2b10: st_next IDLE ; default: st_next GET15 ; endcase default: st_next IDLE ; endcase end //第三段状态机时序逻辑 非阻塞赋值 always (posedge clk or negedge rstn) begin if (!rstn) begin change_r 2b0 ; sell_r 1b0 ; end else begin case (st_cur) IDLE: begin change_r 2b0 ; sell_r 1b0 ; end GET05: begin change_r 2b0 ; sell_r 1b0 ; end GET10: begin if (coin 2d2) begin change_r 2b0 ; sell_r 1b1 ; end else begin change_r 2b0 ; sell_r 1b0 ; end end GET15: begin if (coin 2h1) begin change_r 2b0 ; sell_r 1b1 ; end else if (coin 2h2) begin change_r 2b1 ; sell_r 1b1 ; end else begin change_r 2b0 ; sell_r 1b0 ; end end default: begin change_r 2b0 ; sell_r 1b0 ; end endcase end end assign sell sell_r ; assign change change_r ; endmoduletestbench 设计如下。仿真中模拟了 4 种情景分别是case1 对应连续输入 4 个 5 角硬币case2 对应 1 元 - 5 角 - 1 元的投币顺序case3 对应 5 角 - 1 元 - 5 角的投币顺序case4 对应连续 3 个 5 角然后一个 1 元的投币顺序。实例timescale 1ns/1ps module test ; reg clk; reg rstn ; reg [1:0] coin ; wire [1:0] change ; wire sell ; //clock generating parameter CYCLE_200MHz 10 ; always begin clk 0 ; #(CYCLE_200MHz/2) ; clk 1 ; #(CYCLE_200MHz/2) ; end //motivation generating reg [9:0] buy_oper ; initial begin buy_oper h0 ; coin 2h0 ; rstn 1b0 ; #8 rstn 1b1 ; (negedge clk) ; //case(1) 0.5 - 0.5 - 0.5 - 0.5 #16 ; buy_oper 10b00_0101_0101 ; repeat(5) begin (negedge clk) ; coin buy_oper[1:0] ; buy_oper buy_oper 2 ; end //case(2) 1 - 0.5 - 1, taking change #16 ; buy_oper 10b00_0010_0110 ; repeat(5) begin (negedge clk) ; coin buy_oper[1:0] ; buy_oper buy_oper 2 ; end //case(3) 0.5 - 1 - 0.5 #16 ; buy_oper 10b00_0001_1001 ; repeat(5) begin (negedge clk) ; coin buy_oper[1:0] ; buy_oper buy_oper 2 ; end //case(4) 0.5 - 0.5 - 0.5 - 1, taking change #16 ; buy_oper 10b00_1001_0101 ; repeat(5) begin (negedge clk) ; coin buy_oper[1:0] ; buy_oper buy_oper 2 ; end end //(1) mealy state with 3-stage vending_machine_p3 u_mealy_p3 ( .clk (clk), .rstn (rstn), .coin (coin), .change (change), .sell (sell) ); //simulation finish always begin #100; if ($time 10000) $finish ; end endmodule仿真结果如下:由图可知代表出货动作的信号 sell 都能在投币完毕后正常的拉高而代表找零动作的信号 change 也都能根据输入的硬币场景输出正确的是否找零信号。状态机修改2 段式将 3 段式状态机 2、3 段描述合并其他部分保持不变状态机就变成了 2 段式描述。修改部分如下实例reg [1:0] change_r ; reg sell_r ; always (*) begin case(st_cur) IDLE: begin change_r 2b0 ; sell_r 1b0 ; case (coin) 2b01: st_next GET05 ; 2b10: st_next GET10 ; default: st_next IDLE ; endcase end GET05: begin change_r 2b0 ; sell_r 1b0 ; case (coin) 2b01: st_next GET10 ; 2b10: st_next GET15 ; default: st_next GET05 ; endcase end GET10: case (coin) 2b01: begin st_next GET15 ; change_r 2b0 ; sell_r 1b0 ; end 2b10: begin st_next IDLE ; change_r 2b0 ; sell_r 1b1 ; end default: begin st_next GET10 ; change_r 2b0 ; sell_r 1b0 ; end endcase GET15: case (coin) 2b01: begin st_next IDLE ; change_r 2b0 ; sell_r 1b1 ; end 2b10: begin st_next IDLE ; change_r 2b1 ; sell_r 1b1 ; end default: begin st_next GET15 ; change_r 2b0 ; sell_r 1b0 ; end endcase default: begin st_next IDLE ; change_r 2b0 ; sell_r 1b0 ; end endcase end将上述修改的新模块例化到 3 段式的 testbench 中即可进行仿真结果如下:由图可知出货信号 sell 和 找零信号 change 相对于 3 段式状态机输出提前了一个时钟周期这是因为输出信号都是阻塞赋值导致的。如图中红色圆圈部分输出信号都出现了干扰脉冲这是因为输入信号都是异步的而且输出信号是组合逻辑输出没有时钟驱动。实际中如果输入信号都是与时钟同步的这种干扰脉冲是不会出现的。如果是异步输入信号首先应当对信号进行同步。状态机修改1 段式慎用将 3 段式状态机 1、 2、3 段描述合并状态机就变成了 1 段式描述。修改部分如下实例reg [1:0] change_r ; reg sell_r ; always (posedge clk or negedge rstn) begin if (!rstn) begin st_cur b0 ; change_r 2b0 ; sell_r 1b0 ; end else begin case(st_cur) IDLE: begin change_r 2b0 ; sell_r 1b0 ; case (coin) 2b01: st_cur GET05 ; 2b10: st_cur GET10 ; endcase end GET05: begin case (coin) 2b01: st_cur GET10 ; 2b10: st_cur GET15 ; endcase end GET10: case (coin) 2b01: st_cur GET15 ; 2b10: begin st_cur IDLE ; sell_r 1b1 ; end endcase GET15: case (coin) 2b01: begin st_cur IDLE ; sell_r 1b1 ; end 2b10: begin st_cur IDLE ; change_r 2b1 ; sell_r 1b1 ; end endcase default: begin st_cur IDLE ; end endcase // case (st_cur) end end将上述修改的新模块例化到 3 段式的 testbench 中即可进行仿真结果如下:由图可知输出信号与 3 段式状态机完全一致。1 段式状态机的缺点就是许多种逻辑糅合在一起不易后期的维护。当状态机和输出信号较少时可以尝试此种描述方式。状态机修改Moore 型如果使用 Moore 型状态机描述售卖机的工作流程那么还需要再增加 2 个状态编码用以描述 Mealy 状态机输出时的输入信号和状态机状态。3 段式 Moore 型状态机描述的自动售卖机 Verilog 代码如下实例module vending_machine_moore ( input clk , input rstn , input [1:0] coin , output [1:0] change , output sell ); parameter IDLE 3d0 ; parameter GET05 3d1 ; parameter GET10 3d2 ; parameter GET15 3d3 ; parameter GET20 3d4 ; parameter GET25 3d5 ; reg [2:0] st_next ; reg [2:0] st_cur ; //(1) state transfer always (posedge clk or negedge rstn) begin if (!rstn) begin st_cur b0 ; end else begin st_cur st_next ; end end always (*) begin //all case items need to be displayed completely case(st_cur) IDLE: case (coin) 2b01: st_next GET05 ; 2b10: st_next GET10 ; default: st_next IDLE ; endcase GET05: case (coin) 2b01: st_next GET10 ; 2b10: st_next GET15 ; default: st_next GET05 ; endcase GET10: case (coin) 2b01: st_next GET15 ; 2b10: st_next GET20 ; default: st_next GET10 ; endcase GET15: case (coin) 2b01: st_next GET20 ; 2b10: st_next GET25 ; default: st_next GET15 ; endcase GET20: st_next IDLE ; GET25: st_next IDLE ; default: st_next IDLE ; endcase end reg [1:0] change_r ; reg sell_r ; always (posedge clk or negedge rstn) begin if (!rstn) begin change_r 2b0 ; sell_r 1b0 ; end else if (st_cur GET20 ) begin sell_r 1b1 ; end else if (st_cur GET25) begin change_r 2b1 ; sell_r 1b1 ; end else begin change_r 2b0 ; sell_r 1b0 ; end end assign sell sell_r ; assign change change_r ; endmodule将上述修改的 Moore 状态机例化到 3 段式的 testbench 中即可进行仿真结果如下:由图可知输出信号与 Mealy 型 3 段式状态机相比延迟了一个时钟周期这是因为进入到新增加的编码状态机时需要一个时钟周期的时延。此时输出再用非阻塞赋值就会导致最终的输出信号延迟一个时钟周期。这也属于 Moore 型状态机的特点。输出信号赋值时用阻塞赋值则可以提前一个时钟周期。输出逻辑修改如下。实例reg [1:0] change_r ; reg sell_r ; always (*) begin change_r b0 ; sell_r b0 ; //not list all condition, initializing them if (st_cur GET20 ) begin sell_r 1b1 ; end else if (st_cur GET25) begin change_r 2b1 ; sell_r 1b1 ; end end输出信号阻塞赋值的仿真结果如下:由图可知输出信号已经和 3 段式 Mealy 型状态机一致。