这是洗子的第二个FPGA实验啦!本节的实验任务是使用开发板上的两个按键控制两个LED灯的亮灭。按下不同的按键,LED灯呈现不同的效果。以下是本次实验要实现的效果,其中LED流水或闪烁的间隔时间为0.5s。
按键状态 | LED显示效果 |
---|---|
无按键按下 | 两个LED灯全灭 |
按下PL_KEY0 | 两个LED灯交替闪烁 |
按下PL_KEY1 | 两个LED灯同闪同灭 |
一、项目总体思路的构建
我们查阅开发板图,可以看到初始状态下,按键是没有闭合的,即该支路不成回路。我们想要当按键闭合时灯LED可以点亮,就需要获取按键的状态,因此这里定义一个key[1:0]来表示按键状态。当按键没有按下时,PL_KEY端口会被+3.3v上拉到电阻值为1,所以当key=11时表示按键都没有被按下;当key=10时表示PL_KEY1没有被按下,按键PL_KEY0被按下;当key=01时表示PL_KEY0没有被按下,按键PL_KEY1被按下。
接下来,我们绘制一个思维导图,确定这个按键控制LED灯模块所需要的内容。与上个实验(流水灯)相比这里的输入端口就是多了上述将的KEY。现在我们可以实现识别LED的显示效果,但是每一种显示效果都各有两种状态。那么如何分辨两种状态呢?我们新增加一个标志位led_flag,当KEY=10,led_flag=0,PL_KEY0不亮,PL_KEY1亮;当KEY=10,led_flag=1,PL_KEY0亮,PL_KEY1不亮。当KEY=01时同理。
我们根据上述的思路绘制图像来模拟流水灯功能的实现,以下图像包含:LED灯的变化情况,架构的设计图如下:
波形图如下:
二、编写代码
进入rtl文件夹,在此文件夹内建立key_led.v文件开始编写程序。
- 建立key_led模块,定义输入输出变量
module key_led(
input sys_clk,
input sys_rst_n,
input [1:0] key,
output reg [1:0] led
);
endmodule
- 确定系统时钟代码块
我们使用计算器可以计算出系统时钟cnt为25位二进制数,因此位宽为[24:0]。我们建立一个循环当系统时钟处于上升沿或者复位信号处于下降沿时,执行代码块。如果复位信号为低电平时,系统时钟清零。如果系统时钟处于1~249999ns时,系统时钟一次累加1ns。
♾️ verilog 代码:reg [24:0] cnt;
parameter CNT_Max = 25'd25000000;//计数器计时0.5s
//计数器计时0.5s
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt <= 25'd0;
else if(cnt < (CNT_Max - 25'd1))
//else if(cnt < (25'd25 - 25'd1))
cnt <= cnt + 25'd1;
else
cnt <= 25'd0;
end
- 确定LED状态标志位的切换代码块
首先先初始化led_flag为0,当系统时钟为最大值时,就取反,这样便可实现闪烁时间为0.5s,同时得到LED状态的标志位。
♾️ verilog 代码:reg led_flag;
//LED状态标志位的切换
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
led_flag <= 1'b0;
else if(cnt == (CNT_Max - 25'd1))
led_flag <= ~led_flag;
else
led_flag <= led_flag;
end
- 确定LED控制代码块(根据KEY和led_flag的值,对led进行控制)
KEY | led_flag | LED灯状态 |
---|---|---|
11 | / | 00 |
10 | 0 | 01 |
10 | 1 | 10 |
01 | 0 | 00 |
01 | 1 | 11 |
//LED控制(根据KEY和led_flag的值,对led进行控制)
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
led <= 2'b00;
else begin
case(key)
2'b11 : led <= 2'b00;
2'b10 :
if(led_flag == 1'b0)
led <= 2'b01;
else
led <= 2'b10;
2'b01 :
if(led_flag == 1'b0)
led <= 2'b00;
else
led <= 2'b11;
default : ;
endcase
end
end
- 完整代码
module key_led(
input sys_clk,
input sys_rst_n,
input [1:0] key,
output reg [1:0] led
);
reg [24:0] cnt;
reg led_flag;
parameter CNT_Max = 25'd25000000;//计数器计时0.5s
//parameter CNT_Max = 25'd25;//计数器计时500ns
//计数器计时0.5s
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt <= 25'd0;
else if(cnt < (CNT_Max - 25'd1))
//else if(cnt < (25'd25 - 25'd1))
cnt <= cnt + 25'd1;
else
cnt <= 25'd0;
end
//LED状态标志位的切换
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
led_flag <= 1'b0;
else if(cnt == (CNT_Max - 25'd1))
led_flag <= ~led_flag;
else
led_flag <= led_flag;
end
//LED控制(根据KEY和led_flag的值,对led进行控制)
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
led <= 2'b00;
else begin
case(key)
2'b11 : led <= 2'b00;
2'b10 :
if(led_flag == 1'b0)
led <= 2'b01;
else
led <= 2'b10;
2'b01 :
if(led_flag == 1'b0)
led <= 2'b00;
else
led <= 2'b11;
default : ;
endcase
end
end
endmodule
三、验证仿真
在sim文件夹建立tb_key_led.v文件,用来编辑仿真文件。tb文件与上一个实验(流水灯)大同小异这里就不多做赘述。代码如下:
♾️ verilog 代码:`timescale 1ns/1ns //仿真单位/仿真精度
module tb_key_led();
parameter CLK_PERIOD = 20;
reg sys_clk;
reg sys_rst_n;
reg [1:0] key;
wire [1:0] led;
initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
key <= 2'b11;
#200
sys_rst_n <= 1'b1;
#2000
key <= 2'b11; //按键没有按下
#2000
key <= 2'b10; //按下KEY0
#2000
key <= 2'b01; //按下KEY1
#2000
key <= 2'b11; //按键没有按下
end
always #(CLK_PERIOD/2) sys_clk =~ sys_clk;
key_led u_key_led(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.key (key ),
.led (led )
);
endmodule
由仿真图可看出,当KEY=11时,两个LED灯都不亮;当KEY=10时,LED以流水灯形式闪烁,流水间隔为0.5s(仿真为了提高效率采用500ns)。
由仿真图可看出,当KEY=01时,两个LED灯同闪同灭,时间间隔为0.5s(仿真为了提高效率采用500ns)。综上所述,RTL代码无误。
四、上板验证
查询开发板资料,设置好IO口,并烧录,最终结果如下: