1.任务内容
通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。
使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0秒计数到9.9秒,然后翻转,计数值每0.1秒精确更新一次。
秒表使用四个按钮输入:开始、停止、增量和清除(重置)。开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次);停止输入使计数器停止递增,但使数码管显示当前计数器值;每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。
2.任务要求
- 在WebIDE环境下进行Verilog代码编程、综合、仿真、生成JED代码并下载到FPGA中进行验证(注:由于WebIDE的性能限制,仿真若在WebIDE环境中报错,可利用Diamond中的仿真功能,仅限于仿真)
- 每一个功能模块都通过GPT等大模型(工具不限制)来生成,进行验证、修改后整合在一起实现所需的功能
3.功能分析
3.1 开始按键
管脚分配时将第一个按键设置为“开始”。按键后,秒表以10Hz时钟速率递增。当计数至9.9时,秒表重置为0.0,重新开始计数。
3.2 停止按键
管脚分配时将第二个按键设置为“停止”。按键后,秒表停止计数。
3.3 增量按键
管脚分配时将第三个按键设置为“增量”。当秒表处于停止状态时,按键后,秒表显示值增加0.1,长按也只增加0.1。当秒表处于开始状态时,秒表同样随每次按键增加0.1,肉眼观察不明显。
3.4 清除按键
管脚分配时将第四个按键设置为“清除”。按键后,秒表重置为0.0。
详细的功能演示见视频。
4.实现思路
将代码分为三个模块,按键输入模块(顶层文件)、分频模块、数码管显示模块。在顶层文件中,需要实现通过按键调控数码管显示的功能,对“开始”、“停止”、“增量”、“清除”四个按键分别编程。本实验对消抖的需求较低,代码中不添加消抖模块,使程序更简洁。
5.代码说明
编写代码的过程使用了chatgpt,经过反复修改和验证,功能正确无误。代码的英文注释由chatgpt生成。
5.1 分频模块
module divide(
input clk, // Clock input
input rst, // Reset input
output reg clk_out // Divided clock output
);
reg [31:0] divide_counter; // Counter to keep track of clock cycles
always @(posedge clk or negedge rst) begin
/* Reset condition */
if( !rst )
divide_counter <= 32'b0; // Reset the counter to 0
/* Counter operation */
else begin
if( divide_counter == 32'd60_0000 - 1'b1 ) begin // If counter reaches 60 million minus 1
divide_counter <= 32'b0; // Reset the counter
clk_out <= ~clk_out; // Toggle the divided clock output
end
else begin
divide_counter <= divide_counter + 1'b1; // Increment the counter by 1
end
end
end
endmodule
这个分频模块的目的是将输入时钟信号分频,即减慢时钟速率。在这个秒表应用中,我们需要将输入时钟信号分频到10Hz的频率,以便每0.1秒更新一次计数器值。
编写思路如下:
- 模块定义:定义一个名为 divide 的 Verilog 模块,该模块接受一个输入时钟信号
clk
,并输出一个分频后的时钟信号clk_out
。 - 计数器和时钟输出寄存器:使用一个寄存器
divide_counter
来计数输入时钟信号的上升沿。另外,定义一个寄存器clk_out
来存储分频后的时钟信号。 - 时钟分频逻辑:在时钟信号的上升沿检测模块中,通过一个计数器来统计时钟信号的上升沿数量。当计数器达到预设的阈值时,将时钟输出
clk_out
取反,并将计数器清零,以实现分频。在本例中,为了将时钟分频到10Hz,需要在计数器达到一定值时(根据输入时钟的频率和目标频率计算),翻转clk_out
信号。 - 模块结构:将计数器和逻辑放在
always
块中,在每个输入时钟信号的上升沿触发下执行逻辑。当计数器达到阈值时,执行分频逻辑,否则继续递增计数器。 - 模块接口:定义模块的输入和输出端口,输入为时钟信号
clk
,输出为分频后的时钟信号clk_out
。
这样,当将这个分频模块与按钮输入和数码管显示模块结合使用时,可以实现秒表功能,确保计时器每0.1秒更新一次。
5.2 数码管显示模块
module segment_led(
input [3:0] units_display, // Input for the units digit of the display
input [3:0] tens_display, // Input for the tens digit of the display
output [8:0] led_1, // Output for the LED segments representing units digit
output [8:0] led_2 // Output for the LED segments representing tens digit
);
reg[8:0] seg_memory [10:0]; // Memory array to store segment patterns for each digit
/* segment led lookup table initialization */
initial
begin
seg_memory[0] <= 9'h3f; // Segments for displaying '0'
seg_memory[1] <= 9'h06; // Segments for displaying '1'
seg_memory[2] <= 9'h5b; // Segments for displaying '2'
seg_memory[3] <= 9'h4f; // Segments for displaying '3'
seg_memory[4] <= 9'h66; // Segments for displaying '4'
seg_memory[5] <= 9'h6d; // Segments for displaying '5'
seg_memory[6] <= 9'h7d; // Segments for displaying '6'
seg_memory[7] <= 9'h07; // Segments for displaying '7'
seg_memory[8] <= 9'h7f; // Segments for displaying '8'
seg_memory[9] <= 9'h6f; // Segments for displaying '9'
seg_memory[10]<= 9'h00; // Segments for displaying nothing (for clearing)
end
assign led_1 = seg_memory[units_display]; // Assign the segment pattern for units digit to LED output
assign led_2 = seg_memory[tens_display] + 9'h80; // Assign the segment pattern for tens digit to LED output
endmodule
这段 Verilog 代码实现了一个数码管显示模块,用于在两个七段显示器上显示数字。以下是编写思路:
segment_led
模块定义了一个数码管显示器,它有两个输入units_display
和tens_display
,分别表示个位和十位的数字。同时,模块有两个输出led_1
和led_2
,用于连接实际的数码管。seg_memory
是一个内部寄存器数组,用于存储每个数字对应的七段显示器的LED模式。数组的索引对应于要显示的数字。- 在
initial
块中,通过初始化每个数字的LED模式,将LED模式存储在seg_memory
数组中。例如,数字 0 的LED模式是9'h3f
,数字 1 的LED模式是9'h06
,以此类推。 - 使用
assign
语句,将units_display
和tens_display
对应的LED模式分配给led_1
和led_2
输出端口。这样,当units_display
和tens_display
变化时,LED模式会自动更新,从而在数码管上显示相应的数字。
通过这样的设计,可以在FPGA核心板上实现一个简单的数码管显示功能,用于显示秒表的计数值。
5.3 按键输入模块(顶层文件)
module top (
input clk, // Clock input
input rst, // Reset input
input start_btn, // Start button input
input stop_btn, // Stop button input
input inc_btn, // Increment button input
output [8:0] led1, // 7-segment display 1 output
output [8:0] led2 // 7-segment display 2 output
);
reg prev_inc_btn = 1'b0; // Previous state of increment button
reg [3:0] units_display; // Units place of the display
reg [3:0] tens_display; // Tens place of the display
reg start_pressed = 1'b0; // State indicating start button press
reg stop_pressed = 1'b0; // State indicating stop button press
reg prev_start = 1'b0; // Previous state of start button
reg prev_stop = 1'b0; // Previous state of stop button
wire clk_1hz; // Clock at 1 Hz
/* Clock divider */
divide u_divide (
.clk (clk),
.rst (rst),
.clk_out (clk_1hz)
);
/* Segment LED display */
segment_led u_segment_led (
.units_display (units_display),
.tens_display (tens_display),
.led_1 (led1),
.led_2 (led2)
);
/* Counter */
always @(posedge clk_1hz or negedge rst) begin
/* Reset */
if( !rst ) begin
units_display <= 1'b0;
tens_display <= 1'b0;
prev_inc_btn <= 1'b1;
end
/* Normal counter */
else begin
prev_inc_btn <= inc_btn;
if( start_pressed && !stop_pressed ) begin
if( units_display >= 4'd9 ) begin
units_display <= 4'd0;
if( tens_display >= 4'd9 )
tens_display <= 4'd0;
else
tens_display <= tens_display + 1'b1;
end
else
units_display <= units_display + 1'b1;
end
/* Press increase */
if( inc_btn & !prev_inc_btn ) begin
if( units_display >= 4'd9 ) begin
units_display <= 4'd0;
if( tens_display >= 4'd9 )
tens_display <= 4'd0;
else
tens_display <= tens_display + 1'b1;
end
else
units_display <= units_display + 1'b1;
end
end
end
/* Press detection */
always @(posedge clk or negedge rst) begin
/* Reset */
if( !rst ) begin
start_pressed <= 1'b0;
stop_pressed <= 1'b0;
prev_start <= 1'b0;
prev_stop <= 1'b0;
end
else begin
prev_start <= start_btn;
prev_stop <= stop_btn;
/* Press start */
if( start_btn && !prev_start ) begin
start_pressed <= 1'b1;
stop_pressed <= 1'b0;
end
/* Press stop */
if( stop_btn && !prev_stop ) begin
stop_pressed <= 1'b1;
end
end
end
endmodule
这个顶层模块是用Verilog编写的,旨在在FPGA核心板上创建一个带有两个七段显示器的2位数秒表。以下是这个模块的编写思路:
- 输入输出定义:模块有四个输入信号(clk,rst,start_btn,stop_btn,inc_btn)和两个输出信号(led1和led2),其中:
clk
是时钟信号,用于同步操作。rst
是重置信号,用于将秒表重置为0。start_btn
用于开始秒表计时。stop_btn
用于停止秒表计时。inc_btn
用于增加秒表计时的值。led1
和led2
是两个七段显示器的输出信号,用于显示秒表的计时值。
- 变量声明:模块内部声明了一些寄存器(reg)用于存储状态和输入信号的前一个状态。
- 时钟分频器:使用一个时钟分频器模块(
divide
)将输入时钟clk
分频为1Hz的时钟信号clk_1hz
,用于实现秒表的1秒递增。 - 七段显示器模块:使用一个七段显示器模块(
segment_led
),将秒表的计时值转换为对应的七段显示器的控制信号。 - 计数器:使用一个 always 块,在每次1Hz的时钟上升沿或者复位信号下降沿时进行计数。如果开始按钮按下且停止按钮未按下,则秒表开始递增计数。同时,也检测到了增加按钮的按下,并且在每次按下时增加计数值。当秒表达到9.9秒时,会从0开始重新计数。
- 按键检测:使用另一个 always 块,检测开始和停止按钮的按下。如果检测到开始按钮按下,会将秒表状态设置为开始计时;如果检测到停止按钮按下,会将秒表状态设置为停止计时。
通过这些模块,可以实现在FPGA核心板上创建一个简单的2位数秒表,并根据按键输入来控制秒表的开始、停止、增加和重置功能。
6.仿真结果
仿真代码的英文注释由chatgpt生成。
`timescale 1ns/1ps
module tb_top();
reg clk; // Clock signal
reg rst; // Reset signal
reg start_btn; // Start button signal
reg stop_btn; // Stop button signal
reg inc_btn; // Increment button signal
wire [8:0] led1; // Output signal for the first 7-segment display
wire [8:0] led2; // Output signal for the second 7-segment display
// Initializing signals
initial begin
rst = 1'b0; // Reset initially low
#100
rst = 1'b1; // Release reset after 100 time units
start_btn = 1'b1; // Start button initially released
stop_btn = 1'b1; // Stop button initially released
inc_btn = 1'b1; // Increment button initially released
/* test start */
#500
start_btn = 1'b0; // Press start button after 500 time units
#50
start_btn = 1'b1; // Release start button after 50 time units
/* test stop */
#500
stop_btn = 1'b0; // Press stop button after 500 time units
#50
stop_btn = 1'b1; // Release stop button after 50 time units
/* test increment */
#500
inc_btn = 1'b0; // Press increment button after 500 time units
#10000
inc_btn = 1'b1; // Release increment button after 10000 time units
#10000
inc_btn = 1'b0; // Press increment button after 10000 time units
#10000
inc_btn = 1'b1; // Release increment button after 10000 time units
#10000
inc_btn = 1'b0; // Press increment button after 10000 time units
#10000
inc_btn = 1'b1; // Release increment button after 10000 time units
end
// Generating clock signal
initial begin
clk = 1'b0; // Initial clock state
forever #10 clk = ~clk; // Toggle clock every 10 time units
end
// Instantiating the top module
top u_top(
.clk (clk), // Clock signal
.rst (rst), // Reset signal
.start_btn (start_btn), // Start button signal
.stop_btn (stop_btn), // Stop button signal
.inc_btn (inc_btn), // Increment button signal
.led1 (led1), // Output signal for the first 7-segment display
.led2 (led2) // Output signal for the second 7-segment display
);
endmodule
仿真过程出现了问题。从图上可以看到,对按键的仿真正确无误,但units_display和tens_display无反应,导致led无反应。这个问题有待进一步的研究。
7.资源利用
本模块使用chatgpt生成。将“FPGA映射”后的run.log放入chatgpt中,并要求其给出资源利用结果。
以下是chatgpt提供的结果:
资源利用报告提供了有关设计在目标设备上的资源利用情况的详细信息。以下是资源利用报告中的主要内容:
- 寄存器(Register)利用率:1%(34个寄存器被使用,总计4635个)
- PFU 寄存器(PFU registers)利用率:1%(34个寄存器被使用,总计4320个)
- PIO 寄存器(PIO registers)利用率:0%(未使用)
- SLICE(片上计算元件)利用率:1%(31个SLICE被使用,总计2160个)
- LUT4(Look-Up Table)利用率:1%(61个LUT4被使用,总计4320个)
- I/O 利用率:9.6%(23个PIO被使用,总计280个)
- GSR(Global Set/Reset)利用率:100%(1个GSR被使用,总计1个)
总体而言,资源利用率相对较低,表明设计在目标设备上的资源利用相对高效。
8.心得体会
本次项目对我来说既是一次新颖的尝试,又是一次不算轻松的挑战。在过去的大学生活中,我对fpga和werilog的了解不算深入,对chatgpt这一近年来出现的智能工具也涉猎不多。本次项目让我对这些领域有了更深层次的了解和认识,同样锻炼了我的编程能力。
在完成项目的过程中,我主要在程序编写时遇到了困难。本实验要求使用GPT等大模型工具来生成每一个模块的代码。然而,chatgpt直接生成的代码或多或少存在问题。经过研究,我发现应该向chatgpt精确地描述自己的需求,尽可能多地添加限制和提示,才有机会获得接近要求的代码。除此之外,chatgpt的上下文关联能力较弱,最好在一个对话框中向其完整地提出要求。当然,这个过程需要我们不断修改代码、放入WebIDE环境中进行验证。
对于零基础或基础较弱的学生而言,chatgpt并不能帮助他们一步登天,直接获得需求的代码。但对于有一定基础,或在项目过程中不断学习的学生而言,chatgpt可以减少一定的工作量,比如提供接近需求的框架、生成注释、给出资源利用情况,甚至帮助撰写项目报告等等。至少就目前而言,chatgpt等AI大模型工具还无法取代人类的工作。当然,这些工具的出现和不断升级也对相关领域工作者的专业能力提出了更高的要求。
此外,在微信群中与同学、硬禾科技的老师的交流也令我受益匪浅。感谢硬禾科技开展本次活动,给予我学习fpga领域知识、提升编程能力的平台和机会,让我度过了一个充实而满足的寒假。
9.未来建议
- 在WebIDE环境中,当对源代码进行修改后(假定代码修改前可以运行,修改后存在逻辑错误),如果不进行“逻辑综合”,直接点击“FPGA映射”,仍能生成JED文件,然而该文件为修改源代码前的JED文件,此时可能对使用者产生误导。我认为这种情况可以增加提示,提醒使用者进行“逻辑综合”,或者对“FPGA映射”添加“逻辑综合”的功能。
- 在使用WebIDE环境的人数较多时,“逻辑综合”与“FPGA映射”所需的时间长,甚至会无法使用。
- 仿真界面应添加水平拉条,便于看到完整的仿真结果。当前的仿真界面只能通过屏幕缩放查看被遮挡的波形,观察不便。
- 仿真界面波形密集时高低电平分辨不清,可在波形最左侧标注数字,方便观察。