數電基礎
狀態機的基礎知識依然強烈推薦mooc上華科的數字電路與邏輯設計,yyds!但是數電基礎一定要和實際應用結合起來,理論才能發揮真正的價值。我們知道FPGA是并行執行的,如果我們想要處理具有前后順序的事件就需要引入狀態機。
狀態機有同步和異步之分,同步是指狀態機的狀態跳轉是在時鐘的作用下進行的,異步是指狀態跳轉不是由統一的時鐘控制。同步有限狀態機分為Moore型和 Mealy型 ,Moore型的輸出只與當前狀態有關,而Mealy型的輸出與當前狀態和輸入有關。
每一個狀態都代表一個事件,從初始狀態出發,不同的輸入可能引發不同的下一個狀態,并獲得不同的輸出(輸出不是必須的,但一定有輸入)。
設計規劃
我們的目標是用狀態機實現一個簡單的可樂販賣機系統。具體功能是:可樂機每次只能投入1枚1元硬幣,且每瓶可樂賣3元錢,即投入3個硬幣就可以讓可樂機出可樂,如果投幣不夠3元想放棄投幣需要按復位鍵,否則之前投入的錢不能退回。
Moore型用狀態圖來表示:
初始狀態是IDLE,如果輸入0枚跳轉到自身狀態,輸入1枚跳轉到ONE狀態,跳轉到TWO狀態也是同理,再輸入0枚跳轉到自身狀態,輸入1枚跳轉到初始狀態并輸出1表示可樂售賣成功,其間任意狀態復位有效都要回到初始狀態并退錢。
Mealy型用狀態圖來表示:
有四種狀態,到TWO狀態都與前面一致,TWO狀態時投1枚跳轉到THREE狀態,THREE狀態如果輸入0枚就售出可樂且跳轉到初始狀態,輸入1枚就售出可樂且跳轉到ONE狀態。
編寫代碼
module simple_fsm
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire pi_money ,
output reg po_cola
);
//parameter define
parameter IDLE = 3'b001;
parameter ONE = 3'b010;
parameter TWO = 3'b100;
//reg define
reg [2:0] state ;
//第一段狀態機,描述當前狀態state如何根據輸入跳轉到下一狀態
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE; //任何情況下只要按復位就回到初始狀態
else case(state)
IDLE : if(pi_money == 1'b1) //判斷輸入情況
state <= ONE;
else
state <= IDLE;
ONE : if(pi_money == 1'b1)
state <= TWO;
else
state <= ONE;
TWO : if(pi_money == 1'b1)
state <= IDLE;
else
state <= TWO;
default: state <= IDLE;
endcase
//第二段狀態機,描述當前狀態state和輸入pi_money如何影響po_cola輸出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_cola <= 1'b0;
else if((state == TWO) && (pi_money == 1'b1))
po_cola <= 1'b1;
else
po_cola <= 1'b0;
endmodule
輸入輸出定義
參數定義:狀態要用參數來表示,為了區分不同的狀態,我們需要給 狀態編碼 ,這里使用了獨熱碼,只有一位為1其余位為0。事實上這里使用二進制或格雷碼也可以表示。二進制編碼使用2位位寬就可以表示4種狀態(有一種狀態未使用)。使用獨熱碼的原因是:獨熱碼每個狀態只有1bit是不同的,所以在執行(state == TWO)這條語句時,綜合器會識別出這是一個比較器,而因為只有1比特為1,所以綜合器會進行智能優化為(state[2] == 1’ b1),這就相當于把之前3比特的比較器變為了1比特的比較器,大大節省了組合邏輯資源。而我們FPGA中組合邏輯資源相對較少,而寄存器資源較多,所以犧牲寄存器資源來節省組合邏輯資源。狀態很多時可以采用格雷碼進行編碼,位數少,且相鄰狀態轉換時只有一位發生變化,相當于二進制和獨熱碼的折衷處理。
采用新兩段式,第一段用于定義狀態跳轉,第二段定義輸出。這種新的寫法現在在不同綜合器中都可以被識別出來,既消除了組合邏輯可能產生的毛刺,又減小了代碼量,僅僅根據狀態轉移圖就能實現。如果有多個輸出時第二段狀態機就可以分為多個always塊來表達,但理論上仍屬于新二段狀態機,所以幾段式狀態機并不是由always塊的數量簡單決定的)。
定義狀態跳轉 :狀態變化的條件是時鐘上升沿和復位。首先復位時,狀態恢復到初始狀態。沒有復位時,需要定義每個狀態的跳轉。這里采用了case語句,復習一下:case語句檢查表達式與列表中其他表達式是否匹配并對應分支。這里是檢查state與IDLE,ONE,TWO匹配,當處于三種狀態時,都有pi_money=0或1兩種情況,按照之前討論的跳轉狀態去設置。注意case語句如果不加default可能出現latch。
定義輸出 :復位有效時,輸出為0;只有一種情況輸出為1,就是有足夠買到可樂的錢時,也就是狀態為TWO且投入1塊錢;其他時候輸出為0。
編寫testbench
`timescale 1ns/1ns
module tb_simple_fsm();
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg pi_money ;
//wire define
wire po_cola;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
//sys_clk:模擬系統時鐘,每10ns電平翻轉一次,周期為20ns,頻率為50MHz
always #10 sys_clk = ~sys_clk;
//pi_money:產生輸入隨機數,模擬投幣1元的情況
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_money <= 1'b0;
else
pi_money <= {$random} % 2; //取模求余數,產生非負隨機數0、1
//將RTL模塊中的內部信號引入到Testbench模塊中進行觀察
wire [2:0] state = simple_fsm_inst.state;
initial begin
$timeformat(-9, 0, "ns", 6);
$monitor("@time %t: pi_money=%b state=%b po_cola=%b",
$time, pi_money, state, po_cola);
end
//------------------------simple_fsm_inst------------------------
simple_fsm simple_fsm_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.pi_money (pi_money ),
.po_cola (po_cola )
);
endmodule
輸入輸出定義、初始化、時鐘產生、隨機數產生、打印結果、實例化都是我們非常熟悉的內容了。需要補充說明的是第29行,重新定義了一個state(名稱盡量與rtl中一致),將實例化模塊中的state與其等效,這樣就可以在transcript中打印并觀察到。因為transcript中觀察到打印信息只能是RTL的端口信號,而state是內部信號(端口信號是輸入輸出時鐘復位,中間信號是內部信號)。
對比波形
狀態跳轉與預期一致
應用拓展
前面的可樂販賣機只能投1元的,我們來看看投0.5元的狀態機:可樂定價為2.5元一瓶,可投入0.5元、1元硬幣,投幣不夠2.5元需要按復位鍵退回錢款,投幣超過2.5元需找零。
看似很復雜,實際只是變成兩種輸入,三種輸出,五種狀態。輸入有0.5,1元;輸出有不找零不出可樂,不找零出可樂,找零并出可樂;狀態有0,0.5,1,1.5,2,到2塊之后輸入0.5就到0的狀態并出可樂,輸入1就到0的狀態出可樂并找零。
-
FPGA
+關注
關注
1629文章
21759瀏覽量
604283 -
狀態機
+關注
關注
2文章
492瀏覽量
27576 -
狀態圖
+關注
關注
0文章
11瀏覽量
7341
發布評論請先 登錄
相關推薦
評論