Verilog、Xilinx ISE 13.4、BASYS2、EDA、数字钟
利用实验板设计实现一个能显示时分秒的多功能电子钟。
1.具有“秒”、“分”、“时”计时的功能,小时计数器按24小时制;
2.具有校时功能,能对“分”和“时”进行调整;
3.具有手动输入设置定时闹钟的功能,亮1分钟;
4.可实现时钟复位功能:00:00:00;
5.报整点:几点亮几下。
顶层模块包含:
分频模块:获得1HZ脉冲,供计时模块使用;
校时模块:对时分、分秒、闹钟置数;
计时模块:含时、分、秒三个子模块,用于计时;
闹钟模块:用于整点报时和定时闹钟;
取数模块:决定当前显示的内容(4位),并送到扫描模块;
扫描模块:将取到的数分成4份(1位),分时送到译码模块;
译码模块:将数字转化为数码管引脚电平,并判断是否显示小数点。
//顶层模块
module top(
input cp,//20ns脉冲
input reset,//复位按键BTN0
input set,//置数按键BTN1
input apply,//移位按键BTN2
input inc,//增加按键BTN3
input set_enable,//校时拨码SW7
input alarm_enable,//闹钟拨码SW6
input hour_show,//时分显示拨码SW5
input sec_show,//分秒显示拨码SW4
input count_enable,//计时拨码SW3
output [3:0] loc,//数码管位置
output [7:0] pin,//数码管引脚电平
output hour_alarm,//整点报时LD2
output alarm,//闹钟LD1
output sec//秒针LD0
);
wire [23:0] counter_num;//当前时间
wire [15:0] set_num;//校时时间
wire [15:0] alarm_num;//闹钟时间
wire [15:0] show_num;//显示内容
wire [3:0] scan_num;//译码内容
wire [3:0] set_loc;//置数位置
//分频模块
Clock P13(
.cp(cp),
.clock(clock)
);
//校时模块
Seter P14(
.enable(set_enable),
.inc(inc),
.apply(apply),
.set_num(set_num),
.set_loc(set_loc)
);
//计时模块
Counter P1(
.cp(clock),
.enable(count_enable),
.reset(reset),
.set(set && !alarm_enable && !set_enable),
.set_num(set_num),
.sec_show(sec_show),
.hour_show(hour_show),
.counter_num(counter_num),
.hour(hour),
.sec(sec)
);
//闹钟模块
Alarm P19(
.cp(clock),
.enable(alarm_enable),
.set(set && !set_enable),
.set_num(set_num),
.hour(hour),
.counter_num(counter_num),
.alarm(alarm),
.hour_alarm(hour_alarm),
.alarm_num(alarm_num)
);
//取数模块
Select P20(
.cp(cp),
.counter_num(counter_num),
.set_num(set_num),
.alarm_num(alarm_num),
.set_enable(set_enable),
.alarm_enable(alarm_enable),
.hour_show(hour_show),
.sec_show(sec_show),
.show_num(show_num)
);
//扫描模块
Scan P5(
.cp(cp),
.set_enable(set_enable),
.num(show_num),
.loc(loc),
.set_loc(set_loc),
.point(point),
.scan_num(scan_num)
);
//译码模块
Trans P6(
.scan_num(scan_num),
.point(point),
.pin(pin)
);
endmodule
将20ns的电路板脉冲50000000分频,输出1s的脉冲,作为计时cp。
//分频模块
module Clock(
input cp,
output clock
);
parameter i = 25000000;//下载(计时频率1Hz)
//parameter i = 5;//调试程序
reg [31:0] counter = 0;
reg clock_temp = 0;
always @(posedge cp)//上升沿触发
begin
if (counter >= i-1)
begin
counter = 0;
clock_temp = ~clock_temp;//取反
end
else
counter = counter+1;
end
assign clock = clock_temp;
endmodule
校时模块由4个n进制计数器(n=10)子模块组成,set_loc用于记录操作哪一位计数器,set_num用于记录校时的值。在enable拨码为1时,apply按键每按下一次,指针向右移一位;inc按键每按下一次,当前计数器获得一个cp,即值加1。在enable拨码为0时,该模块不起作用。
//校时功能模块
module Seter(
input enable,
input inc,
input apply,
output [15:0] set_num,
output [3:0] set_loc
);
//中间变量
reg [3:0] set_loc_temp = 4'b1000;//修改的位
always @(posedge apply)
begin
if (enable)
begin
set_loc_temp[0] <= set_loc_temp[1];
set_loc_temp[1] <= set_loc_temp[2];
set_loc_temp[2] <= set_loc_temp[3];
set_loc_temp[3] <= set_loc_temp[0];//移位
end
end
Count_n P15(
.cp(set_loc_temp[0] && enable && inc),
.reset(0),
.set(0),
.set_num(0),
.n(10),//十进制
.countn_num(set_num[3:0]),
.countn_co()//输出悬空
);
Count_n P16(
.cp(set_loc_temp[1] && enable && inc),
.reset(0),
.set(0),
.set_num(0),
.n(10),
.countn_num(set_num[7:4]),
.countn_co()
);
Count_n P17(
.cp(set_loc_temp[2] && enable && inc),
.reset(0),
.set(0),
.set_num(0),
.n(10),
.countn_num(set_num[11:8]),
.countn_co()
);
Count_n P18(
.cp(set_loc_temp[3] && enable && inc),
.reset(0),
.set(0),
.set_num(0),
.n(10),
.countn_num(set_num[15:12]),
.countn_co()
);
assign set_loc = set_loc_temp;
endmodule
计数器的进制n与计数脉冲cp由输入给定,输出当前计数值和进位。具有清零和置数的功能。
//n进制计数器模块
module Count_n(
input cp,
input reset,
input set,
input [3:0] set_num,//置数值
input [3:0] n,//进制
output [3:0] countn_num,//计数值
output countn_co//进位
);
//中间变量
reg [3:0] counter = 0;
reg countn_co_temp = 0;
always @(posedge cp, posedge reset, posedge set)
begin
//计数
if (reset)//高电平清零
begin
counter = 0;
countn_co_temp = 0;
end
else
begin
if (set)//高电平置数
begin
if (set_num < n)
begin
counter = set_num;
countn_co_temp = 0;
end
end
else
begin
counter = counter+1;//阻塞赋值,顺序执行
if (counter >= n)
begin
counter = 0;
countn_co_temp = 1;
end
else
countn_co_temp = 0;
end
end
end
assign countn_num = counter;
assign countn_co = countn_co_temp;
endmodule
计时功能模块含秒、分、时三个子模块,串行连接,秒针cp由分频模块给出,分针cp由秒进位给出,时针cp由分针进位给出。输出当前时间、秒针LED电平、分针进位(供闹钟整点报时使用)。具有清零和置数功能。
计时功能:当enable拨码为1时,将cp给秒针模块;否则不进行计数。
清零功能:reset按键按下,时分秒全部清零。
置数功能:当sec_show(显示分秒)或hour_show(显示时分)拨码为1时,set按键按下,将置数值赋给分秒/时分;否则置数功能不起作用。
//计时功能模块
module Counter(
input cp,
input reset,
input enable,
input set,
input [15:0] set_num,//置数值
input sec_show,//显示分秒
input hour_show,//显示时分
output [23:0] counter_num,//当前时间
output hour,//分针进位
output sec//秒针LED
);
//中间变量
wire co_sec;//秒进位
wire co_min;//分进位
wire [7:0]sec_num;//秒
wire [7:0]min_num;//分
wire [7:0]hour_num;//时
wire sec_temp;
reg [23:0] set_num_temp = 0;
reg [2:0] set_temp = 0;
//置数判断
always @(set)
begin
if (set)
begin
if (hour_show)
begin
set_num_temp[23:8] = set_num;
set_temp = 3'b110;
end
else
if (sec_show)
begin
set_num_temp[15:0] = set_num;
set_temp = 3'b011;
end
end
else
set_temp = 0;
end
//秒针
Sec_counter P2(
.cp(cp),
.reset(reset),
.set(set_temp[0]),
.set_num(set_num_temp[7:0]),
.enable(enable),
.sec_num(sec_num),
.co_sec(co_sec),
.sec(sec_temp)
);
assign sec = sec_temp;
//分针
Min_counter P3(
.cp(co_sec),
.reset(reset),
.set(set_temp[1]),
.set_num(set_num_temp[15:8]),
.co_min(co_min),
.min_num(min_num)
);
//时针
Hour_counter P4(
.cp(co_min),
.reset(reset),
.set(set_temp[2]),
.set_num(set_num_temp[23:16]),
.hour_num(hour_num)
);
assign hour = co_min;
assign counter_num[23:16] = hour_num;
assign counter_num[15:8] = min_num;
assign counter_num[7:0] = sec_num;
endmodule
秒针模块包含两个n进制计数器子模块(个位n=10,十位n=6),串行连接,个位进位接到十位cp上。cp、set、reset、置数值由父模块给定。输出秒LED电平、秒针数值、秒针进位。由于当计数使能为0时,计数停止,秒LED应不再闪烁,因此LED电平不能由分频模块直接提供。
//秒针模块
module Sec_counter (
input cp,
input reset,
input set,
input [7:0] set_num,
input enable,
output [7:0] sec_num,//秒针数值
output co_sec,//进位
output sec//秒LED
);
//中间变量
wire [1:0] co_sec_temp;
wire enable_h;
wire [7:0] sec_num_temp;
reg sec_temp = 0;
//10进制计数器计个位
Count_n P7(
.cp(cp && enable),
.reset(reset),
.set(set),
.set_num(set_num[3:0]),
.n(10),
.countn_num(sec_num_temp[3:0]),
.countn_co(co_sec_temp[0])
);
//6进制计数器计十位
assign enable_h = co_sec_temp[0];
Count_n P8(
.cp(enable_h),
.reset(reset),
.set(set),
.set_num(set_num[7:4]),
.n(6),
.countn_num(sec_num_temp[7:4]),
.countn_co(co_sec_temp[1])
);
assign co_sec = co_sec_temp[1];
//采取秒针LED电平
always @(sec_num_temp)
begin
if (sec_num_temp[0])
sec_temp = 1;
else
sec_temp = 0;
end
assign sec = sec_temp;
assign sec_num = sec_num_temp;
endmodule
原理同秒针模块,不再赘述。
//分针模块
module Min_counter(
input cp,
input reset,
input set,
input [7:0] set_num,
output co_min,
output [7:0] min_num
);
//中间变量
wire [1:0] co_min_temp;
wire [7:0] min_num_temp;
wire enable_h;
//10进制计数器计个位
Count_n P9(
.cp(cp),
.reset(reset),
.set(set),
.set_num(set_num[3:0]),
.n(10),
.countn_num(min_num_temp[3:0]),
.countn_co(co_min_temp[0])
);
//6进制计数器计十位
assign enable_h = co_min_temp[0];
Count_n P10(
.cp(enable_h),
.reset(reset),
.set(set),
.set_num(set_num[7:4]),
.n(6),
.countn_num(min_num_temp[7:4]),
.countn_co(co_min_temp[1])
);
assign min_num = min_num_temp;
assign co_min = co_min_temp[1];
endmodule
十位为3进制,个位进制由十位数值决定,当十位为0或1时,个位为10进制;当十位为2时,个位为4进制。其余原理同分、秒模块。
//时针模块
module Hour_counter(
input cp,
input reset,
input set,
input [7:0] set_num,
output [7:0] hour_num
);
//中间变量
wire [1:0] co_hour_temp;
wire [7:0] hour_num_temp;
reg [3:0] n_low = 10;
wire enable_h;
//10-4进制计数器计个位
Count_n P11(
.cp(cp),
.reset(reset),
.set(set),
.set_num(set_num[3:0]),
.n(n_low),
.countn_num(hour_num_temp[3:0]),
.countn_co(co_hour_temp[0])
);
assign enable_h = co_hour_temp[0];
//3进制计数器计十位
Count_n P12(
.cp(enable_h),
.reset(reset),
.set(set),
.set_num(set_num[7:4]),
.n(3),
.countn_num(hour_num_temp[7:4]),
.countn_co(co_hour_temp[1])
);
//控制个位进制
always @(hour_num_temp[7:4])
begin
case(hour_num_temp[7:4])
2: n_low = 4;
default: n_low = 10;
endcase
end
assign hour_num = hour_num_temp;
endmodule
闹钟模块有整点报时和定时闹钟两个功能:
整点报时:当产生分进位信号时,根据当前时针数值(由计时模块给定)决定LED闪烁次数,(闪烁频率由分频模块给定)。
定时闹钟: 置数方式与计时模块相似,当alarm_show(显示闹钟)拨码为1时,set按键按下,将置数值赋给闹钟;否则置数功能不起作用。当前时间与闹钟数值相同时,闹钟LED常亮一分钟。
//闹钟功能模块
module Alarm(
input cp,
input enable,
input set,
input [15:0] set_num,
input hour,//进位信号
input [23:0] counter_num,
output alarm,//定时闹钟
output hour_alarm,//整点报时
output [15:0] alarm_num//闹钟时间
);
//中间变量
reg [7:0] hour_num = 0;//时针时间
reg [15:0] alarm_num_temp = 0;
reg hour_alarm_temp = 0;
reg alarm_temp = 0;
reg switch = 1;//控制闪烁
always @(posedge cp)
begin
//整点报时
if (hour && (counter_num[15:0] == 0))//装入初值,不断减一,直到为0
hour_num = counter_num[23:20]*10 + counter_num[19:16];
else
begin
if (hour_num > 0)
begin
if (switch)
begin
hour_num = hour_num - 1;
hour_alarm_temp = 1;
switch = ~switch;
end
else
begin
hour_alarm_temp = 0;
switch = ~switch;
end
end
else
hour_alarm_temp = 0;
end
//定时闹钟
if (alarm_num == counter_num[23:8])
alarm_temp = 1;
else
alarm_temp = 0;
end
assign hour_alarm = hour_alarm_temp;
assign alarm = alarm_temp;
//定时
always @(posedge set)
begin
if (enable)
alarm_num_temp = set_num;
end
assign alarm_num = alarm_num_temp;
endmodule
按优先级排列如下:
当set_enable(置数使能)拨码为1时,输出置数内容;
当alarm_enable(显示闹钟)拨码为1时,输出闹钟内容;
当hour_show(显示时分)拨码为1时,输出时分内容;
当sec_show(显示分秒)拨码为1时,输出分秒内容。
//取数模块
module Select(
input cp,
input [23:0] counter_num,
input [15:0] set_num,
input [15:0] alarm_num,
input set_enable,
input alarm_enable,
input hour_show,
input sec_show,
output [15:0] show_num
);
reg [15:0] show_num_temp = 0;
always @(posedge cp)
begin
if(set_enable)
show_num_temp <= set_num;
else if (alarm_enable)
show_num_temp <= alarm_num;
else if (hour_show)
show_num_temp <= counter_num[23:8];
else if (sec_show)
show_num_temp <= counter_num[15:0];
else
show_num_temp <= 0;
end
assign show_num = show_num_temp;
endmodule
将取数模块输出值分时送到译码模块。扫描频率的计算方法与模六十计数器相同,但由于是对4个数码管进行动态显示,分频数应是模六十计数器的一半,即2的18次方。
//扫描模块
module Scan(
input cp,
input [15:0] num,
input set_enable,
input [3:0] set_loc,
output [3:0] loc,
output [3:0] scan_num,
output point
);
//中间变量
parameter j = 18;//控制扫描速率,仿真时取1;下载时取18(扫描频率100Hz)
reg [33:0] scan_counter = 0;
reg [3:0] loc_temp = 4'b1111;
reg [3:0] scan_num_temp = 0;
reg point_temp = 1;
wire [15:0] num_temp;
assign num_temp = num;
always @(posedge cp)//上升沿触发
begin
scan_counter <= scan_counter+1;
end
always @(scan_counter[j])
begin
case(scan_counter[j+1:j])
2'b00:
begin
loc_temp <= 4'b1110;
scan_num_temp <= num_temp[3:0];
point_temp <= set_loc[0];
end
2'b01:
begin
loc_temp <= 4'b1101;
scan_num_temp <= num_temp[7:4];
point_temp <= set_loc[1];
end
2'b10:
begin
loc_temp <= 4'b1011;
scan_num_temp <= num_temp[11:8];
point_temp <= set_loc[2];
end
2'b11:
begin
loc_temp <= 4'b0111;
scan_num_temp <= num_temp[15:12];
point_temp <= set_loc[3];
end
endcase
if (set_enable == 0)
point_temp <= 0;
end
assign loc = loc_temp;
assign point = point_temp;
assign scan_num = scan_num_temp;
endmodule
将扫描模块输入的数字转化成对应的数码管电平输出,与模六十计数器相同。但由于置数时需要用小数点来显示设置的位置,当set_enable(置数使能)拨码为1时,根据置数位置来确定是否显示小数点;当set_enable(置数使能)拨码为0时,不显示小数点。
//译码模块
module Trans(
input [3:0] scan_num,
input point,
output [7:0] pin
);
//中间变量
wire [3:0] scan_num_temp;
assign scan_num_temp = scan_num;
reg [7:0] pin_temp;
always @(scan_num_temp, point)
begin
case(scan_num_temp)
0: pin_temp[7:1] = 7'b0000001;
1: pin_temp[7:1] = 7'b1001111;
2: pin_temp[7:1] = 7'b0010010;
3: pin_temp[7:1] = 7'b0000110;
4: pin_temp[7:1] = 7'b1001100;
5: pin_temp[7:1] = 7'b0100100;
6: pin_temp[7:1] = 7'b0100000;
7: pin_temp[7:1] = 7'b0001111;
8: pin_temp[7:1] = 7'b0000000;
9: pin_temp[7:1] = 7'b0000100;
default: pin_temp = 7'b0110000;//其他状态显示E
endcase
pin_temp[0] = !point;
end
assign pin = pin_temp;
endmodule
仿真代码如下:
module Test_Top;
// Inputs
reg cp;
reg reset;
reg set;
reg apply;
reg inc;
reg set_enable;
reg alarm_enable;
reg hour_show;
reg sec_show;
reg count_enable;
// Outputs
wire [3:0] loc;
wire [7:0] pin;
wire alarm;
wire hour_alarm;
wire sec;
// Instantiate the Unit Under Test (UUT)
top uut (
.cp(cp),
.reset(reset),
.set(set),
.apply(apply),
.inc(inc),
.set_enable(set_enable),
.alarm_enable(alarm_enable),
.hour_show(hour_show),
.sec_show(sec_show),
.count_enable(count_enable),
.loc(loc),
.pin(pin),
.alarm(alarm),
.hour_alarm(hour_alarm),
.sec(sec)
);
//时钟周期20ns
parameter PERIOD = 20;
//时钟信号
always begin
cp = 1'b0;
#(PERIOD/2)cp = 1'b1;
#(PERIOD/2);
end
initial begin
// Initialize Inputs
cp = 0;
reset = 1;
set = 0;
apply = 0;
inc = 0;
set_enable = 0;
alarm_enable = 0;
hour_show = 0;
sec_show = 0;
count_enable = 0;
// Wait 100 ns for global reset to finish
#100;
reset = 0;
// Add stimulus here
//////////////////////////////////////////////使能拨码测试
//开始计数
#100;count_enable=1;
//显示分秒
#100;sec_show=1;
//显示时分
#100;hour_show=1;
//闹钟模式
#100;alarm_enable=1;
//置数模式
#100;set_enable=1;
//////////////////////////////////////////////闹钟置数测试
//闹钟置数1200
#100;inc=1; #20;inc=0;//1
#100;apply=1; #20;apply=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;//2
#100;set_enable=0;
#100;set=1; #20;set=0;
//////////////////////////////////////////////时钟置数测试
//时分置数1100
set_enable=1;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;//1
#100;set_enable=0;alarm_enable=0;//1
#100;set=1; #20;set=0;
//分秒置数5949
set_enable=1;
#100;apply=1; #20;apply=0; #100;apply=1; #20;apply=0; #100;apply=1; #20;apply=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0;//5
#100;apply=1; #20;apply = 0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;//9
#100;apply=1; #20;apply = 0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0; //4
#100;apply=1; #20;apply=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;//9
#100;set_enable=0;hour_show=0;
#100;set=1; #20;set=0;
#500;
//////////////////////////////////////////////清零测试
//清零
#100;reset=1;#20;reset=0;
//////////////////////////////////////////////计时使能拨码测试
//停止计时
#100;count_enable=0;
//////////////////////////////////////////////进位测试
//分秒置数5949
#100;set=1; #20;set=0;
//时分置数2359
set_enable=1;
#100;apply=1; #20;apply=0;//9
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #100;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0;//2
#100;apply=1; #20;apply=0;
#100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0; #100;inc=1; #20;inc=0;
#100;inc=1; #20;inc=0;//3
#100;apply=1; #20;apply=0;
#100;inc=1; #20;inc=0;//5
#100;set=1; #20;set=0;//空置数(当置数使能为0时,inc按键不应起作用)
#100;set_enable=0;
#100;sec_show=0; #100;set=1; #20;set=0;//空置数
#100;hour_show=1; #100;set=1; #20;set=0;//时分置数
//恢复计时
#100;count_enable=1;
end
endmodule
cp:电路板脉冲(20ns);
clock:分频模块产生脉冲,仿真时为10分频;
reset:清零按键;
set:置数按键;
inc:加一按键,在置数模式下对选定位加一;
apply:移位按键,在置数模式下改变选定的位置;
set_enable:置数使能拨码;
alarm_enable:显示闹钟拨码;
hour_show:显示时分拨码;
sec_show:显示分秒拨码;
count_enable:计时使能拨码;
set_num:置数内容:为观察方便,采用16进制显示,下同;
alarm_num:闹钟时间;
count_num:计时时间;
show_num:数码管显示内容;
loc:扫描位置;
scan_num:扫描数字,初始值为X,因为是中间变量,不会对引脚电平产生影响;
pin:数码管引脚电平,最后一位为小数点;
sec:秒LED;
hour_alarm:整点报时LED;
alarm:闹钟LED。
2. 使能拨码测试
初始化时关闭所有拨码,数码管显示0000;逐步打开拨码,数码管显示对应内容,如黄线所示,此时hour_show拨码打开,显示时分,counter_num内容为000001,故show_num为0000。
3. 闹钟置数测试
通过inc和apply按键使置数值达到1200,关闭set_enable,此时alarm_enable优先级最高,按下set按键,如黄线所示,alarm_num由0000变为1200。
4. 时钟置数测试
用同样的方法,先后使时钟的时分置1100,分秒置5949,如黄线所示。
5. 清零测试
如黄线所示,reset按下后,counter_num清零。
6. 计时使能拨码测试
如黄线所示,count_enable拨码置0后,不再计数,count_num保持不变。
7. 进位测试
将时间置到235949,进位正常,由235959到000000,如黄线所示。
从电路角度去想问题
不要像我这么复杂