2024年寒假在家一起练 - 具有启动、停止、递增和清除功能的秒表
该项目使用了STEP-MXO2-LPC,实现了秒表的设计,它的主要功能为:启动、停止、递增和清除功能。
标签
FPGA
小脚丫
数字逻辑
USB Type C
蔡尚巽
更新2024-04-01
128

一、 项目需求:

1. 通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。

2. 两个7段数码显示器来显示秒表的数值。

3. 秒表的显示范围应该是从0.0秒到9.9秒。如果秒表的计数达到9.9秒,应该自动回滚到0.0秒开始重新计数。

4.  秒表以10Hz的频率递增(即每0.1秒计数一次)

  • 4个轻触按键来实现不同的控制功能(开始、停止、增量、清除/重置)。 ·
  • 开始按钮: 当按下开始按钮时,秒表开始运行。
  • 停止按钮: 按下停止按钮时,秒表停止计数,但数码管保持显示当前的计数值。
  • 增量按钮: 每次按下增量按钮时,无论按住多久,秒表的显示值增加0.1秒。 ·
  • 清除/重置按钮: 按下清除/重置按钮时,秒表的计数值重置为0.0秒。

 

二、 需求分析

1. 功能需求

a. 时间显示

  • 显示范围:0.0秒至9.9秒,十分之一秒精度。
  • 回滚机制:达到9.9秒后自动回到0.0秒。

b. 用户交互

  • 开始/停止:控制秒表的开始和暂停。
  • 增量:按键实现时间的快速增加。
  • 重置:重置时间到0.0秒。

2. 硬件需求

  • 核心板:STEP-MXO2-LPC核心板(缺少除法器)。
  • 显示器:两个七段数码显示器的规格。
  • 输入:四个轻触按键的规格与布局。

3. 模块化设计

a. 顶层模块

  • 整合各子模块。
  • 定义全局信号和接口。

b. 时钟模块

  • 生成适合秒表的10Hz时钟信号。
  • 确保时钟信号的准确性和稳定性。

c. 七段显示器模块

  • 控制数码管以显示当前时间。
  • 管理数码显示的数据格式和刷新率。

d. 消抖动模块

  • 消除按键的抖动,提高输入的稳定性和准确性。
  • 实现简单的软件延时或硬件滤波。

4. 测试与验证

  • 模块测试:独立测试每个模块的功能。
  • 集成测试:验证各模块集成后的整体功能。
  • 性能测试:确保时间计数和显示的准确性。

 

三、 实现方法

在WebIDE环境下进行Verilog代码编程、逻辑综合、管脚分配、FPGA映射。通过Diamond软件对代码进行仿真。若仿真波形图符合要求,则返回WebIDE环境生成JED文件并下载到核心板中进行验证。


四、 功能框图

  1. 时钟模块/主功能模块:主要通过计数器实现,每计满100ms data0 自动加一。当 data0 加到9时,data0 清零同时向 data1 进位,data1 加一。当 data0 和 data1 都加到9时,在下一个100ms到来时全部归零(既 0.0) 。
  2. 数码管译码模块:把四位二进制数 data0 和 data1 转化成对应的数码管段码,使数码管的led点亮,拼接成对应的阿拉伯数字0-9
  3. 开始、暂停、增量三个按键的消抖模块:通过计数器实现,按住保持10ms则输出有一个时钟周期的有效信号key_flag。如果发生抖动计数器清零并重新计时10ms,计满10ms则不再计数,这样即使一直按住也只能发出一个有效信号。


五、 代码及说明

  1. 顶层模块(top)

这个模块是整个秒表的核心。它集成了所有的子模块,并处理来自按钮的输入信号(开始、停止和增量)。它还控制七段显示器的输出。

输入: 系统时钟(clk),低电平有效复位信号(rst_n), 以及开始(key_start),停止(key_stop),增量(key_add)按钮的输入。

输出: 驱动两个七段显示器的输出信号(segdata0, segdata1)。

内部连接: 将消抖动模块(xiaodou)和时钟模块(clock)的输出连接到七段显示模块(segment)。

 

// The 'top' module is the main module that integrates components of a digital stopwatch.
// It handles input signals for start, stop, and add functions, and controls the display.
module top
(
    input wire clk,           // System clock input.
    input wire rst_n,         // Active low reset signal.
    input wire key_start,     // Input signal for the start button.
    input wire key_stop,      // Input signal for the stop button.
    input wire key_add,       // Input signal for the add button.
    output wire [8:0] segdata0, // Output data for the first digit of the 7-segment display.
    output wire [8:0] segdata1  // Output data for the second digit of the 7-segment display.
);
 
// Intermediate wires to connect sub-modules.
wire start_flag;  // Flag for the start button debounced state.
wire stop_flag;   // Flag for the stop button debounced state.
wire add_flag;    // Flag for the add button debounced state.
wire [3:0] data0; // Data for the first digit.
wire [3:0] data1; // Data for the second digit.
 
// Instance of the clock module to handle timing.
clock
#(
    .CNT_100ms(1_199_999) // Set threshold for the 100ms counter.
)
clock_inst
(
    .clk(clk),
    .rst_n(rst_n),
    .start_flag(start_flag),
    .stop_flag(stop_flag),
    .add_flag(add_flag),
    .data0(data0), // Connects to the first digit.
    .data1(data1)  // Connects to the second digit.
);
 
// Instances of the xiaodou module for debouncing start, stop, and add buttons.
// These instances ensure that the button press is sustained for at least 10ms to be considered valid.
xiaodou
#(
    .CNT_10ms(119_999) // Set threshold for 10ms debounce.
)
xiaodou_start
(
    .clk(clk),
    .rst_n(rst_n),
    .key_in(key_start),
    .key_flag(start_flag)
);
 
xiaodou
#(
    .CNT_10ms(119_999)
)
xiaodou_stop
(
    .clk(clk),
    .rst_n(rst_n),
    .key_in(key_stop),
    .key_flag(stop_flag)
);
 
xiaodou
#(
    .CNT_10ms(119_999)
)
xiaodou_add
(
    .clk(clk),
    .rst_n(rst_n),
    .key_in(key_add),
    .key_flag(add_flag)
);
 
// Instance of the segment module to convert binary data to 7-segment display format.
segment segment_inst
(
    .data0(data0),
    .data1(data1),
    .segdata0(segdata0),
    .segdata1(segdata1)
);
 
endmodule


  1. 时钟模块(clock)

用于计时和更新秒表的显示值。

参数: CNT_100ms用于设置100毫秒计数阈值。

输入: 系统时钟((clk), 低电平有效复位(rst_n),以及控制信号(start_flag,stop_flag,add_flag)。

输出: 分别表示个位和十分之一位的数据(data0, data1)。

 

// The 'clock' module is designed for counting time in a digital system.
module clock
#(
    parameter CNT_100ms = 1_199_999 // Set the counter threshold for 100 milliseconds.
)
(
    input wire clk,          // System clock input.
    input wire rst_n,        // Active low reset signal.
    input wire start_flag,   // Flag to start the counter.
    input wire stop_flag,    // Flag to stop the counter.
    input wire add_flag,     // Flag to manually increment the counter.
    output reg [3:0] data0,  // Output for the most significant digit (ones place).
    output reg [3:0] data1   // Output for the least significant digit (tenths place).
);
 
reg [31:0] cnt100ms; // Counter for 100ms time intervals.
reg en;              // Enable signal for the counter.
 
// Counter logic for generating 100ms intervals.
always @(posedge clk or negedge rst_n)
    if (!rst_n)
        cnt100ms <= 0;
    else if (cnt100ms == CNT_100ms)
        cnt100ms <= 0;
    else
        cnt100ms <= cnt100ms + 1;
 
// Logic to handle the start and stop functionality of the counter.
always @(posedge clk or negedge rst_n)
    if (!rst_n)
        en <= 0;
    else if (start_flag)
        en <= 1;
    else if (stop_flag)
        en <= 0;
    else
        en <= en;
 
// Logic for incrementing data0 (tenths place).
// If en = 0, data0 can be manually incremented.
// If en = 1, data0 increments every 100ms or manually if add_flag is set.
always @(posedge clk or negedge rst_n)
    if (!rst_n)
        data0 <= 0;
    else if (!en)
        begin
            if (add_flag)
                data0 <= (data0 == 9) ? 0 : data0 + 1;
        end
    else
        begin
            if (cnt100ms == CNT_100ms || add_flag)
                data0 <= (data0 == 9) ? 0 : data0 + 1;
        end
 
// Logic for incrementing data1 (ones place).
// Increment occurs on the transition of data0 from 9 to 0.
always @(posedge clk or negedge rst_n)
    if (!rst_n)
        data1 <= 0;
    else if (!en)
        begin
            if (add_flag && data0 == 9)
                data1 <= (data1 == 9) ? 0 : data1 + 1;
        end
    else
        begin
            if ((cnt100ms == CNT_100ms || add_flag) && data0 == 9)
                data1 <= (data1 == 9) ? 0 : data1 + 1;
        end
 
endmodule

 

  1. 七段显示器模块(segment)

将二进制的计数值转换成七段显示器上的显示格式。

输入: 分别代表个位和十分之一位的二进制数据(data0, data1)。

输出: 驱动七段显示器的信号(segdata0, segdata1)。

 

// The 'segment' module is a 7-segment LED display decoder.
// It converts 4-bit binary input into 7-segment display format for two digits.
module segment
(
    input wire [3:0] data0,    // 4-bit binary input for the first digit.
    input wire [3:0] data1,    // 4-bit binary input for the second digit.
    output reg [8:0] segdata0, // 9-bit output for the first digit (includes segments A-G, DP, and a digit control bit).
    output reg [8:0] segdata1  // 9-bit output for the second digit (includes segments A-G, DP, and a digit control bit).
);
 
// Process to convert data0 to 7-segment format for the first digit.
always @(data0)
    begin
        case (data0)
            // Each case maps a 4-bit binary number to the corresponding 7-segment display pattern.
            // Patterns are defined as 'DIG_DP_GFEDCBA', where DIG is the digit control bit, DP is the decimal point, and A-G are the segments.
            4'b0000 : segdata0 = 9'b0_0_0111111; // 0
            4'b0001 : segdata0 = 9'b0_0_0000110; // 1
            4'b0010 : segdata0 = 9'b0_0_1011011; // 2
            4'b0011 : segdata0 = 9'b0_0_1001111; // 3
            4'b0100 : segdata0 = 9'b0_0_1100110; // 4
            4'b0101 : segdata0 = 9'b0_0_1101101; // 5
            4'b0110 : segdata0 = 9'b0_0_1111101; // 6
            4'b0111 : segdata0 = 9'b0_0_0000111; // 7
            4'b1000 : segdata0 = 9'b0_0_1111111; // 8
            4'b1001 : segdata0 = 9'b0_0_1101111; // 9
            default : segdata0 = 9'b0_0_0000000;  // Blank display for undefined inputs.
        endcase
    end
 
// Process to convert data1 to 7-segment format for the second digit.
// This is similar to the first digit with an additional bit for the second digit control.
always @(data1)
    begin
        case (data1)
            // The segment patterns are the same as for the first digit, with the digit control bit set to '1'.
            4'b0000 : segdata1 = 9'b0_1_0111111; // 0
            4'b0001 : segdata1 = 9'b0_1_0000110; // 1
            4'b0010 : segdata1 = 9'b0_1_1011011; // 2
            4'b0011 : segdata1 = 9'b0_1_1001111; // 3
            4'b0100 : segdata1 = 9'b0_1_1100110; // 4
            4'b0101 : segdata1 = 9'b0_1_1101101; // 5
            4'b0110 : segdata1 = 9'b0_1_1111101; // 6
            4'b0111 : segdata1 = 9'b0_1_0000111; // 7
            4'b1000 : segdata1 = 9'b0_1_1111111; // 8
            4'b1001 : segdata1 = 9'b0_1_1101111; // 9
            default : segdata1 = 9'b0_1_0000000;  // Blank display for undefined inputs.
        endcase
    end
 
endmodule

 

  1. 消抖模块(xiaodou)

每个按钮都通过一个消抖实例来进行消抖处理,确保按键的稳定和准确。

参数: CNT_10ms用于设置10毫秒消抖阈值。

输入: 系统时钟(clk),低电平有效复位(rst_n),按钮原始输入(key_in)。

输出: 经过消抖处理后的稳定按键信号(key_flag)。

 

// The 'xiaodou' module implements a debouncing mechanism for a push button input.
// It ensures that a button press is considered valid only if sustained for at least 10 milliseconds.
module xiaodou
#(
    parameter CNT_10ms = 119_999 // Counter threshold value for 10 milliseconds.
)
(
    input wire clk,      // System clock input.
    input wire rst_n,    // Active low reset signal.
    input wire key_in,   // Raw input signal from the push button.
    output reg key_flag  // Flag output which indicates a valid button press.
);
 
reg [31:0] cnt; // Counter for measuring the duration of the button press.
 
// Counter logic to measure the duration of the button press.
always @(posedge clk or negedge rst_n)
    if (!rst_n)
        cnt <= 0; // Reset the counter to 0 on reset.
    else if (key_in)
        cnt <= 0; // Reset the counter to 0 if the button is released.
    else if (cnt == CNT_10ms)
        cnt <= CNT_10ms; // Keep the counter at maximum value once it reaches 10ms.
    else
        cnt <= cnt + 1; // Increment the counter otherwise.
 
// Logic to set the key_flag indicating a valid button press.
always @(posedge clk or negedge rst_n)
    if (!rst_n)
        key_flag <= 0; // Reset key_flag to 0 on reset.
    else if (cnt == (CNT_10ms - 1))
        key_flag <= 1; // Set key_flag to 1 when the button is pressed continuously for 10ms.
    else
        key_flag <= 0; // Reset key_flag if the button press does not sustain for 10ms.
 
endmodule

 

  1. 测试模块(tb_top)

这个模块用于模拟和验证整个秒表的行为,包括输入信号的模拟和输出信号的观察。

模拟输入: 生成系统时钟、模拟按键操作(开始、停止、增量)。

观察输出: 观察和验证七段显示器的输出。

 

`timescale 1ns/1ns
 
// Testbench module for the 'top' module.
// This testbench simulates the behavior of the top module by driving input signals
// and observing the output signals.
module tb_top();
 
    reg clk;              // Clock signal for the testbench.
    reg rst_n;            // Active low reset signal for the testbench.
    reg key_start;        // Input signal to simulate the start button press.
    reg key_stop;         // Input signal to simulate the stop button press.
    reg key_add;          // Input signal to simulate the add button press.
    wire [8:0] segdata0;  // Output data from the top module for the first digit of the display.
    wire [8:0] segdata1;  // Output data from the top module for the second digit of the display.
 
    // Intermediate signals to observe internal states of the top module.
    wire start_flag;
    wire stop_flag;
    wire add_flag;
    wire [3:0] data0;
    wire [3:0] data1;
 
    // Initial block to set up the initial state and apply test vectors.
    initial
    begin
        clk = 1;           // Initialize clock.
        rst_n <= 0;        // Assert reset.
        key_start <= 1;    // Initialize start button to inactive.
        key_stop <= 1;     // Initialize stop button to inactive.
        key_add <= 1;      // Initialize add button to inactive.
        #30
        rst_n <= 1;        // Release reset.
        
        // Simulate start button press.
        #1000
        key_start <= 0;
        #1000
        key_start <= 1;
        
        // Simulate stop button press.
        #10000
        key_stop <= 0;
        #1000
        key_stop <= 1;
        
        // Simulate add button press.
        #1000
        key_add <= 0;
        #1000
        key_add <= 1;
        
        // Simulate start button press again to continue timing.
        #1000
        key_start <= 0;
        #1000
        key_start <= 1;
    end
 
    // Clock signal generation with a period of 20ns (50MHz frequency).
    always #10 clk = ~clk;
 
    // Instantiation of the top module and its submodules with test parameters.
    clock
    #(
        .CNT_100ms(19) // Reduced counter threshold for quicker simulation.
    )
    clock_inst
    (
        .clk(clk),
        .rst_n(rst_n),
        .start_flag(start_flag),
        .stop_flag(stop_flag),
        .add_flag(add_flag),
        .data0(data0), // Connects to the first digit.
        .data1(data1)  // Connects to the second digit.
    );
 
    // Instantiation of xiaodou modules for debouncing with reduced thresholds.
    xiaodou
    #(
        .CNT_10ms(9) // Reduced debounce threshold for quicker simulation.
    )
    xiaodou_start
    (
        .clk(clk),
        .rst_n(rst_n),
        .key_in(key_start),
        .key_flag(start_flag)
    );
 
    xiaodou
    #(
        .CNT_10ms(9)
    )
    xiaodou_stop
    (
        .clk(clk),
        .rst_n(rst_n),
        .key_in(key_stop),
        .key_flag(stop_flag)
    );
 
    xiaodou
    #(
        .CNT_10ms(9)
    )
    xiaodou_add
    (
        .clk(clk),
        .rst_n(rst_n),
        .key_in(key_add),
        .key_flag(add_flag)
    );
 
    // Instantiation of the segment module for 7-segment display encoding.
    segment segment_inst
    (
        .data0(data0),
        .data1(data1),
        .segdata0(segdata0),
        .segdata1(segdata1)
    );
 
endmodule

 

六、 仿真波形图

1. tb_top/clk 是时钟信号,可以看到它在高和低之间周期性切换,为整个系统提供时序。

2. tb_top/rst_n 是复位信号,它开始时处于低状态(即复位激活),然后在仿真开始不久后变为高状态,这释放了复位。

3. tb_top/key_start, tb_top/key_stop和 tb_top/key_add 分别对应开始、停止和增量按钮。每个按钮在被按下时,其信号会从高到低,然后再回到高,模拟按钮的按下和释放。

4. tb_top/segdata0 和 tb_top/segdata1 是分别连接到第一位和第二位数码管的输出信号,显示为二进制编码的7段数据。

5. tb_top/start_flag, tb_top/stop_flag, 和 tb_top/add_flag 是通过消抖动逻辑后的稳定信号,用来指示相关按钮是否已稳定地被按下。

6. tb_top/data0 和 tb_top/data1 是内部计数值,表示七段显示器上应该显示的数字。


仿真波形图中:

  1. 系统复位(rst_n):
  • 在仿真的初始时刻,rst_n 信号是低电平,这表明系统正在复位状态。
  • 在复位信号释放(即从低跳变到高)之后,电路的其他部分开始正常工作。这是因为在低电平复位期间,所有寄存器都会被清零或设定到初始状态。
  1. 开始按钮(key_start)与开始标志(start_flag):
  • key_start 信号从高电平跳变到低电平,模拟按下开始按钮。
  • 经过消抖模块处理后,start_flag 从低变高,这表明开始按钮已稳定被按下并且消抖完成。此时,计时开始。
  1. 停止按钮(key_stop)与停止标志(stop_flag):
  • 类似地,key_stop 信号的低电平模拟按下停止按钮。
  • 在key_stop 信号稳定之后,stop_flag 信号会变高,这表示停止按钮动作已被接受。当stop_flag 为高时,计时停止。
  1. 七段显示信号(segdata0和segdata1):
  • 这些信号反映了秒表的计时值,通过对应的模块转换为七段显示格式。
  • 在仿真中可以看到,随着时间的推移,这些信号的变化应该与计时器的计数相匹配。
  1. 增量按钮(key_add)与增量标志(add_flag):
  • key_add 信号的低电平模拟按下增量按钮。
  • 消抖动模块处理后,add_flag 会在按钮按下且稳定一段时间后跳变为高电平,表示信号已消抖且稳定。
  • 在add_flag 为高时,如果秒表已停止(由stop_flag 高电平表示),计时器的data0(十分之一秒)和/或data1(秒)应该会递增。

 

七、 FPGA的资源利用说明

设计结果

  • 寄存器使用: 总共4635个寄存器位,使用了129位,占比约2%。
  • 查找表(LUTs)使用: 总共4320个LUT4,使用了152个,占比约3.5%。
  • 输入/输出(I/O)使用: 总共280个I/O,使用了27个,包括JTAG口,占比约9.6%。
  • 逻辑单元(SLICE)使用: 总共2160个SLICE,使用了144个,占比约6.6%。
  • 时钟资源: 有1个时钟信号(clk_c),其驱动组件clk位于非专用管脚,可能会有延迟或偏移。

性能

  • 目标频率: 1 MHz
  • 实际达到的最大频率: 约93.145 MHz
  • 时钟报告: 时钟使能网有1个,主时钟clk_c有77个负载。


附件下载
附件.7z
代码和jed文件
团队介绍
电子科学与技术专业 - 大三本科生一枚
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号