【FPGA零基础学习之旅#3】时序逻辑电路设计-计数器设计和闪烁LED灯

🎉欢迎来到FPGA专栏~时序逻辑电路设计-计数器设计和闪烁LED灯


  • ☆* o(≧▽≦)o *☆~我是小夏与酒🍹
  • 博客主页:小夏与酒的博客
  • 🎈该系列文章专栏:FPGA学习之旅
  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
  • 📜 欢迎大家关注! ❤️
    FPGQ2

CSDN

遇见未来

一、前言

🥝实现效果

1、使板载LED灯每500ms翻转一次电平:
1
2、板载LED流水灯:
2

🥝时序逻辑电路基本概念

数字电路根据逻辑功能的不同特点,可以分成两大类,一类叫组合逻辑电路(简称组合电路),另一类叫做时序逻辑电路(简称时序电路)。组合逻辑电路在逻辑功能上的特点是任意时刻的输出仅仅取决于该时刻的输入,与电路原来的状态无关。而时序逻辑电路在逻辑功能上的特点是任意时刻的输出不仅取决于当时的输入信号,而且还取决于电路原来的状态,或者说,还与以前的输入有关。
更多内容请参考:时序逻辑电路

🥝计数器工作原理

1、AC620开发板上的晶振输出时钟频率为50MHz,即时钟周期为20ns。我们要使LED每500ms翻转一次,即需要经过500000000ns÷20ns=25000000个时钟周期的时间LED才翻转电平。
2、因此,我们需要设计一个计数器使其计数25000000次。需要一个至少25位的计数器(224<25000000<225),达到计数最大值时清零,并重新开始计数。
3、计数器的核心元件是触发器。

📜简单计数器原理参考下图:
计数器原理

二、实验过程

🍍编写计数器HDL文件

module led_flash(

//端口列表
	Clk50M,
	Rst_n,
	led
);

//端口定义
	input Clk50M;
	input Rst_n;
	
	output reg [3:0]led;//通过寄存器输出
	
