Verilog BASYS2 数字钟:12模块化设计实现5大功能(含完整仿真波形)

在FPGA开发领域,数字钟设计一直是验证硬件描述语言掌握程度的经典项目。本文将深入剖析基于BASYS2开发板的Verilog数字钟系统,通过12个精心设计的模块实现计时、校时、闹钟等五大核心功能。不同于简单的代码堆砌,我们将从工程架构角度揭示模块化设计的精髓,并附赠完整的仿真波形解读。

1. 系统架构设计与模块划分

一个健壮的数字钟系统需要将复杂功能分解为可管理的子模块。我们的设计采用分层架构,将系统划分为以下12个功能模块:

  • 时钟分频模块 :将板载50MHz时钟分频为1Hz基准信号
  • 校时控制模块 :处理用户输入的校时操作
  • 计时核心模块 :包含时、分、秒三个计数器
  • 闹钟功能模块 :实现定时提醒与整点报时
  • 显示处理模块 :包含扫描控制、数据选择和七段译码

各模块通过清晰的接口定义进行数据交互,顶层模块采用结构化例化方式连接各子模块。这种设计使得单个模块的修改不会影响整体系统,大幅提升代码可维护性。

1.1 顶层模块接口设计

顶层模块作为系统的"中枢神经",需要明确定义所有外部接口和内部互连信号:

module top(
    input clk_50MHz,      // 板载50MHz时钟
    input reset,          // 全局复位
    input set_time,       // 校时使能
    input adjust_hour,    // 小时调整
    input adjust_min,     // 分钟调整
    input alarm_set,      // 闹钟设置
    output [6:0] seg,     // 七段显示
    output [3:0] an,      // 位选信号
    output alarm_led      // 闹钟指示灯
);

1.2 关键数据流分析

系统数据流可分为三个主要路径:

  1. 计时路径 :分频模块 → 秒计数器 → 分计数器 → 时计数器
  2. 校时路径 :按钮输入 → 消抖处理 → 校时逻辑 → 计数器置位
  3. 显示路径 :时间数据 → 扫描控制 → 数据选择 → 七段译码 → 数码管

这种数据流分离设计确保了各功能模块的独立性,避免信号交叉干扰。

2. 核心模块实现细节

2.1 可配置分频器设计

分频模块需要将50MHz主时钟转换为1Hz计时脉冲,传统做法是使用50,000,000分频。但我们采用更灵活的参数化设计:

module clock_divider #(
    parameter DIVISOR = 50_000_000
)(
    input clk_in,
    output reg clk_out
);
    reg [31:0] counter;
    
    always @(posedge clk_in) begin
        if(counter >= (DIVISOR-1))
            counter <= 0;
        else 
            counter <= counter + 1;
            
        clk_out <= (counter < DIVISOR/2) ? 1'b1 : 1'b0;
    end
endmodule

提示:仿真时可减小DIVISOR参数值加速验证过程,实际部署时恢复为50,000,000

2.2 智能校时控制系统

校时模块需要处理机械按钮的抖动问题,并实现三种校时模式:

