【数字钟实验2.0】这次是用systemverilog/verilog来完成数字钟实验1.0中的数字钟功能(还增加了日期功能!),其实感觉比画电路简单哈哈哈哈:)
软件平台:Vivado 2020.1, vscode
硬件平台:Basys3开发板
智能平台(重要!非常重要!):一个能清醒记住变量名, 在不同模块中打对变量名, 并不会忘记打上位宽的脑子
module cnt60 (
input clk,
input clr,
output reg [7:0] q,
output reg c
);
reg [3:0] nextq0;
reg [3:0] nextq1;
always @(posedge clk, posedge clr)
begin
if(clr)
q<=8'b0000_0000;
else
begin
q[7:4]<=nextq1;
q[3:0]<=nextq0;
c<=1'b0;
if(q[7:4]==4'b0101 && q[3:0]==4'b1001)
c<=1'b1;
end
end
always @ (*)
begin
if (q[7:4]!=4'b0101) //秒钟/分钟的高位不为5时
begin
if (q[3:0]==4'b1001)//高位不为5,低位不是9的话,低位+1
begin
nextq1=q[7:4]+1; nextq0=4'b0000;
end
//高位不为5,低位为9时,时钟到来后,高位加1,低位变为0
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;
//低位不为9时,时钟到来后,低位加1,高位不变
end
end
else //秒钟/分钟的高位为5时
begin
if (q[3:0]==4'b1001)
begin
nextq1=4'b0000; nextq0<=4'b0000;
end
//59的下一个状态时00
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1;
end //低位不为9时,下一时刻低位加1,高位不变
end
end
endmodule
module cnt24 (
input clk,
input clr,
output reg [7:0] q,
output reg c
);
reg[3:0] nextq0;
reg[3:0] nextq1;
always @(posedge clk,posedge clr)
begin
if(clr)
q<=9'b0_0000_0000;
else
begin
q[3:0]<=nextq0;
q[7:4]<=nextq1;
end
end
always @ (*)
begin
if (q[7:4]!=4'b0010) //时钟的高位不为2时
begin
if (q[3:0]==4'b1001)
begin
nextq1=q[7:4]+1; nextq0=4'b0000;c=0;
end
//时钟低位为9时,时钟到来后,高位加1,低位变为0
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
//低位不为9时,时钟到来后,低位加1,高位不变
end
end
else //时钟的高位为2时
begin
if (q[3:0]==4'b0011)
begin
nextq1=4'b0000; nextq0=4'b0000;c=1;
end
//23小时的下一个状态时00时,否则就+1
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
end
end
end
endmodule
秒表的计数器直接就是一样,倒计时的计数器改一改组合逻辑的部分就好,不知道有多简单~⭐
要实现完全的日期计数还要继续实现月和年的计数,这里放day的代码做一个栗子叭!
module counterday (
input clk,
input rst,
input [7:0] month,
input [15:0] year,
output reg [7:0] q,
output reg c
);
reg [3:0] nextq0;
reg [3:0] nextq1;
always @(posedge clk,posedge rst) begin
if(rst)
q<=8'b0000_0001;
else
begin
q[3:0]<=nextq0;
q[7:4]<=nextq1;
end
end
always @(*)
begin
case(month)
8'd1,8'd3,8'd5,8'd7,8'd8,8'd10,8'd12:begin
if (q[7:4]!=4'b0011) //时钟的高位不为3时
begin
if (q[3:0]==4'b1001)
begin
nextq1=q[7:4]+1; nextq0=4'b0000;c=0;
end
//时钟低位为9时,时钟到来后,高位加1,低位变为0
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
//低位不为9时,时钟到来后,低位加1,高位不变
end
end
else //高位为3时
begin
if (q[3:0]==4'b0001)
begin
nextq1=4'b0000; nextq0=4'b0001;c=1;
end
//31日的下一个状态时01日,否则就+1
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
end
end
end
8'd4,8'd6,8'd9,8'd11:
begin
if (q[7:4]!=4'b0011) //时钟的高位不为3时
begin
if (q[3:0]==4'b1001)
begin
nextq1=q[7:4]+1; nextq0=4'b0000;c=0;
end
//时钟低位为9时,时钟到来后,高位加1,低位变为0
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
//低位不为9时,时钟到来后,低位加1,高位不变
end
end
else //高位为3时
begin
if (q[3:0]==4'b0000)
begin
nextq1=4'b0000; nextq0=4'b0001;c=1;
end
//30日的下一个状态时01日,否则就+1
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
end
end
end
8'd2:begin
if((year%(16'd4)==0 && year%(16'd100)!=0) || year%(16'd400)==0)begin//闰年
if (q[7:4]!=4'b0010) //时钟的高位不为2时
begin
if (q[3:0]==4'b1001)
begin
nextq1=q[7:4]+1; nextq0=4'b0000;c=0;
end
//时钟低位为9时,时钟到来后,高位加1,低位变为0
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
//低位不为9时,时钟到来后,低位加1,高位不变
end
end
else //高位为3时
begin
if (q[3:0]==4'b1001)
begin
nextq1=4'b0000; nextq0=4'b0001;c=1;
end
//29日的下一个状态时01日,否则就+1
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
end
end
end
else//平年
begin
if (q[7:4]!=4'b0010) //时钟的高位不为2时
begin
if (q[3:0]==4'b1001)
begin
nextq1=q[7:4]+1; nextq0=4'b0000;c=0;
end
//时钟低位为9时,时钟到来后,高位加1,低位变为0
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
//低位不为9时,时钟到来后,低位加1,高位不变
end
end
else //高位为2时
begin
if (q[3:0]==4'b1000)
begin
nextq1=4'b0000; nextq0=4'b0001;c=1;
end
//28日的下一个状态时01日,否则就+1
else
begin
nextq1=q[7:4]; nextq0=q[3:0]+1'b1;c=0;
end
end
end
end
default:begin nextq1=4'b0000; nextq0=4'b0001;c=0; end
endcase
end
endmodule //counterday
这里使用clk来设置时间,也就是说min和hour的时钟信号要么是来自前一级的进位(非设置模式),要么来自1HZ时钟信号(设置模式)
module adjust (
input clk,//1HZ clk
input co1,//second->min c
input co2,//min->hour c
input set1,//adjust min c
input set2,//adjust hour c
input led,//alarm or clock
output logic clk1,//adjust clk for min
output logic clk2,//adjust clk for hour
output logic clk3,//alarm clk for min
output logic clk4//alarm clk for min
);
assign clk1 = (set1 &&!led)?clk:co1;
assign clk2 = (set2&&!led)?clk:((!set1) && co2);
assign clk3 = (set1&&led)? clk:0;
assign clk4 = (set2&&led)? clk:0;
endmodule
倒计时的初始时间设置、日期的初始设置模块也差不太多,原理是一样的,就不重复展示啦!
开发板的振晶产生100MHZ时钟信号,要通过时钟分频模块产生1HZ的时钟信号来实现1s的time
module fenpin (
input res,
input clk,
output reg clk_f
);
parameter clk_number=32'd5000_0000;
reg [31:0] clk_count;
always @(posedge clk,posedge res)
begin
if(res)
begin
clk_f<=0;
clk_count<=0;
end
else if(clk_count==clk_number)
begin
clk_count<=0;
clk_f<=~clk_f;
end
else
clk_count<=clk_count+1;
end
endmodule
在数字钟实验1.0中我们提到,实验1.0使用的是静态显示,动态显示会在实验2中实现。
所谓动态显示,就是7段数码管其实并非在同一时间内全都选通,而是通过较高的扫描频率进行数码管的通电选通(大于100HZ)使人眼看上去全部的数码管都有显示
查看开发板的文档可知,7段数码管低选通(低电平通电),且各段数码管共阳极(这关系到数字显示是高选通还是低选通,此处为低选通,低电平的那一段亮)
module scan (
input wire clk,
input wire [15:0] data,
output reg[6:0] seg7,
output reg [3:0] an
);
reg [1:0] q=2'b00;
reg [3:0] d;
always @(posedge clk)
begin
q<=q+2'b01;
end
always @(posedge clk)
begin
case(q)
2'b00:d<=data[15:12];
2'b01:d<=data[11:8];
2'b10:d<=data[7:4];
2'b11:d<=data[3:0];
default: d<=data[3:0];
endcase
case(q)
2'b00:an<=4'b0111;
2'b01:an<=4'b1011;
2'b10:an<=4'b1101;
2'b11:an<=4'b1110;
default:an<=4'b1110;
endcase
end
always @(d)
begin
case(d)
4'b0000:seg7=7'b1000000;
4'b0001:seg7=7'b1111001;
4'b0010:seg7=7'b0100100;
4'b0011:seg7=7'b0110000;
4'b0100:seg7=7'b0011001;
4'b0101:seg7=7'b0010010;
4'b0110:seg7=7'b0000010;
4'b0111:seg7=7'b1111000;
4'b1000:seg7=7'b0000000;
4'b1001:seg7=7'b0010000;
default:seg7=7'b1000000;
endcase
end
endmodule
module clock (
input clk,
input clr,
input set1,//min+1 button
input set2,//hour+1 button
input set3,//alarm or clock
input set4,//h+m or m+s
input set5,//min+1 switch
input set6,//h+1 switch
input setday,//day+1 switch
input setmonth,//month+1 switch
input setyear0,//year+1 swithc
input setyear1,//year+100 switch
input on1,//clock
input on2,//alarm
input on3,//countdown
input on4,//timekeeper
input on5,//date
input stop,//stop the timekeeper
output [6:0] seg7,
output dp,//The demical point
output [3:0] an,// low effective, choose 1 in 4 seg
output reg led1, //nowtime
output reg led2, //alarm, clock adjust
output reg led3,//time is to alarm
output reg led4,//alarm on/off
output reg led5//time is up
);
reg set7,set8;//switch or button adjust
reg [7:0] qs,qm,qh;//h,m,s of the clock
reg [7:0] a_m,a_h;//h,m of the alarm
reg [7:0] d_m,d_h,d_s;//h,m of the countdown
reg [7:0] k_m,k_s,k_h;//h,m,s of the timekeeper
logic [15:0]datas;
logic ck1,ck2,co1,co2,co3,cd1,cd2;
logic clk_1,clk1,clk2,clk3,clk4,clkd1,clkd2;
logic clk_200;
assign dp=1'b1;
fenpin u1(.res(clr),.clk(clk),.clk_f(clk_1));
fenpin #2500_00 u2(.res(clr),.clk(clk),.clk_f(clk_200));
//clock and alarm
cnt60 u3(.clk(clk_1),.clr(clr),.q(qs),.c(co1));
cnt60 u4(.clk(clk1),.clr(clr),.q(qm),.c(co2));
cnt24 u5(.clk(clk2),.clr(clr),.q(qh),.c(co3));
assign set7 = set1 || set5;
assign set8 = set2 || set6;
adjust u6(.clk(clk_1),.co1(co1),.co2(co2),.set1(set7),.set2(set8),.clk1(clk1),.clk2(clk2),.led(led2),.clk3(clk3),.clk4(clk4));
cnt60 u7(.clk(clk3),.clr(clr),.q(a_m));
cnt24 u8(.clk(clk4),.clr(clr),.q(a_h));
assign led1=(on1 && qm==0)?clk_1:0;
assign led3=(on2 && !set3 && {qh,qm}=={a_h,a_m})?1:0;
assign led2=(on2 && set3)?clk_1:0;
assign led4=(on2)?1:0;
//timekeeper
timekeeper u9(.clk(clk_1),.clr(clr),.stop(stop),.ks(k_s),.km(k_m),.kh(k_h));
//countdown
dcnt60 u12(.clk(clk_1),.clr(clr),.q(d_s),.c(cd1));
dcnt60 u13(.clk(clkd1),.clr(clr),.q(d_m),.c(cd2));
dcnt100 u14(.clk(clkd2),.clr(clr),.q(d_h));
dadjust u15(.clk(clk_1),.cd1(cd1),.cd2(cd2),.set1(set7),.set2(set8),.clkd1(clkd1),.clkd2(clkd2));
//date
wire [7:0]year0;
wire [7:0]year1;
wire [15:0]year={year1,year0};
wire [7:0]day;
wire [7:0]month;
counterday u17(.clk(clkday),.rst(clr),.month(month),.year(year),.q(day),.c(cday));
countermonth u18(.clk(clkmonth),.rst(clr),.q(month),.c(cmonth));
counteryear0 u19(.clk(clkyear0),.rst(clr),.q(year0),.c(cyear0));
counteryear1 u20(.clk(clkyear1),.rst(clr),.q(year1));
adjdate u21(.clk(clk_1),.chour(co3),.cday(cday),.cmonth(cmonth),.cyear0(cyear0),.set1(setday),.set2(setmonth),.set3(setyear0),.set4(setyear1),.clk1(clkday),.clk2(clkmonth),.clk3(clkyear0),.clk4(clkyear1));
assign led5=(on3 && d_h==0&&d_m==0&&d_s==0)?1:0;
always @(*)
begin
if(on1 && set4 && !set3)
datas={qh,qm};
else if(on1 && !set4 && !set3)
datas={qm,qs};
else if(on2 && set3)
datas={a_h,a_m};
else if(on3 && set4)
datas={d_h,d_m};
else if(on3 && !set4)
datas={d_m,d_s};
else if(on4 && !set4)
datas={k_m,k_s};
else if(on4 && set4)
datas={k_h,k_m};
else if(stop==1 && on4 && !set4)
datas={k_m,k_s};
else if(stop==1 && on4 && set4)
datas={k_h,k_m};
else if(on5 && !set4)
datas={month,day};
else if(on5 && set4)
datas={year1,year0};
else datas=16'b0000_0000_0000_0000;
end
scan u16(.clk(clk_200),.data(datas),.seg7(seg7),.an(an));
endmodule
在日期功能中考虑了闰年的问题,不得不使用除法(而且还涉及到非2的幂次的100和400,无法用移位实现)。
硬件描述语言意味着你写了除法,那就必然会产生除法器。
除法器是非常非常昂贵的硬件,真的别写(就勉强别考虑闰年了,反正一些钟也是要自己调日期)
这里我推荐使用直接上开发板调试的方法,但有利有弊
利:现象直观,可以保证调试完一般就能用了
弊:一通综合、实现、生成bit文件再program下来,改一次没有5分钟是不太可能的
Simulation调试
testbench中要使用比100MHZ高的clk信号,否则那个波形真就得跑1s长度的模拟,真的超级超级超级缓慢(为了看进位之类的,好歹得跑2分钟的模拟吧,漫长的等待)
就写到这里啦,最后祝大家在经典数字钟实验中获得快乐:)