1. 实现按键消抖
参见 http://www.stepfpga.com/doc/altera_7deb
1)模拟不消除抖动的状态
module top(
key, //按键输入
rst, //复位输入
led //led输出
);
input key,rst;
output reg led;
always @(key or rst)
if (!rst) //复位时led熄灭
led = 1;
else if(key == 0)
led = ~led; //按键按下时led翻转
else
led = led;
endmodule
ctrl+k通过之后assignments->pinplanners布局布线,然后start complication。
连上小脚丫,开始烧录。(视频传不上来,大概意思就是会发现,有的时候即使松开了按键,led仍旧亮着)
2)延时去抖
主代码如下
module top (clk,rst,key,led);
input clk;
input rst;
input key;
output reg led;
wire key_pulse;
//当按键按下时产生一个高脉冲,翻转一次led
always @(posedge clk or negedge rst)
begin
if (!rst)
led <= 1'b1;
else if (key_pulse)
led <= ~led;
else
led <= led;
end
//例化消抖module,这里没有传递参数N,采用了默认的N=1
debounce u1 (
.clk (clk),
.rst (rst),
.key (key),
.key_pulse (key_pulse)
);
endmodule
然后直接在刚刚创建的那个project里面new file Verilog HDL。
module debounce (clk,rst,key,key_pulse);
parameter N = 1; //要消除的按键的数量
input clk;
input rst;
input [N-1:0] key; //输入的按键
output [N-1:0] key_pulse; //按键动作产生的脉冲
reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值
reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值
wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲
//利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中
always @(posedge clk or negedge rst)
begin
if (!rst) begin
key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1
key_rst_pre <= {N{1'b1}};
end
else begin
key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre
key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值
end
end
assign key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平
reg [17:0] cnt; //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器
//产生20ms延时,当检测到key_edge有效是计数器清零开始计数
always @(posedge clk or negedge rst)
begin
if(!rst)
cnt <= 18'h0;
else if(key_edge)
cnt <= 18'h0;
else
cnt <= cnt + 1'h1;
end
reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量
reg [N-1:0] key_sec;
//延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec <= {N{1'b1}};
else if (cnt==18'h3ffff)
key_sec <= key;
end
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec_pre <= {N{1'b1}};
else
key_sec_pre <= key_sec;
end
assign key_pulse = key_sec_pre & (~key_sec);
endmodule
注意:先ctrl+k后面这个,再前面那个,不然前面那个找不到例化的debounce。
然后重新布置管脚,烧录,就ok啦!(可以看到按键对led的精确控制)
2. 实现流水灯
参见 http://www.stepfpga.com/doc/altera_6led
有一点小复杂,他需要两个附件,
38译码器:3._3-8译码器 [STEP FPGA开源社区]
module decode38 (sw,led);
input [2:0] sw; //开关输入信号,利用了其中3个开关作为3-8译码器的输入
output [7:0] led; //输出信号控制特定LED
reg [7:0] led; //定义led为reg型变量,在always过程块中只能对reg型变量赋值
//always过程块,括号中sw为敏感变量,当sw变化一次执行一次always中所有语句,否则保持不变
always @ (sw)
begin
case(sw) //case语句,一定要跟default语句
3'b000: led=8'b0111_1111; //条件跳转,其中“_”下划线只是为了阅读方便,无实际意义
3'b001: led=8'b1011_1111; //位宽'进制+数值是Verilog里常数的表达方法,进制可以是b、o、d、h(二、八、十、十六进制)
3'b010: led=8'b1101_1111;
3'b011: led=8'b1110_1111;
3'b100: led=8'b1111_0111;
3'b101: led=8'b1111_1011;
3'b110: led=8'b1111_1101;
3'b111: led=8'b1111_1110;
default: ;
endcase
end
endmodule
module divide ( clk,rst_n,clkout);
input clk,rst_n; //输入信号,其中clk连接到FPGA的C1脚,频率为12MHz
output clkout; //输出信号,可以连接到LED观察分频的时钟
//parameter是verilog里常数语句
parameter WIDTH = 3; //计数器的位数,计数的最大值为 2**WIDTH-1
parameter N = 5; //分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出
reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器
reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟
//上升沿触发时计数器的控制
always @ (posedge clk or negedge rst_n ) //posedge和negedge是verilog表示信号上升沿和下降沿
//当clk上升沿来临或者rst_n变低的时候执行一次always里的语句
begin
if(!rst_n)
cnt_p<=0;
else if (cnt_p==(N-1))
cnt_p<=0;
else cnt_p<=cnt_p+1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器
end
//上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_p<=0;
else if (cnt_p<(N>>1)) //N>>1表示右移一位,相当于除以2去掉余数
clk_p<=0;
else
clk_p<=1; //得到的分频时钟正周期比负周期多一个clk时钟
end
//下降沿触发时计数器的控制
always @ (negedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_n<=0;
else if (cnt_n==(N-1))
cnt_n<=0;
else cnt_n<=cnt_n+1;
end
//下降沿触发的分频时钟输出,和clk_p相差半个时钟
always @ (negedge clk)
begin
if(!rst_n)
clk_n<=0;
else if (cnt_n<(N>>1))
clk_n<=0;
else
clk_n<=1; //得到的分频时钟正周期比负周期多一个clk时钟
end
assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //条件判断表达式
//当N=1时,直接输出clk
//当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p
//当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与
endmodule
流水灯
module flashled (clk,rst,led);
input clk,rst;
output [7:0] led;
reg [2:0] cnt ; //定义了一个3位的计数器,输出可以作为3-8译码器的输入
wire clk1h; //定义一个中间变量,表示分频得到的时钟,用作计数器的触发
//例化module decode38,相当于调用
decode38 u1 (
.sw(cnt), //例化的输入端口连接到cnt,输出端口连接到led
.led(led)
);
//例化分频器模块,产生一个1Hz时钟信号
divide #(.WIDTH(32),.N(12000000)) u2 ( //传递参数
.clk(clk),
.rst_n(rst), //例化的端口信号都连接到定义好的信号
.clkout(clk1h)
);
//1Hz时钟上升沿触发计数器,循环计数
always @(posedge clk1h or negedge rst)
if (!rst)
cnt <= 0;
else
cnt <= cnt +1;
endmodule
意思就是:在新建的project里将会有三个下属v文件。
然后一起ctrl+k, 按照下图分配管脚之后start complication再烧录就行。(好像也不太难)
3. 实现呼吸灯
参见http://www.stepfpga.com/doc/altera_9breath
module breath_led(clk,rst,led);
input clk; //系统时钟输入
input rst; //复位输出
output led; //led输出
reg [24:0] cnt1; //计数器1
reg [24:0] cnt2; //计数器2
reg flag; //呼吸灯变亮和变暗的标志位
parameter CNT_NUM = 2400; //计数器的最大值 period = (2400^2)*2 = 24000000 = 2s
//产生计数器cnt1
always@(posedge clk or negedge rst) begin
if(!rst) begin
cnt1<=13'd0;
end
else begin
if(cnt1>=CNT_NUM-1)
cnt1<=1'b0;
else
cnt1<=cnt1+1'b1;
end
end
//产生计数器cnt2
always@(posedge clk or negedge rst) begin
if(!rst) begin
cnt2<=13'd0;
flag<=1'b0;
end
else begin
if(cnt1==CNT_NUM-1) begin //当计数器1计满时计数器2开始计数加一或减一
if(!flag) begin //当标志位为0时计数器2递增计数,表示呼吸灯效果由暗变亮
if(cnt2>=CNT_NUM-1) //计数器2计满时,表示亮度已最大,标志位变高,之后计数器2开始递减
flag<=1'b1;
else
cnt2<=cnt2+1'b1;
end else begin //当标志位为高时计数器2递减计数
if(cnt2<=0) //计数器2级到0,表示亮度已最小,标志位变低,之后计数器2开始递增
flag<=1'b0;
else
cnt2<=cnt2-1'b1;
end
end
else cnt2<=cnt2; //计数器1在计数过程中计数器2保持不变
end
end
//比较计数器1和计数器2的值产生自动调整占空比输出的信号,输出到led产生呼吸灯效果
assign led = (cnt1<cnt2)?1'b0:1'b1;
endmodule