有限狀態機(FiniteStateMachine, FSM),是由寄存器組合組合邏輯構成的硬件時序電路。
有限狀態機一般包含:
1.輸入;
2.狀態;
3.狀態轉移條件;
4.輸出。
三段式狀態機:三段式狀態機比一段式狀態機和兩段式狀態機更加簡潔,清晰明了,更好的描述狀態的轉移。
三段式狀態機一般有三個always塊組成。
第一個always塊使用同步時序邏輯電路,描述狀態的跳轉條件。
1 //============================================================================== 2 //三段式狀態機第一段,時許邏輯電路,描述初始狀態 3 // 4 // 5 //============================================================================== 6 always@(posedge clk_50MHZ or negedge rst_n) 7 begin 8 if(!rst_n) 9 Current_state <= IDLE; 10 else 11 Current_state <= Next_state; 12 end
第二個always塊使用組合邏輯電路,描述狀態的跳轉。
//============================================================================== //三段式狀態機第二段,組合邏輯電路,描述狀態跳轉 // // //============================================================================== always @(*) begin case(Current_state) IDLE: begin if(key_negedge) begin cnt_en<=1'b1; Next_state<= S1; end else begin cnt_en<=1'b0; Next_state<= IDLE; end end S1: begin if(cnt_full) begin cnt_en<=1'b0; Next_state<= S2; end else begin if(key_posedge) begin cnt_en<=1'b0; Next_state<= IDLE; end else begin cnt_en<=1'b1; Next_state<= S1; end end end S2: begin if(key_posedge) begin cnt_en<=1'b1; Next_state<= S3; end else begin cnt_en<=1'b0; Next_state<= S2; end end S3: begin if(cnt_full) begin cnt_en<=1'b0; Next_state<= IDLE; end else begin if(key_negedge) begin cnt_en<=1'b0; Next_state<= S2; end else begin cnt_en<=1'b1; Next_state<= S3; end end end default: ; endcase end
第三個always塊使用組合邏輯電路,描述狀態輸出。
//============================================================================== //三段式狀態機第三段,組合邏輯電路。 // // //============================================================================== always @(Current_state or rst_n) begin if(!rst_n) key_flag<=1'b1; else begin case(Current_state) IDLE: key_flag<=1'b1; S1: key_flag<=1'b1; S2: key_flag<=1'b0; S3: key_flag<=1'b0; default :key_flag<=1'b1; endcase end end
注意:
1.三段式狀態機中狀態的定義這里采用獨熱碼的形式來定義,可以更加簡潔明了,缺點是當狀態過多的情況下使用的寄存器位數會增加。
//============================================================================== //狀態跳轉條件: // // //============================================================================== localparam IDLE =4'b0001 ;//按鍵沒有按下,空閑狀態 localparam S1 =4'b0010 ;//按鍵按下,濾除抖動狀態 localparam S2 =4'b0100 ;//按鍵按下,穩定狀態 localparam S3 =4'b1000 ;//按鍵釋放,濾除抖動狀態 reg [3:0] Current_state ;//初態 reg [3:0] Next_state ;//次態
2.狀態機的第三段可以使用組合邏輯電路輸出,也可以使用時序邏輯電路輸出,一般推薦使用時序電路輸出,因為狀態機的設計和其它設計一樣,最好使用同步時序方式設計,以提高設計的穩定性,消除毛刺。
本文使用按鍵消抖的例子,使用三段式狀態機進行按鍵軟件消抖。完整代碼如下:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 20:54:39 09/03/2019 // Design Name: // Module Name: key_filter_top // Project Name: key_filter // Target Devices: XC6LX9 // Tool versions: // Description: 通過三段式狀態機方式實現按鍵消抖功能 // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module key_filter_top ( input clk_50MHZ ,//系統時鍾50M input rst_n ,//系統復位 input key_in ,//按鍵 output reg [3:0] led , //4位LED燈 output reg key_flag, output led_flag ); reg key_temp0 ;//用於按鍵上一次狀態存儲 reg key_temp1 ;//用於按鍵當前狀態存儲 wire key_posedge ;//按鍵上升沿 wire key_negedge ;//按鍵下降沿 //wire led_flag; reg [20:0]cnt ;//20位2進制計數器 reg cnt_en ;//下降沿計數使能 reg cnt_full ;//上升沿計數使能 reg key_flag_temp0 ;//用於按鍵上標志一次狀態存儲 reg key_flag_temp1 ;//用於按鍵標志當前狀態存儲 //=============================================================================== // //邊沿檢測,采用寄存器方式。 // //=============================================================================== always@(posedge clk_50MHZ or negedge rst_n) begin if(!rst_n) begin key_temp0<=1'b0; key_temp1<=1'b0; end else begin key_temp1<=key_in; key_temp0<=key_temp1; end end assign key_posedge=(!key_temp0)&key_temp1; assign key_negedge=(!key_temp1)&key_temp0; //=============================================================================== // //檢測到計時器使能開始計數器計數20mS // //=============================================================================== always@(posedge clk_50MHZ or negedge rst_n) begin if(!rst_n) cnt<=1'b0; else if(cnt_en==1) begin if(cnt==20'd9) cnt<=1'b0; else cnt<=cnt+1'b1; end else cnt<=1'b0; end always@(posedge clk_50MHZ or negedge rst_n) begin if(!rst_n) cnt_full<=1'b0; else if(cnt==20'd9) cnt_full<=1'b1; else cnt_full<=1'b0; end //============================================================================== //狀態跳轉條件: // // //============================================================================== localparam IDLE =4'b0001 ;//按鍵沒有按下,空閑狀態 localparam S1 =4'b0010 ;//按鍵按下,濾除抖動狀態 localparam S2 =4'b0100 ;//按鍵按下,穩定狀態 localparam S3 =4'b1000 ;//按鍵釋放,濾除抖動狀態 reg [3:0] Current_state ;//初態 reg [3:0] Next_state ;//次態 //============================================================================== //三段式狀態機第一段,時許邏輯電路,描述初始狀態 // // //============================================================================== always@(posedge clk_50MHZ or negedge rst_n) begin if(!rst_n) Current_state <= IDLE; else Current_state <= Next_state; end //============================================================================== //三段式狀態機第二段,組合邏輯電路,描述狀態跳轉 // // //============================================================================== always @(*) begin case(Current_state) IDLE: begin if(key_negedge) begin cnt_en<=1'b1; Next_state<= S1; end else begin cnt_en<=1'b0; Next_state<= IDLE; end end S1: begin if(cnt_full) begin cnt_en<=1'b0; Next_state<= S2; end else begin if(key_posedge) begin cnt_en<=1'b0; Next_state<= IDLE; end else begin cnt_en<=1'b1; Next_state<= S1; end end end S2: begin if(key_posedge) begin cnt_en<=1'b1; Next_state<= S3; end else begin cnt_en<=1'b0; Next_state<= S2; end end S3: begin if(cnt_full) begin cnt_en<=1'b0; Next_state<= IDLE; end else begin if(key_negedge) begin cnt_en<=1'b0; Next_state<= S2; end else begin cnt_en<=1'b1; Next_state<= S3; end end end default: ; endcase end //============================================================================== //三段式狀態機第三段,組合邏輯電路。 // // //============================================================================== always @(Current_state or rst_n) begin if(!rst_n) key_flag<=1'b1; else begin case(Current_state) IDLE: key_flag<=1'b1; S1: key_flag<=1'b1; S2: key_flag<=1'b0; S3: key_flag<=1'b0; default :key_flag<=1'b1; endcase end end //=============================================================================== // //邊沿檢測,采用寄存器方式。 // //=============================================================================== always@(posedge clk_50MHZ or negedge rst_n) begin if(!rst_n) begin key_flag_temp0<=1'b0; key_flag_temp1<=1'b0; end else begin key_flag_temp0<=key_flag; key_flag_temp1<=key_flag_temp0; end end assign led_flag=key_flag_temp1&(!key_flag_temp0); always @(posedge led_flag or negedge rst_n) begin if(!rst_n) led<=4'b0001; else if(led==4'b1000) led<=4'b0001; else led<=(led<<1); end endmodule
按鍵消抖testbench仿真代碼如下:
`timescale 1ns / 1ps //////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 17:46:30 02/23/2020 // Design Name: key_filter_top // Module Name: C:/mydesign/key_filter/key_filtertb.v // Project Name: key_filter // Target Device: // Tool versions: // Description: // // Verilog Test Fixture created by ISE for module: key_filter_top // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // //////////////////////////////////////////////////////////////////////////////// module key_filtertb; // Inputs reg clk_50MHZ; reg rst_n; reg key_in; // Outputs wire [3:0] led; wire key_flag; wire led_flag; // Instantiate the Unit Under Test (UUT) key_filter_top uut ( .clk_50MHZ(clk_50MHZ), .rst_n(rst_n), .key_in(key_in), .led(led), .key_flag(key_flag), .led_flag(led_flag) ); initial begin // Initialize Inputs clk_50MHZ = 0; rst_n = 0; key_in = 1; // Wait 100 ns for global reset to finish #10; rst_n = 1; key_in = 1; #10; key_in = 0; #10; key_in = 1; #10; key_in = 0; #10; key_in = 1; #10; key_in = 0; #200; key_in = 1; #10; key_in = 0; #10; key_in = 1; #10; key_in = 0; #10; key_in = 1; #10; key_in = 0; #10; key_in = 1; #200; key_in = 0; #10; key_in = 1; #10; key_in = 0; #10; key_in = 1; #10; key_in = 0; #200; key_in = 1; #10; key_in = 0; #10; key_in = 1; #10; key_in = 0; #10; key_in = 1; #10; key_in = 0; #10; key_in = 1; #200; // Add stimulus here end always #5 clk_50MHZ=~clk_50MHZ; endmodule
仿真波形如下: