芯航線——普利斯隊長精心奉獻
實驗目的:1.學習狀態機的相關概念
2.理解一段式、兩段式以及三段式狀態機的區別以及優缺點
實驗平台:芯航線FPGA核心板
實驗原理:
狀態機全稱是有限狀態機(finite-state machine,縮寫:FSM)是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。
狀態機分為摩爾(Moore)型有限狀態機與米利(Mealy)型有限狀態機。摩爾狀態機輸出是只由輸入確定的有限狀態機(不直接依賴於當前狀態)。米利有限狀態機的輸出不止與其輸入有關還於它的當前狀態相關,這也是與摩爾有限狀態機的不同之處。其分別的原理圖如圖8-1,8-2所示
圖8-1 摩爾型狀態機
圖8-2 米利型狀態機
狀態機顧名思義會有各種狀態,這也就分支出一種情況如何對狀態進行有效的編碼。編碼格式,最簡單的就是直接使用二進制進行表示,除此之外還有使用格雷碼、獨熱碼。假設有八個狀態從A到R,利用不同的編碼格式分別如表8-1所示。
編碼格式 狀態 |
二進制 |
獨熱碼 |
格雷碼 |
A |
3'b000 |
8'b0000_0000 |
4'b0000 |
B |
3'b001 |
8'b0000_0010 |
4'b0001 |
C |
3'b010 |
8'b0000_0100 |
4'b0011 |
D |
3'b011 |
8'b0000_1000 |
4'b0010 |
E |
3'b100 |
8'b0001_0000 |
4'b0110 |
F |
3'b101 |
8'b0010_0000 |
4'b0111 |
G |
3'b110 |
8'b0100_0000 |
4'b0101 |
H |
3'b111 |
8'b1000_0000 |
4'b0100 |
表8-1 狀態不同的編碼格式
獨熱碼,每一個狀態均使用一個寄存器,相比其他譯碼簡單;格雷碼,所需寄存器數與二進制碼一樣,譯碼復雜,但相鄰位只跳動一位,一般用於異步多時鍾域多bit位的轉換,如異步FIFO; 二進制碼,最為常見的編碼方式,所用寄存器少,譯碼較復雜。
按照Altera給的建議,選擇哪一種編碼格式是要根據狀態機的復雜度、器件類型以及從非法狀態中恢復出來的要求均有關。在使用不同的編碼格式生成出來的RTL視圖中可以看出二進制比獨熱碼使用更少的寄存器。二進制用7個寄存器就可以實現100個狀態的狀態機,但是獨熱碼就需要100個寄存器。但是另一方面,雖然獨熱碼使用更多的寄存器但是其組合邏輯相對簡單。一般推薦在CPLD中由於提供較多的組合邏輯資源多使用前者,FPGA中提供較多的時序邏輯而多用后者。
狀態機描述方式,可分為一段式、兩段式以及三段式。
一段式,整個狀態機寫到一個always模塊里面,在該模塊中既描述狀態轉移,又描述狀態的輸入和輸出;
兩段式,用兩個always模塊來描述狀態機,其中一個always模塊采用同步時序描述狀態轉移;另一個模塊采用組合邏輯判斷狀態轉移條件,描述狀態轉移規律以及輸出;
三段式,在兩個always模塊描述方法基礎上,使用三個always模塊,一個always模塊采用同步時序描述狀態轉移,一個always采用組合邏輯判斷狀態轉移條件,描述狀態轉移規律,另一個always模塊描述狀態輸出(可以用組合電路輸出,也可以時序電路輸出)。
可以看出兩段式有限狀態機與一段式有限狀態機的區別是FSM將時序部分(狀態轉移部分)和組合部分(判斷狀態轉移條件和產生輸出)分開,寫為兩個always語句,即為兩段式有限狀態機。將組合部分中的判斷狀態轉移條件和產生輸入再分開寫,則為三段式有限狀態機。這樣就使得二段式在組合邏輯特別復雜時,注意需在后面加一個觸發器以消除組合邏輯對輸出產生的毛刺。三段式則沒有這個問題,這是由於第三個always會生成觸發器。其實現在的器件根本不在乎這一點資源消耗,推薦使用二段式或者三段式以及輸出寄存的狀態機輸出來進行描述。
編寫狀態機還需主要注意的事項是狀態窮舉或者default 避免生成鎖存器;在編寫狀態定義時推薦使用本地化參數定義localparam,這樣可以在編寫時狀態更清晰且不容易出錯,也方便修改;在復位或者跑飛能回到初始態或者預定態;要有異步或者同步復位來確保狀態機上電有個初始態。
實驗步驟:
為了實現讓FPGA輸出一個HELLO字符串,首先畫出其狀態轉移圖,如圖8-3所示。
圖8-3 "HELLO"狀態轉移圖
由上圖可以看出如果在任意態不符合轉換條件,那么狀態就會重新回到初始態H。且每一個狀態都有特定的方向,這是一個摩爾型狀態機。
建立工程子文件夾后,新建一個以名為Hello的工程保存在prj下,並在本工程目錄的rtl文件夾下新建verilog file文件在此文件下輸入以下內容並以Hello.v保存。這里對狀態機用獨熱碼編碼且如果完成一次"HELLO"輸出, led燈翻轉一次。
module Hello(Clk,Rst_n,data,led);
input Clk;//50M input Rst_n;//低電平復位
input [7:0]data;
output reg led;
localparam CHECK_H = 5'b0_0001, CHECK_e = 5'b0_0010, CHECK_la = 5'b0_0100, CHECK_lb = 5'b0_1000, CHECK_o = 5'b1_0000;
reg[4:0]state;
always@(posedge Clk or negedge Rst_n) if(!Rst_n)begin led <= 1'b1; state <= CHECK_H; end else begin case(state) CHECK_H: if(data == "H") state <= CHECK_e; else state <= CHECK_H;
CHECK_e: if(data == "e") state <= CHECK_la; else state <= CHECK_H;
CHECK_la: if(data == "l") state <= CHECK_lb; else state <= CHECK_H;
CHECK_lb: if(data == "l") state <= CHECK_o; else state <= CHECK_H;
CHECK_o: begin state <= CHECK_H; if(data == "o") led <= ~led; else led <= led; end
default:state <= CHECK_H; endcase end
endmodule |
進行分析和綜合直至沒有錯誤以及警告。可以在RTL Viewer中看到實現的狀態機如圖8-4所示,與預期設計相同。
圖8-4 生成的狀態轉移圖
為了測試仿真編寫測試激勵文件,新建Hello_tb.v文件保存到testbench文件夾下,輸入以下內容再次進行分析和綜合直至沒有錯誤以及警告。本激勵文件除產生正常的時鍾以及復位信號外,還生成了狀態轉移信號。
`timescale 1ns/1ns `define clock_period 20
module Hello_tb;
reg Clk; reg Rst_n; reg [7:0]ASCII;
wire led;
Hello Hello0( .Clk(Clk), .Rst_n(Rst_n), .data(ASCII), .led(led) );
initial Clk = 1; always#(`clock_period/2)Clk = ~Clk;
initial begin Rst_n = 0; ASCII = 0; #(`clock_period*200); Rst_n = 1; #(`clock_period*200 + 1); forever begin ASCII = "I"; #(`clock_period); ASCII = "A"; #(`clock_period); ASCII = "M"; #(`clock_period); ASCII = "X"; #(`clock_period); ASCII = "i"; #(`clock_period); ASCII = "a"; #(`clock_period); ASCII = "o"; #(`clock_period); ASCII = "M"; #(`clock_period); ASCII = "e"; #(`clock_period); ASCII = "i"; #(`clock_period); ASCII = "g"; #(`clock_period); ASCII = "e";
#(`clock_period); ASCII = "H"; #(`clock_period); ASCII = "E";
#(`clock_period); ASCII = "M"; #(`clock_period); ASCII = "l";
#(`clock_period); ASCII = "H"; #(`clock_period); ASCII = "E"; #(`clock_period); ASCII = "L"; #(`clock_period); ASCII = "L"; #(`clock_period); ASCII = "O"; #(`clock_period);
ASCII = "H"; #(`clock_period); ASCII = "e"; #(`clock_period); ASCII = "l"; #(`clock_period); ASCII = "l"; #(`clock_period); ASCII = "o";
#(`clock_period); ASCII = "l"; end end
endmodule |
設置好仿真腳本后進行功能仿真,可以看到如圖8-5所示的波形文件,可以看出在復位信號置高之前狀態均不發生轉移。在復位有效后,只有當輸入發生變化時狀態才會根據設計進行轉換,且沒有出現轉移錯誤,led的狀態也能根據狀態來進行翻轉。
圖8-5 仿真波形文件
至此就完成了一個簡單的狀態機的設計,在后面的例程中會經常用到狀態機設計思想,這里也就不再對二段式、三段式狀態機展開。