【FPGA实战】Verilog实现DE2-115的流水灯控制
- 一、实验要求
- 二、实现步骤
-
- [2.1 环境配置](#2.1 环境配置)
-
- [2.1.1 VScode与Quartus的联动](#2.1.1 VScode与Quartus的联动)
-
- [step1 下载Verilog插件](#step1 下载Verilog插件)
- [step2 Quartus绑定VScode](#step2 Quartus绑定VScode)
- [2.2 实现思路](#2.2 实现思路)
-
- [2.2.1 LedBlink.v](#2.2.1 LedBlink.v)
- [2.2.2 fenpin.v](#2.2.2 fenpin.v)
- [2.2.1 display.v](#2.2.1 display.v)
- [2.3 烧录实验](#2.3 烧录实验)
-
- [2.3.1 编译以及一些问题](#2.3.1 编译以及一些问题)
- [2.3.2 烧录及一些问题](#2.3.2 烧录及一些问题)
- [2.3.3 实验效果](#2.3.3 实验效果)
- 三、实验总结
- 四、参考博客
一、实验要求
在DE2-115开发板上,用Verilog设计一个LED流水灯实验:用6个LED完成周期为1秒的跑马灯效果。
此外,最好规范化实现,采用层次化设计,分别在VScdoe编程各代码文件,如top顶层模块(比如LedBlink.v)、分频模块(fenpin.v)、显示模块(display.v);
增加流水灯的按键暂停、按键恢复功能,使得当按暂停键时流水灯暂停,再按一下继续流水灯。
1)安装VScode,在其中下载安装Verilog-HDL/SystemVerilog插件(实现Verilog代码的语法高亮/自动补全);
二、实现步骤
2.1 环境配置
工欲善其事必先利其器,在Quartus自带的文本编辑器上编写代码不太适应而且也不美观,于是我们可以引入程序员神器------VScode。
2.1.1 VScode与Quartus的联动
step1 下载Verilog插件
这里假设你已经下载了VScode,首先我们要下载Verilog插件。
安装扩展后,可以尝试创建新的文本文件,如下图
点击选择语言,搜索Verilog
选择第一个,之后我们写代码就很方便了~
step2 Quartus绑定VScode
打开Quartus,在菜单栏里找到
tools->Options->Preferred Text Editor
选择Custom,在下面输入
c
"E:\Microsoft VS Code\Code.exe" -g %f:%l
当然,引号内是你VScode的.exe的文件地址,找不到怎么办?
右键快捷键点击打开文件所在位置就能找到了!
这之后我们用Quartus操作Verilog文件都会自动弹出VScode编辑了。
(当然,这里也有个bug,比如你新建一个Verilog文件的话可能没办法命名,假如你知道怎么解决的话请在评论告诉我。)
2.2 实现思路
一开始拿到这个题目是不是没有思路?那我们就先AI!
AI给出的思路是------
要分成顶层模块 、分频模块 和显示模块 。顶层模块连接各个子模块,分频模块负责将系统时钟分频到合适的频率,显示模块负责控制LED的状态变化。
2.2.1 LedBlink.v
因此,top顶层模块(LedBlink.v)需要实现各模块的连接;
具体代码我们先按下不表,后面再展示。
2.2.2 fenpin.v
分频模块(fenpin.v)需要将系统时钟分频到合适的频率。
晶振频率为50MHz,则一个时钟周期 T = 1 ( 50 × 1 0 6 ) = 0.02 u s T= {1 \over (50×10^6)} = 0.02us T=(50×106)1=0.02us
而我们要使得1s闪六个等就相当于 1 6 s {1\over6}s 61s闪一个灯,约166.666ms,于是,闪烁一次我们需要 166666 0.02 = 83333 {166 666\over0.02}=83333 0.02166666=83333个周期,也就是计数83,333次。
因此我们需要一个counter能够存储从0到83332的整数,也就是 2 23 − 1 2^{23}-1 223−1(即8388607)。
因此,我们需要一个寄存器cnt作计数器
c
reg [22:0] cnt;
在复位信号rst_n的控制下可以回到初始状态,在正常工作时,计数器cnt会随着时钟信号clk不断增加,当cnt数到8333332时,会发出一个高电平的使能信号en,然后cnt重新开始计数,如此循环。这个使能信号en就可以用来控制其他模块按照这个特定的周期进行工作。
fenpin.v代码
c
module fenpin(
input clk,
input rst_n,
output reg en
);
reg [22:0] cnt;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt<=23'd0;
en<=1'b0;
end else begin
if(cnt==23'd8_333_332) begin
cnt<=23'd0;
en<=1'b1;
end else begin
cnt<=cnt+1'b1;
en<=1'b0;
end
end
end
endmodule
2.2.1 display.v
显示模块的主要功能是实现走马灯也就是流水灯的闪烁控制,暂停键的控制逻辑以及复位键的逻辑。
1)对于流水灯:
我们可以有两种方法,一种是case语句列举六个灯的逻辑语句。
bash
case (cnt1)
3'b000 : led <= 6'b000001;
3'b001 : led <= 6'b000010;
3'b010 : led <= 6'b000100;
3'b011 : led <= 6'b001000;
3'b100 : led <= 6'b010000;
3'b101 : led <= 6'b100000;
default: led <= led;
endcase
第二种就是用位移实现闪烁。
bash
if(!rst_n) begin
led <= 6'b000001;
end else if(en_ls && !pause_reg) begin
led <= {led[0], led[5:1]};
end
2)重置按钮
在我们单片机学习过程中,对于按键我们一般是要采取消抖的,因此我们还要引入一些变量。
刚开始(复位):如果按下了 "重置按钮"(rst_n 是低电平),所有的信号都会被清空,回到初始状态。
每次秒表滴答一下(时钟上升沿),就把按键信号放到 sync_key 里,同时把 sync_key 之前的状态记录到 key_delay 里。
如果 sync_key 和 key_delay 不一样,说明按键状态变了,这时候 "消抖计数器" 就开始工作,把计数设为 999999,然后慢慢减 1;等 "消抖计数器" 减到 0 了,就看看按键是按下去了(sync_key[1] 是 0)还是松开了(sync_key[1] 是 1)。
3)暂停键:
要实现暂停,我们可以加入 key_pressed 用于记录按键状态看是否按下。
按键从按下状态变为释放状态时,翻转暂停状态。
display.v代码
因此,对于顶层模块display.v的代码如下:
csharp
module display(
input clk,
input rst_n,
input en_ls,
input pause_key,
output reg pause_reg,
output reg [5:0] led
);
reg [1:0] sync_key;
reg key_delay;
reg [19:0] debounce_cnt;
reg key_pressed;
// 按键消抖逻辑
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
sync_key <= 2'b11;
key_delay <= 1'b1;
debounce_cnt <= 20'd0;
key_pressed <= 1'b0;
end else begin
sync_key <= {sync_key[0], pause_key};
key_delay <= sync_key[1];
if(sync_key[1] != key_delay)
debounce_cnt <= 20'd999_999;
else if (debounce_cnt != 0)
debounce_cnt <= debounce_cnt - 1;
if(debounce_cnt == 0 && sync_key[1] == 1'b0)
key_pressed <= 1'b1;
if(debounce_cnt == 0 && sync_key[1] == 1'b1)
key_pressed <= 1'b0;
end
end
reg key_release_flag; // 记录按键释放事件
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
pause_reg <= 1'b0;
key_release_flag <= 1'b0;
end else begin
// 当按键从按下状态变为释放状态时,翻转暂停状态
if(key_pressed == 1'b1 && sync_key[1] == 1'b1)
key_release_flag <= 1'b1;
else
key_release_flag <= 1'b0;
if(key_release_flag)
pause_reg <= ~pause_reg;
end
end
// LED闪烁控制逻辑
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
led <= 6'b000001;
end else if(en_ls && !pause_reg) begin
led <= {led[0], led[5:1]};
end
end
endmodule
LedBlink.v代码
因此,对于顶层模块LedBlink.v的代码如下:
csharp
module LedBlink(
input clk,//50Mhz
input rst_n,//复位信号
input pause_key,
output [5:0] led
);
//模块间连接信号
wire en_ls;
wire pause_reg;//暂停状态信号
fenpin u_fenpin(
.clk (clk),
.rst_n (rst_n),
.en (en_ls)
);
display u_display(
.clk (clk),
.rst_n (rst_n),
.en_ls (en_ls),
.pause_key (pause_key),
.pause_reg (pause_reg),
.led (led)
);
endmodule
2.3 烧录实验
2.3.1 编译以及一些问题
代码都写得差不多了,现在就到了编译的时候了。
在编译的时候,会发现VScode写代码后,找不到文件,这怎么编译啊?
因此,我们需要再做:
双击Files,点击Add All
然后,右键点击我们的top文件LedBlink,设置为顶层文件,编译
没有问题后,分配引脚
暂停键是KEY2,复位键是KEY1
2.3.2 烧录及一些问题
烧录的时候也出了一点小情况,就是发现效果不是我代码预计的效果。
经过搜索发现,错误是因为我用的上一次的文件,因此,还需要将之前的文件给delete,然后add新的.sof文件
然后就烧录成功了
2.3.3 实验效果
【FPGA入门】DE2-115按键控制流水灯
三、实验总结
以上是我最终的实验内容,其实一开始并没有分层,只是简单地在一个模块中实现流水灯,分层次设计后也遇到了一些问题,比如一开始我的暂停键是只有一直按着暂停键流水灯才能停止,折腾半天最后引入了key_pressed变量得以解决。
这次实验的突破在于学会了规范化分层设计,这无疑对以后的开发是有益处的。
同时,在实现的过程中,从一开始的毫无头绪到最终比较好的实现效果,AI工具起到了很大的作用。利用DS深度思考打开思路学习其中的逻辑思维,再一步步地进行理解和改良优化。
四、参考博客
VSCode配置verilog环境(代码提示+自动例化+格式化)
Nios实验入门------用Verilog编程方式完成LED流水灯显示并使用串口输出"Hello Nios-II"字符到笔记本电脑
【整理】DE2-115引脚列表 word版