ROCCHI THE ROCK!|

Yamada_Ryo

园龄:1年4个月粉丝:3关注:5

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

实验现象:

每次按下按键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

本文作者:Yamada_Ryo

本文链接:https://www.cnblogs.com/little55/p/17866867.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Yamada_Ryo  阅读(190)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起