1. 项目功能介绍
使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。
秒表使用四个按钮输入:开始、停止、增量和清除(重置)。开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。
2. 设计思路
由于需要时钟信号为10Hz,参考时钟信号为12MHz,所以需要设计分频器。项目要求“增量输入都会导致显示值增加一次,无论按住增量按钮多长时间”,所以需要按键防抖模块。需要使用数码管显示,需要用到译码器。将几个模块输入ChatGPT使其生成代码,在此基础上修改代码完成项目。
3. 硬件框图
硬件为基于Lattice MXO2的小脚丫FPGA核心板,小脚丫FPGA团队最新推出的FPGA核心模块。
4. 实现的功能及图片展示
实现的功能有启动按钮开始计时,暂停按钮停止计时,增量按钮使秒表显示值增加一次,清除按钮使秒表清零。
5. 功能框图
我使用了chatGPT来生成功能框图。
6.仿真结果图
这是分频器模块仿真结果,发现分频器能实现12MHz到10Hz的分频。(使用Vivado仿真)
下面是秒表主程序仿真结果
清除按键功能仿真结果,使数码管显示变成0(我觉得间接体现了译码器模块功能)
启动按键功能仿真结果。使数码管开始计数。
暂停按键功能仿真结果。使数码管暂停计数。
增量按键功能没有仿真成功。
6. FPGA资源占用报告
· 寄存器位数:51个寄存器位中使用了1%,共计51个寄存器位。
· PFU寄存器:在4320个PFU寄存器中使用了1%,共计51个寄存器。
· PIO寄存器:在315个PIO寄存器中没有使用。
· SLICE数量:在2160个SLICE中使用了2%,共计48个SLICE。
· LUT4数量:在4320个LUT4中使用了2%,共计95个LUT4,其中55个用作逻辑LUTs,40个用作Ripple Logic。
· RAM数量:未使用任何SLICE作为RAM。
· Carry数量:在2160个SLICE中使用了1%,共计20个SLICE用作Carry。
· PIO sites使用情况:在105个PIO sites中使用了23个,占比26%。
· Block RAM数量:未使用任何Block RAM。
· GSR数量:在1个GSR中使用了100%。
7. 主要代码段及说明
module debounce(
input clk, //时钟信号
input rst, //复位信号
input button_in, //按键输入信号
output reg button_out //防抖输出信号
);
reg [15:0] cnt; //计数器
always @(posedge clk or negedge rst) begin
if(!rst) begin
cnt<=0; //复位使计数器清零
button_out<=0; //复位使输出信号置零
end else begin
if(button_in==button_out) begin
cnt<=0; //输入信号与输出信号相同时,计数器清零
end else if(cnt<16'hffff) begin
cnt <= cnt+1; //计数器计数
if (cnt==16'hfffe) begin
button_out <= button_in; //计数器将计满时更新输出信号
end
end
end
end
endmodule //防抖输入模块
该代码段为防抖输入模块,确保按键信号稳定后再输出。其中cnt是一个16位计数器,最后的输出信号button_out是经过防抖后的按键信号。
module Segment_led
(
input [3:0] cnt_ge, //个位
input [3:0] cnt_shi, //十位
output [8:0] seg_led_1, //个位数码管灯
output [8:0] seg_led_2 //十位数码管灯
);
reg [6:0] seg [9:0];
initial
begin
seg[0] = 7'h3f; // 0
seg[1] = 7'h06; // 1
seg[2] = 7'h5b; // 2
seg[3] = 7'h4f; // 3
seg[4] = 7'h66; // 4
seg[5] = 7'h6d; // 5
seg[6] = 7'h7d; // 6
seg[7] = 7'h07; // 7
seg[8] = 7'h7f; // 8
seg[9] = 7'h6f; // 9
end
assign seg_led_1[8:0] = {2'b00,seg[cnt_ge]};
assign seg_led_2[8:0] = {2'b01,seg[cnt_shi]};
endmodule //译码器模块
该段代码为译码器模块,实现了数码管的译码功能,根据输入的个位和十位数字,选择对应的数码管段亮起,以显示相应数字。
module divide(
input clk, //12MHz时钟信号
output reg clk_10h //分频后10Hz时钟信号
);
reg [31:0] counter; //计数器,用于分频
always @(posedge clk) begin
if (counter == 600000-1) begin // 12MHz分频到10Hz需要计数600000个周期
counter <= 0;
clk_10h <= ~clk_10h; //每计满240000个周期翻转一次输出信号
end else
counter <= counter + 1;
end
endmodule //分频器模块
该段代码为分频器模块,将FPGA开发板自带的12MHz时钟信号分频为10Hz时钟信号。
module miaobiao (clk, start, stop, inc, rst, seg_led_1, seg_led_2);
input clk, start, stop, inc, rst;
output [8:0] seg_led_1;
output [8:0] seg_led_2;
reg inc_out = 0; //上一时刻增量输出信号
reg [3:0] cnt_ge;
reg [3:0] cnt_shi;
reg start_flag = 0; //启动按键标志位
reg stop_flag = 0; //暂停按键标志位
reg start_out = 0; //上一时刻启动输出信号
reg stop_out = 0; //上一时刻暂停输出信号
wire clk_10h; //10Hz时钟信号
wire inc_pulse; //增量按键消抖后信号
//调用译码器模块
Segment_led u1(
.cnt_ge(cnt_ge),
.seg_led_1(seg_led_1),
.seg_led_2(seg_led_2),
.cnt_shi(cnt_shi)
);
//调用分频器模块
divide u2(
.clk(clk),
.clk_10h(clk_10h)
);
//对增量键进行防抖处理
debounce db_inc(
.clk(clk),
.rst(rst),
.button_in(inc),
.button_out(inc_pulse)
);
always @(posedge clk or negedge rst) begin
if(!rst) begin
//复位信号低有效
start_flag <= 0; //启动按键标志位置零
stop_flag <= 0; //暂停按键标志位置零
start_out <= 0; //启动输出信号置零
stop_out <= 0; //暂停输出信号置零
end else begin
start_out <= start; //更新启动输出信号
stop_out <= stop; //更新暂停输出信号
if(start && !start_out) begin
//第一次按下启动键,没有连续按下
//确保计数器开始计数
start_flag <= 1; //将启动标志位置1,保证计数器计数
stop_flag <= 0; //将暂停标志位置零,使计数器不停止
end
if(stop && !stop_out) begin
//第一次按下暂停键,没有连续按下
//确保计数器暂停计数
stop_flag <= 1; //将暂停标志位置1,使计数器暂停计数
end
end
end
该段always语句使开发板可以判断启动按键或暂停按键是否按下
//在10Hz时钟上计数
always @(posedge clk_10h or negedge rst) begin
if(!rst) begin
//复位信号低有效
cnt_ge <= 0; //使个位计数器置零
cnt_shi <= 0; //使十位计数器置零
inc_out <= 0; //使增量输出信号置零
end else begin
inc_out <= inc_pulse; //更新增量输出信号
if(start_flag && !stop_flag) begin
//当启动标志位为1,暂停标志位为0时,在10Hz时钟上计数
//启动计数功能
if(cnt_ge >= 9) begin
cnt_ge <= 0;
if(cnt_shi >= 9)
cnt_shi <= 0;
else
cnt_shi <= cnt_shi +1;
end else
cnt_ge <= cnt_ge + 1;
end
if(inc_pulse && !inc_out) begin
//当增量按键按下时计数器计数加1
if(cnt_ge>=9) begin
cnt_ge <= 0;
if(cnt_shi >= 9)
cnt_shi <= 0;
else
cnt_shi <= cnt_shi + 1;
end else
cnt_ge <= cnt_ge + 1;
end
end
end
endmodule
该段always语句使秒表在清除键按下时计数清零,启动键按下正常计数,暂停按键按下时暂停计数及增量按键按下时使计数增加1次。
整段代码是实现秒表计时功能,其中包含了按键的防抖处理、启动/暂停逻辑、10Hz时钟上的计数逻辑、增量按键的处理逻辑等。根据按键的操作控制计数器的计数,实现秒表的计时功能。同时,根据计数器的值,通过译码器模块显示在数码管上。
8. 遇到的主要难题及解决办法
由于是新学FPGA,很多东西不懂。最主要的是Verilog语言不懂。
解决办法:使用chatGPT和项目示例来实现不同模块,买了一本Verilog数字系统设计教程来学习。
这是ChatGPT给出的防抖输入模块,我将其中的一些信号名称缩写改成更方便我理解的名称。
这是chatGPT给出的主模块,同样将一些信号名改成和自己其他模块对应的信号名,并修改一些不对的地方。
这是ChatGPT给出的分频器模块,但我发现他这里面存在错误,应该是技术600000个周期翻转一次输出信号。同样改了信号名。
initial //在过程块中只能给reg型变量赋值,Verilog中有两种过程块always和initial
//initial和always不同,其中语句只执行一次
begin
seg[0] = 9'h3f; //对存储器中第一个数赋值9'b00_0011_1111,相当于共阴极接地,DP点变低不亮,7段显示数字 0
seg[1] = 9'h06; //7段显示数字 1
seg[2] = 9'h5b; //7段显示数字 2
seg[3] = 9'h4f; //7段显示数字 3
seg[4] = 9'h66; //7段显示数字 4
seg[5] = 9'h6d; //7段显示数字 5
seg[6] = 9'h7d; //7段显示数字 6
seg[7] = 9'h07; //7段显示数字 7
seg[8] = 9'h7f; //7段显示数字 8
seg[9] = 9'h6f; //7段显示数字 9
end
assign seg_led_1 = seg[seg_data_1];
assign seg_led_2 = seg[seg_data_2];
译码器模块参照stepfpga中的示例项目驱动数码管,由于秒表计数需要小数点。所以采用下段计时控制示例中的代码
initial
begin
seg[0] = 7'h3f; // 0
seg[1] = 7'h06; // 1
seg[2] = 7'h5b; // 2
seg[3] = 7'h4f; // 3
seg[4] = 7'h66; // 4
seg[5] = 7'h6d; // 5
seg[6] = 7'h7d; // 6
seg[7] = 7'h07; // 7
seg[8] = 7'h7f; // 8
seg[9] = 7'h6f; // 9
/*若需要显示A-F,解除此段注释即可
seg[10]= 7'hf7; // A
seg[11]= 7'h7c; // b
seg[12]= 7'h39; // C
seg[13]= 7'h5e; // d
seg[14]= 7'h79; // E
seg[15]= 7'h71; // F*/
end
assign seg_led_1[8:0] = {2'b00,seg[cnt_ge]};
assign seg_led_2[8:0] = {2'b00,seg[cnt_shi]};
除此之外没有解决的问题是我使用清零按钮时,按下去秒表清零,当松开时,秒表显示0.1。
由于对仿真不是很明白,简单仿真波形貌似显示一切正常。
秒表显示0.1原因:在按下清除按键时增量输出信号置为零。
解决办法:
if(!rst) begin
//复位信号低有效
cnt_ge <= 0; //使个位计数器置零
cnt_shi <= 0; //使十位计数器置零
inc_out <= 1; //使增量输出信号置1
end
所以我发现只要用心,还是能找到解决办法的。
9. 未来的计划或建议
在这次活动中我觉得我最大的收获应该是对AI的使用更加熟练,由于我平常使用的是MATLAB或ADS等软件,对FPGA应该只是一次感兴趣的尝试。由于这次活动一开始没有做,后面开始做时又被学校的事耽误,所以完成的不是很好,所以未来或许会完整地参加一次FPGA活动。