任务要求:
- 通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。
- 使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。
- 秒表使用三个按钮输入:开始、停止、增量和清除(重置)。 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。
硬件介绍:
该项目使用硬件平台为:基于Lattice MXO2的小脚YFPGA核心板 - Type C接口。
系统框图如下:
3 完成的功能及资源占用情况:
3.1 完成的功能:
- 首先,上电后数码管显示值为0.0,并在点击“开始”按键之前保持0.0。
- 按下“开始”按键后,秒表开始以10Hz时钟速率递增,每次递增0.1s,当计时到9.9时,会翻转回0.0重新开始计时。
- 按下“停止”按键时,秒表停止计时并锁定在当前时间,此时点击“增量”按键可使计时值增加0.1,同样当计时超过9.9时,会翻转回0.0重新开始计时。
- 在自动计时或者停止计时的状态下,按下“清零”按键,秒表将清零显示0.0,并锁定在0.0,等待再次按下“开始”按键或“增量”按键。
3.2 资源占用情况:
Design Summary:
Number of registers: 64 out of 4635 (1%)
PFU registers: 64 out of 4320 (1%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 50 out of 2160 (2%)
SLICEs as Logic/ROM: 50 out of 2160 (2%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 21 out of 2160 (1%)
Number of LUT4s: 99 out of 4320 (2%)
Number used as logic LUTs: 57
Number used as distributed RAM: 0
Number used as ripple logic: 42
Number used as shift registers: 0
Number of PIO sites used: 23 + 4(JTAG) out of 105 (26%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 0 out of 2 (0%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)
4 实现思路:
1、核心部分的计时部分,需要以10Hz的频率工作,故首先需要将系统时钟12MHz进行分频处理以得到10Hz的时钟信号;
2、控制秒表开始和停止计时部分可以采取一个标志位,通过标志位判断当前处于暂停状态还是继续状态;
3、由于需要将计时数值显示在数码管上,故需要构建一个数码管驱动模块,将计时时的BCD码值转换成数码管显示的段码值;
4、“增量”按键的需求为无论按住增量按钮多长时间都只会增加一次,所以需要为其编写按键消抖代码。
5 实现过程:
5.1 程序架构图:
- 主模块stopwatch9_9.v负责对按键的检测、时钟分频和计时功能的实现;
- 副模块digital_tube_drive.v负责将计时的BCD码值转换成数码管显示段码值。
- 副模块debounce.v负责按键消抖;
5.2 主模块stopwatch9_9.v:
首先定义一系列输入和输出的引脚分别为:clk(时钟信号输入)、key_start(开始按键输入)、key_stop(停止按键输入)、key_add(增量按键的输入)、key_rst(复位/清零按键的输入)、seg1(数码管1段码输出)、seg1_dig(数码管1公共端输出)、seg2(数码管2段码输出)、seg2_dig(数码管2公共端输出)。
input wire clk;
input wire key_start;
input wire key_stop;
input wire key_add;
input wire key_rst;
output [7:0] seg1;
output seg1_dig;
output [7:0] seg2;
output seg2_dig;
然后是获得10Hz的时钟信号,对于系统时钟12MHz的情况下,当计数到927C0H时将产生10Hz频率的寄存器进行取反,即可得到需要的时钟信号。
定义一个常量存储计数的最大值:
parameter COUNT_MAX = 20'h927C0;
927C0H转换成二进制后的长度是20位,下面分别定义clk_10作为产生10Hz频率的计数器,以及clk_10_out作为10Hz频率输出寄存器:
reg [19:0] clk_10;
reg clk_10_out = 1'b0;
然后在always块中执行产生10Hz时钟频率的代码:
always @(posedge clk) begin
if(clk_10 == COUNT_MAX) begin
clk_10 <= 20'd0;
clk_10_out = ~clk_10_out;
end
else begin
clk_10 <= clk_10 + 1'b1;
end
end
然后是按键检测的部分,这里定义start_flag用于判断当前是处于自动计时模式还是暂停模式,其为1表示处于自动计时模式,为0表示处于暂停模式,上电复位后默认为暂停模式:
reg start_flag = 1'b0;
always @(negedge key_start, negedge key_stop, negedge key_rst) begin
if(!key_rst) begin
start_flag <= 1'b0;
end
else if(!key_stop) begin
start_flag <= 1'b0;
end
else if(!key_start) begin
start_flag <= 1'b1;
end
else begin
start_flag <= start_flag;
end
end
然后是整个项目的核心,秒表的计时部分功能实现,这里有两个位宽为4的寄存器time1和time2,以及一个时钟信号counter_clk。时钟信号counter_clk根据start_flag的值不同分别选择使用clk_10_out计时或key_add_out计时。
在always块中,根据counter_clk和key_rst信号的状态进行操作。如果key_rst为低电平,即按下了复位按键,就将time1和time2清零。否则,当time2计数达到9时,将其清零并检查time1,如果time1也达到9,则将其清零,否则将其递增。如果time2未达到9,则递增time2。整个逻辑实现了time1作为计时的低位,time2作为计时的高位,两者组合达到了0到99的计数。
reg [3:0] time1;
reg [3:0] time2;
wire counter_clk;
assign counter_clk = start_flag ? clk_10_out : !key_add_out;
always @(posedge counter_clk, negedge key_rst)
begin
if(!key_rst) begin
time1 <= 4'd0;
time2 <= 4'd0;
end
else begin
if(time2 == 9) begin
time2 <= 4'd0;
if(time1 == 9)
time1 <= 4'd0;
else
time1 <= time1 + 1'b1;
end
else
time2 <= time2 + 1'b1;
end
end
5.3 副模块digital_tube_drive.v:
该模块接收一个4位BCD码输入和一个小数点输入,输出控制数码管的段输出和公共端输出。
数码管的段输出值根据输入的BCD数字进行更新,在always块中使用case语句对所有可能的输入进行了处理,并将相应的段输出值赋给seg_out_val寄存器。
由于使用的核心板上的数码管为共阴极数码管,所以公共端输出被固定为低电平,小数点的输出直接由输入dp控制。
module digital_tube_drive(
input [3:0] bcd_num , // 输入的BCD数字
input dp , // 小数点
output wire [7:0] seg_out , // 数码管段输出
output wire seg_dig // 数码管公共端输出
);
reg [6:0] seg_out_val; // 数码管段输出值
assign seg_dig = 1'b0; // 数码管公共端输出为低电平
assign seg_out[0] = dp; // 小数点输出
assign seg_out[7:1] = seg_out_val; // 数码管段输出
always @ (bcd_num) // 根据输入的BCD数字更新数码管段输出值
begin
case (bcd_num)
4'd0: seg_out_val <= 7'b111_1110; // 数字0
4'd1: seg_out_val <= 7'b011_0000; // 数字1
4'd2: seg_out_val <= 7'b110_1101; // 数字2
4'd3: seg_out_val <= 7'b111_1001; // 数字3
4'd4: seg_out_val <= 7'b011_0011; // 数字4
4'd5: seg_out_val <= 7'b101_1011; // 数字5
4'd6: seg_out_val <= 7'b101_1111; // 数字6
4'd7: seg_out_val <= 7'b111_0000; // 数字7
4'd8: seg_out_val <= 7'b111_1111; // 数字8
4'd9: seg_out_val <= 7'b111_1011; // 数字9
4'd10: seg_out_val <= 7'b111_0111; // 字母A
4'd11: seg_out_val <= 7'b001_1111; // 字母b
4'd12: seg_out_val <= 7'b100_1111; // 字母C
4'd13: seg_out_val <= 7'b011_1101; // 字母d
4'd14: seg_out_val <= 7'b100_1111; // 字母E
4'd15: seg_out_val <= 7'b100_0111; // 字母F
endcase
end
endmodule
5.4 副模块debounce.v:
按键消抖内容参考了电子森林的代码,网址如下:
按键消抖: https://www.eetree.cn/wiki/7._%E6%8C%89%E9%94%AE%E6%B6%88%E6%8A%96
这个按键消抖模块十分强大,调用时可自定义按键个数,仅例化一个模块即可实现多个按键的消抖操作。
6.模块仿真
6.1 主模块stopwatch9_9.v:
在正常计数期间,可以看到time1和time2按照预期的功能依次进行递增;
这里可以分别看到,当key_stop按下后,time1和time2停止递增,counter_clk改为由key_add决定,key_add每按下一次,time2的值加1,同时在后面key_rst按下后,time1和time2全部清零,并在接下来的key_start按下时重新开始计数。
由此,整个主模块工作正常。
6.2 副模块digital_tube_drive.v:
可以看出,在bcd_num依次为不同值时,seg_out_val都为对应的共阴数码管的段码值,成功实现了将BCD码译码为数码管的段码值。
6.3 副模块debounce.v:
该模块取自电子森林的代码,进行仿真时也看到了在抖动期间的低电平都被消除了,只有低电平持续一定时间的状态才判断为有效状态,key_pulse才进行输出。
7.AI大模型的使用
7.1 副模块digital_tube_drive.v:
副模块digital_tube_drive的代码主要由大模型生成,本人只对其进行了较小的改动。
7.1 按键检测的always块:
按键检测的always块也主要由大模型生成,本人同样在调试过程中对其进行了适当的修改。
8. 遇到的主要难题及解决方法
遇到的主要难题在于由于是初学FPGA,在编程思维上依旧是普通单片机的思维,这里的变量不能在不同的always里面进行赋值操作,在初次编写代码时碰了不少壁,后面自己花了一点时间去仔细学了一下Verilog的编写方式方法,以及通过本次的项目练习,现在已经了解了一些Verilog的编程技巧了。
9. 未来的计划或建议
未来计划继续在这个项目的基础上增加功能,比如目前的的秒表计时只能记到9.9秒,我希望到9.9.后下一步不是清零,而是改变数码管显示的精度然后从10秒继续计时,一直记到59秒后再清零,同时由于数码管显示变为秒十位和秒个位,所以递增按键按下后的递增值需要从0.1秒变为1秒才行,然后还可以连接蜂鸣器,当计时值满时让蜂鸣器发出1s左右的提示声音。