1. 项目需求
- 使用小脚丫 FPGA 核心板上的 2 个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。
- 在 WebIDE环境 下进行 Verilog 代码编程、综合、仿真、生成 JED 代码并下载到FPGA中进行验证。
- 开发过程中,每一个功能模块都通过 GPT 等大模型(工具不限制)来生成,进行验证、修改后整合在一起实现所需的功能。
2. 需求分析
2.1 秒表
- 输入
- 开始按键
按压后,秒表开始以 10Hz 时钟速率递增(即每0.1秒计数一次),数码管从 0.0 开始计数到 9.9,然后重复此过程 - 停止按键
按压后,计数器停止递增,数码管保持当前计数器值 - 增量按键
每按一次,数码管显示值增加 0.1 - 清除按键
计数器恢复为 0.0
- 开始按键
- 输出
使用七段数码管作为输出设备,需要能够显示 0.0 至 9.9 的计数范围,也就是说一位数码管能够显示数字 0-9,另一位需要多显示一个小数点 - 时钟
每 0.1 秒计数器更新一次,也就是说需要产生一个 10Hz 的时钟
2.2 综合仿真
需要熟悉 WebIDE环境 的使用,从硬禾学堂的教学视频来看,似乎比较简单,特别是管脚约束比较便捷。
2.3 GPT 大模型
这里可以使用网上免费的 chatGPT 的第三方服务,可以自行搜索,也可以使用电子森林AI助手)。
3. 功能框图
3.1 模块分解
3.2 信号连接
4. 项目开发
在本项目的开发中,使用 WebIDE 创建项目工程,然后通过 GPT 生成各模块的代码,稍加调整后导入到 WebIDE 工程中,然后综合、仿真、生成 JED 文件。
4.1 数码管显示模块
数码管
数码管是一种常见的数字显示设备,从段数来分,常见的有7段和8段数码管:
- 7段数码管由7个独立控制的线性排列的LED(发光二极管)组成。这些LED按照特定的排列方式被用来显示数字0到9和一些字母,通常用
a-g
来分别对这7个LED进行编号。 - 8段数码管比传统的7段数码管多了一个可控制的额外段 ,除了
a-g
七个标准LED段之外,8段数码管还包括了一个dp
LED段用于表示小数点。如下图:
按发光二极管单元连接方式,可以分为共阳极数码管和共阴极数码管:
- 共阳极,对不同的LED段输出低电平可点亮LED
- 共阴极,对不同的LED段输出高电平可点亮LED
在小脚丫开发板上使用的是2位8段共阴极数码
共阴极数码管数字和字母的编码列表
字符 | 二进制编码 | 16进制编码 |
---|---|---|
0 | 00111111 | 0x3F |
1 | 00000110 | 0x06 |
2 | 01011011 | 0x5B |
3 | 01001111 | 0x4F |
4 | 01100110 | 0x66 |
5 | 01101101 | 0x6D |
6 | 01111101 | 0x7D |
7 | 00000111 | 0x07 |
8 | 01111111 | 0x7F |
9 | 01101111 | 0x6F |
A | 01110111 | 0x77 |
B | 01111100 | 0x7C |
C | 00111001 | 0x39 |
D | 01011110 | 0x5E |
E | 01111001 | 0x79 |
F | 01110001 | 0x71 |
GPT 提示
代码调整
GPT 生成的代码,共阴极数码管显示数字 0 - 9 所对应的编码是错误的,这里手动修正
另外,数码管的编码位宽其实是9位,bit-0 到 bit-6 控制 7 段线形 LED,bit-7 对应的是小数点,bit-8 对应的控制位能熄灭所有的 LED,需要设置为 0,数码管才能正常工作。我在调试过程中因为忽视了 bit-8,没有对这个 bit 位赋值,并且没有做管脚约束,导致这个管脚的输入电平处于不确定态,从来导致数码管的亮度特别低,似乎在闪烁。
调整后的代码如下:
module bcd2segment (
input wire [3:0] bcd,
output reg [8:0] seg
);
/*
* control the display of decimal point
* - 0: off
* - 1: on
*/
parameter dp_enable = 0;
always @(*) begin
case (bcd)
4'b0000: seg = 9'h3F; // number '0'
4'b0001: seg = 9'h06; // number '1'
4'b0010: seg = 9'h5B; // number '2'
4'b0011: seg = 9'h4F; // number '3'
4'b0100: seg = 9'h66; // number '4'
4'b0101: seg = 9'h6D; // number '5'
4'b0110: seg = 9'h7D; // number '6'
4'b0111: seg = 9'h07; // number '7'
4'b1000: seg = 9'h7F; // number '8'
4'b1001: seg = 9'h6F; // number '9'
default: seg = 9'h00;
endcase
if (dp_enable) begin
seg[7] = 1; // show decimal point
end
end
endmodule
4.2 16进制数转BCD编码模块
BCD 编码
当需要将数字显示在数码管上时,BCD(Binary-Coded Decimal)编码。BCD编码是一种用二进制数形式来表示十进制数的方式,每个十进制数都用4个位来进行表示。例如,十进制数57会被表示为0101 0111
。
在控制数码管时,使用BCD编码可以简化数字到显示的转换过程。
GPT 提示
或许是因为我用的 GPT 3.5 的原因,也有可能是我提示的方式不对。在尝试多次提示 GPT “16进制转 BCD 编码的 verilog 实现” 之后,我始终没能得到合理的答案,GPT 尝试将两位16进制数的值全部遍历一遍,这种代码是没有办法使用的。
代码调整
最终我参考下文实现了这个模块:
module hex2bcd (
input wire [6:0] hex,
output reg [7:0] bcd
);
reg [3:0] ones;
reg [3:0] tens;
integer i;
always @(*) begin
ones = 4'd0;
tens = 4'd0;
for (i = 6; i >= 0; i = i - 1) begin
if (ones >= 4'd5)
ones = ones + 4'd3;
if (tens >= 4'd5)
tens = tens + 4'd3;
tens = {tens[2:0], ones[3]};
ones = {ones[2:0], hex[i]};
end
bcd = {tens, ones};
end
endmodule
4.3 按键消抖模块
按键消抖
在带有按键的系统中,当用户按下按钮时,机械开关可能会在接点闭合之前或之后产生多次不稳定的接触和分离。这种不稳定性会导致按键信号的抖动,可能导致系统错误地识别了多次按下按钮,为了避免这种误触发,按键消抖是必须要做的。
按键消抖模块可以确保只有在按键稳定按下一段时间后才发出按键信号,从而避免因按键抖动而引起的误操作。因此,按键消抖对于确保数字电子系统的可靠性和稳定性非常重要。
GPT 提示
代码调试
从 GPT 的回答来看由一些错误,但是大概思路已经给出来了,在检测到按键状态改变时,开始计数,当计数到20ms之后,按键状态始终保持刚才的状态,就保存为消抖后的按键状态。如果中间检测到按键跳变,那就把计数清零。
调整后的代码如下:
module debounced_key (
input wire clk, // Clock input
input wire rst_n, // Active low reset signal
input wire key, // Input for the key
output reg debounced_key // Output for the debounced key
);
parameter COUNT_WIDTH = 20; // Width of the counter
parameter COUNT_MAX = 240000; // Max value for the counter
reg [COUNT_WIDTH-1:0] count; // Counter with specified width
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
count <= 1'b0;
// Initial state set to high
debounced_key <= 1'b1;
end else begin
if (key != debounced_key) begin
if (count == COUNT_MAX) begin
debounced_key <= key; // Update debounced key state
end else begin
count <= count + 1'b1;
end
end else begin
count <= 1'b0;
end
end
end
endmodule
4.4 按键脉冲模块
增量按键要求
增量按键要求每按一次,数码管显示值增加 0.1,且持续按压住时,计数不再增加。也就是说在检测到按键按下时,产生一个时钟周期的脉冲,在计数模块内检测到这个脉冲时,把计数值加1即可。
代码
在按键消抖模块上稍加修改实现代码如下:
其实为了节省资源,应该将按键脉冲集成到消抖模块中一起实现,这里我拆成两个模块加以区分。
module debounced_key_pulse (
input wire clk, // Clock input
input wire rst_n, // Active low reset signal
input wire key, // Input for the key
output key_pulse // Output for the key_pulse signal
);
parameter COUNT_WIDTH = 20; // Width of the counter
parameter COUNT_MAX = 240000; // Max value for the counter
reg [COUNT_WIDTH-1:0] count; // Counter with specified width
reg debounced_key; // Debounced key status
reg previous_key; // Previous key status
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
count <= 1'b0;
// Initial state set to high
debounced_key <= 1'b1;
previous_key <= 1'b1;
end else begin
previous_key <= debounced_key; // Store previous state before the change
if (key != debounced_key) begin
if (count == COUNT_MAX) begin
debounced_key <= key; // Update debounced key state
end else begin
count <= count + 1'b1;
end
end else begin
count <= 1'b0;
end
end
end
// Update key_pulse
assign key_pulse = previous_key & ( ~debounced_key);
endmodule
4.5 计数模块
说明
在计数模块中,除了要每100ms更新计数值之外,还要根据输入的按键状态做出不同的响应。可以使用一个简单的状态转换来实现。
GPT 提示
代码调整
在 GPT 产生的代码基础上,增加100ms周期的计数器更新。代码如下:
module counter_with_key (
input wire clk, // Clock input
input wire rst_n, // Active low reset signal
input wire start_key, // Input for start key
input wire pause_key, // Input for pause key
input wire count_up_key, // Input for count up key
output reg [6:0] count_output // Output for the count value
);
reg [ 1:0] state; // State register to store the current state
reg [23:0] timer;
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
count_output <= 7'd0; // Initialize counter to 0
timer <= 24'd0;
state <= 2'b00; // Set initial state to idle
end else begin
case (state)
2'b00: begin // Idle state
if (start_key == 1'b0) begin
state <= 2'b01; // Transition to counting state on start key press
end
if (count_up_key == 1'b1) begin
if (count_output == 7'd99) begin
count_output <= 7'b0; // Reset count to 0 when it reaches 99
end else begin
count_output <= count_output + 1'd1; // Increment count
end
end
end
2'b01: begin // Counting state
if (pause_key == 1'b0) begin
state <= 2'b00; // Transition back to idle state on pause key press
end else if (timer == 1199999) begin
if (count_output == 7'd99) begin
count_output <= 7'd0; // Reset count to 0 when it reaches 99
end else begin
count_output <= count_output + 1'd1; // Increment count
end
timer <= 24'd0;
end else begin
timer <= timer + 1'd1;
end
end
default: begin
//state <= 2'b00; // Default to idle state
end
endcase
end
end
endmodule
4.6 顶层模块
顶层模块实例化下列模块
- 按键消抖模块
- 按键脉冲模块
- 计数模块
- 16进制转BCD码模块
- 数码管显示模块
代码如下:
module stopwatch (
input wire clk,
input wire rst_n,
input wire start_key,
input wire pause_key,
input wire count_up_key,
output wire [8:0] seg0_output,
output wire [8:0] seg1_output
);
wire debounced_start_key, debounced_pause_key, debounced_count_up_key;
wire [6:0] count;
wire [7:0] bcd;
// start key debounce instance
debounced_key debounced_key_inst_0 (
.clk (clk ),
.rst_n (rst_n ),
.key (start_key ),
.debounced_key (debounced_start_key)
);
// pause key debounce instance
debounced_key debounced_key_inst_1 (
.clk (clk ),
.rst_n (rst_n ),
.key (pause_key ),
.debounced_key (debounced_pause_key)
);
// count up key debounce instance
debounced_key_pulse debounced_key_pulse_inst (
.clk (clk ),
.rst_n (rst_n ),
.key (count_up_key ),
.key_pulse (debounced_count_up_key)
);
// counter instanse
counter_with_key counter_with_key_inst (
.clk (clk ),
.rst_n (rst_n ),
.start_key (debounced_start_key ),
.pause_key (debounced_pause_key ),
.count_up_key (debounced_count_up_key),
.count_output (count )
);
// hexadecimal to bcd instance
hex2bcd hex2bcd_inst (
.hex (count),
.bcd (bcd )
);
// segment 2 instance
bcd2segment bcd2segment_inst_0 (
.bcd (bcd[3:0] ),
.seg (seg1_output)
);
// segment 1 instanse, with decimal point displayed
bcd2segment #(.dp_enable (1)) bcd2segment_inst_1 (
.bcd (bcd[7:4] ),
.seg (seg0_output)
);
endmodule
4.7 使用 WebIDE 创建项目工程
- 创建项目
在 WebIDE环境 注册账号之后,点击首页的 马上开始创建项目,输入项目名称和选择设备之后点击提交: - 新建
stopwatch.v
- 设置为顶层文件
- 依次新增各个子模块
- 逻辑综合
- 管脚分配
- FPGA映射
- 下载 JED 文件
- 将 JED 文件拖入小脚丫的系统U盘里即可完成烧录
5. 仿真波形图
尝试使用 WebIDE 进行仿真时老是报错,具体错误信息见下方,于是我换成 modelsim 仿真
WebIDE 在线仿真出错信息
Reading /usr/local/altera9.1/modelsim_ae/tcl/vsim/pref.tcl
# 6.5b
# do sim.tcl
# ** Warning: (vlib-34) Library already exists at "work".
# Modifying /usr/local/altera9.1/modelsim_ae/linux/../modelsim.ini
# Model Technology ModelSim ALTERA vlog 6.5b Compiler 2009.10 Oct 1 2009
# -- Compiling module bcd2segment
# -- Compiling module counter_with_key
# -- Compiling module debounced_key
# -- Compiling module debounced_key_pulse
# -- Compiling module hex2bcd
# -- Compiling module stopwatch
#
# Top level modules:
# stopwatch
# Model Technology ModelSim ALTERA vlog 6.5b Compiler 2009.10 Oct 1 2009
# -- Compiling module debounced_key_tb
#
# Top level modules:
# debounced_key_tb
# vsim -lib work null
# ** Error: (vsim-3170) Could not find '/opt/vhosts/stepfpga/hh-stepfpga/projects/6/10138/work.null'.
# Error loading design
Error loading design
下面是部分模块的仿真波形:
按键消抖模块仿真
按键脉冲模块仿真
6. FPGA的资源利用说明
资源使用如下:
Design Summary:
Number of registers: 96 out of 4635 (2%)
PFU registers: 96 out of 4320 (2%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 103 out of 2160 (5%)
SLICEs as Logic/ROM: 103 out of 2160 (5%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 50 out of 2160 (2%)
Number of LUT4s: 205 out of 4320 (5%)
Number used as logic LUTs: 105
Number used as distributed RAM: 0
Number used as ripple logic: 100
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%)
Notes:-
1. Total number of LUT4s = (Number of logic LUT4s) + 2*(Number of distributed RAMs) + 2*(Number of ripple logic)
2. Number of logic LUT4s does not include count of distributed RAM and ripple logic.
Number of clocks: 1
Net clk_c: 55 loads, 55 rising, 0 falling (Driver: PIO clk )
Number of Clock Enables: 10
Net debounced_key_inst_1/clk_c_enable_3: 1 loads, 1 LSLICEs
Net debounced_key_inst_1/clk_c_enable_40: 11 loads, 11 LSLICEs
Net debounced_key_inst_1/clk_c_enable_61: 1 loads, 1 LSLICEs
Net debounced_key_pulse_inst/clk_c_enable_94: 11 loads, 11 LSLICEs
Net debounced_key_pulse_inst/clk_c_enable_62: 1 loads, 1 LSLICEs
Net counter_with_key_inst/clk_c_enable_59: 4 loads, 4 LSLICEs
Net counter_with_key_inst/clk_c_enable_75: 12 loads, 12 LSLICEs
Net debounced_key_inst_0/clk_c_enable_26: 2 loads, 2 LSLICEs
Net debounced_key_inst_0/clk_c_enable_76: 9 loads, 9 LSLICEs
Net debounced_key_inst_0/clk_c_enable_60: 1 loads, 1 LSLICEs
Number of LSRs: 4
Net debounced_key_inst_1/n1463: 12 loads, 12 LSLICEs
Net debounced_key_pulse_inst/n1461: 11 loads, 11 LSLICEs
Net counter_with_key_inst/n948: 4 loads, 4 LSLICEs
Net debounced_key_inst_0/n1460: 11 loads, 11 LSLICEs
Number of nets driven by tri-state buffers: 0
Top 10 highest fanout non-clock nets:
Net counter_with_key_inst/n47: 25 loads
Net debounced_key_inst_1/clk_c_enable_40: 13 loads
Net counter_with_key_inst/clk_c_enable_75: 12 loads
Net debounced_key_inst_1/n1463: 12 loads
Net debounced_key_pulse_inst/clk_c_enable_94: 12 loads
Net debounced_key_inst_0/clk_c_enable_76: 11 loads
Net debounced_key_inst_0/n1460: 11 loads
Net debounced_key_pulse_inst/n1461: 11 loads
Net count_0: 9 loads
Net count_6: 9 loads