一、实验任务
用FPGA器件和EDA技术实现多功能数字钟的设。
基本功能:
1、以数字形式显示时、分、秒的时间;
2、小时计数器为同步24进制;
3、要求手动校时、校分。
扩展功能:
1、任意时刻闹钟;
2、小时显示(12/24)切换电路
3、仿广播电台正点报时;
4、自动报整点时数。
二、实验要求
1、熟悉EDA软件的使用;
2、拟定数字钟的组成框图,划分模块;
3、采用分模块、分层次的方法设计电路;
4、各单元模块电路的设计与仿真;
5、总体电路的设计与仿真;
6、总体电路的下载与调试。
7、设计可以采用原理图或HDL语言
三、实验环境
1.Windows10操作系统
2. ISE软件Vivado2020.2
3.FPGA实验开发装置Nexys4-DDR
四、数字钟设计方案及组成框图
本次设计的多功能数字钟分为主体电路与扩展电路两个部分,其中主体电路分为计数器模块、显示模块、校时模块,扩展电路分为闹钟模块与仿电台正点报时模块。在100MHz的CP信号激励下主电路计数器模块与闹钟定时计数器模块中的寄存器分别输出时分秒对应的BCD码,经由数据选择器输入显示模块,BCD码在该模块中译码为数码管对应编码,通过500Hz的CP信号动态扫描将数码管编码经过总线分时映射到对应的位数码管上,实现译码器与输出端口的复用。闹钟模块将主电路计数器模块底层寄存器中的BCD码闹钟计数器中寄存器进行对比,,相同则产生间隔1秒的1秒500Hz的低音。整点报时与仿电台正点报时模块将主电路计数器模块底层寄存器中的BCD码与报时时刻进行对比,在每个小时的59分51秒、53秒、55秒、57秒产生1秒500Hz的低音,在59秒产生1秒的1000Hz高音提示整点已到,此后再产生与对应小时数相等次数的500Hz低音进行报时。
综上所述,多功能数字钟组成框图如下图所示:
图1 多功能数字钟组成框图
五、分模块源代码设计及注释
多功能数字钟的模块设计如下所示:
图2 多功能数字钟模块设计组成图
MIPS汇编语言源程序分模块设计如下:
(1) 主电路计数器模块
主电路计数器模块主要由分频器、模60计数器、模24计数器构成,其中模60计数器由模10计数器与模6计数器构成。100MHz时钟信号CP经分频器得到1Hz时钟信号,将其分别作为两个模60计数器与一个模24计数器的激励实现秒计数、分计数与时计数。秒计数器使能恒为有效,分计数器使能在秒计数器数值为59时或调分信号有效时有效,时计数器使能在分计数器、秒计数器为59时或调时信号有效时有效。当系统为显示时间模式正常计时,另外可以通过改变调分、调时开关的是否有效实现校时功能。此外通过模式转换按钮trans实现12小时/24小时模式切换,因计数电路只使用了模24计数器,因此通过24时制与12时制的转换关系可以直接得到12时制的输出结果。整体层次关系如下:
图3 主电路计数器整体层次关系
各计数器均基于模10计数器或模6计数器等四位输出计数器实现,各模块verilog源码如下:
module freq_divider(//分频器
input CP100M,
output reg CP1
);
integer i,j;
initial begin
j=0;
CP1=0;
end
always @(posedge CP100M)
begin
j <= j + 1;
if(j==32'd49999999)
begin j <= 1'b0; CP1 <= ~CP1; end
end
endmodule
module counter60(//60进制计数器
input nCR,
input EN,
input CP,
output wire [7:0] Cnt
);
wire ENP;
counter10 UC0 (nCR, EN, CP, Cnt[3:0]); //计数器的个位
counter6 UC1 (nCR, ENP, CP, Cnt[7:4]); //计数器的十位
assign ENP = (Cnt[3:0]==4'h9) & EN; //产生计数器十位的使能信号
endmodule
module counter10(//10进制计数器
input nCR,
input EN,
input CP,
output reg [3:0] Q
);
initial begin
Q=4'b0000;
end
always @(posedge CP or negedge nCR)
begin
if(~nCR) Q <= 4'b0000; // nCR=0,计数器被异步清零
else if(~EN) Q <= Q; //EN=0,暂停计数
else if(Q == 4'b1001) Q <= 4'b0000;
else Q <= Q + 1'b1; //计数器增1计数
end
endmodule
module counter6(//6进制计数器
input nCR,
input EN,
input CP,
output reg [3:0] Q
);
initial begin
Q=4'b0000;
end
always @(posedge CP or negedge nCR)
begin
if(~nCR) Q <= 4'b0000; // nCR=0,计数器被异步清零
else if(~EN) Q <= Q; //EN=0,暂停计数
else if(Q == 4'b0101) Q <= 4'b0000;
else Q <= Q + 1'b1; //计数器增1计数
end
endmodule
在顶层模块中与主体部分计时器有关的代码以及注释如下:
freq_divider(CP,CP1);//分频
counter60 u0(nCR,EN,CP1,second[7:0]);//秒变量60进制计数器
counter60 u1(nCR,min_EN && EN,CP1,minute[7:0]);//分变量60进制计数器
assign min_EN=Adj_Min ? Vdd && ~alarm_clock:(second==8'h59);
//分进位信号,如果调分使能有效且闹钟调时使能无效,则进位使能有效。如果调分使能无效但秒变量为59时,分进位使能有效
counter24 u2(nCR,hour_EN && EN,CP1,hour[7:0]);
//时变量24进制计数器
assign hour_EN=Adj_Hour ? Vdd && ~alarm_clock:((minute == 8'h59) && (second == 8'h59));
//时进位信号,如果调时使能有效且闹钟调时使能无效,则进位使能有效。如果调时使能无效但分变量和秒变量为59时,时进位使能有效
//数字钟主体部分
(2) 闹钟定时计数器模块
整体层次关系与详细设计与主电路计数器模块基本相同。不同的是闹钟定时计数器模块的三级计数器使能信号都需要与闹钟调时信号alarm_clock相与。
在顶层模块中与主体部分计时器有关的代码如下:
counter60 a0(nCR,alarm_clock,CP1,second_alarm[7:0]);
counter60 a1(nCR,minalarm_EN && alarm_clock,CP1,minute_alarm[7:0]);
assign minalarm_EN=Adj_Min ? Vdd:(second_alarm==8'h59);
counter24 a2(nCR,houralarm_EN && alarm_clock,CP1,hour_alarm[7:0]);
assign houralarm_EN=Adj_Hour ? Vdd:((minute_alarm == 8'h59) && (second_alarm == 8'h59));
//当闹钟调时使能有效时,原调时信号直接作用于闹钟定时变量
//闹钟主体
(3) 显示模块
显示模块由BCD-数码管译码器、位移寄存器、复用器构成。显示模块工作原理是基于人眼视觉暂留效应将6片计数器中的4位BCD码数据通过复用器分时输入至译码器,经码得到与之对应的8位码驱动6片数码管阴极引脚,其*阳极由位移寄存器驱动分时高频率依次点亮各片数码管实现动态扫描。整体层次关系如下:
图3 显示模块整体层次关系
其中数码管译码器设计为:
module decoder(
input [3:0] I,
output reg [7:0] Y
);
always @(*) begin
casex(I)
4'b0000: Y = 8'b11000000; //0
4'b0001: Y = 8'b11111001; //1
4'b0010: Y = 8'b10100100; //2
4'b0011: Y = 8'b10110000; //3
4'b0100: Y = 8'b10011001; //4
4'b0101: Y = 8'b10010010; //5
4'b0110: Y = 8'b10000010; //6
4'b0111: Y = 8'b11111000; //7
4'b1000: Y = 8'b10000000; //8
4'b1001: Y = 8'b10010000; //9
4'b1010: Y = 8'b10001000; //A
4'b1011: Y = 8'b10000011; //b
4'b1100: Y = 8'b11000110; //C
4'b1101: Y = 8'b10100001; //d
4'b1110: Y = 8'b10000110; //E
4'b1111: Y = 8'b10001110; //F
endcase
end
endmodule
位移寄存器设计为,其中freq_divider2为100MHz分为500Hz扫描CP信号:
module shift(
input CLK,
output reg[7:0] AN
);
wire CP;
initial begin
AN <= 8'b1111_1110;
end
freq_divider2 sdivider_u0(CLK,CP);//分频
always @(posedge CP) begin
AN[5:0] <= {AN[0],AN[5:1]};//循环位移
end
endmodule
复用器设计如下:
module switch(
input [63:0] I,//6端8位数码管码拼接
input [7:0] AN,//位移寄存器输出,共阴极
output reg [7:0] BCD
);
always @(*) begin//AN[i]循环位移并对输入信号选择
if(~AN[0]) BCD <= I[7:0];
else if(~AN[1])BCD <= I[15:8];
else if(~AN[2])BCD <= I[23:16];
else if(~AN[3])BCD <= I[31:24];
else if(~AN[4])BCD <= I[39:32];
else if(~AN[5])BCD <= I[47:40];
else if(~AN[6])BCD <= I[55:48];
else if(~AN[7])BCD <= I[63:56];
end
endmodule
扫描显示顶层模块如下:
module scan(
input [23:0] I,
input CLK,
output wire [7:0] Y,
output wire [7:0] AN
);
wire [3:0]BCD;
shift shift_u0(CLK,AN);//位移寄存器
switch switch_u0(I,AN,BCD);//数据分配器
decoder decoder_u0(BCD,Y);//BCD数码管译码器
endmodule
数字钟顶层通过调用scan函数实现循环扫描:
scan( scanner, CP , Y[7:0], AN[7:0]);
//scanner是通过多次选择得到的闹钟或时钟12/24时制的输出显示BCD码
//Y为扫描得出的阳极信号
//AN为扫描得出的阴极信号
(4) 音频模块(闹钟或仿电台正点报时与整点报时)
多功能数字钟的闹钟模块与仿电台正点报时与整点报时模块为实现音频输出调用了FPGA实验板自带的音频输出引脚,通过连接外置耳机播放。该模块的设计要求在闹钟音频信号有效时,输出间隔1秒,时长为1秒的500Hz音频信号至音频输出引脚;在每个小时59分51秒、53秒、55秒以及57秒别响时长一秒的低音并在59秒响时长一秒的高音从而实现正点报时目的;在进入下一小时时产生与小时数相等次数的低频响声,保持时间1s,间隔时间1s。其中音频控制模块verilog代码如下:
module baoshi(CP1,CP100M,hour,second,second_alarm,minute,AUD_PWM,alarm);
//输入1Hz、100MHz的CP信号,主体时钟的时分秒BCD码,闹钟模块的秒BCD码,音频输出,闹钟使能信号
input [7:0]hour,minute,second,second_alarm;
input CP1,CP100M;
input alarm; //闹钟信号
output reg AUD_PWM;
wire CP1K,CP500;
freq_divider3 f0(CP100M,CP500,CP1K);
//分频器3,将100MHz信号分为500Hz和1000Hz的音频信号
always@(CP1) begin
if(alarm)
//闹钟使能信号有效
begin
if(second[0] == second_alarm[0]) AUD_PWM <= CP500;
//在主体模块与闹钟模块的秒计数器最低位相等时直接将500Hz音频信号赋予音频输出
else AUD_PWM <= 0;
//不相等时无声音
end
else if(minute == 'd89)//当59分时
begin
if(second == 'd83 || second == 'd85 || second == 'd87 || second == 'd81)
AUD_PWM <= CP500;
//53、55、57、51秒将500Hz低音信号输入
else if(second == 'd89)
AUD_PWM <= CP1K;
//59秒将1000Hz高音信号输入
else
AUD_PWM <= 'd0;
//其他时间无声音
end
else if((hour == 0 || hour == 'd18) && minute == 'd0 )//12点敲12下
begin
if(second <= 'd34)begin
if (second[0] == 0) AUD_PWM <= CP500;
else AUD_PWM <= 'd0;
end
end
else if(minute == 'd0 && second[7:4]*10 + second[3:0] <= 2*((hour[3:0]+hour[5:4]*10)%12)-1'b1 ) begin
//整点报时,当主体模块秒计数器的值小于2*(时%12)-1时,刚好实现指定次数的报时
if (second[0] == 0) begin
AUD_PWM <= CP500;
//低位为0时敲钟
end
else AUD_PWM <= 'd0;
end
else AUD_PWM <= 'd0;
end
endmodule
在顶层模块中调用baoshi函数实现音频控制
baoshi(CP1,CP,hour,second,second_alarm,minute,AUD_PWM,alarm);
//1HZ的CP信号
//100MHz的CP信号
//时分秒计数器
//闹钟秒计数器
//音频输出变量
//闹钟使能信号
(5) 顶层模块
顶层模块输入有:清零端,使能端,调分,调时,闹钟调时,闹钟开关,12、24时制转换,100MHz时钟信号CP。
输出有:音频输出,七段数码管阳极信号、阴极信号
调用分频器1得到1Hz时钟;调用60进制以及24进制计数器实现主体模块以及闹钟模块的计数,调用scan函数扫描显示,调用baoshi函数实现音频控制
主模块完成逻辑功能:
1、通过多次选择得到的闹钟或时钟的12/24时制的输出显示BCD码scanner[7:0]
2、通过比较时钟主体计数器与闹钟计数器各位,获得闹钟音频信号alarm
module top_clock(
input alarm_clock,alarm_switch,//闹钟的输入控制信号,分别为闹钟调时和闹钟开关
output wire AUD_PWM,//闹钟声音信号输出至耳机插口端
input CP,//输入FPGA自带晶振的100MHz时钟信号
input nCR,EN,//清零端,使能端
input Adj_Min,Adj_Hour,trans,//分调整,时调整,12-24小时转换
output wire [7:0]Y,[7:0]AN //Y为七段数码管的显示结果,AN为8个数码管中选择一个显示
);
reg [23:0]scanner;//scanner为6位数码管输出信号,用作扫描输出
wire [7:0] hour,minute,second;//顶层模块时分秒wire变量
wire [7:0] hour_alarm, minute_alarm, second_alarm;//顶层模块闹钟时分秒wire变量
supply1 Vdd;//高电平
wire min_EN,hour_EN,minalarm_EN,houralarm_EN;//时分以及闹钟时分进位使能信号
wire CP1;//1Hz时钟信号
freq_divider(CP,CP1);//分频
reg alarm;//闹钟内置开关寄存器变量
initial begin
alarm = 0;//初始时闹钟关闭
end
counter60 u0(nCR,EN,CP1,second[7:0]);//秒变量60进制计数器
counter60 u1(nCR,min_EN && EN,CP1,minute[7:0]);//分变量60进制计数器
assign min_EN=Adj_Min ? Vdd && ~alarm_clock:(second==8'h59);
//分进位信号,如果调分使能有效且闹钟调时使能无效,则进位使能有效。如果调分使能无效但秒变量为59时,分进位使能有效
counter24 u2(nCR,hour_EN && EN,CP1,hour[7:0]);
//时变量24进制计数器
assign hour_EN=Adj_Hour ? Vdd && ~alarm_clock:((minute == 8'h59) && (second == 8'h59));
//时进位信号,如果调时使能有效且闹钟调时使能无效,则进位使能有效。如果调时使能无效但分变量为59时,时进位使能有效
//数字钟主体部分
counter60 a0(nCR,alarm_clock,CP1,second_alarm[7:0]);
counter60 a1(nCR,minalarm_EN && alarm_clock,CP1,minute_alarm[7:0]);
assign minalarm_EN=Adj_Min ? Vdd:(second_alarm==8'h59);
counter24 a2(nCR,houralarm_EN && alarm_clock,CP1,hour_alarm[7:0]);
assign houralarm_EN=Adj_Hour ? Vdd:((minute_alarm == 8'h59) && (second_alarm == 8'h59));
//当闹钟调时使能有效时,原调时信号直接作用于闹钟定时变量
//闹钟主体
always @(*) begin
if (alarm_clock == 0) begin
//闹钟调时信号无效,输出时钟主体计数器模块
if(trans && hour[7:0] == 8'b00000000) scanner = {8'b00010010, minute[7:0], second[7:0]};
//trans时制转换信号有效,且为24时制的0点,时显示输出12时制12点
else if(trans && hour[7:0] > 8'b00010010 ) scanner = {hour[7:0]-8'b00010010, minute[7:0], second[7:0]};
//trans时制转换信号有效,时显示输出24时制的时计数器时间-12
else scanner = {hour[7:0], minute[7:0], second[7:0]};
//trans时制转换信号无效,时显示输出24时制的时计数器时间
end
else begin//闹钟调时信号有效,输出时钟计数器模块
if(trans && hour_alarm[7:0] == 8'b00000000) scanner = {8'b00010010, minute_alarm[7:0], second_alarm[7:0]};
//trans时制转换信号有效,且为24时制的0点,时显示输出12时制12点
else if(trans && hour_alarm[7:0] > 8'b00010010) scanner = {hour_alarm[7:0]-8'b00010010, minute_alarm[7:0], second_alarm[7:0]};
//trans时制转换信号有效,时显示输出24时制的时计数器时间-12
else scanner = {hour_alarm[7:0], minute_alarm[7:0], second_alarm[7:0]};
//trans时制转换信号无效,时显示输出24时制的时计数器时间
end
if ({hour[7:0], minute[7:0], second[7:0]} == {hour_alarm[7:0], minute_alarm[7:0], second_alarm[7:0]} && (alarm_switch == 1))
alarm = 1;
//闹钟模块使能有效,时钟主体计数器与闹钟计数器各位相同,闹钟音频信号有效
else if(alarm_switch == 0) alarm = 0;
//闹钟部分
end
scan( scanner, CP , Y[7:0], AN[7:0]);
//scanner是通过多次选择得到的闹钟或时钟的12/24时制的输出显示BCD码
//Y为扫描得出的阳极信号
//AN为扫描得出的阴极信号
baoshi(CP1,CP,hour,second,second_alarm,minute,AUD_PWM,alarm);
//1HZ的CP信号
//100MHz的CP信号
//时分秒计数器
//闹钟秒计数器
//音频输出变量
//闹钟使能信号
endmodule
6) 引脚约束
set_property -dict { PACKAGE_PIN T10 IOSTANDARD LVCMOS33 } [get_ports { Y[7] }]; #IO_L24N_T3_A00_D16_14 Sch=ca
set_property -dict { PACKAGE_PIN R10 IOSTANDARD LVCMOS33 } [get_ports { Y[6] }]; #IO_25_14 Sch=cb
set_property -dict { PACKAGE_PIN K16 IOSTANDARD LVCMOS33 } [get_ports { Y[5] }]; #IO_25_15 Sch=cc
set_property -dict { PACKAGE_PIN K13 IOSTANDARD LVCMOS33 } [get_ports { Y[4] }]; #IO_L17P_T2_A26_15 Sch=cd
set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS33 } [get_ports { Y[3] }]; #IO_L13P_T2_MRCC_14 Sch=ce
set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { Y[2] }]; #IO_L19P_T3_A10_D26_14 Sch=cf
set_property -dict { PACKAGE_PIN L18 IOSTANDARD LVCMOS33 } [get_ports { Y[1] }]; #IO_L4P_T0_D04_14 Sch=cg
set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { Y[0] }]; #IO_L19N_T3_A21_VREF_15 Sch=dp
#阳极信号
set_property -dict { PACKAGE_PIN J17 IOSTANDARD LVCMOS33 } [get_ports { AN[0] }]; #IO_L23P_T3_FOE_B_15 Sch=an[0]
set_property -dict { PACKAGE_PIN J18 IOSTANDARD LVCMOS33 } [get_ports { AN[1] }]; #IO_L23N_T3_FWE_B_15 Sch=an[1]
set_property -dict { PACKAGE_PIN T9 IOSTANDARD LVCMOS33 } [get_ports { AN[2] }]; #IO_L24P_T3_A01_D17_14 Sch=an[2]
set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { AN[3] }]; #IO_L19P_T3_A22_15 Sch=an[3]
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { AN[4] }]; #IO_L8N_T1_D12_14 Sch=an[4]
set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { AN[5] }]; #IO_L14P_T2_SRCC_14 Sch=an[5]
set_property -dict { PACKAGE_PIN K2 IOSTANDARD LVCMOS33 } [get_ports { AN[6] }]; #IO_L23P_T3_35 Sch=an[6]
set_property -dict { PACKAGE_PIN U13 IOSTANDARD LVCMOS33 } [get_ports { AN[7] }]; #IO_L23N_T3_A02_D18_14 Sch=an[7]
#阴极信号
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets alarm_switch_IBUF]
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { CP}]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz
#100MHz时钟信号CP
set_property -dict { PACKAGE_PIN A11 IOSTANDARD LVCMOS33 } [get_ports { AUD_PWM }]; #IO_L4N_T0_15 Sch=aud_pwm
#音频输出端口
set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { nCR }]; #IO_L24N_T3_RS0_15 Sch=sw[0]
set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { EN }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=sw[1]
set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { Adj_Min }]; #IO_L6N_T0_D08_VREF_14 Sch=sw[2]
set_property -dict { PACKAGE_PIN R15 IOSTANDARD LVCMOS33 } [get_ports { Adj_Hour}]; #IO_L13N_T2_MRCC_14 Sch=sw[3]
set_property -dict { PACKAGE_PIN R17 IOSTANDARD LVCMOS33 } [get_ports { alarm_clock }]; #IO_L12N_T1_MRCC_14 Sch=sw[4]
set_property -dict { PACKAGE_PIN T18 IOSTANDARD LVCMOS33 } [get_ports { alarm_switch }]; #IO_L7N_T1_D10_14 Sch=sw[5]
set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { trans }]; #IO_L17N_T2_A13_D29_14 Sch=sw[6]
#开关,分别为清零端,使能端,调分,调时,闹钟调时,闹钟开关,12、24时制转换
六、实现过程
1)模60计数器的设计与仿真
整体仿真,调出60进制计数器模块
图4 模6计数器仿真波形
图5 模10计数器仿真波形
图6 模60计数器仿真波形
图7 模24计数器仿真波形
2)显示模块的仿真
图8 位选信号仿真波形
图9 七段数码管段选信号仿真波形
七、实现过程
1、依照上述设计进行代码编写,逻辑中和,布线。
2、进行引脚绑定,生成bit流。
3、将程序bit流下载到FPGA实验板。