FPGA 数字时钟 Verilog 模块化设计:4大核心模块与 50MHz 分频实战
FPGA 数字时钟 Verilog 模块化设计4大核心模块与 50MHz 分频实战数字时钟作为FPGA入门经典项目看似简单却蕴含模块化设计的精髓。本文将彻底拆解一个完整的数字时钟系统从50MHz主时钟到1Hz秒信号的分频计算再到时分秒计数与数码管显示的完整实现。不同于市面上常见的大杂烩式代码我们将采用严格的模块化设计思想每个功能单元独立封装接口定义清晰可单独验证和复用。1. 系统架构与模块划分一个典型的数字时钟系统包含四大核心模块时钟分频模块将高频系统时钟分频为1Hz基准信号秒计数模块60进制计数器产生秒信号分计数模块60进制计数器产生分信号显示驱动模块数码管动态扫描显示// 顶层模块连接示意图 module top( input clk_50MHz, input reset_n, output [3:0] digit_sel, output [6:0] seg_data ); wire clk_1Hz; wire [3:0] sec_units, sec_tens; wire [3:0] min_units, min_tens; wire carry_sec, carry_min; clk_divider u_clk_div( .clk(clk_50MHz), .reset_n(reset_n), .clk_out(clk_1Hz) ); second_counter u_sec_cnt( .clk(clk_50MHz), .reset_n(reset_n), .enable(clk_1Hz), .sec_units(sec_units), .sec_tens(sec_tens), .carry(carry_sec) ); minute_counter u_min_cnt( .clk(clk_50MHz), .reset_n(reset_n), .enable(carry_sec), .min_units(min_units), .min_tens(min_tens), .carry(carry_min) ); display_driver u_display( .clk(clk_50MHz), .reset_n(reset_n), .sec_units(sec_units), .sec_tens(sec_tens), .min_units(min_units), .min_tens(min_tens), .digit_sel(digit_sel), .seg_data(seg_data) ); endmodule这种模块化设计带来三大优势可复用性每个模块可独立用于其他项目易调试可单独验证每个模块功能可扩展性方便添加新功能如小时计数2. 50MHz到1Hz的分频设计FPGA开发板通常提供50MHz系统时钟而数字时钟需要1Hz的秒脉冲信号。我们需要设计一个分频系数为50,000,000的分频器。分频参数计算输入时钟频率50MHz (周期20ns)目标频率1Hz (周期1s)分频比N 50MHz/1Hz 50,000,000由于采用双边沿检测实际计数次数 N/2 25,000,000module clk_divider( input clk, input reset_n, output reg clk_out ); parameter DIVISOR 26d25_000_000; reg [25:0] counter; always (posedge clk or negedge reset_n) begin if (!reset_n) begin counter 0; clk_out 0; end else if (counter DIVISOR - 1) begin counter 0; clk_out ~clk_out; end else begin counter counter 1; end end endmodule实际工程中建议使用PLL进行高频分频本文计数器分频方式更适合教学演示。当分频比过大时计数器会占用较多逻辑资源。3. 秒计数器模块设计秒计数器是一个60进制BCD码计数器需要分别对个位和十位进行计数个位0-9循环计数到9时产生进位十位0-5循环计数接收个位进位module second_counter( input clk, input reset_n, input enable, output reg [3:0] sec_units, output reg [3:0] sec_tens, output reg carry ); always (posedge clk or negedge reset_n) begin if (!reset_n) begin sec_units 0; sec_tens 0; carry 0; end else if (enable) begin if (sec_units 4d9) begin sec_units 0; if (sec_tens 4d5) begin sec_tens 0; carry 1; end else begin sec_tens sec_tens 1; carry 0; end end else begin sec_units sec_units 1; carry 0; end end else begin carry 0; end end endmodule关键设计要点采用同步使能设计只在enable为高时计数严格遵循BCD编码规则防止出现非法状态进位信号只维持一个时钟周期4. 分计数器模块设计分计数器与秒计数器结构类似但需要特别注意进位信号的同步处理module minute_counter( input clk, input reset_n, input enable, output reg [3:0] min_units, output reg [3:0] min_tens, output reg carry ); reg enable_sync; // 同步使能信号检测上升沿 always (posedge clk or negedge reset_n) begin if (!reset_n) begin enable_sync 0; end else begin enable_sync enable; end end wire enable_pos enable ~enable_sync; always (posedge clk or negedge reset_n) begin if (!reset_n) begin min_units 0; min_tens 0; carry 0; end else if (enable_pos) begin if (min_units 4d9) begin min_units 0; if (min_tens 4d5) begin min_tens 0; carry 1; end else begin min_tens min_tens 1; carry 0; end end else begin min_units min_units 1; carry 0; end end else begin carry 0; end end endmodule与秒计数器的主要区别增加了使能信号的上升沿检测防止因使能信号过长导致多次计数同样采用BCD编码和单周期进位5. 数码管显示驱动设计数码管显示需要解决两个核心问题BCD码到7段码的译码多位数码管的动态扫描7段数码管编码表数字g f e d c b a十六进制00 1 1 1 1 1 10x3F10 0 0 0 1 1 00x0621 0 1 1 0 1 10x5B31 0 0 1 1 1 10x4F41 1 0 0 1 1 00x6651 1 0 1 1 0 10x6D61 1 1 1 1 0 10x7D70 0 0 0 1 1 10x0781 1 1 1 1 1 10x7F91 1 0 1 1 1 10x6Fmodule display_driver( input clk, input reset_n, input [3:0] sec_units, input [3:0] sec_tens, input [3:0] min_units, input [3:0] min_tens, output reg [3:0] digit_sel, output reg [6:0] seg_data ); reg [1:0] scan_cnt; reg [15:0] refresh_cnt; wire [3:0] digit_data; // 扫描计数器 always (posedge clk or negedge reset_n) begin if (!reset_n) begin refresh_cnt 0; scan_cnt 0; end else if (refresh_cnt 16d12500) begin // 200Hz刷新率 refresh_cnt 0; scan_cnt scan_cnt 1; end else begin refresh_cnt refresh_cnt 1; end end // 数码管选择与数据多路复用 always (*) begin case (scan_cnt) 2b00: begin digit_sel 4b1110; digit_data sec_units; end 2b01: begin digit_sel 4b1101; digit_data sec_tens; end 2b10: begin digit_sel 4b1011; digit_data min_units; end 2b11: begin digit_sel 4b0111; digit_data min_tens; end endcase end // BCD到7段译码 always (*) begin case (digit_data) 4d0: seg_data 7b0111111; 4d1: seg_data 7b0000110; 4d2: seg_data 7b1011011; 4d3: seg_data 7b1001111; 4d4: seg_data 7b1100110; 4d5: seg_data 7b1101101; 4d6: seg_data 7b1111101; 4d7: seg_data 7b0000111; 4d8: seg_data 7b1111111; 4d9: seg_data 7b1101111; default: seg_data 7b0000000; endcase end endmodule显示驱动关键参数扫描频率200Hz (人眼无闪烁)每位显示时间5ms共阳极数码管低电平有效6. 功能验证与调试技巧模块化设计的优势在于可以单独验证每个模块。推荐以下验证流程分频模块测试仿真验证分频比是否正确用LED观察输出时钟频率// 分频模块测试代码 initial begin $dumpfile(clk_divider.vcd); $dumpvars(0, testbench); #100000000 $finish; // 仿真100ms end计数器模块测试验证60进制计数序列检查进位信号时序测试复位功能显示模块测试检查数码管扫描顺序验证7段译码正确性测量扫描频率常见问题排查数码管显示乱码检查BCD到7段译码表显示闪烁调整扫描频率计数不准检查使能信号同步资源占用过高优化计数器位宽7. 扩展与优化方向基础功能实现后可以考虑以下增强功能时间设置功能添加按键消抖模块实现时分秒的手动调整闹钟功能增加闹钟时间寄存器比较器触发报警输出显示优化添加小数点显示实现12/24小时制切换低功耗设计时钟门控技术动态频率调整// 时间设置功能示例 module time_set( input clk, input reset_n, input set_min, input set_sec, output reg [3:0] min_units, output reg [3:0] min_tens, output reg [3:0] sec_units, output reg [3:0] sec_tens ); // 按键消抖逻辑 debounce u_debounce_min( .clk(clk), .reset_n(reset_n), .key_in(set_min), .key_out(set_min_db) ); // 分钟设置逻辑 always (posedge clk or negedge reset_n) begin if (!reset_n) begin min_units 0; min_tens 0; end else if (set_min_db) begin if (min_units 4d9) begin min_units 0; if (min_tens 4d5) min_tens 0; else min_tens min_tens 1; end else begin min_units min_units 1; end end end endmodule通过模块化设计这些扩展功能可以轻松集成到现有系统中而无需重构整个设计。这正是模块化设计的最大价值所在。

相关新闻