用FPGA设计一个数字闹钟应该说是一个比较综合的小系统,包含了按键、数码
管、状态机等内容,本文主要是讲述三键输入的数字闹钟总体的设计,整个作品和小时候两三块一个的电子手表十分类似
功能描述
1 用四个数码管正常显示时、分高低位,实现正常显示时间。
2 具备调分调时功能
3 闹钟功能
功能并不复杂,我们现在来分析一下需要哪些模块。
首先是时钟功能,正常显示时间肯定是必须的,为实现这一可以设计一个60进制计数器和一个24进制计数器,当然也可以根据逻辑直接书写出来,但无论是什么办法,这肯定需要一个模块来实现它。
第二就是调分调时了,说白了就是置数,要置数,那么就必须有一个区域去控制数据,也需要一个地方存储数据,然后将置数的数据传给时钟,所以它应该与键盘的联系,内部有存储器。
第三是闹钟,闹钟不难想,比较器,我设定闹钟时间,然后与时钟的时间比较,如果两者相同,输出闹钟信号,就是如此。
最后的便是显示电路,主要是数码管的显示原理,驱动数码管显示时间。
就是这几样部分,貌似这么一说,确实没有什么东西,一个系统就是需要输入和输出相互协调好,这里面的逻辑必须是相互对应的,不出矛盾,个人认为,这是设计的难度所在。
整体设计图
模块讲解
键盘模块(key)
输入 | 功能说明 | 输出 | 功能说明 |
---|---|---|---|
add_in | 加 | c_add | 时钟加 |
aub_in | 减 | c_sub | 时钟减 |
model_in | 模式控制端 | a_hour | 调闹钟小时 |
clk | 时钟 | a_minute | 调闹钟分钟 |
rst_n | 复位 | cnt_control | 计数器开关 |
Display_Model | 显示控制 | ||
Time_model | 调时钟转换信号 |
细节讲解
model:模式的选择寄存器
整个闹钟系统我设置为五个模式,所以model需要是三位的[2:0]
00:时钟正常走时
01:时钟调分模式,该模式下时钟的计数器停止,时钟是不走的,同时显示模式也会转到调时钟模式。
10:时钟调时模式,与调分模式类似。
11:闹钟调分设置模式,此时时钟走时,显示模式为闹钟模式。
100:闹钟调时模式,与调分时类似。
cnt_control:计数器开关
正常走时和调闹钟模式下,计数器开,cnt_control = 0;
当进入调分和调时模式,计数器关闭,cnt_control = 1。
Time_Model:调时钟转换信号
这个是连接时钟模块(clock)的,是调分模式和调时模式的切换信号。
Display_Model:显示控制
正常走时,进入调分和调时模式时,停止走时,整个过程我设置为同一种显示模式;
闹钟模式下,显示模式转换;
所以一共是两种模式,一根线足以。
代码展示
module key(
input clk,
input rst_n,
input add_in,//增加按钮
input sub_in,//减去按钮
input model_in,//模式选择
output reg Display_Model,
//时钟与闹钟显示选择,0 代表时钟,1代表闹钟
output reg cnt_control, //控制定时器
output reg [2:0]Time_model,
output reg c_add, //控制时钟 加
output reg c_sub, //控制时钟 减
output reg a_add, //控制闹钟 加
output reg a_sub //控制闹钟 减
);
/************************************/
parameter T40MS = 20'd40_000;
parameter T1S = 30'd1_000_000;
/************************************/
/// add_in 按键 /////
reg add;
reg [19:0]cnt_a;
reg [1:0]a;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt_a <= 0;
a <= 0;
add <= 1;
end
else
begin
case(a)
0:begin
if(cnt_a < T40MS)
// 按下时间大于40MS 认为有效
begin
if(!add_in)
cnt_a = cnt_a+1'b1;
else
cnt_a = 0;
end
else //counter> 40MS ,说明确实按键是按下了
begin
add = 0;
// 给冲击信号 ,0~1 是上升沿
a = 1;
//确定按键按下,转到状态 1
cnt_a = 0; //计数器清零
end
end
1:begin
add = 1; //产生尖脉冲
if(cnt_a < T40MS)
// 按下时间大于40MS 松开有效
begin
if(add_in)
cnt_a = cnt_a+1'b1;
else
cnt_a = 0;
end
else
begin
a = 0;
// 若松开,回到状态 0 ,等待下次按键到来
cnt_a = 0;
end
end
default : a = 1;
endcase
end
end
//////////////////////////////////////////////////////////
/// sub_in 按键 ///
reg sub;
reg [19:0]cnt_s;
reg [1:0]s;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt_s <= 0;
s <= 0;
sub <= 1;
end
else
begin
case(s)
0:begin
if(cnt_s < T40MS)
// 按下时间大于40MS 认为有效
begin
if(!sub_in)
cnt_s = cnt_s+1'b1;
else
cnt_s = 0;
end
else //counter> 40MS ,说明确实按键是按下了
begin
sub = 0;
// 给冲击信号 ,0~1 是上升沿
s = 1;
//确定按键按下,转到状态 1
cnt_s = 0; //计数器清零
end
end
1:begin
sub = 1; //产生尖脉冲
if(cnt_s < T40MS)
// 按下时间大于40MS 松开有效
begin
if(sub_in)
cnt_s = cnt_s+1'b1;
else
cnt_s = 0;
end
else
begin
s = 0;
// 若松开,回到状态 0 ,等待下次按键到来
cnt_s = 0;
end
end
default : s = 1;
endcase
end
end
////////////////////////////////////////////////////////////
/// model_in 按键 ///
reg model;
reg [19:0]cnt_m;
reg [1:0]m;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt_m <= 0;
m <= 0;
model <= 1;
end
else
begin
case(m)
0:begin
if(cnt_m < T40MS)
// 按下时间大于40MS 认为有效
begin
if(!model_in)
cnt_m = cnt_m+1'b1;
else
cnt_m = 0;
end
else //counter> 40MS ,说明确实按键是按下了
begin
model = 0;
// 给冲击信号 ,0~1 是上升沿
m = 1;
//确定按键按下,转到状态 1
cnt_m = 0; //计数器清零
end
end
1:begin
model = 1; //产生尖脉冲
if(cnt_m < T40MS)
// 按下时间大于40MS 松开有效
begin
if(model_in)
cnt_m = cnt_m+1'b1;
else
cnt_m = 0;
end
else
begin
m = 0;
// 若松开,回到状态 0 ,等待下次按键到来
cnt_m = 0;
end
end
default : m = 1;
endcase
end
end
////////////////////////////////////////////////////////////
/************************************************/
reg [2:0]type;
//00:时钟正常跑模式
//01:时钟调分模式,在该模式时间计数器停止计数
//10: 时钟调时模式,在该模式时间计数器停止计数
//11:闹钟调分模式,在该模式时间计数器正常计数
//100:闹钟调时模式,在该模式时间计数器正常计数
/************************************************/
always @(posedge clk or negedge rst_n)
if(!rst_n)
begin
Display_Model <= 1'b0;
a_add <= 1'b0;
a_sub <= 1'b0;
c_add <= 1'b0;
c_sub <= 1'b0;
Time_model <= 3'b000;
type <= 3'b000;
cnt_control <= 1'b1;//启动计数
end
else
begin
if(!model)
begin
if(type == 3'b100)
type = 3'b000;
else
begin
type = type + 1'b1;
end
end
case(type)
//时钟正常开始跑
3'b000:
begin
Time_model <= 3'b000;
cnt_control <= 1'b1;//启动计数
Display_Model <= 1'b0;
a_add <= 1'b0;
a_sub <= 1'b0;
c_add <= 1'b0;
c_sub <= 1'b0;
end
//调分模式
3'b001:
begin
cnt_control <= 1'b0; //关闭计数
Time_model <= 3'b001;
Display_Model <= 1'b0;
if(!add)//加
begin
c_add <=1'b1 ;
end
else
begin
c_add <= 1'b0;
end
if(!sub)//减
begin
c_sub <= 1'b1;
end
else
begin
c_sub <= 1'b0;
end
end
//调时模式
3'b010:
begin
cnt_control <= 1'b0;//关闭计数
Time_model <= 2'b010;
Display_Model <= 1'b0;
if(!add)//加
begin
c_add <=1'b1 ;
end
else
begin
c_add <= 1'b0;
end
if(!sub)//减
begin
c_sub <= 1'b1;
end
else
begin
c_sub <= 1'b0;
end
end
//调分模式
3'b011:
begin
cnt_control <= 1'b1; //kaijishu
Time_model <= 3'b011;
Display_Model <= 1'b1;
if(!add)//加
begin
a_add <=1'b1 ;
end
else
begin
a_add <= 1'b0;
end
if(!sub)//减
begin
a_sub <= 1'b1;
end
else
begin
a_sub <= 1'b0;
end
end
//调时模式
3'b100:
begin
cnt_control <= 1'b1;//关闭计数
Time_model <= 3'b100;
Display_Model <= 1'b1;
if(!add)//加
begin
a_add <=1'b1 ;
end
else
begin
a_add <= 1'b0;
end
if(!sub)//减
begin
a_sub <= 1'b1;
end
else
begin
a_sub <= 1'b0;
end
end
default:type <= 3'b000;
endcase
end
endmodule
</div>
时钟模块(clock)
输入 | 功能说明 | 输出 | 功能说明 |
---|---|---|---|
c_add | 加 | hour_h | 小时高位 |
c_sub | 减 | hour_l | 小时低位 |
Time_model | 模式控制端 | minute_h | 分钟高位 |
cot_control | 计数器开关 | minute_l | 分钟低位 |
clk | 时钟 | ||
rst_n | 复位 |
细节讲解
时钟模块的输入都是来自键盘模块,所以输入不多说了,提一点,我的板子上的输入的时钟(clk),用的是50MHz晶振,而时钟是1Hz,也就是一秒走一下,所以内部必须分频,代码里注意看一下,我为了方便调试,分频并不是分频到1Hz,如果你有需要用到,修改里面的参数就好。
模块输出
模块的输出就是时钟的数据,是传给显示模块去驱动数码管显示的。
小时高位,0~2,三种情况,两根线[1:0]
小时低位,0~0,十种情况,四根线[3:0]
分钟高位,0~5,六种情况,三根线[2:0]
分钟低位,0~9,十种情况,四根线[3:0]
代码展示
module clock(
input clk,
input rst_n,
input cnt_control, //控制定时器 ,1启动 ,0停止
input c_add, //控制 加
input c_sub, //控制 减
input [2:0]Time_model,
output [1:0]hour_h, //小时 十位
output [3:0]hour_l, //小时 个位
output [2:0]minute_h, //分钟 十位
output [3:0]minute_l //分钟 个位
);
parameter S= 100000000;
parameter M=60;
/*********************************************************/
//1S计数器
reg [31:0] cnt1s;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt1s <= 15'd0;
else if((cnt1s == S) || (!cnt_control))
cnt1s <= 15'd0;
else if(cnt_control)
cnt1s <= cnt1s + 1'b1;
/*********************************************************/
///////////////////////////////////////////////////////////
// 功能控制 //
//////////////////////////////////////////////////////////
reg [1:0]flag;//用来标志reg1是否到了2,到了2,reg2只能加到4
reg [1:0] reg1;//时的第一位:0~2
reg [3:0] reg2;
//时的第二位:当第一位为0和1时,可以是0~9,当第一位为2时,只能是0~9,
reg [2:0] reg3;//分的第一位:只能是0~5
reg [3:0] reg4;//分的第二位:是0~9
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
reg1 <= 2'd0; //小时 高位
reg2 <= 4'd0; //小时 低位
reg3 <= 3'd0; //分钟 高位
reg4 <= 4'd0; //分钟 低位
flag <= 2'd0;
end
else
case(Time_model)
//时钟正常开始跑
3'b000:
begin
if(cnt1s == S) //一分钟到了
begin
reg4 <= reg4 + 1'b1;
if(reg4 == 4'd9)
begin
reg4 <= 4'd0;
reg3 <= reg3 + 1'b1;
if(reg3 == 3'd5)
begin
reg3 <= 3'd0;
if(reg1 == 2'd2)
begin
reg2 <= reg2 + 1'b1;
if(reg2 == 4'd3)
begin
reg2 <= 4'd0;
reg1 <= 2'd0;
end
end
else
begin
reg2 <= reg2 + 1'b1;
if(reg2 == 4'd9)
begin
reg2 <= 4'd0;
reg1 <= reg1 + 1'b1;
end
end
end
end
end
end
//调分模式
3'b001:
begin
if(c_add)//加
begin
reg4 <= reg4 + 1'b1;
if(reg4 == 4'd9)
begin
reg4 <= 4'd0;
reg3 <= reg3 + 1'b1;
if(reg3 > 3'd5)
reg3 <= 3'd0;
end
end
else if(c_sub)//减
begin
reg4 <= reg4 - 1'b1;
if(reg4 == 4'd0)
begin
reg4 <= 4'd9;
reg3 <= reg3 - 3'd1;
if(reg3 == 3'd0)
reg3 <= 3'd5;
end
end
end
//调时模式
3'b010:
begin
if(c_add)//加
begin
if(flag == 2'd2)
begin
reg2 <= reg2 + 1'b1;
if(reg2 >= 4'd3)
begin
reg2 <= 4'd0;
reg1 <= 2'd0;
flag <= 2'd0;
end
end
else
begin
reg2 <= reg2 + 1'b1;
if(reg2 == 4'd9)
begin
flag <= flag + 1'b1;
reg2 <= 4'd0;
reg1 <= reg1 + 1'b1;
end
end
end
else if(c_sub)//减
begin
if(flag == 2'd0)
begin
reg2 <= reg2 - 1'b1;
if(reg2 == 4'd0)
begin
reg2 <= 4'd3;
reg1 <= 2'd2;
flag <= 2'd2;
end
end
else
begin
reg2 <= reg2 - 1'b1;
if(reg2 == 4'd0)
begin
flag <= flag - 1'b1;
reg1 <= reg1 - 1'b1;
reg2 <= 4'd9;
end
end
end
end
endcase
end
/************************************************/
assign hour_h = reg1;
assign hour_l = reg2;
assign minute_h = reg3;
assign minute_l = reg4;
/************************************************/
endmodule
闹钟模块(alarm)
输入 | 功能说明 | 输出 | 功能说明 |
---|---|---|---|
a_hour | 闹钟小时控制 | a_hour_h | 闹钟小时高位 |
a_minute | 闹钟分钟控制 | a_hour_l | 闹钟小时低位 |
clk | 时钟 | a_minute_h | 闹钟分钟高位 |
rst_n | 复位 | a_minute_l | 闹钟分钟低位 |
细节讲解
闹钟模块与时钟模块的其实没什么区别,就是在调闹钟时,时钟正常运行,时钟调分,调时都会停止运行,也就是计数器不计数,其他的区别不大。
代码展示
<button class="testButton" onclick="document.all.child3.style.display=(document.all.child3.style.display =='none')?'':'none'">点击显\隐代码</button>
<div id="child3" style="display:">
module alarm(
input clk,
input rst_n,
input c_add, //控制 加
input c_sub, //控制 减
input [2:0]Time_model,
output [1:0]a_hour_h, //闹钟小时 十位
output [3:0]a_hour_l, //闹钟小时 个位
output [2:0]a_minute_h, //闹钟分钟 十位
output [3:0]a_minute_l //闹钟分钟 个位
);
//////////////////////////////////////////////////////////
// 功能控制 //
////////////////////////////////////////////////////////
reg [1:0]flag;//用来标志reg1是否到了2,到了2,reg2只能加到4
reg [1:0] reg1;//时的第一位:0~2
reg [3:0] reg2;
//时的第二位:当第一位为0和1时,可以是0~9,当第一位为2时,只能是0~9,
reg [2:0] reg3;//分的第一位:只能是0~5
reg [3:0] reg4;//分的第二位:是0~9
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
reg1 <= 2'd0; //闹钟小时 高位
reg2 <= 4'd0; //闹钟小时 低位
reg3 <= 3'd0; //闹钟分钟 高位
reg4 <= 4'd0; //闹钟分钟 低位
flag <= 2'd0;
end
else
case(Time_model)
//闹钟调分模式
3'b011:
begin
if(c_add)//加
begin
reg4 <= reg4 + 1'b1;
if(reg4 == 4'd9)
begin
reg4 <= 4'd0;
reg3 <= reg3 + 1'b1;
if(reg3 > 3'd4)
reg3 <= 3'd0;
end
end
else if(c_sub)//减
begin
reg4 <= reg4 - 1'b1;
if(reg4 == 4'd0)
begin
reg4 <= 4'd9;
reg3 <= reg3 - 3'd1;
if(reg3 == 3'd0)
reg3 <= 3'd5;
end
end
end
//闹钟调时模式
3'b100:
begin
if(c_add)//加
begin
if(flag == 2'd2)
begin
reg2 <= reg2 + 1'b1;
if(reg2 >= 4'd3)
begin
reg2 <= 4'd0;
reg1 <= 2'd0;
flag <= 2'd0;
end
end
else
begin
reg2 <= reg2 + 1'b1;
if(reg2 == 4'd9)
begin
flag <= flag + 1'b1;
reg2 <= 4'd0;
reg1 <= reg1 + 1'b1;
end
end
end
else if(c_sub)//减
begin
if(flag == 2'd0)
begin
reg2 <= reg2 - 1'b1;
if(reg2 == 4'd0)
begin
reg2 <= 4'd3;
reg1 <= 2'd2;
flag <= 2'd2;
end
end
else
begin
reg2 <= reg2 - 1'b1;
if(reg2 == 4'd0)
begin
flag <= flag - 1'b1;
reg1 <= reg1 - 1'b1;
reg2 <= 4'd9;
end
end
end
end
endcase
end
/************************************************/
assign a_hour_h = reg1;
assign a_hour_l = reg2;
assign a_minute_h = reg3;
assign a_minute_l = reg4;
/************************************************/
endmodule
显示模块(display)
输入 | 功能说明 | 输出 | 功能说明 |
---|---|---|---|
a_hour_h | 闹钟小时高位 | data | 段选信号 |
a_hour_l | 闹钟小时低位 | select_wei | 位选信号 |
a_minute_h | 闹钟分钟高位 | beep | 蜂鸣器 |
a_minute_l | 闹钟分钟低位 | ||
hour_h | 时钟小时高位 | ||
hour_l | 时钟小时低位 | ||
minute_h | 时钟分钟高位 | ||
minute_l | 时钟分钟低位 | ||
Display_Model | 显示模式控制 | ||
clk | 时钟 | ||
rst_n | 复位 |
显示模块的输入有三种类型:
- 闹钟的数据
- 时钟的数据
- 显示模式控制信号
输出也只有三个
- data是八段数码管的段选信号,由于我没用小数点,所以七根线
- select_wei数码管位选,四个数码管,四根线
- beep链接蜂鸣器的,一根线。
细节讲解
显示模块里用到的主要是数码管动态扫描的知识,扫描频率如何设置,这些问题,查一下数码管的是如何显示就知道了,抓住段选和位选,还有动态扫描的原理,然后分清楚你使用的数码管是共阴还是共阳,这个显示模块就很好理解了。
代码展示
module display(
input clk,
input rst_n,
//时钟模式 双段选数据
input [1:0] hour_h,
input [3:0] hour_l,
input [2:0] minute_h,
input [3:0] minute_l,
//闹钟模式 段选数据
input [1:0] a_hour_h,
input [3:0] a_hour_l,
input [2:0] a_minute_h,
input [3:0] a_minute_l,
input Display_Model,//0:时钟模式,1:秒表模式
output [6:0] data,//数码管段选
output reg[3:0] select_wei, //数码管位选
output reg alarm_out
);
/***********************************/
parameter
SEG_0 = 7'h7e,//c0,
SEG_1 = 7'h30,//f9,
SEG_2 = 7'h6d,//a4,
SEG_3 = 7'h79,//b0,
SEG_4 = 7'h33,//99,
SEG_5 = 7'h5b,//92,
SEG_6 = 7'h5f,//82,
SEG_7 = 7'h70,//F8,
SEG_8 = 7'h7f,//80,
SEG_9 = 7'h7b;//90,
/***********************************/
//时钟数据编码
/***********************************/
wire [6:0]c_data1;
wire [6:0]c_data2;
wire [6:0]c_data3;
wire [6:0]c_data4;
//数码管一要显示的列表数据(0~2)
reg [6:0] data1_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data1_temp <= SEG_0;
else
case(hour_h)
2'd0: data1_temp <= SEG_0;
2'd1: data1_temp <= SEG_1;
2'd2: data1_temp <= SEG_2;
default: data1_temp <= SEG_0;
endcase
/***********************************/
//数码管二要显示的列表数据(0~9)
reg [6:0] data2_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data2_temp <= SEG_0;
else
case(hour_l)
4'd0: data2_temp <= SEG_0;
4'd1: data2_temp <= SEG_1;
4'd2: data2_temp <= SEG_2;
4'd3: data2_temp <= SEG_3;
4'd4: data2_temp <= SEG_4;
4'd5: data2_temp <= SEG_5;
4'd6: data2_temp <= SEG_6;
4'd7: data2_temp <= SEG_7;
4'd8: data2_temp <= SEG_8;
4'd9: data2_temp <= SEG_9;
default: data2_temp <= SEG_0;
endcase
/***********************************/
//数码管三要显示的列表数据 (0~5)
reg [6:0] data3_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data3_temp <= SEG_0;
else
case(minute_h)
3'd0: data3_temp <= SEG_0;
3'd1: data3_temp <= SEG_1;
3'd2: data3_temp <= SEG_2;
3'd3: data3_temp <= SEG_3;
3'd4: data3_temp <= SEG_4;
3'd5: data3_temp <= SEG_5;
default: data3_temp <= SEG_0;
endcase
/***********************************/
//数码管四要显示的列表数据(1~9)
reg [6:0] data4_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data4_temp <= SEG_0;
else
case(minute_l)
4'd0: data4_temp <= SEG_0;
4'd1: data4_temp <= SEG_1;
4'd2: data4_temp <= SEG_2;
4'd3: data4_temp <= SEG_3;
4'd4: data4_temp <= SEG_4;
4'd5: data4_temp <= SEG_5;
4'd6: data4_temp <= SEG_6;
4'd7: data4_temp <= SEG_7;
4'd8: data4_temp <= SEG_8;
4'd9: data4_temp <= SEG_9;
default: data4_temp <= SEG_0;
endcase
/*****************************************/
assign c_data1 = data1_temp;
assign c_data2 = data2_temp;
assign c_data3 = data3_temp;
assign c_data4 = data4_temp;
/*****************************************/
/***********************************/
//闹钟数据编码
/***********************************/
wire [6:0]a_data1;
wire [6:0]a_data2;
wire [6:0]a_data3;
wire [6:0]a_data4;
//数码管一要显示的列表数据(0~5)
reg [6:0] a_data1_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
a_data1_temp <= SEG_0;
else
case(a_hour_h)
3'd0: a_data1_temp <= SEG_0;
3'd1: a_data1_temp <= SEG_1;
3'd2: a_data1_temp <= SEG_2;
3'd3: a_data1_temp <= SEG_3;
3'd4: a_data1_temp <= SEG_4;
3'd5: a_data1_temp <= SEG_5;
default: a_data1_temp <= SEG_0;
endcase
/***********************************/
//数码管二要显示的列表数据(0~9)
reg [6:0] a_data2_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
a_data2_temp <= SEG_0;
else
case(a_hour_l)
4'd0: a_data2_temp <= SEG_0;
4'd1: a_data2_temp <= SEG_1;
4'd2: a_data2_temp <= SEG_2;
4'd3: a_data2_temp <= SEG_3;
4'd4: a_data2_temp <= SEG_4;
4'd5: a_data2_temp <= SEG_5;
4'd6: a_data2_temp <= SEG_6;
4'd7: a_data2_temp <= SEG_7;
4'd8: a_data2_temp <= SEG_8;
4'd9: a_data2_temp <= SEG_9;
default: a_data2_temp <= SEG_0;
endcase
/*****************************************/
//数码管三要显示的列表数据(0~9)
reg [6:0] a_data3_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
a_data3_temp <= SEG_0;
else
case(a_minute_h)
4'd0: a_data3_temp <= SEG_0;
4'd1: a_data3_temp <= SEG_1;
4'd2: a_data3_temp <= SEG_2;
4'd3: a_data3_temp <= SEG_3;
4'd4: a_data3_temp <= SEG_4;
4'd5: a_data3_temp <= SEG_5;
4'd6: a_data3_temp <= SEG_6;
4'd7: a_data3_temp <= SEG_7;
4'd8: a_data3_temp <= SEG_8;
4'd9: a_data3_temp <= SEG_9;
default: a_data3_temp <= SEG_0;
endcase
/***********************************/
//数码管四要显示的列表数据(0~9)
reg [6:0] a_data4_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
a_data4_temp <= SEG_0;
else
case(a_minute_l)
4'd0: a_data4_temp <= SEG_0;
4'd1: a_data4_temp <= SEG_1;
4'd2: a_data4_temp <= SEG_2;
4'd3: a_data4_temp <= SEG_3;
4'd4: a_data4_temp <= SEG_4;
4'd5: a_data4_temp <= SEG_5;
4'd6: a_data4_temp <= SEG_6;
4'd7: a_data4_temp <= SEG_7;
4'd8: a_data4_temp <= SEG_8;
4'd9: a_data4_temp <= SEG_9;
default: a_data4_temp <= SEG_0;
endcase
/*******************************************************/
assign a_data1 = a_data1_temp;
assign a_data2 = a_data2_temp;
assign a_data3 = a_data3_temp;
assign a_data4 = a_data4_temp;
/***************************************************/
/******************************************/
parameter shuaxin = 17'h1ffff;
// 数码管扫描频率
reg [19:0] cnt;
reg [1:0] num;//每隔5MS,num加1
always @(posedge clk or negedge rst_n)
if(!rst_n)
begin
select_wei <= 4'd8; // 1000
cnt <= 18'd0;
num <= 2'd0;
end
else if(cnt == shuaxin)
begin
num <= num + 1'b1;
cnt <= 18'd0;
if(select_wei == 4'd1) //0001
select_wei <= 4'd8;
else
select_wei <= {1'b0,select_wei[3:1]}; //右移
end
else
cnt <= cnt + 1'b1;
/******************************************/
//通过Display_Model来确定是要送秒表数据还是闹钟的数据
reg [7:0] data_temp;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_temp <= 7'h7e;
else if(cnt == shuaxin)
case(num)
2'd0: data_temp <= Display_Model ? a_data1:c_data1;//给第一个数码管送数据
2'd1: data_temp <= Display_Model ? a_data2:c_data2;//给第二个数码管送数据
2'd2: data_temp <= Display_Model ? a_data3:c_data3;//给第二个数码管送数据
2'd3: data_temp <= Display_Model ? a_data4:c_data4;//给第二个数码管送数据
endcase
end
assign data = data_temp;
/******************************************/
////// 比较器模块 //////
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
alarm_out <= 0 ;
else if(a_data1 == c_data1 && a_data2 == c_data2 && a_data3 == c_data3 && a_data4 == c_data4)
alarm_out <=1;
else
alarm_out<=0;
end
endmodule
</div>
top层
主要是链接,直接上代码吧。
代码展示
module top(
input clk,
input rst_n,
input add_in,
input sub_in,
input model_in,
output [6:0] data,//数码管段选数据
output [3:0] select_wei, //数码管位选
output alarm_out
);
wire [1:0] hour_h;
wire [3:0] hour_l;
wire [2:0] minute_h;
wire [3:0] minute_l;
wire [1:0] a_hour_h;
wire [3:0] a_hour_l;
wire [2:0] a_minute_h;
wire [3:0] a_minute_l;
wire Display_Model;
wire [2:0]Time_model;
wire c_add;
wire c_sub;
wire a_add;
wire a_sub;
wire cnt_control; //控制定时器
//
key U1(
//top的input
.clk(clk),
.rst_n(rst_n),
.add_in(add_in),
.sub_in(sub_in),
.model_in(model_in),
//连clock的线
.cnt_control(cnt_control),
.c_add(c_add),
.c_sub(c_sub),
.Time_model(Time_model),
//连alarm的线
.a_add(a_add),
.a_sub(a_sub),
//连display的线
.Display_Model(Display_Model)
);
clock U2(
//input
.clk(clk),
.rst_n(rst_n),
//从key来的线
.c_add(c_add),
.c_sub(c_sub),
.cnt_control(cnt_control),
.Time_model(Time_model),
//output
//连display的线
.hour_h(hour_h),
.hour_l(hour_l),
.minute_h(minute_h),
.minute_l(minute_l)
);
alarm U3(
//input
.clk(clk),
.rst_n(rst_n),
//从key来的线
.c_add(a_add),
.c_sub(a_sub),
.Time_model(Time_model),
//output
//连display的线
.a_hour_h(a_hour_h),
.a_hour_l(a_hour_l),
.a_minute_h(a_minute_h),
.a_minute_l(a_minute_l)
);
display U4(
//input
.clk(clk),
.rst_n(rst_n),
//从clock 来的线
.hour_h(hour_h),
.hour_l(hour_l),
.minute_h(minute_h),
.minute_l(minute_l),
//从alarm 来的线
.a_hour_h(a_hour_h),
.a_hour_l(a_hour_l),
.a_minute_h(a_minute_h),
.a_minute_l(a_minute_l),
//从key 来的线
.Display_Model(Display_Model),
//output
.data(data),
.select_wei(select_wei),
.alarm_out(alarm_out)
);
endmodule
细节讲解
top层的作用是连接各个模块,列出系统的输入输出,然后分清楚中间节点,如果之前没有写过top层,可以先从试着写个小系统,比如:全加器(调用半加器实现),如果你已经是老司机,相信已经驾轻就熟了,不多说了。其实,我这个例化写得有点烦了,例化如果非要分写法可以分为两种,我这是比较烦的,不过我也不改了,╭(╯^╰)╮
写在后面的话
数字钟就讲诉到这里,整个系统不难理解,只是模块之间的联系必须弄清楚,我的观点是:把需要的模块列出来,分析模块之间的联系,再具体看各个模块的内容,单个验证,自顶向下设计,主要还是多琢磨吧,有耐心就好。
关于文章,如果有任何问题都可以在评论区和我交流,如果有错误,欢迎斧正,谢谢了~~