从零到一:用Verilog在DE2-115上构建你的第一个数字钟
还记得第一次看到七段数码管亮起时的兴奋吗?那跳动的数字不仅仅是电流的舞蹈,更是逻辑与时间的完美交融。如果你刚接触FPGA开发,想要找一个既有趣又实用的项目来练手,数字钟绝对是你的不二之选。它涵盖了数字电路设计的核心概念:时钟分频、状态控制、计数器设计、显示驱动,甚至还包括了一些高级功能如闹钟和时间校准。本文将带你从零开始,一步步在DE2-115开发板上实现一个功能完整的数字钟,无论你是电子工程的学生,还是对硬件设计感兴趣的开发者,都能从中获得扎实的实践技能。
我们将使用Verilog这一硬件描述语言,搭配Intel的Quartus Prime开发环境,从最基本的时钟分频开始,逐步构建起时、分、秒的计数逻辑,添加时间设置和闹钟功能,最终驱动开发板上的数码管显示。过程中,我会分享一些实际开发中容易踩坑的细节,比如按键消抖的处理、多时钟域的设计注意事项,以及如何高效地进行仿真测试。不用担心你是初学者,只要具备基本的数字电路知识,就能跟上这个项目的节奏。
1. 开发环境搭建与项目初始化
在开始编写代码之前,我们需要准备好开发环境。DE2-115开发板搭载了Cyclone IV EP4CE115F29C7 FPGA芯片,这意味着我们需要安装与之兼容的Quartus Prime版本。建议使用Quartus Prime Lite Edition 18.1或更高版本,这个版本对教育用途免费,并且支持DE2-115开发板的所有特性。
安装完成后,第一步是创建一个新的Quartus项目。打开Quartus Prime,选择File > New Project Wizard,按照向导步骤操作。在设备选择页面,务必正确选择芯片型号:Cyclone IV E系列,EP4CE115F29C7。这一步很重要,因为不同的FPGA芯片其资源规模和引脚定义都不相同,选错了会导致后续编译和烧录失败。
项目创建好后,我们需要添加Verilog源文件。数字钟项目通常采用模块化设计,建议为每个主要功能创建独立的.v文件:
// 项目主要模块
module digital_clock(
input clk_50M, // 50MHz主时钟
input reset_n, // 复位信号(低有效)
input [1:0] sw_mode, // 模式选择开关
input key_hour, // 小时调整按键
input key_minute, // 分钟调整按键
output [6:0] hex0, // 数码管段选信号
output [6:0] hex1,
output [6:0] hex2,
output [6:0] hex3,
output [6:0] hex4,
output [6:0] hex5,
output alarm_led // 闹钟提示LED
);
// 模块内部信号和实例化将在这里实现
endmodule
在开始编码前,建议先规划好项目的目录结构。一个良好的结构可以大大提高开发效率:
project_root/
│
├── src/ # Verilog源代码
│ ├── digital_clock_top.v
│ ├── clock_divider.v
│ ├── time_counter.v
│ ├── alarm_module.v
│ ├── display_driver.v
│ └── debounce.v
│
├── sim/ # 仿真文件
│ └── tb_digital_clock.v
│
├── quartus/ # Quartus项目文件
│ ├── digital_clock.qpf
│ └── digital_clock.qsf
│
└── doc/ # 文档和笔记
└── pin_assignment.md
提示:在项目初期就建立良好的版本控制习惯。使用Git管理你的代码,定期提交并撰写清晰的commit message,这会在后期调试和功能扩展时节省大量时间。
2. 时钟分频与按键消抖处理
DE2-115开发板提供的50MHz时钟信号对于数字钟来说太快了,我们需要将其分频到1Hz(每秒一个脉冲)来驱动秒计数器。时钟分频是FPGA设计中的基础操作,但实现方式有多种选择,每种都有其适用场景。
最简单的分频方法是使用计数器进行整数分频:
module clock_divider(
input clk_50M,
input reset_n,
output reg clk_1Hz
);
// 50MHz到1Hz需要分频50,000,000倍
reg [25:0] counter; // 26位计数器,最大计数值67,108,863
always @(posedge clk_50M or negedge reset_n) begin
if (!reset_n) begin
counter <= 0;
clk_1Hz <= 0;
end
else if (counter == 26'd49_999_999) begin
counter <= 0;
clk_1Hz <= ~clk_1Hz;
end
else begin
counter <= counter + 1;
end
end
endmodule
这种直接计数的方法简单直观,但需要注意计数器的位宽要足够大,否则会导致分频错误。26位计数器最大可计数67,108,863,刚好满足50,000,000的分频需求。
在实际应用中,机械按键会产生抖动现象,即按下或释放时信号会快速跳变多次后才稳定。如果不处理这种抖动,一次按键操作可能会被误识别为多次操作。我们需要添加消抖模块:
module debounce(
input clk,
input button_in,
output reg button_out
);
parameter DEBOUNCE_LIMIT = 16'd50000; // 50MHz时钟下约1ms消抖时间
reg [15:0] count;
reg button_state;
always @(posedge clk) begin
if (button_in != button_state) begin
// 输入状态变化,重置计数器
button_state <= button_in;
count <= 0;
end
else if (count == DEBOUNCE_LIMIT) begin
// 稳定时间达到阈值,输出稳定状态
button_out <= button_state;
end
else begin
// 继续计数
count <= count + 1;
end
end
endmodule
消抖原理很简单:当检测到按键状态变化时,开始计时,只有状态保持一定时间(通常是1-20ms)后才认为变化有效。这样可以过滤掉机械抖动产生的高频噪声。
| 参数名称 | 推荐值 | 说明 |
|---|---|---|
| DEBOUNCE_LIMIT | 50,000 | 50MHz时钟下对应1ms消抖时间 |
| 计数器位宽 | 16位 | 足够计到65,535,满足消抖需求 |
| 采样频率 | 50MHz | 使用系统主时钟即可 |

698

被折叠的 条评论
为什么被折叠?



