第四届2024年“寒假在家一起练”基于Lattice MXO2的小脚丫FPGA核心板制作的具有启动、停止、递增和清除功能的秒表_哔哩哔哩_bilibili
一.项目报告
项目旨在利用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。此秒表设计涉及四个按钮输入:开始、停止、增量和清除(重置),每个按钮具有特定功能。
二。设计要求
- 开始按钮:启动秒表,使其以10Hz时钟速率递增,即每0.1秒计数一次。
- 停止按钮:停止秒表的递增,但仍然显示当前计数器值。
- 增量按钮:每次按下增量按钮,显示值增加一次,不受按住按钮时间的影响。
- 复位/清除按钮:强制计数器值为零,重置秒表。
三。设计流程
- 首先,设计时钟模块以提供10Hz的时钟信号,用于秒表的计时。
- 创建计数器模块,用于递增计数并更新显示值。
- 集成按钮输入模块,根据按钮输入执行相应的操作:开始、停止、增量和清除。
- 设计七段显示器控制模块,将计数器的值转换为七段显示器所需的信号。
- 集成所有模块,确保功能正确性和稳定性。
- 进行综合和布局布线,生成FPGA可加载的比特流文件。
四。实现结果
- 成功实现了一个2位数秒表,能够从0.0秒计数到9.9秒,并在达到最大值后翻转。
- 开始按钮启动秒表,按照要求以10Hz的速率递增。
- 停止按钮能够暂停计数器的递增,但仍然显示当前值。
- 增量按钮每次按下都会增加显示值一次,无论按住时间长短。
- 复位/清除按钮能够强制将计数器值重置为零。
五.资源报告
以下是我的FPGA资源占用报告:
六.代码展示
下面是我的代码展示阶段:
module miaobiao( // Module declaration
clk , // Clock signal
start , // Start button
stop , // Stop button
increase , // Increase button
rst , // Reset button
shumaguan_0, // 7-segment display 0
shumaguan_1 // 7-segment display 1
);
input clk ; // Clock signal
input start ; // Start button
input stop ; // Stop button
input increase; // Increase button
input rst ; // Reset button
output reg [8:0] shumaguan_0; // 7-segment display 0
output reg [8:0] shumaguan_1; // 7-segment display 1
reg [23:0] cnt = 24'd0; // Counter for timing
reg [3:0] shi = 4'd0 ; // Tens digit
reg [3:0] ge = 4'd0 ; // Ones digit
reg cnt_flag = 1'b0 ; // Counter flag
reg increase_flag = 1'b0; // Increase flag
reg pre_inc = 1'b1 ; // Previous increase state
reg [7:0] debounce_cnt = 8'd0 ; // Button debounce counter
reg [1:0] debounce_state = 2'b00; // Button debounce state
这一串代码我是定义了输入输出端口和各种寄存器,用于实现秒表功能。时钟信号clk
用于控制计时器的计数。start
、stop
、increase
和rst
分别对应启动、停止、增量和复位按钮的输入。shumaguan_0
和shumaguan_1
是用于显示十位数和个位数的数码管输出。计数器使用cnt
寄存器进行计时,同时使用shi
和ge
分别表示十位数和个位数。cnt_flag
用于控制计数器的递增,increase_flag
用于检测增量按钮的按下,pre_inc
记录前一个增加状态。消抖计数器debounce_cnt
和消抖状态debounce_state
用于对按钮输入进行消抖处理,确保稳定的按钮信号输入。
这段代码基本实现了秒表的基本功能,可以根据按钮输入进行计时、显示和控制操作。
always @(posedge clk) begin // Button debounce
if (rst == 1'b0) begin
debounce_cnt <= 8'd0 ;
debounce_state <= 2'b00;
end
else begin
if (debounce_state == 2'b00) begin // State 0: Waiting for button release
if (start == 1'b0 || stop == 1'b0 || increase == 1'b0) begin
debounce_cnt <= debounce_cnt + 1;
if (debounce_cnt == 8'd20) begin
debounce_state <= 2'b01; // Switch to state 1
end
end
else begin
debounce_cnt <= 8'd0; // Reset counter
end
end
else if (debounce_state == 2'b01) begin // State 1: Waiting for button press
if (start == 1'b1 || stop == 1'b1 || increase == 1'b1) begin
debounce_cnt <= debounce_cnt + 1;
if (debounce_cnt == 8'd20) begin
debounce_state <= 2'b10; // Switch to state 2
end
end
else begin
debounce_cnt <= 8'd0; // Reset counter
debounce_state <= 2'b00; // Switch back to state 0
end
end
else begin // State 2: Button is stably pressed
debounce_cnt <= 8'd0; // Reset counter
debounce_state <= 2'b10; // Stay in state 2
end
end
end
我这段 Verilog 代码是一个计时器和数字递增逻辑。在时钟上升沿 (posedge clk) 触发的始终块中:
首先,如果复位信号 (rst) 为低电平 (1'b0),则表示需要进行重置操作。在这种情况下,将 cnt、shi 和 ge 分别置为零,表示重新开始计时。否则,表示不需要重置,进入 else if 分支。如果 start 为低电平 (1'b0),表示开始计时,此时设置 计时标志位为高电平 (1'b1)。如果 stop 为低电平 (1'b0),表示停止计时,此时设置 cnt_flag 为低电平 (1'b0)。如果 increase 为低电平 (1'b0),表示需要进行递增操作。如果 pre_inc 为高电平 (1'b1),表示上一个时刻 increase 为低电平,此时设置 increase_flag 为高电平 (1'b1)。将 pre_inc(按钮检测)置为低电平 (1'b0)。如果 increase 为高电平 (1'b1),表示需要等待下一个时刻进行递增操作。如果 pre_inc 为低电平 (1'b0),表示上一个时刻 increase 为高电平,此时将 pre_inc 置为高电平 (1'b1)。接下来根据计时标志 cnt_flag 和递增标志 increase_flag 的状态执行相应的计时和递增逻辑。如果 cnt_flag 为高电平 (1'b1),表示需要进行计时。如果计数值 cnt 达到 24'd1199999,则将 cnt 置为零,并根据个位和十位的值进行递增。假如进一标志位为高电平 (1'b1),表示需要进行递增。根据个位和十位的值进行递增操作。该逻辑通过时钟触发和各个控制信号的状态,实现了计时和数字递增的功能。
always @(posedge clk) begin // Counter and timing logic
if (rst == 1'b0) begin
cnt <= 24'd0;
shi <= 4'd0 ;
ge <= 4'd0 ;
end
else if (start == 1'b0) begin
cnt_flag <= 1'b1; // Start timing
end
else if (stop == 1'b0) begin
cnt_flag <= 1'b0; // Stop timing
end
else if (increase == 1'b0) begin
if (pre_inc == 1'b1) begin
increase_flag <= 1'b1; // Set increase flag
end
pre_inc <= 1'b0;
end
else if (increase == 1'b1) begin
if (pre_inc == 1'b0) begin
pre_inc <= 1'b1;
end
end
if (cnt_flag == 1'b1) begin
if (cnt == 24'd1199999) begin
cnt <= 24'd0;
if (ge == 4'd9) begin
ge <= 4'd0;
if (shi == 4'd9) begin
shi <= 4'd0;
ge <= 4'd0 ;
end
else begin
shi <= shi + 4'd1;
end
end
else begin
ge <= ge + 4'd1;
end
end
else begin
cnt <= cnt + 24'd1;
end
end
if (increase_flag == 1'b1) begin
increase_flag <= 1'b0;
if (ge == 4'd9) begin
ge <= 4'd0;
if (shi == 4'd9) begin
shi <= 4'd0;
ge <= 4'd0;
end
else begin
shi <= shi + 4'd1;
end
end
else begin
ge <= ge + 4'd1;
end
end
end
而这代码是一个计数器和定时逻辑的描述。首先,在时钟信号上触发的始终块中,根据不同的条件进行操作。如果复位信号为0,则将计数器、十位数和个位数都设置为0。这表示在复位时将计数器和显示值重置为初始状态。如果启动信号为0,则将计数标志设置为1。这表示开始计时。如果停止信号为0,则将计数标志设置为0。这表示停止计时。如果增加信号为0,则在前一次增加信号(pre_inc)为1的情况下,设置增加标志为1。这表示设置增加标志。如果增加信号为1,则在前一次增加信号(pre_inc)为0的情况下,将前一次增加信号设置为1。这表示检测到增加信号。接下来,在计数标志为1的情况下,进行计数和显示更新。
如果计数器达到最大值(24'd1199999),则将计数器重置为0,并检查是否需要增加十位数和个位数。如果个位数已经是9,则将个位数重置为0,并检查是否需要增加十位数。如果十位数已经是9,则将十位数重置为0,并将个位数重置为0。否则,将十位数增加1。否则,将个位数增加。否则,将计数器增加1。
always @(shi) begin // Display on 7-segment for tens digit
case (shi)
4'd0: shumaguan_1 = 9'hbf; // Display number 0
4'd1: shumaguan_1 = 9'h86; // Display number 1
4'd2: shumaguan_1 = 9'hdb; // Display number 2
4'd3: shumaguan_1 = 9'hcf; // Display number 3
4'd4: shumaguan_1 = 9'he6; // Display number 4
4'd5: shumaguan_1 = 9'hed; // Display number 5
4'd6: shumaguan_1 = 9'hfd; // Display number 6
4'd7: shumaguan_1 = 9'h87; // Display number 7
4'd8: shumaguan_1 = 9'hff; // Display number 8
4'd9: shumaguan_1 = 9'hef; // Display number 9
endcase
end
always @(ge) begin // Display on 7-segment for ones digit
case (ge)
4'd0: shumaguan_0 = 9'h3f; // Display number 0
4'd1: shumaguan_0 = 9'h06; // Display number 1
4'd2: shumaguan_0 = 9'h5b; // Display number 2
4'd3: shumaguan_0 = 9'h4f; // Display number 3
4'd4: shumaguan_0 = 9'h66; // Display number 4
4'd5: shumaguan_0 = 9'h6d; // Display number 5
4'd6: shumaguan_0 = 9'h7d; // Display number 6
4'd7: shumaguan_0 = 9'h07; // Display number 7
4'd8: shumaguan_0 = 9'h7f; // Display number 8
4'd9: shumaguan_0 = 9'h6f; // Display number 9
endcase
end
这部分 Verilog 代码是用来根据十位数和个位数的值设置对应的 7 段数码管显示内容的逻辑。下面是对代码中两个 always 块的详细解释:always @(shi)
块:当十位数 shi 的值发生变化时,根据其值设置要显示在 7 段数码管上的内容。使用 case 语句对 shi 进行匹配,并设置相应的数码管显示内容。例如,当 shi 的值为 4 位二进制的 0000 时,将 shumaguan_1 设置为 9 位十六进制的 bf,用于显示数字 0。当 shi 的值为其他值时,依次设置不同的数码管显示内容。
在always @(ge)
块中:当个位数 ge 的值发生变化时,根据其值设置要显示在 7 段数码管上的内容。同样使用 case 语句对 ge 进行匹配,并设置相应的数码管显示内容。例如,当 ge 的值为 4 位二进制的 0000 时,将 shumaguan_0 设置为 9 位十六进制的 3f,用于显示数字 0。当 ge 的值为其他值时,依次设置不同的数码管显示内容。
这两个 always 块通过对十位数和个位数的变化进行检测,并根据其值设置相应的数码管显示内容,实现了在数码管上显示正确的数字。
其实我们会发现为什么十位的代码和个位的代码不一样,是因为我十位的代码是是直接用了件下的代码通过一定的逻辑编译出来的,假如说我用同样的逻辑去编译个位,那么就会出现个位小数点点亮的情况,当时我就想着可不可以用数码管内部的代码来直接驱动,我经过尝试以后确实发现可以,所以我的个位上就是直接用的数码管的驱动代码这也算耍了个小聪明。
七.设计框图
这个逻辑功能图主要是想表明通过控制逻辑代码来实现按钮输入信号执行特定功能比如:
- 开始按钮:启动秒表计时。
- 停止按钮:暂停秒表计时。
- 增量按钮:增加计数器值。
- 清除按钮:将计数器值重置为0.0秒。
四个按钮输入:开始、停止、增量和清除。这些按钮将作为输入信号传递给FPGA核心板。包含一个计数器,用于存储秒表的当前计数值。计数器将在每0.1秒更新一次,从0.0秒开始计数,直到9.9秒,然后重新开始。还有对四个按钮进行进行延时的代码。最终在两个七段数码管显示,分别用于显示十位数和个位数。这些模块将根据计数器的值更新显示内容。
八.编写遇到过程中遇到的问题并解决问题
在我本次写代码的过过程中主要是有以下这几个问题:
1:FPGA设计中,时序是一个重要且复杂的代码问题至少对于我来说是问题。正确的时钟分频可以确保设计满足时序要求,但不正确的时序约束可能导致设计无法正常工作。解决方法包括使用时序分析工具来分析和优化设计的时序路径,以确保时序约束得到满足。这就导致我有一段时间基本上就是看的迷迷糊糊的,十分难受。
2:在FPGA设计中,调试是一个非常困难的挑战。由于FPGA是并行的硬件系统,问题的排查可能比较困难。解决方法包括使用仿真工具进行功能验证和时序验证等等,但我都不太行,所以经常就是调试的代码错误越跳越多老难受了。
3:就是FPGA的资源是有限的,当设计的代码越多越复杂度时,也许就需要用到大容量的FPGA器件,但是本次总体上来说因为涉及的难度较小没有出现这种情况,但还是要注意。
我的解决方法是: 在设计开始之前,进行充分的规划和分析是至关重要的。这包括时序分析、资源规划和功能分解等。通过合理的设计规划,可以降低后期调试和优化的难度。经过时间的洗刷,已经在代码上能看懂了,不会再出现一些比较低级的错误,我以后再写always块的时候,我尽量多分成多个always块,否则进行检修的时候那真的是才叫难受。
九.总结
经过了本次的寒假比赛我将会继续深入学习,参加FPGA比赛是一个很好的学习机会,但在比赛后仍然可以继续学习深入的FPGA知识和技能,探索更多的应用领域和算法。利用比赛中学到的经验和技能,可以尝试开展自己的项目并将其落地,从而提升实际操作能力和解决问题的能力。随着FPGA在硬件加速领域有着广泛的应用,我希望可以尝试深入研究硬件加速技术,在人工智能、数据处理等领域寻找更多的应用机会。