“硬件設計很講究並行設計思想,雖然用Verilog描述的電路大都是並行實現的,但是對於實際的工程應用,往往需要讓硬件來實現一些具有一定順序的工作,這就要用到狀態機思想。什么是狀態機呢?簡單的說,就是通過不同的狀態遷移來完成一些特定的順序邏輯。硬件的並行性決定了用Verilog描述的硬件實現(臂如不同的always語句)都是並行執行的,那么如果希望分多個時間完成一個任務,怎么辦?也許可以用多個使能信號來銜接多個不同的模塊,但是這樣做多少顯得繁瑣。狀態機的提出會大大簡化這一工作。”
——特權同學《深入淺出玩轉FPGA》
一、狀態機分類:
1.Moore型:狀態機的狀態變化僅和當前狀態有關(特權同學《深入淺出玩轉FPGA》);時序邏輯電路的輸出只取決於當前狀態(夏宇聞《Verilog數字系統設計》)。設計高速電路時常用此類狀態機,把狀態變化直接用作輸出。
2.Mealy型:狀態機的狀態變化不僅與當前的狀態有關,還取決於當前的輸入條件(特權同學《深入淺出玩轉FPGA》);時序邏輯的輸出不但取決於狀態還取決於輸入(夏宇聞《Verilog數字系統設計》)。平常使用較多的是此類狀態機。
“其實這幾種狀態機之間,只要做一些改變,便可以從一種形式轉變為另一種形式。把狀態機精確的分為這類或那類,其實並不重要,重要的是設計者如何把握輸出的結構能滿足設計的整體目標,包括定時的准確性和靈活性。”
——夏宇聞《Verilog數字系統設計》
二、狀態機編碼:
狀態機的參數定義采用的都是獨熱碼,和格雷碼相比,雖然獨熱碼多用了觸發器,但所用組合電路可以省一些,因而使電路的速度和可靠性有顯著提高,而總的單元數並無顯著增加。采用獨熱編碼后有了多余的狀態,就有一些不可達到的狀態。為此在case語句的最后需要增加default分支向。這可以用默認項表示該項,也可以用確定項表示,以確保回到初始狀態。一般綜合器都可以通過綜合指令的控制來合理地處理默認項。
三、實例分析
狀態機一般有三種不同的寫法,即一段式、兩段式和三段式的狀態機寫法,他們在速度、面積、代碼可維護性等各個方面互有優劣,不要對任何一種寫法給出“一棍子打死”的定論。手頭上剛好有一個狀態機的例子,借此記錄一下三種狀態機的Verilog寫法。
要求:
售貨機里有價值4元的脈動飲料,支持1元和2元硬幣。請設計一個狀態機,檢測投入的硬幣,當累計投入幣值大於等於脈動價格時,售貨機自動找零並彈出1瓶脈動飲料。硬幣和商品都是一個一個的進出,不會出現一次性投很多個硬幣彈出很多瓶脈動的情況。
信號 | 含義 |
clk | 時鍾信號 |
rst_n | 復位信號 |
in | 輸入信號,幣值,有1和2兩種,投錢 |
out | 輸出信號,幣值,有1和2兩種,找零 |
out_vld | 輸出信號,脈動,為1則輸出1瓶脈動 |
狀態轉移圖:
根據要求,我們先把狀態轉移圖畫出來,繪畫軟件:Visio,如果沒有安裝也可以用wps自帶應用的“流程圖”功能:
testbench:
1 `timescale 1ns/1ps //時間精度 2 `define Clock 20 //時鍾周期 3 4 module FSM_3_tb; 5 //--------------------< 端口 >------------------------------------------ 6 reg clk ; 7 reg rst_n ; 8 reg [1:0] in ; 9 wire [1:0] out ; 10 wire out_vld ; 11 12 //---------------------------------------------------------------------- 13 //-- 模塊例化 14 //---------------------------------------------------------------------- 15 FSM_3 u_FSM_3 16 ( 17 .clk (clk ), 18 .rst_n (rst_n ), 19 .in (in ), 20 .out (out ), 21 .out_vld (out_vld ) 22 ); 23 24 //---------------------------------------------------------------------- 25 //-- 狀態機名稱查看器 26 //---------------------------------------------------------------------- 27 localparam S0 = 4'b0001 ; 28 localparam S1 = 4'b0010 ; 29 localparam S2 = 4'b0100 ; 30 localparam S3 = 4'b1000 ; 31 //2字符16位 32 reg [15:0] state_name ; 33 34 always@(*)begin 35 case(u_FSM_3.state_c) 36 S0: state_name = "S0"; 37 S1: state_name = "S1"; 38 S2: state_name = "S2"; 39 S3: state_name = "S3"; 40 default:state_name = "S0"; 41 endcase 42 end 43 44 //---------------------------------------------------------------------- 45 //-- 時鍾信號和復位信號 46 //---------------------------------------------------------------------- 47 initial begin 48 clk = 1; 49 forever 50 #(`Clock/2) clk = ~clk; 51 end 52 53 initial begin 54 rst_n = 0; #(`Clock*20+1); 55 rst_n = 1; 56 end 57 58 //---------------------------------------------------------------------- 59 //-- 設計輸入信號 60 //---------------------------------------------------------------------- 61 initial begin 62 #1; 63 in = 0; 64 #(`Clock*20+1); //初始化完成 65 //情況1-------------------------- 66 in = 1; //1塊錢 67 #(`Clock*1); 68 in = 0; 69 #(`Clock*1); 70 in = 1; //1塊錢 71 #(`Clock*1); 72 in = 0; 73 #(`Clock*1); 74 in = 1; //1塊錢 75 #(`Clock*1); 76 in = 0; 77 #(`Clock*1); 78 in = 1; //1塊錢 79 #(`Clock*1); 80 in = 0; 81 #(`Clock*10); 82 //情況2-------------------------- 83 in = 1; //1塊錢 84 #(`Clock*1); 85 in = 0; 86 #(`Clock*1); 87 in = 1; //1塊錢 88 #(`Clock*1); 89 in = 0; 90 #(`Clock*1); 91 in = 1; //1塊錢 92 #(`Clock*1); 93 in = 0; 94 #(`Clock*1); 95 in = 2; //2塊錢 96 #(`Clock*1); 97 in = 0; 98 #(`Clock*10); 99 //情況3-------------------------- 100 in = 1; //1塊錢 101 #(`Clock*1); 102 in = 0; 103 #(`Clock*1); 104 in = 1; //1塊錢 105 #(`Clock*1); 106 in = 0; 107 #(`Clock*1); 108 in = 2; //2塊錢 109 #(`Clock*1); 110 in = 0; 111 #(`Clock*10); 112 //情況4-------------------------- 113 in = 1; //1塊錢 114 #(`Clock*1); 115 in = 0; 116 #(`Clock*1); 117 in = 2; //2塊錢 118 #(`Clock*1); 119 in = 0; 120 #(`Clock*1); 121 in = 2; //2塊錢 122 #(`Clock*1); 123 in = 0; 124 #(`Clock*10); 125 //情況5-------------------------- 126 in = 2; //2塊錢 127 #(`Clock*1); 128 in = 0; 129 #(`Clock*1); 130 in = 2; //2塊錢 131 #(`Clock*1); 132 in = 0; 133 #(`Clock*10); 134 135 136 $stop; 137 end 138 139 140 endmodule
1. 一段式狀態機
只定義一個轉移狀態:state,總體結構是一段always時序邏輯,用於描述狀態轉移和輸出。由於是時序邏輯能夠自動保持,所以可以省略else。但建議在初始狀態時(例如下文的S0),else處賦一下初始值。
1 //====================================================================== 2 // --- 名稱 : FSM_1 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2018-12-15 5 // --- 描述 : 售貨機練習,采用一段式狀態機 6 //====================================================================== 7 8 module FSM_1 9 //---------------------<端口聲明>--------------------------------------- 10 ( 11 input clk , 12 input rst_n , 13 input [1:0] in , 14 output reg [1:0] out , 15 output reg out_vld 16 ); 17 //---------------------<信號定義>--------------------------------------- 18 reg [3:0] state ; 19 //---------------------<狀態機參數>------------------------------------- 20 localparam S0 = 4'b0001 ; 21 localparam S1 = 4'b0010 ; 22 localparam S2 = 4'b0100 ; 23 localparam S3 = 4'b1000 ; 24 25 //---------------------------------------------------------------------- 26 //-- 狀態機第1段 27 //---------------------------------------------------------------------- 28 always@(posedge clk or negedge rst_n)begin 29 if(!rst_n)begin 30 state <= S0; 31 out <= 0 ; 32 out_vld <= 0 ; 33 end 34 else begin 35 case(state) 36 S0: begin 37 if(in==1)begin 38 state <= S1; 39 end 40 else if(in==2)begin 41 state <= S2; 42 end 43 else begin 44 out <= 0 ; 45 out_vld <= 0 ; 46 end 47 end 48 S1: begin 49 if(in==1)begin 50 state <= S2; 51 end 52 else if(in==2)begin 53 state <= S3; 54 end 55 end 56 S2: begin 57 if(in==1)begin 58 state <= S3; 59 end 60 else if(in==2)begin 61 state <= S0; 62 out_vld <= 1 ; 63 end 64 end 65 S3: begin 66 if(in==1)begin 67 state <= S0; 68 out_vld <= 1 ; 69 end 70 else if(in==2)begin 71 state <= S0; 72 out <= 1 ; 73 out_vld <= 1 ; 74 end 75 end 76 default:state <= S0; 77 endcase 78 end 79 end 80 81 82 83 endmodule
仿真波形如下:
結論:波形和預想一致!
2. 二段式狀態機:
二段式狀態機,第一段用時序邏輯描述state_c(現態)和state_n(次態),第二段用組合邏輯描述狀態轉移和輸出。由於是組合邏輯,為避免產生鎖存器,else處一定要寫上 if 中說使用了的信號。
1 //====================================================================== 2 // --- 名稱 : FSM_2 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2018-12-15 5 // --- 描述 : 售貨機練習,采用二段式狀態機 6 //====================================================================== 7 8 module FSM_2 9 //---------------------<端口聲明>--------------------------------------- 10 ( 11 input clk , 12 input rst_n , 13 input [1:0] in , 14 output reg [1:0] out , 15 output reg out_vld 16 ); 17 //---------------------<信號定義>--------------------------------------- 18 reg [3:0] state_c ; 19 reg [3:0] state_n ; 20 //---------------------<狀態機參數>------------------------------------- 21 localparam S0 = 4'b0001 ; 22 localparam S1 = 4'b0010 ; 23 localparam S2 = 4'b0100 ; 24 localparam S3 = 4'b1000 ; 25 26 //---------------------------------------------------------------------- 27 //-- 狀態機第1段 28 //---------------------------------------------------------------------- 29 always@(posedge clk or negedge rst_n)begin 30 if(!rst_n) 31 state_c <= S0; 32 else 33 state_c <= state_n; 34 end 35 36 //---------------------------------------------------------------------- 37 //-- 狀態機第2段 38 //---------------------------------------------------------------------- 39 always@(*)begin 40 case(state_c) 41 S0: begin 42 if(in==1)begin 43 state_n = S1; 44 end 45 else if(in==2)begin 46 state_n = S2; 47 end 48 else begin 49 state_n = state_c; 50 out = 0 ; 51 out_vld = 0 ; 52 end 53 end 54 S1: begin 55 if(in==1)begin 56 state_n = S2; 57 end 58 else if(in==2)begin 59 state_n = S3; 60 end 61 else begin 62 state_n = state_c; 63 end 64 end 65 S2: begin 66 if(in==1)begin 67 state_n = S3; 68 end 69 else if(in==2)begin 70 state_n = S0; 71 out_vld = 1 ; 72 end 73 else begin 74 state_n = state_c; 75 out_vld = 0; 76 end 77 end 78 S3: begin 79 if(in==1)begin 80 state_n = S0; 81 out_vld = 1 ; 82 end 83 else if(in==2)begin 84 state_n = S0; 85 out = 1 ; 86 out_vld = 1 ; 87 end 88 else begin 89 state_n = state_c; 90 out = 0; 91 out_vld = 0; 92 end 93 end 94 default:state_n = S0; 95 endcase 96 end 97 98 99 endmodule
仿真波形如下所示:
結論:波形和預想一致!但是產生了毛刺,這也是二段式狀態機的缺點。
毛刺產生原因:狀態機通常包含主控時序進程、主控組合進程和輔助進程三個部分。其中,主控組合進程的任務是根據外部輸入的控制信號和當前狀態的狀態值確定下一 狀態的取向,並確定對外輸出內容和對內部其他組合或時序進程輸出控制信號的內容。一方面,由於有組合邏輯進程的存在,狀態機輸出信號會出現毛刺——競爭冒險現象;另一方面,如果狀態信號是多位值的,則在電路中對應了多條信號線。由於存在傳輸延遲,各信號線上的值發生改變的時間則存在先后,從而使得狀態遷移時在初始狀態和目的狀態之間出現臨時狀態——毛刺。
簡單理解為:state_n 會因為組合邏輯原因不斷出現臨時狀態,這些狀態是無效的,而輸出也因為組合邏輯原因產生這些臨時狀態,即毛刺。
3. 三段式狀態機
三段式狀態機,第一段用時序邏輯描述state_c(現態)和state_n(次態),第二段用組合邏輯描述狀態轉移,第三段用時序邏輯描述輸出,第三段可以是多個always塊。
1 //====================================================================== 2 // --- 名稱 : FSM_3 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2018-12-15 5 // --- 描述 : 售貨機練習,采用三段式狀態機 6 //====================================================================== 7 8 module FSM_3 9 //---------------------<端口聲明>--------------------------------------- 10 ( 11 input clk , 12 input rst_n , 13 input [1:0] in , 14 output reg [1:0] out , 15 output reg out_vld 16 ); 17 //---------------------<信號定義>--------------------------------------- 18 reg [3:0] state_c ; 19 reg [3:0] state_n ; 20 //---------------------<狀態機參數>------------------------------------- 21 localparam S0 = 4'b0001 ; 22 localparam S1 = 4'b0010 ; 23 localparam S2 = 4'b0100 ; 24 localparam S3 = 4'b1000 ; 25 26 //---------------------------------------------------------------------- 27 //-- 狀態機第1段 28 //---------------------------------------------------------------------- 29 always @(posedge clk or negedge rst_n)begin 30 if(!rst_n) 31 state_c <= S0; 32 else 33 state_c <= state_n; 34 end 35 36 //---------------------------------------------------------------------- 37 //-- 狀態機第2段 38 //---------------------------------------------------------------------- 39 always @(*)begin 40 case(state_c) 41 S0: begin 42 if(in==1) 43 state_n = S1; 44 else if(in==2) 45 state_n = S2; 46 else 47 state_n = state_c; 48 end 49 S1: begin 50 if(in==1) 51 state_n = S2; 52 else if(in==2) 53 state_n = S3; 54 else 55 state_n = state_c; 56 end 57 S2: begin 58 if(in==1) 59 state_n = S3; 60 else if(in==2) 61 state_n = S0; 62 else 63 state_n = state_c; 64 end 65 S3: begin 66 if(in==1 || in==2) // in != 0也行 67 state_n = S0; 68 else 69 state_n = state_c; 70 end 71 default:state_n = S0; 72 endcase 73 end 74 75 //---------------------------------------------------------------------- 76 //-- 狀態機第3段 77 //---------------------------------------------------------------------- 78 //找零錢 79 always @(posedge clk or negedge rst_n)begin 80 if(!rst_n) 81 out <= 0; 82 else if(state_c==S3 && in==2) 83 out <= 1; 84 else 85 out <= 0; 86 end 87 88 //輸出脈動 89 always @(posedge clk or negedge rst_n)begin 90 if(rst_n==1'b0) 91 out_vld <= 0; 92 else if((state_c==S2 && in==2) || (state_c==S3 && in!=0)) 93 out_vld <= 1; 94 else 95 out_vld <= 0; 96 end 97 98 99 endmodule
仿真波形如下所示:
結論:波形和預想一致!這也是較多書籍推薦的寫法。
4. 一段式和三段式結合的狀態機(by 威三學院FPGA教程)
V3學院狀態機,只定義一個轉移狀態:state。第一段用時序邏輯描述state狀態轉移,第二段用時序邏輯描述輸出,第二段可以是多個always塊。由於是時序邏輯能夠自動保持,所以可以省略else。這種狀態機的優點是既消除了組合邏輯可能產生的毛刺,又減少了代碼量。
1 //====================================================================== 2 // --- 名稱 : FSM_V3 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2019-06-12 5 // --- 描述 : 售貨機練習,采用V3學院的狀態機 6 //====================================================================== 7 8 module FSM_V3 9 //---------------------<端口聲明>--------------------------------------- 10 ( 11 input clk , 12 input rst_n , 13 input [1:0] in , 14 output reg [1:0] out , 15 output reg out_vld 16 ); 17 //---------------------<信號定義>--------------------------------------- 18 reg [3:0] state ; 19 //---------------------<狀態機參數>------------------------------------- 20 localparam S0 = 4'b0001 ; 21 localparam S1 = 4'b0010 ; 22 localparam S2 = 4'b0100 ; 23 localparam S3 = 4'b1000 ; 24 25 //---------------------------------------------------------------------- 26 //-- 狀態機 27 //---------------------------------------------------------------------- 28 always @(posedge clk or negedge rst_n)begin 29 if(!rst_n) 30 state <= S0; 31 else begin 32 case(state) 33 S0: begin 34 if(in==1) 35 state <= S1; 36 else if(in==2) 37 state <= S2; 38 end 39 S1: begin 40 if(in==1) 41 state <= S2; 42 else if(in==2) 43 state <= S3; 44 end 45 S2: begin 46 if(in==1) 47 state <= S3; 48 else if(in==2) 49 state <= S0; 50 end 51 S3: begin 52 if(in==1 || in==2) // in != 0也行 53 state <= S0; 54 end 55 default:state <= S0; 56 endcase 57 end 58 end 59 60 //---------------------------------------------------------------------- 61 //-- 輸出 62 //---------------------------------------------------------------------- 63 //找零錢 64 always @(posedge clk or negedge rst_n)begin 65 if(!rst_n) 66 out <= 0; 67 else if(state==S3 && in==2) 68 out <= 1; 69 else 70 out <= 0; 71 end 72 73 //輸出脈動 74 always @(posedge clk or negedge rst_n)begin 75 if(rst_n==1'b0) 76 out_vld <= 0; 77 else if((state==S2 && in==2) || (state==S3 && in!=0)) 78 out_vld <= 1; 79 else 80 out_vld <= 0; 81 end 82 83 84 endmodule
仿真波形如下所示:
結論:波形和預想一致!
四、狀態機名稱查看器
可以看到,我的Modelsim波形中出現了一個信號state_name,里面顯示了狀態機的名稱,這是怎么做到的呢?方法有很多種,這里介紹兩種。
1. testbench法
testbench里增加一段參數轉ASCII碼的代碼,如下所示:
1 //---------------------------------------------------------------------- 2 //-- 狀態機名稱查看器 3 //---------------------------------------------------------------------- 4 localparam S0 = 4'b0001 ; 5 localparam S1 = 4'b0010 ; 6 localparam S2 = 4'b0100 ; 7 localparam S3 = 4'b1000 ; 8 //2字符16位 9 reg [15:0] state_name ; 10 11 always@(*)begin 12 case(u_FSM_3.state_c) 13 S0: state_name = "S0"; 14 S1: state_name = "S1"; 15 S2: state_name = "S2"; 16 S3: state_name = "S3"; 17 default:state_name = "S0"; 18 endcase 19 end
在Modelsim中點擊信號state_name,右鍵選擇用ASSIC碼查看就可以看到狀態機的名稱,而不再是頭疼的的0001、0010等字符。編寫時注意一下位寬,一個ASSIC碼字符寬度是8位,例如“S0”有2個字符則需要16位寬。
2. do文件法(tcl文件也是一樣的)
首先你得學會怎么使用Modelsim的自動化腳本仿真,那么我們只要再do文件中加入這段代碼即可:
1 # ====================================================================== 2 # == 狀態機名稱查看器 3 # ====================================================================== 4 5 # 結構體設置 6 virtual type { 7 {4'b0001 S0} 8 {4'b0010 S1} 9 {4'b0100 S2} 10 {4'b1000 S3} 11 } fsm_type; 12 13 # 結構體和信號名關聯,命名為state_name 14 virtual function {(fsm_type)/fsm_tb/u_fsm/state} state_name
參考資料:
[1]小梅哥FPGA教程
[2]威三學院FPGA教程
[3]吳厚航. 深入淺出玩轉FPGA[M]. 北京航空航天大學出版社, 2013.
[4]夏宇聞. Verilog數字系統設計教程.第3版[M]. 北京航空航天大學出版社, 2013.
[5]韓彬, 於瀟宇, 張雷鳴. FPGA設計技巧與案例開發詳解[M]. 電子工業出版社, 2014.