fpga项目:计数器的3种不同写法 - minichao9901/TangNano-20k-Zynq-7020 GitHub Wiki

需求

  • 我们希望由一个start_pulse去触发一个counter,去产生一个特定的序列或者延时。一共有3种实现方案,我们逐一分析。
  • 注意是每来一个start_pulse,就会触发一次这样的序列。

image

方案1:计数器+fsm

这个方案中fsm其实就是一个最简单的cnt_valid信号。cnt_valid信号是一个辅助信号,它是一个reg类型,能够实现one-time counter

    /*********************************************************************************/ 
    /* 1) counter+fsm */
    /*********************************************************************************/              
    parameter CNT_MAX=6;
    reg [5:0] div_cnt;
    reg cnt_valid;   
    
    always @(posedge clk or negedge rst_n)
    if(rst_n==0)
        div_cnt<=0;
    else if(add_div_cnt) begin
        if(end_div_cnt)
            div_cnt<=0;
        else
            div_cnt<=div_cnt+1; 
    end 
    
    assign add_div_cnt=cnt_valid;
    assign end_div_cnt=add_div_cnt && (div_cnt==CNT_MAX-1);    
    
    always @(posedge clk or negedge rst_n)
    if(rst_n==0)
        cnt_valid<=0;
    else case(cnt_valid)
        0: if(start_pulse) cnt_valid<=1;
        1: if(end_div_cnt) cnt_valid<=0;  //comment this line, becomes free running counter
    endcase     
    /*********************************************************************************/

方案2:纯fsm

这个方案代码很比较长,适合于计数值比较小的情况。不太适合于计数长度为变量的情况。

    /*********************************************************************************/ 
    /* 2) pure fsm */
    /*********************************************************************************/   
    reg [5:0] div_cnt2;
    
    always @(posedge clk or negedge rst_n)
    if(rst_n==0)
        div_cnt2<=0;
    else case(div_cnt2)
        0: if(start_pulse) div_cnt2<=1;
        1: div_cnt2<=2;
        2: div_cnt2<=3;
        3: div_cnt2<=4;            
        4: div_cnt2<=5;
        5: div_cnt2<=0;
        default: div_cnt2<=0;
    endcase      
    
    assign cnt_valid2=(div_cnt2>=1) && (div_cnt2<=5);
    assign end_div_cnt2=(div_cnt2==5);    
    /*********************************************************************************/ 

方案3:将方案2的case语句改写成if...else if...的形式

这个方案代码短,适合于计数长度为任意值的情况。从代码看,这个很类似于计数器,但其实不是计数器,其实是状态机写法。

    /*********************************************************************************/ 
    /* 3) rewrite fsm, result is counter-like fsm */
    /*********************************************************************************/   
    reg [5:0] div_cnt3;
    
    always @(posedge clk or negedge rst_n)
    if(rst_n==0)
        div_cnt3<=0;
    else if(div_cnt3==0 && start_pulse)
        div_cnt3<=1;
    else if(div_cnt3>=1 && div_cnt3<5)
        div_cnt3<=div_cnt3+1;
    else if(div_cnt3==5)
        div_cnt3<=0;     
    
    assign cnt_valid3=(div_cnt3>=1) && (div_cnt3<=5);
    assign end_div_cnt3=(div_cnt3==5);    
    /*********************************************************************************/  

仿真波形

image

可以看到,都实现了相应的功能。注意到方案1会多一个计数值,也就是0,也就是当start_pulse来的时候,我们让计数值清零了。其余2个方案,计数值范围是1-5。 从代码长度看,方案3最短。这是状态机的if...else写法,请仔细品味一下。它的特点是,在if()条件中,会根据当前state的值和外部的条件,决定下一个state值。

完整tb代码

module counter_or_fsm;
    reg clk, rst_n;
    initial clk=0;
    always #10 clk=~clk;
    initial begin
        rst_n=0;
        #1000;
        rst_n=1;
    end 
    
    reg start_pulse;
    initial begin
        start_pulse=0;
        wait(rst_n==1);
        #1000;
        @(posedge clk)
        #1 start_pulse=1;
        @(posedge clk)
        #1 start_pulse=0;
    end    
 
    /*********************************************************************************/ 
    /* 1) counter+fsm */
    /*********************************************************************************/              
    parameter CNT_MAX=6;
    reg [5:0] div_cnt;
    reg cnt_valid;   
    
    always @(posedge clk or negedge rst_n)
    if(rst_n==0)
        div_cnt<=0;
    else if(add_div_cnt) begin
        if(end_div_cnt)
            div_cnt<=0;
        else
            div_cnt<=div_cnt+1; 
    end 
    
    assign add_div_cnt=cnt_valid;
    assign end_div_cnt=add_div_cnt && (div_cnt==CNT_MAX-1);    
    
    always @(posedge clk or negedge rst_n)
    if(rst_n==0)
        cnt_valid<=0;
    else case(cnt_valid)
        0: if(start_pulse) cnt_valid<=1;
        1: if(end_div_cnt) cnt_valid<=0;  //comment this line, becomes free running counter
    endcase     
    /*********************************************************************************/
    

    /*********************************************************************************/ 
    /* 2) pure fsm */
    /*********************************************************************************/   
    reg [5:0] div_cnt2;
    
    always @(posedge clk or negedge rst_n)
    if(rst_n==0)
        div_cnt2<=0;
    else case(div_cnt2)
        0: if(start_pulse) div_cnt2<=1;
        1: div_cnt2<=2;
        2: div_cnt2<=3;
        3: div_cnt2<=4;            
        4: div_cnt2<=5;
        5: div_cnt2<=0;
        default: div_cnt2<=0;
    endcase      
    
    assign cnt_valid2=(div_cnt2>=1) && (div_cnt2<=5);
    assign end_div_cnt2=(div_cnt2==5);    
    /*********************************************************************************/ 
    
    
    /*********************************************************************************/ 
    /* 3) rewrite fsm, result is counter-like fsm */
    /*********************************************************************************/   
    reg [5:0] div_cnt3;
    
    always @(posedge clk or negedge rst_n)
    if(rst_n==0)
        div_cnt3<=0;
    else if(div_cnt3==0 && start_pulse)
        div_cnt3<=1;
    else if(div_cnt3>=1 && div_cnt3<5)
        div_cnt3<=div_cnt3+1;
    else if(div_cnt3==5)
        div_cnt3<=0;     
    
    assign cnt_valid3=(div_cnt3>=1) && (div_cnt3<=5);
    assign end_div_cnt3=(div_cnt3==5);    
    /*********************************************************************************/          
    
endmodule