Verilog实现4位流水PWM呼吸灯


(野火ALTERA FPGA 教程作业训练题)
在完成呼吸灯的基础上,尝试流水呼吸灯。

基本思路

首先流水灯部分代码基本上可以继续用,基本思路还是两个并行的部分,PWM呼吸灯的效果使用led变量赋值,一直运行;而流水部分,通过一个流水灯代码不停地移位,选择亮的那位,将led的值利用拼接符拼接即可。

关于流水灯部分的思路,是受了自己一年前用spartan6学习FPGA时的一个按键的教程:FPGA实战篇——【2】按键控制LED灯闪烁

先上代码再解释:

Verilog代码

module breath_led_flash
#(
    parameter CNT_2US_MAX = 6'd99   ,
    parameter CNT_2MS_MAX = 10'd999 ,
    parameter CNT_2S_MAX  = 10'd999 
)
(
    input   wire    	sys_clk     ,  
	input   wire    	sys_rst_n   ,  
	
	output  reg [3:0]  	led_out
);

reg [6:0] cnt_2us;//0-99需要7位
reg [9:0] cnt_2ms;//0-999需要10位
reg [9:0] cnt_2s ;//0-999需要10位
reg       cnt_en ;//cnt_1s_en = 0 时实现呼吸灯从灭到亮的过程
reg       led    ;//作为一位呼吸灯运行
reg [1:0] led_ctrl;//控制哪个led灯亮

reg       cnt_en_1  ;//取en的下降沿来作为触发信号
wire      negde     ;//下降沿信号

//2us计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
		cnt_2us <= 7'b0;
	else	if(cnt_2us == CNT_2US_MAX)
		cnt_2us <= 7'b0;
	else	
		cnt_2us <= cnt_2us + 1'b1;	
end
//2ms计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
		cnt_2ms <= 10'b0;
	else	if(cnt_2us == CNT_2US_MAX && cnt_2ms == CNT_2MS_MAX)
		cnt_2ms <= 10'b0;
	else	if(cnt_2us == CNT_2US_MAX)
		cnt_2ms <= cnt_2ms + 1'b1;	
end
//2s计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
		cnt_2s <= 10'b0;
	else	if(cnt_2us == CNT_2US_MAX && cnt_2ms == CNT_2MS_MAX && cnt_2s == CNT_2S_MAX)
		cnt_2s <= 10'b0;
	else	if(cnt_2us == CNT_2US_MAX && cnt_2ms == CNT_2MS_MAX)
		cnt_2s <= cnt_2s + 1'b1;	
end

//cnt_en:2s 计数器标志信号
 always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
		begin
            cnt_en      <= 1'b0;
            
        end
    else    if(cnt_2us == CNT_2US_MAX && cnt_2ms == CNT_2MS_MAX && cnt_2s == CNT_2S_MAX)
        begin
            cnt_en <= ~cnt_en;
            
        end
 end
//cnt_en_1 延一拍不能写在上一个2s的always块中,否则延迟的就不是一个sysclk而是2s
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt_en_1      <= 1'b0;
    else
        cnt_en_1      <= cnt_en;
end
//led:负责呼吸灯控制
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
		led <= 1'b0;
    else    if((cnt_en && cnt_2ms < cnt_2s) || (!cnt_en && cnt_2ms > cnt_2s))
        led <= 1'b0;
    else
        led <= 1'b1;
end

//取en的下降沿
assign negde = ~cnt_en && cnt_en_1;

//led_ctrl:作为led信号位的控制
always@(posedge negde or negedge sys_rst_n) begin
//如果使用sysclk的上升沿,计数满清零是不符合时序的,只会保持一个sysclk周期就由11变成00,
    if(!sys_rst_n)
		led_ctrl <= 2'd0;
    else    if(led_ctrl == 2'd3)
        led_ctrl <= 2'd0;
    else 
        led_ctrl <= led_ctrl + 1'b1;
end

//led_out:输出信号连接到外部的 led_out 灯
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
		led_out <= 4'b1111;//全灭
    else    
        case(led_ctrl)
            2'b00:  led_out <= {led       , {3{1'b1}}           };
            2'b01:  led_out <= {1'b1      , led    , {2{1'b1}}  };
            2'b10:  led_out <= {{2{1'b1}} , led    , 1'b1       };
            2'b11:  led_out <= {{3{1'b1}} , led                 };  
            default:led_out <= 4'b1111;             //全灭
        endcase
        //不能这样拼接 {led, 3’b111   };而且{led, 3{1'b1}};也不对,要给3{1'b1}也加{}
