0. 簡介
在數電FPGA中,FSM是一個重要的部分,藉此可以完成一些復雜算法的硬件實現等。其中有關於FSM的寫法按照always塊的個數來划分,又分為一段式、兩段式、三段式狀態機。顧名思義,一段式就是狀態機由一個always塊組成;同理,兩段式為兩個always塊,三段式為三個always塊組成。
我們以Moore狀態機來進行一段、兩段、三段式狀態機的討論,Moore狀態機的結構如圖1所示。
圖1, 時鍾同步的Moore狀態機結構
1. 三段式狀態機(推薦使用)
上文中我們說道三段式狀態機為三個always塊,那么根據圖1我們可以划分出三段式狀態機的三個always塊的功能,如圖2所示。
圖2, 三段式always塊
其中三個always塊各自對應三個邏輯塊。則可以得出其書寫一般如下,其中狀態寄存器書寫時放在前面,美觀整潔,實質上放在第幾個always里沒有差別。
1 // 第一個always塊,描述當前狀態的狀態寄存器,non-blocking 2 always @ (posedge clk or negedge rst_n) begin 3 if (!rst_n) 4 curr_state <= idle; 5 else 6 curr_state <= next_state; 7 end 8 9 // 第二個always塊,描述狀態轉移,即下一狀態的狀態寄存器,blocking 10 always @ (*) begin 11 next_state = idle; // 初始化 12 case (curr_state) 13 idle: begin 14 if (...) 15 next_state = sx; 16 else 17 next_state = sy; 18 end 19 ... 20 default: 21 next_state = sz; 22 endcase 23 end 24 25 // 第三個always塊,組合邏輯描述輸出,blocking 26 always @ (*) begin 27 if (!rst_n) begin 28 o1 = 1'b0; 29 end 30 else begin 31 case (curr_state) 32 s1: begin 33 o1 = 1'b1; 34 end 35 ... 36 default: begin 37 o1 = 1'b0; 38 end 39 endcase 40 end 41 end
其中,有些地方需要注意:
a) 第一個always塊描繪狀態寄存器,為時序邏輯;后兩個描述轉移和輸出,為組合邏輯。 b) 第二個和第三個always塊的敏感列表使用always@(*)時,可以減少綜合時的error和warning(可能)。 c) 第二塊中,描述狀態轉移時的next_state = idle;其目的為上電初始化后可以使得next_state正常,不加大多數情況也可以,最好加上,避免未知情況的出現。
d) 由於第三段是組合邏輯輸出,那么其輸出可能產生毛刺,如果時序要求不高,可以在其后使用寄存器打一拍進行優化處理。 e) 在使用到case的情況下,最好做到"full-case"的情況,default不可或缺。
綜合上面的代碼結構及編寫風格,我們可以總結出三段式狀態機一些特點,三段式狀態機是一種推薦的寫法。
a) 三段式狀態機可以清晰完整的顯示出狀態機的結構。
b) 可以輕易的將狀態圖state diagram轉換為verilog code。
c) 代碼清晰,降低編寫維護復雜度。
d) 在簡單狀態機(狀態少,轉移條件少這類)的應用上,三段式代碼量和一二段的比較起來長些。
2. 兩段式狀態機(推薦使用)
從圖1我們得出,若是將三個邏輯塊寫在兩個always塊中,有着三種方法,在此我們介紹比較推薦的一種將狀態寄存器和狀態跳轉寫在一個always塊中,組合邏輯輸出寫在另一個always塊中的形式,如圖3所示。
圖3, 兩段式always塊
書寫時可以參照以下的格式來書寫,由於狀態寄存器和狀態轉移放在了一起,所以為non-blocking,組合邏輯輸出仍為blocking。
1 // 第一個always塊,描述狀態轉移和狀態寄存器,non-blocking 2 always @ (posedge clk or negedge rst_n) begin 3 if (!rst_n) begin 4 curr_state <= idle; 5 end 6 else begin 7 case (curr_state) 8 idle: begin 9 if (...) 10 curr_state = sx; 11 else 12 curr_state = sy; 13 end 14 ... 15 default: 16 curr_state = sz; 17 endcase 18 end 19 end 20 21 // 第二個always塊,組合邏輯描述輸出,blocking 22 always @ (*) begin 23 if (!rst_n) begin 24 o1 = 1'b0; 25 end 26 else begin 27 case (curr_state) 28 s1: begin 29 o1 = 1'b1; 30 end 31 ... 32 default: begin 33 o1 = 1'b0; 34 end 35 endcase 36 end 37 end
由於狀態跳轉和狀態寄存器組合在一起,所以可以去除next_state變量,但對於綜合或結構上是沒有太大的影響的。
在編寫過程中的一些注意事項和三段式里寫的都差不多,在此不做贅述。下面來簡單寫一下兩段式狀態機(邏輯跳轉和狀態寄存器組合一起)的一些特點。
a) 兩段式狀態機可以較清晰完整的顯示出狀態機的結構。
b) 可以輕易的將狀態圖state diagram轉換為verilog code。
c) 代碼清晰,降低編寫維護復雜度。
3. 一段式狀態機(不推薦)
上文中我們說道一段式狀態機為一個always塊,那么根據圖1我們可以知道,一段式狀態機要同時包含狀態跳轉和信號輸出,即如圖2中紅色框線所示。
圖4, 一段式always塊
其中將狀態跳轉、狀態寄存器、輸出組合邏輯放在一起,所以其中為non-blocking。其寫法可參考如下,但是大多情況下不推薦此種寫法。
1 // 一個always塊,描述狀態轉移,狀態寄存器,邏輯輸出,non-blocking 2 always @ (posedge clk or negedge rst_n) begin 3 if (!rst_n) begin 4 curr_state <= idle; 5 o1 <= 1'b0; 6 end 7 else begin 8 case (curr_state) 9 idle: begin 10 if (...) begin 11 curr_state = sx; 12 if (isignal) o1 = 1'b1; 13 else o1 = 1'b0; 14 end 15 else begin 16 curr_state = sy; 17 if (isignal) o1 = 1'b1; 18 else o1 = 1'b0; 19 end 20 end 21 ... 22 default: begin 23 curr_state = sz; 24 if (isignal) o1 = 1'b1; 25 else o1 = 1'b0; 26 end 27 endcase 28 end 29 end
使用三段式后,整體寫在一個non-blocking的邏輯塊中,但是又由於Moore狀態機的輸出只是跟當前狀態有關的,若將兩種邏輯單元使用一個時鍾去探測,可能會出現問題,在此的解決辦法就是邏輯單元輸出時的輸出判斷使用前一個時鍾的next_state狀態進行判斷或者將輸出提前一個時鍾,在此需要注意。因為是寫在一個always塊中,所以next_state也省了,故此只有將輸出提前判斷一個時鍾周期。在此簡單總結下使用一段式狀態機的一些特點。
a) 將所有的邏輯寫在一個always塊中,增加代碼復雜度,給后期更改維護帶來不便。
b) 由於其中有狀態寄存器,整體使用non-blockin,描述輸出組合邏輯時,需要提前一個時鍾,需要額外注意。
4. Mealy狀態機相關
以上都是根據Moore狀態機來對各種狀態機進行舉例,現在對於Mealy狀態機的寫法大致說明下。
三段式和二段式(狀態跳轉和狀態寄存器寫在一起)的寫法仍是推薦寫法。但是由於Mealy狀態機的輸出和輸入和狀態均有關,此時二段式中的狀態寄存器和輸出邏輯組合在一起無法和一段式狀態機無法正常的表述出Mealy狀態機。
其推薦寫法和上述Moore狀態機中描述大致相似,只是在邏輯輸出時添加上輸入判斷條件即可,如下。
1 // 組合邏輯描述輸出,blocking 2 always @ (*) begin 3 if (!rst_n) begin 4 o1 = 1'b0; 5 end 6 else begin 7 case (curr_state) 8 s1: begin 9 // 添加輸入條件判斷 10 if (isignal) o1 = 1'b1; 11 else o1 = 1'b0; 12 end 13 ... 14 default: begin 15 o1 = 1'b0; 16 end 17 endcase 18 end 19 end