1. 实验目标
呼吸灯在我们的生活中很常见,在手机上多作为消息提醒指示灯而被广泛使用,其效果是小灯在一段时间内从完全熄灭的状态逐渐变到最亮,再在同样的时间段内逐渐达到完全熄灭的状态,并循环往复。这种效果就像“呼吸”一样,有张有弛,而且给人一种很舒服的感觉。
2. 原理分析
2.1 内部原理
2.2 实现原理
2.3 计数器解释
1)cnt_1us: us 计数器,一个us的产生需要50个时钟周期
2)cnt_1ms:ms计数器,一个ms的产生需要1000个us
3)cnt_1s: s计数器,一个s的产生需要1000个ms
4)cnt_1s_en,使能信号为低电平时实现呼吸灯从灭到亮的过程,cnt_1s_en 使能信号为高的时候实现呼吸灯从亮到灭的过程
我们先画出呼吸灯从灭到亮时间段预期的 led_out 波形图,结合三个计数器我们发现 led_out 为低电平时间时 cnt_1s 计数器的计数值总是小于 cnt_1ms 计数器的计数值,这简直就是太完美的发现了,我们让 cnt_1s 计数器的计数值大于 cnt_1ms 计数器的计数值就可以实现每一个 1ms 的时间小段的低电平持续时间都不一样且渐渐增加的效果。同理呼吸灯从亮到灭正好是相反的过程,要使 cnt_1s 计数器的计数值总是大于 cnt_1ms 计数器的计数值才能实现每一个 1ms 的时间小段的低电平持续时间都不一样且渐渐减小的效果。
3. 波形图
3.1 由亮变暗
3.2 由暗变亮
4. RTL
module breath_led
#(
parameter CNT_1US_MAX = 6'd50 ,
parameter CNT_1MS_MAX = 10'd1000 ,
parameter CNT_1S_MAX = 10'd1000
/*
1)50MHZ 时钟周期为20ns 1us=1000ns 需要 1000/20 = 50 需要50个时钟,计数器个数为50
2)呼吸灯的效果,完全熄灭到完全点亮 时间为 1s
3)1s = 1000ms 则需要1000个s计数器,用来计ms的个数
4)1ms = 1000us 则需要1000个ms计数器,用来计ns的个数 ns==50-1 计数器加一
*/
)
(
input wire sys_clk , //系统时钟 50MHz
input wire sys_rst_n , //全局复位
output reg led_out //输出信号,控制 led 灯
);
reg [5:0] cnt_1us ;
reg [9:0] cnt_1ms ;
reg [9:0] cnt_1s ;
reg cnt_1s_en ; // 计数器标志信号,当0.5秒时候,作为熄灭和点亮间的波形转换
// 计数 us
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
cnt_1us <= 6'd0;
end
else if(cnt_1us == CNT_1US_MAX - 6'd1)
begin
cnt_1us <= 6'd0;
end
else
begin
cnt_1us <= cnt_1us + 6'd1;
end
end
// 计数 ms
// if else if 的
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
cnt_1ms <= 10'd0;
end
else if((cnt_1ms == CNT_1MS_MAX - 10'd1) && (cnt_1us == CNT_1US_MAX - 6'd1))
begin
cnt_1ms <= 10'd0;
end
else if(cnt_1us == CNT_1US_MAX - 6'd1)
begin
cnt_1ms <= cnt_1ms + 10'd1;
end
end
// 计数 ls
// if else if 的
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
cnt_1s <= 10'd0;
end
else if((cnt_1ms == CNT_1MS_MAX - 10'd1) && (cnt_1us == CNT_1US_MAX - 6'd1)
&& (cnt_1s == CNT_1S_MAX - 10'd1))
begin
cnt_1s <= 10'd0;
end
else if((cnt_1ms == CNT_1MS_MAX - 10'd1) && (cnt_1us == CNT_1US_MAX - 6'd1))
begin
cnt_1s <= cnt_1s + 10'd1;
end
end
//cnt_1s_en:1s 计数器标志信号
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
cnt_1s_en <= 1'b0;
end
else if((cnt_1ms == CNT_1MS_MAX - 10'd1) && (cnt_1us == CNT_1US_MAX - 6'd1)
&& (cnt_1s == CNT_1S_MAX - 10'd1))
begin
cnt_1s_en <= ~cnt_1s_en;
end
else
begin
cnt_1s_en <= cnt_1s_en;
end
end
//led_out:输出信号连接到外部的 led 灯
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
led_out <= 1'b0;
end
else if((cnt_1s_en == 1'b1 && cnt_1ms < cnt_1s) ||
(cnt_1s_en == 1'b0 && cnt_1ms > cnt_1s))
begin
led_out <= 1'b0;
end
else
begin
led_out <= 1'b1;
end
end
endmodule
5. testbench
`timescale 1ns/1ns
module tb_breath_led();
//因为 testbench 不对外进行信号的输入输出,只是自己产生
//激励信号提供给内部实例化待测 RTL 模块使用,所以端口列表
//中没有内容,只是列出“()”,当然可以将“()”省略,括号
//后有个“;”不要忘记
//要在 initial 块和 always 块中被赋值的变量一定要是 reg 型
//在 testbench 中待测试 RTL 模块的输入永远是 reg 型变量
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
reg sys_clk;
reg sys_rst_n;
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
wire led_out;
//初始化值在没有特殊要求的情况下给 0 或 1 都可以。如果不赋初值,仿真时信号
//会显示为不定态(ModelSim 中的波形显示红色)
initial
//initial 只在通电执行一次
//在仿真中 begin...end 块中的内容都是顺序执行的,
//在没有延时的情况下几乎没有差别,看上去是同时执行的,
//如果有延时才能表达的比较明了;
//而在 rtl 代码中 begin...end 相当于括号的作用, begin...end 在 Testbench 中的用法及意义(区别 -----------------------------------------------------)
//在同一个 always 块中给多个变量赋值的时候要加上
begin
sys_clk = 1'b1; //时钟信号的初始化为 1,且使用“=”赋值,
//其他信号的赋值都是用“<=”
sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为 0
#20 //延时20ns
sys_rst_n <= 1'b1; //初始化 20ns 后,复位释放,因为是低电平复位
//都是顺序执行的
end
// always语句 一直在执行
sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50Mhz
always #10 sys_clk = ~sys_clk;//取模求余数,产生随机数 1'b0、1'b1//每隔 10ns 产生一次随机数
breath_led
#(
.CNT_1US_MAX(6'd4 ),
.CNT_1MS_MAX(10'd9 ),
.CNT_1S_MAX (10'd9 )
)
breath_led_inst
(
//前面的“in1”表示被实例化模块中的信号,后面的“in1”表示实例化该模块并要和这个
//模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察)
//“.”可以理解为将这两个信号连接在一起
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.led_out (led_out)
);
endmodule