🎉欢迎来到FPGA专栏~时序逻辑电路设计-计数器设计和闪烁LED灯
🎉【FPGA】时序逻辑电路设计目录
一、前言
🥝实现效果
1、使板载LED灯每500ms翻转一次电平:
2、板载LED流水灯:
🥝时序逻辑电路基本概念
数字电路根据逻辑功能的不同特点,可以分成两大类,一类叫组合逻辑电路(简称组合电路),另一类叫做时序逻辑电路(简称时序电路)。组合逻辑电路在逻辑功能上的特点是任意时刻的输出仅仅取决于该时刻的输入,与电路原来的状态无关。而时序逻辑电路在逻辑功能上的特点是任意时刻的输出不仅取决于当时的输入信号,而且还取决于电路原来的状态,或者说,还与以前的输入有关。
更多内容请参考:时序逻辑电路
🥝计数器工作原理
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的电平进行了翻转:
2、 计数器从0变为2499时,经过了2500×20ns=50000ns
:
如果将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
仿真结果:
计数器从0变为249时,经过了250×20ns=5000ns
:
✨注意:当我们在全编译的时候,CNT_MAX = 25'd24_999_999
。
此时的RTL视图:
🍍时序仿真
在进行时序仿真时,参数化设计失效并会在仿真时报错:
📜即参数化设计只支持前方真(功能仿真)。
我们更改代码,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
布局布线造成的延迟:
出现中间态,该部分的时间远小于一个时钟周期:
时序逻辑将只会在时钟的边沿进行采样。
如果时钟沿恰好在中间态读取到信号,将会造成逻辑功能设计失败。
时序逻辑可以消除组合逻辑产生的毛刺。
🍍板级验证
注意首先需要修改代码:parameter CNT_MAX = 25'd24_999_999;
。
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、实现效果:
全编译之后,将程序下载到开发板中,实现效果如下:
三、扩展学习
📜设计流水灯,使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
🍋实现效果
两种编程方式的实现效果相同:
🧸结尾