基于verilog的流水灯实验(菜鸟入门)

设计前言

	掐指一算,学习FPGA也有好一段时间了,大一开始接触,到现在都研一了,跌跌撞撞,中途也断了好长时间没有继续学习,现在研究生阶段打算继续研究
FPGA相关的设计,因为爱好硬件设计,所以做这方面也更有兴趣,未来打算往SoC设计方向发展,有高手欢迎带我啊啊。以前的学习都是看别人的代码,拿着程序
烧进板子去看结果,很少会自己动手写程序,仿真测试,学到了现在,我有点领会了,学习FPGA,仿真验证设计才是真正掌握这门技术的核心,最后上板验证只是
水到渠成。因为一个复杂的设计都是许多模块组合成的,都要一个个验证逻辑正确了才能保证最后的结果达到要求。以前自己就是啥也不懂,写完就烧程序,编译
又长时间,学不到仿真的核心,现在打算重新学习一下,整理出来,写一写,方便自己查看,也希望能给刚入门的小伙伴一些小小的指导,互相学习。

设计要求

1.4个LED灯在1秒内轮流点亮,间隔250ms
2.一个轮流结束后,用一个四位加法器依次累加LED循环的次数,加法器数值从0~F循环
3.当第四盏LED熄灭后,改用这四盏LED灯同时点亮显示加法器中16进制的时间,点亮时间2s

	初看这设计还挺简单的啊,于是我便开始写流水灯的程序,简单的循环流水用一个循环移位可以实现,用了一个位拼接的语法,直接放上程序和仿真结果。
module led_test
(
	input           clk,           // 50M时钟
	input           rst_n,         // 复位
	output reg[3:0] led            // 输出LED,低电平点亮
);

reg [23:0]      cnt_250ms;
parameter TIME_250MS = 24'd12_500_000;//50MHz的晶振,周期为20ns,计数时间为250ms,
                                    //则参数定义计算TIME_250MS = 250*1000000/20 = 12500000
