1. 项目概述:FPGA数字钟的设计初衷
去年在教授数字逻辑课程时,我给学生布置了一个特别的期末作业——用SystemVerilog在Basys3开发板上实现一个完整的数字钟。这个看似简单的项目实际上涵盖了数字电路设计的核心知识点:从基础的组合逻辑到时序电路设计,从有限状态机到外设接口控制。Basys3开发板搭载的Xilinx Artix-7 FPGA为学生提供了理想的实践平台,板载的4位7段数码管、按钮和开关等外设正好满足数字钟的显示和设置需求。
这个项目的独特之处在于,它既是一个完整的嵌入式系统原型,又能让学生体验从硬件描述语言编码到实际硬件验证的全流程。通过设计这个数字钟,学生需要掌握时钟分频、按键消抖、BCD计数器、动态扫描显示等关键技术,这些都是在工业级FPGA设计中经常遇到的典型问题。
2. 硬件平台与开发环境配置
2.1 Basys3开发板关键资源分析
Digilent Basys3开发板作为教学级FPGA开发平台,其核心是一颗Xilinx Artix-7 XC7A35T-1CPG236C FPGA芯片。对于我们的数字钟项目,需要重点关注以下硬件资源:
- 时钟源:板载100MHz晶体振荡器,需要通过分频电路得到1Hz的基准时钟
- 显示设备:4位共阳极7段数码管(配置为复用模式)
- 输入设备:5个瞬时按键(其中4个可配置为方向键)和16个拨码开关
- 其他资源:16个LED指示灯(可用于显示状态)
注意:Basys3的7段数码管采用动态扫描方式工作,每个位选信号持续约1ms,设计显示驱动时需要特别注意刷新频率与亮度平衡。
2.2 Vivado开发环境搭建
推荐使用Xilinx Vivado 2018.3版本(与Basys3官方教程保持一致),安装时需注意:
- 选择"WebPACK"免费版本即可满足需求
- 安装时勾选"Artix-7"器件支持
- 额外安装Digilent板级支持包(Board Files)
创建新项目时的关键配置:
tcl复制create_project digital_clock -part xc7a35tcpg236-1
set_property board_part digilentinc.com:basys3:part0:1.1 [current_project]
3. 数字钟系统架构设计
3.1 顶层模块划分
整个数字钟系统采用层次化设计,主要模块及其功能如下:
| 模块名称 | 功能描述 | 关键信号 |
|---|---|---|
| clk_divider | 将100MHz时钟分频为1Hz基准 | clk_100MHz, clk_1Hz |
| button_debounce | 按键消抖处理(设置时间用) | btn_raw, btn_clean |
| time_counter | 时分秒BCD计数器(支持进位) | sec, min, hour |
| display_driver | 动态扫描7段数码管显示控制 | anodes, cathodes |
| alarm_control | 闹钟功能模块(可选扩展) | alarm_en, alarm_time |
3.2 时钟分频方案选择
从100MHz到1Hz的分频比高达100,000,000:1,直接使用计数器分频会导致资源浪费。我们采用分级分频方案:
- 首先分频到1kHz(用于数码管扫描)
- 再分频到1Hz(基准时钟)
SystemVerilog实现示例:
systemverilog复制module clk_divider (
input logic clk_100MHz,
output logic clk_1kHz,
output logic clk_1Hz
);
logic [16:0] cnt_kHz;
logic [9:0] cnt_Hz;
always_ff @(posedge clk_100MHz) begin
if (cnt_kHz == 100_000 - 1) begin
cnt_kHz <= 0;
clk_1kHz <= ~clk_1kHz;
end else begin
cnt_kHz <= cnt_kHz + 1;
end
if (clk_1kHz) begin
if (cnt_Hz == 500 - 1) begin
cnt_Hz <= 0;
clk_1Hz <= ~clk_1Hz;
end else begin
cnt_Hz <= cnt_Hz + 1;
end
end
end
endmodule
4. 核心功能模块实现细节
4.1 时间计数器的设计技巧
时分秒计数器需要处理多种边界条件:
- 秒计数:59→00时触发分进位
- 分计数:59→00时触发时进位
- 时计数:23→00(24小时制)或11→12(12小时制)
推荐使用BCD码计数方式,便于直接驱动数码管显示。SystemVerilog实现示例:
systemverilog复制module time_counter (
input logic clk_1Hz,
input logic reset,
output logic [3:0] sec_units, sec_tens,
output logic [3:0] min_units, min_tens,
output logic [3:0] hour_units, hour_tens
);
always_ff @(posedge clk_1Hz or posedge reset) begin
if (reset) begin
{sec_tens, sec_units} <= 8'h00;
{min_tens, min_units} <= 8'h00;
{hour_tens, hour_units} <= 8'h12; // 初始12:00:00
end else begin
// 秒计数逻辑
if (sec_units == 9) begin
sec_units <= 0;
if (sec_tens == 5) begin
sec_tens <= 0;
// 分计数逻辑...
end else begin
sec_tens <= sec_tens + 1;
end
end else begin
sec_units <= sec_units + 1;
end
// 分和时计数逻辑类似...
end
end
endmodule
4.2 按键消抖的可靠实现
机械按键存在5-10ms的抖动期,必须进行消抖处理。我们采用"采样滤波"法:
- 以1kHz频率采样按键信号
- 连续16次采样值相同才确认状态变化
systemverilog复制module button_debounce (
input logic clk_1kHz,
input logic btn_raw,
output logic btn_clean
);
logic [3:0] shift_reg;
always_ff @(posedge clk_1kHz) begin
shift_reg <= {shift_reg[2:0], btn_raw};
if (&shift_reg) btn_clean <= 1; // 所有位为1
else if (~|shift_reg) btn_clean <= 0; // 所有位为0
end
endmodule
5. 显示驱动设计与优化
5.1 动态扫描显示原理
Basys3的4位数码管共享8段信号线,通过位选信号(AN0-AN3)轮流点亮。设计要点:
- 刷新率应>60Hz(每位显示时间约1ms)
- 需要将BCD码转换为7段编码
- 注意消隐处理避免"鬼影"
显示驱动状态机设计:
systemverilog复制module display_driver (
input logic clk_1kHz,
input logic [3:0] hour_tens, hour_units,
input logic [3:0] min_tens, min_units,
output logic [3:0] anodes,
output logic [7:0] cathodes
);
logic [1:0] sel;
logic [3:0] bcd;
always_ff @(posedge clk_1kHz) begin
sel <= sel + 1;
case(sel)
2'b00: begin anodes <= 4'b1110; bcd <= hour_tens; end
2'b01: begin anodes <= 4'b1101; bcd <= hour_units; end
2'b10: begin anodes <= 4'b1011; bcd <= min_tens; end
2'b11: begin anodes <= 4'b0111; bcd <= min_units; end
endcase
case(bcd) // 共阳极7段译码
4'h0: cathodes <= 8'b11000000;
4'h1: cathodes <= 8'b11111001;
// ...其他数字编码
default: cathodes <= 8'b11111111;
endcase
end
endmodule
5.2 亮度均衡技巧
由于动态扫描时各数码管点亮时间不同,会导致亮度不均。解决方法:
- 使用PWM调节各段电流
- 在段选信号上叠加高频抖动信号
- 优化扫描时序,确保各段导通时间一致
6. 时间设置功能的实现
6.1 状态机设计
通过三个按键实现时间设置:
- MODE键:切换设置模式(时/分/正常显示)
- UP键:当前设置位加1
- DOWN键:当前设置位减1
systemverilog复制typedef enum logic [1:0] {
NORMAL,
SET_HOUR,
SET_MINUTE
} mode_state;
module time_set (
input logic clk_1kHz,
input logic btn_mode,
input logic btn_up,
input logic btn_down,
output mode_state curr_mode
);
mode_state next_mode;
logic btn_mode_clean;
button_debounce deb_mode(clk_1kHz, btn_mode, btn_mode_clean);
always_ff @(posedge clk_1kHz) begin
if (btn_mode_clean) begin
case(curr_mode)
NORMAL: next_mode <= SET_HOUR;
SET_HOUR: next_mode <= SET_MINUTE;
SET_MINUTE: next_mode <= NORMAL;
endcase
end
curr_mode <= next_mode;
end
endmodule
6.2 设置时的视觉反馈
在设置模式下,可以让当前设置的数字闪烁显示(每秒切换显示/消隐):
systemverilog复制logic blink;
always_ff @(posedge clk_1Hz) blink <= ~blink;
// 修改显示驱动
if ((sel==2'b00 && curr_mode==SET_HOUR) ||
(sel==2'b10 && curr_mode==SET_MINUTE)) begin
cathodes <= blink ? 8'b11111111 : seg_data;
end
7. 功能扩展与进阶设计
7.1 闹钟功能实现
添加闹钟功能需要:
- 扩展寄存器存储闹钟时间
- 比较当前时间与闹钟时间
- 添加蜂鸣器驱动电路
systemverilog复制module alarm_control (
input logic clk_1kHz,
input logic [3:0] hour, min,
input logic [3:0] alarm_hour, alarm_min,
input logic alarm_en,
output logic buzzer
);
logic match;
logic [9:0] tone_cnt;
assign match = (hour==alarm_hour) && (min==alarm_min);
always_ff @(posedge clk_1kHz) begin
if (match && alarm_en) begin
tone_cnt <= (tone_cnt == 500) ? 0 : tone_cnt + 1;
buzzer <= (tone_cnt < 250);
end else begin
buzzer <= 0;
end
end
endmodule
7.2 通过UART同步时间
添加CH340 USB转串口芯片驱动,实现PC时间同步:
- 定义简单的串口协议(如"HH:MM:SS\n"格式)
- 添加接收状态机和数据解析
- 验证波特率误差(在100MHz时钟下生成115200bps)
8. 调试技巧与常见问题
8.1 典型问题排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 数码管显示不全 | 位选信号时序错误 | 检查AN信号使能时间和相位 |
| 时间走时不准 | 时钟分频计算错误 | 验证计数器终值和时钟源频率 |
| 按键响应不灵敏 | 消抖参数不合适 | 调整采样频率和稳定次数阈值 |
| 显示有重影 | 消隐时间不足 | 在段选变化前增加1us消隐间隔 |
8.2 SignalTap逻辑分析仪使用
当硬件行为与仿真不一致时,使用SignalTap进行实时调试:
- 添加需要观察的信号(如时钟、计数器值)
- 设置触发条件(如按键按下)
- 配置采样深度(通常1024点足够)
- 注意采样时钟选择(通常用系统主时钟)
调试心得:在验证动态扫描显示时,我发现将刷新率从200Hz提高到1kHz后,显示稳定性明显改善。这是因为更高的刷新率减少了人眼可察觉的闪烁,同时每个LED的导通时间更接近其标称参数。
9. 工程优化与资源利用
9.1 资源占用分析
使用Vivado综合后查看资源报告:
- LUT使用:约120个(Artix-7有33,280个)
- 寄存器使用:约80个
- 块存储器:0个
优化方向:
- 将部分组合逻辑改为时序逻辑
- 共享相同功能的子模块
- 使用参数化设计方便复用
9.2 功耗估算与优化
静态功耗:约15mW
动态功耗:主要来自:
- 数码管驱动(每个段约5mA,共约50mA)
- FPGA核心(约20mA)
降低功耗的方法:
- 降低数码管亮度(减小限流电阻)
- 在不需要时关闭显示
- 使用时钟门控技术
10. 项目总结与扩展思考
这个数字钟项目虽然基础,但涵盖了FPGA开发的完整流程。在实际教学中,学生最常见的困难来自三个方面:时钟域交叉处理、机械按键消抖实现,以及动态扫描显示的时序控制。通过这个项目,学生能够建立起对同步数字系统设计的直观认识。
对于想进一步挑战的同学,可以考虑以下扩展:
- 添加温度显示功能(通过I2C接口连接传感器)
- 实现网络时间协议(NTP)同步
- 开发图形化界面配置工具(通过USB HID)
- 加入语音报时功能(使用PWM驱动扬声器)
从工程实践角度看,这个项目最值得关注的不是功能的复杂度,而是如何确保在各种边界条件下的可靠运行。比如处理23:59:59到00:00:00的过渡,或者用户在设置时间时快速连续按键的情况。这些细节才真正体现出一个电子工程师的专业素养。