FPGA的特點是并行執行,但如果需要處理一些具有前后順序的事件,就需要使用狀態機。狀態機是一種用于處理具有前后順序的事件的計算機模型,包含現態、條件、動作和次態四個要素,它可以將一個復雜的控制流程分解成多個互相獨立的狀態,從而簡化設計過程并提高了系統的可靠性和性能。本文將對FPGA狀態機進行詳細介紹,幫助大家了解狀態機的設計和應用。
一、FPGA狀態機基礎
1、基礎概念
FPGA狀態機是一種能夠描述對象在運行周期內的所有狀態,以及從一個狀態到另一個狀態轉換的過程的抽象模型。狀態機可歸納為4個要素,即現態、條件、動作、次態。
①現態:當前所處的狀態。
②條件:當一個條件被滿足,將會觸發一個動作,或者執行一次運行狀態的變化。
③動作:條件滿足后執行的動作。動作不是必需的,也可以直接遷移到新狀態而不進行任何動作。
④次態:條件滿足后要跳轉到的新狀態。其中,“次態”是相對于“現態”而言的,一旦被跳轉后,“次態”就轉變成新的“現態”了。
2、狀態機分類
通常情況下,FPGA狀態機一般有兩種類型:
- Moore型狀態機:下一狀態只由當前狀態決定 。
- Mealy 型狀態機:下一狀態不但與當前狀態有關,還與當前輸入值有關 。
由于Mealy型狀態機的輸出與輸入有關,輸出信號很容易出現毛刺,所以一般采用Moore型狀態機。
(1)Mealy狀態機
輸出邏輯不但取決于當前“狀態”還取決于“輸入”,如圖所示。
(2)Moore狀態機
輸出邏輯僅僅取決于當前狀態,且與當前時刻的輸入無關,如圖所示。
二、FPGA狀態機實現方式
FPGA狀態機的描述方式主要分為3種,分別是一段式、兩段式、三段式。
1、一段式狀態機
一段式狀態機使用1個always塊,把狀態跳轉和寄存器輸出邏輯都寫在一起,其輸出是寄存器輸出,無毛刺,但是這種方式代碼較混亂,邏輯不清晰,難于修改和調試,應該盡量避免使用。
下面給出一個一段式的Mealy狀態機示例:
input clk,
input rst_n,
input [1:0] inp,
output reg outp
);
// 定義狀態
localparam STATE_0 = 0,
STATE_1 = 1,
STATE_2 = 2,
STATE_3 = 3;
// 定義狀態寄存器和初始狀態
reg [1:0] state_r;
// 初始化狀態寄存器
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
state_r<= STATE_0;
end else begin
case (state_reg)
STATE_0: begin
if (inp == 2'b00) begin
state_r <= STATE_0;
outp <= 0;
end else if (inp == 2'b01) begin
state_r <= STATE_1;
outp <= 1;
end else if (inp == 2'b10) begin
state_r <= STATE_2;
outp <= 0;
end else begin
state_r <= STATE_3;
outp <= 1;
end
end
STATE_1: begin
if (inp == 2'b00) begin
state_r <= STATE_1;
outp <= 1;
end else if (inp == 2'b01) begin
state_r <= STATE_2;
outp <= 0;
end else if (inp == 2'b10) begin
state_r <= STATE_3;
outp <= 1;
end else begin
state_r <= STATE_0;
outp <= 0;
end
end
STATE_2: begin
if (inp == 2'b00) begin
state_r <= STATE_2;
outp <= 0;
end else if (inp == 2'b01) begin
state_r <= STATE_3;
outp <= 1;
end else if (inp == 2'b10) begin
state_r <= STATE_0;
outp <= 0;
end else begin
state_r <= STATE_1;
outp <= 1;
end
end
STATE_3: begin
if (inp == 2'b00) begin
state_r <= STATE_3;
outp <= 1;
end else if (inp == 2'b01) begin
state_r <= STATE_0;
outp <= 0;
end else if (inp == 2'b10) begin
state_r <= STATE_1;
outp <= 1;
end else begin
state_reg <= STATE_2;
outp <= 0;
end
end
endcase
end
end
endmodule
2、二段式狀態機
二段式狀態機使用2個always塊,都是時序邏輯,其中一個always塊用于寫狀態機的狀態跳轉邏輯,另一個always塊用于寫當前狀態下的寄存器輸出邏輯。這種方式邏輯代碼清晰,易于調試和理解,是比較推薦的一個方式。
下面給出一個二段式的Moore狀態機示例:
input clk,
input rst_n,
output reg out_reg
);
// 狀態寄存器和下一個狀態寄存器
reg [1:0] state_r;
// 狀態定義
parameter IDLE = 2'b00;
parameter STATE1 = 2'b01;
parameter STATE2 = 2'b10;
parameter STATE3 = 2'b11;
// always @(posedge clk or negedge rst_n) 時序邏輯代碼塊,實現狀態跳轉邏輯
always@(posedge clk or negedge rst_n) begin
if(~rst_n) begin
state_r <= IDLE;
end else begin
case(state_r)
IDLE: begin
state_r <= STATE1;
end
STATE1: begin
state_r <= STATE2;
end
STATE2: begin
state_r <= STATE3;
end
STATE3: begin
state_r <= IDLE;
end
endcase
end
end
// always @(*) 時序邏輯代碼塊,實現狀態輸出邏輯
always@(posedge clk or negedge rst_n) begin
if(~rst_n) begin
out_reg <= 1'b0;
end else begin
case(state_r)
IDLE: begin
out_reg <= 1'b0;
end
STATE1: begin
out_reg <= 1'b1;
end
STATE2: begin
out_reg <= 1'b1;
end
STATE3: begin
out_reg <= 1'b0;
end
endcase
end
end
endmodule
3、三段式狀態機
三段式狀態機使用3個always塊,其中一個組合always塊用于寫狀態機的狀態跳轉邏輯,一個時序always塊用于緩存狀態寄存器,另一個always塊用于寫當前狀態下的寄存器輸出邏輯。這種方式邏輯代碼清晰,易于調試和理解,也是比較推薦的一個方式。
input clk,
input rst_n,
input [1:0] inp,
output reg outp
);
// 定義狀態
localparam STATE_0 = 0,
STATE_1 = 1,
STATE_2 = 2,
STATE_3 = 3;
// 定義狀態寄存器和初始狀態
reg [1:0] state_r, next_state ;
// 定義狀態寄存器
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
state_r <= STATE_0;
end else begin
state_r <= next_state;
end
end
// 定義狀態轉移邏輯
always @(*) begin
case (state_r)
STATE_0: begin
if (inp == 2'b00) begin
next_state = STATE_0;
end else if (inp == 2'b01) begin
next_state = STATE_1;
end else if (inp == 2'b10) begin
next_state = STATE_2;
end else begin
next_state = STATE_3;
end
end
STATE_1: begin
if (inp == 2'b00) begin
next_state = STATE_1;
end else if (inp == 2'b01) begin
next_state = STATE_2;
end else if (inp == 2'b10) begin
next_state = STATE_3;
end else begin
next_state = STATE_0;
end
end
STATE_2: begin
if (inp == 2'b00) begin
next_state = STATE_2;
end else if (inp == 2'b01) begin
next_state = STATE_3;
end else if (inp == 2'b10) begin
next_state = STATE_0;
end else begin
next_state = STATE_1;
end
end
STATE_3: begin
if (inp == 2'b00) begin
next_state = STATE_3;
end else if (inp == 2'b01) begin
next_state = STATE_0;
end else if (inp == 2'b10) begin
next_state = STATE_1;
end else begin
next_state = STATE_2;
end
end
endcase
end
// 定義輸出邏輯
always @(*) begin
case (state_r)
STATE_0: outp = 0;
STATE_1: outp = 1;
STATE_2: outp = 0;
STATE_3: outp = 1;
endcase
end
endmodule
注意:組合邏輯代碼中,if語句和case語句必須寫滿,否則容易形成latch,導致實際運行出問題。
三、狀態機的編碼方式
1、獨熱碼
獨熱碼(One-hot)是一種狀態編碼方式,其特點是對于任意給定的狀態,狀態寄存器中只有1位為1,其余位都為0。使用獨熱碼可以簡化譯碼邏輯電路,因為狀態機只需對寄存器中的一位進行譯碼,同時可用省下的面積抵消額外觸發器占用的面積。相比于其他類型的有限狀態機,加入更多的狀態時,獨熱碼的譯碼邏輯并不會變得更加復雜,速度僅取決于到某特定狀態的轉移數量。
此外,獨熱碼還具有諸如設計簡單、修改靈活、易于綜合和調試等優點。但值得注意的是,相對于二進制碼,獨熱碼速度更快但占用面積較大。
input clk,
output reg [3:0] state_out
);
localparam STATE_A = 4'b0001;
localparam STATE_B = 4'b0010;
localparam STATE_C = 4'b0100;
localparam STATE_D = 4'b1000;
reg [3:0] current_state, next_state;
always @(posedge clk) begin
current_state <= next_state; // 當時鐘上升沿到來時更新狀態
end
always @(*) begin
case (current_state)
STATE_A: next_state = STATE_B;
STATE_B: next_state = STATE_C;
STATE_C: next_state = STATE_D;
STATE_D: next_state = STATE_A;
default: next_state = STATE_A; // 默認情況下返回初始狀態
endcase
end
assign state_out = current_state; // 將當前狀態作為輸出
endmodule
2、格雷碼
格雷碼是一種相鄰的兩個碼組之間僅有一位不同的編碼方式。在格雷碼中,相鄰的兩個碼組之間僅有一位不同,這種編碼方式可以用于實現相鄰的兩個狀態之間只有一位不同的狀態機;
FPGA 中的狀態機通常需要高速運行,因此使用格雷碼可以減少狀態轉換的開銷,并提高時序性能。
input clk,
output reg [3:0] state_out
);
localparam G0 = 4'b0000;
localparam G1 = 4'b0001;
localparam G2 = 4'b0011;
localparam G3 = 4'b0010;
reg [3:0] current_state, next_state;
always @(posedge clk) begin
current_state <= next_state; // 當時鐘上升沿到來時更新狀態
end
always @(*) begin
case (current_state)
G0: next_state = G1;
G1: next_state = G3;
G2: next_state = G0;
G3: next_state = G2;
default: next_state = G0; // 默認情況下返回初始狀態
endcase
end
assign state_out = current_state; // 將當前狀態作為輸出
endmodule
3、普通二進制碼
FPGA狀態機可以用普通二進制碼表示,不同狀態按照二進制數累加表示,是常用的一種方式,仿真調試時,狀態顯示清晰,易于理解代碼。
input clk,
output reg [1:0] state_out
);
localparam STATE_A = 2'b00;
localparam STATE_B = 2'b01;
localparam STATE_C = 2'b10;
reg [1:0] current_state, next_state;
always @(posedge clk) begin
current_state <= next_state; // 當時鐘上升沿到來時更新狀態
end
always @(*) begin
case (current_state)
STATE_A: next_state = STATE_B;
STATE_B: next_state = STATE_C;
STATE_C: next_state = STATE_A;
default: next_state = STATE_A; // 默認情況下返回初始狀態
endcase
end
assign state_out = current_state; // 將當前狀態作為輸出
endmodule
4、格雷碼與普通二進制碼互轉
- 二進制碼:一個n位的二進制碼可以表示2^n種狀態,它有2^(n-1)個相鄰狀態之間只有一個位不同。
- 格雷碼:一個n位的格雷碼可以表示2^n種狀態,它有2^(n-1)個相鄰狀態之間只有一位變化。
(1)二進制碼轉換成格雷碼
假設當前的狀態用二進制碼表示為B,那么它所對應的格雷碼G可以按照以下方式計算得出:
G = B xor (B >> 1)
其中,">>"表示右移操作,"xor"表示異或操作。具體來說,我們先將B右移一位,再與原來的B進行異或運算,就可以得到對應的格雷碼G。
(2)格雷碼轉換成二進制碼
假設當前的狀態用格雷碼表示為G,那么它所對應的二進制碼B可以按照以下方式計算得出:
B = G xor (G >> 1)
與二進制碼轉換成格雷碼的方法類似,我們先將G右移一位,再與原來的G進行異或運算,就可以得到對應的二進制碼B。
個,下面給出“4位格雷碼與4位二進制碼轉換”的示例代碼:
input [3:0] bin,
output reg [3:0] gray
);
always @* begin
gray[0] = bin[0];
gray[1] = bin[1] ^ bin[0];
gray[2] = bin[2] ^ bin[1];
gray[3] = bin[3] ^ bin[2];
end
endmodule
注意用來轉換格雷碼時,如果輸入的格雷碼不是有效的格雷碼,輸出的結果將會是無意義的。
四、總結
以上總結了FPGA狀態機中有關的知識點,大家可以參考下,建議狀態機的寫法一般用二段式或三段式,代碼邏輯清晰,易于理解和調試,同時需要注意always@(*)塊中的組合邏輯,使用if和case語句都要寫滿,否則綜合后容易形成latch,導致上板與仿真結果不一致。
評論