第一次接触数字电子钟设计时,我完全被各种计数器、分频器和译码器绕晕了。直到把整个系统拆解成几个核心模块,才真正理解了模块化设计的精妙之处。这次我们要做的智能电子钟包含五大功能:基础计时、秒表、闹钟、校时和扩展显示,每个功能都可以独立设计再组合。
模块化设计的核心优势在于调试方便。想象一下,如果所有电路都混在一起,出现问题时根本无从下手。我去年带学生做课设时就遇到过这种情况——一个同学把全部功能集成在单一电路图中,调试花了整整两周。而采用模块化设计,通常一天就能定位问题。
具体到我们的设计,系统架构分为三层:
在Quartus中创建工程时,建议先建立这样的目录结构:
code复制digital_clock/
├── rtl/ // 存放所有Verilog模块
├── sim/ // 仿真文件
├── ip/ // 生成的IP核
└── constraints/ // 引脚约束文件
FPGA开发板上的50MHz晶振频率太高,直接用来计时显然不合适。我们需要设计分频电路将其转换为实用的低频信号。这里有个坑我踩过——直接用计数器分频会产生累积误差,更好的做法是使用锁相环(PLL)结合计数器。
具体实现时,我推荐这种分级分频方案:
verilog复制// 第一级:PLL生成10MHz
pll pll_inst(
.inclk0(CLK_50M),
.c0(CLK_10M)
);
// 第二级:计数器分频
reg [23:0] div_cnt;
always @(posedge CLK_10M) begin
if(div_cnt == 24'd9_999_999) begin
clk_1hz <= ~clk_1hz;
div_cnt <= 0;
end else
div_cnt <= div_cnt + 1;
end
计时功能需要三个关键计数器:
新手常犯的错误是直接使用74390芯片的模10计数功能拼凑模60计数器。我有个更简洁的方案——用Verilog行为级描述:
verilog复制// 模60计数器示例
module counter60(
input clk, rst,
output reg [5:0] count,
output carry
);
always @(posedge clk or posedge rst) begin
if(rst) count <= 0;
else if(count == 59) count <= 0;
else count <= count + 1;
end
assign carry = (count == 59);
endmodule
八位数码管静态显示会占用大量IO口,动态扫描是必选方案。这里有个关键参数要注意:刷新率不能低于60Hz,否则会出现闪烁。我的实测数据显示:
具体电路设计时,dig_select模块需要配合cnt8计数器工作。这里分享一个调试技巧:可以用示波器同时监测位选信号和段选信号,确保它们的时序完全同步。
秒表功能需要处理两个按键:
按键消抖是必须的,我对比过几种方案:
这里给出一个Verilog实现的按键状态机:
verilog复制always @(posedge clk_1khz) begin
case(state)
IDLE: if(!key_in) state <= DEBOUNCE;
DEBOUNCE: begin
if(cnt == 20) begin
if(!key_in) state <= PRESSED;
else state <= IDLE;
end
cnt <= cnt + 1;
end
PRESSED: if(key_in) state <= RELEASE;
RELEASE: state <= IDLE;
endcase
end
闹钟功能最怕误触发,我设计了三种保护机制:
比较模块的电路设计有个小技巧:不要直接比较时、分、秒,而是将它们拼接成一个大数再比较。例如:
verilog复制wire [16:0] current_time = {hour, minute, second};
wire [16:0] alarm_time = {alarm_h, alarm_m, alarm_s};
assign alarm_trigger = (current_time == alarm_time);
校时功能通过四个按键实现:
这里要注意按键优先级设计。我的方案是:
好的工程结构能提升50%的开发效率。我的项目模板包含:
在带学生做课设时,我总结了这些典型问题:
最后分享一个实用技巧:在Quartus中使用SignalTap II逻辑分析仪时,设置采样时钟为系统时钟的1/4,既能捕获足够信息又不会消耗太多资源。