FPGA入门笔记007_A——按键消抖模块设计与验证(状态机、$random、仿真模型、task语法)

发布时间 2023-11-30 11:11:15作者: Yamada_Ryo

实验现象:

每次按下按键0,4个LED显示状态以二进制加法格式加1。

每次按下按键1,4个LED显示状态以二进制加法格式减1。

知识点:

1、testbench中随机数发生函数$random的使用;

2、仿真模型的概念

1、按键波形分析:

按键波形分析

按键未按,FPGA管脚检测到高电平。

按键按下,FPGA管脚检测到低电平。

2、设置四个状态:

1、未按下时,空闲态;

2、抖动滤除状态;

3、按下稳定状态;

4、释放抖动滤除状态

localparam
    IDEL    = 4'b0001,	//空闲状态
    FILTER0 = 4'b0010,	//抖动滤除状态
    DOWN    = 4'b0100,	//按下稳定状态
    FILTER1 = 4'b1000;	//释放抖动滤除状态

reg [3:0]state;

3、状态迁移图:

IDEL:空闲状态

FILTER:抖动滤除状态(过了20ms后还未出现上升沿,进入DOWN状态,否则进入IDEL状态)

DOWN:按下稳定状态

FILTER1:释放抖动滤除状态((过了20ms后还未出现下降沿,进入IDEL状态,否则进入DOWN状态))

微信图片编辑_20231128140608

4、消抖模块模型:

微信图片编辑_20231128144710

module key_filter(
	Clk,
	Rst_n,
	key_in,
	key_flag,
	key_state
);
	
	input Clk;	//时钟信号
	input Rst_n;	//复位信号
	input key_in;	//按键输入信号
	
	output reg key_flag;	//按键状态标志信号
	output reg key_state;	//按键状态信号
    
endmodule

5、边沿检测电路:微信图片编辑_20231128150318

reg key_tmp0,key_tmp1;
wire pedge,nedge;

always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
    key_tmp0 <= 1'b0;
    key_tmp1 <= 1'b0;
end
else begin
    key_tmp0 <= key_in;
    key_tmp1 <= key_tmp0;
end

assign nedge = !key_tmp0 & key_tmp1;	//下降沿
assign pedge = key_tmp0 & !key_tmp1;	//上升沿

6、设置20ms计数器

reg [19:0]cnt;	//计数器
reg en_cnt;	//使能计数寄存器,控制计数器cnt
reg cnt_full;	//计满标志信号

always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
	cnt <= 20'd0;
else if(en_cnt)
    cnt <= cnt + 1'b1;
else
    cnt <= 20'd0;

always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
	cnt_full <= 1'b0;
else if(cnt == 999_999)	//计数1_000_000次
    cnt_full <= 1'b1;
else
    cnt_full <= 1'b0;

7、设置状态机

always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
    en_cnt <= 1'b0;
    state <= IDEL;
    key_flag <= 1'b0;
    key_state <= 1'b1;
end
else begin
	case(state)
        IDEL:begin
            key_flag <= 1'b0;
            if(nedge)begin	//出现下降沿,转到FILTER0状态
                state <= FILTER0;
                en_cnt <= 1'b1;	//开始计数
            end
            else
                state <= IDEL;
        end
        FILTER0:
            if(cnt_full)begin	//计数器计满,转到DOWN状态
                key_flag <= 1'b1;
                key_state <= 1'b0;
                state <= DOWN;
                en_cnt <= 1'b0;	//停止计数,并使计数器清零
            end
        	else if(pedge)begin	//出现上升沿,转到IDEL状态
            	state <= IDEL;
            	en_cnt <= 1'b0;	//停止计数,并使计数器清零
       		end
        	else
            	state <= FILTER0;
        DOWN:begin
            key_flag <= 1'b0;
            if(pedge)begin	//出现上升沿,转到FILTER0状态
                state <= FILTER1;
                en_cnt <= 1'b1;	//开始计数
            end
            else
                state <= DOWN;
        end
        FILTER1:
            if(cnt_full)begin	//计数器计满,转到IDEL状态
                key_flag <= 1'b1;
                key_state <= 1'b1;
                state <= IDEL;
                en_cnt <= 1'b0;	//停止计数,并使计数器清零
            end
      		else if(nedge)begin	//出下降沿,转到DOWN状态
            	state <= DOWN;	
                en_cnt <= 1'b0;	//停止计数,并使计数器清零
        	end
        	else
            	state <= FILTER1;
        default:begin
            state <= IDEL;
            en_cnt <=1'b0;
            key_flag <= 1'b0;
            key_state <= 1'b1;
        end
    endcase
end

8、状态图(State Machine Viewer):

微信截图_20231128163821

9、设置testbench文件(仿真模型)

9.1、设置仿真模型key_model.v

`timescale 1ns/1ns

module key_model(key);

	output reg key;
	
	reg [15:0]myrand;
	
	initial begin
		key = 1'b1;
		press_key;
		#10000;
		press_key;
		#10000;
		press_key;
		$stop;
	end
	
	task press_key;	//设置press_key任务
		begin
			repeat(50)begin
                myrand = {$random}%65536;	//产生0~65535之间的随机数
				#myrand key = ~key;
			end
			key = 0;
			#50000000;
			
			repeat(50)begin
				myrand = {$random}%65536;	//0~65535之间的随机数
				#myrand key = ~key;
			end
			key = 1;
			#50000000;
		end
	endtask

endmodule

9.2、设置testbench文件key_filter_tb.v

`timescale 1ns/1ns
`define clock_period 20

module key_filter_tb;

	reg Clk;
	reg Rst_n;
	wire key_in;	//key_in在此时相当于是一根线,所以设置为wire类型
	
	wire key_flag;
	wire key_state;

	key_filter key_filter0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(key_flag),
		.key_state(key_state)
	);
	
	key_model key_model(.key(key_in));
	
	initial Clk = 1;
	always#(`clock_period/2) Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		#(`clock_period*10) Rst_n = 1'b1;
		#(`clock_period*10 + 1);
	end

endmodule

9.3、将仿真模型与testbench文件链接

Assignments->Settings...->Test Benches...->Edit...

微信截图_20231130110128

在testbench文件夹中选中key_model文件,点击Add

微信截图_20231130110321