基于AX530开发板的Verilog数字钟实战:从模块化设计到整点报时
在FPGA开发领域,数字钟项目堪称"Hello World"般的存在,但真正要实现一个功能完善、稳定可靠的数字钟系统,却需要开发者对数字逻辑设计有深入理解。本文将手把手带你用Verilog HDL在AX530开发板上构建一个具备闹钟和整点报时功能的数字钟系统,从Quartus II 13.0工程创建到最终下载调试,完整呈现项目开发全流程。
1. 项目规划与硬件准备
AX530开发板作为一款性价比较高的FPGA学习平台,搭载了Cyclone IV EP4CE6F17C8芯片,板载资源包括:
- 6位共阳数码管
- 4个独立按键
- 多个LED指示灯
- 50MHz系统时钟
核心功能需求分析:
- 24小时制时间显示(时:分:秒)
- 按键校时功能(时/分可调)
- 闹钟设置与触发(LED指示)
- 整点报时功能(LED闪烁对应次数)
- 系统复位功能(归零)
硬件连接方案:
| 功能模块 | FPGA引脚 | 开发板资源 |
|---|---|---|
| 数码管段选 | PIN_XX | SMG_A~H |
| 数码管位选 | PIN_XX | DIG1~DIG6 |
| 按键输入 | PIN_XX | KEY1~KEY3 |
| LED输出 | PIN_XX | LED1~LED3 |
2. 系统架构设计与模块划分
采用自顶向下的设计方法,将系统分解为多个功能模块,通过顶层模块实现互联。这种模块化设计不仅便于调试,也符合工业级FPGA开发规范。
2.1 顶层模块设计
顶层模块digclk.v主要实现各子模块的信号连接:
module Digclk( input clk, // 50MHz系统时钟 input rst_n, // 复位信号(低有效) input [2:0] btn, // 按键输入[切换,移位,加一] output [3:0] led, // LED输出[秒针,闹钟,整点] output [7:0] smg_sig, // 数码管段选 output [5:0] smg_loc // 数码管位选 ); // 状态控制信号线 wire [1:0] mode; // 工作模式 wire [3:0] set_loc; // 设置位置 wire set_inc; // 加一信号 // 实例化各子模块 Ctrl u_ctrl(...); Time u_time(...); Alarm u_alarm(...); Display u_display(...); endmodule2.2 关键子模块功能说明
状态控制模块(Ctrl):
- 处理按键输入(消抖后)
- 生成当前工作模式信号
- 输出校时位置和加一脉冲
计时模块(Time):
- 实现时分秒计数器
- 处理校时逻辑
- 生成秒脉冲和整点报时信号
闹钟模块(Alarm):
- 存储闹钟设置时间
- 比较当前时间与闹钟时间
- 触发闹钟信号
显示模块(Display):
- 根据模式选择显示内容
- 驱动数码管显示
3. 核心代码实现与技巧
3.1 按键消抖模块优化
机械按键存在10-20ms的抖动期,采用状态机实现的消抖模块更可靠:
module Filter( input clk, input rst_n, input btn, output reg btn_f ); parameter IDLE = 2'b00; parameter DEBOUNCE = 2'b01; parameter PRESS = 2'b10; reg [19:0] cnt; // 20ms计数器 reg [1:0] state; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin state <= IDLE; btn_f <= 1'b0; end else begin case(state) IDLE: if(!btn) begin state <= DEBOUNCE; cnt <= 0; end DEBOUNCE: if(cnt == 20'd999_999) begin // 20ms@50MHz state <= PRESS; btn_f <= 1'b1; end else begin cnt <= cnt + 1; if(btn) state <= IDLE; end PRESS: if(btn) begin state <= IDLE; btn_f <= 1'b0; end endcase end end endmodule3.2 计时模块的BCD计数器实现
采用BCD码计数器可简化数码管显示逻辑:
// 秒个位计数器示例 reg [3:0] cnt_s0; always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt_s0 <= 4'd0; else if(en_s0) cnt_s0 <= (cnt_s0 == 4'd9) ? 4'd0 : cnt_s0 + 1; end // 产生秒十位使能信号 assign en_s1 = (cnt_s0 == 4'd9) && en_s0;3.3 整点报时功能实现
整点报时需要两个关键技术点:
- 整点检测:当分为59且秒为59时准备触发
- 闪烁控制:使用计数器控制LED闪烁次数
// 整点检测逻辑 wire hour_strike = (cnt_h1*10 + cnt_h0 != 0) && (cnt_m1 == 4'd5) && (cnt_m0 == 4'd9) && (cnt_s1 == 4'd5) && (cnt_s0 == 4'd9); // 闪烁控制计数器 reg [5:0] strike_cnt; always @(posedge clk or negedge rst_n) begin if(!rst_n) strike_cnt <= 6'd0; else if(hour_strike) strike_cnt <= 6'd0; else if(strike_cnt < (cnt_h1*10 + cnt_h0)*2) strike_cnt <= strike_cnt + 1; end // LED控制 assign hour_led = (strike_cnt < (cnt_h1*10 + cnt_h0)*2) ? strike_cnt[0] : 1'b0;4. Quartus II工程配置与下载调试
4.1 工程创建关键步骤
新建工程:
- 选择Cyclone IV E系列
- 指定器件型号EP4CE6F17C8
- 添加所有Verilog源文件
引脚分配:
- 根据开发板原理图分配引脚
- 保存为AX530.qsf约束文件
编译设置:
- 优化策略选择Balanced
- 开启SignalTap II逻辑分析仪支持
4.2 常见问题解决方案
数码管显示闪烁:
- 检查扫描频率(推荐200Hz-1kHz)
- 确保位选信号切换与数据更新同步
按键响应不灵敏:
- 调整消抖时间常数(15-20ms为宜)
- 检查按键引脚上拉电阻配置
计时不准:
- 验证时钟分频计算
- 使用SignalTap抓取实际计数器波形
# 示例引脚约束(部分) set_location_assignment PIN_E1 -to clk set_location_assignment PIN_M1 -to rst_n set_location_assignment PIN_E16 -to btn[0] set_location_assignment PIN_E15 -to btn[1] set_location_assignment PIN_M2 -to smg_sig[0]5. 功能扩展与优化建议
5.1 可扩展功能方向
增加日期显示:
- 扩展计时模块支持年月日
- 添加闰年判断逻辑
多组闹钟:
- 使用寄存器堆存储多组闹钟时间
- 添加闹钟使能控制位
串口配置:
- 通过UART接口设置时间/闹钟
- 实现PC端配置工具
5.2 性能优化技巧
时钟域处理:
- 对按键输入进行同步化处理
- 添加跨时钟域同步寄存器
低功耗设计:
- 动态数码管扫描亮度调节
- 时钟门控技术应用
代码优化:
- 参数化设计关键时间常数
- 使用generate语句简化相似逻辑
// 参数化设计示例 parameter CLK_FREQ = 50_000_000; // 50MHz parameter SEC_DIV = CLK_FREQ - 1; // 秒脉冲生成 reg [25:0] sec_cnt; always @(posedge clk or negedge rst_n) begin if(!rst_n) sec_cnt <= 26'd0; else sec_cnt <= (sec_cnt == SEC_DIV) ? 26'd0 : sec_cnt + 1; end wire sec_pulse = (sec_cnt == SEC_DIV);6. 项目总结与进阶思考
完成这个数字钟项目后,建议尝试以下进阶练习:
- 将各模块改为使用状态机实现
- 添加自动亮度调节功能(根据环境光)
- 实现通过红外遥控器设置时间
在调试过程中发现,良好的模块划分可以显著降低调试难度。例如将显示驱动与业务逻辑分离后,当出现显示问题时可以快速定位到显示模块进行单独测试。