简介一份聚焦低功耗常系数乘法器设计的PDF技术文献适合数字集成电路设计人员、数字信号处理相关研发者及电子工程专业学生阅读。内容围绕DCT/IDCT变换等场景中乘法器的低功耗低面积需求详细介绍了基于CSD编码与Wallace Tree乘法算法的新型设计并结合截断处理、变数校正等优化技术给出了SMIC 0.18μm工艺下面积139742μm²、100MHz时钟频率功耗0.69mW的综合结果同时与DA算法、改进型BOOTH算法实现方案进行了比较分析便于读者理解不同乘法器结构的性能与资源取舍。资源共1个文件为PDF格式压缩包大小149KB内容精炼但涵盖原理、算法、设计与结论。已有118人学习可作为一种高效的技术参考帮助快速掌握低功耗常系数乘法器的关键实现路径。1. 为什么低功耗常系数乘法器绕不开 CSD 与 Wallace TreeDCT/IDCT 这类变换电路里乘法器是最耗硬件也最耗电的部件之一。8×8 的 DCT 做行列分解后核心运算就是输入数据乘上 8 个固定余弦系数系数在流片前就定死了。如果用通用乘法器去实现等于把每个周期都必须做动态 Booth 编码的硬件开销背在身上这在低功耗 ASIC 里相当不划算。复旦大学这篇论文给出了一条非常务实的路径用 CSDCanonical Signed Digit编码把固定系数的非零位压到最少把乘法转成若干个移位相加再用 Wallace Tree 把这些部分积并行压缩最后通过截断处理和变数校正来砍面积与功耗。最终在 SMIC 0.18um 工艺下做到 100MHz、0.69mW面积 13974um²延迟 2.42ns。这个数字放在今天看依然有参考价值——它把一个 15bit×15bit 的乘法器功耗做到了 DA 乘法器的六成、改进 Booth 乘法器的四成。对正在做 ASIC 低功耗 IP、数字信号处理器或者 RTL 低功耗优化的工程师来说这套设计思路可以直接迁移到自己的卷积核、FIR 滤波器和变换核里。2. CSD 编码把部分积行数先砍掉一半2.1 固定系数乘法器的本质是部分积行数常系数乘法器之所以能做优化是因为乘数中有一个操作数在综合前就是确定的。15bit 的乘数用二进制补码展开其中有几个 1就意味着乘法器里要生成多少行部分积。比如系数 01111101100010115 位里有 9 个非零位对应的部分积就有 9 行这 9 行最终要经过 8 次加法才能收敛出结果。部分积行数直接决定加法器的数量、走线密度和翻转功耗。CSD 编码的意义就在于把二进制补码表示转换成“1 / -1 / 0”的三值编码并且在转换过程中保证任意两个非零位不相邻使得非零位个数达到理论最少。一个 n 位的普通二进制数非零位平均数量级是 n/2 左右CSD 编码后平均降到 n/3 左右。对 15 位系数来说通常能把部分积行数从 7~9 行压到 4~6 行。CSD 编码还有个容易忽略的性质它的表示是唯一的。同一个数的二进制补码可以有很多种等价写法但 CSD 只有一种最小非零位形式这意味着硬件实现时不需要在多个等价编码里做搜索直接按规范转换即可。以论文表 1 中 8 位数的例子来看二进制补码 01111101 有 5 个非零位CSD 表示后是 1000-10-10只有 3 个非零位而且相邻位不同时为非零。换成工程语言部分积从 5 行减到 3 行加法树少两级面积和功耗自然下降。2.2 从二进制补码到 CSD 的转换规则转换算法从最低位向最高位扫描规则就三条当前位为 0且没有进位写 0当前位为 1且下一位为 0且没有进位写 1当前位为 1且下一位也为 1或者有进位当前位写 -1并产生一个进位到更高位这个规则等价于把连续的“1”序列做替换。举例来说4 位二进制 1111 表示 15转换时从最低位开始连续四个 1最终得到 1000-1即 16-1非零位只有 2 个。8 位二进制 01110110 表示 118转换后是 1000-10-10即 128-8-2非零位 3 个。每一位的权值就是 2 的幂次硬件上对应一次移位操作非零位在第 k 位就把输入左移 k 位符号由 1 或 -1 决定所有移位结果累加即完成乘法。在 DCT/IDCT 的系数场景中这个收益更加直接。论文表 3 列出了 cos(nπ/16) 的 15 位补码与 CSD 编码对比节选几个系数如下n二进制补码表示非零位个数CSD 编码表示非零位个数10111110110001019100000-10-100010153011010100110111910-101010100-100-174010110101000001610-10-101010000016700011000111110070010-10010000-1004从表格能看出n1 的系数补码表示有 9 个非零位CSD 后降到 5 个部分积行数直接少 4 行n7 的系数从 7 个非零位降到 4 个。注意 n4 这个特例两种编码非零位个数相同都是 6说明 CSD 并非在所有系数上都能带来收益但对于大多数余弦系数减少 30%~40% 的部分积行数是常态。2.3 在 RTL 里如何生成 CSD 部分积CSD 编码在硬件上并不会真的存一个三值数的 ROM而是把系数预先转成移位与加减的组合。 下面是一个工程上常见的参数化部分积生成代码// 常系数乘法器部分积生成: 系数以 CSD 位串形式给出 // csd[ pos ] 1 - 左移 pos 位后加 // csd[ pos ] -1 - 左移 pos 位后减 module csd_partial_products #( parameter DATA_W 15, parameter COEF_W 15 )( input wire [DATA_W-1:0] din, output wire [DATA_WCOEF_W-1:0] partial_products [0:5] // 最多 6 行 ); localparam [COEF_W-1:0] CSD_COEF 15b100000_10_1000101; // 示意值 genvar g; generate for (g 0; g COEF_W; g g 1) begin : gen_pp if (CSD_COEF[g] 1) begin assign partial_products[g] { {COEF_W{1b0}}, din } g; end else if (CSD_COEF[g] 1b1) begin // 实际工程中需区分 -1 assign partial_products[g] ~({ {COEF_W{1b0}}, din } g) 1; end else begin assign partial_products[g] 0; end end endgenerate endmodule这段代码的核心逻辑是遍历系数 CSD 位串的每一位遇到 1 就生成一个左移后的部分积遇到 -1 就生成取反加一后的补码形式遇到 0 直接给全零行。代码里的partial_products数组每个元素宽度是DATA_W COEF_W因为 15bit 乘 15bit 的完整乘积有 29bit。参数DATA_W和COEF_W分别控制输入和系数位宽实际使用中把 CSD 位宽固定成参数便于在多个余弦系数之间复用同一个模块。有一点需要提醒CSD 中的 -1 位不能用 Verilog 的普通bit select直接表达实际工程里要么把位串拆成正位掩码和负位掩码两个数组要么在预处理阶段就把系数展开成一组“移位量 符号”的结构体。上面代码只是为了说明映射思路真正可综合的版本应该用localparam声明正负位掩码再用generate条件分支生成对应的加法和减法部分积行。3. Wallace Tree 压缩把 15 行部分积压成 2 行3.1 为什么选择 Wallace Tree 而不是串行加法链部分积生成之后常规做法是像竖式乘法那样逐行累加但每加一行都要等前一行出结果关键路径是线性的15 行部分积就需要十几级的加法延迟。Wallace Tree 的思路是改变相加顺序不再让部分积逐行串行相加而是把每一列上相同权重的 bit 独立出来用全加器3:2 压缩器和半加器2:2 压缩器并行压缩。全加器输入 3 个同权重的 bit输出 1 个本位和 1 个高一位进位相当于把这一列的 bit 数从 3 降到 2多轮压缩后所有列的高度最终降到 2 行最后再用一个超前进位加法器CLA一次性把这两行加起来。论文图 4 里画的就是这个结构部分积矩阵先经过若干层全加器和半加器最后一列方块是超前进位加法器。Wallace Tree 的压缩层数与部分积行数呈对数关系。15 行部分积的压缩级数大约在 6 级左右而串行累加至少需要 14 级加法关键路径缩短了一半以上。论文里给的是 2.42ns 的延迟相比改进 Booth 乘法器的 2.86ns 有约 15% 的优势就是这个并行压缩带来的。3.2 压缩过程的列高变化以 15×15 乘法器为例部分积矩阵有 15 行、29 列初始列高分布是中间高两边低。使用 3:2 压缩器迭代后各轮压缩的最大列高变化如下压缩层级操作最大列高第 0 级初始部分积矩阵15第 1 级每列 3 个 bit 一组做全加器压缩10第 2 级继续按列 3:2 压缩7第 3 级继续压缩5第 4 级部分列用半加器处理4第 5 级最终压缩到两行2这里每一级的“3:2 压缩”指的是某一列如果有 3 个 bit送入一个全加器产生 1 个本位和留在本列和 1 个进位移到高一位列。压缩后该列 bit 数减少 1但高一位列会增加若干 bit所以列高分布会动态变化实际实现时需要通过列高统计来安排全加器的位置。Wallace Tree 还有一个常被忽略的好处进位不再需要沿着整条加法链逐位传递而是在压缩树内部局部消化只有最后一级 CLA 才需要真正处理进位传播。 这对降低动态功耗很有帮助因为进位链上的毛刺和重复翻转被限制在最后几级中间级只做局部压缩。3.3 一个可综合的压缩框架Wallace Tree 的 RTL 实现可以抽象成“按列分组、逐级调用 3:2 压缩”的循环结构// 3:2 压缩器: 输入三个同权重 bit, 输出 sum 和 carry module compress3_2 ( input wire a, b, c, output wire sum, carry ); assign sum a ^ b ^ c; assign carry (a b) | (b c) | (a c); endmodule // Wallace Tree 压缩主逻辑 (示意) module wallace_compress #( parameter ROWS 6, // 部分积行数 parameter WIDTH 29 // 部分积列宽 ) ( input wire [WIDTH-1:0] pp [0:ROWS-1], output wire [WIDTH-1:0] sum_row, carry_row ); // level[0] 保存初始部分积 // 逐级调用 compress3_2 对每列做分组压缩 // 压缩过程中 sum 留在本列, carry 送到高一位列 // 直到所有列的高度 2, 输出 sum_row 和 carry_row endmodule这段代码只是一个结构框架真正的实现难点在列管理的细节每一列的高度是动态的压缩后高一位列会接收来自低位列的进位因此需要维护一个“列高度数组”每一轮都统计当前每列有多少个 bit再按 3 个一组分配全加器、2 个一组分配半加器。实际做 RTL 时我一般用generate块加function来生成压缩树而不是手动例化每一级的全加器否则 29 列的矩阵结构维护起来很容易出错。ROWS参数在 CSD 优化后通常取 4 到 7对应不同余弦系数的非零位个数。这里有个工程选择如果为 8 个 DCT 系数分别做一套 Wallace Tree面积开销太大更常见的做法是让部分积行数最少的系数决定压缩树的规模行数多的系数做流水共享或者干脆把行数统一扩展到位宽上限让所有系数复用同一棵压缩树。3.4 CSD 与 Wallace Tree 的组合逻辑CSD 减行、Wallace Tree 加速这两者并不是独立的设计决策。Booth 编码也能减行但它处理的是动态乘法的场景乘数每个周期都在变编码逻辑本身要消耗额外的面积和功耗。常系数场景下乘数固定CSD 是在综合前就完成了编码硬件上完全没有 Booth 编码器的开销。论文里把改进 Booth 乘法器的功耗做到 1.59mW而 CSDWallace Tree 的功耗是 0.69mW差异主要来自这里Booth 编码器在每次乘法时都要实时运算CSD 编码器则是一组固定的移位导线。4. 截断处理与变数校正误差与功耗的工程权衡4.1 全精度输出在 DCT 场景下是浪费15bit 输入乘以 15bit 系数完整乘积是 29bit。但 DCT/IDCT 的中间结果并不需要 29bit 的全精度最终输出只要 15bitQ3 格式。如果把 29bit 全部算出来再截断等于为不需要的低位做了大量无用加法。这就带来截断乘法器的基本思路直接把部分积矩阵的低列砍掉只保留对最终输出高位有影响的列。论文图 2 展示的就是这个结构右侧浅色区域是截断掉的低列部分积左侧深色区域保留参与 Wallace Tree 压缩。这样做的问题在于截断会引入系统性误差。低列部分积虽然权值小但数量多累积起来对高位的贡献不可忽略。如果完全不补偿乘法器输出会存在明显的负向偏差。4.2 常数校正与变数校正的数学对比截断误差的补偿有两种主流方案。常数校正的思路是假设被截断的每一列中逻辑 1 出现的概率是 1/2那么截断误差的期望值可以算出来在结果上加上一个固定常数 C 来抵消。补偿向量 C 的计算公式在论文中写为C (2n 2k - 1) / 2^(k1)其中 k 是截断的最低有效列到最高有效列的跨度n 是输入位宽。这个公式的物理含义是把被截断列的所有 bit 按概率期望求和得到平均截断误差再把它的相反数做成补偿常数。常数校正实现非常简单RTL 里就是加一个常数但有两个明显的缺陷。第一当乘法器输入为 0 时输出应该为 0但常数校正会输出一个非零常数 C这在 DCT 变换中会导致直流分量计算错误。第二它只能保证平均误差为 0最大误差和方差都偏大。论文表 2 给出了 16bit×16bit、输出 18bit 截断乘法器的仿真对比误差指标常数校正变数校正平均误差1.640.26最大误差5.002.21标准差1.460.57变数校正在三个误差指标上全面优于常数校正尤其是最大误差从 5 降到 2.21标准差从 1.46 降到 0.57。它的原理是不用固定常数而是用被截断区域的最高一列第 nk-1 列的部分积作为补偿向量把这个向量加到相邻的保留列上。这一列本来就要参与压缩只是被判定为“可丢弃”现在把它的一部分信息引入高位相当于把截断误差的符号和大小随输入数据动态调整所以叫变数校正。4.3 变数校正在 Wallace Tree 里的实现方式变数校正的硬件实现比想象中省资源。第 nk-1 列的部分积有若干个 bit把这些 bit 按位相加得到的值恰好能反映出被截断列数据的大致量级。具体做法是把这一列的部分积作为额外的补偿行送入 Wallace Tree 的压缩级与第 nk-2 列的保留部分积相加。由于 Wallace Tree 本身就需要处理多个输入行补偿行只是多占一个小列的压缩资源并不会增加独立的加法器。工程实现时要注意一个细节补偿行不能直接和所有保留列一起参与压缩因为它对某些列的影响是负向的。 正确做法是把补偿行的各位按权值分配到对应的列并标记为减法项在最后一级 CLA 之前统一处理符号。论文图 3 的矩阵图中补偿项被安排在原第 nk-2 列的位置正是为了让它和周围列的部分积一起经过全加器压缩避免额外的符号处理逻辑。5. 从 RTL 到 SMIC 0.18um 综合面积功耗实测与复现路径5.1 功能仿真从 Modelsim 到现代仿真器论文使用 Verilog 完成行为描述并在 Modelsim 5.5 下做了功能仿真。当时的验证流程在今天看来依然有效只是工具版本可以替换成 VCS 或 QuestaSim。仿真流程的核心是三点给足随机激励、对比全精度乘法器与截断乘法器的输出、统计误差是否在 DCT 容许范围内。一个最小可用的仿真脚本如下vlib work vlog -sv multiplier_15bit.v tb_multiplier.v vsim -novopt work.tb_multiplier add wave -position end sim:/tb_multiplier/* run -all仿真时建议在 testbench 里同时例化一个理想乘法器模型直接用*运算符作为参考。每跑一组随机输入就计算一次截断乘法器与理想乘法器的差值记录最大误差与均方误差。论文没有给出具体允许的误差阈值但 DCT 变换的典型需求是峰值信噪比不低于 40dB对应的截断误差标准差大概在 0.5 到 1 之间。变数校正把标准差压到 0.57正好落在这个区间内。5.2 综合约束与功耗分析逻辑综合使用 DC 或 Genus工艺库选择 SMIC 0.18um。综合时建议把时钟约束设得比目标频率略紧论文目标是 100MHz也就是时钟周期 10ns实际综合约束可以设到 9.5ns 左右给布局布线留出余量。一个典型 SDC 约束文件如下set CLK_PERIOD 10.0 create_clock -name clk -period $CLK_PERIOD [get_ports clk] set_clock_uncertainty 0.1 [get_clocks clk] set_clock_transition 0.1 [get_clocks clk] set_input_delay 1.0 -max -clock clk [all_inputs] set_output_delay 1.0 -max -clock clk [all_outputs] set_max_area 0 set_max_dynamic_power 0这里的set_clock_uncertainty通常设周期误差和片上偏差的总和0.18um 工艺下 0.1ns 到 0.3ns 都算合理。set_input_delay表示外部逻辑到乘法器输入的到达时间设得越紧综合器对乘法器内部逻辑的时序要求越高面积和功耗也会相应增加。实际优化时建议先把输入输出延迟放宽到 2ns 跑一版看面积功耗是否满足要求再逐步收紧。功耗分析需要注意操作条件设置。SMIC 0.18um 库通常提供ss_1.62v_125c和tt_1.8v_25c两种操作条件前者是慢速高温情况后者是典型情况。论文报告的 0.69mW 应该是在典型条件下的总功耗。做功耗分析时还需要提供翻转率toggle rate约束默认 0.2 的整体翻转率对流片后的实际功耗估计会偏乐观建议按 0.3 到 0.4 反标。5.3 三种乘法器的实测性能对比论文表 4 给出了三种乘法器在 SMIC 0.18um 工艺下的综合结果指标DA 乘法器改进 Booth 乘法器CSDWallace Tree 本设计最高速度 (ns)2.402.862.42面积 (um²)167302254913974功耗 (mW100MHz)1.131.590.69三列数据放在一起能看出几个值得注意的位置DA 乘法器速度最快2.40ns但面积比本设计大 20%功耗高了近一倍原因是 DA 需要 ROM 存储查表数据和流水寄存器改进 Booth 乘法器面积最大、速度最慢、功耗最高因为 Booth 编码器动态工作且乘数和被乘数都不是常数时无法预优化CSDWallace Tree 的设计在速度与 DA 接近的前提下把面积和功耗做到了三者最低。功耗方面本设计比 DA 低 39%比 Booth 低 57%这个差距主要来自部分积行数少带来的加法器数量下降以及截断后低列不再翻转。有一点需要说明这是 15bit×15bit、输出 15bit 的特定场景结果换成 32bit 乘法器Booth 编码减少部分积行数的优势会更明显CSD 的截断和校正策略也需要重新评估。 但 DCT/IDCT 这类图像处理场景位宽和精度要求决定了 CSDWallace Tree 的这套组合在区域上有明显优势。6. 让常系数乘法器更省电的三个进阶技巧6.1 用 CSE 复用公共子表达式DCT 的 8 个余弦系数之间存在大量公共移位项。例如 cos(π/16) 和 cos(7π/16) 都包含类似 x3、x5 这样的子项与其让每个乘法器各自做一次移位不如先算出公共子项再分发到各个系数。以式子的形式来看假设 x 是输入先计算 x3 x3、x5 x5、s35 x3 x5然后再让每个系数乘法器去组合这几个子项。这样单个系数乘法器的部分积行数还能再降 1 到 2 行。实现时先在系统层面做一次公共子项扫描列出所有系数的 CSD 位串把出现次数超过 2 的移位项提取出来无法提取的再保留在乘法器内部。6.2 用误差预算脚本确定截断列数 k截断列数 k 的取值直接决定面积和功耗但论文中一笔带过。工程上我习惯用脚本做参数扫描先在所有可能的 k 值下跑一遍数值误差模型统计最大误差和均方误差再对照 DCT 系统的 PSNR 预算确定 k。这个流程可以用一段简短的 Python 代码模拟import random def truncated_multiplier(x, c, keep_bits15): full x * c # 模拟硬件截断: 去掉低扩展位后保留高位 shift 15 15 - keep_bits return full shift x_list [random.randint(-16384, 16383) for _ in range(100000)] c 16070 # cos(pi/16) 的 Q14 定点表示 err [x*c - truncated_multiplier(x, c, 15) for x in x_list] mse sum(e*e for e in err) / len(err) print(MSE , mse)这段脚本把硬件截断简化成最终结果的移位没有模拟 Wallace Tree 内部每列的精确截断但用于在系统设计阶段快速筛选 k 值已经够用。keep_bits15对应输出位宽c16070对应 Q14 格式的 cos(π/16)。注意这只是误差预算验证真正定 k 还要跑完综合比较不同 k 值下的面积功耗报告通常 k 每加 1面积能省 3% 到 5%最大误差增大约 0.5 个 LSB。6.3 操作数隔离与门控时钟CSD 乘法器的功耗由两个来源组成部分积生成级的移位翻转以及 Wallace Tree 内部加法器的动态翻转。当输入数据有效信号拉低时这两个来源可以通过操作数隔离来截断。做法是把部分积生成级的输入寄存器替换成使能控制的保持寄存器valid无效时锁存上半周期的旧值输出阶段保持稳定Wallace Tree 内就没有多余翻转。另一种做法是在输入为 0 时直接旁路掉压缩树输出清零这个逻辑对 CSD 乘法器尤其有效因为截断设计保证输入为 0 时输出也为 0只是功耗不会因为输入为 0 自动降下来必须显式加门控。时钟树上的功耗同样值得处理。论文的 0.69mW 应该是未加门控时钟的裸功耗如果在 8 个系数乘法器之间加上时钟门控让没有数据到达的通道在空闲周期关闭时钟整体功耗还能再降 20% 到 30%。具体做法是用一个 AND 门把clk_en和clk组合成局部时钟送进乘法器单元综合库里的CLKGATE单元会自动处理时钟偏斜和毛刺过滤不需要在 RTL 里手工例化门控逻辑。本文还有配套的精品资源点击获取