一、设计题目
基于FPGA的多功能数字钟
二、设计目的
1.掌握可编程逻辑器件的应用开发技术
——设计输入、编译、仿真和器件编程;
2.熟悉一种EDA软件使用;
3.掌握Verilog设计方法;
4.掌握分模块分层次的设计方法;
5.用Verilog完成一个多功能数字钟设计;
6.学会FPGA的仿真。
三、设计内容
设计一个多功能数字时钟,具有时分、秒计数显示、闹钟功能。能够利用按键实现对闹钟时间的设定并在当前显示时间到时后能够进行闹钟提示。能够利用按键实现“较时”、“较分”功能,随时对数码管的显示进行校正和校对。数字中系统主要由系统时钟,三个功能按键(mode,turn,change),FPGA,数码管和蜂鸣器部分组成。
四、FPGA及硬件描述语言简介
1.FPGA简介
FPGA(Field-Programmable Gate Array),即现场可编程门阵列,它是在PAL、GAL、CPLD等可编程器件的基础上进一步发展的产物。它是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的,既解决了定制电路的不足,又克服了原有可编程器件门电路数有限的缺点。
目前以硬件描述语言(Verilog 或 VHDL)所完成的电路设计,可以经过简
单的综合与布局,快速的烧录至 FPGA 上进行测试,是现代 IC 设计验证的技术主流。这些可编辑元件可以被用来实现一些基本的逻辑门电路(比如AND、OR、XOR、NOT)或者更复杂一些的组合功能比如解码器或数学方程式。在大多数的FPGA里面,这些可编辑的元件里也包含记忆元件例如触发器(Flip-flop)或者其他更加完整的记忆块。
系统设计师可以根据需要通过可编辑的连接把FPGA内部的逻辑块连接起来,就好像一个电路试验板被放在了一个芯片里。一个出厂后的成品FPGA的逻辑块和连接可以按照设计者而改变,所以FPGA可以完成所需要的逻辑功能。 FPGA一般来说比ASIC(专用集成芯片)的速度要慢,无法完成复杂的设计,而且消耗更多的电能。但是他们也有很多的优点比如可以快速成品,可以被修改来改正程序中的错误和更便宜的造价。厂商也可能会提供便宜的但是编辑能力差的FPGA。因为这些芯片有比较差的可编辑能力,所以这些设计的开发是在普通的FPGA上完成的,然后将设计转移到一个类似于ASIC的芯片上。
2.硬件描述语言简介
硬件描述语言HDL(Hardware Description Language)是一种用形式化方法来描述数字电路和系统的语言。目前,电子系统向集成化、大规模和高速度等方向发展,以硬件描述语言和逻辑综合为基础的自顶向下的电路设计方法在业界得到迅猛发展,HDL 在这种形势下显示出了巨大的优势,展望将来HDL 在硬件设计领域的地位将与C 和C++在软件设计领域的地位一样,在大规模数字系统的设计中,它将逐步取代传统的逻辑状态表和逻辑电路图等硬件描述方法,而成为主要的硬件描述工具。
Verilog HDL是一种硬件描述语言(hardware description language),为了制作数字电路而用来描述ASICs和FPGA的设计之用。Verilog HDL是目前应用最为广泛的硬件描述语言,可以用来进行各种层次的逻辑设计,也可以进行数字系统的逻辑综合,仿真验证和时序分析等,适合算法级,寄存器级,逻辑级,开关级、系统级和版图级等各个层次的设计和描述。
Verilog HDL进行设计最大的优点是其工艺无关性,这使得工程师在功能设计,逻辑验证阶段可以不必过多考虑门级及工艺实现的具体细节,只需根据系统设计的要求施加不同的约束条件,即可设计出实际电路。
Verilog HDL 是工业和学术界的硬件设计者所使用的两种主要的HDL 之一,另一种是VHDL。现在它们都已成为IEEE 标准。两者各有特点,但Verilog HDL 拥有更悠久的历史、更广泛的设计群体,资源也远比VHDL 丰富,且非常容易学习掌握。本设计提出了以 Verilog HDL 语言为手段,设计了多功能数字钟。其代码具有良好的可读性和易理解性,源程序经Altera 公司的QuartusⅡ 和ModelSim软件完成综合、仿真。此程序通过下载到FPGA 芯片后,可应用于
实际的数字钟显示中。
五、总体设计原理
1、关于模式信号mode选择各个功能显示的构思:
考虑到使用mode按键产生0、1信号在正常时间显示、调节时间功能、调节闹钟功能和跑表功能这四个功能之间的转换。所以mode信号的作用主要体现在控制模块(1)和显示模块中,虽然计时模块中也用到mode信号,但是它只是turn信号将秒信号清零的辅助作用,保证只有在m=0(即普通时钟显示)下turn信号清零功能才起作用,在校时功能下只能是分、小时的切换和跑表下的暂停功能。
a、 在控制模块下的作用:
在控制模块下,其实mode和turn信号的作用更像2-4译码器的功能,将change数字上加信号按不同的mode和turn分成四个信号,分别是count1(时间显示下的分信号)、counta(时间显示下的小时信号)、count2(闹铃显示下的分信号)、countb(闹铃显示下的小时信号)。
b、 在显示模块下的作用:
同在控制模块下的作用。只是将turn信号选出的小时和分钟在同一个mode下一起送至数码管显示。
2、关于时间调整和闹铃时间调整中数字上加的原理:
对于这个问题,我们要考虑两种情况,首先是时间调整的情况:因为在时间调整下,数字的上加不仅受到change信号的作用(即人工调时),还受本身在1Hz信号下计时而随时发生的累加。而闹铃时间调整不存在这种情况,因为闹铃下的时间数字发生上加只可能人工调节(change信号作用下)的结果。
a、 时间调整下的上加:
由于在控制模块(2)下又设置了快加的功能,所以有三部分信号对上加起作用,一是快加下的numXclk,表示以原始时钟的速率上加,二是慢加下的change具体到各模块、各位的count1或counta,三是秒信号记到9向分信号的进位。
b、 闹铃时间调整下的上加:
该部分原理同上,只是少了低位记到9向高位的进位。所以只有两部分组成,一是快加下的numXclk,表示以原始时钟的速率上加,二是慢加下的change具体到各模块、各位的count2或countb。
图1 多功能数字钟总体设计模块
六、各模块说明
1. 分频模块
由于FPGA内部提供的时钟信号频率大约为50MHz,在这需要将它转化成1Hz的标准时钟信号供数字钟的计时显示;在此采用了级联分频法。
代码如下:
module fenpin(clk,clk_1Hz,clk_100Hz,clk_1k);
output clk_1Hz,clk_100Hz,clk_1k;
input clk;
reg clk_1Hz=0,clk_3=0,clk_1=0,clk_2=0,clk_1k=0;
reg [6:0] cnt1=0,cnt2=0,cnt3=0,cnt4=0,cnt5=0;
wire clk_100Hz;
always @(posedge clk)
begin
if ( cnt1 < 156/2-1) /////////////////////////156分频,生成1MHz信号
begin
cnt1 <= cnt1 + 1;
end
else
begin
cnt1 <= 0;
clk_1 <= ~clk_1;
end
end
always @(posedge clk_1)
if ( cnt2 < 156/2-1) ////////////////////100分频,生成10000Hz信号
begin
cnt2 <= cnt2 + 1;
end
else
begin
cnt2 <= 0;
clk_2 <= ~clk_2;
end
always @(posedge clk_2)
if ( cnt5 < 10/2-1) //////////////////////10分频,生成1kHz标准信号
begin
cnt5<= cnt5 + 1;
end
else
begin
cnt5<= 0;
clk_1k<= ~clk_1k;
end
always @(posedge clk_2)
if ( cnt3 < 100/2-1) //////////////////////100分频,生成100Hz信号
begin
cnt3 <= cnt3 + 1;
end
else
begin
cnt3 <= 0;
clk_3 <= ~clk_3;
end
assign clk_100Hz=clk_3;
always @(posedge clk_3)
if ( cnt4 < 100/2-1) ////////////////////100分频,生成1Hz标准信号
begin
cnt4<= cnt4 + 1;
end
else
begin
cnt4<= 0;
clk_1Hz<= ~clk_1Hz;
end
endmodule
最终输出的是1Hz,100Hz,1kHz的标准时钟信号clk_1Hz ,clk_100Hz,clk_1k。
2、 计时模块
原理:m是模式按键,当m=0时,进入计时模式,在计时模式下可以进行时间调整。num3,num4产生加速调整时间,当其值为1时,可以快速调整时间,该调整时间的频率由clk提供。counta,count1是手动调节时间。Turn接按键,可以改变当前调节的是小时还是分钟,长按turn键还可以使秒钟信号清零。sec1,min1,hour1输出的是计时的秒,分,时。
代码如下:
module jishi(clk,clk_1Hz,
turn,//// turn: 接按键,在手动校时功能时,选择是调整小时,还是分钟;若长时间按住该键,还可使秒信号清零,用于精确调时
mode,count1,counta,sec1,min1,hour1,num3,num4);
input clk,clk_1Hz,turn,num3,num4;
input mode;
input count1,counta;
output [7:0] sec1,min1;
output [7:0] hour1;
wire clk_1Hz,ct1,cta,turn,num3,num4;
reg [7:0] sec1=0,min1=0;
reg [7:0] hour1=0;
reg [1:0] m;
wire count1,counta;
reg minclk,hclk;
always @(posedge mode) //mode 信号控制系统在三种功能间转换
begin
if(m==4) m<=0;
else m<=m+1;
end
/////秒钟计时模块//////
always @(posedge clk_1Hz)
if((sec1==8'h59)|turn&(!m))///////若长时间按住该键,还可使秒信号清零,用于精确调时。
begin
sec1<=0; //按住“turn”按键一段时间,秒信号可清零,该功能用于手动精确调时
if(!(turn&(!m))) minclk<=1;///产生进位
end
else begin
if(sec1[3:0]==4'b1001)
begin sec1[3:0]<=4'b0000; sec1[7:4]<=sec1[7:4]+1; end
else sec1[3:0]<=sec1[3:0]+1;
minclk<=0;
end
////////分钟计时模?///
assign m_clk=minclk||count1;/////m_clk产生进位或校正改变
assign ct1=(num3&clk)|(!num3&m_clk); //ct1 用于计时、校时中的分钟计数
always @(posedge ct1)
begin
if(min1==8'h59) begin min1<=0; hclk<=1; end
else begin
if(min1[3:0]==9)
begin min1[3:0]<=0; min1[7:4]<=min1[7:4]+1; end
else min1[3:0]<=min1[3:0]+1;
hclk<=0;
end
end
////////小时计时模块///
assign h_clk=hclk||counta;//////h_clk产生进位或校正改变
assign cta=(num4&clk)|(!num4&h_clk); //cta 用于计时、校时中的小时计数
always @(posedge cta)
if(hour1==8'h23) hour1<=0;
else if(hour1[3:0]==9)
begin hour1[7:4]<=hour1[7:4]+1; hour1[3:0]<=0; end
else hour1[3:0]<=hour1[3:0]+1;
endmodule
3、 闹钟模块
原理:num1,num2产生加速调整时间,当其值为1时,可以快速调整时间,该调整时间的频率由clk提供。countb,count2是手动调节闹钟时间。amin,ahour是输出的闹钟的分钟和小时,LD_alert指示当前是否开启闹钟。
代码如下:
module Alarm(clk,amin,ahour,num1,num2,count2,countb,LD_alert);
input clk,num1,num2,count2,countb;
output [7:0] amin;
output [7:0] ahour;
output LD_alert;
wire LD_alert;
reg [7:0] amin=0;
reg [7:0] ahour=0;
assign ct2=(num1&clk)|(!num1&count2); //ct2 用于定时状态下调整分钟信号
assign LD_alert=(ahour|amin)?1:0;//指示是否进行了闹铃定时
always @(posedge ct2)
if(amin==8'h59) amin<=0;
else if(amin[3:0]==9)
begin amin[3:0]<=0; amin[7:4]<=amin[7:4]+1; end
else amin[3:0]<=amin[3:0]+1;
assign ctb=(num2&clk)|(!num2&countb); ////ctb 用于定时状态调节小时信号
always @(posedge ctb)
if(ahour==8'h23) ahour<=0;
else if(ahour[3:0]==9)
begin ahour[3:0]<=0; ahour[7:4]<=ahour[7:4]+1; end
else ahour[3:0]<=ahour[3:0]+1;
endmodule
4、 控制模块(1)
原理:m是模式按键,当m=0时,指当前输出的是计时功能;当m=1时,指当前调整的是闹钟时间;当m=2时,指当前调整的是计时时间;当m=3时,此时turn按键可用于跑表的暂停与开始。change 接按键,手动调整时,每按一次,计数器加1;如果长按,则连续快速加 1,用于快速调时和定时;turn接按键,在手动校时功能时,选择是调整小时,还是分钟;若长时间按住该键,还可使秒信号清零,用于精确调时。count1,count2,counta,countb分别是用来调节计时时间和闹钟时间。LD_min,LD_hour,指示当前调节的是分钟还是小时。
代码如下:
Module ctrol(change,turn,count1,count2,counta,countb,pause,LD_min,LD_hour,mode);
input change,mode,turn;
output count1,count2,counta,countb,pause,LD_min,LD_hour;
reg [1:0] m;
reg fm=0,count1=0,count2=0,counta=0,countb=0,pause=0,LD_min=0,LD_hour=0;
wire mode,turn,change;
always @(posedge mode) //mode 信号控制系统在三种功能间转换
begin
if(m==4) m<=0;
else m<=m+1;
end
always @(posedge turn)//////////接按键,在手动校时功能时,选择是调整小时,还是分钟;
begin
fm<=~fm;
end
always @ (m or fm or change)
begin
case(m)
3: begin ////////3:跑表功能;
if(fm)
pause=1;
else
pause=0;
end
2: begin ////////2:调节时间功能;
if(fm)
begin count1<=change; {LD_min,LD_hour}<=2;
end//////指示当前调整的是分钟
else
begin counta<=change; {LD_min,LD_hour}<=1;
end/////指示当前调整的是小时
{count2,countb}<=0;
end
1: begin //////1:调节闹钟功能
if(fm)
begin count2<=change; {LD_min,LD_hour}<=2;
end/////指示当前调整的是分
else
begin countb<=change; {LD_min,LD_hour}<=1;
end/////指示当前调整的是小时
{count1,counta}<=0;
end
0: begin {count1,count2,counta,countb,LD_min,LD_hour}<=0;
end ////0:计时功能
endcase
end
endmodule
5、 控制模块(2)
原理:此模块是加速调节时间模块,count1,count2,counta,countb是手动调节时间,当长时间按这些键时,num1,num2,num3,num4的值会发生变化,当他们值有为1时,对应的调节会快速加1。
代码如下:
module faster(clk,num1,num2,num3,num4,count1,count2,counta,countb);
input clk;
input count1,count2,counta,countb;
output num1,num2,num3,num4;
wire count1,count2,counta,countb;
reg[2:0] loop1=0,loop2=0,loop3=0,loop4=0;
reg num1,num2,num3,num4;
always @(negedge clk)//如果长时间按下“change”键,则生成“num*”信号用于连续快速加1
if(count2) begin
if(loop1==3) begin loop1<=0; num1<=1; end
else
begin loop1<=loop1+1; num1<=0; end
end
else begin loop1<=0; num1<=0; end
always @(negedge clk)
if(countb) begin
if(loop2==3) begin loop2<=0; num2<=1; end
else
begin loop2<=loop2+1; num2<=0; end
end
else begin loop2<=0; num2<=0; end
always @(negedge clk)
if(count1) begin
if(loop3==3) begin loop3<=0; num3<=1; end
else
begin loop3<=loop3+1; num3<=0; end
end
else begin loop3<=0; num3<=0; end
always @(negedge clk)
if(counta) begin
if(loop4==3) begin loop4<=0; num4<=1; end
else
begin loop4<=loop4+1; num4<=0; end
end
else begin loop4<=0; num4<=0; end
endmodule
6、 显示模块
原理:同时输入计时模块的时间和闹钟模块的时间,选择m值,当m=0时,指当前输出的是计时模块的时间;当m=1时,指当前输出的是闹钟模块的时间;当m=2时,指当前输出的是调节计时模块的时间;当m=3时,指当前输出的是跑表计时的时间。
代码如下:
Module show(min1,sec1,amin,hour1,ahour,MSH,MSL,SH,SL,MH,ML,min,sec,hour,mode);
input [7:0] min1,sec1,amin;
input [7:0] hour1,ahour;
input[3:0] MSH,MSL,SH,SL,MH,ML;
input mode;
output [7:0] min,sec;
output [7:0] hour;
reg [7:0] min,sec;
reg [7:0] hour;
reg [1:0] m;
always @(posedge mode) //mode 信号控制系统在三种功能间转换
begin
if(m==4) m<=0;
else m<=m+1;
end
always @(min1 or sec1 or amin or hour1 or ahour or m)
begin
case(m)
0: begin hour<=hour1; min<=min1; sec<=sec1; end
1: begin hour<=ahour; min<=amin; sec<=8'hzz; end
2: begin hour<=hour1; min<=min1; sec<=8'hzz; end
3: begin hour<={SH,SL}; min<={MSH,MSL}; sec<={MH,ML}; end
endcase
end
endmodule
7、 响铃模块
原理:同时输入计时模块的时间和闹钟模块的时间。当计时模块的时间快到达整点时,会产生响声;当计时模块的时分同时等于闹钟模块的时分时,也产生响铃。这时alert为1,如果在这时按住change键,可以屏蔽闹钟响铃。
代码如下:
module ring(clk,clk_1k,min1,sec1,amin,hour1,ahour,change,alert);
input [7:0] min1,sec1,amin;
input [7:0] hour1,ahour;
input change,clk,clk_1k;
output alert;
wire [7:0] min1,sec1,amin;
wire [7:0] hour1,ahour;
wire change;
reg alert1=0,alert2=0;
reg [1:0] sound;
reg ear;
wire alert;
wire clk_1k,clk;
always @ (posedge clk)
if((min1==amin)&&(hour1==ahour)&&(amin|ahour)&&(!change))
if(sec1<30) alert1<=1;
else alert1<=0;
else alert1<=0;
always @ (posedge clk)
begin
if(sound==3) begin sound<=0; ear<=1; end
//ear 信号用于产生或屏蔽声音
else begin sound<=sound+1; ear<=0; end
if((min1==8'h59)&&(sec1>8'h54)||(!(min1|sec1)))
if(sec1>8'h54) alert2<=ear&clk_1k; //产生短音
else alert2<=!ear&clk_1k; //产生长音
else alert2 <= 0; //停止发声
end
///assign alert=((alert1)?clk_100Hz&clk:0)|alert2;//产生闹铃音或整点报时音
assign alert=((alert1)?clk_1k&clk:0)|alert2; //产生闹铃音或整点报时音
endmodule
8、 跑表模块
原理:输入该模块的是100Hz时钟信号。百分秒是模为100的BCD码计数器,为方便数码管显示,将百分秒的两位分别用4位的MSH(百分秒高位),MSL(百分秒低位)表示。这样MSH,MSL就分别对应每一个数码管显示的BCD码。同理,秒信号为模为60的BCD码计数器,两位也用四位的SH(秒高位),SL(秒低位)。此时turn键可用作跑表的pause键,按下turn键后跑表暂停,再按下后秒表启动。此外clr按键可实现跑表的异步清零。
/*信号定义:clk_100Hz: clk_100Hz 为时钟信号;clr: 为异步复位信号;pause: 为暂停信号;MSH,MSL: 百分秒的高位和低位;SH,SL: 秒信号的高位和低位;MH,ML: 分钟信号的高位和低位。 */
module paobiao(clk_100Hz,clr,pause,MSH,MSL,SH,SL,MH,ML);
input clk_100Hz,clr;
input pause;
output[3:0] MSH,MSL,SH,SL,MH,ML;
reg[3:0] MSH,MSL,SH,SL,MH,ML;
reg cn1,cn2; //cn1 为百分秒向秒的进位,cn2 为秒向分的进位
//百分秒计数进程,每计满100,cn1 产生一个进位
always @(posedge clk_100Hz or posedge clr)
begin
if(clr) begin //异步复位
{MSH,MSL}<=8'h00;
cn1<=0;
end
else if(!pause) //PAUSE 为0 时正常计数,为1 时暂停计数
begin
if(MSL==9) begin
MSL<=0;
if(MSH==9)
begin MSH<=0; cn1<=1; end
else MSH<=MSH+1;
end
else begin
MSL<=MSL+1; cn1<=0;
end
end
end //秒计数进程,每计满60,cn2 产生一个进位
always @(posedge cn1 or posedge clr)
begin
if(clr) begin //异步复位
{SH,SL}<=8'h00;
cn2<=0;
end
else if(SL==9) //低位是否为9
begin
SL<=0;
if(SH==5)
begin SH<=0; cn2<=1; end
else SH<=SH+1;
end
else
begin SL<=SL+1; cn2<=0; end
end //分钟计数进程,每计满60,系统自动清零
always @(posedge cn2 or posedge clr)
begin
if(clr)
begin {MH,ML}<=8'h00; end //异步复位
else if(ML==9) begin
ML<=0;
if(MH==5) MH<=0;
else MH<=MH+1;
end
else ML<=ML+1;
end
endmodule
七、各工作模式仿真波形
1.时钟正常计时波形 00:00:59时刻波形
00:59:59时刻波形
23:59:59时刻波形
2.定时闹钟波形
如图所示为01:02:00-01:02:59的一分钟闹钟波形。
3.仿电台报时波形
如图所示,在任意小时59分的51、53、55、57秒内蜂鸣器为低频(512Hz)信号输出,在59秒内蜂鸣器为高频(1024Hz)信号输出,实现模仿电台报时功能。
4.整点报时波形
如图所示,在06:00:00后的6秒内,蜂鸣器前半秒无输入,后半秒有高频(1024Hz)输入,达到整点报时功能。在N小时整时刻,蜂鸣器会鸣响N次。如此仿真中为六点,响六下。
分计数器仿真波形图:
小时计数器仿真波形图:
八、在FPGA板上调试过程如下:
1.当前m=0,输出的是计时模块的时间;LD_alert=0,表示没有设置闹钟。
2.当m=1时,输出的是闹钟时间。改变change的值,可以调节闹钟的时间。可以看出设置得闹钟为8:25。LD-alert=1提示设置了闹钟。
3.当m=2or3时,输出的是调整计时模块的时间。改变change的值,可以调节时间。可以看出,时间调整为8:21。
4.从下可以看出,LD_alert=1,表明设置有闹钟。alert=0,表示闹钟时间还没有到。
时间为8:25:00,闹钟报警,alert=1,报警时间长为30秒,如果按住change键,则可以屏蔽闹铃。
时间为8:25:31,闹铃停止。
测试成功!
九、总结
通过这次项目设计,我初步掌握了EDA设计的基本流程(即设计输入—编译—调试—仿真—下载),领会了自顶而下结构化设计的优点,并具备了初步的EDA程序设计能力。根据设计内容,对FPGA也进行了学习,对软件总体操作步骤初步了解了,但还是要提高对FPGA的掌握能力,将FPGA运用到其他方面的应用上。另外,设计中最重要的就verilog语言的应用,对Verilog语言的学习不仅是能读懂别人的程序,更重要的是能写出条理清晰的程序。通过做多功能数字钟,我发现自己对Verilog语言还不是很熟悉,编写程序也不够熟练,在接下来的时间里我还要要加强verilog语言的学习,多看一些语言方面的书籍。
在做设计的过程中,我感觉,这个项目最难的地方在于将各个子模块按照原理有机地结合起来,这需要扎实的理底。相比而言,子模块的设计难度不是太大,因为Verilog语言和C语言有很多相似之处,只要明白了实验原理,就不难完成,水平的高下只体现在程序的简洁与否。但是Verilog源程序的编写很容易出现错误,有时候在没注意的情况下就会出现,这就需要耐心的调试。因为很多情况下,一长串的错误往往是由一个不经意的小错误引起的。当程序屡调屡错的时候,可以去寻求同学的帮助,因为自己编写的程序往往自己看不出问题所在,说不定他们不经意的一句话,就可能给我启发,使问题迎刃而解。
这次项目设计,给我感触最深的还是行为态度问题。人的能力有大有小,但只要端正态度,不抛弃,不放弃,任何人都能取得令自己满意的成绩。