11-计数器
1.计数器
FPGA中一切与时间有关的电路都会使用到计数器
计数是一种最简单的基本运算,计数器就是实现这种运算的逻辑电路,计数器在数字系统中主要是对脉冲的个数进行计数,以实现测量\计数和控制的功能,同时兼有分频功能.
计数器在数字系统中应用广泛,如电子计算机的控制器中对指令地址进行计数,以便顺序取出下一条指令,在运算器中做乘法,除法运算时记下加法\减法次数;有如在数字仪器中对脉冲的计数等
计数器在FPGA最常用的时序逻辑电路,通过计数器可以知道各个信号之间的关系
计数器一般从0开始计数,计满清0或者是计到一定次数清0
2. FPGA实现
- 前0.5s处于点亮状态,后0.5s处于熄灭状态
2.1 计数器设计关键
计数器关键:什么时候开始计数,什么时候清零
- 计数器在复位信号撤销之后,时钟沿到来就可以立即进行计数
- 计数器清0:计满或者计到需要的值,哪一个值是需要计到的值?比如需要计数1s,就需要知道计数器计多少个数(50M时钟)
- f = 50MHz = 50000KHz = 50000_000(十进制)Hz,单位时间内信号进行周期性变化的次数,50000_000Hz表示单位时间内进行了50000_000次周期性变化,每次变化所用的时间为t
- t = 1/f = (1/50000_000) = 20ns -- 时钟周期,计数器每一次计数经过的时间为20ns
- 计数器需要计数的个数M:M = 1s/20ns = 5*10^7
- 计数器的最大值是:M-1(从0开始计数)
2.2 模块框图和波形图
- 计数器是对时钟信号进行计数,所以要有时钟信号,复位信号
- 引出一路输出信号到LED灯
波形图绘制非常重要,按照时钟,复位信号,输入,输出的顺序画波形图 - 设置一个中间变量进行计数,复位信号下赋予初值,复位信号撤销且在上升沿开始计数
优化 - 计数到M/2-1之后进行清零,再计数到M/2-1之后再次清0,也可以实现上述前0.5s灯亮,后0.5s灯灭,且每次计数都是M/2
- 第一种条件下计数到M-1(4999999),转化为二进制需要26bit,优化之后计数到M/2-1(2499999)需要使用25bit,节省资源量
2.3 RTL
module counter
#(
parameter CNT_MAX = 25'd24_999_999 // 例化模块的参数传递接口,参数之间加,
)
(
input wire sys_clk,
input wire sys_rst_n,
output reg led_out
);
// 声明计数器变量,位宽25bit
reg [24:0] cnt;
// 参数定义
// 计数器的计数最大值
// parameter可以用与模块内部,也可以用于参数例化模块的参数传递
parameter CNT_MAX = 25'd24_999_999; // 普通模块参数定义
// localparam CNT_MAX = 25'd24_999_999; // 只用于模块内部
counter
#(
.CNT_MAX (100); // 实例化的时候参数也写在这个位置,并且可以修改参数
)
counter_inst1 // 带有参数的模块实例化的模块名称写在参数列表之后
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.led_out (led_out)
);
counter
#(
.CNT_MAX (100); // 实例化的时候参数也写在这个位置,并且可以修改参数
) // 多个模块实例化可以传递不同的参数
counter_inst1
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.led_out (led_out)
);
endmodule
module counter
#(
parameter CNT_MAX = 25'd24_999_999; // 例化模块的参数传递接口
)
(
input wire sys_clk,
input wire sys_rst_n,
output reg led_out
);
// 声明计数器变量,位宽25bit
reg [24:0] cnt;
// 计数器变量赋值
always @ (posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
cnt <= 25'd0;
end
else if(cnt = CNT_MAX)
begin
cnt <= 25'b0;
end
else
cnt <= cnt + 25'd1;
end
// 输出信号赋值
always@(posedge sys_clk pr negedge sys_clk_n) begin
if(sys_rst_n == 1'b0)
led_out <= 1'b0;
else if(cnt == CNT_MAX)
led_out <= ~led_out;
else
led_out <= led_out;
end
endmodule
- 编译代码
2.4 Testbench
`timescale 1ns/1ns
module tb_counter();
reg sys_clk;
reg sys_rst_n;
wire led_out;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20;
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
counter
#(
.CNT_MAX (25'd24)
)
counter_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.led_out (led_out)
);
endmodule
2.5 上板验证
2.6 其他方式实现
- 当计数器计满之后,添加一个cnt_reg变量,给这个变量的信号产生一个周期的脉冲(拉高一个周期),然后拉低
- cnt信号正常
- led_out信号在检测到cnt_flag信号为高电平的时候,会进行一次翻转
- 使用脉冲标志信号cnt_flag,当计数器计数到M/2-2的时候,在下一个周期(M/2-1)周期上升沿产生一个脉冲,同样在cnt_flag信号拉高的时候led_out进行翻转,这样就能实现led_out和计数信号同步,没有延迟周期
引入脉冲标志信号cnt_flag,可以简化if中的条件语句,在进行复杂设计的时候可以节省资源
module counter
#(
parameter CNT_MAX = 25'd24_999_999; // 例化模块的参数传递接口
)
(
input wire sys_clk,
input wire sys_rst_n,
output reg led_out
);
// 声明计数器变量,位宽25bit
reg [24:0] cnt;
reg cnt_flag;
// 计数器变量赋值
always @ (posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
begin
cnt <= 25'd0;
end
else if(cnt = CNT_MAX)
begin
cnt <= 25'b0;
end
else
cnt <= cnt + 25'd1;
end
// 计数器标志位赋值
always @ (posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_flag <= 1'b0;
else if(cnt ==(CNT_MAX - 25'd1))
cnt_flag <= 1'b1;
else
cnt_flag <= 1'b0;
// 输出信号赋值
always@(posedge sys_clk pr negedge sys_clk_n) begin
if(sys_rst_n == 1'b0)
led_out <= 1'b0;
else if(cnt_flag == 1'b1)
led_out <= ~led_out;
else
led_out <= led_out;
end
endmodule