目录
一、引言
又到了最常规的流水灯环节!通过在DE2-115开发板上实现一个LED流水灯实验,加深对Verilog HDL的理解,还能培养模块化设计的习惯。本文将详细介绍如何在DE2-115开发板上设计一个周期为1秒的6个LED跑马灯效果,并提供规范化的代码实现。
二、开发环境搭建
1. 安装VSCode
下载并安装VSCode
在VSCode中安装Verilog-HDL/SystemVerilog插件,实现Verilog代码的语法高亮和自动补全功能。(安装这个功能的话在VScode中编程就不容易出错,很简单就可以实现修改、报错、提示等等!)亲测!!好用!!!
文件芯片:EP4CE115F29C7
三、模块化设计
任务要求:
1)采用层次化设计,分别在VScdoe编程各代码文件,如top顶层模块(比如LedBlink.v)、分频模块(fenpin.v)、显示模块(display.v);
2)尽量采用计数器、状态机思想;
题目分析:
DE2-115 开发板搭载了一个50MHz 固定频率晶振,作为 FPGA 系统的核心时钟源。该晶振可产生稳定的周期性电信号,每秒震荡 50×10⁶次,为整个电路提供时序基准。
时钟周期(Clock Period)指时钟信号完成一个完整周期(如上升沿到下一个上升沿)的时间,计算公式为:
时钟周期=时钟频率1
对于 50MHz 时钟:
周期=50×106Hz1=20×10−9秒=20纳秒(ns)
功能目标:1 秒内依次点亮 6 个 LED,即每个 LED 的点亮间隔为 1/6 秒(约 0.167 秒)
整体代码:
module led_test(
input wire clk, // 50MHz时钟输入
input wire rst_n, // 复位信号,低电平有效
output reg [5:0] led // 6个LED灯的状态
);
// 计数器,计数1s需要50_000_000个时钟周期
reg [25:0] cnt;
// 计数器模块
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 26'd0; // 复位时计数器归零
end
else if (cnt == 50_000_000 - 1) begin // 计满1秒后复位
cnt <= 26'd0;
end
else begin
cnt <= cnt + 1'd1; // 使用非阻塞赋值
end
end
// LED状态更新逻辑:每次触发时先清零所有LED
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led <= 6'b000000; // 复位时关闭所有LED
end
else begin
// 默认保持当前状态(非阻塞赋值)
led <= led;
if (cnt == 8_333_333 - 1) begin
led <= 6'b000001; // 点亮第一个LED
end
else if (cnt == 16_666_666 - 1) begin
led <= 6'b000010; // 点亮第二个LED
end
else if (cnt == 25_000_000 - 1) begin
led <= 6'b000100; // 点亮第三个LED
end
else if (cnt == 33_333_333 - 1) begin
led <= 6'b001000; // 点亮第四个LED
end
else if (cnt == 41_666_666 - 1) begin
led <= 6'b010000; // 点亮第五个LED
end
else if (cnt == 50_000_000 - 1) begin
led <= 6'b100000; // 点亮第六个LED
end
end
end
endmodule
1. 顶层模块(LedBlink.v)
定义顶层模块,整合各个子模块,实现整体功能。
顶层模块负责初始化和连接各个子模块,确保整个系统协调工作。
module LedBlink ( input clk, // 系统时钟信号 input rst, // 复位信号 output [5:0] led // 6个LED的输出信号 ); wire clk_1hz; // 1Hz的时钟信号 // 实例化分频模块 fenpin u_fenpin ( .clk(clk), .rst(rst), .clk_out(clk_1hz) ); // 实例化显示模块 display u_display ( .clk(clk_1hz), .rst(rst), .led(led) ); endmodule
2. 分频模块(fenpin.v)
设计分频模块,将系统时钟信号分频为1秒的时钟信号。
使用计数器实现分频功能,确保时钟信号的稳定性和准确性。
module fenpin ( input clk, // 系统时钟信号 input rst, // 复位信号 output reg clk_out // 分频后的时钟信号 ); reg [24:0] counter; // 25位计数器,用于分频 always @(posedge clk or posedge rst) begin if (rst) begin counter <= 0; // 复位计数器 clk_out <= 0; // 复位输出时钟 end else begin if (counter == 25'd49999999) begin counter <= 0; // 计数器溢出,重置计数器 clk_out <= ~clk_out; // 翻转输出时钟 end else begin counter <= counter + 1; // 计数器加1 end end end endmodule
3. 显示模块(display.v)
设计显示模块,控制6个LED的动态显示效果。
使用状态机思想,实现LED的跑马灯效果。
module display ( input clk, // 时钟信号 input rst, // 复位信号 output reg [5:0] led // 6个LED的输出信号 ); reg [2:0] state; // 3位状态寄存器,用于状态机 always @(posedge clk or posedge rst) begin if (rst) begin state <= 3'b000; // 复位状态 led <= 6'b000001; // 初始状态,点亮第一个LED end else begin case (state) 3'b000: led <= 6'b000010; // 状态0,点亮第二个LED 3'b001: led <= 6'b000100; // 状态1,点亮第三个LED 3'b010: led <= 6'b001000; // 状态2,点亮第四个LED 3'b011: led <= 6'b010000; // 状态3,点亮第五个LED 3'b100: led <= 6'b100000; // 状态4,点亮第六个LED 3'b101: led <= 6'b000001; // 状态5,回到第一个LED default: led <= 6'b000001; // 默认状态 endcase state <= state + 1; // 状态机状态更新 end end endmodule
四、代码实现
-
使用一个25位计数器
counter
,将50MHz的系统时钟分频为1Hz的时钟信号clk_1hz
。每当计数器计数到49999999时,输出时钟信号翻转一次,实现1秒的周期。
显示模块:
使用一个3位状态寄存器
state
,通过状态机控制6个LED的动态显示效果。每个状态对应一个LED的点亮,状态机在每个1Hz时钟周期更新状态。
module FLED (
input clk, // 系统时钟信号,通常为50MHz
input rst, // 复位信号
output reg [5:0] led // 6个LED的输出信号
);
// 分频模块:将50MHz的系统时钟分频为1Hz
reg [24:0] counter; // 25位计数器,用于分频
reg clk_1hz; // 1Hz的时钟信号,定义为reg类型
always @(posedge clk or posedge rst) begin
if (rst) begin
counter <= 0; // 复位计数器
clk_1hz <= 0; // 复位输出时钟
end else begin
if (counter == 25'd49999999) begin
counter <= 0; // 计数器溢出,重置计数器
clk_1hz <= ~clk_1hz; // 翻转输出时钟
end else begin
counter <= counter + 1; // 计数器加1
end
end
end
// 显示模块:控制6个LED的动态显示效果
reg [2:0] state; // 3位状态寄存器,用于状态机
always @(posedge clk_1hz or posedge rst) begin
if (rst) begin
state <= 3'b000; // 复位状态
led <= 6'b000001; // 初始状态,点亮第一个LED
end else begin
case (state)
3'b000: led <= 6'b000010; // 状态0,点亮第二个LED
3'b001: led <= 6'b000100; // 状态1,点亮第三个LED
3'b010: led <= 6'b001000; // 状态2,点亮第四个LED
3'b011: led <= 6'b010000; // 状态3,点亮第五个LED
3'b100: led <= 6'b100000; // 状态4,点亮第六个LED
3'b101: led <= 6'b000001; // 状态5,回到第一个LED
default: led <= 6'b000001; // 默认状态
endcase
state <= state + 1; // 状态机状态更新
end
end
endmodule
//博主这个代码有点小瑕疵还待完善
代码成功跑通啦!!!
五、功能测试与验证
1. 测试环境搭建
介绍如何在DE2-115开发板上搭建测试环境,包括硬件连接和软件配置。
文件芯片:EP4CE115F29C7
说明如何将编译后的代码下载到开发板上进行测试。
配置芯片:
烧录,运行:
2. 测试过程
1s周期流水灯
运行视频
六、扩展功能(选做)
1. 按键暂停与恢复功能
介绍如何在流水灯实验中增加按键暂停和恢复功能。
提供扩展功能的代码实现,解释按键功能的逻辑和实现方法。
DE2-115 提供了四个按钮开关,每个按钮开关都通过一个施 密特触发器进行了去抖动处理。四个施密特触发器的输出信 号,分别为KEY0、KEY1、KEY2、KEY3,直接连接到了Cyclone
IV E FPGA。当按钮没有 被按下的时候,它的输出是高电平,按下去则给出一个低电平
KEY0:复位
KEY1:按下暂停
代码实现如下:
module LedBlink(
input wire clk, // 50MHz时钟输入
input wire rst_n, // 复位信号,低电平有效
input wire stop_n, //停止信号
output reg [5:0] led // 6个LED灯输出
);
reg [25:0] cnt; // 26位计数器,用于计数1秒周期
// 计数器模块
always @(posedge clk or negedge rst_n or negedge stop_n) begin // posedge是指clk的上升沿 negedge是指rst_n的下降沿
if (!rst_n) begin
cnt <= 26'd0; // 复位时,计数器从0开始计数
led <= 6'b000001; // 复位时,第一个LED亮
end
else if (!stop_n) begin
cnt <= cnt;
led <= led;
end
else if (cnt == 50_000_000 - 1) begin // 计数到50,000,000 - 1
cnt <= 26'd0; // 重置计数器
led <= {led[4:0], led[5]}; // 循环右移一位,保持流水灯效果
end
else begin
cnt <= cnt + 1; // 增加计数器
end
end
endmodule
结果演示:
运行结果如视频:
按键控制流水灯暂停
七、总结
在本次流水灯模块设计实践中,我们系统掌握了从硬件资源分析到软件代码实现的全流程开发方法,深度理解了数字电路中时序控制与逻辑设计的核心原理。学习到很多内容,掌握了多种方式点亮LED,实现LED灯光管理和控制,按键控制。