计数器 verilog FPGA 基础练习3

计数器 FPGA verilog 基础练习3

发现问题,用技术解决问题。兴趣是自己的源动力 !

目录

    • 计数器 FPGA verilog 基础练习3
  • 前言
  • 一、verilog语法
  • 二、计数器的使用
    • 2.1 LED闪缩的解决方案
    • 2.2 代码
    • 2.3 仿真结果
  • 总结

前言

RTL语言中计数器是相当重要的。可以说计数器,是FPGA实现各种功能的灵魂,让时间变得有了意义。


一、verilog语法

在学习计数器之前,建议数量掌握时序逻辑电路的语法知识,不然在使用编写计数器时,会有很多困惑。如果出现以下困惑,建议观看链接的资料。

困惑1:不明白阻塞赋值和非阻塞赋值的区别。
困惑2:在设计计数器时,在时钟上升沿采样的值到底是多少?是时钟上升沿正对的值,还是偏左边的值,还是偏右边的值。
**如果出现上面两个困惑,建议看看链接的视频:挑战每天1条verilog语法-028:testbench中信号赋值时机的小建议

另外,如果语法还没有熟练掌握,可以看下面的教学网站,非常推荐
Verilog 教程

二、计数器的使用

提出一个计数器的使用场景:计数器计数1s时间间隔,来实现led灯每隔1s闪烁一次的效果。计数的时钟用系统时钟50MHz,那么使用计数器在该时钟频率下,需要技术(50*10^6 - 1)次(因为是从0开始计数的)。另外要考虑资源问题,可以从以下两个方面考虑。

  • 设计的计数器变量是否存在对称性使用的条件?
  • 设计的变量如果位宽很大,是否要引入flag信号来标志?因为一个位宽很大的变量多次去使用,会浪费资源。结合下面的代码就可以理解了

2.1 LED闪缩的解决方案

  • 先考虑计数器是否存在对称使用的可能性:可以只计数到0.5s后对开关取反,这样就从49_999_999的最大计数值(位宽为25bit),变成29_999_999(位宽为24bit)
  • 使用flag信号来标志

2.2 代码

计数器代码如下:

module counter 
#(
	parameter CNT_MAX = 24'd24_999_999
)
(
 input wire sys_clk , //系统时钟50MHz
 input wire sys_rst_n , //全局复位

 output reg led_out //输出控制led灯
);

 reg [23:0] cnt ; //经计算得需要25位宽的寄存器才够500ms
 reg cnt_flag;
 
// =========================
// 注意:每一个always只输出一个reg变量,或者或一个reg变量只能在一个always理输出
// 除非这个变量只输出一次。
// =========================

//cnt:计数器计数,当计数到 CNT_MAX 的值时清零
always@(posedge sys_clk or negedge sys_rst_n) begin
	if(sys_rst_n == 1'b0)
		cnt <= 24'b0;  		// 注意时序逻辑要使用阻塞赋值
	else if(cnt == CNT_MAX) 
		cnt <= 25'b0;
	else
		cnt <= cnt + 1'b1;
end
		
// cnt_flag:计数到最大值产生的标志信号,每当计数满标志信号有效时取反
// 如果多个地方都要用到这个计数器,那么就会去调用多次这个24bit的cnt,会浪费资源
always@(posedge sys_clk or negedge sys_rst_n) begin
	if(sys_rst_n == 1'b0)
		cnt_flag <= 1'b0;
	else if(cnt == CNT_MAX - 24'b1)  // 这里要有一个减1的操作,cnt_flag会延迟一拍输出,参考下面波形理解
		cnt_flag <= 1'b1;
	else
		cnt_flag <= 1'b0;
end

//led_out:输出控制一个LED灯
always@(posedge sys_clk or negedge sys_rst_n) begin
	if(sys_rst_n == 1'b0)
		led_out <= 1'b0;
	else if(cnt_flag == 1'b1)
		led_out <= ~led_out;

end
endmodule

tb代码如下:

`timescale 1ns/1ns
module tb_top();

//reg define
reg sys_clk;
reg sys_rst_n;

//wire define
wire led_out;

 //初始化输入信号
 initial begin
 sys_clk = 1'b1;
 sys_rst_n <= 1'b0;
 #20
 sys_rst_n <= 1'b1;
 end

 //sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
 always #10 sys_clk = ~sys_clk;

 //---------------------flip_flop_inst----------------------
 // 这里参数写24,即25个时钟周期一个反转(一个时钟周期20ns),即500ns 一次亮,500ns一次灭
 counter
 #(
 .CNT_MAX (24'd24 ) //实例化带参数的模块时要注意格式,当我们想要修改常数在
 //当前模块的值时,直接在实例化参数名后面的括号内修改即可
 )
 counter_inst(
 .sys_clk (sys_clk ), //input sys_clk
 .sys_rst_n (sys_rst_n ), //input sys_rst_n

 .led_out (led_out ) //output led_out
 );

 endmodule

2.3 仿真结果

在这里插入图片描述在这里插入图片描述

note 1:在刚刚拉高flag为1的那个时钟上升沿,采样cnt的值是23, 在该上升沿之后,flag变化为24,在此时,时间过去了24*20ns = 480ns(时间是过程量,当计数到24时,只是时刻到了24(时刻是状态量)),此时还没有到500ns,
note 2:当flag变为0时的时钟上升沿,此时时间过去了25 *20ns = 500ns,此时采样flag为1,则立刻会拉高led;

看下面代码中cnt_flag == 1’b1,因为延迟flag会延迟一拍,所以下一个采样时,刚好完成了最后20ns的过程量变化,到达500ns, 此时拉高led
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1’b0)
led_out <= 1’b0;
else if(cnt_flag == 1’b1)
led_out <= ~led_out;

小结:只要记住,计数时间时,当前采样到的flag值,是已经完成当前flag值对应的时间过程的变化。即,如果clk周期为20ns,当采样到cnt为1时,其实时间已经计数40ns了。

  • 这里会解释的有点啰嗦,我一直对时间这个状态量和过程量没搞清楚过,就像1点和5点之间到底隔了几个小时(如果是时钟按照1小时一次来采样的话,其实是5个小时吧),所以这里多记录下自己的想法。

总结

核心思想:计数器就是实现各种功能的灵魂,目的就是掌握对时间的把控!

  • 欢迎一起交流学习,如有错误之处,还请各位指正。

参考资料

[1] 语法学习1
[2] 语法学习2
[3] 野火】FPGA系列教学

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值