概述
题目描述:计时器:在 6 个七段管上分别显示 小时(0-23 或 11)、分(0-59)、秒(0-59),各占 2 个管。外部时钟 50Mhz。可以用按键来产生一个复位信号 key,当按键按下立刻(异步)将时间复位成 0 小时、0 分、0 秒重新开始计时。
目录
- 1、题目解析
- 2、综合代码
- 3、测试代码
- 4、效果展示
- 5、代码v2.0
(提示:其中 2-4为第一版代码,如果是学习的话可以看看,如果只是想要最好的结果,直接跳转到代码 v2.0 即可)
1、题目解析:
分析1:如何实现这个功能?
我们需要6个7为的寄存器(或者一个7*6 = 42 位的寄存器)来保留每一个时刻的时间,“秒”和“分”的4个计数器的显示分别对应着一个60进制的计数模块,而“时”则对应着一个24位的计数器。
分析2:如果实现每一秒进行一次变化?
根据系统外部时钟为50 MHz,我们可以写一个计数器,时钟每变化一个周期就记录一次,一直数到50 M时输出一个电平。或者每数到 25M 改变一个让一个输出寄存器里的值发生反转,那么对于这个寄存器,他的周期就是 1Hz 了。
分析3:如何实现 类似00:00:59
到 00:01:00
的变化?
要实现这个逻辑,需要将“秒”对应的进位信号作为“分”的输入信号,当有进位信号的时候才能将分进行计数。“分” 和 “时” 之间也要有类似的进位关系。
2、综合代码
module timer(
// 控制输入
input reset, // 重置信号, 当其值为低电平时,发生重置
input set, // 置位标致位,当输入高电平时进行输入
input clk50, // 时钟信号
input[1:0] setp, // set_position 选择要输入的位置 00,表示秒位, 01 表示分位, 10表示时位,其余报错
input[5:0] setv, // set_value 输入的数值
// 输出的 6个七段管
output reg[6:0] second1,
output reg[6:0] second2,
output reg[6:0] munite1,
output reg[6:0] munite2,
output reg[6:0] hous1,
output reg[6:0] hous2
);
wire set1, set2, set3; // 对应每一个管子的设置
wire car1, car2, carr3; // 对应每一个管子的输出进位标志
wire clk1;
// 接收每个计数器的计数器值
wire[3:0] num1,num2,num3,num4,num5,num6;
// 接受每一个七段管的输出值
wire[6:0] tub1,tub2,tub3,tub4,tub5,tub6;
divclk1hz clock1(reset,clk50,clk1);
// 定义秒
counter60 c60_1(
.clk(clk1),
.in(1),
.reset(reset),
.set(set1),
.inv(setv),
.carry(car1),
.out1(num1),
.out2(num2)
);
// 定义分
counter60 c60_2(
.clk(clk1),
.in(car1),
.reset(reset),
.set(set2),
.inv(setv),
.carry(car2),
.out1(num3),
.out2(num4)
);
// 定义时
counter24 c24_2(
.clk(clk1),
.in(car2),
.reset(reset),
.set(set3),
.inv(setv),
.carry(car3),
.out1(num5),
.out2(num6)
);
// 将计数器输出的数值信号转化为七段管的亮暗状态
int2tube i1( .in(num1), .out(tub1) );
int2tube i2( .in(num2), .out(tub2) );
int2tube i3( .in(num3), .out(tub3) );
int2tube i4( .in(num4), .out(tub4) );
int2tube i5( .in(num5), .out(tub5) );
int2tube i6( .in(num6), .out(tub6) );
// 以下代码的作业就是链接各个模块
// 这里加入reset 的检测是为了异步处理
always @(posedge clk1 or negedge reset) begin
if(!reset) begin
second1 <= 8'b0000001;
second2 <= 8'b0000001;
munite1 <= 8'b0000001;
munite2 <= 8'b0000001;
hous1 <= 8'b0000001;
hous2 <= 8'b0000001;
end else begin
second1 <= tub1;
second2 <= tub2;
munite1 <= tub3;
munite2 <= tub4;
hous1 <= tub5;
hous2 <= tub6;
end
end
// 设置置位信号
assign set1 = (set==1)?((setp == 0)?1:0):0;
assign set2 = (set==1)?((setp == 1)?1:0):0;
assign set3 = (set==1)?((setp == 2)?1:0):0;
endmodule
/** 60位计数器 */
module counter60(
input clk, // 时钟信号
input in, // 输入信号
input reset, // 重置信号, 当其值为低电平时,发生重置
input set, // 置位标致位,当输入高电平时进行输入
input[5:0] inv,// 置位信号
output reg carry, // 进位信号
output reg[3:0] out1, // 输出十位
output reg[3:0] out2 // 输出个位
);
reg[5:0] inner; // 内置计数器
always @(posedge clk or posedge set or negedge reset) begin
if(!reset) begin // 检测是否要进行重置 低电平有效
inner <= 0;
carry <= 0;
out1 <= 0;
out2 <= 0;
end else if(set) begin // 检测是否要进行置数 高电平有效
inner <= inv;
out1 <= inv / 10;
out2 <= inv % 10;
end else begin
if(in) begin // 没有重置和置数时,in为高电平时进行加数
if(inner < 58) begin // 如果没到58就继续数
inner <= inner + 1;
end else if(inner == 58)begin // 到58结束时输出进位信号,这样在59结束时(新的1秒开始)就可以
carry <= 1; // 并且进行进位
inner <= inner + 1;
end else begin // 如果到了59, 那么下一时刻就要进行从 0 开始数
inner <= 0;
carry <= 0;
end
end
out1 <= inner / 10;
out2 <= inner % 10;
end
end
endmodule
/** 24位计数器 */
module counter24(
input clk, // 时钟信号
input in, // 输入信号
input reset, // 重置信号, 当其值为低电平时,发生重置
input set, // 置位标致位,当输入高电平时进行输入
input[5:0] inv,// 置位信号
output reg carry, // 进位信号
output reg[3:0] out1, // 输出十位
output reg[3:0] out2 // 输出个位
);
reg[4:0] inner; // 内置计数器
always @(posedge clk or posedge set or negedge reset) begin
if(!reset) begin // 检测是否要进行重置 低电平有效
inner <= 0;
carry <= 0;
out1 <= 0;
out2 <= 0;
end else if(set) begin // 检测是否要进行置数 高电平有效
inner <= inv;
out1 <= inv / 10;
out2 <= inner % 10;
end else begin
if(in) begin // 没有重置和置数时,in为高电平时进行加数
if(inner < 23) begin // 如果没到59就继续数
inner <= inner + 1;
carry <= 0;
end else begin // 如果到了59, 那么下一时刻就要进行从 0 开始数
inner <= 0;
carry <= 1; // 并且进行进位
end
end
out1 <= inner / 10;
out2 <= inner % 10;
end
end
endmodule
/** 将数值信号转化为七段管的亮暗状态 */
module int2tube(
input[3:0] in,
output reg[6:0] out
);
always @(in) begin
case (in)
0: out <= 8'b0000001;
1: out <= 8'b0011111;
2: out <= 8'b0010010;
3: out <= 8'b0000110;
4: out <= 8'b1001100;
5: out <= 8'b0100100;
6: out <= 8'b0100000;
7: out <= 8'b0001111;
8: out <= 8'b0000000;
9: out <= 8'b0000100;
endcase
end
endmodule
/** 分频模块,每数到25M改变一个让一个输出寄存器clk1里的值发生反转 */
module divclk1hz(reset,clk50,clk1);
input clk50,reset; //clk50 为输入50Mhz 信号,reset 为复位信号
output reg clk1; // 新产生的1hz 信号
integer i=0; //50Mhz 频率下,周期计数器
always@(posedge clk50) begin
if(!reset) begin
i=0;
clk1 = 0;
end else begin
if(i==30) begin i=0; clk1=~clk1; // 25000000 这里的i应该=25M,但是为了更方便的展示效果,我将i的值改为了30,这样七段管里的数值会改变的更快。
end
else i=i+1;
end
end
endmodule
3、测试代码
`timescale 10 ns/ 1 ns
module timer_vlg_tst();
reg clk50;
reg reset;
reg set;
reg [1:0] setp;
reg [5:0] setv;
wire [6:0] hous1;
wire [6:0] hous2;
wire [6:0] munite1;
wire [6:0] munite2;
wire [6:0] second1;
wire [6:0] second2;
timer i1 (
.clk50(clk50),
.hous1(hous1),
.hous2(hous2),
.munite1(munite1),
.munite2(munite2),
.reset(reset),
.second1(second1),
.second2(second2),
.set(set),
.setp(setp),
.setv(setv)
);
initial begin
// 初始化外部时钟
clk50 = 0;
// 重置一遍timer模块
reset = 1; #5 reset =0; #5 reset =1;
set = 0;
// 准备将秒位设置到55秒
setv = 33; setp = 0;
// 等待2100 个时间单位,观察时间变化
# 2100;
// 进行数值修改
set = 1;
// 修改完毕,结束修改信号
#1 set = 0;
// 运行900 时间单位,观察效果,进行复位
#900 reset = 0;
// 复位信号归位
#5 reset = 1;
// 运行900个单位以后停止运行
#900 $stop;
end
always begin
#1 clk50 =~ clk50;
end
endmodule
4、效果展示
异步复位信号的效果:可以看到在reset信号进入赋值以后,6个七段管的值都恢复到0000001
对应数值0
。
同步置数的效果:,当set型号进入高点平,改变所设置的时间的值(将“秒”数值置为33),0000110
对应数值为3。
5、代码 v2.0
5.1 第一版代码存在的问题
对于上述代码,我们可以看到,在数字发生复位或者置位以后会有很长时间的延时,小编在之后的学习中了解到这个是由于寄存器的使用会导致信号延时,这道题中,七段管的输出信号要经过计数器计数寄存器inner
, 个位十位寄存器out1
和 out2
最后在经过 int2tube数值转七段输出的out
寄存器,得到输出结果,这会让我们的输入信号执行的效果出现严重延时。针对这个问题,在代码 v2.0 中就以减少中间寄存器为目标,开始了优化。
5.2 综合代码
在综合代码中,我们去除了大量reg, 只保留下最基本的计数器计数寄存器inner
,其他都用逻辑电路进行链接。
module timer(
// 控制输入
input reset, // 重置信号, 当其值为低电平时,发生重置
input set, // 置位标致位,当输入低电平时进行输入
input clk50, // 时钟信号
input showtype, // 显示方式,是24小时还是12小时。0为24 | 1为12
input[1:0] setp, // set_position 选择要输入的位置, 00表示秒位, 01 表示分位, 10表示时位,其余报错
input[5:0] setv, // set_value 输入的数值
input set_cp, // set_clock_position, 要输入的闹钟值,1时位, 0分位, 其他无效
input set_c, // 闹钟置位标致位,当输入低电平时进行输
input close, // 关闭闹钟和整点报时
// 输出的 6个七段管
output[6:0] second1,
output[6:0] second2,
output[6:0] munite1,
output[6:0] munite2,
output[6:0] hous1,
output[6:0] hous2,
// 输出的闹钟信号
output led,
output reg err
);
// 寄存器变量
reg[11:0] clock_reg; // 用于调整闹钟报时的时间
reg[5:0] noise; // 用于表示闹钟的持续时间
reg cancel_clock; // 用于表示用户是否已取消闹钟
reg[16:0] time_reg; // 记录当前时间
// 中间变量,为了方便计算
wire[3:0] num5,num4,num3,num2,num1,num0;
// 加速变量, 对于运算中重复出现的表达,我们将组合电路模块话
wire[11:0] _n1; // 表示60*分钟位 + 秒位
wire[4:0] _n2; // 表示小时位
// 利用函数转化计数器输出的
function[6:0] int2tube;
input[3:0] _in;
case (_in)
0: int2tube = 8'b0000001;
1: int2tube = 8'b0011111;
2: int2tube = 8'b0010010;
3: int2tube = 8'b0000110;
4: int2tube = 8'b1001100;
5: int2tube = 8'b0100100;
6: int2tube = 8'b0100000;
7: int2tube = 8'b0001111;
8: int2tube = 8'b0000000;
9: int2tube = 8'b0000100;
endcase
endfunction
// 初始化寄存器
initial begin
clock_reg <= 12'b10_0000_00_0000; // 不设置闹钟
noise <= 6'b0;
end
// 定义分频模块
divclk1hz clock1(.reset(reset), .clk50(clk50), .clk1(clk1));
// 内部计时寄存器控制
always @(posedge clk1 or negedge set or negedge reset) begin
// 复位信号
if(!reset) begin
time_reg <= 17'b0;
end
// 置位信号
else if(!set) begin
case (setp)
2'b00: time_reg <= num0*36000 + num1*3600 + num2*600 + num3*60 + setv;
2'b01: time_reg <= num0*36000 + num1*3600 + setv*60 + num4*10 + num5;
2'b10: time_reg <= setv*3600 + num2*600 + num3*60 + num4*10 + num5;
default: time_reg <= time_reg;
endcase
end
// 正常情况
else if(clk1) begin
time_reg <= (_n2==23 && _n1==12'b1110_0000_1111)? 17'b0:time_reg + 1; // 23:59:59 清零
end
end
// 设置闹钟时间
always @(negedge set_c) begin
if(set_cp) begin // 如果是时位时
clock_reg[11:6] <= setv;
end
else begin // 否则当分位时
clock_reg[5:0] <= setv;
end
end
// 铃声开启检测
always @(posedge clk1 ,negedge close) begin
if (!close) begin
noise <= 0;
cancel_clock = 1; // 防止闹钟在一分钟内多次响起
end
else begin
// 当正在响铃时
if(noise) begin
noise <= noise - 1;
if(noise == 1) cancel_clock = 1; // 防止闹钟在一分钟内多次响起
end
// 当触发闹钟时,这里规定闹钟优先级大于整点报时
else if(clock_reg[11:6]==num0*8+num0*2+num1 && clock_reg[5:0 ]==num2*8+num2*2+num3) begin
if(cancel_clock==0) begin
noise <= 30; // 规定闹钟响半分钟
err <= 1;
end
end
// 当整点时
else if(_n1==0) begin
noise <= 5;
end
else begin
cancel_clock = 0;
end
end
end
// 铃声开启检测
always @(clk1) begin
end
// 计算加速
assign _n1 = time_reg%3600;
assign _n2 = time_reg/3600;
// 让计数器模块的输出转变成系统七段管输出
assign num5 = time_reg%10;
assign num4 = time_reg%60/10;
assign num3 = _n1/60%10;
assign num2 = _n1/600;
assign num1 = showtype?(_n2%12%10):(_n2%10); // 考虑12小时表示和24时表示
assign num0 = showtype?(_n2%12/10):(_n2/10); // 考虑12小时表示和24时表示
// 让计数器模块的输出转变成系统七段管输出
assign second2 = int2tube(num5);
assign second1 = int2tube(num4);
assign munite2 = int2tube(num3);
assign munite1 = int2tube(num2);
assign hous2 = int2tube(num1);
assign hous1 = int2tube(num0);
// 闹钟输出
assign led = noise?0:1;
endmodule
/** 分频模块,每数到25M改变一个让一个输出寄存器clk1里的值发生反转 */
module divclk1hz(reset,clk50,clk1);
input clk50,reset; //clk50 为输入50Mhz 信号,reset 为复位信号
output reg clk1; // 新产生的1hz 信号
integer i=0; //50Mhz 频率下,周期计数器
always @(posedge clk50 or negedge reset) begin
if(!reset) begin
i = 0;
clk1 = 0;
end
else begin
if(i==30) begin
i=0; clk1=~clk1; // 25000000 这里的i应该=25M,但是为了更方便的展示效果,我将i的值改为了30,这样七段管里的数值会改变的更快。
end
else begin
i=i+1;
end
end
end
endmodule
5.4 效果展示
波形变化细节
版本1波形:
版本2波形:
可以看在版本2中,我们在置数以后,不用等待很长时间就可以看到清零的效果,但仍然存在一些问题,这里是由于分频模块第一次计数开始到产生上升沿只有半个周期,所以在半秒(理论上)以后就进入了下一秒,感兴趣的朋友可以再对代码进行修改,调整一下分频模块,让第一次尽可能准。(欢迎大家评论补充)
文本输出效果
最后
以上就是重要仙人掌为你收集整理的EDA设计(verilog)—— 七段管时钟的全部内容,希望文章能够帮你解决EDA设计(verilog)—— 七段管时钟所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复