FPGA的特點是并行執(zhí)行,但如果需要處理一些具有前后順序的事件,就需要使用狀態(tài)機。狀態(tài)機是一種用于處理具有前后順序的事件的計算機模型,包含現(xiàn)態(tài)、條件、動作和次態(tài)四個要素,它可以將一個復雜的控制流程分解成多個互相獨立的狀態(tài),從而簡化設(shè)計過程并提高了系統(tǒng)的可靠性和性能。本文將對FPGA狀態(tài)機進行詳細介紹,幫助大家了解狀態(tài)機的設(shè)計和應用。
一、FPGA狀態(tài)機基礎(chǔ)
1、基礎(chǔ)概念
FPGA狀態(tài)機是一種能夠描述對象在運行周期內(nèi)的所有狀態(tài),以及從一個狀態(tài)到另一個狀態(tài)轉(zhuǎn)換的過程的抽象模型。狀態(tài)機可歸納為4個要素,即現(xiàn)態(tài)、條件、動作、次態(tài)。
①現(xiàn)態(tài):當前所處的狀態(tài)。
②條件:當一個條件被滿足,將會觸發(fā)一個動作,或者執(zhí)行一次運行狀態(tài)的變化。
③動作:條件滿足后執(zhí)行的動作。動作不是必需的,也可以直接遷移到新狀態(tài)而不進行任何動作。
④次態(tài):條件滿足后要跳轉(zhuǎn)到的新狀態(tài)。其中,“次態(tài)”是相對于“現(xiàn)態(tài)”而言的,一旦被跳轉(zhuǎn)后,“次態(tài)”就轉(zhuǎn)變成新的“現(xiàn)態(tài)”了。
2、狀態(tài)機分類
通常情況下,F(xiàn)PGA狀態(tài)機一般有兩種類型:
- Moore型狀態(tài)機:下一狀態(tài)只由當前狀態(tài)決定 。
- Mealy 型狀態(tài)機:下一狀態(tài)不但與當前狀態(tài)有關(guān),還與當前輸入值有關(guān) 。
由于Mealy型狀態(tài)機的輸出與輸入有關(guān),輸出信號很容易出現(xiàn)毛刺,所以一般采用Moore型狀態(tài)機。
(1)Mealy狀態(tài)機
輸出邏輯不但取決于當前“狀態(tài)”還取決于“輸入”,如圖所示。

(2)Moore狀態(tài)機
輸出邏輯僅僅取決于當前狀態(tài),且與當前時刻的輸入無關(guān),如圖所示。