//使用reg语句来定义一个寄存器
	reg [24:0]cnt;
	
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		cnt <= 25'd0;//非阻塞赋值方式
	else if(cnt == 25'd24_999_999)
		cnt <=25'd0;
	else
		cnt <= cnt + 1'b1;
		
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		led <= 4'b1111;//led全灭
	else if(cnt == 25'd24_999_999)
		led <= ~led;
	else
		led <= led;
	
endmodule

🍍编写测试脚本

`timescale 1ns/1ns
`define clock_period 20

module led_flash_tb;

	//激励源定义为reg类型
	reg Clk50M;
	reg Rst_n;
	
	wire [3:0]led;
	
	//被测试模块
	led_flash led_flash0(
		.Clk50M(Clk50M),
		.Rst_n(Rst_n),
		.led(led)
	);
	
	//生成50M时钟信号
	
	initial Clk50M = 1;
	always #(`clock_period/2) Clk50M = ~Clk50M;

	initial begin
		Rst_n = 0;
		
		#(`clock_period*20 + 1) Rst_n = 1;
		
		#(`clock_period*25000000 +1);
		$stop;
		
	end
	
endmodule

🍍功能仿真

为了减少仿真所需要的时间,并且能够展现出设计好的功能,将HDL文件中的代码进行微小更改,即对计数器的值进行更改cnt == 25'd24_99

module led_flash(

//端口列表
	Clk50M,
	Rst_n,
	led
);

//端口定义
	input Clk50M;
	input Rst_n;
	
	output reg [3:0]led;//通过寄存器输出
	
//使用reg语句来定义一个寄存器
	reg [24:0]cnt;
	
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		cnt <= 25'd0;//非阻塞赋值方式
	else if(cnt == 25'd24_99)
		cnt <=25'd0;
	else
		cnt <= cnt + 1'b1;
		
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		led <= 4'b1111;//led全灭
	else if(cnt == 25'd24_99)
		led <= ~led;
	else
		led <= led;
	
endmodule

仿真结果:
1、 当计数器从2499变为0的时候,LED的电平进行了翻转:
功能仿真1
2、 计数器从0变为2499时,经过了2500×20ns=50000ns
功能仿真2
如果将cnt == 25'd24_99改为cnt == 25'd24_999_999的话,计数器从0变为24999999时,经过了25000000×20ns=500000000ns=500ms,说明计数器的功能设计正确。

Verilog中,我们还可以按照以下的写法(参数化设计),方便我们进行仿真和修改:
HDL文件:

module led_flash(

//端口列表
	Clk50M,
	Rst_n,
	led
);

//端口定义
	input Clk50M;
	input Rst_n;
	
	output reg [3:0]led;//通过寄存器输出
	
//使用reg语句来定义一个寄存器
	reg [24:0]cnt;
	
	parameter CNT_MAX = 25'd24_999_999;
	
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		cnt <= 25'd0;//非阻塞赋值方式
	else if(cnt == CNT_MAX)
		cnt <=25'd0;
	else
		cnt <= cnt + 1'b1;
		
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		led <= 4'b1111;//led全灭
	else if(cnt == CNT_MAX)
		led <= ~led;
	else
		led <= led;
	
endmodule

testbench文件:

`timescale 1ns/1ns
`define clock_period 20

module led_flash_tb;

	//激励源定义为reg类型
	reg Clk50M;
	reg Rst_n;
	
	wire [3:0]led;
	
	//被测试模块
	led_flash 
	#(
		.CNT_MAX(25'd249)
	)
	led_flash0(
		.Clk50M(Clk50M),
		.Rst_n(Rst_n),
		.led(led)
	);
	
	//生成50M时钟信号
	
	initial Clk50M = 1;
	always #(`clock_period/2) Clk50M = ~Clk50M;

	initial begin
		Rst_n = 0;
		
		#(`clock_period*20 + 1) Rst_n = 1;
		
		#(`clock_period*25000000 +1);
		$stop;
		
	end
		
endmodule

仿真结果:
时序仿真1

计数器从0变为249时,经过了250×20ns=5000ns
功能仿真3

注意:当我们在全编译的时候,CNT_MAX = 25'd24_999_999

此时的RTL视图:
RTL2

🍍时序仿真

在进行时序仿真时,参数化设计失效并会在仿真时报错:
报错
📜即参数化设计只支持前方真(功能仿真)

我们更改代码,HDL文件:

module led_flash(

//端口列表
	Clk50M,
	Rst_n,
	led
);

//端口定义
	input Clk50M;
	input Rst_n;
	
	output reg [3:0]led;//通过寄存器输出
	
//使用reg语句来定义一个寄存器
	reg [24:0]cnt;
	
	parameter CNT_MAX = 25'd24_9;
	
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		cnt <= 25'd0;//非阻塞赋值方式
	else if(cnt == CNT_MAX)
		cnt <=25'd0;
	else
		cnt <= cnt + 1'b1;
		
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		led <= 4'b1111;//led全灭
	else if(cnt == CNT_MAX)
		led <= ~led;
	else
		led <= led;
	
endmodule

testbench文件:

`timescale 1ns/1ns
`define clock_period 20

module led_flash_tb;

	//激励源定义为reg类型
	reg Clk50M;
	reg Rst_n;
	
	wire [3:0]led;
	
	//被测试模块
//	led_flash 
//	#(
//		.CNT_MAX(25'd249)
//	)
//	led_flash0(
//		.Clk50M(Clk50M),
//		.Rst_n(Rst_n),
//		.led(led)
//	);

	led_flash led_flash0(
		.Clk50M(Clk50M),
		.Rst_n(Rst_n),
		.led(led)
	);
	
	//生成50M时钟信号
	
	initial Clk50M = 1;
	always #(`clock_period/2) Clk50M = ~Clk50M;

	initial begin
		Rst_n = 0;
		
		#(`clock_period*20 + 1) Rst_n = 1;
		
		#(`clock_period*25000000 +1);
		$stop;
		
	end
		
endmodule

布局布线造成的延迟
延迟
出现中间态,该部分的时间远小于一个时钟周期:
中间态1
中间态2

时序逻辑将只会在时钟的边沿进行采样。
如果时钟沿恰好在中间态读取到信号,将会造成逻辑功能设计失败。
时序逻辑可以消除组合逻辑产生的毛刺。

🍍板级验证

注意首先需要修改代码:parameter CNT_MAX = 25'd24_999_999;
1、引脚分配:
使用自动化脚本分配引脚:
脚本1
Tcl脚本内容如下:

set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to Clk50M
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to Rst_n
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to led[0]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to led[1]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to led[2]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to led[3]

set_location_assignment PIN_E1 -to Clk50M
set_location_assignment PIN_E16 -to Rst_n
set_location_assignment PIN_A2 -to led[0]
set_location_assignment PIN_B3 -to led[1]
set_location_assignment PIN_A4 -to led[2]
set_location_assignment PIN_A3 -to led[3]

执行该脚本后,Pin Planner如下:
脚本2

2、实现效果:
全编译之后,将程序下载到开发板中,实现效果如下:
1

三、扩展学习

📜设计流水灯,使4个LED灯按照流水灯的方式循环闪烁。

🍋移位操作

module led_flash(

//端口列表
	Clk50M,
	Rst_n,
	led
);

//端口定义
	input Clk50M;
	input Rst_n;
	
	output [3:0]led;//通过寄存器输出
	
//使用reg语句来定义一个寄存器
	reg [24:0]cnt;
	
	parameter CNT_MAX = 25'd24_999_999;
	
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		cnt <= 25'd0;//非阻塞赋值方式
	else if(cnt == CNT_MAX)
		cnt <=25'd0;
	else
		cnt <= cnt + 1'b1;
		
	reg [3:0]led_r;
	
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		led_r <= 4'b0001;
	else if(cnt == CNT_MAX)begin
		if(led_r == 4'b1000)
			led_r <= 4'b0001;
		else
		led_r <= led_r << 1;
	end
	else
		led_r <= led_r;
	
	assign led = ~led_r;
	
endmodule

🍋位拼接操作

module led_flash(

//端口列表
	Clk50M,
	Rst_n,
	led
);

//端口定义
	input Clk50M;
	input Rst_n;
	
	output [3:0]led;//通过寄存器输出
	
//使用reg语句来定义一个寄存器
	reg [24:0]cnt;
	
	parameter CNT_MAX = 25'd24_999_999;
	
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		cnt <= 25'd0;//非阻塞赋值方式
	else if(cnt == CNT_MAX)
		cnt <=25'd0;
	else
		cnt <= cnt + 1'b1;
		
	reg [3:0]led_r;
	
	always@(posedge Clk50M or negedge Rst_n)
	if(!Rst_n)
		led_r <= 4'b0001;
	else if(cnt == CNT_MAX)
		led_r <= {led_r[2:0],led_r[3]};
	else
		led_r <= led_r;
	
	assign led = ~led_r;
	
endmodule

🍋实现效果

两种编程方式的实现效果相同:
light

csdn

🧸结尾


  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小夏与酒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值