2024年寒假练 - 基于小脚丫FPGA实现秒表
一、任务要求
(一)、任务名称
具有启动、停止、递增和清除功能的秒表
(二)、任务宗旨
- 结合数字电路书本知识,深刻理解数字逻辑的功能实现及设计流程
- 培养工程化设计理念、规范化的设计流程及解决未知问题的能力
- 探索使用行业新工具在项目研发中存在的问题和解决方法
(三)、任务内容
通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。
二、需求分析
(一)、任务目标
- 使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表;
- 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次;
- 秒表使用四个按钮输入:开始、停止、增量和清除(重置);
- 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次);
- 停止输入使计数器停止递增,但使数码管显示当前计数器值;
- 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。
(二)、实现方法
- 在WebIDE环境下进行Verilog代码编程、综合、仿真、生成JED代码并下载到FPGA中进行验证;
- 通过GPT等大模型来生成,进行验证、修改后整合在一起实现所需的功能
(三)、前置知识
FPGA:了解FPGA的基本概念和工作原理,包括可编程逻辑块(PLBs)、可编程连接(Interconnects)等。
Verilog:这是常用于FPGA设计的硬件描述语言(HDL)。需要了解如何使用它来描述数字电路和逻辑功能。
数字电路设计:理解数字电路的基础知识,比如逻辑门、寄存器、计数器等。秒表可以被视为一个计时器,需要理解如何设计这样的逻辑电路。
数码管原理:了解数码管的工作原理,包括共阳极和共阴极数码管的区别,以及如何驱动它们显示不同的数字。
按键输入:学习如何读取和处理来自按键的输入信号,比如检测按键的按下和松开。
状态机设计:秒表通常可以使用状态机来实现。需要了解状态机的概念,并且知道如何在Verilog中实现状态机。
三、功能框图
四、代码分析
(一)、分频器divider
/*-------------------------------------*/
// Module name : divide
// Author : STEP
// Description : 任意整数分频
// Web : www.stepfpga.com
/*-------------------------------------*/
module divide #
(
parameter WIDTH = 24, //计数器的位数,计数的最大值为 2**(WIDTH-1)
parameter N = 12_000_000 //分频系数,确保 N<2**(WIDTH-1)
)
(
input clk,
input rst,
output clkout
);
reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器
reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟
/**********上升沿触发部分**************************************/
//上升沿触发时计数器的控制
always @(posedge clk or negedge rst) begin
if(!rst)
cnt_p <= 1'b0;
else if(cnt_p == (N-1))
cnt_p <= 1'b0;
else
cnt_p <= cnt_p + 1'b1;
end
//上升沿触发的分频时钟输出
always @(posedge clk or negedge rst)
begin
if(!rst)
clk_p <= 1'b0;
else if(cnt_p < (N>>1))
clk_p <= 1'b0;
else
clk_p <= 1'b1;
end
/*****************下降沿触发部分**************************************/
//下降沿触发时计数器的控制
always @(negedge clk or negedge rst)
begin
if(!rst)
cnt_n <= 1'b0;
else if(cnt_n == (N-1))
cnt_n <= 1'b0;
else
cnt_n <= cnt_n + 1'b1;
end
//下降沿触发的分频时钟输出,和clk_p相差半个clk时钟
always @(negedge clk or negedge rst)
begin
if(!rst)
clk_n <= 1'b0;
else if(cnt_n < (N>>1))
clk_n <= 1'b0;
else
clk_n <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟
end
/*************************************************************************/
wire clk1 = clk; //当N=1时,直接输出clk
wire clk2 = clk_p; //当N为偶数也就是N的最低位为0,N[0]=0,输出clk_p
wire clk3 = clk_p & clk_n; //当N为奇数也就是N最低位为1,N[0]=1,输出clk_p&clk_n。
assign clkout = (N==1)? clk1:(N[0]? clk3:clk2);
endmodule
在WEBIDE中,有现成的模块可以调用。该模块的功能为将FPGA自身的12MHz频率转化为其他频率,方便我们下一步使用。在本例中,需要转化为10Hz,故parameter N应为1200000,在逻辑综合模块实例化时覆写此参数。
(二)、显示控制模块display
module display(input1, input2, output1, output2);
input [3:0] input1;
input [3:0] input2;
output [8:0] output1;
output [8:0] output2;
//数组,用于转换输入与输出
reg [8:0] array1 [9:0];
reg [8:0] array2 [9:0];
initial
begin
//左边LED显示,且小数点常亮
array1[0] = 9'hbf;
array1[1] = 9'h86;
array1[2] = 9'hdb;
array1[3] = 9'hcf;
array1[4] = 9'he6;
array1[5] = 9'hed;
array1[6] = 9'hfd;
array1[7] = 9'h87;
array1[8] = 9'hff;
array1[9] = 9'hef;
//右边LED显示,且小数点长灭
array2[0] = 9'h3f; // 0
array2[1] = 9'h06; // 1
array2[2] = 9'h5b; // 2
array2[3] = 9'h4f; // 3
array2[4] = 9'h66; // 4
array2[5] = 9'h6d; // 5
array2[6] = 9'h7d; // 6
array2[7] = 9'h07; // 7
array2[8] = 9'h7f; // 8
array2[9] = 9'h6f; // 9
end
//查表
assign output1 = array1[input1];
assign output2 = array2[input2];
endmodule
该模块的设计思路为:设计两个逻辑表(需要两个是因为实际需求,个位LED小数点需要常亮,而十分位LED小数点需要常灭,造成了两个LED逻辑表不同),传入需要显示的数字(十进制),输出控制信号(二进制)。
(三)、逻辑模块stopwatch
module stopwatch(clk, start, stop, reset, increase, output_led1, output_led2);
input clk, start, stop, reset, increase;
output [8:0] output_led1;
output [8:0] output_led2;
reg [3:0] data1;
reg [3:0] data2;
reg run_status;
reg increase_flag;
initial
begin
data1 = 4'd0;
data2 = 4'd0;
run_status = 0;
increase_flag = 0;
end
//显示模块实例化
display display_instance(
.input1(data1),
.input2(data2),
.output1(output_led1),
.output2(output_led2)
);
//分频模块实例化,分出10Hz的频
wire clk_100ms;
divide #(
.WIDTH(24),
.N(1200000))u0(
.clk(clk),
.rst(reset),
.clkout(clk_100ms));
always @(posedge clk_100ms or negedge reset)
begin
if(reset == 0)
begin
run_status <= 1'd0;
data1 <= 4'd0;
data2 <= 4'd0;
increase_flag <= 0;
end
else
begin
if(stop == 0 && run_status == 1)
begin
run_status <= 1'd0;
end
if(start == 0 && run_status == 0)
begin
run_status <= 1'd1;
end
//增量逻辑
if(!increase && run_status == 0)
begin
if(increase_flag != 1)
begin
increase_flag <= 1;
//十分位0-8时,仅十分位递增1
if(data2 != 4'd9)
begin
data2 <= data2 + 4'd1;
end
//十分位为9时
else
begin
if(data1 == 4'd9)
begin
data2 <= 4'd0;
data1 <= 4'd0;
end
else
begin
data2 <= 4'd0;
data1 <= data1 + 4'd1;
end
end
end
end
//当increase没有被持续按下时,刷新flag
else
begin
increase_flag <= 0;
end
//标准走时逻辑
if(run_status == 1)
begin
//十分位为9时的特殊情况
if(data2 == 4'd9)
begin
//到达9.9后,循环秒表
if(data1 == 4'd9)
begin
data2 <= 4'd0;
data1 <= 4'd0;
end
//十分位为9且个位不为9时,十分位变0,个位+1
else
begin
data2 <= 4'd0;
data1 <= data1 + 4'd1;
end
end
//其他情况,仅十分位递增1
else
begin
data2 <= data2 + 4'd1;
end
end
end
end
endmodule
该模块接受来自用户的按键输入,并将分频器和显示模块实例化,根据项目的要求实现逻辑。
五、FPGA的资源利用说明
Design Summary:
Number of registers: 35 out of 4635 (1%)
PFU registers: 35 out of 4320 (1%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 37 out of 2160 (2%)
SLICEs as Logic/ROM: 37 out of 2160 (2%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 13 out of 2160 (1%)
Number of LUT4s: 74 out of 4320 (2%)
Number used as logic LUTs: 48
Number used as distributed RAM: 0
Number used as ripple logic: 26
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%)
六、遇到的问题及解决方案
(一)、阻塞赋值与非阻塞赋值
在项目中,需要认真考虑使用阻塞与非阻塞赋值中的哪一种,否则有可能导致程序逻辑错误
解决方案:
阻塞赋值使用单个等号(=)来进行赋值操作。当执行阻塞赋值时,Verilog 将按照顺序执行,每次只执行一个阻塞赋值,然后继续执行下一个语句。这意味着在阻塞赋值的语句执行完毕之前,后续的语句将会被阻塞。
非阻塞赋值使用双等号(<=)来进行赋值操作。它们允许在一个时钟周期内同时进行多个赋值,而不受执行顺序的影响。非阻塞赋值在时序逻辑中经常用于描述寄存器的行为,因为它们可以模拟时钟的行为,所有赋值操作在一个时钟周期内同时生效。
(二)、数据长度不匹配造成溢出或不足
在编写程序传递参数或变量时,长度不匹配会造成程序报错或板卡运行异常
解决方案:
参数传递时仔细核对传入与传出的位数是否匹配。
(三)、特殊功能实现
在本次项目中,需要实现一个特殊功能:当长按增量键时,秒表只能增长0.1,不能持续增长。
解决方案:
使用一个变量用来记录上个周期增量键是否被按下,如果按下则这周期增量键再被激活时不进入增量逻辑循环。若不是则刷新该变量。