相关推荐recommended
Verilog手撕代码(6)分频器
作者:mmseoamin日期:2024-01-19

目录

  • 分频概念
  • 偶数分频
    • 二分频
    • 任意偶数
    • 占空比问题
    • 奇分频
      • 非常规占空比的奇分频
      • 分频时钟的使用
      • 小数分频

        分频概念

        分频就是生成一个新时钟,该新时钟的频率是原有时钟频率的整数分之一倍,新周期是原有周期的整数倍。

        再简单来说,让你手撕一个四分频电路,就是写代码生成一个周期是原来四倍的时钟,如果手撕一个三分频电路,就是写代码生成一个周期是原来三倍的时钟。

        Verilog手撕代码(6)分频器,在这里插入图片描述,第1张

        如图为四分频波形图,clk_out的频率是clk的1/4,但周期是clk的4倍。

        分频主要分为偶数分频、奇数分频、小数分频。

        偶数分频

        二分频

        二分频引入,在每个时钟上升沿来到时,翻转新时钟

        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		clk_2 <= 0;
        	else
        		clk_2 <= ~clk_2;
        end
        

        得到的结果如下:

        Verilog手撕代码(6)分频器,在这里插入图片描述,第2张

        任意偶数

        代码:

        module div
        #(
        	parameter num = 8
        )
        (
        	input clk,
        	input rst_n,
        	output reg clk_out
        );
        reg [2:0]cnt;
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)begin
        		cnt <= 0;
        		clk_out <= 0;
        	end
        	else if(cnt == num/2 -1)begin
        		cnt <= 0;
        		clk_out <= ~clk_out; 
        	end	
        	else
        		cnt <= cnt + 1'b1;
        end
        endmodule
        

        Testbench

        module div_tb();
        	reg clk;
        	reg rst_n;
        	wire clk_out;
        	
        	div u0(
        		.clk(clk),
        		.rst_n(rst_n),
        		.clk_out(clk_out)
        	);
        	
        	always #10 clk = ~clk;
        	
        	initial begin
        		clk = 0;
        		rst_n = 0;
        		#15 
        		rst_n = 1;
        		
        		#50000
        		$stop;
        	
        	end
        	
        endmodule
        

        仿真结果:

        Verilog手撕代码(6)分频器,在这里插入图片描述,第3张

        每4各clk,clk_out翻转一次,即8个clk周期的为clk_out的一个周期。

        在Testbench中使用defparam语句覆盖原始rtl中的num初始值,改为4分频。

        defparam num = 4;
        

        结果为:

        Verilog手撕代码(6)分频器,在这里插入图片描述,第4张

        占空比问题

        上面写的任意偶数分频代码的占空比都是50%,实际上面试手撕代码不会让你50%占空比

        方法:==进行计数,对于一个八分频,开始就把时钟设为高电平,我用cnt 计数到两个时钟上升沿后再把它拉低,计数到7后cnt 拉低 时钟拉高,这样就实现了两个周期高,六个周期低,占空比为2/8 即 25% 的八分频。 ==

        奇分频

        任意奇数分频代码:

        module div_2
        #(
        	parameter N = 7
        )
        (
        	input clk,
        	input rst_n,
        	output reg clk_out
        );
        reg [2:0] cnt;
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		cnt <= 3'd0;
        	else 
        		cnt <= (cnt == (N-1))?3'd0:cnt + 1'd1;
        end
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		clk_out <= 1'b0;
        	else if((cnt == N-1)|(cnt == (N-1)/2))
        		clk_out <= ~clk_out;
        	else
        		clk_out <= clk_out;
        end
        endmodule
        

        Testbench:

        `timescale 1ns/1ns
        module div_2_tb();
        	reg clk;
        	reg rst_n;
        	wire clk_out;
        	
        	div_2 inst(
        		.clk(clk),
        		.rst_n(rst_n),
        		.clk_out(clk_out)
        	);
        	
        	always #10 clk = ~clk;
        	
        	initial begin
        		clk = 0;
        		rst_n = 0;
        		#15
        		rst_n = 1;
        		#3000
        		$stop;
        	end
        endmodule
        

        仿真结果:

        Verilog手撕代码(6)分频器,在这里插入图片描述,第5张

        在cnt = 4 和cnt = 6的时候对clk_out翻转,可以看到每经过7个clk,生成一个clk_out完整周期。但是计算clk_out的占空比可得为3/7,并不是规整的50%占空比。

        实现50%占空代码:

        module div_2
        #(
        	parameter N = 7
        )
        (
        	input clk,
        	input rst_n,
        	output wire clk_out
        );
        reg [2:0] cnt_pos;
        reg [2:0] cnt_neg;
        reg clk_pos,clk_neg;
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		cnt_pos <= 3'd0;
        	else 
        		cnt_pos <= (cnt_pos == (N-1))?3'd0:cnt_pos + 1'd1;
        end
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		clk_pos <= 1'b0;
        	else if((cnt_pos == N-1)|(cnt_pos == (N-1)/2))
        		clk_pos <= ~clk_pos;
        	else
        		clk_pos <= clk_pos;
        end
        always@(negedge clk or negedge rst_n)begin
        	if(!rst_n)
        		cnt_neg <= 3'd0;
        	else 
        		cnt_neg  <= (cnt_neg  == (N-1))?3'd0:cnt_neg  + 1'd1;
        end
        always@(negedge clk or negedge rst_n)begin
        	if(!rst_n)
        		clk_neg <= 1'b0;
        	else if((cnt_neg == N-1)|(cnt_neg == (N-1)/2))
        		clk_neg <= ~clk_neg;
        	else
        		clk_neg <= clk_neg;
        end
        assign clk_out = clk_pos | clk_neg;
        endmodule
        

        Testbench:

        `timescale 1ns/1ns
        module div_2_tb();
        	reg clk;
        	reg rst_n;
        	wire clk_out;
        	
        	div_2 inst(
        		.clk(clk),
        		.rst_n(rst_n),
        		.clk_out(clk_out)
        	);
        	
        	always #10 clk = ~clk;
        	
        	initial begin
        		clk = 0;
        		rst_n = 0;
        		#15
        		rst_n = 1;
        		#3000
        		$stop;
        	end
        endmodule
        

        仿真结果:

        Verilog手撕代码(6)分频器,在这里插入图片描述,第6张

        非常规占空比的奇分频

        非常规占空比的奇数分频器,比如 3/10占空比的五分频, 5/18占空比的九分频?

        与上面实现50%任意奇数分频器的原理是类似的,但是采用与运算。用占空比为 2/5 上升沿采样的信号和 2/5占空比下降沿采样的信号相与。

        所以相与后,占空比就为 2/5 - 1/10 = 3/10 ,示意图如下:

        Verilog手撕代码(6)分频器,在这里插入图片描述,第7张

        5/18占空比的九分频就是用上升沿采样的3/9占空比九分频 和下降沿采样的3/9占空比九分频相与,最后结果为3/9-1/18 = 5/18 占空比。具体修改只需要改cnt判断数值以及把clk_out 的赋值从clk_out1,clk_out2相或改成相与。

        代码参考:verilog代码

        分频时钟的使用

        在其他模块利用分频时钟时,分频得到的时钟并未连接到FPGA内部的全局时钟树,因此使用分频时钟的模块与其他使用系统时钟clk的模块存在时钟到达时间的偏差,而在设计过程中,我们希望的使各个模块的时钟达到时间使相近的,减少时序问题。

        在一些低速系统或模块中,使用分频时钟出现问题的概率较低,但在高速系统和模块中,使用分频时钟就会容易出现问题,导致各模块的时钟到达时间存在较大的偏差,为解决这个问题,分频时生成脉冲时钟。

        以7分频为例:

        module div_3
        #(
        	parameter N = 7
        )
        (
        	input clk,
        	input rst_n,
        	output reg clk_flag
        );
        reg [2:0] cnt;
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		cnt <= 3'd0;
        	else 
        		cnt <= (cnt == (N-1))?3'd0:cnt + 1'd1;
        end
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		clk_flag <= 1'b0;
        	else if(cnt == N-2)
        		clk_flag <= 1'b1;
        	else
        		clk_flag <= 1'b0;
        end
        endmodule
        

        Testbench与上面一样

        `timescale 1ns/1ns
        module div_3_tb();
        	reg clk;
        	reg rst_n;
        	wire clk_flag;
        	
        	div_3 inst(
        		.clk(clk),
        		.rst_n(rst_n),
        		.clk_flag(clk_flag)
        	);
        	
        	always #10 clk = ~clk;
        	
        	initial begin
        		clk = 0;
        		rst_n = 0;
        		#15
        		rst_n = 1;
        		#3000
        		$stop;
        	end
        endmodule
        

        仿真结果:

        Verilog手撕代码(6)分频器,在这里插入图片描述,第8张

        在Testbench中利用defparam对N进行修改,改为4分频

        	defparam inst.N = 4;
        

        Verilog手撕代码(6)分频器,在这里插入图片描述,第9张

        由此,在利用分频后的脉冲信号clk_flag 进行判断和处理,例如:

        module div_3
        #(
        	parameter N = 7
        )
        (
        	input clk,
        	input rst_n,
        	output reg clk_out,
        	output reg clk_flag
        );
        reg [2:0] cnt;
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		cnt <= 3'd0;
        	else 
        		cnt <= (cnt == (N-1))?3'd0:cnt + 1'd1;
        end
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		clk_out <= 1'b0;
        	else if((cnt == N-1)|(cnt == (N-1)/2))
        		clk_out <= ~clk_out;
        	else
        		clk_out <= clk_out;
        end
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		clk_flag <= 1'b0;
        	else if(cnt == N-2)
        		clk_flag <= 1'b1;
        	else
        		clk_flag <= 1'b0;
        end
        reg [2:0] a,b;
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		a <= 3'b0;
        	else if(clk_flag == 1)
        		a <= a + 1'b1;
        end
        always@(posedge clk_out or negedge rst_n)begin
        	if(!rst_n)
        		b <= 3'b0;
        	else 
        		b <= a + 1'b1;
        end
        endmodule
        

        其中a计数器是由clk_flag控制的,b计数器是由clk_out控制的,而本质上clk_flag是由系统时钟clk进行控制的,因此a由系统时钟clk控制,与其他采用系统时钟clk的模块都保持着相同的时钟关系,b由clk_out控制,与系统时钟clk存在一定的偏差,因此推荐使用脉冲信号的写法。

        本质上来说,脉冲信号clk_flag是降频写法,无法对占空比进行设计,clk_out是分频写法,达到的效果是一样的,但在时序问题上,脉冲信号clk_flag写法更好!

        小数分频

        编码小数分频,就不能看微观了,要用宏观的眼界去看,比如实现一个 17/3 分频,表达成:17 除以 3 得商为 5 余2。

        那么我们就可以通过5(商)分频和7(商+余数)来实现 17/3 分频。 确定5分频和7分频的次数,设:5分频的次数为a , 7分频的次数为b,那么应该有:

         a+b=3(除数)
         5a+7b = 17(被除数)
        

        得a=2,b=1,也就是说通过2次5分频和1次7分频可得到 17/3 分频。

        宏观来看,总共是17个时钟周期,由三个分频器均分,那么平均每个分频器就是分到17/3了,这就是小数分频,这是一个宏观的平均概念。

        代码:

        module div_M_N(
        	input clk,
        	input rst_n,
        	output reg clk_out
        );
        reg [4:0] cnt;
        reg [2:0] cnt1;
        reg [2:0] cnt2;
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		cnt <= 5'd0;
        	else 
        		cnt <= (cnt == 5'd16)?5'd0:cnt + 1'b1;
        end
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)begin
        		cnt1 <= 3'd0;
        		cnt2 <= 3'd0;
        		clk_out <= 1'b0;
        	end
        	else if(cnt < 5'd10)begin //cnt<5'd10中包含两个5分频。占空比为1/5
        		if(cnt1 == 3'd4)begin
        			cnt1 <= 3'd0;
        			clk_out <= 1'b1;
        		end
        		else if(cnt1 == 3'd0)begin
        			cnt1 <= cnt1 + 1'b1;
        			clk_out <= 1'b0;
        		end
        		else begin
        			cnt1 <= cnt1 + 1'b1;
        		end		
        	end
        	else begin                   //5'd10= 
        

        Testbench:

        module div_M_N_tb();
        	reg clk;
        	reg rst_n;
        	wire clk_out;
        	div_M_N inst(
        		.clk(clk),
        		.rst_n(rst_n),
        		.clk_out(clk_out)
        	);
        	
        	always #10 clk = ~clk;
        	
        	initial begin
        		clk = 0;
        		rst_n = 0;
        		#20
        		rst_n = 1;
        		#5000
        		$stop;		
        	end
        endmodule
        

        仿真结果:

        Verilog手撕代码(6)分频器,在这里插入图片描述,第10张

        可以看出,两个五分频,一个七分频,每十七个周期循环一次。即每十七个周期有三个分频器,平摊下来就是 17/3 。

        小数分频的缺点就是 占空比不为50%,要想实现50%占空比的小数分频,涉及很多算法,具体算法十分复杂。

        可参考:任意小数分频(50%占空比)

        Verilog手撕代码(6)分频器,在这里插入图片描述,第11张

        8.7分频verilog代码:

        module div_M_N_2(
        	input clk,
        	input rst_n,
        	output clk_out
        );
        parameter M_N = 8'd87;  //总周期
        parameter c89 = 8'd24;	//8分频9分频切换点
        parameter div_e = 5'd8;	//偶分频周期
        parameter div_o = 5'd9;	//奇分频周期
        reg [7:0] cnt;
        reg [3:0] cnt_8;
        reg [3:0] cnt_9;
        reg clk_out_r;
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		cnt <= 8'd0;
        	else if(cnt == M_N - 1)
        		cnt <= 8'd0;
        	else
        		cnt <= cnt + 1'b1;
        end
        reg div_flag;
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)
        		div_flag <= 1'b0;
        	else if((cnt == (M_N -1)) | (cnt ==  (c89 - 1)))
        		div_flag <= ~div_flag;
        	else 
        		div_flag <= div_flag;
        end
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)begin
        		cnt_8 <= 3'd0;
        		cnt_9 <= 4'd0;
        	end
        	else if(div_flag == 0)begin
        		cnt_8 <= (cnt_8 == (div_e - 1))?0:cnt_8 + 1'b1;
        	end
        	else if(div_flag == 1)begin
        		cnt_9 <= (cnt_9 == (div_o - 1))?0:cnt_9 + 1'b1;
        	end
        end
        always@(posedge clk or negedge rst_n)begin
        	if(!rst_n)	
        		clk_out_r <= 1'b0;
        	else if( ((cnt_8 == 4 | cnt_8 == 0)&&(div_flag == 0)) | ( (cnt_9 == 0 | cnt_9 == 4)&&(div_flag == 1 )) )
        		clk_out_r <= ~clk_out_r;
        end
        assign clk_out = clk_out_r;
        endmodule	
        

        Testbeench:

        module div_M_N_tb();
        	reg clk;
        	reg rst_n;
        	wire clk_out;
        	div_M_N_2 inst(
        		.clk(clk),
        		.rst_n(rst_n),
        		.clk_out(clk_out)
        	);
        	
        	always #10 clk = ~clk;
        	
        	initial begin
        		clk = 0;
        		rst_n = 0;
        		#20
        		rst_n = 1;
        		#5000
        		$stop;		
        	end
        endmodule
        

        仿真结果:

        Verilog手撕代码(6)分频器,在这里插入图片描述,第12张