注:
视频已上传至哔哩哔哩平台,
账号:Esther陈小飞,
视频标题:2024年寒假练-基于Lattice MXO2的小脚丫FPGA核心板设计具有启动、停止、递增和清除功能的秒表
一、 项目需求
1. 任务名称
2024年寒假练-——基于Lattice MXO2的小脚丫FPGA核心板(Type C接口)设计具有启动、停止、递增和清除功能的秒表。
2. 任务宗旨
A. 结合数字电路书本知识,深刻理解数字逻辑的功能实现及设计流程;
B. 培养工程化设计理念、规范化的设计流程及解决未知问题的能力;
C. 探索使用行业新工具在项目研发中存在的问题和解决方法。
3. 任务内容
通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。
使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。秒表应从0.0秒计数到9.9秒,然后翻转,计数值每0.1秒精确更新一次。
秒表使用四个按钮输入:开始、停止、增量和清除(重置)。
A. 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次);
B. 停止输入使计数器停止递增,但使数码管显示当前计数器值;
C. 每次按下按钮时,增量输入都会导致显示值增加一次(0.1),无论按住增量按钮多长时间;
D. 复位/清除输入强制计数器值为零。
4. 实现要求
A. 在WebIDE环境下进行Verilog代码编程、综合、仿真、生成JED代码并下载到FPGA中进行验证。
B. 每一个功能模块都通过GPT等大模型(工具不限制)来生成,进行验证、修改后整合在一起实现所需的功能。
5. 基础知识
A. 数字电路基础理论
B. 数字逻辑设计思想
二、 需求分析
该任务有四个经按键输入的信号,分别为“复位”、“增量”,“开始”,“结束”。
设计的主要思路为通过四个输入量控制两个计数器“cnt_ge”和“cnt_shi”进行0.0~9.9之间的循环计数,并将两个计数器的计数值映射为两个七段数码管的显示输出。
三、 实现的方式
程序采用以状态机为主体的设计,状态机的两种状态:
State = 1'd0 :空闲状态;
State = 1'd1 : 计数状态。
初始状态为空闲状态,计数器不进行计数;
任意时刻按下复位键可将状态机复位至空闲状态,且计数器清零;
初始状态下,按下开始键,可将状态机调整至计数状态,计数器从当前计数值开始计数,且计数到9.9时自动恢复为0.0持续计数;
计数状态下,按下停止键,可将状态机调整至空闲状态,计数器保持当前计数值;
状态机空闲状态下,可通过增量键以计数值+0.1s/次的步进对计数器进行计数值累加;
对“增量”、“开始”、“结束”输入进行下降沿抓取处理,保证按键效果与按键时长无关。
使用GPT工具协助工程的设计:
注:GPT给出的建议以及示例代码还需仿真验证及修改调试,最终表现为本实验报告中最终展示的版本。
四、 功能框图
五、 代码及说明
注:此处说明使用中文,附件中代码注释按要求使用英文。
1. 顶层模块
module time_counter
(
clk ,
rst ,
increment ,
start ,
stop ,
seg_led_1 ,
seg_led_2
);
input clk; //输入时钟
input rst; //输入复位
input increment; //输入增量
input start; //输入开始
input stop; //输入结束
output [8:0] seg_led_1; //输出数码管十位
output [8:0] seg_led_2; //输出数码管个位
wire clk10hz; //经分频模块输出10Hz时钟,对应计数器分辨率0.1s
reg [8:0] seg [9:0];
reg [3:0] cnt_ge; //个位
reg [3:0] cnt_shi; //十位
reg [1:0] shiftstart; //上升沿检测
reg [1:0] shiftstop; //上升沿检测
reg [1:0] shiftincrement; //上升沿检测
reg state; //状态机
// 例化分频器模块,产生一个10Hz时钟信号
IntegerDivider #(.WIDTH(32),.N(1200000))
IntegerDivider_inst
(
.clk(clk),
.rst_n(rst),
.clkout(clk10hz)
);
// 检测信号上升沿
always @(posedge clk10hz or negedge rst) begin
if(!rst == 1) begin
shiftstart <= 2'b11;
shiftstop <= 2'b11;
shiftincrement <= 2'b11;
end
else begin
shiftstart <= {shiftstart[0], start};
shiftstop <= {shiftstop[0], stop};
shiftincrement <= {shiftincrement[0], increment};
end
end
// 状态机
always @ (posedge clk10hz or negedge rst) begin
if(!rst == 1) begin
state <= 0;
cnt_ge <= 4'd0;
cnt_shi <= 4'd0;
end
else begin
case(state)
//IDLE 空闲状态
1'd0 : begin
if(shiftincrement == 2'b10) begin
if(cnt_shi==9 && cnt_ge==9) begin
cnt_shi <= 0;
cnt_ge <= 0;
end
else if(cnt_ge==9) begin
cnt_shi <= cnt_shi + 1;
cnt_ge <= 0;
end
else begin
cnt_ge <= cnt_ge + 1;
end
end
else if(shiftstart == 2'b10) begin
state <= 1'd1;
end
else begin
cnt_shi <= cnt_shi;
cnt_ge <= cnt_ge;
end
end
//COUNTER 计数状态
1'd1 : begin
if(shiftstop == 2'b10) begin
state <= 1'd0;
end
else begin
if(cnt_shi==9 && cnt_ge==9) begin
cnt_shi <= 0;
cnt_ge <= 0;
end
else if(cnt_ge==9) begin
cnt_shi <= cnt_shi + 1;
cnt_ge <= 0;
end
else begin
cnt_ge <= cnt_ge + 1;
end
end
end
default : state <= 0;
endcase
end
end
//数码管输出映射
initial
begin
seg[0] = 9'h3f; // 0
seg[1] = 9'h06; // 1
seg[2] = 9'h5b; // 2
seg[3] = 9'h4f; // 3
seg[4] = 9'h66; // 4
seg[5] = 9'h6d; // 5
seg[6] = 9'h7d; // 6
seg[7] = 9'h07; // 7
seg[8] = 9'h7f; // 8
seg[9] = 9'h6f; // 9
end
//输出数码管十位,使能小数点DP
assign seg_led_1 = seg[cnt_shi] + 9'b10000000;
//输出数码管个位
assign seg_led_2 = seg[cnt_ge];
endmodule
2. 时钟分频模块
module IntegerDivider ( clk,rst_n,clkout);
input clk,rst_n;
//输入信号,其中clk连接到FPGA的C1脚,频率为12MHz
output clkout;
//输出信号,可以连接到LED观察分频的时钟
//parameter是verilog里常数语句
parameter WIDTH = 3;
//计数器的位数,计数的最大值为 2**WIDTH-1
parameter N = 5;
//分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出
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_n )
//posedge和negedge是verilog表示信号上升沿和下降沿
//当clk上升沿来临或者rst_n变低的时候执行一次always里的语句
begin
if(!rst_n)
cnt_p<=0;
else if (cnt_p==(N-1))
cnt_p<=0;
else cnt_p<=cnt_p+1;
//计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器
end
//上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_p<=0;
else if (cnt_p<(N>>1))
//N>>1表示右移一位,相当于除以2去掉余数
clk_p<=0;
else
clk_p<=1;
//得到的分频时钟正周期比负周期多一个clk时钟
end
//下降沿触发时计数器的控制
always @ (negedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_n<=0;
else if (cnt_n==(N-1))
cnt_n<=0;
else cnt_n<=cnt_n+1;
end
//下降沿触发的分频时钟输出,和clk_p相差半个时钟
always @ (negedge clk)
begin
if(!rst_n)
clk_n<=0;
else if (cnt_n<(N>>1))
clk_n<=0;
else
clk_n<=1;
//得到的分频时钟正周期比负周期多一个clk时钟
end
assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p;
//条件判断表达式
//当N=1时,直接输出clk
//当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p
//当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与
endmodule
六、 仿真波形图
1. 时钟分频模块仿真文件代码:
module tb_IntegerDivider(
);
reg clk; // 输入时钟信号
reg rst_n; // 复位信号(低有效)
wire clkout; // 输出分频后的时钟信号
initial clk = 1;
always #4 clk = ~clk;
initial begin
rst_n = 1;
#40
rst_n = 0;
#40
rst_n = 1;
end
IntegerDivider #
(
.your_divisor(12)
)
IntegerDivider_inst
(
.clk(clk),
.rst_n(rst_n),
.clkout(clkout)
);
endmodule
2. 时钟分频模块仿真波形
分频系数=3
分频系数=10
3. 顶层模块仿真代码
module tb_time_counter(
);
reg clk; //输入时钟
reg rst; //输入复位
reg increment; //输入增量
reg start; //输入开始
reg stop; //输入结束
wire [3:0] mark_cnt_ge;
wire [3:0] mark_cnt_shi;
wire mark_state;
initial clk = 1;
always #4 clk = ~clk;
initial begin
rst = 1;
#40
rst = 0;
#40
rst = 1;
increment = 1;
start = 1;
stop = 1;
#40
increment = 0;
#40
increment = 1;
#40
increment = 0;
#40
increment = 1;
#40
start = 0;
#1200
stop = 0;
end
time_counter time_counter_inst
(
.clk(clk),
.rst(rst),
.increment(increment),
.start(start),
.stop(stop),
.seg_led_1(),
.seg_led_2(),
.mark_cnt_ge(mark_cnt_ge),
.mark_cnt_shi(mark_cnt_shi),
.mark_state(mark_state)
);
endmodule
注:对于顶层模块仿真时,为方便抓取波形(原分频系数为1200000,仿真波形不容易观察),顶层模块未调用时钟分频模块,而是直接使用仿真输入时钟clk;为方便观察计数原理是否正确,引出计数器mark_cnt_ge和mark_cnt_shi作为输出,取代原七段数码管显示输出。基于以上原因,进行顶层文件仿真时对其进行部分修改:
4. 顶层模块仿真波形
七、 FPGA的资源利用说明
八、 遇到的主要难题及解决方法
GPT作为该实验推荐的工具,在实际使用中,并不能完全符合实验要求,也无法直接通过仿真测试及上板验证,还是需要加深自己本身对于任务的理解,借助GPT提供的思路,梳理出模块架构,不断仿真调试、不断修改优化,直至成功完成试验任务。
九、 未来的计划或建议等
作为硬件FPGA逻辑新手,2024寒假在家练项目确实对我逻辑开发的能力大有裨益,在不断调试的过程中,我的经验值不断增加,期待今后在小脚丫STEP-MXO2-LPC开发板上探索更多可能。
十、 程序附件清单
1. time_counter.v
2. IntegerDivider.v
3. time_counter_for_tb.v
4. tb_time_counter.v
5. tb_IntegerDivider.v
6. implement .jed
十一、 对照成绩构成自评注释
十二、实验成功实物照片