状态机设计
(看了小梅哥的教学视频后,学习的第九课)
按键按下抬起过程中,有等待按下空闲状态(IDEL),按下抖动滤除状态(FILTER0),按下稳定状态(DOWN),抬起抖动滤除状态(FILTER1)。
按键在按下和抬起过程中,大致会有20ms的波形抖动,不稳定状态。
接下来,用状态机实现该过程(verilog):
// An highlighted block
var foo = 'bar';
module key_filter(Clk,Rst_n,key_in,key_state,key_flag);//filter:滤波
input Clk;
input Rst_n;
input key_in;
output reg key_state;//按键稳定状态
output reg key_flag;//按键检测成功标志信号
localparam//定义状态
IDLE = 4'b0001,//空闲状态
FILTER0 = 4'b0010,//按下滤波状态
DOWN = 4'b0100,//按下稳定状态
FILTER1 = 4'b1000;//释放滤波状态
//在quartus中,寄存器放在哪里都行,但在modelsim中不行
reg[3:0]state;//状态寄存器
reg key_temp0,key_temp1;//定义两个寄存器
wire pedge,negdge;//定义一个上升沿和一个下降沿
reg cnt_full;//定义计数满寄存器(计数器满标志信号)
reg [19:0]cnt;//定义计数器20位宽,需要计数20ms
//给出使能计数信号后,才开始计数,其余时候为清零状态
reg en_cnt;//使能计数寄存器
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
key_temp0 <= 1'b0;
key_temp1 <= 1'b0;
end
else begin
key_temp0 <= key_in;//寄存两次状态
key_temp1 <= key_temp0;
end
//检测上升沿与下降沿
assign negdge = !key_temp0 & key_temp1;//组合逻辑输出值为1,则检测到下降沿
assign pedge = key_temp0 & (!key_temp1);//!为取非 ~为按位取反
//eg:0110~1001 !(0110)=0 !(0000)=1
//状态机主程序
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin//所有赋值的值,再复位的时候都要赋个初值
key_flag <= 1'b0;
key_state <= 1'b1;
state <= IDLE;
en_cnt <= 1'b0;
end
else begin
case(state)
IDLE:
begin
key_flag <= 1'b0;//因为在FILTER1里拉高了,在这里要清零
if(negdge)begin
state <= FILTER0;
en_cnt <= 1'b1;
end
else
state <= IDLE;
end
FILTER0:
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
en_cnt <= 1'b0;//计数满了之后清零
state <= DOWN;
end
else if(pedge)begin//如果还没有计数完,检测到上升沿
state <= IDLE;
en_cnt <= 1'b0;//表示这只是抖动,不用计数,等待下降沿的到来才开始计数
end
else
state <= FILTER0;
DOWN:
begin
key_flag <= 1'b0;//该语句与之后的if语句并行的
if(pedge)begin
state <= FILTER1;
en_cnt <= 1'b1;
end
else
state <= DOWN;
end
FILTER1:
if(cnt_full)begin
key_flag <= 1'b1;//可忽略,也可表示再次检测到按键,按键释放
key_state <= 1'b1;
//en_cnt <= 1'b0;//计数满了之后清零
state <= IDLE;
end
else if(negdge)begin//如果还没有计数完,检测到上升沿
state <= DOWN;
en_cnt <= 1'b0;//表示这只是抖动,不用计数,等待下降沿的到来才开始计数
end
else
state <= FILTER1;
default:
begin
en_cnt <= 1'b0;//给一个回到正常状态的值
key_flag <= 1'b0;//默认为没有按键的时候的一个状态
key_state <= 1'b1;
state <= IDLE;
end
endcase
end
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 == 999999)//1000000-1
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
endmodule
这次用了仿真模型,key_model,仿真模型,不可综合模块,所以得修改脚本。
//按键模型(和仿真有一定的相似之处)在按键里,不存在时钟概念
//仿真模型,不可综合模块,所以得修改脚本
`timescale 1ns/1ns
//`define clk_period 20系统时间周期可不写
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;//按键抖动函数(按下抖动模拟)“$random”为随即发生函数
begin
repeat (50)begin //若没有“{}”取值范围为(-65536~65536)
myrand = {$random}%65536;//限定了myrand的取值范围(0~65536)
#myrand key = ~key;//延时了myrand ns的时间后,发生翻转
end
key = 0;
#50000000;
//释放抖动过程
repeat (50)begin
myrand = {$random}%65536;
#myrand key = ~key;
end
key = 1;
#50000000;
end
endtask
endmodule
仿真主程序(test_bench文件):
//使用仿真模型进行仿真
`timescale 1ns/1ns
`define clk_period 20
module key_filter_tb;
reg Clk;
reg Rst_n;
wire key_in;//因为是两个模块之间进行连接,所以用wire型
wire key_flag;
wire key_state;
key_filter key_filter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_state(key_state),
.key_flag(key_flag)
);
//模块调用
key_model key_model(.key(key_in));//连接到key_in
initial Clk = 1;
always#(`clk_period/2) Clk = ~Clk;
initial begin//逻辑编写
Rst_n = 1'b0;
#(`clk_period*10)Rst_n = 1'b1;
#(`clk_period*10+1);//加一方便后面看波形
end
endmodule
欢迎指正(: