通常情况下,时钟树由大量的缓冲器和反相器组成。而时钟信号为设计中翻转率最高的信号,时钟树的功耗可高达整个设计功耗 30%。加入门控时钟(clock gating)电路,可减少时钟树的开关行为,能节省开关功耗。同时,时钟引脚开关行为的减少,寄存器的内部功耗也会减少。所以,采用门控时钟,可以有效地降低功耗。
通俗来讲,当模块或触发器不工作时,将时钟关闭而不影响正常功能的逻辑,可以称之为门控时钟逻辑。此时时钟并不是一直存在的,所以可以形象的称之为门控时钟。
实现门控时钟的方法主要有以下 3 种。
最简单的方法,是直接将时钟使能控制(门控)信号与时钟做”与”逻辑。
例如对一块 ram 的时钟进行该操作,代码如下:
// use and-logic
module clkgate_basic
(
input clk ,
input clken ,
input rstn ,
input wr_en ,
input [3:0] addr ,
input [7:0] data ,
output [7:0] q
);
//clk gate
wire clk_gate = clk & clken ;
ram #(4, 8)
u1_ram16x8
(
.CLK (clk_gate),
.A (addr),
.D (data),
.EN (clken),
.WR (wr_en),
.Q (q));
endmodule
ram 模型如下:
module ram
#( parameter AW = 2 ,
parameter DW = 3 )
(
input CLK ,
input [AW-1:0] A ,
input [DW-1:0] D ,
input EN ,
input WR , //1 for write and 0 for read
output reg [DW-1:0] Q
);
parameter MASK = 3 ;
reg [DW-1:0] mem [0:(1<<AW)-1] ;
always @(posedge CLK) begin
if (EN && WR) begin
mem[A] <= D ;
end
else if (EN && !WR) begin
Q <= mem[A] ;
end
end
endmodule
testbench 代码如下:
`timescale 1ns/1ns
module test ;
//signals declaration
reg rstn ;
reg clk ;
reg clken ;
reg wr_en ;
reg [3:0] addr ;
reg [7:0] data ;
wire [7:0] q ;
initial begin
rstn = 0 ;
#7 rstn = 0 ;
end
always begin
#50 clk = 0 ;
#50 clk = 1 ;
end
//data logic
initial begin
clken = 0 ;
wr_en = 0 ;
addr = 4h3 ;
data = 8h31 ;
# 53 ;
//(1) normal write and read
clken = 1 ;
wr_en = 1 ;
repeat(9) begin
@(negedge clk) ;
data = data + 1 ;
addr = addr + 1 ;
end
@(negedge clk) ;
clken = 0 ;
wr_en = 0 ;
//read
#211;
addr = 4h3 ;
clken = 1 ;
repeat(9) begin
@(negedge clk) ;
addr = addr + 1 ;
end
@(negedge clk) ;
//end
clken = 0 ;
end // initial begin
clkgate_basic u_ram_clkgate
(
.clk (clk),
.clken (clken),
.rstn (rstn),
.wr_en (wr_en),
.addr (addr),
.data (data),
.q (q)
);
//simulation finish
always begin
#100;
if ($time >= 10000) begin
#1 ;
$finish ;
end
end
endmodule
抓取 ram 端口信号,测试结果如下。
如图可知在读、写操作中间,ram 时钟有一段一直为 0 的时刻。时钟在 ram 没有工作的时候不会翻转,实际中也会减少很多的功耗。
该方法缺点也非常明显。由于时序或抖动的原因,时钟使能信号与时钟进行”与”逻辑后,容易产生毛刺,会对数字电路产生严重影响。
在 testbench 中对时钟使能信号进行非理想的模拟,加入如下仿真代码:
//(2) jitter at the end of read
#985;
addr = 4h3 ;
clken = 1 ;
repeat(9) begin
@(negedge clk) ;
addr = addr + 1 ;
end
@(negedge clk) ;
clken = 0 ;
#20 clken = 1 ;
#21 clken = 0 ;
#31 clken = 1 ;
#13 clken = 0 ;
读 ram 的仿真结果如下。
由图可知,因为信号 clken 的异步或抖动问题,导致输入到 ram 的时钟已经出现了毛刺。地址为 0x3 的数据被遗漏(和使能信号时序有关),地址为 0xC 的数据读了 2 次。显然该门控时钟的逻辑设计非常的危险。
为解决此类问题,需要使用 latch 结构来消除毛刺。
在 《Verilog 教程》章节《Verilog 避免Latch》中讲到,数字设计中应当避免 Latch 的产生,但 clock gating 是个例外。所以在进行时序分析时,不用关心 clock gating 部分产生的 Latch。
使用 latch 消除门控时钟毛刺的电路图如下所示。
在时钟下降沿对时钟使能信号进行锁存,并保持一个时钟周期内不变。锁存后的信号再与时钟进行”与”逻辑操作,可将门控时钟中的毛刺消除掉。
将仿真例程钟直接”与”操作的门控时钟逻辑部分,改写为使用 latch 逻辑,修改如下:
//(2) using latch
reg en_latch ;
always @(*) begin
if (!clk) begin
en_latch = clken ;
end
end
wire clk_gate = clk & en_latch ;
仿真结果如下。
虽然地址为 0x3 的数据仍然被遗漏(和使能信号时序有关),但 ram 的时钟已经是正常时钟,不再出现毛刺(标黄的 CLK 信号)。
虽然使用 latch 可以解决门控时钟毛刺的出现,但是时序也需要严格的约束。
FPGA 或 IC 设计时,综合库中往往会有集成门控逻辑单元。此类门控逻辑单元经过了大量的更新迭代和验证,使用起来更加的方便、安全。
因此一般情况下,门控时钟的设计也都会直接调用专用的集成门控逻辑单元。调用方式和基本的与门、缓冲器等基本单元类似,直接例化即可。
合理的使用门控时钟逻辑,对电路时钟进行控制,也会有效的减少功耗。
增加时钟使能信号,人为的控制模块工作时钟的有无。
模块工作时,将使能信号有效,时钟打开;模块空闲时,将使能信号无效,时钟关闭,节省功耗。
上一节的仿真设计,就可以看做是手动 gating 的例子。ram 读写时,使能信号为高,ram 时钟打开,ram 开始工作;使能信号为低时,ram 不工作,此时无输入时钟。
模块在工作时,可以自动检测自己的工作状态,并输出一个 busy(忙碌) 信号。外部可以通过该指示信号,对模块内部的部分逻辑进行时钟门控,来减少时钟的翻转,达到自动 gating 的控制。
与手动 gating 不同的是,这些模块在工作时,会有短暂的空闲状态。自动 gating 就是要在这短暂的空闲状态时间内关闭掉无用的时钟,而不是像手动 gating 直接关闭掉整个模块的时钟,否则该模块就不能再正常工作。
例如 uart 在完成一次传输数据时会有一段空闲的状态,此时可将一些 fifo 逻辑、波特率产生逻辑等模块的时钟自动关闭。
例如在包含 cpu 的设计中,可加入检测 bus 总线空闲状态的逻辑,自动控制开关 bus 总线的时钟,以此降低功耗。
限于篇幅,这里先不再举例仿真。
当 RTL 设计完成之后进行逻辑综合时,编译器也会对代码的逻辑进行自动优化,这就包括将一些触发器的时钟端进行 gating。例如一个带使能端的同步 D 触发器的 RTL 描述如下:
//(2) Flip-Flop with enable port
always @(posedge CLK) begin
if (EN) begin
Q = D ;
end
end
其 RTL 前级仿真如下图所示:
综合后的仿真波形往往会如下图所示:
对比可知,综合后已经没有了 EN 信号,时钟(CP 端)经过 clock gating 后也不是一直存在。既保证了逻辑的正确性,又减少了时钟翻转,降低了功耗。
点击这里下载源码