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的時序邏輯中實現。這種寫法看起來很簡潔,但是不利於維護,如果狀態複雜一些就很容易出錯,不推薦這種方法。在簡單的狀態機可以使用。
//時序邏輯電路
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、兩段式狀態機
//兩段式狀態機代碼
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、三段式狀態機模板
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
狀態轉移圖:

//採用了三段式,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 序列檢測

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塊的數量簡單決定的)。
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
五、參考
1、 野火]FPGA Verilog開發實戰指南——基於Altera EP4CE10 征途Pro開發板 文檔
2、Moore型狀態機和Mealy型狀態機 - 青河 - 博客園