数字IC设计技能篇之如何在RTL级降低功耗?
芯片前端后端过程中都会有对应的手段和工具做功耗的优化,本文目前只讨论前端在电路设计阶段如何降低功耗。
本文主要总结博主最近一个实际的芯片项目上,RTL级如何改变coding方式降低功耗。
数字IC设计技能篇主要总结了工作中遇到的实际问题及解决方案,欢迎一起交流共同进步;
PS:本文将持续更新。
文章目录
问题背景
博主在一个实际芯片项目中,整体芯片功率偏高,需要在RTL级降低功耗,在此过程中发现了很多自己的问题。
RTL级虽然语法简单,但是想要符合符合低功耗设计的代码规范,还有需要注意的地方。
很多都是很细节很微不足道的地方,越简单和细节的地方在工程中越容易被忽略。
一、模块级的clk gating的加入
模块级的clk gating是第一选择,如果某个模块在某些情况下不会被用到,可以使用合适的控制信号做门控时钟;
比如SRAM中的bist模块,可以将SRAM中的clk一路直接给sram中的存储体用,一路使用bist_en信号做好gating后给bist模块用。
再比如一些debug模块,只在debug的时候才会使用,那么就可以使用一个debug_en做clk gating。
GCKHBQX1AS6 u_icg ( .Q(Q), .CK(clk), .E(clk_gate_enable), .TE(TE))
GCKHBQXAS6是标准单元库中的一个clk gating单元,其中Q为输出的门控时钟,CK为原始时钟,E为门控时钟控制信号,TE是做SCAN时用的使能信号。
注意使能信号clk_gate_enable有时候和clk不是同一时钟域,要先经过cdc cell的同步。
二、模块中寄存器变量clk gating的加入
首先代码中如果是多bit的reg类型数据,一定要使用可以插入clk gating的编码风格;
下段代码是不能插入clk gating的代码:
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
a_cnt <= 4'b0;
else if (a_cnt_clr)
a_cnt <= 4'b0;
else if (a_cnt == 4'd10)
a_cnt <= 4'd10;
else if (a_cnt_flag)
a_cnt <= a_cnt + 1'b1;
else
a_cnt <= 4'b0;
end
上段代码不可插入clk gating的代码风格,因为有else;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
a_cnt <= 4'b0;
else if (a_cnt_clr)
a_cnt <= 4'b0;
else if (a_cnt == 4'd10)
a_cnt <= 4'd10;
else if (a_cnt_flag)
a_cnt <= a_cnt + 1'b1;
else if (~a_cnt_flag)
a_cnt <= 4'b0;
end
是不是改成上述代码,没有else if 就可以了呢?
并不是,改后的代码中其中两个条件是互斥的,一个是a_cnt_flag和~a_cnt_flag,也就是说计数器在任何情况下都会被赋值。即使有时候都是赋值0,a_cnt不翻转,但是有clk还是会有功耗。
可以用一种通俗的方式理解clk gating的插入:工具会自动分析a_cnt工作的条件,在不满足工作条件时会把clk关掉。
a_cnt这个变量只在条件A、B、C的情况下工作,但是如果加一个else条件,就代表在ABC条件下工作,同时在不是ABC条件下也工作,那这个时候clk在任何时间都不能被gating掉。
如果没有else,但是a_cnt要在A、B、C、和~C的条件下工作,这样也等价于a_cnt在任何时间都在工作,clk不能在任何时间被gating掉。
所以如果想在综合时能够自动插入clk gating ,always块中不能有else,也不能有互斥条件,要改写成下面的方式;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
a_cnt <= 4'b0;
//else if (a_cnt_clr)
else if (a_cnt_clr | a_cnt_clr_ensure)
a_cnt <= 4'b0;
else if (a_cnt == 4'd10)
a_cnt <= 4'd10;
else if (a_cnt_flag)
a_cnt <= a_cnt + 1'b1;
end
在最开始的代码中a_cnt_clr信号作为清零信号优先级最高,其实是不需要最后的else再清零的,直接删除最后的else清零逻辑即可插入clk gating;
但是有些情况下a_cnt_clr的清零信号可能不会按时给到你(这在实际工程中是非常可能的事情),这时候为了保险原先代码中加了else去清零。
所以为了保证清零能正常运行这时候需要一个可靠的清零信号a_cnt_clr_ensure来保证清零一定可以完成。
这个a_cnt_clr_ensure的信号可能是协议层的也可能是物理层的,要根据实际项目自己去做选择或者自己生成(在此不再赘述)。
扩展内容:
综合工具插入clk gating 是在DC脚本中插入这样一句命令:
set_clk_gating_style -sequential_cell latch \
-positive_edge_logic {integrated} \
-control_point before \
-control_signal scan_enable \
-num_stages 1 \
-minimum_bitwidth 3
上述命令中值得注意的是 -minimum_bitwidth 为3,也就是说小于3bit的reg类型就不插门控时钟了,这可能是在功耗和面积上的一个平衡;
那么对于1bit的信号如果没必要加clk gating ,是否有机会优化功耗表现?
其实对于有些信号没必要做时序逻辑,可以将其改为组合逻辑。
always @ (posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
a <= 1'b0;
else if (A)
a <= 1'b0;
else if (B)
a<= 1'b1;
else
a<=1'b0;
end
////////改进后//////////
assign a = B;
对于上述代码如果后续对a信号没有要求时序逻辑输出,那么改为组合逻辑会更省功耗;
三、检查是否含有冗余逻辑
冗余逻辑要尽量删除或者注释掉,因为在实际工程中都是迭代开发,最开始的时候的是实现方式或者需要用的一些输入输出信号,后边可能不需要了,这时候要尽快将冗余逻辑注释掉(一开始不要删除,万一需要回到最初版)。也不要接0,总之不要总想着依赖DC可以给你优化掉。
四、删除不必要的CDC Cell
cdc cell在高频时钟下因为是always on的,所以其实功耗并不小,只在必须跨时钟域的地方使用,多bit尽量不要直接跨时钟域。
五、去除隐藏的冗余逻辑
下段代码可以看作是一个地址选择器,如果有相应的使能信号,则addr被赋予对应的地址值;
在组合逻辑中必须要有else,否则会生成latch,这是我们不想看到的。
但其实addr只在对应使能信号来的时候才有效,是不需要清零的。逻辑中清零这一部分的功耗是可以省去的。
可以换一种方式来写,即虽然选择信号有A、B、C,但是只要选择A和B做选择剩下的都赋值为C条件即可。
always @ (*) begin
if (A)
addr = A_addr;
else if (B)
addr = B_addr;
else if (C)
addr = C_addr;
else
addr = 'd0;
end
////////改进后////////
assign addr = A ? A_addr : ( B ? B_addr : C_addr);
////////或者/////////
always @ (*) begin
if (A)
addr = A_addr;
else if (B)
addr = B_addr;
else
addr = C_addr;
end
在下段代码中实现的是一个计数器,这个计数器有一个cnt_set信号置位为1,有一个cnt_clear信号复位为0,如果a_cnt的值在[1-14]之间,则自加1;
不考虑DC综合会不会优化,这样的结构会用到一个三选一的选择器,其中一个选择信号还会用到两个比较器来比大小才能实现,资源会有很大的浪费;
改进后的代码只需要一个两路选择器,同时其中一个选择信号变成了比较器,节省了功耗和资源;
always @ (posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
a_cnt <= 4'b0;
else if (cnt_set)
a_cnt <= 4'b1;
else if (a_cnt > 4'b0 & a_cnt < 4'd15)
a_cnt <= a_cnt + 1'b1;
else if (cnt_clear)
a_cnt <= 4'b0
end
///////////////改进后///////////////
always @ (posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
a_cnt <= 4'b0;
else if (cnt_set)
a_cnt <= 4'b1;
else if (a_cnt != 4'b0)
a_cnt <= a_cnt + 1'b1;
end
六、数据门控
数据门控在需要大量数据运算时用的比较多,其主要关注的时非时钟信号。通用的概念就是当这些跳变是无用的计算时,防止信号跳变向下游逻辑传播。
下面是一个数据门控的实例:
assign a_out = sel ? A : A*B ;
///////////改进后//////////////
assign a_sel = sel & A;
assign b_sel = sel & B;
assign a_out = sel ? A : a_sel * b_sel;
如果乘法器没有被选择,则防止数据通路经过乘法器,通过防止无用操作去实现功耗节省。
七、数据门控和门控时钟给设计带来的负面影响是什么?(待补充更新)
时钟门控的弊端:时钟门控并不总是降低功耗,额外插入的时钟门控逻辑在枝叶节点切换时会消耗功耗。如果一个时钟基本不会被关闭,那么额外消耗的功率可能会超过时钟门控的功耗节省。另外时钟门控太多可能会造成复杂的时钟树综合,一般认为100个以上的时钟门控会导致低时钟偏移的实现更为复杂。
数据门控的弊端:数据门控引入了额外的门控单元,而这些门控单元可能会造成路径延时影响时序。
总结
以上就是本次降低芯片功耗的部分心得和收获,由于是在芯片设计阶段还没办法后端直接给出power的报告,前端一般使用spyglass和fsdb来进行功耗分析,后续会更新如何使用spyglass在前仿阶段进行power的分析,以及持续更新有关RTL级降低功耗的相关内容。