有限狀態(tài)機(jī)(Finite-State Machine,F(xiàn)SM),簡稱狀態(tài)機(jī),是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)模型。狀態(tài)機(jī)不僅是一種電路的描述工具,而且也是一種思想方法,在電路設(shè)計的系統(tǒng)級和 RTL 級有著廣泛的應(yīng)用。
Verilog 中狀態(tài)機(jī)主要用于同步時序邏輯的設(shè)計,能夠在有限個狀態(tài)之間按一定要求和規(guī)律切換時序電路的狀態(tài)。狀態(tài)的切換方向不但取決于各個輸入值,還取決于當(dāng)前所在狀態(tài)。 狀態(tài)機(jī)可分為 2 類:?Moore
?狀態(tài)機(jī)和 ?Mealy
?狀態(tài)機(jī)。
?Moore
?型狀態(tài)機(jī)的輸出只與當(dāng)前狀態(tài)有關(guān),與當(dāng)前輸入無關(guān)。
輸出會在一個完整的時鐘周期內(nèi)保持穩(wěn)定,即使此時輸入信號有變化,輸出也不會變化。輸入對輸出的影響要到下一個時鐘周期才能反映出來。這也是 ?Moore
?型狀態(tài)機(jī)的一個重要特點:輸入與輸出是隔離開來的。
?Mealy
?型狀態(tài)機(jī)的輸出,不僅與當(dāng)前狀態(tài)有關(guān),還取決于當(dāng)前的輸入信號。
?Mealy
?型狀態(tài)機(jī)的輸出是在輸入信號變化以后立刻發(fā)生變化,且輸入變化可能出現(xiàn)在任何狀態(tài)的時鐘周期內(nèi)。因此,同種邏輯下,?Mealy
?型狀態(tài)機(jī)輸出對輸入的響應(yīng)會比 ?Moore
?型狀態(tài)機(jī)早一個時鐘周期。
根據(jù)設(shè)計需求畫出狀態(tài)轉(zhuǎn)移圖,確定使用狀態(tài)機(jī)類型,并標(biāo)注出各種輸入輸出信號,更有助于編程。一般使用最多的是 ?Mealy
?型 3 段式狀態(tài)機(jī),下面用通過設(shè)計一個自動售賣機(jī)的具體實例來說明狀態(tài)機(jī)的設(shè)計過程。
自動售賣機(jī)的功能描述如下:
飲料單價 2 元,該售賣機(jī)只能接受 0.5 元、1 元的硬幣。考慮找零和出貨。投幣和出貨過程都是一次一次的進(jìn)行,不會出現(xiàn)一次性投入多幣或一次性出貨多瓶飲料的現(xiàn)象。每一輪售賣機(jī)接受投幣、出貨、找零完成后,才能進(jìn)入到新的自動售賣狀態(tài)。
該售賣機(jī)的工作狀態(tài)轉(zhuǎn)移圖如下所示,包含了輸入、輸出信號狀態(tài)。
其中,coin = 1 代表投入了 0.5 元硬幣,coin = 2 代表投入了 1 元硬幣。
狀態(tài)機(jī)設(shè)計如下:
Mealy
?型狀態(tài)機(jī),根據(jù)當(dāng)前狀態(tài)和當(dāng)前輸入,確定輸出信號。// vending-machine
// 2 yuan for a bottle of drink
// only 2 coins supported: 5 jiao and 1 yuan
// finish the function of selling and changing
module vending_machine_p3 (
input clk ,
input rstn ,
input [1:0] coin , //01 for 0.5 jiao, 10 for 1 yuan
output [1:0] change ,
output sell //output the drink
);
//machine state decode
parameter IDLE = 3'd0 ;
parameter GET05 = 3'd1 ;
parameter GET10 = 3'd2 ;
parameter GET15 = 3'd3 ;
//machine variable
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
//(2) state switch, using block assignment for combination-logic
//all case items need to be displayed completely
always @(*) begin
//st_next = st_cur ;//如果條件選項考慮不全,可以賦初值消除latch
case(st_cur)
IDLE:
case (coin)
2'b01: st_next = GET05 ;
2'b10: st_next = GET10 ;
default: st_next = IDLE ;
endcase
GET05:
case (coin)
2'b01: st_next = GET10 ;
2'b10: st_next = GET15 ;
default: st_next = GET05 ;
endcase
GET10:
case (coin)
2'b01: st_next = GET15 ;
2'b10: st_next = IDLE ;
default: st_next = GET10 ;
endcase
GET15:
case (coin)
2'b01,2'b10:
st_next = IDLE ;
default: st_next = GET15 ;
endcase
default: st_next = IDLE ;
endcase
end
//(3) output logic, using non-block assignment
reg [1:0] change_r ;
reg sell_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
end
else if ((st_cur == GET15 && coin ==2'h1)
|| (st_cur == GET10 && coin ==2'd2)) begin
change_r <= 2'b0 ;
sell_r <= 1'b1 ;
end
else if (st_cur == GET15 && coin == 2'h2) begin
change_r <= 2'b1 ;
sell_r <= 1'b1 ;
end
else begin
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
end
end
assign sell = sell_r ;
assign change = change_r ;
endmodule
testbench 設(shè)計如下。仿真中模擬了 4 種情景,分別是:
case1 對應(yīng)連續(xù)輸入 4 個 5 角硬幣;case2 對應(yīng) 1 元 - 5 角 - 1 元的投幣順序;case3 對應(yīng) 5 角 - 1 元 - 5 角的投幣順序;case4 對應(yīng)連續(xù) 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 ; //store state of the buy operation
initial begin
buy_oper = 'h0 ;
coin = 2'h0 ;
rstn = 1'b0 ;
#8 rstn = 1'b1 ;
@(negedge clk) ;
//case(1) 0.5 -> 0.5 -> 0.5 -> 0.5
#16 ;
buy_oper = 10'b00_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 = 10'b00_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 = 10'b00_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 = 10'b00_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 // test
仿真結(jié)果如下:
由圖可知,代表出貨動作的信號 sell 都能在投幣完畢后正常的拉高,而代表找零動作的信號 change 也都能根據(jù)輸入的硬幣場景輸出正確的是否找零信號。
將 3 段式狀態(tài)機(jī) 2、3 段描述合并,其他部分保持不變,狀態(tài)機(jī)就變成了 2 段式描述。
修改部分如下:
//(2) state switch, and output logic
//all using block assignment for combination-logic
reg [1:0] change_r ;
reg sell_r ;
always @(*) begin //all case items need to be displayed completely
case(st_cur)
IDLE: begin
change_r = 2'b0 ;
sell_r = 1'b0 ;
case (coin)
2'b01: st_next = GET05 ;
2'b10: st_next = GET10 ;
default: st_next = IDLE ;
endcase // case (coin)
end
GET05: begin
change_r = 2'b0 ;
sell_r = 1'b0 ;
case (coin)
2'b01: st_next = GET10 ;
2'b10: st_next = GET15 ;
default: st_next = GET05 ;
endcase // case (coin)
end
GET10:
case (coin)
2'b01: begin
st_next = GET15 ;
change_r = 2'b0 ;
sell_r = 1'b0 ;
end
2'b10: begin
st_next = IDLE ;
change_r = 2'b0 ;
sell_r = 1'b1 ;
end
default: begin
st_next = GET10 ;
change_r = 2'b0 ;
sell_r = 1'b0 ;
end
endcase // case (coin)
GET15:
case (coin)
2'b01: begin
st_next = IDLE ;
change_r = 2'b0 ;
sell_r = 1'b1 ;
end
2'b10: begin
st_next = IDLE ;
change_r = 2'b1 ;
sell_r = 1'b1 ;
end
default: begin
st_next = GET15 ;
change_r = 2'b0 ;
sell_r = 1'b0 ;
end
endcase
default: begin
st_next = IDLE ;
change_r = 2'b0 ;
sell_r = 1'b0 ;
end
endcase
end
將上述修改的新模塊例化到 3 段式的 testbench 中即可進(jìn)行仿真,結(jié)果如下:
由圖可知,出貨信號 sell 和 找零信號 change 相對于 3 段式狀態(tài)機(jī)輸出提前了一個時鐘周期,這是因為輸出信號都是阻塞賦值導(dǎo)致的。
如圖中紅色圓圈部分,輸出信號都出現(xiàn)了干擾脈沖,這是因為輸入信號都是異步的,而且輸出信號是組合邏輯輸出,沒有時鐘驅(qū)動。
實際中,如果輸入信號都是與時鐘同步的,這種干擾脈沖是不會出現(xiàn)的。如果是異步輸入信號,首先應(yīng)當(dāng)對信號進(jìn)行同步。
將 3 段式狀態(tài)機(jī) 1、 2、3 段描述合并,狀態(tài)機(jī)就變成了 1 段式描述。
修改部分如下:
//(1) using one state-variable do describe
reg [1:0] change_r ;
reg sell_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
st_cur <= 'b0 ;
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
end
else begin
case(st_cur)
IDLE: begin
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
case (coin)
2'b01: st_cur <= GET05 ;
2'b10: st_cur <= GET10 ;
endcase
end
GET05: begin
case (coin)
2'b01: st_cur <= GET10 ;
2'b10: st_cur <= GET15 ;
endcase
end
GET10:
case (coin)
2'b01: st_cur <= GET15 ;
2'b10: begin
st_cur <= IDLE ;
sell_r <= 1'b1 ;
end
endcase
GET15:
case (coin)
2'b01: begin
st_cur <= IDLE ;
sell_r <= 1'b1 ;
end
2'b10: begin
st_cur <= IDLE ;
change_r <= 2'b1 ;
sell_r <= 1'b1 ;
end
endcase
default: begin
st_cur <= IDLE ;
end
endcase // case (st_cur)
end // else: !if(!rstn)
end
將上述修改的新模塊例化到 3 段式的 testbench 中即可進(jìn)行仿真,結(jié)果如下:
由圖可知,輸出信號與 3 段式狀態(tài)機(jī)完全一致。
1 段式狀態(tài)機(jī)的缺點就是許多種邏輯糅合在一起,不易后期的維護(hù)。當(dāng)狀態(tài)機(jī)和輸出信號較少時,可以嘗試此種描述方式。
如果使用 ?Moore
?型狀態(tài)機(jī)描述售賣機(jī)的工作流程,那么還需要再增加 2 個狀態(tài)編碼,用以描述 ?Mealy
?狀態(tài)機(jī)輸出時的輸入信號和狀態(tài)機(jī)狀態(tài)。
3 段式 ?Moore
?型狀態(tài)機(jī)描述的自動售賣機(jī) Verilog 代碼如下:
module vending_machine_moore (
input clk ,
input rstn ,
input [1:0] coin , //01 for 0.5 jiao, 10 for 1 yuan
output [1:0] change ,
output sell //output the drink
);
//machine state decode
parameter IDLE = 3'd0 ;
parameter GET05 = 3'd1 ;
parameter GET10 = 3'd2 ;
parameter GET15 = 3'd3 ;
// new state for moore state-machine
parameter GET20 = 3'd4 ;
parameter GET25 = 3'd5 ;
//machine variable
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
//(2) state switch, using block assignment for combination-logic
always @(*) begin //all case items need to be displayed completely
case(st_cur)
IDLE:
case (coin)
2'b01: st_next = GET05 ;
2'b10: st_next = GET10 ;
default: st_next = IDLE ;
endcase
GET05:
case (coin)
2'b01: st_next = GET10 ;
2'b10: st_next = GET15 ;
default: st_next = GET05 ;
endcase
GET10:
case (coin)
2'b01: st_next = GET15 ;
2'b10: st_next = GET20 ;
default: st_next = GET10 ;
endcase
GET15:
case (coin)
2'b01: st_next = GET20 ;
2'b10: st_next = GET25 ;
default: st_next = GET15 ;
endcase
GET20: st_next = IDLE ;
GET25: st_next = IDLE ;
default: st_next = IDLE ;
endcase // case (st_cur)
end // always @ (*)
// (3) output logic,
// one cycle delayed when using non-block assignment
reg [1:0] change_r ;
reg sell_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
end
else if (st_cur == GET20 ) begin
sell_r <= 1'b1 ;
end
else if (st_cur == GET25) begin
change_r <= 2'b1 ;
sell_r <= 1'b1 ;
end
else begin
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
end
end
assign sell = sell_r ;
assign change = change_r ;
endmodule
將上述修改的 ?Moore
?狀態(tài)機(jī)例化到 3 段式的 testbench 中即可進(jìn)行仿真,結(jié)果如下:
由圖可知,輸出信號與 ?Mealy
?型 3 段式狀態(tài)機(jī)相比延遲了一個時鐘周期,這是因為進(jìn)入到新增加的編碼狀態(tài)機(jī)時需要一個時鐘周期的時延。此時,輸出再用非阻塞賦值就會導(dǎo)致最終的輸出信號延遲一個時鐘周期。這也屬于 ?Moore
?型狀態(tài)機(jī)的特點。
輸出信號賦值時,用阻塞賦值,則可以提前一個時鐘周期。
輸出邏輯修改如下:
// (3.2) output logic, using block assignment
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 = 1'b1 ;
end
else if (st_cur == GET25) begin
change_r = 2'b1 ;
sell_r = 1'b1 ;
end
end
輸出信號阻塞賦值的仿真結(jié)果如下:
由圖可知,輸出信號已經(jīng)和 3 段式 ?Mealy
?型狀態(tài)機(jī)一致。
點擊這里下載源碼
更多建議: