目前笔者正在接受明德扬FPGA网上培训班的培训,讲的内容非常适合新手,且以练习和互动答疑的教学模式让我学到了很多东西。由于是根据自身时间安排进度的,所以战线拉的比较长,发现做些设计总结非常重要,可以帮助自己理清思路,同时也能得到很好的复习。
之前一直在做altera FPGA的相关学习,对xilinx还不是很熟悉,借着这个契机,将比较基础常用的设计在VIVADO开发环境中过一遍,对我来说是个不错的选择。进入今天的正题,本篇博文旨在通过一个小例子掌握状态机的设计方法。由于设计非常简单,采用常见的三段式状态机来规范设计。后续复杂的例子中,将采用明德扬提出的四段式状态机,个人理解虽然与三段式基本思想相同,但有助于简化设计,理清思路。
众所周知,硬件按键都存在机械抖动。所以一次人为按下的动作会触发数次按键按下的行为。所谓“按键消抖”模块的功能就是将抖动滤除掉,保证对按键状态的有效识别。单片机的设计思想比较通用,即检测到按键连接端口为低电平(低电平有效)后,延迟一段时间再次确认是否为低。若是则说明此次低电平确实为一次按键行为,否则视为抖动。按键松手检测同理。其大体设计流程如下:
这是典型的顺序设计思想,但FPGA是并行的。所以这种时间有先后,且操作差异较大的处理过程要用到状态机进行设计。简化后可将上述过程分为四个状态:初始空闲状态、延迟并检测低电平状态、检测释放状态和延迟并检测高电平状态。以下是状态转移图:
空闲状态下如检测到按键接口低电平进入延迟并确认低电平状态,延迟计数时间设定为10ms。若计数完成且依然为低电平则按下有效进入检测释放状态,若计数期间按键出现高电平说明为抖动回到初始状态。在检测释放状态中若出现高电平进入延迟确认状态,否则持续检测高电平。在延迟确认高电平状态若计数完成且为高电平视为有效松手行为,此时置位有效标志位,按键完成了一次按下到松手的完整有效过程回到IDLE状态再检测下一次按下。如果计数期间出现低电平同样为抖动回到检测释放状态重新检测。
1 `timescale 1ns / 1ps
2
3 module key_jitter#
4 (
5 parameter DELAY_TIME = 2000_000 //延迟10ms
6 )
7 (
8 input clk,
9 input rst_n,
10
11 input key_i,
12 output reg led_o
13 );
14
15 localparam IDLE = 4'b0001,
16 DELAY_LOW = 4'b0010,
17 CHECK_RELEASE = 4'b0100,
18 DELAY_HIGH = 4'b1000;
19
20 reg [20:0] div_cnt;
21 reg [3:0] state_c,state_n;
22 reg key_tmp0,key_tmp1;
23
24 wire add_cnt,end_cnt;
25 wire vld_flag;
26 wire cnt_during;
27
28 //消除亚稳态
29 always@(posedge clk or negedge rst_n)begin
30 if(!rst_n)begin
31 key_tmp0 <= 0;
32 key_tmp1 <= 0;
33 end
34 else begin
35 key_tmp0 <= key_i;
36 key_tmp1 <= key_tmp0;
37 end
38 end
39
40 //状态机
41 always@(posedge clk or negedge rst_n)begin
42 if(!rst_n)
43 state_c <= IDLE;
44 else
45 state_c <= state_n;
46 end
47
48 always@(*)begin
49 case(state_c)
50 IDLE:begin //初始状态检测是否有按键按下 //4'b0001
51 if(key_tmp1 == 0)//有按键按下进入延时后再次确认低电平状态
52 state_n <= DELAY_LOW;
53 else
54 state_n <= state_c;
55 end
56
57 DELAY_LOW:begin //延时并再次确认低电平状态 //4'b0010
58 if(end_cnt && key_tmp1 == 0)//10ms后依然是低电平则有按键按下,此时检测是否松手
59 state_n <= CHECK_RELEASE;
60 else if(cnt_during && key_tmp1 == 1)
61 state_n <= IDLE;//若未计数完成出现高电平则视为抖动,重新检测按下
62 else
63 state_n <= state_c;//计数未完成继续
64 end
65
66 CHECK_RELEASE:begin //4'b0100
67 if(key_tmp1 == 1)//为高电平则等待并再次确认
68 state_n <= DELAY_HIGH;
69 else
70 state_n <= state_c;//若没有高电平则持续检测
71 end
72
73 DELAY_HIGH:begin //4'b1000
74 if(vld_flag)//10ms后依然高电平则按键释放
75 state_n <= IDLE;//释放后回到初始状态再次检测下一次的按下
76 else if(cnt_during && key_tmp1 == 0)//若延时后为0则松手过程视为抖动
77 state_n <= CHECK_RELEASE;
78 else
79 state_n <= state_c;//继续计数
80 end
81
82 default:
83 state_n <= IDLE;
84 endcase
85 end
86
87 assign cnt_during = add_cnt && div_cnt < DELAY_TIME;
88
89 //延迟计数器
90 always@(posedge clk or negedge rst_n)begin
91 if(!rst_n)
92 div_cnt <= 0;
93 else if(add_cnt)begin
94 if(end_cnt)
95 div_cnt <= 0;
96 else
97 div_cnt <= div_cnt + 1'b1;
98 end
99 else
100 div_cnt <= 0;
101 end
102
103 assign add_cnt = state_c == DELAY_HIGH || state_c == DELAY_LOW;
104 assign end_cnt = add_cnt && div_cnt == DELAY_TIME - 1;
105 //按下一次并释放后表示一次有效的操作,此时led翻转
106 always@(posedge clk or negedge rst_n)begin
107 if(!rst_n)
108 led_o <= 0;//上电复位点亮
109 else if(state_c == DELAY_HIGH && vld_flag)//可将()内条件作为按键有效输出
110 led_o <= ~led_o;
1