从移位相加到硬件实现:FPGA二进制乘法器的设计精髓
1. 从纸笔计算到硬件逻辑二进制乘法的本质记得第一次学二进制乘法时我拿着铅笔在纸上画了半天移位相加的步骤。比如计算1101×1011就像小学生列竖式一样先写下1101×11101然后1101×1左移一位变成11010接着1101×0左移两位变成000000最后1101×1左移三位变成1101000把这些结果相加得到10001111143。这种移位相加的方法本质上就是把十进制竖式乘法搬到了二进制世界。但真正让我感到震撼的是当我把这个算法用Verilog代码实现后综合出来的电路竟然和纸上的计算步骤一一对应每个与门对应着乘数某一位的判断每级移位器对应着纸上的左移操作而加法器链就是最后那一步求和。这种算法到硬件的直接映射正是FPGA设计最迷人的地方——你的代码就是在画电路。二进制乘法的硬件优势在于它把数学运算分解成了与、或、非这些最基础的逻辑操作。比如两个4位数相乘软件可能需要几十条指令而硬件只需要4个与门阵列生成部分积3个加法器完成累加若干移位寄存器对齐数据这种并行处理的能力让FPGA在做定点乘法时速度可以比CPU快上几十倍。我在一个图像处理项目里实测过用Xilinx Artix-7实现的8位乘法器延迟只有3.2ns而同样算法在树莓派上跑需要120ns。2. 硬件乘法器的核心架构2.1 与门阵列乘法的基础细胞设计乘法器时我习惯先从与门阵列开始搭建。这部分对应着人工计算中乘数的每一位与被乘数相与的步骤。以4位乘法器为例需要用4组4位与门生成部分积// 每位乘数控制一个与门阵列 wire [3:0] partial_products [0:3]; generate for (genvar i0; i4; i) begin assign partial_products[i] a {4{b[i]}}; end endgenerate这里有个设计细节与门阵列的输出宽度要和被乘数一致。我曾在一次项目里犯过错把部分积直接赋给8位寄存器结果综合出来一堆无用逻辑。正确的做法是后续再通过位拼接实现移位。2.2 移位操作硬件中的对齐魔法在纸上计算时我们需要手动左移硬件中则通过位拼接实现。Verilog的{}运算符就像个智能移位器// 给部分积添加对应的前导零和尾零 wire [7:0] shifted_products [0:3]; assign shifted_products[0] {4d0, partial_products[0]}; // 不移位 assign shifted_products[1] {3d0, partial_products[1], 1d0}; // 左移1位 assign shifted_products[2] {2d0, partial_products[2], 2d0}; // 左移2位 assign shifted_products[3] {1d0, partial_products[3], 3d0}; // 左移3位这里有个坑要注意移位后的位宽必须是最终结果位宽乘数位宽之和。我有次少算了一位导致乘法结果高位被截断图像处理时出现了奇怪的条纹。2.3 加法器树速度与面积的权衡最后一步求和最简单的做法是三级级联加法器wire [7:0] sum_stage1 shifted_products[0] shifted_products[1]; wire [7:0] sum_stage2 sum_stage1 shifted_products[2]; assign m sum_stage2 shifted_products[3];但在实际项目中当乘数位宽较大时比如16位我会改用Wallace树结构来优化。曾经用Carry-Save加法器重构过一个8位乘法器延迟从15ns降到了9ns代价是多用了200个LUT。这种时空权衡是FPGA设计的永恒主题。3. Verilog实现的艺术3.1 可综合编码风格刚开始写乘法器时我直接用了行为级的*运算符output reg [7:0] m; always (*) begin m a * b; // 简单但不可控 end直到有次需要优化时序才发现综合器生成的电路用了DSP48单元虽然速度快但灵活性差。现在我会明确写出结构化的移位相加逻辑这样可以精确控制用LUT还是DSP方便插入流水线寄存器利于做面积优化3.2 参数化设计技巧固定位宽的乘法器复用性差我后来改用参数化设计module param_mult #(parameter WIDTH4) ( input [WIDTH-1:0] a, b, output [2*WIDTH-1:0] m ); // 生成可变数量的部分积 wire [2*WIDTH-1:0] shifted_products [0:WIDTH-1]; generate for (genvar i0; iWIDTH; i) begin assign shifted_products[i] {{(WIDTH-i){1b0}}, (a {WIDTH{b[i]}}), {i{1b0}}}; end endgenerate // 可配置的加法器树 assign m shifted_products.sum(); // SystemVerilog的数组缩减操作 endmodule这种写法在最近的一个通信项目中帮了大忙同一套代码既能实现8位粗调又能做16位精算。3.3 时序约束要点硬件乘法器最常见的时序问题是加法器链过长。我的解决方案是设置合理的时钟约束create_clock -period 5 [get_ports clk]对关键路径插入寄存器always (posedge clk) begin stage1_reg shifted_products[0] shifted_products[1]; stage2_reg stage1_reg shifted_products[2]; m_reg stage2_reg shifted_products[3]; end必要时改用流水线结构在Zynq项目里通过三级流水线把200MHz的设计提升到了300MHz代价是增加了2个周期的延迟。4. 硬件优化的实战策略4.1 资源利用对比用Xilinx Vivado综合4位乘法器时不同实现方式的资源消耗实现方式LUT寄存器最大频率行为级(*)30650MHz移位相加280320MHz流水线移位相加3224550MHz可以看到虽然行为级写法最省资源但当我们需精确控制电路结构时还是应该采用显式的移位相加实现。4.2 进位保留技巧在高速设计中我常用进位保留加法器(CSA)来优化关键路径// 3:2压缩器示例 module compressor_3to2( input [7:0] a, b, c, output [7:0] sum, carry ); assign sum a ^ b ^ c; assign carry {a[6:0]b[6:0] | a[6:0]c[6:0] | b[6:0]c[6:0], 1b0}; endmodule这种结构可以减少加法器级数在一个雷达信号处理项目中用CSA重构的8位乘法器频率提升了40%。4.3 混合架构设计对于大位宽乘法如32位纯组合逻辑会占用大量资源。我的经验是低位部分用组合逻辑实现高位部分改用时序逻辑分步计算最后用DSP单元做结果整合这种混合架构在保证吞吐量的同时能显著减少LUT消耗。去年做的那个神经网络加速器用这种方法把乘法器面积减少了35%。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2452129.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!