二、FPGA狀態(tài)機實現(xiàn)方式
FPGA狀態(tài)機的描述方式主要分為3種,分別是一段式、兩段式、三段式。
1、一段式狀態(tài)機
一段式狀態(tài)機使用1個always塊,把狀態(tài)跳轉(zhuǎn)和寄存器輸出邏輯都寫在一起,其輸出是寄存器輸出,無毛刺,但是這種方式代碼較混亂,邏輯不清晰,難于修改和調(diào)試,應該盡量避免使用。
下面給出一個一段式的Mealy狀態(tài)機示例:
input clk,
input rst_n,
input [1:0] inp,
output reg outp
);
// 定義狀態(tài)
localparam STATE_0 = 0,
STATE_1 = 1,
STATE_2 = 2,
STATE_3 = 3;
// 定義狀態(tài)寄存器和初始狀態(tài)
reg [1:0] state_r;
// 初始化狀態(tài)寄存器
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、二段式狀態(tài)機
二段式狀態(tài)機使用2個always塊,都是時序邏輯,其中一個always塊用于寫狀態(tài)機的狀態(tài)跳轉(zhuǎn)邏輯,另一個always塊用于寫當前狀態(tài)下的寄存器輸出邏輯。這種方式邏輯代碼清晰,易于調(diào)試和理解,是比較推薦的一個方式。
下面給出一個二段式的Moore狀態(tài)機示例:
input clk,
input rst_n,
output reg out_reg
);
// 狀態(tài)寄存器和下一個狀態(tài)寄存器
reg [1:0] state_r;
// 狀態(tài)定義
parameter IDLE = 2'b00;
parameter STATE1 = 2'b01;
parameter STATE2 = 2'b10;
parameter STATE3 = 2'b11;
// always @(posedge clk or negedge rst_n) 時序邏輯代碼塊,實現(xiàn)狀態(tài)跳轉(zhuǎ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 @(*) 時序邏輯代碼塊,實現(xiàn)狀態(tài)輸出邏輯
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、三段式狀態(tài)機
三段式狀態(tài)機使用3個always塊,其中一個組合always塊用于寫狀態(tài)機的狀態(tài)跳轉(zhuǎn)邏輯,一個時序always塊用于緩存狀態(tài)寄存器,另一個always塊用于寫當前狀態(tài)下的寄存器輸出邏輯。這種方式邏輯代碼清晰,易于調(diào)試和理解,也是比較推薦的一個方式。
input clk,
input rst_n,
input [1:0] inp,
output reg outp
);
// 定義狀態(tài)
localparam STATE_0 = 0,
STATE_1 = 1,
STATE_2 = 2,
STATE_3 = 3;
// 定義狀態(tài)寄存器和初始狀態(tài)
reg [1:0] state_r, next_state ;
// 定義狀態(tài)寄存器
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
// 定義狀態(tài)轉(zhuǎn)移邏輯
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,導致實際運行出問題。
三、狀態(tài)機的編碼方式
1、獨熱碼
獨熱碼(One-hot)是一種狀態(tài)編碼方式,其特點是對于任意給定的狀態(tài),狀態(tài)寄存器中只有1位為1,其余位都為0。使用獨熱碼可以簡化譯碼邏輯電路,因為狀態(tài)機只需對寄存器中的一位進行譯碼,同時可用省下的面積抵消額外觸發(fā)器占用的面積。相比于其他類型的有限狀態(tài)機,加入更多的狀態(tài)時,獨熱碼的譯碼邏輯并不會變得更加復雜,速度僅取決于到某特定狀態(tài)的轉(zhuǎn)移數(shù)量。
此外,獨熱碼還具有諸如設(shè)計簡單、修改靈活、易于綜合和調(diào)試等優(yōu)點。但值得注意的是,相對于二進制碼,獨熱碼速度更快但占用面積較大。
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; // 當時鐘上升沿到來時更新狀態(tài)
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; // 默認情況下返回初始狀態(tài)
endcase
end
assign state_out = current_state; // 將當前狀態(tài)作為輸出
endmodule
2、格雷碼
格雷碼是一種相鄰的兩個碼組之間僅有一位不同的編碼方式。在格雷碼中,相鄰的兩個碼組之間僅有一位不同,這種編碼方式可以用于實現(xiàn)相鄰的兩個狀態(tài)之間只有一位不同的狀態(tài)機;
FPGA 中的狀態(tài)機通常需要高速運行,因此使用格雷碼可以減少狀態(tài)轉(zhuǎn)換的開銷,并提高時序性能。
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; // 當時鐘上升沿到來時更新狀態(tài)
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; // 默認情況下返回初始狀態(tài)
endcase
end
assign state_out = current_state; // 將當前狀態(tài)作為輸出
endmodule
3、普通二進制碼
FPGA狀態(tài)機可以用普通二進制碼表示,不同狀態(tài)按照二進制數(shù)累加表示,是常用的一種方式,仿真調(diào)試時,狀態(tài)顯示清晰,易于理解代碼。
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; // 當時鐘上升沿到來時更新狀態(tài)
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; // 默認情況下返回初始狀態(tài)
endcase
end
assign state_out = current_state; // 將當前狀態(tài)作為輸出
endmodule
4、格雷碼與普通二進制碼互轉(zhuǎn)
- 二進制碼:一個n位的二進制碼可以表示2^n種狀態(tài),它有2^(n-1)個相鄰狀態(tài)之間只有一個位不同。
- 格雷碼:一個n位的格雷碼可以表示2^n種狀態(tài),它有2^(n-1)個相鄰狀態(tài)之間只有一位變化。
(1)二進制碼轉(zhuǎn)換成格雷碼
假設(shè)當前的狀態(tài)用二進制碼表示為B,那么它所對應的格雷碼G可以按照以下方式計算得出:
G = B xor (B >> 1)
其中,">>"表示右移操作,"xor"表示異或操作。具體來說,我們先將B右移一位,再與原來的B進行異或運算,就可以得到對應的格雷碼G。
(2)格雷碼轉(zhuǎn)換成二進制碼
假設(shè)當前的狀態(tài)用格雷碼表示為G,那么它所對應的二進制碼B可以按照以下方式計算得出:
B = G xor (G >> 1)
與二進制碼轉(zhuǎn)換成格雷碼的方法類似,我們先將G右移一位,再與原來的G進行異或運算,就可以得到對應的二進制碼B。
個,下面給出“4位格雷碼與4位二進制碼轉(zhuǎn)換”的示例代碼:
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
注意用來轉(zhuǎn)換格雷碼時,如果輸入的格雷碼不是有效的格雷碼,輸出的結(jié)果將會是無意義的。
四、總結(jié)
以上總結(jié)了FPGA狀態(tài)機中有關(guān)的知識點,大家可以參考下,建議狀態(tài)機的寫法一般用二段式或三段式,代碼邏輯清晰,易于理解和調(diào)試,同時需要注意always@(*)塊中的組合邏輯,使用if和case語句都要寫滿,否則綜合后容易形成latch,導致上板與仿真結(jié)果不一致。
電子發(fā)燒友App






































評論