2024 年寒假练 - 基于小脚丫 FPGA 核心板实现具有启动、停止、递增和清除功能的秒表
该项目使用了小脚丫 FPGA 核心板,实现了具有启动、停止、递增和清除功能的秒表的设计,它的主要功能为:0~10s的计数器,可复位、暂停、启动、增量计数器。。 该项目使用了WebIDE与GPT,实现了verilog语言秒表程序的设计,它的主要功能为:实现十位数查找、计数器、分频器、七段数码管显示模块。。
标签
FPGA
小脚丫fpga
秒表
时序逻辑
2024 年寒假练
组合逻辑
GPT辅助创作
sll
更新2024-04-02
59

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

项目需求

作业题目

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

作业需求

  • 使用七段显示器作为输出设备,在小脚丫 FPGA 核心板上创建一个 2 位数秒表。 秒表应从 0.0 秒计数到 9.9 秒,然后翻转,计数值每 0.1 秒精确更新一次。
  • 秒表使用四个按钮输入:开始、停止、增量和清除(重置)。
    • 开始输入使秒表开始以 10Hz 时钟速率递增(即每 0.1 秒计数一次);
    • 停止输入使计数器停止递增,但使数码管显示当前计数器值;
    • 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间;
    • 复位 / 清除输入强制计数器值为零。

需求分析

  • 数码管显示: 使用小脚丫 FPGA 核心板上的七段数码管进行秒表数值的显示。
    • 两个七段数码管原理图:
    • image.png
    • 每个七段数码管有9个引脚连接在芯片引脚
    • abcdefg七个数码led显示
    • dp小数点
    • dig背光
    • dp控制引脚高电平有效,其他8个灯光控制引脚均为低电平有效。
  • 按键输入: 利用核心板上的按钮作为输入,用于控制秒表的开始、停止、增量和清除功能。
    • 四个用户按键原理图
    • image.png
    • 四个用户按键均为低电平有效。
  • 软件需求
    • 组合逻辑电路,使用译码器将十/二进制数转换为七段数码管显示。
    • 时序逻辑电路
      • 分频器,时序逻辑电路的基本单元
      • 计数器,用于实现秒表自增功能
    • 锁存器,保存按键下降沿(不知道这样是否合理)。


实现的方式

开发环境介绍

  • 此次设计使用小脚丫社区提供的WebIDE,摆脱了Diamond专业软件的学习成本,快速入门verilog语言,验证数字电路设计。
  • GPT语言模型辅助设计:
    • 虽然题目很简单,但是Verilog语言与C语言实现思想有一定的差距,所以选择大模型辅助
    • AI辅助模块设计,比让它完成整个工程要做的漂亮。
    • 大大缩短设计时间,提高开发效率

设计思路

  • 根据前文提到的软件需求,我们可以向GPT描述我们的题目要求,它会给我们一个简单的计数器框架。
  • image.png
module Stopwatch(
input wire clk,
input wire reset,
input wire start_stop,
output reg [6:0] seg1,
output reg [6:0] seg2
);

reg [23:0] count = 0; // 24位计数器,以12MHz时钟频率计数

always @(posedge clk) begin
if (reset) begin
count <= 0;
end else if (start_stop) begin
count <= count + 1;
end
end

always @(*) begin
// 计算秒数
reg [3:0] seconds_tens = count[23:20];
reg [3:0] seconds_ones = count[19:16];
reg [3:0] tenths = count[15:12];

// 数码管显示逻辑
case ({seconds_tens, seconds_ones})
4'b0000: begin // 显示0
seg1 = 7'b1000000;
end
// 其他数字类似地添加
default: begin
seg1 = 7'b1111111; // 显示空白
end
endcase

case ({tenths})
4'b0000: begin // 显示0
seg2 = 7'b1000000;
end
// 其他数字类似地添加
default: begin
seg2 = 7'b1111111; // 显示空白
end
endcase
end

endmodule

  • 这段程序很乱,我们希望它是模块化设计,可以告诉AI,使用模块重构这部分代码。
    • image.png
module Divider( //分频器模块
input wire clk,
output reg divided_clk
);
reg [11:0] count = 0;

always @(posedge clk) begin
if (count == 1200 - 1) begin // 12MHz时钟下的1200个周期为10Hz
count <= 0;
divided_clk <= ~divided_clk;
end else begin
count <= count + 1;
end
end

endmodule

