重写EDA数字钟,尽量保持原有功能不变,修改各模块之间的关系以及模块内部逻辑,使程序更加简洁、符合电路规范。
设计功能与实现方式和上一篇保持一致:
1.具有“秒”、“分”、“时”计时的功能,小时计数器按24小时制;
2.具有校时功能,能对“分”和“时”进行调整;
3.具有手动输入设置定时闹钟的功能,亮1分钟;
4.可实现时钟复位功能:00:00:00;
5.报整点:几点亮几下。
主要有两个比较大的改动:
(1)将n进制计数器模块改为同步置零的无进位计数器;
(2)增加了消抖模块,使按扭输入更加稳定。
按键的优先级从上到下递减
移位、增加通过消抖模块输入
复位与置数按键均为异步
移位与增加按键用于校时与闹钟数据的输入
拨码用于控制显示内容(校时/闹钟/时分/分秒/不显示)
显示拨码优先级从上到下递减(不包括计时使能拨码)
由于只有四个数码管,所以时分/分秒分开显示
秒计数用一个LED显示
另用2个LED代表整点报时和定时闹钟
module Digclk(
input clk,//20ns脉冲
input rst_n,//复位按键BTN0
input set,//置数按键BTN1
input move,//移位按键BTN2
input inc,//增加按键BTN3
input set_en,//校时拨码SW7
input alm_en,//闹钟拨码SW6
input h_m_en,//时分显示拨码SW5
input m_s_en,//分秒显示拨码SW4
input cnt_en,//计时使能拨码SW3
output [3:0] loc,//数码管位置
output [7:0] pin,//数码管引脚电平
output h_alm,//整点报时LD2
output s_alm,//定时闹钟LD1
output sec);//秒计时LD0
//消抖部分
wire move_f, inc_f;//消抖后的按钮信号
Filter u0_filter(
.clk(clk),
.rst_n(rst_n),
.botton(move),
.botton_f(move_f));
Filter u1_filter(
.clk(clk),
.rst_n(rst_n),
.botton(inc),
.botton_f(inc_f));//消抖模块
//校时部分
reg [5:0] set_n;//校时信号
wire [15:0] set_num;//校时部分数据
reg [23:0] set_time;//校时整体数据
wire [3:0] set_loc;//指针位置
Set_time u0_set_time(
.rst_n(rst_n),
.en(set_en),
.move(move_f),
.inc(inc_f),
.set_num(set_num),
.set_loc(set_loc));//移位输入模块
always @(set, set_en, alm_en, h_m_en, m_s_en) begin//判断所需修改的位
if(set) set_n = 6'b000000;
else if(set_en || alm_en) set_n = 6'b000000;
else if(h_m_en) set_n = 6'b111100;
else if(m_s_en) set_n = 6'b001111;
else set_n = 6'b000000;
end
always @(h_m_en, set_num) begin//用于和计时部分连接
if(h_m_en)
set_time = {set_num, 8'b00000000};
else set_time = {8'b00000000, set_num};
end
//计时部分
wire clk_cnt;//计时时钟
wire [23:0] cnt_num;//计时数据
wire [5:0] co;//进位信号
Div_cnt u_div_cnt(
.clk(clk),
.rst_n(rst_n),
.clk_cnt(clk_cnt));//分频模块
Time_cnt u_time_cnt(
.clk(clk_cnt),
.rst_n(rst_n),
.en(cnt_en),
.set_n(set_n),
.set_num(set_time),
.cnt(cnt_num),
.co(co));//计时模块
assign sec = cnt_num % 2;
//整点报时
H_alarm u_h_alarm(
.clk(clk_cnt),
.rst_n(rst_n),
.en(co[3] & co[2] & co[1] & co[0]),//同步进位使能需要将前级所有进位相与
.num(cnt_num[23:16]),
.led_out(h_alm));//整点报时模块
//闹钟部分
wire [15:0] alm_num;//闹钟数据
wire [3:0] alm_loc;//指针位置
Set_time u1_set_time(
.rst_n(rst_n),
.en(!set_en && alm_en),
.move(move_f),
.inc(inc_f),
.set_num(alm_num),
.set_loc(alm_loc));
assign s_alm = (cnt_num[23:8] == alm_num) && cnt_en;//闹钟响一分钟
//显示部分
Scan u_scan(.clk(clk), .rst_n(rst_n), .loc(loc));//扫描模块
reg [15:0] show_num;//待取数字
reg [3:0] show_dot;//小数点代表指针位置
always @(*) begin//取数(校时/闹钟/计时)
if(set_en) begin
show_num = set_num;
show_dot = set_loc;
end
else if(alm_en) begin
show_num = alm_num;
show_dot = alm_loc;
end
else if(h_m_en) begin
show_num = cnt_num[23:8];
show_dot = 4'b0000;
end
else if(m_s_en) begin
show_num = cnt_num[15:0];
show_dot = 4'b0000;
end
else begin
show_num = 0;
show_dot = 4'b0000;
end
end
reg [4:0] con_num;//待译码数字,最后一位为小数点
always @(loc, show_num, show_dot) begin//取数(4选1)
case(loc)
4'b0111: con_num = {show_num[15:12], show_dot[3]};
4'b1011: con_num = {show_num[11:8], show_dot[2]};
4'b1101: con_num = {show_num[7:4], show_dot[1]};
4'b1110: con_num = {show_num[3:0], show_dot[0]};
default: con_num = 5'bxxxxx;//避免锁存
endcase
end
Convert u_convert(.num(con_num), .pin(pin));//译码模块
endmodule
按下按键(下跳沿)一段时间后发送一个时钟周期的负脉冲
此期间不接收按键信号
延时500ms
counter_end = 50M * 延时时间 / 2 - 1
module Filter(
input clk,
input rst_n,
input botton,
output botton_f);
//parameter CNT_END = 12 499 999;//下载时延时500ms
parameter CNT_END = 4;//仿真时延时100ns
reg [23:0] counter;
reg dly1, dly2;//延时标志位
//dly1输出
always @(posedge clk, negedge rst_n) begin
if(!rst_n) dly1 <= 0;
else if(!botton) dly1 <= 1;
else if(counter >= CNT_END) dly1 <= 0;
end
//延时计数
always @(posedge clk, negedge rst_n) begin
if(!rst_n) counter <= 0;
else if(dly1) counter <= counter + 1;
else counter <= 0;
end
//dly2输出
always @(posedge clk, negedge rst_n) begin
if(!rst_n) dly2 <= 0;
else dly2 <= dly1;
end
assign botton_f = dly1 || !dly2;//控制输出一个时钟的负脉冲
endmodule
由4个无进位计数器子模块储存输入内容
set_loc用于记录操作哪一位计数器
set_num用于记录校时的值
apply按键每按下一次,指针向右移一位;
inc按键每按下一次,当前计数器获得一个cp,即值加1
module Set_time(
input rst_n,
input en,
input move,
input inc,
output [15:0] set_num,
output [3:0] set_loc);
reg [3:0]loc;//指针位置
//循环移位,时序逻辑
always @(negedge rst_n, negedge move) begin
if(!rst_n) loc <= 4'b1000;
else if(!move && en) loc <= {loc[0], loc[3:1]};
end
assign set_loc = loc;
//计数器子模块
Count u0_count(
.clk(inc),
.rst_n(rst_n),
.en(loc[0] && en),
.set_0(set_num[3:0]==9),
.set_n(1'b0),
.set_num(4'b0000),
.cnt(set_num[3:0]));
Count u1_count(
.clk(inc),
.rst_n(rst_n),
.en(loc[1] && en),
.set_0(set_num[7:4]==9),
.set_n(1'b0),
.set_num(4'b0000),
.cnt(set_num[7:4]));
Count u2_count(
.clk(inc),
.rst_n(rst_n),
.en(loc[2] && en),
.set_0(set_num[11:8]==9),
.set_n(1'b0),
.set_num(4'b0000),
.cnt(set_num[11:8]));
Count u3_count(
.clk(inc),
.rst_n(rst_n),
.en(loc[3] && en),
.set_0(set_num[15:12]==9),
.set_n(1'b0),
.set_num(4'b0000),
.cnt(set_num[15:12]));
endmodule
16进制计数器
同步置零在外部电路完成
包含异步置数功能
module Count(
input clk,//时钟
input rst_n,//异步复位
input en,//使能
input set_0,//同步置0
input set_n,//异步置数
input [3:0] set_num,//置数内容
output reg [3:0] cnt);//计数值
always @(posedge clk, negedge rst_n, posedge set_n) begin
if(!rst_n) cnt <= 0;
else if(set_n) cnt <= set_num;
else if(set_0 && en) cnt <= 0;
else if(en) cnt <= cnt + 1;
end
endmodule
系统时钟50MHz
仿真时输出1MHz
下载时输出1Hz
上升沿触发计数,保证reset之后的第一次计数
CNT_END=50M/(2x计数频率)
module Div_cnt (
input clk,
input rst_n,
output reg clk_cnt);
reg [24:0] counter;
parameter [24:0] CNT_END = 24;//仿真时计数25
//parameter [24:0] CNT_END = 24 999 999;//下载时计数25M
always @(posedge clk, negedge rst_n) begin
if (!rst_n) begin
counter <= 0;
clk_cnt <= 1;//上升沿触发计数
end
else if (counter == CNT_END) begin
counter <= 0;
clk_cnt <= !clk_cnt;
end
else counter <= counter + 1;
end
endmodule
6个无进位计数器表示时、分、秒
计数器进位通过外部连线完成
含异步清零、置数功能
为防止校时值超过进制,进位信号用>=代替==
module Time_cnt(
input clk,
input rst_n,
input en,
input [5:0] set_n,
input [23:0] set_num,
output [23:0] cnt,
output [5:0] co);
wire [5:0] enable;
//计数器子模块
//秒个位
Count u0_count(
.clk(clk),
.rst_n(rst_n),
.en(enable[0]),
.set_0(co[0]),
.set_n(set_n[0]),
.set_num(set_num[3:0]),
.cnt(cnt[3:0]));
assign enable[0] = en;
assign co[0] = (cnt[3:0] >= 9);
//秒十位
Count u1_count(
.clk(clk),
.rst_n(rst_n),
.en(enable[1]),
.set_0(co[1]),
.set_n(set_n[1]),
.set_num(set_num[7:4]),
.cnt(cnt[7:4]));
assign enable[1] = enable[0] && co[0];
assign co[1] = (cnt[7:4] >= 5);
//分个位
Count u2_count(
.clk(clk),
.rst_n(rst_n),
.en(enable[2]),
.set_0(co[2]),
.set_n(set_n[2]),
.set_num(set_num[11:8]),
.cnt(cnt[11:8]));
assign enable[2] = enable[1] && co[1];
assign co[2] = (cnt[11:8] >= 9);
//分十位
Count u3_count(
.clk(clk),
.rst_n(rst_n),
.en(enable[3]),
.set_0(co[3]),
.set_n(set_n[3]),
.set_num(set_num[15:12]),
.cnt(cnt[15:12]));
assign enable[3] = enable[2] && co[2];
assign co[3] = (cnt[15:12] >= 5);
//时个位
Count u4_count(
.clk(clk),
.rst_n(rst_n),
.en(enable[4]),
.set_0(co[4]),
.set_n(set_n[4]),
.set_num(set_num[19:16]),
.cnt(cnt[19:16]));
assign enable[4] = enable[3] && co[3];
assign co[4] = (cnt[19:16] >= 9) || co[5];
//时十位
Count u5_count(
.clk(clk),
.rst_n(rst_n),
.en(enable[5]),
.set_0(co[5]),
.set_n(set_n[5]),
.set_num(set_num[23:20]),
.cnt(cnt[23:20]));
assign enable[5] = enable[4] && co[4];
assign co[5] = (cnt[23:20] >= 2) && (cnt[19:16] >= 3);
endmodule
几点LED闪几下
0点闪24下
共阳极LED,低电平点亮
参考消抖模块,24时flag在第47次后拉低,而counter会计到48
cnt_end = 2 * (num[7:4] * 10 + num[3:0] + 1) - 1 - 1;
module H_alarm(
input clk,
input rst_n,
input en,
input [7:0] num,
output led_out);
reg [5:0] counter;//最多计到47
reg [5:0] cnt_end;//将BCD码转为十进制
reg flag;//运行标志位
always @(posedge clk, negedge rst_n) begin//参考消抖模块
if(!rst_n) begin
flag <= 0;
cnt_end <= 0;
end
else if(en) begin
flag <= 1;
cnt_end <= 2 * (num[7:4] * 10 + num[3:0]);
end
else if(counter >= cnt_end) begin
flag <= 0;
cnt_end <= 0;
end
end
always @(posedge clk, negedge rst_n) begin
if(!rst_n) counter <= 0;
else if(flag) counter <= counter + 1;
else counter <= 0;
end
assign led_out = counter % 2;
endmodule
扫描分频,系统时钟50MHz
仿真时2.5MHz(每计两次数扫描5轮)
下载时100Hz(扫描一轮100Hz)
数码管选择,为0表示被选中
电平触发扫描
CNT_END=50M/(扫描数x扫描频率)-1
module Scan(
input clk,
input rst_n,
output reg [3:0] loc);
//扫描分频
reg [16:0] counter = 0;//最多计到124 999
reg [1:0] flag = 0;//选择标志
parameter [16:0] CNT_END = 4;//仿真时计数5
//parameter [16:0] CNT_END = 124 999;//下载时计数125 000
always @(posedge clk, negedge rst_n) begin
if (!rst_n) begin
counter <= 0;
flag <= 0;
end
if (counter == CNT_END) begin
counter <= 0;
flag <= flag + 1;
end
else counter <= counter + 1;
end
//数码管选择
always @(flag) begin
case(flag)
0: loc = 4'b1110;
1: loc = 4'b1101;
2: loc = 4'b1011;
3: loc = 4'b0111;
default loc = 4'bxxxx;
endcase
end
endmodule
将取到的数转换为数码管对应的电平
组合逻辑
低电平有效
module Convert(
input [4:0] num,
output reg [7:0] pin);
always @(num) begin
case(num[4:1])
0: pin[7:1] = 8'b0000001;
1: pin[7:1] = 8'b1001111;
2: pin[7:1] = 8'b0010010;
3: pin[7:1] = 8'b0000110;
4: pin[7:1] = 8'b1001100;
5: pin[7:1] = 8'b0100100;
6: pin[7:1] = 8'b0100000;
7: pin[7:1] = 8'b0001111;
8: pin[7:1] = 8'b0000000;
9: pin[7:1] = 8'b0000100;
default: pin = 8'b0110000;//其他状态显示E
endcase
pin[0] = ~num[0];//小数点
end
endmodule
采用function与repeat组合的方式模拟按键,简化了激励的施加。
`timescale 1ns/1ns
module Test_Top;
// Inputs
reg clk;
reg rst_n;
reg set;
reg move;
reg inc;
reg set_en;
reg alm_en;
reg h_m_en;
reg m_s_en;
reg cnt_en;
// Outputs
wire [3:0] loc;
wire [7:0] pin;
wire h_alm;
wire s_alm;
wire sec;
//模拟按键按下
task SET;
begin
#200 set = 0;
#50 set = 1;
end
endtask
task MOVE;
begin
#200 move = 0;
#50 move = 1;
end
endtask
task INC;
begin
#200 inc = 0;
#50 inc = 1;
end
endtask
// Instantiate the Unit Under Test (UUT)
Digclk uut (
.clk(clk),
.rst_n(rst_n),
.set(set),
.move(move),
.inc(inc),
.set_en(set_en),
.alm_en(alm_en),
.h_m_en(h_m_en),
.m_s_en(m_s_en),
.cnt_en(cnt_en),
.loc(loc),
.pin(pin),
.h_alm(h_alm),
.s_alm(s_alm),
.sec(sec)
);
//时钟周期20ns
parameter PERIOD = 20;
//时钟信号
initial clk = 0;
always #(PERIOD/2) clk = ~clk;
//初始化
initial begin
// Initialize Inputs
rst_n = 1;
set = 1;
move = 1;
inc = 1;
set_en = 0;
alm_en = 0;
h_m_en = 0;
m_s_en = 0;
cnt_en = 0;
// Wait 200 ns for global rst_n to finish
#100 rst_n = 0;
#100 rst_n = 1;
// Add stimulus here
//////////////////////////////////////////////使能拨码测试
//开始计数
#100 cnt_en = 1;
//显示分秒
#1000 m_s_en = 1;
//显示时分
#1000 h_m_en = 1;
//闹钟模式
#100 alm_en = 1;
//////////////////////////////////////////////闹钟测试
//闹钟置数1200,闹钟指针1000
INC;//1
MOVE;
repeat(2) INC;//2
//////////////////////////////////////////////校时测试
//时分置数1100,校时指针1000
//置数模式
#100 set_en = 1;
INC;//1
MOVE;
INC;//1
#100 set_en = 0;
#100 alm_en = 0;
SET;
//分秒置数5949,校时指针0100
#100 set_en = 1;
repeat(3) MOVE;
repeat(4) INC;//5
MOVE;
repeat(8) INC;//9
MOVE;
repeat(4) INC;//4
MOVE;
repeat(9) INC;//9
#100 set_en = 0;
#100 h_m_en = 0;
SET;
//////////////////////////////////////////////计时使能拨码测试
//停止计时
#100000 cnt_en = 0;
//////////////////////////////////////////////清零测试
//清零
#1000 rst_n = 0;
#100 rst_n = 1;
//////////////////////////////////////////////进位测试
//分秒置数5949,校时指针1000
#100 set_en = 1;
repeat(5) INC;//5
MOVE;
repeat(9) INC;//9
MOVE;
repeat(4) INC;//4
MOVE;
repeat(9) INC;//9
#100 set_en = 0;
SET;
//时分置数2359,校时指针0001
#100 set_en = 1;
MOVE;
repeat(7) INC;//2
MOVE;
repeat(4) INC;//3
MOVE;
INC;//5
#100 set_en = 0;
#100 m_s_en = 0;
INC;//空加(当set_en为0时,inc按键不应起作用)
SET;//空置数(当h_m_en与m_s_en为0时,set按键不应起作用)
#100 h_m_en = 1;
SET;//时分置数
//恢复计时
#100 cnt_en = 1;
//////////////////////////////////////////////防抖测试
#100 set_en = 1;//校时指针0010
#100 inc = 0;//触发
#10 inc = 1;
#30 inc = 0;//忽略
#50 inc = 1;
#70 inc = 0;//触发
#90 inc = 1;
end
endmodule
变量名、测试内容与上一篇基本相似,不进行过多叙述。
1、变量名称说明
变量名称有所不同,但位置保持完全一致。
2、使能拨码测试
具体解释见上一篇,下同。
3、闹钟置数测试
此处闹钟直接置数,不需要SET,和上一篇有所不同(因为我重写的时候忘了原来的逻辑)。
4、时钟置数测试
注意:FPGA的按键是低电平有效(虽然我不知道原来的程序为什么也能用)。
5、清零测试
6、计时使能拨码测试
7、进位测试
8、防抖测试
修改了部分的变量名称,但顶层模块的输入和输出基本保持不变,由于程序只进行了简单的仿真,不排除在实际使用中可能会出现问题。
I might be just begining,
I might be near the end.