一、二、三段式狀態機
關於狀態機 一段式 二段式 三段式 (網上資料蒐集)
對於自認很有軟體程式設計經驗的我,初識狀態機,覺得沒什麼大不了的,實現起來沒什麼難度,
初學FPGA時學的是verilog, 看夏宇聞的書上狀態機的例子使用的一段式,當然他沒有說明這種寫法是一段式,當時覺得挺簡單明瞭.
後來用VHDL, 看的一本E文的書上, 狀態機的例子是典型的二段式(作者也沒說明這是兩段式),當時還覺得這種寫法挺麻煩的,沒有一段式的看起來舒服, 當時還沒有切身的體會兩種的區別以及一段式的劣處.
後來在一段式狀態機上吃了虧, 才想到去重新思考和認識狀態機,才知道了一段式 二段式 三段式的概念.
關於詳細地概念,我就不復述了,看兩篇網上轉載的文章就可以了:
Verilog三段式狀態機描述(轉載)
時序電路的狀態是一個狀態變數集合,這些狀態變數在任意時刻的值都包含了為確定電路的未來行為而必需考慮的所有歷史資訊。
狀態機採用VerilogHDL語言編碼,建議分為三個always段完成。
三段式建模描述FSM的狀態機輸出時,只需指定case敏感表為次態暫存器, 然後直接在每個次態的case分支中描述該狀態的輸出即可,不用考慮狀態轉移條件。
三段式描述方法雖然程式碼結構複雜了一些,但是換來的優勢是:使FSM做到了同步暫存器輸出,消除了組合邏輯輸出的不穩定與毛刺的隱患,而且更利於時序路徑分組,一般來說在FPGA/CPLD等可程式設計邏輯器件上的綜合與佈局佈線效果更佳。
示列如下:
//第一個程序,同步時序always模組,格式化描述次態暫存器遷移到現態暫存器
always @ (posedge clk or negedge rst_n) //非同步復位
if(!rst_n)
current_state <= IDLE;
else
current_state <= next_state; //注意,使用的是非阻塞賦值
//第二個程序,組合邏輯always模組,描述狀態轉移條件判斷
always @ (current_state) //電平觸發
begin
next_state = x; //要初始化,使得系統復位後能進入正確的狀態
case(current_state)
S1: if(...)
next_state = S2; //阻塞賦值
...
endcase
end
//第三個程序,同步時序always模組,格式化描述次態暫存器輸出
always @ (posedge clk or negedge rst_n)
...//初始化
case(next_state)
S1:
out1 <= 1'b1; //注意是非阻塞邏輯
S2:
out2 <= 1'b1;
default:... //default的作用是免除綜合工具綜合出鎖存器
endcase
end
兩段式有限狀態機與三段式有限狀態機的區別
FSM將時序部分(狀態轉移部分)和組合部分(判斷狀態轉移條件和產生輸出)分開,寫為兩個always語句,即為兩段式有限狀態機。
將組合部分中的判斷狀態轉移條件和產生輸入再分開寫,則為三段式有限狀態機。
區別:
二段式在組合邏輯特別複雜時適用,但要注意需在後面加一個觸發器以消除組合邏輯對輸出產生的毛刺。三段式沒有這個問題,由於第三個always會生成觸發器。
設計時注意方面:
1.編碼原則,binary和gray-code適用於觸發器資源較少,組合電路資源豐富的情況(CPLD),對於FPGA,適用one-hot code。這樣不但充分利用FPGA豐富的觸發器資源,還因為只需比較一個bit,速度快,組合電路簡單。
2.FSM初始化問題:
GSR(Gobal Set/Reset)只是在加電時清零所有的reg和片內ram,並不保證FSM能進入初始化狀態,要利用GSR,方案是適用one-hot code with zero idle,即初始狀態編碼為全零。已可以適用非同步復位rst
3.FSM輸出可以適用task
4FSM中的case最好加上default,預設態可以設為初始態
5.尤其注意:
第二段的always(組合部分,賦值用=)裡面判斷條件一定要包含所有情況!可以用else保證包含完全。
6第二段always中,組合邏輯電平要維持超過一個clock,模擬時注意。
Original address: http://blog.csdn.net/BuilderChen/article/details/1614963
以前看了不少關於如何寫VDHL狀態機的文章,都是提倡使用二段式或三段式的寫法,都建議避免使用一段式的寫法,但看了之後,都沒什麼體會。象我們寫軟體出身的,心理上總喜歡一段式的寫法,覺得思路比較連貫,而且可以寫在一個process裡,“內聚性”比較高。軟體工程師是最討厭多個函式共用全域性變數的了。
但對於硬體開發,就不一樣了。因為VHDL還是無法完全遮蔽掉硬體的物理特性,不好的佈局,會使得寫的邏輯錯誤執行。最近寫的一個狀態機,就遇到了這個麻煩。因為喜好的緣故,加上狀態機裡面有計數器,用組合邏輯寫比較麻煩,於是我用了一段式的寫法。結果實際執行的時候,發現狀態機經常無故鎖死,用邏輯分析儀看,發現陷入了非法的狀態,而且when others語句也無法使狀態機回到IDLE狀態。開始懷疑邏輯上有錯誤,折騰幾天後,把狀態切換部分獨立出來放在一個同步process裡,問題解決了。雖然偶爾還會發現落入非法狀態,但狀態機會自動恢復到初始狀態,不會鎖死了,而程式邏輯沒有做如何修改。看來以後還是得規規矩矩用二段或三段式的寫法了。為了便於記憶,把二段、三段式的特點終結成幾句話:
二段式:狀態切換用時序邏輯,次態輸出和訊號輸出用組合邏輯。
三段式:狀態切換用時序邏輯,次態輸出用組合邏輯,訊號輸出用時序邏輯。訊號輸出的process中,case語句用next state做條件,可以解決比組合邏輯輸出慢一拍的問題。
有時候判斷次態需要用到計數器怎麼辦呢(計數器是時序電路,用組合邏輯是實現不了的)?方法是獨立實現一個計數器,而在組合邏輯裡用使能訊號(或清除、置位等)來控制它。
這最後一篇文章已經把 一段式 二段式
https://blog.csdn.net/Reborn_Lee/article/details/82347216
上篇博文講了:FPGA中有限狀態機的狀態編碼採用格雷碼還是獨熱碼?
那篇博文講了狀態機的狀態編碼是用格雷碼還是獨熱碼的問題,以及兩者之間的優劣。狀態機的描述方式採用的是一段式描述方式,也就是將整個狀態機全部寫到一個always模組中去。
這篇博文仍用上篇博文的案例,說說狀態機的描述方法。一段式的描述方法、二段式以及三段式,並比較三者之間的功能模擬情況,最後真心吐露這個案例的狀態轉移圖的疑問?不能把有問題的地方迴避,我想我不要做這樣的人。
首先看看狀態機的描述方法,和編碼方式,這兩段描述借鑑:基於Verilog HDL的有限狀態機,人家說的不錯,我也懶著碼字了。
狀態機的描述方法
狀態機的描述方法多種多樣,將整個狀態機寫到1個always 模組裡,在該模組中既描述狀態轉移,又描述狀態的輸入和輸出,這種寫法一般被稱為一段式FSM 描述方法;
還有一種寫法是使用兩個always模組,其中一個always 模組採用同步時序的方式描述狀態轉移,而另一個模組採用組合邏輯的方式判斷狀態轉移條件,描述狀態轉移規律,這種寫法被稱為兩段式FSM 描述方法;
還有一種寫法是在兩段式描述方法的基礎上發展而來的,這種寫法使用3 個always模組,一個always 模組採用同步時序的方式描述狀態轉移,一個採用組合邏輯的方式判斷狀態轉移條件,描述狀態轉移規律,第三個always 模組使用同步時序電路描述每個狀態的輸出,這種寫法稱為三段式寫法。
狀態機的狀態編碼
二進位制碼(Binary)和格雷碼(Gray)屬於壓縮狀態編碼,這種編碼的優點是使用的狀態向量最少,但是需要較多的邏輯資源用來狀態譯碼。二進位制碼從一個狀態轉換到相鄰狀態時,可能有多個位元位發生變化,易產生中間狀態轉移問題,狀態機的速度也要比採用其它編碼方式慢。格雷碼兩個相鄰的碼值僅有一位就可區分,這將會減少電路中相鄰物理訊號線同時變化的情況,因而可以減少電路中的電噪聲。Johnson碼也有同樣的特點,但是要用較多的位數。
獨熱碼(One-hot)指對任意給定的狀態,狀態暫存器中只有l位為1,其餘位都為0。n狀態的有限狀態機需要n個觸發器,但這種有限狀態機只需對暫存器中的一位進行譯碼,簡化了譯碼邏輯電路,額外觸發器佔用的面積可用譯碼電路省下來的面積抵消。當設計中加入更多的狀態時,譯碼邏輯沒有變得更加複雜,有限狀態機的速度僅取決於到某特定狀態的轉移數量,而其它型別有限狀態機在狀態增加時速度會明顯下降。獨熱碼還具有設計簡單、修改靈活、易於綜合和除錯等優點。獨熱碼相對於二進位制碼,速度快但佔用面積大。
給出實際案例:
下面是一個狀態轉移圖,我們接下來就這個狀態轉移圖來用不同的描述方式來描述。
1)一段式描述方法:
- `timescale 1ns / 1ps
- //
- // Company:
- // Engineer:
- //
- // Create Date: 21:27:04 09/02/2018
- // Design Name:
- // Module Name: fsm
- // Project Name:
- // Target Devices:
- // Tool versions:
- // Description:
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- //
- //
- module fsm(
- input Clock,
- input rst_n,
- input A,
- output F,
- output G
- );
- reg F, G;
- reg [3:0] state;
- parameter Idle = 4'b1000, Start = 4'b0100, Stop = 4'b0010, Clear = 4'b0001;
- always @(posedge Clock) begin
- if(!rst_n) begin
- state <= Idle;
- F <= 1'b0;
- G <= 1'b0;
- end
- else
- case(state)
- Idle: begin
- if(A) begin
- state <= Start;
- G <= 1'b0;
- end
- else
- state <= Idle;
- end
- Start: begin
- if(!A)
- state <= Stop;
- else
- state <= Start;
- end
- Stop: begin
- if(A) begin
- state <= Clear;
- F <= 1'b1;
- end
- else
- state <= Stop;
- end
- Clear: begin
- if(!A)begin
- state <= Idle;
- F <= 1'b0;
- G <= 1'b1;
- end
- else
- state <= Clear;
- end
- default: state <= Idle;
- endcase
- end
- endmodule
給出測試檔案,測試檔案在這個案例中通用:
- `timescale 1ns / 1ps
- // Company:
- // Engineer:
- //
- // Create Date: 23:39:28 09/02/2018
- // Design Name: fsm
- // Module Name: G:/modelsim_file/fsm01/fsm_tb.v
- // Project Name: fsm01
- // Target Device:
- // Tool versions:
- // Description:
- //
- // Verilog Test Fixture created by ISE for module: fsm
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- //
- module fsm_tb;
- // Inputs
- reg Clock;
- reg rst_n;
- reg A;
- // Outputs
- wire F;
- wire G;
- // Instantiate the Unit Under Test (UUT)
- fsm uut (
- .Clock(Clock),
- .rst_n(rst_n),
- .A(A),
- .F(F),
- .G(G)
- );
- initial begin
- // Initialize Inputs
- rst_n = 0;
- A = 0;
- #30 A = 1;
- rst_n = 1;
- #30 A = 0;
- #20 A = 1;
- #20 A = 0;
- // Wait 100 ns for global reset to finish
- #100;
- end
- initial begin
- Clock = 0;
- forever #10 Clock = ~Clock;
- end
- endmodule
功能模擬:
兩段式描述方法:
- `timescale 1ns / 1ps
- //
- // Company:
- // Engineer:
- //
- // Create Date: 21:27:04 09/02/2018
- // Design Name:
- // Module Name: fsm
- // Project Name:
- // Target Devices:
- // Tool versions:
- // Description:
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- //
- //
- module fsm(
- input Clock,
- input rst_n,
- input A,
- output F,
- output G
- );
- reg F, G;
- reg [3:0] pre_state;
- reg [3:0] next_state;
- parameter Idle = 4'b1000, Start = 4'b0100, Stop = 4'b0010, Clear = 4'b0001;
- //第一個過程,同步時序always塊,描述狀態轉移方程
- always @(posedge Clock) begin
- if(!rst_n)
- pre_state <= Idle;
- else
- pre_state <= next_state;
- end
- //第二個過程,組合邏輯always塊,描述激勵方程以及輸出方程
- always @(pre_state or A or rst_n) begin
- case(pre_state)
- Idle:begin
- if(!rst_n) begin
- next_state = Idle;
- F = 1'b0;
- G = 1'b0;
- end
- else if(A) begin
- next_state = Start;
- G = 1'b0;
- end
- else begin
- next_state = Idle;
- end
- end
- Start: begin
- if(!rst_n) begin
- next_state = Idle;
- F = 1'b0;
- G = 1'b0;
- end
- else if(!A) begin
- next_state = Stop;
- end
- else begin
- next_state = Start;
- end
- end
- Stop: begin
- if(!rst_n) begin
- next_state = Idle;
- F = 1'b0;
- G = 1'b0;
- end
- else if(A) begin
- next_state = Clear;
- F = 1'b1;
- end
- else begin
- next_state = Stop;
- end
- end
- Clear: begin
- if(!rst_n) begin
- next_state = Idle;
- F = 1'b0;
- G = 1'b0;
- end
- else if(!A) begin
- next_state = Idle;
- F = 0;
- G = 1;
- end
- else begin
- next_state = Clear;
- end
- end
- default: begin
- next_state = Idle;
- end
- endcase
- end
- endmodule
三段式描述:
- `timescale 1ns / 1ps
- //
- // Company:
- // Engineer:
- //
- // Create Date: 21:27:04 09/02/2018
- // Design Name:
- // Module Name: fsm
- // Project Name:
- // Target Devices:
- // Tool versions:
- // Description:
- //
- // Dependencies:
- //
- // Revision:
- // Revision 0.01 - File Created
- // Additional Comments:
- //
- //
- module fsm(
- input Clock,
- input rst_n,
- input A,
- output F,
- output G
- );
- reg F, G;
- reg [3:0] pre_state;
- reg [3:0] next_state;
- parameter Idle = 4'b1000, Start = 4'b0100, Stop = 4'b0010, Clear = 4'b0001;
- //第一個過程,同步時序always塊,描述狀態轉移方程
- always @(posedge Clock) begin
- if(!rst_n)
- pre_state <= Idle;
- else
- pre_state <= next_state;
- end
- //第二個過程,組合邏輯always塊,描述激勵方程
- always @(pre_state or A or rst_n) begin
- case(pre_state)
- Idle: begin
- if(!rst_n) next_state = Idle;
- else if(A) next_state = Start;
- else next_state = Idle;
- end
- Start: begin
- if(!rst_n) next_state = Idle;
- else if(!A) next_state = Stop;
- else next_state = Start;
- end
- Stop: begin
- if(!rst_n) next_state = Idle;
- else if(A) next_state = Clear;
- else next_state = Stop;
- end
- Clear: begin
- if(!rst_n) next_state = Idle;
- else if(!A) next_state = Idle;
- else next_state = Clear;
- end
- default: next_state = Idle;
- endcase
- end
- //第三個always塊,描述輸出方程
- always @(pre_state or A or rst_n) begin
- case(pre_state)
- Idle:begin
- if(!rst_n) begin
- F = 1'b0;
- G = 1'b0;
- end
- else if(A) begin
- G = 1'b0;
- end
- else begin
- ;
- end
- end
- Start: begin
- if(!rst_n) begin
- F = 1'b0;
- G = 1'b0;
- end
- else if(!A) begin
- ;
- end
- else begin
- ;
- end
- end
- Stop: begin
- if(!rst_n) begin
- F = 1'b0;
- G = 1'b0;
- end
- else if(A) begin
- F = 1'b1;
- end
- else begin
- ;
- end
- end
- Clear: begin
- if(!rst_n) begin
- F = 1'b0;
- G = 1'b0;
- end
- else if(!A) begin
- F = 0;
- G = 1;
- end
- else begin
- ;
- end
- end
- default: begin
- ;
- end
- endcase
- end
- endmodule
功能模擬:
可見,三種描述方式的模擬圖都是一樣的,說明了完成同樣的功能。
但是從程式碼的簡潔度來看,就這個小狀態轉移圖來說,無疑,一段式的描述方式是最為簡單的。但是隨著專案的複雜度增高,這種描述方式不便於維護和閱讀。
所以呢?要綜合來看,不能說哪一種一定好,哪一種一定壞,要根據實際情況而定。
最後我要提出的問題,就是Clear這個狀態向Idle這個狀態轉移的條件:從狀態圖上看,注意圖中的狀態圖的復位我用rst_n代替,這樣更人性化!繼續:從狀態圖上看,是!rst_n或!A有效時,向Idle狀態轉移,並且輸出是F = 0 ,且 G =1;
但是從原作者的一段式程式碼中,我們可以看出,復位訊號rst_n的優先級別要高,如果復位了,那麼狀態肯定轉向Idle,且此時,輸出F=0且G=0.這明顯和狀態轉移圖中的意思不一樣啊,我們推測狀態轉移圖寫法有誤,所以這裡我個人預設復位訊號有限,其次是輸入A和當前狀態決定輸出。也就是說,如果復位訊號無效時,當前狀態為Clear且 !A有效,那麼輸出就是F = 0 ,且 G =1,並且狀態轉向Idle.