文章目录
- 前言
- 一、设计内容
- 二、模块结构
- 三、代码编写
- 1、顶层模块Digclk
- 2、状态控制模块Ctrl
- 3、按键消抖模块Filter
- 4、计时模块Time
- 5、闹钟模块Alarm
- 6、显示模块Display
- 7、数码管驱动模块Smg
- 四、测试文件
- 五、波形仿真
- 总结
前言
再次编写数字钟Verilog程序,使其符合规范,并采用模板化与模块化编程,使程序思路更加清晰。与之前不同,此次编程在Quartus II 13.0中建立项目,在AX530开发板上下载调试,引脚定义与配置需要视情况更改,项目文件将上传到资源中。
一、设计内容
设计功能与之前保持一致:
1.具有“秒”、“分”、“时”计时的功能,小时计数器按24小时制;
2.具有校时功能,能对“分”和“时”进行调整;
3.具有手动输入设置定时闹钟的功能,亮1分钟;
4.可实现时钟复位功能:00:00:00;
5.报整点:几点亮几下。
AX530开发板有六个数码管,没有拨码开关,为与之前保持一致,仅使用其中四个数码管,用按键切换显示内容。
二、模块结构
根据输入和输出划分模块,顶层模块仅实现模块间的连接,状态控制模块控制当前显示内容,计时模块实现计时、校时、秒针输出、整点报时功能,闹钟模块实现定时闹钟的设置与输出,显示模块根据当前工作模式确定显示的内容。
三、代码编写
1、顶层模块Digclk
顶层模块尽量减少逻辑,只用作模块连接,根据输出内容划分模块功能
/***************数字钟顶层模块***************/
module Digclk(
clk,
rst_n,
btn,
led,
smg_sig,
smg_loc
);
//输入输出
input clk;
input rst_n;
input [2:0] btn;
output [3:0] led;
output [7:0] smg_sig;
output [5:0] smg_loc;
wire [2:0] led;
wire [7:0] smg_sig;
wire [5:0] smg_loc;
/***************状态控制模块***************/
wire [1:0] mode;
wire [3:0] set_loc;
wire set_inc;
Ctrl U_ctrl(
.clk(clk),
.rst_n(rst_n),
.swc_btn(btn[2]),//切换按键
.mov_btn(btn[1]),//移位按键
.inc_btn(btn[0]),//加一按键
.mode(mode),//工作状态
.set_loc(set_loc),//位置信号
.set_inc(set_inc)//加一信号
);
/***************计时模块***************/
wire [23:0] time_data;
Time U_time(
.clk(clk),
.rst_n(rst_n),
.mode(mode),
.set_loc(set_loc),
.set_inc(set_inc),
.time_data(time_data),
.sec(led[0]),//秒针LED
.hour(led[2])//整点报时
);
/***************闹钟模块***************/
wire [15:0] alm_data;
Alarm U_alarm(
.clk(clk),
.rst_n(rst_n),
.mode(mode),
.set_loc(set_loc),
.set_inc(set_inc),
.time_data(time_data),
.alm_data(alm_data),
.alm(led[1])//定时闹钟
);
/***************显示模块***************/
Display U_display(
.clk(clk),
.rst_n(rst_n),
.mode(mode),
.time_data(time_data),
.alm_data(alm_data),
.set_loc(set_loc),
.smg_sig(smg_sig),//数码管信号
.smg_loc(smg_loc)//数码管位置
);
endmodule
2、状态控制模块Ctrl
通过消抖模块处理按键信息,输出当前的工作状态与校时操作信号与位置
/***************状态控制模块***************/
module Ctrl(
clk,
rst_n,
swc_btn,//切换按键
mov_btn,//移位按键
inc_btn,//加一按键
mode,//工作状态
set_loc,//位置信号
set_inc//加一信号
);
//输入输出
input clk;
input rst_n;
input swc_btn;
input mov_btn;
input inc_btn;
output [1:0] mode;
output [3:0] set_loc;
output set_inc;
reg [1:0] mode;
reg [3:0] set_loc;
wire set_inc;
/***************按键消抖***************/
wire swc_btn_f;
wire mov_btn_f;
wire inc_btn_f;
Filter U_filter1(
.clk(clk),
.rst_n(rst_n),
.btn(swc_btn),
.btn_f(swc_btn_f)
);
Filter U_filter2(
.clk(clk),
.rst_n(rst_n),
.btn(mov_btn),
.btn_f(mov_btn_f)
);
Filter U_filter3(
.clk(clk),
.rst_n(rst_n),
.btn(inc_btn),
.btn_f(inc_btn_f)
);
/***************工作状态***************/
wire en_mode;
wire co_mode;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
mode <= 0;
end
else if(en_mode)begin
if(co_mode)
mode <= 0;
else
mode <= mode + 1;
end
end
assign en_mode = swc_btn_f;
assign co_mode = (en_mode) && (mode == 2);
/***************位置信号***************/
reg [3:0] cnt_loc;
wire en_loc;
wire co_loc;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_loc <= 0;
end
else if(en_loc)begin
if(co_loc)
cnt_loc <= 0;
else
cnt_loc <= cnt_loc + 1;
end
end
assign en_loc = mov_btn_f || swc_btn_f;
assign co_loc = (en_loc) && ((cnt_loc == 4) || swc_btn_f);
always@(*)begin
case(cnt_loc)
0: set_loc = 4'b0000;
1: set_loc = 4'b1000;
2: set_loc = 4'b0100;
3: set_loc = 4'b0010;
4: set_loc = 4'b0001;
default: set_loc = 4'b0000;
endcase
end
/***************加一信号***************/
assign set_inc = inc_btn_f;
endmodule
3、按键消抖模块Filter
间隔一段时间采样,将按键的下降沿转化为一个clk的脉冲输出,可作为基本模块使用
/***************按键消抖模块***************/
module Filter(
clk,
rst_n,
btn,
btn_f
);
//参数定义
parameter CNT_200MS = 24'd9_999_999;
parameter CNT_200NS = 24'd9;
//输入输出
input clk;
input rst_n;
input btn;
output btn_f;
wire btn_f;
/***************间隔200ms***************/
reg [23:0] cnt;
wire en;
wire co;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(en)begin
if(co)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign en = 1;
assign co = (en) && (cnt == CNT_200MS);
/***************buf1***************/
reg btn_buf1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
btn_buf1 <= 0;
end
else begin
if(co)
btn_buf1 <= btn;
end
end
/***************buf2***************/
reg btn_buf2;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
btn_buf2 <= 0;
end
else begin
btn_buf2 <= btn_buf1;
end
end
/***************输出正脉冲***************/
assign btn_f = (!btn_buf1) && (btn_buf2);
endmodule
4、计时模块Time
使用六个计数器进行时分秒的计时,将加一信号接入使能信号实现校时功能
整点报时和秒针功能也可以单独划分为一个模块
/***************计时模块***************/
module Time(
clk,
rst_n,
mode,
set_loc,
set_inc,
time_data,
sec,
hour
);
//参数定义
parameter CNT_1S = 26'd49_999_999;
parameter CNT_1US = 26'd49;
//输入输出
input clk;
input rst_n;
input [1:0] mode;
input [3:0] set_loc;
input set_inc;
output [23:0] time_data;
output sec;
output hour;
wire [23:0] time_data;
reg sec;
reg hour;
/***************校时***************/
wire set_state;
assign set_state = ((mode == 0) || (mode == 1)) && (set_loc != 0);
reg [5:0] inc;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
inc <= 0;
end
else begin
if((mode == 0) && set_inc)
inc <= {set_loc, 2'b00};
else if((mode == 1) && set_inc)
inc <= {2'b00, set_loc};
else
inc <= 0;
end
end
/***************分频***************/
reg [25:0] cnt_div;
wire en_div;
wire co_div;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_div <= 0;
end
else if(en_div)begin
if(co_div)
cnt_div <= 0;
else
cnt_div <= cnt_div + 1;
end
end
assign en_div = !set_state;
assign co_div = (en_div) && (cnt_div == CNT_1S);
/***************秒个位***************/
reg [3:0] cnt_s0;
wire en_s0;
wire co_s0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_s0 <= 0;
end
else if(en_s0)begin
if(co_s0)
cnt_s0 <= 0;
else
cnt_s0 <= cnt_s0 + 1;
end
end
assign en_s0 = co_div || inc[0];
assign co_s0 = (en_s0) && (cnt_s0 == 9);
/***************秒十位***************/
reg [3:0] cnt_s1;
wire en_s1;
wire co_s1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_s1 <= 0;
end
else if(en_s1)begin
if(co_s1)
cnt_s1 <= 0;
else
cnt_s1 <= cnt_s1 + 1;
end
end
assign en_s1 = co_s0 || inc[1];
assign co_s1 = (en_s1) && (cnt_s1 == 5);
/***************分个位***************/
reg [3:0] cnt_m0;
wire en_m0;
wire co_m0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_m0 <= 0;
end
else if(en_m0)begin
if(co_m0)
cnt_m0 <= 0;
else
cnt_m0 <= cnt_m0 + 1;
end
end
assign en_m0 = co_s1 || inc[2];
assign co_m0 = (en_m0) && (cnt_m0 == 9);
/***************分十位***************/
reg [3:0] cnt_m1;
wire en_m1;
wire co_m1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_m1 <= 0;
end
else if(en_m1)begin
if(co_m1)
cnt_m1 <= 0;
else
cnt_m1 <= cnt_m1 + 1;
end
end
assign en_m1 = co_m0 || inc[3];
assign co_m1 = (en_m1) && (cnt_m1 == 5);
/***************时个位***************/
reg [3:0] cnt_h0;
reg [3:0] cnt_h1;
wire en_h0;
wire co_h0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_h0 <= 0;
end
else if(en_h0)begin
if(co_h0)
cnt_h0 <= 0;
else
cnt_h0 <= cnt_h0 + 1;
end
end
assign en_h0 = co_m1 || inc[4];
assign co_h0 = (en_h0) && ((cnt_h0 == 9) || (((cnt_h1 == 2) && ((cnt_h0 == 3)))));
/***************时十位***************/
wire en_h1;
wire co_h1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_h1 <= 0;
end
else if(en_h1)begin
if(co_h1)
cnt_h1 <= 0;
else
cnt_h1 <= cnt_h1 + 1;
end
end
assign en_h1 = co_h0 || inc[5];
assign co_h1 = (en_h1) && ((((cnt_h1 == 2) && ((cnt_h0 >= 3)))) || ((cnt_h1 >= 2) && inc[5]));
assign time_data = {cnt_h1, cnt_h0, cnt_m1, cnt_m0, cnt_s1, cnt_s0};
/***************整点报时***************/
reg flag_hour;
reg [5:0] cnt_hour;
wire en_hour;
wire co_hour;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_hour <= 0;
end
else begin
if(en_hour)begin
if(co_hour)
cnt_hour <= 0;
else
cnt_hour <= cnt_hour + 1;
end
end
end
assign en_hour = ({cnt_h1, cnt_h0} != 0) && ({cnt_m1, cnt_m0} == 0) && flag_hour && co_div;
assign co_hour = (en_hour) && (cnt_hour >= (2 * (10 * cnt_h1 + cnt_h0) - 1));
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag_hour <= 1;
end
else begin
if(co_hour)
flag_hour <= 0;
else if({cnt_m1, cnt_m0} != 0)
flag_hour <= 1;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
hour <= 1;
end
else begin
if(set_state == 1)
hour <= 1;
else if(({cnt_h1, cnt_h0} != 0) && ({cnt_m1, cnt_m0} == 0) && flag_hour)
hour <= cnt_hour[0];
end
end
/***************秒针***************/
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sec <= 1;
end
else begin
if(set_state == 1)
sec <= 1;
else
sec <= cnt_s0[0];
end
end
endmodule
5、闹钟模块Alarm
闹钟设置与校时功能相似
将计时时间与闹钟时间比较输出闹钟信号
/***************闹钟模块***************/
module Alarm(
clk,
rst_n,
mode,
set_loc,
set_inc,
time_data,
alm_data,
alm
);
//输入输出
input clk;
input rst_n;
input [1:0] mode;
input [3:0] set_loc;
input set_inc;
input [23:0] time_data;
output [23:0] alm_data;
output alm;
wire [23:0] alm_data;
reg alm;
/***************校时***************/
reg [3:0] inc;
always@(*)begin
if((mode == 2) && set_inc)
inc = set_loc;
else
inc = 4'b0000;
end
/***************分个位***************/
reg [3:0] cnt_m0;
wire en_m0;
wire co_m0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_m0 <= 0;
end
else if(en_m0)begin
if(co_m0)
cnt_m0 <= 0;
else
cnt_m0 <= cnt_m0 + 1;
end
end
assign en_m0 = inc[0];
assign co_m0 = (en_m0) && (cnt_m0 == 9);
/***************分十位***************/
reg [3:0] cnt_m1;
wire en_m1;
wire co_m1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_m1 <= 0;
end
else if(en_m1)begin
if(co_m1)
cnt_m1 <= 0;
else
cnt_m1 <= cnt_m1 + 1;
end
end
assign en_m1 = co_m0 || inc[1];
assign co_m1 = (en_m1) && (cnt_m1 == 5);
/***************时个位***************/
reg [3:0] cnt_h0;
reg [3:0] cnt_h1;
wire en_h0;
wire co_h0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_h0 <= 0;
end
else if(en_h0)begin
if(co_h0)
cnt_h0 <= 0;
else
cnt_h0 <= cnt_h0 + 1;
end
end
assign en_h0 = co_m1 || inc[2];
assign co_h0 = (en_h0) && ((cnt_h0 == 9) || (((cnt_h1 == 2) && ((cnt_h0 == 3)))));
/***************时十位***************/
wire en_h1;
wire co_h1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_h1 <= 0;
end
else if(en_h1)begin
if(co_h1)
cnt_h1 <= 0;
else
cnt_h1 <= cnt_h1 + 1;
end
end
assign en_h1 = co_h0 || inc[3];
assign co_h1 = (en_h1) && ((((cnt_h1 == 2) && ((cnt_h0 >= 3)))) || ((cnt_h1 >= 2) && inc[3]));
assign alm_data = {cnt_h1, cnt_h0, cnt_m1, cnt_m0};
/***************输出***************/
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
alm <= 1;
end
else begin
if(set_loc != 0)
alm <= 1;
else if(time_data[23:8] == alm_data)
alm <= 0;
else
alm <= 1;
end
end
endmodule
6、显示模块Display
根据当前工作模式确定显示内容
/***************显示模块***************/
module Display(
clk,
rst_n,
mode,
time_data,
alm_data,
set_loc,
smg_sig,
smg_loc
);
//输入输出
input clk;
input rst_n;
input [1:0] mode;
input [23:0] time_data;
input [15:0] alm_data;
input [3:0] set_loc;
output [7:0] smg_sig;
output [5:0] smg_loc;
wire [7:0] smg_sig;
wire [5:0] smg_loc;
/***************取数***************/
reg [15:0] show_data;
wire [23:0] num_data;
wire [5:0] dot_data;
always@(*)begin
case(mode)
0: show_data = time_data[23:8];
1: show_data = time_data[15:0];
2: show_data = alm_data;
default: show_data = 0;
endcase
end
assign num_data = {8'b00000000, show_data};
assign dot_data = {2'b00, set_loc};
/***************数码管驱动***************/
Smg U_smg(
.clk(clk),
.rst_n(rst_n),
.num_data(num_data),
.dot_data(dot_data),
.smg_sig(smg_sig),
.smg_loc(smg_loc)
);
endmodule
7、数码管驱动模块Smg
带小数点的数码管驱动模块,可作为基本模块使用
/***************显示模块***************/
module Smg(
clk,
rst_n,
num_data,
dot_data,
smg_sig,
smg_loc
);
//参数定义
parameter CNT_2MS = 17'd99_999;
parameter CNT_200NS = 17'd9;
//输入输出
input clk;
input rst_n;
input [23:0] num_data;
input [5:0] dot_data;
output [7:0] smg_sig;
output [5:0] smg_loc;
reg [7:0] smg_sig;
reg [5:0] smg_loc;
/***************分频***************/
reg [16:0] cnt_div;
wire en_div;
wire co_div;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_div <= 0;
end
else if(en_div)begin
if(co_div)
cnt_div <= 0;
else
cnt_div <= cnt_div + 1;
end
end
assign en_div = 1;
assign co_div = (en_div) && (cnt_div == CNT_2MS);
/***************扫描***************/
reg [3:0] cnt_scan;
wire en_scan;
wire co_scan;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_scan <= 0;
end
else if(en_scan)begin
if(co_scan)
cnt_scan <= 0;
else
cnt_scan <= cnt_scan + 1;
end
end
assign en_scan = co_div;
assign co_scan = (en_scan) && (cnt_scan == 5);
/***************取数***************/
reg [3:0] smg_data;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
smg_data <= 0;
end
else begin
case(cnt_scan)
0: smg_data <= num_data[3:0];
1: smg_data <= num_data[7:4];
2: smg_data <= num_data[11:8];
3: smg_data <= num_data[15:12];
4: smg_data <= num_data[19:16];
5: smg_data <= num_data[23:20];
endcase
end
end
/***************显示***************/
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
smg_loc <= 6'b000000;
end
else begin
case(cnt_scan)
0: smg_loc <= 6'b111110;
1: smg_loc <= 6'b111101;
2: smg_loc <= 6'b111011;
3: smg_loc <= 6'b110111;
4: smg_loc <= 6'b101111;
5: smg_loc <= 6'b011111;
endcase
end
end
/***************译码***************/
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
smg_sig <= 8'b00000011;
end
else begin
case(smg_data)
0: smg_sig[7:1] = 7'b0000001;
1: smg_sig[7:1] = 7'b1001111;
2: smg_sig[7:1] = 7'b0010010;
3: smg_sig[7:1] = 7'b0000110;
4: smg_sig[7:1] = 7'b1001100;
5: smg_sig[7:1] = 7'b0100100;
6: smg_sig[7:1] = 7'b0100000;
7: smg_sig[7:1] = 7'b0001111;
8: smg_sig[7:1] = 7'b0000000;
9: smg_sig[7:1] = 7'b0000100;
default: smg_sig[7:1] = 7'b0110000;//E
endcase
smg_sig[0] <= ~dot_data[cnt_scan];
end
end
endmodule
四、测试文件
测试文件在之前的基础上进行修改
`timescale 1 ns/ 1 ns
module Digclk_tb();
reg [2:0] btn;
reg clk;
reg rst_n;
wire [2:0] led;
wire [5:0] smg_loc;
wire [7:0] smg_sig;
//模拟按键按下
task SWC;
begin
#1000 btn[2] = 0;
#1000 btn[2] = 1;
end
endtask
task MOV;
begin
#1000 btn[1] = 0;
#1000 btn[1] = 1;
end
endtask
task INC;
begin
#1000 btn[0] = 0;
#1000 btn[0] = 1;
end
endtask
Digclk u1 (
.btn(btn),
.clk(clk),
.led(led),
.rst_n(rst_n),
.smg_loc(smg_loc),
.smg_sig(smg_sig)
);
initial begin
clk = 0;
rst_n = 1;
end
always begin
#10 clk = ~clk;
end
initial begin
// Initialize Inputs
rst_n = 1;
btn = 3'b111;
// Wait 200 ns for global rst_n to finish
#100 rst_n = 0;
#100 rst_n = 1;
// Add stimulus here
//////////////////////////////////////////////闹钟测试
//闹钟置数1200,闹钟指针1000
INC;//空加
repeat(2) SWC;//2
MOV;
repeat(4) INC;//1
MOV;
repeat(2) INC;//2
//////////////////////////////////////////////校时测试
//时分置数1159
//置数模式
#10000;
SWC;
MOV;
INC;//1
MOV;
INC;//1
MOV;
repeat(5) INC;//5
MOV;
repeat(9) INC;//9
//秒置数58
SWC;
#10000;
repeat(3) MOV;
repeat(1) INC;//5
MOV;
repeat(1) INC;//8
MOV;
//////////////////////////////////////////////状态切换测试
//显示分秒
#10000;
SWC;
//显示闹钟
#10000;
SWC;
//////////////////////////////////////////////清零测试
//清零
#10000 rst_n = 0;
#10000 rst_n = 1;
//////////////////////////////////////////////进位测试
//分置数59
SWC;
MOV;
repeat(5) INC;//5
MOV;
repeat(8) INC;//8
//时置数23
repeat(2) SWC;
#10000
MOV;
repeat(5) INC;//2
MOV;
repeat(3) INC;//3
SWC;
//////////////////////////////////////////////防抖测试
#10000 btn[2] = 0;//忽略
#10 btn[2] = 1;
#30 btn[2] = 0;//触发
#1000 btn[2] = 1;
#50 btn[2] = 0;//忽略
#70 btn[2] = 1;
#1000 btn[2] = 0;//触发
#1000 btn[2] = 1;
end
endmodule
五、波形仿真
1、变量名称说明
rst_n:清零按键;
btn[2:0]:切换、移位、加一按键;
xxx_btn_f:经过消抖模块后的按键信号;
mode:工作模式(0:时分,1:分秒,2:闹钟)
time_data:计时时间;
alm_data:闹钟时间;
num_data:显示内容;
smg_loc:数码管位置;
smg_data:数码管数据;
smg_sig:数码管信号;
set_loc:操作位置;
dot_data:小数点信息;
led[2:0]:整点报时LED、闹钟LED,秒LED。
2、闹钟设置测试
将闹钟设置为12:00
3、时钟校时测试
将时钟设置为11:59:58
4、闹钟、整点报时测试
到12:00闹钟响一分钟,整点报时闪12下
5、显示内容测试
切换工作状态,分别显示时分、分秒、闹钟
6、复位测试
7、进位测试
设置计时值为23:59:23,测试数字钟进位
8、消抖测试
对按键btn[2]进行消抖测试,将其转化为swc_btn_f
总结
程序在AX530开发板上实测通过
逻辑部分做了部分改动,主要是为了方便操作
控制模块本来打算用状态机写的,但后来发现没有必要
单独写一个控制模块总感觉有些奇怪
校时的逻辑还是有些混乱,包括整点报时也可以再改改