【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
led显示是数字电路中的输出部分,最简单的应用场景。那输入部分的话,就是按键。这样按键和led,就是最简单的输入和输出部分。很多开发板也是会以这两个case作为范例,来进行教学处理。这样学完之后,大家有一个基本的印象,即用fpga如何来描述输入输出电路。

1、按键的特点
其实按键最大的一个问题就是抖动。按键没有按下去的时候,一般就是一个高电平。但是按下去之后,就变成了低电平。中间呢,因为抖动的原因,还有可能恢复成高电平,这是比较麻烦的事情。后期按键恢复的时候,也会出现这个情况。
2、软件如何解决
大家解决这个问题之前,可以试想一下,如果是软件,该怎么解决。因为嵌入式mcu、和fpga两者本质上是一样的处理思路。处理fpga,只不过是把软件处理的思路转变成fpga描述电路。所以,我们如果是软件处理按键,一般这么来做,
while(1)
{
// read gpio value
gpio_value = read_gpio();
if(gpio_old == gpio_value)
{
sleep(5ms);
continue;
}
sleep(10ms);
gpio_value = read_gpio();
if(gpio_old != gpio_value)
{
report_event(gpio_value);
gpio_old = gpio_value;
}
}
首先,我们需要定时轮询这个数值,如果相同,下次继续轮询。一旦发现不同,就需要sleep大约10ms。这个10ms就是去除硬件抖动的时间,再次读取gpio_value。如果发现还是发现不同,就及时上报。至于是上升沿上报,还是下降沿上报,就看自己的需求了。
3、fpga如何实现
本质上,fpga处理的方法和软件处理的方法是一样的。只不过fpga是并发处理,可以降低cpu轮询时间。并且fpga还有一个好处,那就是不管多少个gpio,对它来说都是一样的处理速度,本来每一个端口都是并发的。同样,我们首先需要依次读入信号,
// input signal
always @(posedge clk or negedge rst)
if(!rst) begin
in1 <= 1'b1;
in2 <= 1'b1;
end
else begin
in1 <= in;
in2 <= in1;
end
接着就是添加一个状态机。添加的目的,主要还是为了方便编写verilog,利用寄存器编写状态机可以让整个流程更加清晰。
// state machine
always @(posedge clk or negedge rst) // state machine defined here
if(!rst)
state <= 1'b0;
else if(in1 != in2 && state == 1'b0)
state <= 1'b1;
else if(count == `TIME_DELAY && state == 1'b1)
state <= 1'b0;
软件处理的时候是统一计数10ms。那么硬件处理的时候,其实也需要进行计数处理。因为硬件触发可能存在抖动,如果发生这一情况,还需要重新开始计数处理,
// about counter
always@(posedge clk or negedge rst)
if(!rst)
count <= 16'h0;
else if(state == 1'b1) begin
if(count == `TIME_DELAY || in1 != in2)
count <= 16'h0;
else
count <= count + 16'h1;
end
最后就是统一输出处理。等到计数时间达到要求的时候,才开始进行统一的输出。这个时候再根据输出来判断按键事件,就会容易很多。
// out signal
always@(posedge clk or negedge rst)
if(!rst)
out1 <= 1'b1;
else if(count == `TIME_DELAY && state == 1'b1)
out1 <= in2;
always @(posedge clk or negedge rst)
if(!rst)
out2 <= 1'b1;
else
out2 <= out1;
// final result
always @(*)
if(!rst)
out = 1'b0;
else if(!out1 & out2)
out = 1'b1;
else // or out1&!out2
out = 1'b0;
4、验证和测试
代码写好之后,在上板子之前一定要自己仿真测试一下。一方面效率比较高,可以去除简单的语法错误,还可以验证功能,特别是corner case。这个时候,我们写的test case是这样的,
`timescale 1ns/1ps
module test();
reg rst;
reg clk;
reg in;
wire out;
key key(.rst(rst),
.clk(clk),
.in(in),
.out(out));
initial
begin
rst = 1;
in = 1;
clk = 0;
#12 rst = 0;
#21 rst = 1;
#35 in = 0;
#50 in = 1;
#70 in = 0;
#500 in = 1;
#1000 $finish;
end
initial
begin
while(1)
clk = #5 !clk;
end
initial
begin
$dumpfile("hello.vcd");
$dumpvars(0, test);
end
endmodule
执行之后,就是这样的逻辑图,

最后给出完整的verilog代码,有需求的同学可以好好看下按键输入是怎么处理的,
module key(input clk, //50M clk
input rst,
input in,
output reg out);
`define TIME_DELAY 16'h20
reg in1;
reg in2;
reg out1;
reg out2;
reg state;
reg[15:0] count;
// input signal
always @(posedge clk or negedge rst)
if(!rst) begin
in1 <= 1'b1;
in2 <= 1'b1;
end
else begin
in1 <= in;
in2 <= in1;
end
// state machine
always @(posedge clk or negedge rst) // state machine defined here
if(!rst)
state <= 1'b0;
else if(in1 != in2 && state == 1'b0)
state <= 1'b1;
else if(count == `TIME_DELAY && state == 1'b1)
state <= 1'b0;
// about counter
always@(posedge clk or negedge rst)
if(!rst)
count <= 16'h0;
else if(state == 1'b1) begin
if(count == `TIME_DELAY || in1 != in2)
count <= 16'h0;
else
count <= count + 16'h1;
end
// out signal
always@(posedge clk or negedge rst)
if(!rst)
out1 <= 1'b1;
else if(count == `TIME_DELAY && state == 1'b1)
out1 <= in2;
always @(posedge clk or negedge rst)
if(!rst)
out2 <= 1'b1;
else
out2 <= out1;
// final result
always @(*)
if(!rst)
out = 1'b0;
else if(!out1 & out2)
out = 1'b1;
else // or out1&!out2
out = 1'b0;
endmodule