时序逻辑电路设计之计数器

描述

前面已经学习了时序逻辑电路中的基本单元:触发器,这次就用其来整点活,实现计数器的设计,计数器可以说是任何和时序有关的设计都会用到他。

一. 计数器原理

计数器的功能主要就是计数,在数字系统中主要对脉冲的个数进行计数,以实现测量,计数和控制的功能,同时也可以做到分频等功能。 同时,其也是FPGA设计中最常用的一种时序逻辑电路,根据计数器的计数值可以精确控制各个信号的状态。

二. 实用

用计数器计数1s间隔,实现led灯每隔1s闪烁的结果。

本设计十分简单,用一个模块附带简单的输入输出便可以完成。 如下所示:

led

sys_clk:输入时钟,为系统时钟

sys_rst_n:复位信号,低电平有效

led_out:输出信号,控制LED灯的亮灭

我们的逻辑是这样:计数器进行计数,计数到1s便给LED灯赋值,让其发亮,就可以达到1s亮灭的结果。 所以需要计算一下,计1s需要计数多少次,如果时钟是50MHz,也就是0.000_000_02s,计数一秒需要计数50_000_000个数才可以,不过在设计时刻视为从0开始计数,所以是49_999_999个。

接下来的设计时,为了节省寄存器资源,只计数24_999_999个,也就是1s内闪烁一次(0.5秒一次)。 这需要25位宽的计数器。

led

下面介绍两种方式的计数器; 带标志位和不带标志位。

不带标志位 :设置一个cnt信号位,时钟的每个上升沿到来时刻,cnt便自动+1,当计数器计到24_999_999的时候,清0,led_out信号取反,只要不复位,变一直计数下去。

带标志位: 在上面方案的基础上,添加一个cnt_flag信号,当计数器计数到24_999_999的时候先不取反,而是让cnt_flag产生一个高脉冲,当led_out 的信号检测到cnt_flag拉高的时候取反。

关于标志位有什么用,先挖个坑,以后说。

led

图1

led

图2

注意一下上面两个图(取自野火教材),标志位拉高的时刻并不一样,下面的图在N-1时刻拉高,led_out的状态是刚刚好在0时刻转换(慢一拍),上面的图实际上到后来是计数计多了。 对于这种简单的设计这个问题不痛不痒,但是对于一些要求比较高的设计来说,一秒也不能差,所以这个地方需要注意。

下面给出代码(带信号标志位)

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


 output reg led_out //输出控制 led 灯
 );
 
 //reg define
 reg [24:0] cnt ; //经计算得需要 25 位宽的寄存器才够 500ms
 reg cnt_flag;
 
 //cnt:计数器计数,当计数到 CNT_MAX 的值时清零
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 cnt <= 25'b0;
 else if(cnt == CNT_MAX)
 cnt <= 25'b0;
 else
 cnt <= cnt + 1'b1;
 
 //cnt_flag:计数到最大值产生的标志信号,每当计数满标志信号有效时取反
 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'b1)
 cnt_flag <= 1'b1;
 else
 cnt_flag <= 1'b0;
 
 //led_out:输出控制一个 LED 灯
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 led_out <= 1'b0;
 else if(cnt_flag == 1'b1)
 led_out <= ~led_out;
 
 endmodule

测试平台:

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

仿真波形:

led

到这里,问题还没解决,在7系列的FPGA的板上,并不输出单端口的时钟,其是200MHz的差分时钟。

led

AD11和AD12差分时钟输出

经过查阅,调用PLL锁相环的IP核进行差分时钟的单端口时钟输出。

led

调用时钟IP核,并且选择PLL,在下面选择差分时钟接口。 output时钟设置50MHz。

重新写顶层代码,例化时钟。

module counter




(
//input wire sys_clk,


input wire sys_rst_n,
input clk_p,
input clk_n,


output  reg led_out
    );


reg [24:0] cnt;
reg        cnt_flag;
parameter CNT_MAX = 25'd24_999_999;


wire locked;
wire sys_clk;


clk_wiz_0 diff_2_single
(
    .clk_out1(sys_clk),


    .reset(1'b0),
    .locked(locked),
    .clk_in1_p(clk_p),
    .clk_in1_n(clk_n)
);








//cnt:计数器计数,当计数到cnt_max的时候值清0
always @(posedge sys_clk or negedge sys_rst_n) 
    if (sys_rst_n == 1'b0) 
        cnt <= 25'b0;
    else if(cnt == CNT_MAX)
        cnt <= 25'b0;
    else
        cnt <= cnt+1'b1;


//cnt_flag:计数到最大值产生的标志位信号,每当计数满标志信号有效时取反
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'b1)
        cnt_flag <= 1'b1;
    else
        cnt_flag <= 1'b0; 




always @(posedge sys_clk or negedge sys_rst_n)
    if (sys_rst_n == 1'b0)
        led_out <= 1'b0;
    else if (cnt_flag == 1'b1)
        led_out <= ~led_out;    


endmodule

RTL电路如下所示:

led

关于kintex7差分时钟引脚约束的问题需要注意:

这个地方只需要约束其中之一即可,另外一个不用管,如果是不清楚电平标准可以在Tcl控制台输入指令查询:

led

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分