计数器的Verilog实现

一、计数器简介

        计数器是一种在数字电子系统中常见的逻辑电路,用于记录输入脉冲或时钟信号的数量。计数器的作用是在输入信号发生变化时,将一个内部计数值递增或递减。通常用于测量时间、频率、脉冲数量等。

        常见的计数器有:

  1. 递增计数器(Up Counter): 每次输入一个时钟脉冲时,计数器的值递增。当达到最大计数值时,计数器可以溢出,重新从零开始。

  2. 递减计数器(Down Counter): 每次输入一个时钟脉冲时,计数器的值递减。当达到最小计数值时,计数器可以溢出,重新从最大值开始。

  3. 同步计数器(Synchronous Counter): 计数操作与时钟信号同步进行。所有触发器在同一时钟边沿触发。

  4. 异步计数器(Asynchronous Counter): 计数操作不一定与时钟信号同步。不同阶段的触发器可以在不同时钟边沿触发。

  5. 可编程计数器: 允许用户根据需要编程计数器的初始值、最大值、最小值等参数。

  6. 环形计数器(Ring Counter): 一种特殊的计数器,只有一个触发器处于“1”的状态,其余为“0”,而且这个“1”在每个时钟脉冲中移动。

        本文按照明德杨的框架详细阐述如何编写计数器Verilog代码。

二、计数器代码规则

        在构建计数器时,只需要考虑三部分,即初值,计数条件和结束值。初值最好设为0,计数值为计数个数减1。在明德杨规范中,计数条件写为aa_cnt,结束计数条件为add_cnt&&cnt==x-1。即当计数到x且满足计数条件时结束计数,增加add_cnt这一条件的目的在于区分复位值和初始默认值(在初始时认为计数条件不满足,即逻辑值为0)。同时明德杨还规定在限定范围时,最好使用>=和<符号,避免对于边界值的考虑。例如要取前6个数,则cnt>=0&&cnt<6,从5开始取10个数,cnt>=5&&cnt<10+5。

以下题为例:

 要求计数器从0到9之间计数,上升沿触发,同步复位,复位到0。计数模块的代码编写如下:

 always@(posedge clk)
        begin
            if(reset)
                q<=4'b0000;
            else if(add_cnt)//计数条件
                begin
                    if(end_cnt)//计数结束条件
                        q<=4'b0000;
                    else
                        q<=q+1;
                end
        end

 接下来对其中的计数条件和结束计数条件作描述

always@(posedge clk)
        begin
            add_cnt=1'b1;
        end
    assign end_cnt=add_cnt&&(q==10-1);

三、示例(时钟)

大致中文翻译为:

创建一组适用于12小时制时钟(带有AM/PM指示器)的计数器。您的计数器由一个快速运行的时钟信号(clk)驱动,每当时钟应该递增时(即每秒一次),ena信号会脉冲一次。

reset信号将时钟复位为12:00 AM。pm信号用于表示AM为0,PM为1。hh、mm和ss分别表示小时(01-12)、分钟(00-59)和秒(00-59),每个都是用二进制编码十进制(BCD)表示。在不启用时,复位具有更高的优先级,并且可以在未启用时发生。

以下时序图显示了从上午11:59:59滚动到下午12:00:00的行为,以及同步复位和启用的行为。

设计计数结构

要实现时钟计数,则需要构建三个计数模块,时、分、秒。其中每个计数模块中要分为十位和个位,个位的计数范围为0~9,十位的计数范围为0~6,综合来看,分、秒的计数范围都为0~59,时的计数范围为0~11。

秒的个位计数条件为使能信号ena脉冲,即add_s_ones=ena。结束条件为end_s_ones=add_s_ones&&(s_ones==4'd9)。十位计数条件为add_s_tens=end_s_ones,结束条件为end_s_tens=add_s_tens&&(s_tens==4'd5)。即当秒钟的个位计数到9时,复位到0,同时十位计数1;

