【数字钟实验2.0】Verilog/SystemVerilog

【数字钟实验2.0】这次是用systemverilog/verilog来完成数字钟实验1.0中的数字钟功能(还增加了日期功能!),其实感觉比画电路简单哈哈哈哈:)

嘿嘿目录

  • 实验设置
  • 分模块代码
      • 60计数器
      • 24计数器
      • 日期计数器(day)
      • 设置时间
      • 时钟分频
      • 动态显示扫描模块
      • 顶层模块
  • 几个问题
      • 1. 日期功能
      • 2. 如何调试

实验设置

软件平台:Vivado 2020.1, vscode
硬件平台:Basys3开发板
智能平台(重要!非常重要!):一个能清醒记住变量名, 在不同模块中打对变量名, 并不会忘记打上位宽的脑子

分模块代码

60计数器

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

24计数器

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)

要实现完全的日期计数还要继续实现月和年的计数,这里放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

几个问题

1. 日期功能

在日期功能中考虑了闰年的问题,不得不使用除法(而且还涉及到非2的幂次的100和400,无法用移位实现)。
硬件描述语言意味着你写了除法,那就必然会产生除法器。
除法器是非常非常昂贵的硬件,真的别写(就勉强别考虑闰年了,反正一些钟也是要自己调日期)

2. 如何调试

这里我推荐使用直接上开发板调试的方法,但有利有弊
利:现象直观,可以保证调试完一般就能用了
弊:一通综合、实现、生成bit文件再program下来,改一次没有5分钟是不太可能的


Simulation调试
testbench中要使用比100MHZ高的clk信号,否则那个波形真就得跑1s长度的模拟,真的超级超级超级缓慢(为了看进位之类的,好歹得跑2分钟的模拟吧,漫长的等待)

就写到这里啦,最后祝大家在经典数字钟实验中获得快乐:)

你可能感兴趣的:(fpga开发)