Verilog 任意整数分频器

发布于:2022-11-28 ⋅ 阅读:(204) ⋅ 点赞:(0)

Foreword

距离上一篇CSDN又有两个月了,这两个月学了好多,感觉这半年我还是有很大进步了的,没有一开始那么心力憔悴了。但是水管依旧到处漏……

最近和老虞还有刘同学吃饭,两个不同领域下的大佬,一个做考古,一个做AI,虽然聊的内容差异巨大,但两个人给我的感触都是,他们好厉害,小汤要好好努力!同时也让我想到了,世界可以分为哲学和数学,但最后都会归结于美学。我身边几个特别厉害的程序员,往往审美也是很好的,可能不是那么艺术,但做出来的东西一定是让人看着舒服的。

分频原理

首先推荐一个画波形原理图的在线网站WaveDrom Online Editor,他们家也有软件可以下载WaveDrom Download Github,使用方法在他家官网上也有介绍Tutorial,可以保存多种格式: json/png/svg,很好用。

在时序电路里,一般系统时钟都是高频的,不同外设对时钟频率的要求不同,所以需要通过分频来获得相应的时钟频率,一般都是将高频的时钟转换为低频的时钟。最简单的分频就是2分频,也就是把时钟频率减半,输出时钟和输入时钟上升沿对齐,波形图是这样的:

div2
当然也会有遇到需要奇数分频的时候,这时候上升沿和下降沿都需要用到,3分频波形图是这样的:

3div50

上面两张图都是50%占空比的,有的时候我们只需要用输出时钟的上升沿,不需要考虑下降沿,这样的话我们的输出时钟只需要是单脉冲的就可以了,设计的时候会简单很多,单脉冲的3分频波形图是这样的:

3div1

50%占空比的偶数分频方法

偶数分频又可以分成两种,一种是2的倍数,一种是2的次方。当分频系数是2的次方时,分频的方法会简单很多。这里还是要吐槽一下,CSDN为什么不支持verilog语法!!!

  • 2的倍数分频

    原理:

    1. 计数器在时钟上升沿+1,加到对应分频系数后,清零
    2. clkOut在计数器记到(N-1)/2和N-1的地方翻转
    3. 当分频系数为0或1时,clkOut = clkIn
    `define __DIVEVEN_V__
    
    module DivEven #(
    parameter PRRWIDTH = 4
    )(
    input  wire					clk,
    input  wire					rst,	// negetive valid
    input  wire					en,
    input  wire	[PRRWIDTH-1:0]	prr,
    
    output reg					clkOut
    );
    
    reg  [PRRWIDTH-1:0]			cnt;
    wire [PRRWIDTH-1:0]			cntValue;
    wire						direct;
    
    // when prr is 0 or 1, clkOut = clk directly
    assign	direct = (prr == {PRRWIDTH{1'b0}}) | (prr == {{(PRRWIDTH-1){1'b0}}, 1'b1});
    assign	cntValue = direct ? {PRRWIDTH{1'b0}} : (prr - 1'b1);
    
    // positive edge count
    always @(posedge clk or negedge rst) begin
    	if (!rst) begin
    		cnt <= {PRRWIDTH{1'b0}};
    	end
    	else if (!en) begin
    		cnt <= {PRRWIDTH{1'b0}};
    	end
    	else if (cnt == cntValue) begin
    		cnt <= {PRRWIDTH{1'b0}};
    	end
    	else begin
    		cnt <= cnt + 1'b1;
    	end
    end
    
    // overturn clkOut when cnt = (cntValue - 1) / 2 or cntValue
    always @(posedge clk or negedge rst) begin
    	if (!rst) begin
    		clkOut <= 1'b0;
    	end
    	else if (!en) begin
    		clkOut <= 1'b0;
    	end
    	else if (direct) begin
    		clkOut <= clk;
    	end
    	else if ((cnt == (cntValue >> 1)) || (cnt == cntValue)) begin
    		clkOut <= !clkOut;
    	end
    end
    
    endmodule
    
  • 2的次方分频

    原理:

    1. 计数器在时钟上升沿+1,加到全1后,清零
    2. cnt[0]的值是2分频,cnt[1]的值是4分频,cnt[2]的值是8分频
    3. 例如4分频,每两个clkIn上升沿,cnt[1]翻转一次,所以得到4分频后的clkOut
    `define __DIVEVEN_V__
    
    module DivEven (
    input  wire					clk,
    input  wire					rst,	// negetive valid
    input  wire					en,
    input  wire	[1:0]			div,
      // div = 0 means prr = 0;
      // div = 1 means prr = 2;
      // div = 2 means prr = 4;
      // div = 3 means prr = 8;
    
    output wire					clkOut
    );
    
    reg  [2:0]	cnt;
    
    assign clkOut = (!rst | !en)	? 1'b0		:
    				div == 2'b00	? clk		:
    				div == 2'b01	? cnt[0]	: // prr = 2
    				div == 2'b10	? cnt[1]	: // prr = 4
    				div == 2'b11	? cnt[2]	: // prr = 8
    				1'b0;
    
    // positive edge count
    always @(posedge clk or negedge rst) begin
    	if (!rst) begin
    		cnt <= 3'b000;
    	end
    	else if (!en) begin
    		cnt <= 3'b000;
    	end
    	else if (cnt == 3'b111) begin
    		cnt <= 3'b000;
    	end
    	else begin
    		cnt <= cnt + 1'b1;
    	end
    end
    
    endmodule
    

50%占空比的任意整数分频方法

原理:

  1. 偶数的时候只用上升沿得到的时钟,奇数的时候将上升沿得到的时钟和下降沿得到的时钟并起来
  2. 上升沿得到的时钟和下降沿得到的时钟都是在计数器记到(N-1)/2和N-1的地方翻转
  3. 当分频系数为0或1时,clkOut = clkIn

code

`define __DIV50PCT_V__

module Div50Pct #(
	parameter PRRWIDTH = 4
)(
	input  wire					clk,
	input  wire					rst,	// negetive valid
	input  wire					en,
	input  wire	[PRRWIDTH-1:0]	prr,

	output wire					clkOut
);

	reg  [PRRWIDTH-1:0]			cntP;
	reg  [PRRWIDTH-1:0]			cntN;
	reg							clkP;
	reg							clkN;
	wire [PRRWIDTH-1:0]			cntValue;
	wire						direct;

	// when prr is 0 or 1, clkOut = clk directly
	assign	direct = (prr == {PRRWIDTH{1'b0}}) | (prr == {{(PRRWIDTH-1){1'b0}}, 1'b1});
	assign	cntValue = direct ? {PRRWIDTH{1'b0}} : (prr - 1'b1);

	// when prr is odd, clkOut = clkP || clkN
	// when prr is even, clkOut = clkP
	assign	clkOut = direct ? clk : (prr[0] ? (clkP | clkN) : clkP);

	// positive edge count
	always @(posedge clk or negedge rst) begin
		if (!rst) begin
			cntP <= {PRRWIDTH{1'b0}};
		end
		else if (!en) begin
			cntP <= {PRRWIDTH{1'b0}};
		end
		else if (cntP == cntValue) begin
			cntP <= {PRRWIDTH{1'b0}};
		end
		else begin
			cntP <= cntP + 1'b1;
		end
	end

	// positive edge clock
	// overturn clkP when cntP = (cntValue - 1) / 2 or cntValue
	always @(posedge clk or negedge rst) begin
		if (!rst) begin
			clkP <= 1'b0;
		end
		else if (!en) begin
			clkP <= 1'b0;
		end
		else if (direct) begin
			clkP <= 1'b0;
		end
		else if ((cntP == (cntValue >> 1)) || (cntP == cntValue)) begin
			clkP <= !clkP;
		end
	end

	// negetive edge count for odd prr
	always @(negedge clk or negedge rst) begin
		if (!rst) begin
			cntN <= {PRRWIDTH{1'b0}};
		end
		else if (!en) begin
			cntN <= {PRRWIDTH{1'b0}};
		end
		else if (prr[0]) begin
			if (cntN == cntValue) begin
				cntN <= {PRRWIDTH{1'b0}};
			end
			else if (cntP != {PRRWIDTH{1'b0}})begin
				cntN <= cntN + 1'b1;
			end
		end
		else begin
			cntN <= {PRRWIDTH{1'b0}};
		end
	end

	// negetive edge clock for odd prr
	// overturn clkN when cntN = (cntValue - 1) / 2 or cntValue
	always @(negedge clk or negedge rst) begin
		if (!rst) begin
			clkN <= 1'b0;
		end
		else if (!en) begin
			clkN <= 1'b0;
		end
		else if (direct) begin
			clkN <= 1'b0;
		end
		else if (prr[0]) begin
			clkN <= ((cntN == (cntValue >> 1)) || (cntN == cntValue)) ? !clkN : clkN;
		end
		else begin
			clkN <= 1'b0;
		end
	end

endmodule

单脉冲的任意整数分频方法

思路:

  1. 计数器在时钟上升沿+1,加到对应分频值后,clkOutReg拉高,计数器清零
  2. 计数器清零后,clkOutReg拉低
  3. 当clkOutReg和clk都为高时,clkOut为高
  4. 当分频系数为0或1时,clkOut = clkIn
`define __DIVSINGLEPULSE_V__

module DivSinglePulse #(
	parameter PRRWIDTH = 4
)(
	input  wire					clk,
	input  wire					rst,	// negetive valid
	input  wire					en,
	input  wire	[PRRWIDTH-1:0]	prr,

	output wire					clkOut
);

    reg							clkOutReg;
	reg  [PRRWIDTH-1:0]			cnt;
	wire [PRRWIDTH-1:0]			cntValue;
	wire						direct;

	// when prr is 0 or 1, clkOut = clk directly
	assign	direct = (prr == {PRRWIDTH{1'b0}}) | (prr == {{(PRRWIDTH-1){1'b0}}, 1'b1});
	assign	cntValue = direct ? {PRRWIDTH{1'b0}} : (prr - 1'b1);

	assign	clkOut = direct ? clk : clkOutReg & clk;

	// positive edge count
	always @(posedge clk or negedge rst) begin
		if (!rst) begin
			cnt <= {PRRWIDTH{1'b0}};
		end
		else if (!en) begin
			cnt <= {PRRWIDTH{1'b0}};
		end
		else if (cnt == cntValue) begin
			cnt <= {PRRWIDTH{1'b0}};
		end
		else begin
			cnt <= cnt + 1'b1;
		end
	end

	// clkOut is high when cnt == cntValue
	always @(posedge clk or negedge rst) begin
		if (!rst) begin
			clkOutReg <= 1'b0;
		end
		else if (!en) begin
			clkOutReg <= 1'b0;
		end
		else if (direct) begin
			clkOutReg <= 1'b0;
		end
        else if (cnt == cntValue) begin
			clkOutReg <= 1'b1;
		end
		else begin
			clkOutReg <= 1'b0;
		end
	end

endmodule

代码下载

以上代码及testbench都可以在verilog任意整数分频及测试.zip中下载噢!

Conclusion

12月到处都是节日的氛围,相信圣诞老人,相信爱、童话和魔法。

昨晚听了Westlife线上演唱会,感觉回到了初中的时候,Seasons in the Sun可能是我最早接触的英文歌。十多年过去了,我工作了,西城男孩也有了岁月的痕迹。They have raised me up and hope to see them once again.

在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看