end

endmodule

代码思路解析

1.呼吸灯

设置reg led ;//作为一位呼吸灯运行,至此呼吸灯部分代码可以看做封装好了,只需将其赋值给led_out对应的位即可。

2.流水灯的切换——拼接符{}的思路

受本文最开始提到自己的那篇博客的启发,对于呼吸灯位置的切换,使用这种拼接的方法进行

//led_out:输出信号连接到外部的 led_out 灯
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
		led_out <= 4'b1111;//全灭
    else    
        case(led_ctrl)
            2'b00:  led_out <= {led       , {3{1'b1}}           };
            2'b01:  led_out <= {1'b1      , led    , {2{1'b1}}  };
            2'b10:  led_out <= {{2{1'b1}} , led    , 1'b1       };
            2'b11:  led_out <= {{3{1'b1}} , led                 };  
            default:led_out <= 4'b1111;             //全灭
        endcase
end

其实思路就像是一个简单的状态机,通过led_ctrl这个state标志位的不断变换,使用case语句进行状态的切换。

debug

拼接符号的使用出现了问题:
//不能这样拼接 {led, 3’b111 };而且{led, 3{1'b1}};也不对,要给3{1'b1}也加{}
详情见:
Verilog 位拼接运算符{}语法要点总结

3.流水灯的切换——led_ctrl的条件

在编写代码的时候,一个难点就是led_ctrl到底什么时候切换增加以及清零。

因为我们的PWM周期是2s*2 = 4s,也就是说,流水的时候一个led要亮4s再切换下一个led,但是我们只有一个2s的计数器,怎么实现呢,我们考虑到2s的标志位,cnt_en,它的一个周期是4s,那要怎么利用这个标志位呢?
cnt_en是一个电平信号,想要用他的高电平或者低电平是不可以的,那么我们就想到之前博客里写的,取下降沿的方式,只要将下降沿信号取出,下降沿信号negde==1的时候让led_ctrl切换下一个状态即可。
取下降沿思路:FPGA触摸按键控制LED——边沿检测——同步时钟的意识
//取en的下降沿 assign negde = ~cnt_en && cnt_en_1;

debug

这里怎么用negde呢?
如果是按下面这样写:(错误示例)

//led_ctrl:作为led信号位的控制
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
		led_ctrl <= 2'd0;
    else    if(led_ctrl == 2'd3)
        led_ctrl <= 2'd0;
    else if(negde)
        led_ctrl <= led_ctrl + 1'b1;
end

这样利用,当led_ctrl == 2’d3时,很快在下一个sysclk就变成0了,根本看不到最后一个led亮,如下图:
在这里插入图片描述
那怎么办呢,我们可以直接把触发条件更改,不用sys_clk的上升沿,而是直接使用negde的上升沿,他的上升沿就代表了4s已经结束了,led_ctrl需要变化了。所以如下:(正确代码)

//led_ctrl:作为led信号位的控制
always@(posedge negde or negedge sys_rst_n) begin
//如果使用sysclk的上升沿,计数满清零是不符合时序的,只会保持一个sysclk周期就由11变成00,
    if(!sys_rst_n)
		led_ctrl <= 2'd0;
    else    if(led_ctrl == 2'd3)
        led_ctrl <= 2'd0;
    else 
        led_ctrl <= led_ctrl + 1'b1;
end

Testbench

`timescale  1ns/1ns

module  tb_breath_led_flash();

wire [3:0]   led_out	;

reg         sys_clk		;
reg         sys_rst_n	;

initial begin
 	sys_clk		 = 1'b1;
	sys_rst_n	<= 1'b0;
	#20
	sys_rst_n	<= 1'b1;   
end

always #10 sys_clk = ~sys_clk;

breath_led_flash
#(
    .CNT_2US_MAX    (7'd9   ),   
    .CNT_2MS_MAX    (10'd9  ),
    .CNT_2S_MAX     (10'd9  )  
)
breath_led_flash_inst
(
    .sys_clk	(sys_clk	),
    .sys_rst_n	(sys_rst_n	),

    .led_out 	(led_out 	) //output

);

endmodule

仿真图

在这里插入图片描述

通过下图中led_out的波形确实能看出来,1111中间夹杂的0111波形的时间确实在变化,这就是呼吸灯的效果,一段时间后又变为1011,以此类推
在这里插入图片描述

在这里插入图片描述

  • 7
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值