module Decoder( //译码器模块
input wire [6:0] input, // 输入信号,用于表示要显示的数字
output reg [6:0] output // 输出信号,用于控制数码管显示
);
always @(*) begin
case (input)
7'b0000000: output = 7'b1000000; // 数字0,对应的段选信号
// 其他数字类似地添加
default: output = 7'b1111111; // 显示空白
endcase
end

endmodule

module Stopwatch(
input wire clk, // 输入时钟信号
input wire reset, // 复位信号
input wire start_stop, // 开始/停止计数信号
output reg [6:0] seg1, // 数码管1的段选信号
output reg [6:0] seg2 // 数码管2的段选信号
);
wire divided_clk;
Divider div_inst(.clk(clk), .divided_clk(divided_clk)); // 实例化分频器模块
reg [6:0] count = 0; // 7位计数器,从0计数到99

always @(posedge divided_clk) begin
if (reset) begin
count <= 0; // 复位计数器
end else if (start_stop && count != 100) begin
count <= count + 1; // 开始/停止计数,同时限制计数范围在0到99之间
end
end

reg [3:0] tens = count[6:3]; // 获取十位数值
reg [3:0] ones = count[2:0]; // 获取个位数值

Decoder dec1(.input(tens), .output(seg1)); // 实例化译码器模块,将十位数值传入并获取段选信号
Decoder dec2(.input(ones), .output(seg2)); // 实例化译码器模块,将个位数值传入并获取段选信号

endmodule
  • 我并没有验证这段代码,但它和我想象的不太一样。
  • 让GPT将计数器部分也独立为模块
module Counter(
input wire clk, // 输入时钟信号
output reg [6:0] counter // 输出计数值
);
reg [23:0] count = 0; // 24位计数器

always @(posedge clk) begin
if (count == 10000000 - 1) begin // 10Hz时钟下的10000000个周期为1秒
count <= 0;
counter <= counter + 1; // 计数值加一
end else begin
count <= count + 1;
end
end

endmodule
  • 现在我们得到了三个模块Counter、Divider、Decoder。基本实现了简单计数器的框架。
  • 回顾代码,发现译码器他没有给出详细的代码,我们可以把译码器的详细要去告诉GPT,并告知它是共阳极数码管
    • (经过一番困难交流)重新生成的译码器模块
module SevenSegmentDecoder(
input wire [3:0] digit,
output reg [6:0] segment_output
);
always @(*) begin
case (digit)
4'b0000: segment_output = 7'b1000000; // 数字0
4'b0001: segment_output = 7'b1111001; // 数字1
4'b0010: segment_output = 7'b0100100; // 数字2
4'b0011: segment_output = 7'b0110000; // 数字3
4'b0100: segment_output = 7'b0011001; // 数字4
4'b0101: segment_output = 7'b0010010; // 数字5
4'b0110: segment_output = 7'b0000010; // 数字6
4'b0111: segment_output = 7'b1111000; // 数字7
4'b1000: segment_output = 7'b0000000; // 数字8
4'b1001: segment_output = 7'b0010000; // 数字9
default: segment_output = 7'b1111111; // 默认显示空白
endcase
end

endmodule
  • 将所得到的模块集成在顶层文件中,经过一番与GPT的交流(困难交流)我们得到下面的代码
module Stopwatch(
input wire clk,
input wire reset,
input wire start_stop,
output reg [6:0] segment_output_tens, segment_output_ones
);

reg divided_clk;
reg [3:0] counter;
reg [3:0] digit_tens, digit_ones; // 将 digit_tens 和 digit_ones 修改为内部信号

Divider div_inst(.clk(clk), .divided_clk(divided_clk));
Counter count_inst(.clk(divided_clk), .counter(counter)); // 实例化计数器模块
SevenSegmentDecoder decoder_tens(.digit(digit_tens), .segment_output(segment_output_tens)); // 十位数的七段数码管译码器模块实例化
SevenSegmentDecoder decoder_ones(.digit(digit_ones), .segment_output(segment_output_ones)); // 个位数的七段数码管译码器模块实例化

always @(*) begin
if (reset) begin
digit_tens <= 4'b0000; // 十位数初始化为0
digit_ones <= 4'b0000; // 个位数初始化为0
end else begin
counter <= counter + 1; // 计数器递增
end
end

endmodule
  • 现在已经基本实现了部分的模块化,和简单的秒表功能
  • 添加停止、开启、复位、增量按键,我们只需要对counter模块进行操作,当然这完全交给gpt完成。


功能框图


代码及说明

