先说明一下:1、本博文绝对原创,转载请注明。
2、小弟刚开始学习FPGA,博文如有什么不对的地方,恳请大家指出。
3、本文目的旨在与新手分享交流学习中的一些问题。
一、实验平台说明
硬件平台:使用的网上一块比较便宜的开发板,100多点。Altear EP4CE6E22C8芯片。
软件平台:quarters-ii 11.0
使用语言:verlog
二、模块层次说明
系统由顶层、数据转换模块、显示模块、时钟分频模块组成。
时钟模块:开发板使用的是50M晶振,在这里我们需要两种时钟,一个用于一秒钟计时(1HZ),一个用于数码管扫描(500HZ);
显示模块:用于在数码管上显示时钟数据,由于只有4位数码管,所以这里其实设计的并不是一个完整的数字时钟,应该叫计时器,;两个数码管显示秒数据,两个数码管显示分钟数据,知道溢出清零,不过大家可以在这个基础上自行添加设计。
数据转换模块:功能是讲0-9的数字转换成数码管显示的段码。
顶层:实例化时钟和显示模块,同时完成一些逻辑任务。这里的实例化大家可以类似的理解成C语言中的函数的调用,但实际上不是的,因为FPGA是硬件,一定要记住并发这个概念。
三、时钟分频模块
module clock_div(clk,clk_out1,clk_out2);
input clk;
output clk_out1;
reg clk_out1;
reg [27:0] DIV_cnt1 =0;
parameter period1=50000000; //频率为1hz
output clk_out2;
reg clk_out2;
reg [27:0] DIV_cnt2 =0;
parameter period2=100000; //100000频率为500hz
//////////////分频1////////////////
always @ (posedge clk)
begin
DIV_cnt1 <= DIV_cnt1 + 1;
if(DIV_cnt1 == (period1>>1)-1)//高电平
clk_out1 <= #1 1'b1;
else if(DIV_cnt1 == period1-1)//低电平
begin
clk_out1 <= #1 1'b0;
DIV_cnt1 <= #1 1'b0;
end
end
//////////////分频2////////////////
always @ (posedge clk)
begin
DIV_cnt2 <= DIV_cnt2 + 1;
if(DIV_cnt2 == (period2>>1)-1)//高电平
clk_out2 <= #1 1'b1;
else if(DIV_cnt2 == period2-1)//低电平
begin
clk_out2 <= #1 1'b0;
DIV_cnt2 <= #1 1'b0;
end
end
endmodule
四、数据转换模块
//////////////数字转换成数码管编码模块///////////////
module num_to_code(num,code);
input num; //输入0-9的数据
output code; //输出0-9数据的数码管段码
wire[3:0] num;
reg[7:0] code;
always @ (num) //当数字变动一次就进行一次转换
begin
case (num)
4'h0 : code <= 8'hc0; //显示"0"
4'h1 : code <= 8'hf9; //显示"1"
4'h2 : code <= 8'ha4; //显示"2"
4'h3 : code <= 8'hb0; //显示"3"
4'h4 : code <= 8'h99; //显示"4"
4'h5 : code <= 8'h92; //显示"5"
4'h6 : code <= 8'h82; //显示"6"
4'h7 : code <= 8'hf8; //显示"7"
4'h8 : code <= 8'h80; //显示"8"
4'h9 : code <= 8'h90; //显示"9"
default : code <= 8'hff;
endcase
end
endmodule
五、数码管显示模块
//////////////数码管显示模块//////////////
module display(clk,seg0,seg1,seg2,seg3,seg_d,seg_w);
input seg0,seg1,seg2,seg3; //输入的4位段码数据
input clk; //扫描时钟
output seg_d; //输出到管脚的段码数据
output seg_w; //输出到管脚的位选数据
wire[7:0] seg0,seg1,seg2,seg3;
reg[7:0] seg_d;
reg[3:0] seg_w;
reg[3:0] scan_cnt; //位选扫描
always @ (posedge clk) //数码管扫描
begin
scan_cnt = scan_cnt+1;
if (scan_cnt == 3'd4)
scan_cnt <= 0;
end
always @ (scan_cnt) //数码管位选
begin
case (scan_cnt)
3'd0 : seg_w<=4'b0111;
3'd1 : seg_w<=4'b1011;
3'd2 : seg_w<=4'b1101;
3'd3 : seg_w<=4'b1110;
default :seg_w<=4'b1111;
endcase
end
always @ (scan_cnt) //数码管显示数据
begin
case (scan_cnt)
3'd0 : seg_d<=seg3; //
3'd1 : seg_d<=seg2;//
3'd2 : seg_d<=seg1;
3'd3 : seg_d<=seg0;//8'hf9
default :seg_d<=8'hff;
endcase
end
endmodule
六、顶层模块
module seg_clock(clock,led,seg_d,seg_w);
input clock; //系统时钟50MHZ晶振输入
output led; //LED
output seg_d; //数码管的段选
output seg_w; //数码管的位选
reg[3:0] led; //4位led灯
wire[7:0] seg_d; //段选8位
wire[3:0] seg_w; //位选4位
wire clk1; //分频后的时钟1 因为分频器输出为reg类型,故这里用wire
wire clk2; //同上
reg[3:0] tim[0:3]; //4位数时间数据
wire[7:0] tim_d[0:3]; //4位时间数据的段码
initial //初始化时钟数据
begin
tim[0]<=4'd0;
tim[1]<=4'd0;
tim[2]<=4'd0;
tim[3]<=4'd0;
end
////////////实例化分频模块////////////////
clock_div clock_div(
.clk(clock),
.clk_out1(clk1),
.clk_out2(clk2)
);
////////////实例化数据转换模块////////////////
num_to_code num_to_code0(
.num(tim[0]),
.code(tim_d[0])
);
num_to_code num_to_code1(
.num(tim[1]),
.code(tim_d[1])
);
num_to_code num_to_code2(
.num(tim[2]),
.code(tim_d[2])
);
num_to_code num_to_code3(
.num(tim[3]),
.code(tim_d[3])
);
////////////实例化显示模块////////////////
display display(
.clk(clk2),
.seg0(tim_d[0]),
.seg1(tim_d[1]),
.seg2(tim_d[2]),
.seg3(tim_d[3]),
.seg_d(seg_d),
.seg_w(seg_w)
);
always @ (posedge clk1) //clk为1hz 此处更新时间
begin
led<=~led;
tim[0]<=tim[0]+4'd1;
if(tim[0]==4'd9) //秒的个位
begin
tim[0]<=4'd0;
tim[1]<=tim[1]+ 4'd1; //秒的十位
if(tim[1]==4'd5)
begin
tim[1]<=4'd0;
tim[2]<=tim[2]+ 4'd1; //分的个位
if(tim[2]==4'd9)
begin
tim[2]<=4'd0;
tim[3]<=tim[3]+ 4'd1;//分的十位
if(tim[3]==4'd9)
begin
tim[3]<=4'd0;
end
end
end
end
end
endmodule
七、实验效果及系统结构图
下载后的效果图
系统整体机构图,这个是我用另一种方式做的,顶层用的原理图的方式设计的,发现这种方式有许多的优点,我会在下一篇文章张中分享。
八、心得分享与注意事项
最后说说过程中遇到的问题,也许也有人会遇到,这样可以帮助大家更快的解决问题。
1、并行!并行!并行!说三遍,使用硬件描述语言相当于是你在搭建电路一样,这个可以通过RTL图看出。
2、大家要注意“<<=”和“=”的使用,以及reg和wire,比如模块一得输出是reg类型了,那么输入到模块2中的输入就要是wire类型了。
3、verlog貌似暂时还不只是数组类型端口,这点要注意。(数组类型和位宽是两回事,不要搞混了哦)。
4、一定要注意数据宽度,特别是是在模块之间传递的时候。
5、暂时就想到这些,第一次写,有什么不足的地方往大家多多指点,下一篇文章使用原理图的方式来设计顶层。
PS:现从事汽车电子MCU相关工作,已经不懂FPGA了 2020