2024年寒假练 - 用基于小脚丫FPGA核心板设计秒表
该项目使用了小脚丫核心板,实现了秒表的设计,它的主要功能为:计时。
标签
FPGA
小脚丫
培训
秒表
参加活动
dzslljq
更新2024-04-08
北京理工大学
166

一、    项目需求

基于Lattice MXO2的小脚丫FPGA核心板设计具有启动、停止、递增和清除功能的秒表

WebIDE环境下进行Verilog代码编程、综合、仿真、生成JED代码并下载到FPGA中进行验证

每一个功能模块都通过GPT等大模型(工具不限制)来生成,进行验证、修改后整合在一起实现所需的功能

二、    需求分析

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

   使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。

秒表使用四个按钮输入:开始、停止、增量和清除(重置)。 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间;复位/清除输入强制计数器值为零。

image.png

三、    硬件介绍

时钟频率12MHz

共阴极数码管

按键按下时为0,默认为1

四、    实现方式

代码实现分成了两个模块Seg7_display.vtime_logic.vstopwatch.v为顶层模块。在第五部分代码及说明中,结合代码详细说明了实现方式。

aef953625aca580baed7d4ad9b9bda4.jpg

管脚分配如下图所示:

image.png

 

五、    代码及说明

(1)  Seg7_display.v

module seg7_display: 定义一个名为 seg7_display 的模块,

输入输出端口:

seg_data_1 和 seg_data_2 是四位宽的输入端口,用于输入要在数码管上显示的数字。

seg_led_1 和 seg_led_2 是九位宽的输出端口,分别控制第一个和第二个数码管的显示。

reg [8:0] seg [9:0]; 和 reg [8:0] seg2 [9:0];:这两行定义了两个 reg 类型的二维数组变量 seg seg2,分别存储了数码管显示数字的数据。每个数组包含 10 个数,每个数占 9 位。

initial 块:在模块初始化时,给 seg seg2 数组赋初值。

seg 数组存储了显示数字 0 9 的共阴极对应的 LED 亮度控制信号。

seg2 数组存储了显示数字 0 9 的共阴极对应的 LED 亮度控制信号。

assign seg_led_1 = seg[seg_data_1]; 和 assign seg_led_2 = seg2[seg_data_2];:将输入的数字索引作为索引,从 seg seg2 数组中取出相应的数码管显示数据,赋给输出端口 seg_led_1 seg_led_2,以控制数码管的显示。

使用GPT生成代码

image.png

 

修改后:

// Module Function:数码管的译码模块初始化

module seg7_display (
input [3:0] seg_data_1, //数码管需要显示0~9十个数字,所以最少需要4位输入做译码
input [3:0] seg_data_2, //小脚丫上第二个数码管
output [8:0] seg_led_1, //在小脚丫上控制一个数码管需要9个信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A
output [8:0] seg_led_2 //在小脚丫上第二个数码管的控制信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A
);

reg [8:0] seg [9:0]; //定义了一个reg型的数组变量,相当于一个10*9的存储器,存储器一共有10个数,每个数有9位宽

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
reg [8:0] seg2 [9:0]; //定义了一个reg型的数组变量,相当于一个10*9的存储器,存储器一共有10个数,每个数有9位宽

initial //在过程块中只能给reg型变量赋值,Verilog中有两种过程块always和initial
//initial和always不同,其中语句只执行一次
begin
seg2[0] = 9'hbf; //对存储器中第一个数赋值9'b00_0011_1111,相当于共阴极接地,DP点变低不亮,7段显示数字 0
seg2[1] = 9'h86; //7段显示数字 1
seg2[2] = 9'hdb; //7段显示数字 2
seg2[3] = 9'hcf; //7段显示数字 3
seg2[4] = 9'he6; //7段显示数字 4
seg2[5] = 9'hed; //7段显示数字 5
seg2[6] = 9'hfd; //7段显示数字 6
seg2[7] = 9'h87; //7段显示数字 7
seg2[8] = 9'hff; //7段显示数字 8
seg2[9] = 9'hef; //7段显示数字 9
end
assign seg_led_1 = seg[seg_data_1]; //连续赋值,这样输入不同四位数,就能输出对于译码的9位输出
assign seg_led_2 = seg2[seg_data_2]; //连续赋值,这样输入不同四位数,就能输出对于译码的9位输出

endmodule


 

(2)time_logic.v

模块声明:定义了一个模块名为 time_logic,包含输入时钟 clk、复位信号 rst_n、开始按钮 start、停止按钮 stop、增量按钮 increment,以及两个BCD编码的数码管显示输出 seg_data_1 seg_data_2

内部信号定义:定义了用于产生10Hz时钟的基础计数器 base_counter、存储当前秒表计数值的 count、运行标志位 run_flag 以及单次增量按钮脉冲信号 btn_increment_pulse

参数定义:定义了分频计数器的参数 CNT_1S CNT_100MS

时钟分频逻辑:使用 base_counter 实现了将12MHz时钟分频为10Hz的逻辑,以产生秒表的基准计时。

秒表计数逻辑:根据时钟的计数和按钮状态更新秒表的显示数值。当基础计数器计数达到10Hz时钟的周期时,根据运行状态 run_flag 和按钮状态进行数值更新。如果运行状态为运行且基础计数器计数达到100ms,秒表数值递增。

运行标志位逻辑:根据开始和停止按钮的状态更新运行标志位 run_flag

增量按钮单次脉冲生成逻辑:根据增量按钮的状态生成单次脉冲信号 btn_increment_pulse

使用GPT生成代码

image.png

image.png


修改后:

module time_logic(
input clk, // 12MHz FPGA板上的时钟输入
input rst_n, // 复位信号(低电平有效)
input start, // 开始按钮
input stop, // 停止按钮
input increment, // 增量按钮
output reg[3:0] seg_data_1, // 数码管显示,BCD编码
output reg[3:0] seg_data_2 // 数码管显示,BCD编码
);

// 定义内部信号
reg [25:0] base_counter; // 用于产生10Hz时钟的基础计数器
reg [7:0] count; // 用于存储当前秒表计数值(0-99)
reg run_flag; // 运行标志位
reg btn_increment_pulse; // 单次增量按钮脉冲

// 12MHz -> 10Hz分频计数器(12,000,000 / 10 = 1,200,000)
parameter CNT_1S = 26'd12_000_000;
parameter CNT_100MS = CNT_1S/10;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
base_counter <= 0;
end else begin
if (base_counter >= CNT_100MS - 1) begin
base_counter <= 0;
end else if (run_flag) begin
base_counter <= base_counter + 1;
end
end
end

// 秒表计数逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
seg_data_1 <= 0;
seg_data_2 <= 0;
end else if (base_counter == CNT_100MS - 1 && run_flag) begin
if (seg_data_2 == 8'd9 && seg_data_1 == 8'd9) begin
seg_data_1 <= 0;
seg_data_2 <= 0;
end else if (seg_data_1 == 8'd9) begin
seg_data_1 <= 0;
seg_data_2 <= seg_data_2 + 1;
end else begin
seg_data_1 <= seg_data_1 + 1;
seg_data_2 <= seg_data_2;
end
end else if (btn_increment_pulse && !run_flag) begin
if (seg_data_2 == 8'd9 && seg_data_1 == 8'd9) begin
seg_data_1 <= 0;
seg_data_2 <= 0;
end else if (seg_data_1 == 8'd9) begin
seg_data_1 <= 0;
seg_data_2 <= seg_data_2 + 1;
end else begin
seg_data_1 <= seg_data_1 + 1;
seg_data_2 <= seg_data_2;
end
end
end

// 运行标志位逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
run_flag <= 0;
end else if (!start) begin
run_flag <= 1;
end else if (!stop) begin
run_flag <= 0;
end
end

// 增量按钮单次脉冲生成逻辑(忽略消抖处理)
reg increment_last_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
increment_last_state <= 0;
btn_increment_pulse <= 0;
end else begin
increment_last_state <= increment;
if (!increment && increment_last_state) begin
btn_increment_pulse <= 1;
end else begin
btn_increment_pulse <= 0;
end
end
end

endmodule



(3)stopwatch.v(顶层模块)

首先定义一些输入和输出信号:

clk:时钟输入,12MHzFPGA板上的时钟信号。

rst_n:复位信号,低电平有效。

start:开始按钮信号。

stop:停止按钮信号。

increment:增量按钮信号。

seg_led_1:控制第一个数码管的9个信号。

seg_led_2:控制第二个数码管的9个信号。

然后定义一个参数 CNT_1S 表示时钟频率,对应的值为12,000,000。接着通过 time_logic 模块实例化一个时钟逻辑模块,该模块会根据时钟信号进行计时,并根据开始、停止和增量按钮的信号控制计时逻辑。

实例化 seg7_display 模块,该模块根据 seg_data_1 seg_data_2 BCD编码输入,控制两个数码管的显示。

整体流程是通过时钟信号进行计时,并根据按钮信号控制计时的开始、停止和增量,然后将计时结果通过BCD编码显示在两个数码管上。

使用GPT生成代码:

image.png

修改后:

module stopwatch(
input clk, // 12MHz FPGA板上的时钟输入
input rst_n, // 复位信号(低电平有效)
input start, // 开始按钮
input stop, // 停止按钮
input increment, // 增量按钮
output [8:0] seg_led_1, // 控制第一个数码管的9个信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A
output [8:0] seg_led_2 // 控制第二个数码管的9个信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A
);

parameter CNT_1S = 26'd12_000_000;//定义时钟频率

// time_logic模块的输出端口与seg7_display模块的输入端口将共享同一寄存器
wire [3:0] seg_data_1; // 为数码管显示所用的BCD编码
wire [3:0] seg_data_2; // 为第二个数码管显示所用的BCD编码

// 实例化time_logic模块
time_logic #(CNT_1S)time_logic_inst(
.clk(clk),
.rst_n(rst_n),
.start(start),
.stop(stop),
.increment(increment),
.seg_data_1(seg_data_1),
.seg_data_2(seg_data_2)
);

// 实例化seg7_display模块
seg7_display seg7_display_inst(
.seg_data_1(seg_data_1),
.seg_data_2(seg_data_2),
.seg_led_1(seg_led_1),
.seg_led_2(seg_led_2)
);

endmodule


六、    仿真波形图(modelsim)

image.png

image.png

image.png


七、    FPGA资源利用说明

image.png

image.png

八、    总结

通过这次实践,我深刻体会到了 FPGA 编程的魅力和挑战。以下是我在这个项目中所获得的一些体会和感悟:

  1. 学习曲线陡峭:由于初次接触 FPGA 编程,我发现学习曲线非常陡峭。需要掌握 Verilog HDL 语言,了解硬件逻辑设计的基本原理以及 FPGA 架构的特点。但通过不断的学习和实践,我逐渐掌握了这些知识和技能。
  2. 理论与实践结合:在项目中,我不仅学习了理论知识,还将其应用到了实践中。从设计到调试,每一个阶段都需要理论知识的支撑,同时也需要不断地实践和调试来验证和完善设计。
  3. 解决问题的能力:在项目中,我遇到了各种各样的问题,如时序约束、逻辑错误等。通过查阅资料、向GPT求助以及尝试不同的解决方案,我逐渐培养了解决问题的能力,并从中获得了成长。
  4. 持之以恒:实现一个项目并不是一蹴而就的,需要持之以恒地坚持下去。在遇到困难和挫折时,我学会了不断地调整思路、寻找解决方案,最终取得了成功。

通过这个项目,我不仅学到了 FPGA 编程的技术知识,还培养了解决问题的能力以及持之以恒的品质。我相信这些经验和收获将对我的未来学习和工作有着重要的影响。

附件下载
archive (1).zip
代码
项目总结报告.pdf
项目总结报告
团队介绍
北京理工大学
团队成员
李浚萁
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号