module time_adjust(
    input clk,
    input set_en,
    input adj_hour,
    input adj_min,
    output reg [5:0] hour_set,
    output reg [5:0] min_set
);
    // 按钮消抖逻辑
    reg [19:0] hour_debounce, min_debounce;
    wire hour_pulse, min_pulse;
    
    assign hour_pulse = (hour_debounce == 20'hFFFFF);
    assign min_pulse = (min_debounce == 20'hFFFFF);
    
    always @(posedge clk) begin
        hour_debounce <= {hour_debounce[18:0], adj_hour};
        min_debounce <= {min_debounce[18:0], adj_min};
        
        if(set_en) begin
            if(hour_pulse) hour_set <= (hour_set == 23) ? 0 : hour_set + 1;
            if(min_pulse) min_set <= (min_set == 59) ? 0 : min_set + 1;
        end
    end
endmodule

2.3 可变进制计数器实现

时、分、秒计数器虽然功能相似,但进制要求不同。我们采用统一的计数器模板:

module counter #(
    parameter MAX_VAL = 59
)(
    input clk,
    input reset,
    input [5:0] set_val,
    input set_en,
    output reg [5:0] count,
    output reg carry
);
    always @(posedge clk or posedge reset) begin
        if(reset) begin
            count <= 0;
            carry <= 0;
        end
        else if(set_en) begin
            count <= set_val;
            carry <= 0;
        end
        else begin
            if(count == MAX_VAL) begin
                count <= 0;
                carry <= 1;
            end
            else begin
                count <= count + 1;
                carry <= 0;
            end
        end
    end
endmodule

实例化时只需修改MAX_VAL参数:

counter #(.MAX_VAL(59)) minute_counter( ... );
counter #(.MAX_VAL(23)) hour_counter( ... );

3. 动态显示系统优化

3.1 四位数码管扫描控制

BASYS2开发板使用四位共阳极数码管,需要通过动态扫描实现稳定显示:

module display_scan(
    input clk,
    input [3:0] digit0, digit1, digit2, digit3,
    output reg [3:0] an,
    output reg [6:0] seg,
    output reg [3:0] current_digit
);
    reg [1:0] scan_count;
    reg [15:0] refresh_counter;
    
    always @(posedge clk) begin
        refresh_counter <= refresh_counter + 1;
        if(refresh_counter == 16'hFFFF) begin
            scan_count <= scan_count + 1;
            case(scan_count)
                2'b00: begin an <= 4'b1110; current_digit <= digit0; end
                2'b01: begin an <= 4'b1101; current_digit <= digit1; end
                2'b10: begin an <= 4'b1011; current_digit <= digit2; end
                2'b11: begin an <= 4'b0111; current_digit <= digit3; end
            endcase
        end
    end
    
    // 七段译码逻辑
    always @(*) begin
        case(current_digit)
            4'h0: seg = 7'b1000000; // 0
            4'h1: seg = 7'b1111001; // 1
            // ... 其他数字译码
            default: seg = 7'b1111111; // 全灭
        endcase
    end
endmodule

3.2 显示数据选择器

根据当前模式选择显示内容(时钟或闹钟设置):

module display_mux(
    input [5:0] hour, min,
    input [5:0] alarm_hour, alarm_min,
    input alarm_mode,
    output reg [3:0] digit0, digit1, digit2, digit3
);
    always @(*) begin
        if(alarm_mode) begin
            digit0 = alarm_hour / 10;
            digit1 = alarm_hour % 10;
            digit2 = alarm_min / 10;
            digit3 = alarm_min % 10;
        end
        else begin
            digit0 = hour / 10;
            digit1 = hour % 10;
            digit2 = min / 10;
            digit3 = min % 10;
        end
    end
endmodule

4. 功能验证与仿真分析

4.1 测试平台搭建

我们构建了完整的测试环境验证五大核心功能:

module testbench;
    reg clk_50MHz;
    reg reset;
    reg set_time;
    reg adjust_hour;
    reg adjust_min;
    reg alarm_set;
    wire [6:0] seg;
    wire [3:0] an;
    wire alarm_led;
    
    // 实例化被测设计
    top uut (.*);
    
    // 时钟生成
    always #10 clk_50MHz = ~clk_50MHz;
    
    initial begin
        // 初始化
        clk_50MHz = 0;
        reset = 1;
        set_time = 0;
        adjust_hour = 0;
        adjust_min = 0;
        alarm_set = 0;
        
        // 复位释放
        #100 reset = 0;
        
        // 测试正常计时
        #200;
        
        // 测试校时功能
        set_time = 1;
        repeat(5) begin
            #100 adjust_hour = 1;
            #100 adjust_hour = 0;
        end
        set_time = 0;
        
        // 测试闹钟功能
        alarm_set = 1;
        #1000;
        
        $finish;
    end
endmodule

4.2 关键仿真波形解读

通过ModelSim仿真,我们重点验证了以下场景:

  1. 正常计时功能 :观察秒、分、时计数器的进位关系
  2. 校时功能 :验证小时和分钟调整的正确性
  3. 闹钟触发 :检查预设时间到达时的LED指示
  4. 显示切换 :确认时钟显示与闹钟设置显示的切换逻辑
  5. 复位功能 :测试全局复位信号的有效性

仿真波形截图 图:数字钟系统关键信号仿真波形

波形分析要点:

  • clk_1Hz信号的50%占空比
  • 秒计数器到59时的进位脉冲
  • 校时模式下计数器值的直接设置
  • 闹钟触发时alarm_led信号的持续时长

5. 工程实践与优化建议

5.1 BASYS2板级调试技巧

  1. 时钟约束 :在XDC文件中添加时序约束确保时钟稳定性

    create_clock -period 20.000 -name clk [get_ports clk_50MHz]
    
  2. IO管脚分配 :正确映射按钮和数码管接口

    set_property PACKAGE_PIN V17 [get_ports {seg[6]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {seg[6]}]
    
  3. 功耗优化 :关闭未使用模块的时钟门控

    always @(posedge clk_50MHz) begin
        if(!display_enable)
            display_clk <= 0;
    end
    

5.2 常见问题解决方案

问题现象 可能原因 解决方案
数码管闪烁 扫描频率过低 提高refresh_counter位宽
计时不准 分频器溢出 检查计数器位宽是否足够
按钮响应异常 消抖时间不足 增加debounce计数器位宽
闹钟不触发 比较逻辑错误 添加仿真检查alarm_set信号

5.3 扩展功能建议

  1. 温度补偿 :添加DS18B20传感器实现走时精度补偿
  2. 网络校时 :通过UART接口接收标准时间信号
  3. 多闹钟 :扩展存储空间支持多个闹钟设置
  4. 亮度调节 :PWM控制数码管亮度适应环境光线

在实现这些扩展功能时,建议采用AXI总线架构连接各外设模块,提升系统可扩展性。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