分频器

  • 输入为12Mhz时钟信号
  • 输出为10hz时钟信号
module divider_10hz(
input clk, // 时钟输入
input rst, // 异步复位信号
output reg clk_out // 输出10hz时钟信号
);

parameter DIVIDE_BY = 12000000;
reg[31:0] counter = 0;

always @(posedge clk or negedge rst) begin
if (!rst) begin
counter <= 0;
clk_out <= 0;
end else if (counter == DIVIDE_BY/20 - 1) begin
counter <= 0;
clk_out <= ~clk_out;
end else begin
counter <= counter + 1;
end
end

endmodule

计数器

  • 大部分逻辑均在计数器中实现
  • 输入时钟信号、rst、start、stop、inc按键信号
  • 变量
reg [7:0] count_reg;    // 计数器寄存器
reg start_btn_prev; // 前一个时刻的start_btn状态
reg inc_btn_prev; // 保存增量按钮的前一个状态
reg stop_btn_prev; // 保存增量按钮的前一个状态
reg counting; // 计数控制信号
  • 启动,停止信号通过检测下降沿给变量赋值。
// 检测下降沿来启动持续计数
if (start_btn_prev && !start_btn) begin
counting <= 1; // 激活持续计数
end
// 停止计数的下降沿检测
if (stop_btn_prev && !stop_btn) begin
counting <= 0; // 停止计数
end
  • 增量信号
        // 检测inc_btn按钮的下降沿,无论计数是否已启动
if (inc_btn_prev && !inc_btn) begin
// 计数器增加逻辑
if (count_reg == 99) begin
count_reg <= 0; // 计数器溢出处理
end else begin
count_reg <= count_reg + 1; // 计数增加
end


  • 计数器自增
         // 如果已激活计数,则不断增加计数器的值
end else if (counting) begin
if (count_reg == 99) begin
count_reg <= 0; // 计数器溢出处理
end else begin
count_reg <= count_reg + 1; // 普通计数增加
end
end

译码器

  • 将得到的十位与个位数字转换为七段数码管对应的数字
module bin_to_seven_seg(
input [3:0] bin, // 4位二进制输入
output reg [6:0] seg // 7段管输出,假设为共阴数码管
);

always @(*) begin
case(bin)
4'b0000: seg = 7'b1111110; // 0
4'b0001: seg = 7'b0110000; // 1
4'b0010: seg = 7'b1101101; // 2
4'b0011: seg = 7'b1111001; // 3
4'b0100: seg = 7'b0110011; // 4
4'b0101: seg = 7'b1011011; // 5
4'b0110: seg = 7'b1011111; // 6
4'b0111: seg = 7'b1110000; // 7
4'b1000: seg = 7'b1111111; // 8
4'b1001: seg = 7'b1111011; // 9
default: seg = 7'b0000000; // 默认情况,全部熄灭
endcase
end
endmodule

FPGA 的资源利用说明

  • 寄存器利用率:
    • 注册位的数量: 设计使用了 53 个寄存器位,总计有 4635 个注册位可用,这代表大概 1% 的利用率。
    • image.png
  • 查找表 (LUT) 利用率:
    • 设计使用了 145 个 LUT4,总计有 4320 个可用的 LUT4,约占 3% 的利用率。
    • image.png
  • IO 资源利用率:
    • 设计使用了 22 个 IO 块加 4 个 JTAG 端口,总计有 105 个 IO 块(不含 JTAG 端口),约占 21% 的利用率。
  • Block RAM (BRAM) 资源利用率:
    • 设计并没有使用 BRAM 资源,Lattice MachXO2 LCMXO2-4000HC FPGA 提供的 10 个 BRAM 块均未使用,利用率为 0%。
  • Slice 资源利用率:
    • 设计使用了 74 个 Slice,而总共有 2160 个 Slice 可用,占用约 3% 的利用率。
  • 其他资源:
    • 使用了 1 个全局设定寄存器 (GSR)。
    • 没有使用 PLL、DLL、高速时钟网络等资源。

  • image.png

总结

  • 对verilog语言进行了复习,配和gpt辅助开发中并未出现太多的语法错误。
  • 在使用GPT的辅助设计时,AI并不能完成整个项目的设计,在简单模块的设计中非常友好。
  • WEBIDE非常友好,节省了大量专业软件的学习时间。
  • 设计中给出现对数据的除法运算出错的问题,由于对verilog语法不清楚,所以使用if else 循环来代替实现。


附件下载
archive.zip
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号