在许多设计中,常常伴随着对时钟的各种需求,如需要进行偶数倍分频,奇数倍分频;对于时钟的处理也很重要,如何防止时钟截断,时钟毛刺,减少累计时钟偏移;在低功耗设计中,如何降低时钟网络和其中寄存器的功耗;
进而衍生出各种技术,如行波计数器、计数分频器、门控时钟、锁存器门控时钟等待。
在数字设计中,产生时钟信号的方法主要有两种,一种是通过PLL锁相环对时钟源进行分频或倍频,另一种是在设计的模块中用硬件描述语言描述分频逻辑。
在本文中将对这些技术进行简要的介绍,并给出若干设计实例。
https://hihii11.github.io/verilog_clockmanager.html
图2.1给出了一个三级行波计数器,每个D触发器的反相输出端~Q与输入端D相连。正向输出端作为下一级触发器的时钟信号。
图2.1 三级行波计数器
这样设计的行波计数器相对于其他分频器来说,其具有以下特点。
1.每级D触发器可对源时钟进行2n分频。
如上图2.1中,
clk1 = clk_source / 2;
clk2 = clk_source / 4;
clk3 = clk_source / 8;
其各级时钟波形如图2.2所示。
图2.2 各级时钟波形
2.资源消耗少
由于行波计数器仅通过若干个触发器级联产生时钟,所以其没有附加的组合逻辑,故消耗资源较少。
3.功耗低
由于行波计数器消耗资源少,故在CLK发生反转时,所带动的处于活跃的组合逻辑部分也较少,因此由这部分逻辑产生的峰值功耗大大降低。
在低功耗设计中常用行波计数器。
行波计数器也存在以下缺点:
1.行波计数器有较严重的级联效应
由于行波计数器的级联结构,其每一级时钟都会产生一定的滞后。
如:
clk1 相较于 clk_source 会产生由U1A所引入的触发器延时及布线延时(tU1A)。
clk2 相较于 clk1 会产生由U2A所引入的触发器延时(tU2A)。
clk3 同理。
由此一来,延迟会不断积累,如图2.3所示。
图2.3 行波计数器的级联效应
一方面这种延迟可能会引入毛刺,另一方面在同步电路设计中,有可能会违背数据的建立保持时间。
因为行波计数器产生的时钟本质上是异步的,依赖其产生的时钟的模块,在进行数据交互时,属于跨时钟域。
2.会对STA工具和综合工具带来麻烦
行波计数器在各阶段创建时钟所引入的级联效应导致的问题,会增加STA工具和综合工具的工作量。
1.模块代码
module travel_wave_counter#(
parameter integer LEVELs = 3
)
(
input wire CLK_IN,
input wire nRST,
output wire [LEVELs-1:0] CLK_OUT
);
genvar i;
generate
for(i = 0;i < LEVELs;i = i+1)
begin
if(i == 0)
begin
travel_wave_level travel_wave_level_inist0(
.CLK_IN(CLK_IN),.nRST(nRST),.DOUT(CLK_OUT[i]));
end
else
begin
travel_wave_level travel_wave_level_inist0(
.CLK_IN(CLK_OUT[i-1]),.nRST(nRST),.DOUT(CLK_OUT[i]));
end
end
endgenerate
endmodule
module travel_wave_level(
input wire CLK_IN,
input wire nRST,
output wire DOUT
);
reg dout;
assign DOUT = dout;
always@(posedge CLK_IN,negedge nRST)
begin
if(~nRST)
begin
dout <= 1'b0;
end
else
begin
dout <= ~dout;
end
end
endmodule
行波计数器产生时钟属于异步,同步应当避免。
在低功耗设计等情况下,可以尝试使用行波计数器,但需要严格控制。
针对上述行波计数器的缺点,在同步电路设计中,可以采用计数器或状态机来产生分频时钟。
利用计数器产生分频器需要注意:
1.应由寄存器直接产生时钟信号,永远不要对计数器或状态机输出进行译码。
原因是状态改变时信号会产生竞争冒险,从而导致逻辑错误,产生的时钟信号出现毛刺,会导致电路逻辑错误。
2.对主频进行分频时,应使用同步计数器或状态机。
典型的计数分频器设计:(注意本小节计数器均为偶数分频)
1.使用两个同步always逻辑块
module div_counter#(
parameter integer DIV_NUM = 2
)(
input wire CLK_IN,
input wire nRST,
output wire CLK_OUT
);
reg [15:0] clk_cnt;
reg clk_out;
assign CLK_OUT = clk_out;
always@(posedge CLK_IN)
begin
if(~nRST)
begin
clk_cnt <= 16'd0;
end
else
begin
if(clk_cnt != DIV_NUM - 1)
clk_cnt <= clk_cnt + 16'd1;
else
clk_cnt <= 16'd0;
end
end
always@(posedge CLK_IN)
begin
if(~nRST)
begin
clk_out <= 1'b0;
end
else
begin
if(clk_cnt < DIV_NUM/2)
clk_out <= 1'b1;
else
clk_out <= 1'b0;
end
end
endmodule
module div_counter#(
parameter integer DIV_NUM = 2
)(
input wire CLK_IN,
input wire nRST,
output wire CLK_OUT
);
reg [15:0] clk_cnt;
reg clk_out;
assign CLK_OUT = (clk_cnt < DIV_NUM/2)?1'b1:1'b0;
always@(posedge CLK_IN)
begin
if(~nRST)
begin
clk_cnt <= 16'd0;
end
else
begin
if(clk_cnt != DIV_NUM - 1)
clk_cnt <= clk_cnt + 16'd1;
else
clk_cnt <= 16'd0;
end
end
endmodule
综合后电路:
两种设计方式的区别在于,第一种设计时钟信号是通过时序逻辑驱动产生的,而第二种是直接通过组合逻辑产生。
第二种设计对于信号毛刺的抗干扰性较差,第一种可以很好地滤除毛刺。
对于行波计数器的缺点,可采取计数器的方法进行时钟分频。
完全同步的设计,时钟偏移现象相对行波计数器较轻。
消耗的资源较行波计数器较多,且功耗较大。
门控时钟是减少功耗的有力手段,在时钟被门控关闭后,该时钟网络和其中的寄存器都会停止翻转,因此功耗会显著减低。
但门控时钟也会带来一些问题:
1.门控时钟属于非同步设计,会增加设计时间和验证工作量。
2.门控时钟会在时钟链路上增加额外的时钟偏移。
3.门控时钟对毛刺敏感,可能导致设计失败。
传统的门控时钟设计如图3.1所示:
图4.1 典型门控时钟电路
对于时钟源clk_source经过一个额外的与门U2A,当clk_en为高时,clk_source能过通过到达U1A触发器,当clk_en为低时clk_source无法到达触发器。
当clk_en为低时,触发器及其时钟网络中的所有组合逻辑停止翻转,out端保持不变。
由于clk_source经过了U2A与门,所有会产生一定的时钟偏移。
上述问题可以通过同步设计来解决。
使用MUX的同步设计方案:
图4.2 伪门控时钟电路
可以通过clk_en来控制触发器输入端口D的数据,若此时选择的数据为out端口,则触发器输出将保持不变。
但由于并未切断时钟树,所以触发器内部包括组合逻辑都处于活跃状态,功耗并未得到实质性降低。
传统的门控时钟仅有一级与门进行门控控制,其会产生时钟截断情况:
图4.3 传统门控时钟的时钟截断现象
在6时刻,因为clk_en由高变低,所以其输出clk_out被截断,同理在8时刻,由于clk_en变高,clk_out同样被截断。
这样的截断会产生以下一些问题:
1.时钟占空比不平衡。
2.产生的脉冲尖峰(干扰)会引起保持建立时间的问题。
故在设计中可采用锁存器来避免时钟截断。
基于锁存器门控时钟的verilog描述:
module gata_clock(
input wire CLK_IN,
input wire CLK_ENI,
output wire CLK_OUT
);
reg clk_eno;
assign CLK_OUT = clk_eno & CLK_IN;
always@(~CLK_IN)
begin
clk_eno <= CLK_ENI;
end
endmodule
时钟截断仿真:
由于在FPGA设计中,板上资源有时没有锁存器;
此时可以将锁存器换为触发器;
module gata_clock(
input wire CLK_IN,
input wire CLK_ENI,
output wire CLK_OUT
);
(*KEEP="true"*)reg clk_eno;
assign CLK_OUT = clk_eno & CLK_IN;
always@(posedge CLK_IN)
begin
clk_eno <= CLK_ENI;
end
endmodule
合理使用门控时钟可以降低模块功耗。
在FPGA设计中,可以使用带触发器的门控时钟来消除时钟截断现象。
对于偶数倍的分频,可以使用行波计数器或计数器等方式实现;
但对于占空比要求为百分之50的奇数分频,上述几种设计就难以实现了;
对于奇数倍的分频,可以采取以下设计方法:
1.创建一个模N计数器,计数值为0-(N-1)
2.创建两个T触发器,用于产生时钟产生逻辑LOGIC0,LOGIC1
需要注意产生LOGIC0的T触发器需在输入时钟的上升沿采样
产生LOGIC1的T触发器在输入时钟的下降沿采样
3.创建两个T触发器使能信号TFF_EN0,TFF_EN1
TFF_EN0在第1步中,计数值为1的时候置位
TFF_EN1在第1步中,计数值为(N+1)/2时置位
4.创建产生时钟逻辑,CLK_OUT = LOGIC0 ^ LOGIC1
奇数分频器设计代码:
module odd_clk_div#(
parameter integer DIV_NUM = 3
)(
input wire CLK_IN,
input wire nRST,
output wire TFF_EN0,
output wire TFF_EN1,
output wire LOGIC0,
output wire LOGIC1,
output wire [15:0] CLK_CNT,
output wire CLK_OUT
);
reg [15:0] clk_cnt;//clock counter
wire tff_en0,tff_en1;
assign CLK_CNT = clk_cnt;
assign tff_en0 = (clk_cnt == 'd0)?1'b1:1'b0;
assign tff_en1 = (clk_cnt == (DIV_NUM+1)/2)?1'b1:1'b0;
assign TFF_EN0 = tff_en0;
assign TFF_EN1 = tff_en1;
//counter
always@(posedge CLK_IN)
begin
if(~nRST)
begin
clk_cnt <= 16'd0;
end
else
begin
if(clk_cnt != DIV_NUM - 1)
begin
clk_cnt <= clk_cnt + 16'd1;
end
else clk_cnt <= 16'd0;
end
end
//clock bulid logic
reg clk_div0_lgic;//sample tff_en0 at clk posegde
reg clk_div1_lgic;//sample tff_en1 at clk negedge
assign CLK_OUT = clk_div0_lgic ^ clk_div1_lgic;
assign LOGIC0 = clk_div0_lgic;
assign LOGIC1 = clk_div1_lgic;
//TFF 0
always@(posedge CLK_IN)
begin
if(~nRST)
begin
clk_div0_lgic <= 1'b0;
end
else
begin
if(tff_en0)
clk_div0_lgic <= ~clk_div0_lgic;
else
clk_div0_lgic <= clk_div0_lgic;
end
end
//TFF 1
always@(negedge CLK_IN)
begin
if(~nRST)
begin
clk_div1_lgic <= 1'b0;
end
else
begin
if(tff_en1)
clk_div1_lgic <= ~clk_div1_lgic;
else
clk_div1_lgic <= clk_div1_lgic;
end
end
本质上来说,这种奇数倍分频产生了两个2N倍的偶数分频信号,只不过利用两个分频信号的相位差进行逻辑异或得到N分频的信号;
因此也可以采用以下的设计思路:
1.首先创建两个模2N的计数器
第一个计数器在clk上升沿采样,初值为0
第二个计数器在clk下降沿采样,初值为(DIV_NUM+1)/2 - 1
2.根据两个计数器的值产生两个2N倍偶数分频信号
3.将产生的偶数分频信号进行异或产生时钟
module odd_clk_div#(
parameter integer DIV_NUM = 3
)(
input wire CLK_IN,
input wire nRST,
output wire LOGIC0,
output wire LOGIC1,
output wire [15:0] CLK_CNT0,
output wire [15:0] CLK_CNT1,
output wire CLK_OUT
);
reg [15:0] clk_cnt0;//clock counter
reg [15:0] clk_cnt1;//clock counter
wire logic0,logic1;
assign LOGIC0 = logic0;
assign LOGIC1 = logic1;
assign CLK_CNT0 = clk_cnt0;
assign CLK_CNT1 = clk_cnt1;
assign CLK_OUT = logic0 ^ logic1;
assign logic0 = (clk_cnt0 < DIV_NUM ) ? 1'b1:1'b0;
assign logic1 = (clk_cnt1 < DIV_NUM ) ? 1'b1:1'b0;
//counter
always@(posedge CLK_IN)
begin
if(~nRST)
begin
clk_cnt0 <= 16'd0;
end
else
begin
if(clk_cnt0 != 2*DIV_NUM - 1)
begin
clk_cnt0 <= clk_cnt0 + 16'd1;
end
else clk_cnt0 <= 16'd0;
end
end
always@(negedge CLK_IN)
begin
if(~nRST)
begin
clk_cnt1 <= (DIV_NUM+1)/2 - 1;
end
else
begin
if(clk_cnt1 != 2*DIV_NUM - 1)
begin
clk_cnt1 <= clk_cnt1 + 16'd1;
end
else clk_cnt1 <= 16'd0;
end
end
endmodule
奇数分频可通过产生两个相位不同的2N倍分频,异或产生
1.《硬件架构的艺术-数字电路的设计方法与技术》