有限状态机(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
仿真波形如下: