Low Power RTL设计优化(转载)

原贴如下,写的很好,转载备查!
http://bbs.eetop.cn/thread-636337-1-1.html

红色字体为我自己加的理解之类的

  论坛里面讨论Low Power RTL前端设计的帖子好像不多,许多大牛的书上来就谈工艺,半导体结构,让我高山仰止,但也只能心向往之(实在有些看不懂)。
  工艺的提升带来的收益可能远比RTL深度优化高,如TSMC 40LP vs TSMC 28HPC+,后者基本上比前者面积小一半,速度快一倍,dynamic power小40~50%,代价是leakage高3~4倍。在老工艺上如何绞尽脑汁优化设计,可能也很难达到这种效果。
  但作为前端RTL designer,一旦工艺选定,我们只能在自己的一亩三分地里面做到最好,本帖就此抛砖引玉,结合自己的工作经验讨论下Low Power RTL design,挖个坑,希望坛子里的大牛一起帮忙填坑。

  刚入行做design时,考虑的是Area和Speed,近些年来,Power变成了越来越重要的指标,有时候更是牺牲Area和Speed来换取Power的收益,毕竟有些领域,电池容量是很难增大的。

  有人做过大体统计,降低功耗的方法和收益大体如下,对于前端RTL设计,最好用的还是clock gating,收益最显著,代价最小,其它方法也能有可观的收益,具体还得依靠具体应用场景分析。


image.png

  Power主要消耗在:1) combinational logic; 2) sequential logic; 3) memory; 4) clock network
  下面我将就以下几个方面展开讨论:
  1. Improve clock gating efficiency
   a. Block level clock gating
   b. RTL clock gating
  2. Reduce data toggle rate
   a. FIFO vs PIPE
   b. FSM coding style
   c. Unnecessary reset
  3. Refine memory selection
   a. Area power balance
   b. I/F bitwidth selection
   c. Depth selection
  4. Reduce memory accessing
   a. Address caching
  5. Reduce logic size
   a. Logic sharing
   b. Logic balancing
   c. Divider optimization
   d. Reduce pipe line length
   e. Hardmacro vs Register
   f. Reduce bitwidth
   g. Async reset vs non-async-reset
  6. Misunderstanding

1. Improve clock gating efficiency

1.1 block level clock gating

  如果某个模块或者功能可以打开或关闭,且logic相对独立,则可以在这部分logic的时钟上手工插入一个ICG,用模块使能控制ICG的开关,这样能最大限度的提升gating效率。
  1) ICG最好选取驱动能力大的,以便于驱动足够多的DFF;
  2) ICG建议加一个wrapper,这样当需要替换其它工艺时,只需要将wrapper里的instance换掉;
  3) 综合时对此类ICG设置don't touch

Q: 综合时对此类ICG设置don't touch,请问原因是什么?是因为综合工具可能把它优化没了吗?
A: 同型号的ICG有多种驱动能力,一般手工插入的ICG会挑驱动能力比较大的,也不希望综合工具去改这个东西。

1.2 RTL clock gating

  Low power RTL综合的精髓就是把本该综合在D端的enable信号,综合到clock端,这样只有enable有效才能释放一个clock使得D端数据传递到Q端。当D超过一定bit数,通常认为>=4bit能节省power,为了达到这种效果,寄存器的赋值一定是条件赋值,示范代码和电路如下:


image.png

  Metor有power_pro能够帮助分析RTL,提取有效的enable,并完成代码的优化,但就我看下来,它优化过的代码可读性非常差,条件有时候也有冗余,导致过多的使能ICG,而且后续如果需要ECO,还需要购买它的formal工具,不然很难再改它的code.
  Low power RTL理想代码结构

always @(posedge clk or negedge rstn) begin
    if(!rstn) begin
        Q <= 'h0;
    end
    else if(condition_1) begin
        Q <= D_1;
    // other condition 2, 3, 4 ...
    end
    else begin
        Q <= Q; // better to remove this assignment, then data will be kept
    end
end

Q: 请问line 9不拿掉不会keep吗?有点不理解,之前一直都是推荐将条件写完整
A: 留着没问题,但我喜欢代码精简,不赋值对于寄存器来说就是保持 (Q <= Q)

  错误代码的示例,如果不管在什么情况下,总有D值需要打到Q, 使得即使综合出来ICG,该ICG也不能关断。错误远不止下面几种,if/else if/else, case/default等等
  1) 没有赋值条件

always @(posedge clk) begin // "if-else" hierarchy 0, default is "else" block
    Q <= D;                 // Error: no condition for data assignment
end

  2) else赋值

always @(posedge clk or negedge rstn) begin // hier 0
    if(!rstn) begin                         // hier 1 "if" block
        Q <= 'h0;
    end else begin                          // hier 1 "else" block
        Q <= D;
    end
end

  3) 多级条件中的else

always @(posedge clk or negedge rstn) begin // hier 0
    if(!rstn) begin                         // hier 1 "if" block
        Q <= 'h0;
    end else if(condition1) begin           // hier 1 "else if" block
        if(condition1_1) begin              // hier 2 "if" block
            Q <= D1_1;
        end else begin                      // hier 2 "else" block
            Q <= D1_2;                      // not report error, as it is in "else if" block
        end
    end else begin                          // hier 1 "else" block
        if(condition2_1) begin              // hier 2 "if" block
            Q <= D2_1;
        end else begin                      // hier 2 "else" block, in hier 1 "else" block
            Q <= D2_2;                      // Error: no condition for data assignment
        end
    end
end

  4) 下面这个例子不会报错

always @(posedge clk or negedge rstn) begin // hier 0
    if(!rstn) begin                         // hier 1 "if" block
        Q <= 'h0;
    end else if(condition1) begin           // hier 1 "else if" block
        Q <= Q;                             // "Q <= Q" ensure ICG insertion
    end else begin                          // hier 1 "else" block
        Q <= D;                             // Not report ERROR as "Q <= Q" in "else if"
    end
end

2. Reduce data toggle rate

2.1 FIFO vs PIPE

  当数据需要穿过N拍delay后再使用时,我们通常有两种方法来实现,一是将数据打N级pipe,一级一级往后传;二是将数据存入FIFO,到后级需要使用时直接读出来。两种方式的vld信号都需要单独传递。
  对比PIPE方式,FIFO的方式有额外地址计算比较的开销,但每个数据只需要读写各一次,不像PIPE,数据需要在整个PIPE中shift一轮,FIFO方式每个寄存器的toggle rate将大幅下降。假设数据的toggle rate为Tr,N级pipe的toggle为N*Tr, FIFO为Tr

image.png

  如果由于FIFO输出数据+后续计算timing不满足,可以考虑缩短一级FIFO,最后一级仍由PIPE输出。
image.png

  根据我在TSMC 40nm和28nm下PTPX仿真结果分析,结论如下:
  FIFO replace PIPE for power reduction:
  when ADDR >= 2 bits, DATA should be >= 4 bits; DATA越宽,级数越深,FIFO power收益越高,但有额外的面积开销。
  上面脚本提供命令power_check.pl -2d -d 来把代码中所有的2D array找出来,有些同学喜欢把PIPE赋值写成下面的样子
代码的第2行和第3行,原文是N,我认为应该是i

for(i = 1; i < N; i ++) begin
    if(VLD[i-1])                   
        Array[i] <= Array[i-1];      // 这个代码虽然是有条件赋值,但如果符合上面的条件,可以考虑换成FIFO
end

Q: 楼主能具体讲一下关于2D reg array的问题吗?
A: 为什么2D reg array要拿出来单独讲。如果写代码的人不注意,Inst的2D reg array可能是几百或者几千bits。我曾遇到一个项目,组里一个同事为了对齐某路复杂计算逻辑结果,把另一路的一组2D reg array多打了一拍,这一拍就打掉3千多bits DFF; Power review前用此脚本抓出所有2D array,发现了这个问题,让他想办法缩短计算逻辑一拍来对齐,power大幅降低。
遇到2D reg array,需要根据应用来确定是否还可以优化:如果是做pipeline,能否考虑使用FIFO结构;如果没有multiple RD/WR的需求,是否可以用hardmacro替换。
总之,写下去的2D array最好要经过review,这个脚本能方便的把这个结果抓出来。

2.2 FSM coding style

  状态机常用编码有3中:
  1) one-hot: 如3'b001 -> 3'b010 -> 3'b100,状态个数和比特位宽相同,每次状态变化,最多只有两个bit变化。
  2)二进制编码,如3'b000 -> 3'b001 -> 3'b010 -> ..., N bit能支持种状态。
  3)格雷码,3'b000 -> 3'b001 -> 3'b011 -> 3'b010 -> ..., 如果按照顺序变化,每次只有一个bit跳变。
  总体来说,one-hot使用bit数较多,power最差,如果状态跳变有一定顺序,采用格雷码能降低toggle rate.
  40LP下4bit FSM,16个状态,从4'h0跳转到4'hf的状态机,功耗面积对比结果如下。
  即使状态机不能完全按照格雷码编码,把频繁跳转的状态用格雷码编码,也能获得举手之劳的power收益。尽量少用one-hot编码。

FSM Power Area
One-hot 1.023e-05 166.69
Binary 9.984e-06 108.48

2.3 Unnecessary reset

  对于控制寄存器,清理掉残余状态很多情况下是必要的,但对于data寄存器,数据残余可以不用复位。
  如果只在read_en的条件下把有效的数据写入rdata,完全没有必要在!read_en的时候复位寄存器,这么做只会白白增加toggle rate.

if(read_en)       rdata <= read_data;
else if(!read_en) rdata <= 'h0;          // Don't reset rdata to all 0

  如果写了这种语句,即使用power优化工具,或者上面那个脚本,都是没有办法来优化的;总的来说,寄存器使能控制越精确(打开ICG几率越小),越有利于power优化。

3. Refine memory selection

  SRAM的选择有很多讲究,我们需要看throughput, size, width来决定到底是选1p, 2p, spra和1prf,通常情况下,在满足速度要求的情况下,挑选面积小的。在这里我提一种新的挑选方法,即在速度达到要求的前提下,挑选power, area性价比最高的。
  当我们选的面积最小的为基准时,看面积增加的百分比和power减少的百分比的差值,假设area和power对性价比评估各占50%的权重,如面积增加10%,但power下降20%,则认为性价比变好了,如果只换来5%的power下降,则认为性价比变差了。
  当然我们可以调整area/power的权重来改变挑选规则,极端情况下,把area权重设置为100%就是通常情况下挑面积最小的那种方法。
  那么,我们只需要用memory生成工具,将满足尺寸的sram按照不同的rf/sram segment_option, mux, width, bits用脚本全部生成一遍,用上述规则挑选最优,有些面积相差不大的sram,power差20%~30%.
  另外,同样位宽,深度减半的也可以考虑(这个从我做的实验看,收益不如double位宽的)
  总之,挑sram不光考虑面积,可以尝试不同的选择,更多的考虑性价比。
  sram面积可以直接从生成文件里读出,但power需要手工计算:假设standby power不考虑,只考虑read/write power, 我们可以从生成文件中得到每次读写所需要的power, pwr, prd,然后根据sram使用的特性,比如在一定时间内(周期内),是多少写多少读,乘上单次读写对应的power,就可以大体估计sram的power.
  另外强烈建议在生成的sram外面加个wrapper,并手动插入一个ICG,只有在RD或者WR的时候才打开ICG,通常情况下,sram的clock是被gating掉的。

4 Memory Address Caching

  当sram使用场景需要对同一个地址多次查表时,可以考虑将上一轮读地址存下来,和新地址比较,如果在上次读之后没有对该地址的写操作,且新地址等于锁存的地址,则可以省略该轮读访问。

5. Reduce logic size

  Area大通常意味着power大,站在save power的角度,减少面积,减少逻辑单元,和降低功耗的方向是一致的。

5.1 Logic share

  有些逻辑的throughput要求不高,一份逻辑分时进行N次计算,等效于N份逻辑在一定时间内计算一次。复用之后计算总量一样,但静态功耗会降低。当然需要考虑1份逻辑N次计算带来的副作用,计算latency加长是否可以接受。

5.2 Multiplier

  乘法器,在这特指变量乘变量,如果是变量乘以常数,综合工具都是综合成加法器,不在考虑范围内。
  同等bit数,乘法器比加法器大许多,对于每个乘法器,我们都:
  1) 需要仔细review它的使用是否必须;
  2) 使用的时候位宽是不是可以降低,乘法器位宽严重影响综合面积和timing,在计算的时候,即使是signed的数,也不要轻易添加N bit最高位(符号位),建议使用verilog-2001语法,用signed(B[dw-1:0]),千万别写成{{(dw){A[dw-1]}}, A[dw-1:0]}{{(dw){B[dw-1]}}, B[dw-1:0]},虽然输出位宽为2dw.
  3) 乘法器是不是前后组合逻辑很多,造成综合工具为了meet timing而产生一个超级并行(巨大)的组合逻辑(这个在logic balance也会提到);
  4) “乘加”逻辑是不是可以转变成“加乘”逻辑,这里“乘加”指的是A
mul和Bmul乘完寄存后再加,还是(A+B)mul后再寄存,如果在同一拍内,即使写成Amul+Bmul,综合工具也会优化成(A+B)*mul,但建议在代码上体现先加后乘。
  下面是4bit和15bit“乘加”、“加乘”面积比较,“乘加”面积几乎大一倍。

Add_before_Mul or Mul_before_Add Area (um2) Multiplier number
ADD_MUL = bit4 * (bit15 + bit15) 543 1
MUL_ADD = bit4 * bit15 + bit4 * bit15 919 2

Q: 现在乘法器好像很少有人自己写了,都用的是EDA公司的DesignWare,不知道楼主那边是不是也是这样子
A: 是的,基本就写个,等着EDA工具去优化,但写在什么位置,先*还是先+,是不是可以合并计算,乘法的位宽等还是要自己考虑。

5.3 Divider optimization (N/M)

  乘法器通常1拍内完成,但除法器少有1拍完成的。乘法我们可以直接在代码里面写“*”号,综合工具通常能给我们满意的结果,但除法器,极少数情况下我们直接写“/”,大部分情况我们需要单独inst除法module.
  1) 标准除法器(N和M都是变量):
  基本的设计思路是移位减,如果每个cycle计算被除数1bit, N/M,我们需要N拍;如果每个cycle 2bit,则需要(N+1)/2拍,这样的除法器面积都不大,但计算latency长。同时EDA公司也提供design-ware除法器,速度更快,面积也更大。当然还有一种查LUT表的除法器,但我没用过,不好评价。
  2) 特殊除法器:
  2.1) 除数是2的幂次,如2/4/8,很多人会想到直接移位,对于被除数是正数来说移位绝对正确;但如果被除数是负数,除法结果收敛到0,移位结果收敛到-1.
  如-5/64 = 0, -5 = 5b'1_1011,右移后,变成5'b1_1111,= -1. 两者结果不同。
  判断高位都全为1,如A[m:0] >> n,需要确认A[m:n]的情况。
  |A[n-1:0] != 1'b0 ? A[m:n] + 1'b1 : A[m:n];
  2.2) 除数是常数,最好为(2的幂次+/-1),如5,7等,可以考虑泰勒展开,用2的幂次和来近似。
  如下,用最近的2的幂去近似的除,将余数和商迭代直到其和小于除数。Div/( - K)
  step 1 Div/ = M0 + R0 if KM0 + R0 >= , to step 2; else M = M0, R = R0 + KM0
   step 2 (K*M0 + R0)/2 ...
   如下:
   100/7 = 14 + 2 step 1 100/8 = 12 + 4 if(12 + 4) >= 7, to step 2
   step 2 (12 + 4)/8 = 2 + 0 if(2+0) < 7. result = 12 + 2 余 2
  2.3) 除数是常数,是不是可以用近似的乘法代替,比如先计算X = 1024/M,再用X*N/1024来还原成N/M.当然这个除法会有精度损失需要考虑。
  网上还有相关文档针对特殊除法器的优化,这个需要具体应用具体分析。
  正因为除法器在面积和latency上的差异非常大,特别是长latency的除法器,很容易成为计算逻辑中的瓶颈,别的逻辑都在等着它的结果才能开始后面的计算。因此在选择不同除法器时,需要考虑整个data-path的开销,减少1个cycle的除法计算,是不是能缩短整个path 1拍,可以节约多少寄存器。
  当data-path不关心除法latency的时候,直接选1bit移位减的除法器,area和power最优,如果能结合逻辑复用效果更佳。

5.4 Reduce pipeline length

  在写RTL前,我们应该知道最基本的计算单元(*, +, >=等)的延时和面积,这个数据可以通过对基本计算单元在sdc中约束紧和松的综合得到。在tight的情况下,我们能看到综合工具把逻辑并行展开,面积非常大,也行还不能meet timing;在loose的情况下,逻辑串行都能满足timing要求。两者面积相差3~6倍都不稀奇。
  Reduce pipeline,要求对基本的组合逻辑delay有基本的认识,合理的排列pipeline,同时注意关注data-path converge点,看看data-path长短的瓶颈在哪条分支上,压缩关键分支是否能带来整个path的缩短,以及这样带来的好处。
  当然预估data-path的延时和面积的影响,这个要求非常高,我自己也难做的很好,一般为了timing容易meet,大家可能都会在设计时留足余量,导致path比理想状态下长不少。但我有一个想法,也许可以帮大家找到最合理的pipeline length,我准备整个脚本来帮忙实现这个功能。(仅针对算法模块,控制少,计算多)
  1) 先直接照搬C code,不考虑timing,把中间计算逻辑RTL写出来,最后输出用寄存器,建议写成参数化的方式,方便后续re-timing;
  2) 进行tight/loose两轮综合,这样我们得到组合逻辑的最大面积(最小delay)和最小面积(最大delay);
  3) 通过最大delay/clock_period,得到最小的pipeline length;通过最小delay/clock_period,得到最大的pipeline length.
  4) 用re-timing的方式,从最小pipeline length开始,调整pipeline重新综合,不停找面积的拐点,我期待看到的结果是,中间有个面积最小的拐点,或者直到pipeline length最大,面积单调递减;
  5) 面积最小的pipeline length就是理想的结果。


image.png

如上图所示,纵轴Z是面积,X轴是频率,Y轴是pipeline length,在不同的频率下(不同的截面),不同的pipeline面积曲线是图上离散的点,我们求的就是面积最小解。
  我们可以用这个结果来指导实践RTL design,达不到最好可以采用次好,但总比没有准备直接上手要好。如果脚本准备妥当,只是花几轮综合的时间,我觉得可行性还是有的,反正都是机器跑,不然这种设计全凭经验,没有benchmark来check quality.
  我还想是不是还可以用HLS的综合结果来当benchmark,用算法C code去综合,看看catapuls给出来的建议是多少级,再和上述re-timing循环综合的最优结果比,差多少,如果两者接近,证明HLS的建议可信。

5.5 Reduce bit width

  最简单的比如4个10 bits数相加,也许就有人得出需要13bit的结论。

c code: F = A + B + C + D; 
wire [10:0] F_pre1 = A[9:0] + B[9:0];
wire [11:0] F_pre2 = F_pre1 + C[9:0];
wire [12:0] F      = F_pre2 + D[9:0]; // sum of four 10 bits only needs 12 bits result

5.6 Hardmacro vs Register

  TSMC 40nm工艺下,简单对比单个寄存器和SRAM的大小
   SDFCNQD1BWP (min size)
   SDFCNQD2BWP
   For , each bit is
   For , each bit is
   For , each bit is
  通过查手册,知道单次读写的power,以便在大量数据需要寄存时,分析到底是用hardmacro还是DFF.

5.7 Logic balancing

  综合工具在时序紧的时候,更多的用并行逻辑来减少计算delay,在时序松的时候用串行逻辑来减少面积,如果我们在写code的时候能把一些不在关键路径上的逻辑拆开,这样能优化面积,同时减少power.
  如下图,非关键路径第一拍逻辑多,第二拍逻辑少。


image.png

  Balance后的结构如下:


image.png

  一个具体实例如下:两个时序很紧的加法器balance后,area和power的变换。area和power都有下降。
Style Power Area
No-balance 6.491e-06 729.06
Balance 5.618e-06 373.08

  当然,如果为了balance逻辑,需要寄存大量的中间结果也是不合适的;上面power没降这么多是因为增加寄存器hold中间结果,这个最好能有工具提供优化提示。

Comments from huoyumutou:

  1. 状态机编码的优化感觉不是很必要,会增加调试的难度(虽然verdi中可以看到content)
  2. memory的选择还要考虑后端F&P的难度,形状不能特别奇怪,否则没法塞
  3. RAM和寄存器的对比,如果只对比面积的话,40nm下1000bits是分界线,如果1000bits以下,寄存器面积小于RAM;如果1000bits以上,寄存器面积大于RAM。除此之外,要考虑到寄存器的吞吐率比RAM高很多,这个主要考虑是不是需要这么高的频率。
    Response from lz:
  4. 状态机优化属于举手之劳,做也没多少油水;
  5. memory那项可以加一个长宽比的限制,剔除掉一些奇怪的形状;
  6. 有时候面积不是唯一的考量,我的想法是求加权平均值最优。

你可能感兴趣的:(Low Power RTL设计优化(转载))