always@(posedge clk or negedge rst_n)begin//250ms计数器,每隔250ms点亮一个灯
	if (rst_n == 1'b0)
		cnt_250ms <= 24'd0;                    
	else if (cnt_250ms == TIME_250MS - 1)  //计数满了清0    
		cnt_250ms <= 24'd0;                     
	else
		cnt_250ms <= cnt_250ms + 24'd1;             
end

always@(posedge clk or negedge rst_n)begin
	if (rst_n == 1'b0)
		led <= 4'b1110;                   
	else if (cnt_250ms == TIME_250MS - 1)      //time counter count to 1st sec,LED1 lighten
		led <= {led[2:0],led[3]};	//循环左移 每次把最高位移动到最低位,依次循环,
	else	                            //D4 D3 D2 D1  1110 -> 1101 -> 1011 -> 0111 -> 1110
		led <= led;
end

endmodule
`timescale 1ns/1ns

`define clk_period 20

module led_test_tb;

	reg clk;
	reg rst_n;
	wire [3:0]led;

	led_test #(	.TIME_250MS(6'd50))  led_test__uo(
																	.clk(clk),
																	.rst_n(rst_n),			
																	.led(led)
																	);
	
	initial clk= 1;
	always#(`clk_period/2) clk = ~clk;
	
	initial begin
		rst_n = 1'b0;
		#(`clk_period*10) 
		rst_n = 1'b1;
		#(`clk_period*1000) 
		$stop;		
	end
	
endmodule

从仿真的结果来看实现了四盏灯的循环点亮

	从仿真的结果来看实现了四盏灯的循环点亮,在仿真测试文件中参数例化了TIME_250MS,减少计数次数可以加快仿真速度,是编写测试文件中经常使用的
一种方法,保护源文件中的参数。接下来的工作就是继续统计流水的次数,然后通过十六进制的形式在LED灯上显示出来,开始打算继续在循环移位的基础上写
下去,但是实际仿真出来的结果总有一段时间的数据不对,就打算不采用移位的方式,简单粗暴一点的,采用类似线性序列机的形式。
module led_test
(
	input           Clk,           
	input           Rst_n,         
	output reg[3:0] led           
);
reg [31:0]      cnt_48s;
reg[3:0] led_r;

parameter TIME_1S  = 32'd50_000_000 - 1,
			 TIME_3S  = 32'd150_000_000 -1,//0000
			 TIME_4S  = 32'd200_000_000 -1,
			 TIME_6S  = 32'd300_000_000 -1,//0001
			 TIME_7S  = 32'd350_000_000 -1,
			 TIME_9S  = 32'd450_000_000 -1,//0010
			 TIME_10S = 32'd500_000_000 -1,
			 TIME_12S = 32'd600_000_000 -1,//0011
			 TIME_13S = 32'd650_000_000 -1,
			 TIME_15S = 32'd750_000_000 -1,//0100
			 TIME_16S = 32'd800_000_000 -1,
			 TIME_18S = 32'd900_000_000 -1,//0101
			 TIME_19S = 32'd950_000_000 -1,
			 TIME_21S = 32'd1_050_000_000 -1,//0110
			 TIME_22S = 32'd1_100_000_000 -1,
			 TIME_24S = 32'd1_200_000_000 -1,//0111
			 TIME_25S = 32'd1_250_000_000 -1,
			 TIME_27S = 32'd1_350_000_000 -1,//1000
			 TIME_28S = 32'd1_400_000_000 -1,
			 TIME_30S = 32'd1_500_000_000 -1,//1001
			 TIME_31S = 32'd1_550_000_000 -1,
			 TIME_33S = 32'd1_650_000_000 -1,//1010
			 TIME_34S = 32'd1_700_000_000 -1,
			 TIME_36S = 32'd1_800_000_000 -1,//1011
			 TIME_37S = 32'd1_850_000_000 -1,
			 TIME_39S = 32'd1_950_000_000 -1,//1100
			 TIME_40S = 32'd2_000_000_000 -1,
			 TIME_42S = 32'd2_100_000_000 -1,//1101
			 TIME_43S = 32'd2_150_000_000 -1,
			 TIME_45S = 32'd2_250_000_000 -1,//1110
			 TIME_46S = 32'd2_300_000_000 -1,
			 TIME_48S = 32'd2_400_000_000 -1;//1111
parameter 
			 TIME_250MS = 24'd12_500_000 -1,
			 TIME_500MS  = 32'd25_000_000 -1,
			 TIME_750MS  = 32'd37_500_000 -1; 			 
always@(posedge Clk or negedge Rst_n)begin//完成整个过程需要48s
	if (Rst_n == 1'b0)
		cnt_48s <= 32'd0;                     
	else if (cnt_48s == TIME_48S)      
		cnt_48s <= 32'd0;                     
	else
		cnt_48s <= cnt_48s + 32'd1;             
end


always@(posedge Clk or negedge Rst_n)begin//取反输出,开发板LED是低电平点亮
	if (Rst_n == 1'b0)
		led <= 4'b1111;                     
	else 
		led <= ~led_r;
end
//LED工作模式的触发信号,___| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|
//                          1s    2s     1s    2s    1s    2s     1s    2s     1s    2s     1s    2s    1s    2s     1s    2s

always@(posedge Clk or negedge Rst_n)begin//在1s内让进行流水灯,在2s内十六进制显示流水灯的次数
	if(Rst_n == 1'b0)
		led_r <= 4'b0000;
	else begin
		case(cnt_48s)
			0,TIME_3S,TIME_6S,TIME_9S,TIME_12S,TIME_15S,TIME_18S,TIME_21S,TIME_24S,
			TIME_27S,TIME_30S,TIME_33S,TIME_36S,TIME_39S,TIME_42S,TIME_45S:			led_r <= 4'b0001;	
			
			TIME_250MS,TIME_3S + TIME_250MS,TIME_6S + TIME_250MS,TIME_9S + TIME_250MS,
			TIME_12S + TIME_250MS,TIME_15S + TIME_250MS,TIME_18S + TIME_250MS,
			TIME_21S + TIME_250MS,TIME_24S + TIME_250MS,TIME_27S + TIME_250MS,
			TIME_30S + TIME_250MS,TIME_33S + TIME_250MS,TIME_36S + TIME_250MS,
			TIME_39S + TIME_250MS,	TIME_42S + TIME_250MS,	TIME_45S + TIME_250MS:	led_r <= 4'b0010;
			
			TIME_500MS,TIME_3S + TIME_500MS,TIME_6S + TIME_500MS,TIME_9S + TIME_500MS,
			TIME_12S + TIME_500MS,TIME_15S + TIME_500MS,TIME_18S + TIME_500MS,
			TIME_21S + TIME_500MS,TIME_24S + TIME_500MS,TIME_27S + TIME_500MS,
			TIME_30S + TIME_500MS,TIME_33S + TIME_500MS,TIME_36S + TIME_500MS,
			TIME_39S + TIME_500MS,	TIME_42S + TIME_500MS,	TIME_45S + TIME_500MS:	led_r <= 4'b0100;
			
			TIME_750MS,TIME_3S + TIME_750MS,TIME_6S + TIME_750MS,TIME_9S + TIME_750MS,
			TIME_12S + TIME_750MS,TIME_15S + TIME_750MS,TIME_18S + TIME_750MS,
			TIME_21S + TIME_750MS,TIME_24S + TIME_750MS,TIME_27S + TIME_750MS,
			TIME_30S + TIME_750MS,TIME_33S + TIME_750MS,TIME_36S + TIME_750MS,	
			TIME_39S + TIME_750MS,	TIME_42S + TIME_750MS,	TIME_45S + TIME_750MS:	led_r <= 4'b1000;
			
			TIME_1S:							led_r <= 4'b0000;//第0次			
			TIME_4S:							led_r <= 4'b0001;//第1次		
			TIME_7S:							led_r <= 4'b0010;//第2次		
			TIME_10S:						led_r <= 4'b0011;//第3次			
			TIME_13S:						led_r <= 4'b0100;//第4次	
			TIME_16S:						led_r <= 4'b0101;//第5次		
			TIME_19S:						led_r <= 4'b0110;//第6次		
			TIME_22S:						led_r <= 4'b0111;//第7次						
			TIME_25S:						led_r <= 4'b1000;//第8次	
			TIME_28S:						led_r <= 4'b1001;//第9次
			TIME_31S:						led_r <= 4'b1010;//第10次			
			TIME_34S:						led_r <= 4'b1011;//第11次			
			TIME_37S:						led_r <= 4'b1100;//第12次 
			TIME_40S:						led_r <= 4'b1101;//第13次
			TIME_43S:						led_r <= 4'b1110;//第14次
			TIME_46S:						led_r <= 4'b1111;//第15次			
			TIME_48S:						led_r <= 4'b1111;			
			default:;
		endcase
	end
end
endmodule
	思路很简单就是在明确的时间做该做的事情,很容易写出来,事实证明这种方法写出来的效率不高,用的资源也很多,于是思考着另一种方法,先把1s和2s的
那个波形图描述出来,然后通过一个加法器来统计到1s过渡到2s 的下降沿,也就是用到了题目设计给出的要求,这样的写法实际用到的资源比第一种写法要高效,
也省了不少资源,代码如下。
module led_test_v2
(
	input           clk,           // 时钟
	input           rst_n,         // 复位
	output reg[3:0] led            // 4个输出LED
);

reg [23:0]      cnt_250ms;//250ms计数器
reg [25:0] 			cnt_1s;//1s计数器
reg [31:0]      cnt_48s;//48s计数器
reg [3:0]  cnt_waterfall;//4位计数器,用来计数流水灯次数,范围0~F
reg 	waterfall_flag;//LED标志信号,高电平表示进行流水灯,低电平表示显示当前第几次流水灯,总共有0~F次流水灯
reg   waterfall_flag_r,waterfall_flag_rr;//信号打两拍,用于下降沿检测

wire nedge;//下降沿标志信号
reg [3:0] led_r;//将当前寄存器取反输出到LED引脚,因为开发板低电平点亮LED,取反后位高电平点亮,符合习惯而已

//时间参数的定义
parameter COUNT_16 = 4'd16,
			 TIME_250MS = 24'd12_500_000,//点亮LED1
			 TIME_500MS = 26'd25_000_000,//点亮LED2
			 TIME_750MS = 26'd37_500_000,//点亮LED3
			 TIME_1S  = 32'd50_000_000,
			 TIME_3S  = 32'd150_000_000,//0000
			 TIME_4S  = 32'd200_000_000,
			 TIME_6S  = 32'd300_000_000,//0001
			 TIME_7S  = 32'd350_000_000,
			 TIME_9S  = 32'd450_000_000,//0010
			 TIME_10S = 32'd500_000_000,
			 TIME_12S = 32'd600_000_000,//0011
			 TIME_13S = 32'd650_000_000,
			 TIME_15S = 32'd750_000_000,//0100
			 TIME_16S = 32'd800_000_000,
			 TIME_18S = 32'd900_000_000,//0101
			 TIME_19S = 32'd950_000_000,
			 TIME_21S = 32'd1_050_000_000,//0110
			 TIME_22S = 32'd1_100_000_000,
			 TIME_24S = 32'd1_200_000_000,//0111
			 TIME_25S = 32'd1_250_000_000,
			 TIME_27S = 32'd1_350_000_000,//1000
			 TIME_28S = 32'd1_400_000_000,
			 TIME_30S = 32'd1_500_000_000,//1001
			 TIME_31S = 32'd1_550_000_000,
			 TIME_33S = 32'd1_650_000_000,//1010
			 TIME_34S = 32'd1_700_000_000,
			 TIME_36S = 32'd1_800_000_000,//1011
			 TIME_37S = 32'd1_850_000_000,
			 TIME_39S = 32'd1_950_000_000,//1100
			 TIME_40S = 32'd2_000_000_000,
			 TIME_42S = 32'd2_100_000_000,//1101
			 TIME_43S = 32'd2_150_000_000,
			 TIME_45S = 32'd2_250_000_000,//1110
			 TIME_46S = 32'd2_300_000_000,
			 TIME_48S = 32'd2_400_000_000;//1111

always@(posedge clk or negedge rst_n)            //250ms实现
begin
	if (rst_n == 1'b0)
		cnt_250ms <= 24'd0;                     
	else if (cnt_250ms == TIME_250MS - 1)      
		cnt_250ms <= 24'd0;                     
	else
		cnt_250ms <= cnt_250ms + 24'd1;             
end

always@(posedge clk or negedge rst_n)            //1000ms实现
begin
	if (rst_n == 1'b0)
		cnt_1s <= 24'd0;                    
	else if (cnt_1s == TIME_1S - 1)      
		cnt_1s <= 24'd0;                     
	else
		cnt_1s <= cnt_1s + 24'd1;             
end


always@(posedge clk or negedge rst_n)            //48s实现 3*16=48
begin
	if (rst_n == 1'b0)
		cnt_48s <= 32'd0;                     
	else if (cnt_48s == TIME_48S - 1)      
		cnt_48s <= 32'd0;                     
	else
		cnt_48s <= cnt_48s + 32'd1;             
end

always@(posedge clk or negedge rst_n)begin
	if (rst_n == 1'b0)
		cnt_waterfall <= 4'd0;                     
	else if(nedge)begin 
		if (cnt_waterfall == COUNT_16 - 1)      
			cnt_waterfall <= 4'd0;                     
		else
			cnt_waterfall <= cnt_waterfall + 4'd1;             
	end
end


always@(posedge clk or negedge rst_n)begin
	if (rst_n == 1'b0)
		led_r <= 4'b0000;                     
	else if(waterfall_flag)begin
		case(cnt_1s)
			0:						led_r <= 4'b0001;	//点亮LED1
			TIME_250MS-1:		led_r <= 4'b0010; //点亮LED2
			TIME_500MS-1:		led_r <= 4'b0100; //点亮LED3
			TIME_750MS-1:		led_r <= 4'b1000;	//点亮LED4
			TIME_1S-1	:		led_r <= 4'b0000;	//全部熄灭
		endcase                               
	end
	else if(nedge)begin//流水灯次数显示 第0次显示0000,第1次显示0001 ...  第15次显示1111
		case(cnt_waterfall)
			 0: led_r <= 4'b0000;		
			 1: led_r <= 4'b0001;
			 2: led_r <= 4'b0010;
			 3: led_r <= 4'b0011;
			 4: led_r <= 4'b0100;
			 5: led_r <= 4'b0101;
			 6: led_r <= 4'b0110;
			 7: led_r <= 4'b0111;
			 8: led_r <= 4'b1000;
			 9: led_r <= 4'b1001;
			10: led_r <= 4'b1010;
			11: led_r <= 4'b1011;
			12: led_r <= 4'b1100;
			13: led_r <= 4'b1101;
			14: led_r <= 4'b1110;
			15: led_r <= 4'b1111;
			default:;
		endcase
	end
end

//LED工作模式的触发信号,___| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|____| ̄ ̄|
//                          1s    2s     1s    2s    1s    2s     1s    2s     1s    2s     1s    2s    1s    2s     1s    2s
   
always@(posedge clk or negedge rst_n)begin
	if(rst_n == 1'b0)
		waterfall_flag <= 1'b1;
	else begin
		case(cnt_48s)
			0,TIME_3S-1,TIME_6S-1,TIME_9S-1,TIME_12S-1,TIME_15S-1,
			TIME_18S-1,TIME_21S-1,TIME_24S-1,TIME_27S-1,TIME_30S-1,
			TIME_33S-1,TIME_36S-1,TIME_39S-1,TIME_42S-1,TIME_45S-1,TIME_48S-1:	waterfall_flag <= 1'b1;	
			
			TIME_1S-1,TIME_4S-1,TIME_7S-1,TIME_10S-1,TIME_13S-1,
			TIME_16S-1,TIME_19S-1,TIME_22S-1,TIME_25S-1,TIME_28S-1,
			TIME_31S-1,TIME_34S-1,TIME_37S-1,TIME_40S-1,TIME_43S-1,TIME_46S-1:	waterfall_flag <= 1'b0;
			default:;
		endcase
	end
end

//waterfall_flag信号的下降沿检测
always@(posedge clk or negedge rst_n)begin
	if (rst_n == 1'b0)begin
		waterfall_flag_r 	<= 1'b0;
		waterfall_flag_rr <= 1'b0;                     
	end
	else begin
		waterfall_flag_r <= waterfall_flag;
		waterfall_flag_rr <= waterfall_flag_r;
	end
end

assign nedge = !waterfall_flag_r & waterfall_flag_rr;

//LED取反后输出
always@(posedge clk or negedge rst_n)begin
	if (rst_n == 1'b0)
		led <= 4'b1111;                     
	else 
		led <= ~led_r;
end


endmodule

从仿真结果中可以看出统计流水灯次数从0~F可以显示,满足设计要求。`在这里插入图片描述在这里插入图片描述在这里插入图片描述

	从上面两个图来看第一个方法需要更多的逻辑单元,第二种方法可以节约逻辑单元,代码量也少,一开始写完程序自己就直接上板子看结果,结果发现现象
不对,看程序又看不出啥毛病,后来还是仿真得知了不正确的地方,以后还是多学习一下仿真啊,这次就写到这里,下次再分享啦!
  • 16
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值