文章目录
一、状态机原理
状态(FSM),又称有限状态机
一段式状态机
一段式状态机似乎是一锅端,把所有逻辑(包括输入,输出,状态)都在一个always里解决了,这种写法看上去好像很简洁,但是往往不利于维护,这种写法不太推荐,但是在一些简单的状态机中还是可以使用的。
两段式状态机
两段式状态机是一种常用的写法,他把时序逻辑和组合逻辑划分开来,时序逻辑里进行当前逻辑和下一逻辑的切换,组合逻辑里实现各个输入输出及状态判断,这种写法相对容易维护,不过组合逻辑输出较容易出现毛刺等常见问题。
三段式状态机
三段式状态机的写法是一种比较推荐的写法,代码容易维护,时序逻辑的输出解决了两段式写法中组合逻辑的毛刺问题,但是从资源消耗上来讲,三段式消耗的资源会多一些,另外,三段式输入从输入到输出比一段式会延迟一个时钟周期。三段式状态机将时序逻辑和组合逻辑分开,状态和输出分开,清晰容易理解。
我推荐就是写三段式状态机
二、设计思路
按键消抖算是一个比较简单的模块,四个状态
1.IDLE(初始状态)
2.K_D(按键按下的状态)
3.H_D(按键按住稳定的值的状态)
4.K_U(按键弹起的状态)(这个可要可不要)
状态图:
状态转移图:
然后去想想这几个状态的跳转条件,无非就是边沿的检测和按键消抖的20ms的延时
assign idle2down = (state_c == IDLE) && nedge;//检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge&& end_cnt_20ms);//计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms);//计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up = (state_c == HOLD) && (pedge);//检测到上升沿跳转到上升态
assign up2idle = (state_c == UP) && end_cnt_20ms;//计数器计数到20ms跳转到初始态
其余的逻辑就跟平常的按键消抖无异,可以看我之前的博客的代码
三、代码部分
这里代码是借鉴同学的代码,我的代码写的写完的时候太乱了,反正这一个按键消抖的模块,要用的时候代码直接照搬,例化就完事了,用里面的key_out信号就行了。
module fsm_key_debounce # (parameter KEY_W = 3,TIME_20MS = 1_000_000)(
input clk ,
input rst_n ,
input [KEY_W - 1:0] key_in ,
output [KEY_W - 1:0] key_out
);
//参数定义
localparam IDLE = 4'b0001;//初始状态
localparam DOWN = 4'b0010;//按键按下抖动
localparam HOLD = 4'b0100;//按键按下后稳定
localparam UP = 4'b1000;//按键上升抖动
//信号定义
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
//状态转移条件定义
wire idle2down;
wire down2idle;
wire down2hold;
wire hold2up ;
wire up2idle ;
reg [KEY_W - 1:0] key_r0;//同步
reg [KEY_W - 1:0] key_r1;//打拍
wire [KEY_W - 1:0] nedge;//下降沿
wire [KEY_W - 1:0] pedge;//上升沿
//20ms计数器
reg [19:0] cnt_20ms;
wire add_cnt_20ms;
wire end_cnt_20ms;
reg [KEY_W - 1:0] key_out_r;//输出寄存
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
always@(*)begin
case(state_c)
IDLE:begin
if(idle2down)begin
state_n = DOWN;
end
else begin
state_n = state_c;
end
end
DOWN:begin
if(down2idle)begin
state_n = IDLE;
end
else if(down2hold)begin
state_n = HOLD;
end
else begin
state_n = state_c;
end
end
HOLD:begin
if(hold2up)begin
state_n = UP;
end
else begin
state_n = state_c;
end
end
UP:begin
if(up2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:state_n = state_c;
endcase
end
assign idle2down = (state_c == IDLE) && nedge;//检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge&& end_cnt_20ms);//计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms);//计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up = (state_c == HOLD) && (pedge);//检测到上升沿跳转到上升态
assign up2idle = (state_c == UP) && end_cnt_20ms;//计数器计数到20ms跳转到初始态
//20ms计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP;//当按键按下或上弹时开始计数
assign end_cnt_20ms = add_cnt_20ms && ((cnt_20ms == TIME_20MS - 1) || pedge);//当计数到最大值或检测到上升沿计数器清零
//同步打拍
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {KEY_W{1'b1}};
key_r1 <= {KEY_W{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
end
end
assign nedge = ~key_r0 & key_r1;//检测下降沿
assign pedge = key_r0 & ~key_r1;//检测上升沿
//按键赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out_r <= {KEY_W{1'b0}};
end
else if(state_c == HOLD && hold2up)begin
key_out_r <= ~key_r1;
end
else begin
key_out_r <= {KEY_W{1'b0}};
end
end
assign key_out = key_out_r;
endmodule
四、仿真验证
这里就不验证了,还是可以去看我之前的博客仿真,无非是多加了4个状态和4个状态转移的条件。