FPGA--有限狀態機(FSM)的設計
學習有限狀態機(FSM)的設計
什麼是狀態機?簡單來說,就是通過不同的狀態遷移來完成一些特定的順序邏輯。
FSM定義:
一個有限狀態機是一個裝置,或者是一個裝置模型,具有有限數量的狀態,它可以在任何給定的時間根據輸入進行操作,使得一個狀態變換到另一個狀態,或者是使一個輸入或者一種行為的發生。一個有限狀態機在任何瞬間只能處在一種狀態。
它的優點:
1.程式設計快速簡單,2.易於除錯,3.很少的計算開銷,4.直覺性,5.靈活性。
圖1-1 狀態機圖例
無論是在C\C++等的高階語言程式設計,還是用硬體描述語言(verilog HDL)中都會涉及到相關的知識點,學習下來作為自己的知識儲備確是一件幸事。
狀態機設計
1、狀態機分類
如果時序邏輯的輸出不但取決於狀態還取決於輸入,稱為 Mealy 狀態機。如果輸入變化,這類狀態機的輸出可能在一個時鐘週期的中間跟著改變。
圖1-2 Mealy 狀態機
而有些時序邏輯電路的輸出只取決於當前狀態,這樣的電路就稱為 Moore狀態機。這類狀態機輸出和輸入成隔離狀態:輸出將在剩餘的時鐘週期內保持穩定(為一個常數),即使輸入在時鐘週期內變化。
圖1-3 Moore狀態機
2、有限狀態機的狀態編碼
常用的編碼有三種:二進位制編碼、 格雷碼 (Gray) 、獨熱碼 (one-hot) 編碼。另外,還可以自定義編碼,比如在高速設計中以狀態編碼作為輸出。
二進位制碼
二進位制碼從一個狀態轉換到相鄰狀態時,可能有多個位元位發生變化,易產生中間狀態轉移問題,狀態機的速度也要比採用其它編碼方式慢。
格雷碼
格雷碼兩個相鄰的碼值僅有一位就可區分,這將會減少電路中相鄰物理訊號線同時變化的情況,因而可以減少電路中的電噪聲。Johnson碼也有同樣的特點,但是要用較多的位數。
獨熱碼
獨熱碼(One-hot)指對任意給定的狀態,狀態暫存器中只有l位為1,其餘位都為0。n狀態的有限狀態機需要n個觸發器,但這種有限狀態機只需對暫存器中的一位進行譯碼,簡化了譯碼邏輯電路,額外觸發器佔用的面積可用譯碼電路省下來的面積抵消。當設計中加入更多的狀態時,譯碼邏輯沒有變得更加複雜,有限狀態機的速度僅取決於到某特定狀態的轉移數量,而其它型別有限狀態機在狀態增加時速度會明顯下降。獨熱碼還具有設計簡單、修改靈活、易於綜合和除錯等優點。獨熱碼相對於二進位制碼,速度快但佔用面積大。
自定義編碼
自定義編碼相對靈活。在設計高速電路時,常常有必要使狀態機的輸出與時鐘幾乎完全同步。有一個辦法是把狀態變數(也可能是狀態變數中的幾位)直接用作輸出,稱為輸出編碼的狀態指定。屬於 Moore 狀態機。但這種方法也有缺點,就是開關的維持時間必須與狀態維持的時間一致,需要增加狀態才能實現。
設計高速狀態機時,在輸出邏輯 G 後面再加一組與時鐘同步的暫存器輸出流水線暫存器,讓 G 所有的輸出訊號在下一個時鐘跳變沿時同時存入暫存器組,即完全同步地輸出,把這種輸出稱為流水線化的輸出。
編碼總結
從設計大小的角度來看,在小設計中可以考慮使用 Gray 碼或 one-hot。大設計中,由於現在技術進步幾乎不用考慮邏輯資源不夠的問題,可以考慮使用one-hot 編碼以提高速度。至於順序二進位制編碼,一般不予考慮。但是要達到最佳效能,需要使用更高階的編碼演算法,針對給定的狀態機進行分析。
從 FPGA/CPLD 的角度來看,由於 CPLD 提供更多的組合邏輯資源,而 FPGA提供更多的觸發器資源,所以 CPLD 多使用 Gray 碼,而 FPGA 多使用 one-hot編碼。
3、狀態機的設計準則
1 基本準則
(1) 一個Verilog模組至多描述一個有限狀態機。這樣不僅可以簡化狀態的定義、修改和除錯,還可以利用一些EDA工具來協助設計。
(2) 使用引數給狀態賦值,而不是用巨集定義(‘define)。因為’define巨集定義在編譯時自動替換整個設計中所定義的巨集,而parameter僅僅定義模組內部的引數,定義的引數不會與模組外的其它狀態機混淆。
(3) 用always模組寫組合邏輯時,採用阻塞賦值,而在always塊中建立時序電路時,用非阻塞賦值。這樣才能保證有限狀態機綜合前和綜合後模擬的一致性。
2 一般步驟
邏輯抽象得出狀態轉換圖
就是把給出的一個實際邏輯關係表示為時序邏輯函式,可以用狀態轉換表來描述,也可以用狀態轉換圖來描述。這就需要:
1) 分析給定的邏輯問題,確定輸入變數、輸出變數以及電路的狀態數。通常是取原因(或條件)作為輸入變數,取結果作為輸出變數。
2) 定義輸入、輸出邏輯狀態的含義,並將電路狀態順序編號。
3) 按照要求列出電路的狀態轉換表或畫出狀態轉換圖。
這樣,就把給定的邏輯問題抽象到了一個時序邏輯函數了。狀態化簡
如果在狀態轉換圖中出現這樣兩個狀態,它們在相同的輸入轉換到同一狀態去,並得出一樣的輸出,則稱為等價狀態。顯然等價狀態是重複的,一合併為一個電路的狀態數越少,儲存電路也就越簡單。狀態化簡的目的就是在於將等價狀態儘可能地的合併,以得到最簡的狀態轉換圖。狀態分配
選定觸發器的型別並求出狀態方程、驅動方程和輸出方程。
- 按照方程得出邏輯圖
用Verilog HDL來描述有限狀態機,可以充分發揮硬體描述語言的抽象建模能力,使用always塊語句和case(if)等條件語句及賦值語句即可方便實現。具體的邏輯化簡、邏輯電路到觸發器對映均可有計算機自動完成。步驟中的2、4、5不再需要人為干預,使電路設計工作得到簡化,效率也有很大的提高。
———————————–節選之夏宇聞老師的Verilog HDL數字系統塊設計教程(第2版)
4、狀態機的描述方法
(1)一段式
使用一個always模組包含整個狀態機他不符合將時序和組合邏輯分開描述的Coding Style,而且在描述當前狀態時要考慮下個狀態的輸出,整個程式碼不清晰,不利於維護修改,並且不利於附加約束,不利於綜合器和佈局佈線器對設計的優化。一般來說,一段式程式碼長度會比兩段式冗長大約80%到150%左右。
圖1-4 一段式狀態機
(2)兩段式
使用兩個always模組,其中一個always模組採用同步時序的方式描述狀態轉移,而另一個模組採用組合邏輯的方式判斷狀態轉移條件、描述狀態轉移規律,稱兩段式FSM描述方法;為了使FSM描述清晰簡潔、易於維護、易於附加時序約束,使綜合器和佈局佈線器更好的優化設計,推薦使用兩段式FSM描述方法。請注意,雖然下一狀態暫存器 NS 為暫存器型別,但是在兩段式 FSM 的判斷狀態轉移條件的 always 塊中,實際對應的真實硬體電路是純組合邏輯電路。
圖1-5 兩段式狀態機
(3)三段式
使用三個 always 模組,一個 always 模組採用同步時序的方式描述狀態轉移,一個採用組合邏輯的方式判斷狀態轉移條件、描述狀態轉換規律,第三個 always模組使用同步時序電路描述每個狀態的輸出,稱三段式寫法。
圖1-6 三段式狀態機
綜上,得出三種描述 FSM 方法的比較表
5、狀態機模板
一段式
###兩段式
兩段式
二段式格式(組合邏輯輸出)
always @ (posedge clk or negedge nrst)
if (!nrst)
CS <= IDLE;
else
CS <=NS;
//敏感列表必須完整:當前狀態及所有相關組合邏輯輸入
always @ (nrst or CS or i1 or i2) begin
//第二段 always 中,組合邏輯電平要維持超過一個 clock,模擬時注意。
NS = 3'b0; //此處賦值一般有三種方式:(1) X,(2) IDLE (3)不改變
//注:因為模擬時“X”視為“don’t care”,所以賦值“X”適合 debug
//而實現時,賦值“IDLE”或者“0”
ERROR_out;
case (CS)
IDLE: begin
IDLE_out;
if (~i1) NS = IDLE;
if (i1 && i2) NS = S1;
if (i1 && ~i2) NS = ERROR;// 裡面判斷條件一定要包含所有情況(即 i1 和
i2 的四種情況)!可以用 else 保證包含完全。
end
S1: begin
S1_out;
if (~i2) NS = S1;
if (i2 && i1) NS = S2;
if (i2 && (~i1)) NS = ERROR;
end
S2: begin
S2_out;
if (i2) NS = S2;
if (~i2 && i1) NS = IDLE;
if (~i2 && (~i1)) NS = ERROR;
end
ERROR: begin
ERROR_out;
if (i1) NS = ERROR;
if (~i1) NS = IDLE;
end
endcase
end
task S1_out;//對於輸出一般用組合邏輯描述,比較簡便的方法是用 task 將輸出封裝起來。
{o1,o2,err} = 3'b100;
Endtask
三段式(一)
圖1-7 三段式狀態機示例
module fsm_cc8_3r(output reg y1, y2, y3,input jmp, go, sk0, sk1, clk, rst_n);
parameter S0 = 4'b0000,
S1 = 4'b0001,
S2 = 4'b0010,
S3 = 4'b0011,
S4 = 4'b0100,
S5 = 4'b0101,
S6 = 4'b0110,
S7 = 4'b0111,
S8 = 4'b1000,
S9 = 4'b1001;
reg [3:0] state, next;
//第一個程序,同步時序 always 塊,格式化描述次態暫存器遷移到現態暫存器
always @(posedge clk or negedge rst_n) //非同步復位
if (!rst_n)
state <= S0;
else
state <= next; //注意,使用的是非阻塞賦值
//第二個程序,組合邏輯塊,判斷狀態轉移條件,電平觸發,敏感列表要完備
always @(state or jmp or go or sk0 or sk1)
begin
next = 4'bS0; //要初始化,使得系統復位後能進入正確的狀態,和 case 中的 default 作用相當,但
//不完全等同於把其放在 default 裡賦值。因為根據順序執行語句,這裡對所有未
//在case 語句中賦值的變數賦值。
case (state) //每一狀態的條件判斷要完備!
S0 : if (!go) next = S0; //阻塞賦值
else if (jmp) next = S3;
else next = S1;
S1 : if (jmp) next = S3;
else next = S2;
S2 : if (jmp) next = S3;
else next = S9;
S3 : if (jmp) next = S3;
else next = S4;
S4 : if (jmp) next = S3;
else if (sk0 && !jmp) next = S6;
else next = S5;
S5 : if (jmp) next = S3;
else if (!sk1 && !sk0 && !jmp) next = S6;
else if (!sk1 && sk0 && !jmp) next = S7;
else if ( sk1 && !sk0 && !jmp) next = S8;
else next = S9;
S6 : if (jmp) next = S3;
else if (go && !jmp) next = S7;
else next = S6;
S7 : if (jmp) next = S3;
else next = S8;
S8 : if (jmp) next = S3;
else next = S9;
S9 : if (jmp) next = S3;
else next = S0;
endcase
end
//第三個程序,同步時序 always 模組,格式化描述次態暫存器輸出
always @(posedge clk or negedge rst_n)
if (!rst_n)
begin //初始化
y1 <= 1'b0;
y2 <= 1'b0;
y3 <= 1'b0;
end
else begin //可以防止 latch,如上述。
y1 <= 1'b0;
y2 <= 1'b0;
y3 <= 1'b0;
case (next) //注意是下一個狀態,即預判
S0, S2, S4, S5 : ; // default outputs
S7 : y3 <= 1'b1; //注意是非阻塞邏輯
S1 : y2 <= 1'b1;
S3 : begin
y1 <= 1'b1;
y2 <= 1'b1;
end
S8 : begin
y2 <= 1'b1;
y3 <= 1'b1;
end
S6, S9 : begin
y1 <= 1'b1;
y2 <= 1'b1;
y3 <= 1'b1;
end
endcase
end
endmodule
三段式格式 (二)–onehot 編碼
parameter S0 = 0, S1 =1, S2 =2, S3 =3, S4 =4, S5= 5, S6 = 6, S7 = 7, S8 = 8, S9 =9;
reg [9:0] current_state, next_state;
//第一個程序,同步時序 always 模組,格式化描述次態暫存器遷移到現態暫存器
always @ (posedge clk or negedge rst_n) //非同步復位
if(!rst_n)
begin
current_state <=0;
current_state[S0] <=1’b1;
end
else
current_state <= next_state; //注意,使用的是非阻塞賦值
//第二個程序,組合邏輯 always 塊,判斷狀態轉移條件, onehot 需要加綜合約束
full case
always @ (current_state or input1 or input2 ) //電平觸發,包括所有組合邏輯輸入
begin
next_state = 0; //初始化(0 或 IDLE),使系統復位後能進入正確的狀態
case(1’b1) // synthesis parallel_case full_case
current_state[S0]: if(...)
next_state [S1]= 1’b1; //阻塞賦值
current_state[S1]: if(...)
next_state [S2]= 1’b1; //阻塞賦值
...
endcase
end
//第三個程序,同步時序 always 模組,格式化描述次態暫存器輸出
always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
Out1<= 1’b0;
Out2<= 1’b0;
Out3<= 1’b0;
end
else begin
Out1<= 1’b0;
Out2<= 1’b0;
Out3<= 1’b0;
case(1’b1) //注意是下一個狀態,即預判
next_state [S0]: out1 <= 1'b1; //注意是非阻塞邏輯
next_state [S1]: out2 <= 1'b1;
…
endcase //注意,沒有 default 選項了
end
總結
有限狀態機是實現高效率、高可靠性數字系統的重要全知途徑,使用Verilog HDL描述有限狀態機時可以有不同的狀態編碼方式和描述風格,實際應用中應根據具體情況和要求來選擇,編碼方式的選擇跟器件結構和狀態數目有關,描述風格則推薦使用兩段式和三段式,應該儘量避免使用一段式。