[筆記]有限狀態機FSM學習筆記(三)


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM