一、项目需求
1.1任务宗旨
1.结合数字电路书本知识,深刻理解数字逻辑的功能实现及设计流程。
2.培养工程化设计理念、规范化的设计流程及解决未知问题的能力。
3.探索使用行业新工具在项目研发中存在的问题和解决方法。
1.2任务内容
通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。
图1 FPGA开发板展示
使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。
秒表使用四个按钮输入:开始、停止、增量和清除(重置)。 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。
1.3实现要求
1.在WebIDE环境下进行Verilog代码编程、综合、仿真、生成JED代码并下载到FPGA中进行验证(注:由于WebIDE的性能限制,仿真若在WebIDE环境中报错,可利用Diamond中的仿真功能,仅限于仿真)。
2.每一个功能模块都通过GPT等大模型(工具不限制)来生成,进行验证、修改后整合在一起实现所需的功能。
1.4 需要的基础
1.数字电路基础理论
2.数字逻辑设计思想
二、需求分析
1.硬件需求:
小脚丫FPGA核心板:提供FPGA芯片和其他必要的硬件组件,如数码管和按键接口。
两个数码管:用于显示秒表的当前值。每个数码管需要至少7个引脚(七段显示器)以及额外的引脚用于控制。
四个轻触按键:用于控制秒表的功能,包括开始、停止、增量和清除。
时钟模块:提供稳定的时钟信号,以便秒表可以准确计时。在这种情况下,时钟速率为10Hz,即每0.1秒触发一次。
2.功能分析:
开始按钮:按下开始按钮后,秒表开始计时,以10Hz的速率递增。这需要一个计数器来跟踪时间,并在每个时钟周期内增加适当的计数值。
停止按钮:按下停止按钮后,秒表停止计时,但显示器仍然显示当前的计数值。这需要一个逻辑来控制计数器的增加,并确保在停止时计数器不再递增。
增量按钮:按下增量按钮后,当前显示的计数值增加一个单位。这需要一个逻辑来监听按钮的按下,并相应地更新显示值。
清除按钮:按下清除按钮后,秒表的计数值被重置为0.0秒。这需要一个逻辑来将计数器的值清零。
3.软件设计:
状态机设计:可以使用状态机来实现秒表的不同状态,例如开始、停止、增量和清除。
时序分析:需要确保设计满足时序要求,尤其是对于按键输入和数码管显示的响应时间。
数码管控制:设计需要包括控制数码管以显示正确的数值。
4.测试计划:
功能测试:验证开始、停止、增量和清除功能是否按预期工作。
计时精度测试:通过比较实际计时和预期计时结果来验证秒表的计时精度。
稳定性测试:持续运行秒表以确保其稳定性和可靠性。
三、可能存在的问题
1.时序约束问题及时序逻辑设计复杂:
难题:时序约束未能满足,导致时序失败或时钟频率不稳定;设计复杂难以入手。
解决方法:通过调整时序约束,优化逻辑布局和时序路径,降低时序延迟;或者采用更高性能的器件或者更低的时钟频率。时序逻辑设计中涉及多个计时器和状态机。
2.资源利用率优化:
难题:资源利用率过高或过低,影响了设计的性能和成本。
解决方法:通过细致的逻辑优化、布局与布线优化,尽可能地利用FPGA资源;或者重新评估设计需求,选择适当规模的FPGA设备。
四、实现的方式
1.模块划分:
本次使用了三个模块进行程序的编写,分别是segment模块、key模块、main模块。下面将分别讲述其作用。segment模块,负责定义七段显示器的信号和初始化,以及将输入的数字转换为七段显示器需要的信号的逻辑。key模块,实现了按键的状态机逻辑,包括按键的按下、释放和按键状态的输出。main模块,主要功能实现模块,包括计时器、秒表逻辑和数码管输出控制。
2.计时器逻辑:
本次一共使用了两个计时器,一个用于秒数计时(one_second),另一个用于0.1秒的计时(01_second)。计时器在时钟的上升沿触发,并在复位时清零。根据计时器的值来控制秒表的计时和显示。
3.秒表逻辑:
使用了一个标志位(flag)来控制秒表的启动和停止。当按下开始按钮时,标志位设置为1,秒表开始计时。当按下停止按钮时,标志位设置为0,秒表停止计时,但显示当前值。按下增量按钮时,秒表的显示值增加。
4.按键逻辑:
每个按键都有自己的状态机处理逻辑,以滤除抖动和确保按键的正常响应.当按键按下时,状态机进入PRESS状态,计时器开始计数。在按键释放时,状态机进入RELEASE状态,计时器开始计数。按键按下和释放的状态由对应的输出信号(press_down和release)表示。
5.数码管输出控制:
将计数器的值转换为数码管需要的信号,然后将其赋值给对应的数码管输出。特别注意的是,控制第一个数码管的输出时,需要考虑到10.0秒时会进位,因此对其进行了额外的判断和处理。
五、功能框图
图2 功能框图
其中,main 模块是主模块,负责整个秒表的控制和显示。segment 模块负责将数字转换为数码管的七段信号,并进行显示。key 模块用于处理按键的状态,并输出按键的状态信号。time模块是计时器模块,负责计时和控制秒表的运行。
main 模块将输入的时钟信号和复位信号连接到其他模块,并接收来自按键模块和计时器模块的信号。根据按键信号控制秒表的启停和增量,同时根据计时器信号更新秒表的显示值,并将更新后的数值发送给 segment 模块进行显示。
六、代码及说明
//定义7段显示器
module segment (
input [3:0] seg_data_1, // 用于显示0~9数字的第一个7段显示器的四位输入
input [3:0] seg_data_2, // 第二个7段显示器的四位输入
output [8:0] seg_led_1, // 控制第一个7段显示器所需的九个信号
output [8:0] seg_led_2 // 控制第二个7段显示器所需的九个信号
);
reg [6:0] seg [9:0];
// 初始化7段显示器
initial begin
seg[0] = 7'b0111111; // 0
seg[1] = 7'b0000110; // 1
seg[2] = 7'b1011011; // 2
seg[3] = 7'b1001111; // 3
seg[4] = 7'b1100110; // 4
seg[5] = 7'b1101101; // 5
seg[6] = 7'b1111101; // 6
seg[7] = 7'b0000111; // 7
seg[8] = 7'b1111111; // 8
seg[9] = 7'b1101111; // 9
end
assign seg_led_1 = seg[seg_data_1];
assign seg_led_2 = seg[seg_data_2];
endmodule
module key (
input key, // 输入信号,表示按键状态
input clk, // 时钟信号
input reset, // 低电平复位信号
output reg press_down, // 信号,表示按键按下
output reg release_state, // 按键释放状态
output reg key_output // 输出信号,表示按键状态
);
// 状态机参数
localparam IDLE = 2'b00; // 空闲状态
localparam PRESS = 2'b01; // 按键按下滤波状态
localparam WAIT = 2'b10; // 等待按键释放状态
localparam RELEASE = 2'b11; // 按键释放滤波状态
parameter MCNT = 1000_000 - 1; // 20毫秒时间信号的最大计数值
// 内部信号
reg [1:0] state; // 状态机状态
reg [29:0] count; // 用于生成20毫秒时间信号的计数器
// 状态机逻辑
always @(posedge clk or negedge reset)
begin
if (!reset)
begin
// 复位状态机和标志位
state <= IDLE;
press_down <= 0;
release_state <= 0;
count <= 0;
key_output <= 1;
end
else
begin
case (state)
IDLE:
begin
release_state <= 0;
if (key)
begin
// 在按键按下时进入PRESS状态
state <= PRESS;
press_down <= 1;
count <= 0;
key_output <= 0;
end
end
PRESS:
begin
if (count >= MCNT)
begin
// 在超时后进入WAIT状态
state <= WAIT;
count <= 0;
end
else if (!key)
begin
// 在按键释放时返回到IDLE状态
state <= IDLE;
count <= 0;
end
else
begin
// 继续滤波
count <= count + 1;
end
end
WAIT:
begin
press_down <= 0;
if (!key)
begin
// 在按键释放时进入RELEASE状态
state <= RELEASE;
release_state <= 1;
count <= 0;
key_output <= 1;
end
end
RELEASE:
begin
if (count >= MCNT)
begin
// 在超时后返回到IDLE状态
state <= IDLE;
count <= 0;
end
else if (key)
begin
// 在按键按下时返回到WAIT状态
state <= WAIT;
count <= 0;
end
else
begin
// 继续滤波
count <= count + 1;
end
end
endcase
end
end
endmodule
module main(
input clk, // 时钟
input reset, // 低电平复位
input [1:0] key, // 输入的按键状态
input add, // 未使用的输入
output wire [8:0] seg_led_1, // 控制第一个7段显示器的输出
output wire [8:0] seg_led_2 // 控制第二个7段显示器的输出
);
// 计时器
reg [23:0] one_second; // 1秒计时器
reg [20:0] o1_second; // 100毫秒计时器
reg flag; // 状态标志
reg [3:0] seg_data_1; // 第一个7段显示器的数据
reg [3:0] seg_data_2; // 第二个7段显示器的数据
wire release_state; // 按键释放状态
// 计时1秒和0.1秒
always @(posedge clk or negedge reset)
begin
if (!reset)
begin
one_second <= 0;
o1_second <= 0;
end
else
begin
if (flag)
begin
if (one_second == 24'd12000_000 - 1)
one_second <= 0;
else
one_second <= one_second + 1;
if (o1_second == 21'd1200_000 - 1)
o1_second <= 0;
else
o1_second <= o1_second + 1;
end
end
end
// 控制第二个7段显示器的数据
always @(posedge clk or negedge reset)
begin
if (!reset)
seg_data_2 <= 0;
else
begin
if (((seg_data_2 == 4'd9) && ((o1_second == 21'd1200_000 - 1) || release_state)) || (flag && (o1_second == 21'd1200_000 - 1)))
seg_data_2 <= 0;
else if ((flag || release_state) && !((seg_data_2 == 4'd9) && (o1_second == 21'd1200_000 - 1)))
seg_data_2 <= seg_data_2 + 1;
end
end
// 控制第一个7段显示器的数据
always @(posedge clk or negedge reset)
begin
if (!reset)
seg_data_1 <= 0;
else
begin
if (((seg_data_2 == 4'd9) && (seg_data_1 == 4'd9) && (one_second == 24'd12000_000 - 1)) || (flag && (seg_data_2 == 4'd9) && (seg_data_1 == 4'd9)))
seg_data_1 <= 0;
else if (((flag && one_second == 24'd12000_000 - 1) || release_state) && !(seg_data_2 == 4'd9))
seg_data_1 <= seg_data_1 + 1;
end
end
// 控制状态标志
always @(posedge clk or negedge reset)
begin
if (!reset)
flag <= 2'd0;
else
begin
case (key)
2'b10: flag <= 1; // 开始
2'b01: flag <= 0; // 停止
default: flag <= flag;
endcase
end
end
// 实例化segment模块
segment segment_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)
);
// 实例化key模块
key key_inst(
.key (key[0]), // 传递单个比特位作为输入
.clk (clk),
.reset (reset),
.press_down (press_down),
.release_state (release_state), // 修改为正确的信号名称
.key_output (key_output)
);
endmodule
七、结果展示
通过上述程序,可以成功通过四个按钮实现开始、停止、增量和清除(重置)功能,其结果如下。
图3 清除展示
图4 停止展示
图5 递增展示
图6 启动展示
八、资源占用
由于不太会查看资源占用情况,所以使用ChatGPT对run.log进行了分析。分析情况如下图。
图7 资源占用情况
九、实验心得
在完成本次实验后,我获得了以下体会和心得。首先,模块化设计的非常重要。在本次实验中,模块化设计使得代码更加清晰和易于理解。通过将功能划分为不同的模块,可以更好地组织代码,提高代码的可维护性和可扩展性。此外,本次实验还使用了状态机,按键模块中使用了状态机来处理按键的状态转换,这种方法有效地解决了按键抖动和状态转换的问题,使得按键操作更加稳定和可靠。最后还得感谢群里面老师同学的帮助,本次实验除了有GPT等平台提供的帮助,更有同学和老师的耐心指导,正是因为他们的帮助,我才能找准实验思路,顺利完成此项目。
总的来说,本次实验让我更加熟悉了 FPGA 设计和数字电路的相关知识,提高了我的实践能力和问题解决能力。或许以后我不会再接触类似项目,但它已然在我的心里画下了浓墨重彩的一笔。