分的个位计数条件为add_m_ones=end_s_tens,结束条件为end_m_tens=add_m_ones&&(s_ones==4'd9)。十位的计数条件为add_m_tens=end_s_ones,结束条件为end_m_tens=add_m_tens&&(m_tens==4'd5);

对于时位的设计稍显复杂。当时位是0时,个位可以由0增加到9,个位再复位到0,同时十位增加到1,当时位是1,个位只能由0增加到2,然后十位个位都复位到0。因此十位为0 时,个位的结束条件为end_h_ones_0=add_h_ones&&(h_ones==4'd9),十位为1时个位的结束条件为end_h_ones_1=add_h_ones && ((h_ones == 4'd2) && (h_tens == 4'd1))。当个位为2十位为1时,结束十位为1 的状态,因此有end_h_tens_1 = add_h_tens && end_h_ones_1。当个位为9时,结束十位为0的状态,因此有end_h_tens_0 =add_h_tens && end_h_ones_0。

代码实现

1.变量设定

    reg 	    pm_temp;//区分上午下午
    reg [3:0]   s_ones;//秒的个位
    reg [3:0]   s_tens;//秒的十位
    reg [3:0]   m_ones;//分的个位
    reg [3:0]   m_tens;//分的十位
    reg [3:0]   h_ones;//时的个位
    reg [3:0]   h_tens;//时的十位
      
    wire		add_s_ones;//秒的个位开始计数条件
    wire		end_s_ones;//秒的个位结束计数条件
    wire		add_s_tens;//秒的十位开始计数条件
    wire		end_s_tens;//秒的十位结束计数条件
    wire		add_m_ones;//分的个位开始计数条件
    wire		end_m_ones;//分的个位结束计数条件
    wire		add_m_tens;//分的十位开始计数条件
    wire		end_m_tens;//分的十位结束计数条件
    wire		add_h_ones;//时的个位开始计数条件
    wire		end_h_ones;//时的个位结束计数条件
    wire		add_h_tens;//时的十位开始计数条件
    wire		end_h_tens;//时的十位结束计数条件
    wire        end_h_ones_0;//时的十位为0个位为9时结束计数
    wire        end_h_ones_1;//时的十位为1个位为2时结束结束
    wire        end_h_tens_0;//在个位为9时,结束十位为0的状态
	wire        end_h_tens_1;//在个位为2十位为1时,结束十位为1的状态
    wire		pm_ding;//区分上午下午

2、秒钟代码块实现

    //秒的个位
    always @(posedge clk)
        begin
            if(reset)
                	s_ones <= 4'b0;
            else if(add_s_ones)
                begin
                    if(end_s_ones)
                        	s_ones <= 4'b0;
                    else 
                        	s_ones <= s_ones + 4'b1;
                end
        end
    assign add_s_ones=ena;
    assign end_s_ones=add_s_ones&&(s_ones==4'd9);
    
    //秒的十位
    always@(posedge clk)
        begin
            if(reset)
            	s_tens<=4'b0;
            else if(add_s_tens)
                begin
                    if(end_s_tens)
                        s_tens<=4'd0;
                    else
                        s_tens<=s_tens+4'b1;
                end
        end
    assign add_s_tens=end_s_ones;
    assign end_s_tens=add_s_tens&&(s_tens==5);

3、分钟代码块实现

    //分的个位
    always @(posedge clk)
        begin
          if(reset)
              m_ones <= 4'b0;
          else if(add_m_ones)
              begin
                  if(end_m_ones)
                      m_ones <= 4'b0;
                  else 
                      m_ones <= m_ones + 4'b1;
         	  end
  		  end
    assign add_m_ones = end_s_tens;
    assign end_m_ones = add_m_ones && (m_ones == 4'd9);
    
    //分的十位
  	always @(posedge clk)
        begin
          if(reset)
              m_tens <= 4'b0;
          else if(add_m_tens)
              begin
                  if(end_m_tens)
                      m_tens <= 4'b0;
                  else 
                      m_tens <= m_tens + 4'b1;
              end
        end
    assign add_m_tens = end_m_ones;
    assign end_m_tens = add_m_tens && (m_tens == 4'd5);

4、时钟的实现

    //时钟个位的实现
    always @(posedge clk)
        begin
            if(reset)
                h_ones <= 4'd2;
            else if(add_h_ones)
                begin
                    if(end_h_ones_0)
                        h_ones <= 4'b0;//如果十位为0,则复位到0
                    else if(end_h_ones_1)
                        h_ones <= 4'b1;//如果十位为1,则复位到1,出现11:59:59
                    else 
                        h_ones <= h_ones+4'b1;
            	end
  		 end
    assign add_h_ones=end_m_tens;
    assign end_h_ones_0=add_h_ones&&(h_ones==4'd9);
    assign end_h_ones_1=add_h_ones && ((h_ones == 4'd2) && (h_tens == 4'd1));
    
    //时钟十位的实现
  	always @(posedge clk)
        begin
          if(reset)
              h_tens <= 4'd1;
            else if(add_h_tens)
              begin
                  if(end_h_tens_0)
                      h_tens <= 4'b1;
                  else if(end_h_tens_1)
                      h_tens <= 4'b0;
              end
        end
    assign add_h_tens = end_m_tens;
    assign end_h_tens_1 = add_h_tens && end_h_ones_1;//在个位为2十位为1时,结束十位为1的状态
    assign end_h_tens_0 =add_h_tens && end_h_ones_0;//在个位为9时,结束十位为0的状态

5、上午下午的区分

本部分参考另一位大佬的代码@日拱一卒_未来可期

    //上午下午的区分
    always@(posedge clk)
        begin
            if(reset)
                pm_temp <= 1'b0;
            else if(pm_ding)
                pm_temp <= ~pm_temp;
		end
	assign pm_ding = h_tens == 4'd1 && h_ones == 4'd1 && end_m_tens;

6、输出的实现

    always@(*)
        begin
            pm=pm_temp;
            s={s_tens,s_ones};
            m={m_tens,m_ones};
            h={h_tens,h_ones};
        end

7、完整代码

module top_module(
    input clk,
    input reset,
    input ena,
    output pm,
    output [7:0] hh,
    output [7:0] mm,
    output [7:0] ss); 

    reg 	    pm_temp;//区分上午下午
    reg [3:0]   s_ones;//秒的个位
    reg [3:0]   s_tens;//秒的十位
    reg [3:0]   m_ones;//分的个位
    reg [3:0]   m_tens;//分的十位
    reg [3:0]   h_ones;//时的个位
    reg [3:0]   h_tens;//时的十位
      
    wire		add_s_ones;//秒的个位开始计数条件
    wire		end_s_ones;//秒的个位结束计数条件
    wire		add_s_tens;//秒的十位开始计数条件
    wire		end_s_tens;//秒的十位结束计数条件
    wire		add_m_ones;//分的个位开始计数条件
    wire		end_m_ones;//分的个位结束计数条件
    wire		add_m_tens;//分的十位开始计数条件
    wire		end_m_tens;//分的十位结束计数条件
    wire		add_h_ones;//时的个位开始计数条件
    wire		end_h_ones;//时的个位结束计数条件
    wire		add_h_tens;//时的十位开始计数条件
    wire		end_h_tens;//时的十位结束计数条件
    wire        end_h_ones_0;//时的十位为0个位为9时结束计数
    wire        end_h_ones_1;//时的十位为1个位为2时结束结束
    wire        end_h_tens_0;//在个位为9时,结束十位为0的状态
	wire        end_h_tens_1;//在个位为2十位为1时,结束十位为1的状态
    wire		pm_ding;//区分上午下午
    
    
    
    //秒的个位
    always @(posedge clk)
        begin
            if(reset)
                	s_ones <= 4'b0;
            else if(add_s_ones)
                begin
                    if(end_s_ones)
                        	s_ones <= 4'b0;
                    else 
                        	s_ones <= s_ones + 4'b1;
                end
        end
    assign add_s_ones=ena;
    assign end_s_ones=add_s_ones&&(s_ones==4'd9);
    
    //秒的十位
    always@(posedge clk)
        begin
            if(reset)
            	s_tens<=4'b0;
            else if(add_s_tens)
                begin
                    if(end_s_tens)
                        s_tens<=4'd0;
                    else
                        s_tens<=s_tens+4'b1;
                end
        end
    assign add_s_tens=end_s_ones;
    assign end_s_tens=add_s_tens&&(s_tens==5);

    
    
    //分的个位
    always @(posedge clk)
        begin
          if(reset)
              m_ones <= 4'b0;
          else if(add_m_ones)
              begin
                  if(end_m_ones)
                      m_ones <= 4'b0;
                  else 
                      m_ones <= m_ones + 4'b1;
         	  end
  		  end
    assign add_m_ones = end_s_tens;
    assign end_m_ones = add_m_ones && (m_ones == 4'd9);
    
    //分的十位
  	always @(posedge clk)
        begin
          if(reset)
              m_tens <= 4'b0;
          else if(add_m_tens)
              begin
                  if(end_m_tens)
                      m_tens <= 4'b0;
                  else 
                      m_tens <= m_tens + 4'b1;
              end
        end
    assign add_m_tens = end_m_ones;
    assign end_m_tens = add_m_tens && (m_tens == 4'd5);
    
    
    //时钟个位的实现
    always @(posedge clk)
        begin
            if(reset)
                h_ones <= 4'd2;
            else if(add_h_ones)
                begin
                    if(end_h_ones_0)
                        h_ones <= 4'b0;//如果十位为0,则复位到0
                    else if(end_h_ones_1)
                        h_ones <= 4'b1;//如果十位为1,则复位到1,出现11:59:59
                    else 
                        h_ones <= h_ones+4'b1;
            	end
  		 end
    assign add_h_ones=end_m_tens;
    assign end_h_ones_0=add_h_ones&&(h_ones==4'd9);
    assign end_h_ones_1=add_h_ones && ((h_ones == 4'd2) && (h_tens == 4'd1));
    
    //时钟十位的实现
  	always @(posedge clk)
        begin
          if(reset)
              h_tens <= 4'd1;
            else if(add_h_tens)
              begin
                  if(end_h_tens_0)
                      h_tens <= 4'b1;
                  else if(end_h_tens_1)
                      h_tens <= 4'b0;
              end
        end
    assign add_h_tens = end_m_tens;
    assign end_h_tens_1 = add_h_tens && end_h_ones_1;//在个位为2十位为1时,结束十位为1的状态
    assign end_h_tens_0 =add_h_tens && end_h_ones_0;//在个位为9时,结束十位为0的状态
    
    
    //上午下午的区分
    always@(posedge clk)
        begin
            if(reset)
                pm_temp <= 1'b0;
            else if(pm_ding)
                pm_temp <= ~pm_temp;
		end
	assign pm_ding = h_tens == 4'd1 && h_ones == 4'd1 && end_m_tens;
    
    //输出
    always@(*)
        begin
            pm=pm_temp;
            ss={s_tens,s_ones};
            mm={m_tens,m_ones};
            hh={h_tens,h_ones};
        end

    
endmodule

8、仿真波形图

复位和秒钟

分钟

时钟

###本文参考《明德杨大道至简的至简设计方法》,和@日拱一卒_未来可期的一篇博客,HDLBits答案(11)_Verilog计数器-CSDN博客。如有侵权,请联系删除。

  • 22
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值