Moore型状态机和Mealy型状态机
一、状态机的定义
状态机就是能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定动作的控制中心。状态机简写为 FSM (Finite State Machine),分为两类:
1:输出只和当前状态有关而与输入无关,则称为摩尔(Moore)状态机;
2:输出不仅和当前状态有关而且和输入有关,则称为米利(Mealy)状态机;
二、两种状态机的区别
1:在波形上区别:以一个序列检测器为例,检测到输入信号11时输出z为1,其他时候为0。用摩尔型FSM实现需要用到三个状态(A,B,C)。而用米利型FSM实现则需要两个状态(A,B)。摩尔型FSM输出函数的输入只由状态变量决定,要想输出z=1,必须C状态形成,即寄存器中的两个1都打进去后才可以。输出z=1会在下一个有效沿到来的时候被赋值。而米利型FSM输出函数是由输入和状态变量共同决定的。状态在B的时候如果输入为1,则直接以组合电路输出z=1,不需要等到下个有效沿到来。从而也就不需要第三个状态C。
2:摩尔状态机更安全:输出在时钟边沿变化(总是在一个周期后)。在Mealy机器中,输入更改可能会在逻辑完成后立即导致输出更改, 当两台机器互连时出现大问题 ,如果不小心,可能会发生异步反馈。
3:Mealy状态机对输入的反应更快:在相同的周期内反应 - 不需要等待时钟。在Moore机器中,可能需要更多逻辑来将状态解码为输出 - 在时钟边沿之后更多的门延迟。并非所有时序电路都可以使用Mealy模型实现。 一些时序电路只能作为摩尔机器实现。
三、经典状态机模板
1、一段式状态机
只有一个always block,把所有的逻辑(输入、输出、状态)都在一个always block的时序逻辑中实现。这种写法看起来很简洁,但是不利于维护,如果状态复杂一些就很容易出错,不推荐这种方法。在简单的状态机可以使用。
verilog
//时序逻辑电路
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
cstate <= IDLE;
cmd <= 3'b000;
end
else
case(cstate)
IDLE:
if(wr_req)begin
cstate <= WR_S1;
cmd <= 3'b001;
end
else if(rd_req)begin
cstate <= RD_S1;
cmd <= 3'b011;
end
else begin
cstate <= IDLE;
cmd <= 3'b000;
end
WR_S1: begin
cstate <= WR_S2;
cmd <= 3'b010;
end
WR_S2: begin
cstate <= IDLE;
cmd <= 3'b000;
end
RD_S1:
if(wr_req)begin
cstate <= WR_S2;
cmd <= 3'b010;
end
else begin
cstate <= RD_S2;
cmd <= 3'b100;
end
RD_S2:
if(wr_req) begin
cstate <= WR_S1;
cmd <= 3'b001;
end
else begin
cstate <= IDLE;
cmd <= 3'b000;
end
default:cstate <= IDLE;
endcase
end
2、两段式状态机
verilog
//两段式状态机代码
reg[:0] cur_state;
reg[:0] nxt_state;
/**************** 第一段:描述状态跳转(时序逻辑)****************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cur_state<=IDLE;
else
cur_state<=nxt_state;
/**************** 第二段:状态判断及输出(组合逻辑)****************/
always@(*)
begin
case(cur_state)
IDLE:if()
begin
nxt_state=;
out=;
end
else if()
begin
nxt_state=;
out=;
end
else
begin
nxt_state=;
out=;
end
WR_S1:
begin
nxt_state=;
out=;
end
WR_S2:begin
nxt_state=;
out=;
end
RD_S1:
if(wr_req)
begin
nxt_state=;
out=;
end
else begin
nxt_state=;
out=;
end
RD_S2:if(wr_req)
begin
nxt_state=;
out=;
end
else
begin
nxt_state=;
out=;
end
default:begin
nxt_state=;
out=;
end
endcase
end
3、三段式状态机模板
verilog
reg [:] current_state ;
reg [:] next_state ;
wire [:0] IDLE ;
wire [:0] S0 ;
wire [:0] S1 ;
wire [:0] S2 ;
//=============================================================================\
//**************************** State Machine *******************************//
//=============================================================================\
/**************** 第一段:描述状态跳转(时序逻辑)****************/
always @(posedge sclk or negedge s_rst_n) begin
if(!s_rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
/**************** 第二段:下一状态判断(组合逻辑)****************/
always @(*) begin
next_state = IDLE;
case(current_state)
IDLE:begin
if(idle2s0 == 1'b1)
next_state = S0;
else
next_state = current_state;
end
S0:begin
if(s02s1 == 1'b1)
next_state = S1;
else
next_state =current_state;
end
S1:begin
if(s12s2 == 1'b1)
next_state = S2;
else
next_state = current_state;
end
S2:begin
if(s22idle == 1'b1)
next_state = IDLE;
else
next_state = current_state;
end
default:begin
next_state = IDLE;
end
endcase
end
/**************** 第三段:当前状态输出(可以是组合逻辑也可以是时序逻辑)****************/
always @(*) begin
case(current_state):
IDLE:
S0:
S1:
S2:
S3:
deafult:
end
4、摩尔型状态机
(1)非重叠检测 1101 1101
状态转移图:

verilog
//采用了三段式,moore状态机
module state_test(
input sclk ,
input s_rst_n ,
input din ,
output reg dout
);
//========================================================================\
// =========== Define Parameter and Internal signals ===========
//========================================================================/
reg [4:0] current_state ;
reg [4:0] next_state ;
parameter S0 = 5'b00001 ;
parameter S1 = 5'b00010 ;
parameter S2 = 5'b00100 ;
parameter S3 = 5'b01000 ;
parameter S4 = 5'b10000 ;
/*
这里五个状态用了5bit,为什么要用独热码呢?通常状态变量还可以通过二进制码或格雷码的方式对状态进行编码,为什么例子中我们使用的是独热码而非二进制码或格雷码呢?那就要从每种编码的特性上说起了,首先独热码因为每个状态只有1bit是不同的,所以在执行到55行时的(state == TWO)这条语句时,综合器会识别出这是一个比较器,而因为只有1比特为1,所以综合器会进行智能优化为(state[2] == 1' b1),这就相当于把之前3比特的比较器变为了1比特的比较器,大大节省了组合逻辑资源,但是付出的代价就是状态变量的位宽需要的比较多,而我们FPGA中组合逻辑资源相对较少,所以比较宝贵,而寄存器资源较多,所以很完美。而二进制编码的情况和独热码刚好相反,他因为使用了较少的状态变量,使之在减少了寄存器状态的 同时无法进行比较器部分的优化,所以使用的寄存器资源较少,而使用的组合逻辑资源较多,我们还知道CPLD就是一个组合逻辑资源多而寄存器逻辑资源少的器件,因为这里我们使用的是FPGA器件,所以使用独热码进行编码。就因为这个比较部分的优化,还使得使用独热码编码的状态机可以在高速系统上运行,其原因是多比特的比 较器每个比特到达比较器的时间可能会因为布局布线的走线长短而导致路径延时的不同,这样在高速系统下,就会导致采集到不稳定的状态,导致比较后的结果产生一个时钟的毛刺,使输出不稳定,而单比特的比较器就不用考虑这种问题。
用独热码编码虽然好处多多,但是如果状态数非常多的话即使是FPGA也吃不消独热码对寄存器的消耗,所以当状态数特别多的时候可以使用格雷码对状态进行编码。格雷码虽然也是和二进制编码一样使用的寄存器资源少,组合逻辑资源多,但是其相邻状态转换时只有一个状态发生翻转,这样不仅能消除状态转换时由多条信号线的传输延 迟所造成的毛刺,又可以降低功耗,所以要优于二进制码的方式,相当于是独热码和二进制编码的折中。
通常,小于4个状态,使用独热码,4-24个使用二进制编码,大于24个使用格雷码。
*/
//=============================================================================
//**************************** Main Code *******************************
//=============================================================================
/********************** 第一段 状态转移 (时序逻辑)**********************/
always @(posedge sclk or negedge s_rst_n) begin
if(!s_rst_n)
current_state <= S0;
else
current_state <= next_state;
end
/********************** 第二段 下一状态判断 (组合逻辑)**********************/
always @(*) begin
next_state = S0;
case(current_state)
S0:begin
if(din == 1'b1) // 1
next_state = S1;
else
next_state = current_state; // 0
end
S1:begin
if(din == 1'b1)
next_state = S2; // 11
else
next_state = S0; // 10
end
S2:begin
if(din == 1'b0) // 110
next_state = S3;
else
next_state = current_state; // 111
end
S3:begin
if(din == 1'b1)
next_state = S4; // 1101
else
next_state = S0; // 1100
end
S4:begin
if(din == 1'b1) // 1101 1
next_state = S1;
else
next_state = S0; // 1101 0
end
default:begin
next_state = S0;
end
endcase
end
/********************** 第三段 不同状态的输出 (组合逻辑)**********************/
//输出只与当前状态有关,与输入无关
always @(posedge sclk or negedge s_rst_n) begin
if(!s_rst_n) begin
dout <= 0;
end
else if(current_state == S4)
dout <= 1;
else
dout <= 0;
end
endmodule
5、米勒型状态机1101 序列检测

verilog
module mealy_state(
input sclk ,
input s_rst_n ,
input din ,
output reg dout
);
//========================================================================\
// =========== Define Parameter and Internal signals ===========
//========================================================================/
reg [3:0] current_state ;
reg [3:0] next_state ;
parameter IDLE = 4'b0001 ;
parameter S0 = 4'b0010 ;
parameter S1 = 4'b0100 ;
parameter S2 = 4'b1000 ;
//=============================================================================\
//**************************** State Machine *******************************
//=============================================================================\
/********************** 第一段 状态转移 (时序逻辑)**********************/
always @(posedge sclk or negedge s_rst_n) begin
if(!s_rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
/********************** 第二段 下一状态判断 (组合逻辑)**********************/
always @(*) begin
next_state = IDLE;
case(current_state)
IDLE:begin
if(din == 1'b1)
next_state = S0;
else
next_state = current_state;
end
S0:begin
if(din == 1'b1)
next_state = S1;
else
next_state = IDLE;
end
S1:begin
if(din == 1'b0)
next_state = S2;
else
next_state = current_state;
end
S2:begin
if(din == 1'b1)
next_state = IDLE;
else
next_state = IDLE;
end
default:begin
next_state = IDLE;
end
endcase
end
/********************** 第三段 输出不仅与当前状态有关还与当前输入有关 (时序逻辑)**********************/
always @(posedge sclk or negedge s_rst_n) begin
if(!s_rst_n)
dout <= 1'b0;
else if(current_state == S2 && din == 1'b1)
dout <= 1'b1;
else
dout <= 1'b0;
end
endmodule
四、总结
老的一段式、二段式、三段式各有优缺点,其中一段式在描述大型状态机时会比较困难,会使整个系统显得十分臃肿,不够清晰;二段式状态机的好处是其结构和理想的理论模型完全吻合,即不会有附加的结构存在,比较精简,但是由于二段状态机的第二段是组合逻辑描述数据的输出,所以有一些情况是无法描述的,比如输出时需要类似计 数的累加情况,这种情况在组合逻辑中会产生自迭代,自迭代在组合逻辑电路中是严格禁止的,而且第二段状态机主要是描述数据的输出,输出时使用组合逻辑往往会产生更多的毛刺,所以并不推荐。所以衍生出三段式状态机,三段状态机的输出就可是时序逻辑了,但是其结构并不是最精简的了。三段式状态机的第一段状态机是用时序逻辑 描述当前状态,第二段状态机是用组合逻辑描述下一状态,如果把这两个部分进行合并而第三段状态机保持不变,就是我们现在最新的二段式状态机了。这种新的写法在现在不同综合器中都可以被识别出来,这样既消除了组合逻辑可能产生的毛刺,又减小了代码量,还更加容易上手,不必再去关心理论模型是怎样的,仅仅根据状态转移图就 非常容易实现,对初学者来说十分友好。所以我们习惯性的使用两个均采用时序逻辑的 always 块,第一个 always 块描述状态的转移为第一段状态机,第二个 always 块描述数据的输出为第二段状态机(如果我们遵循一个always块只描述一个变量的原则,如果有多个输出时第二段状态机就可以分为多个always块来表达,但理论上仍属于新二段状态机,所以几段式状态机并不是由always块的数量简单决定的)。
verilog
module simple_fsm
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire pi_money , //投币方式可以为:不投币(0)、投1元(1)
output reg po_cola //po_cola为1时出可乐,po_cola为0时不出可乐
);
////
//\* Parameter and Internal Signal \//
////
//parameter define
//只有三种状态,使用独热码
parameter IDLE = 3'b001;
parameter ONE = 3'b010;
parameter TWO = 3'b100;
//reg define
reg [2:0] state ;
////
//\* Main Code \//
////
//第一段状态机,描述当前状态state如何根据输入跳转到下一状态
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE; //任何情况下只要按复位就回到初始状态
else case(state)
IDLE : if(pi_money == 1'b1) //判断输入情况
state <= ONE;
else
state <= IDLE;
ONE : if(pi_money == 1'b1)
state <= TWO;
else
state <= ONE;
TWO : if(pi_money == 1'b1)
state <= IDLE;
else
state <= TWO;
//如果状态机跳转到编码的状态之外也回到初始状态
default: state <= IDLE;
endcase
//第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_cola <= 1'b0;
else if((state == TWO) && (pi_money == 1'b1))
po_cola <= 1'b1;
else
po_cola <= 1'b0;
endmodule