狀態機的Verilog寫法


  “硬件設計很講究並行設計思想,雖然用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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM