一、 项目需求:
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文件并下载到核心板中进行验证。
四、 功能框图
- 时钟模块/主功能模块:主要通过计数器实现,每计满100ms data0 自动加一。当 data0 加到9时,data0 清零同时向 data1 进位,data1 加一。当 data0 和 data1 都加到9时,在下一个100ms到来时全部归零(既 0.0) 。
- 数码管译码模块:把四位二进制数 data0 和 data1 转化成对应的数码管段码,使数码管的led点亮,拼接成对应的阿拉伯数字0-9
- 开始、暂停、增量三个按键的消抖模块:通过计数器实现,按住保持10ms则输出有一个时钟周期的有效信号key_flag。如果发生抖动计数器清零并重新计时10ms,计满10ms则不再计数,这样即使一直按住也只能发出一个有效信号。
五、 代码及说明
- 顶层模块(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
- 时钟模块(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
- 七段显示器模块(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
- 消抖模块(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
- 测试模块(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 是内部计数值,表示七段显示器上应该显示的数字。
仿真波形图中:
- 系统复位(rst_n):
- 在仿真的初始时刻,rst_n 信号是低电平,这表明系统正在复位状态。
- 在复位信号释放(即从低跳变到高)之后,电路的其他部分开始正常工作。这是因为在低电平复位期间,所有寄存器都会被清零或设定到初始状态。
- 开始按钮(key_start)与开始标志(start_flag):
- key_start 信号从高电平跳变到低电平,模拟按下开始按钮。
- 经过消抖模块处理后,start_flag 从低变高,这表明开始按钮已稳定被按下并且消抖完成。此时,计时开始。
- 停止按钮(key_stop)与停止标志(stop_flag):
- 类似地,key_stop 信号的低电平模拟按下停止按钮。
- 在key_stop 信号稳定之后,stop_flag 信号会变高,这表示停止按钮动作已被接受。当stop_flag 为高时,计时停止。
- 七段显示信号(segdata0和segdata1):
- 这些信号反映了秒表的计时值,通过对应的模块转换为七段显示格式。
- 在仿真中可以看到,随着时间的推移,这些信号的变化应该与计时器的计数相匹配。
- 增量按钮(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个负载。