拓冰建站拓冰建站
首页 / 资讯中心 / 正文

FPGA加法器设计:从RCA到CLA、CSA的Verilog实现与优化

1. 从“11”开始为什么FPGA开发者必须懂加法器刚接触FPGA和Verilog的朋友可能会觉得写个加法器太基础了不就是个“”号吗直接用assign sum a b;不就完事了我刚开始也是这么想的直到在一个高速数据处理的真实项目里栽了跟头。当时我用一个简单的操作符处理两个32位数据的累加时序仿真一切正常但上板实测时在特定数据模式下系统偶尔会输出错误结果排查了几天才发现是组合逻辑产生的毛刺被后续寄存器捕获了。那一刻我才深刻理解在FPGA的世界里“加法器”远不止是一个运算符它是数字逻辑的基石其实现方式直接决定了你电路的速度、面积和可靠性。对于FPGA工程师而言理解加法器的内部构造就像厨师了解刀工、建筑师懂得力学一样是基本功更是进阶的必经之路。综合工具如Vivado、Quartus确实能自动将“”号综合成某种加法电路但如果你不知道它综合成了什么就无法预知其时序特性更谈不上在性能、功耗和面积PPA之间做精准的权衡。尤其是在高速、低功耗或资源极度受限的设计中手动选择合适的加法器结构往往是优化成败的关键。本文将带你超越简单的行为级描述深入几种经典加法器的Verilog门级或结构级实现。我们会从最直观但也最慢的行波进位加法器Ripple Carry Adder, RCA开始剖析其工作原理和性能瓶颈然后探索如何用超前进位加法器Carry Lookahead Adder, CLA来打破速度限制最后我们会触及在FPGA中极具实用价值的进位选择加法器Carry Select Adder和进位保留加法器Carry Save Adder, CSA的思想。通过动手编写这些加法器的代码你不仅能巩固Verilog语法更能建立起对数字电路底层时序和结构的直觉这对于后续设计滤波器、计数器、状态机乃至复杂的DSP核都至关重要。2. 行波进位加法器RCA最直观的逻辑与它的性能陷阱行波进位加法器RCA是理解加法器原理的最佳起点它的结构直接映射了我们小学时竖式加法的过程从最低位开始相加产生的进位像涟漪一样一位一位地向高位传递。2.1 核心构件全加器Full Adder的门级实现任何加法器的基本单元都是全加器FA。一个全加器有三个输入加数A、加数B以及来自低位的进位Cin它产生两个输出本位和S以及向高位的进位Cout。其真值表如下ABCinSCout0000000110010100110110010101011100111111从真值表可以推导出逻辑表达式S A ⊕ B ⊕ Cin 异或Cout (A B) | ((A ⊕ B) Cin)在Verilog中我们可以用门级原语来精确描述这个电路这有助于理解其物理延迟module full_adder_gate ( input wire A, input wire B, input wire Cin, output wire S, output wire Cout ); wire w1, w2, w3; // 内部连线 // 计算 S A xor B xor Cin xor xor1 (w1, A, B); xor xor2 (S, w1, Cin); // 计算 Cout (AB) | ((A^B)Cin) and and1 (w2, A, B); and and2 (w3, w1, Cin); or or1 (Cout, w2, w3); endmodule注意这里使用了xor、and、or等门级原语。在实际工程中我们更多使用行为级描述assign {Cout, S} A B Cin;但门级描述对于理解延迟路径至关重要。2.2 构建N位RCA串联的艺术与延迟的累积有了全加器构建一个N位的RCA就很简单了将N个全加器串联起来低位FA的Cout连接到高位FA的Cin。下面是一个4位RCA的结构级描述module rca_4bit ( input wire [3:0] A, input wire [3:0] B, input wire Cin, output wire [3:0] S, output wire Cout ); wire [3:0] carry; // 内部进位链 full_adder_gate fa0 (.A(A[0]), .B(B[0]), .Cin(Cin), .S(S[0]), .Cout(carry[0])); full_adder_gate fa1 (.A(A[1]), .B(B[1]), .Cin(carry[0]), .S(S[1]), .Cout(carry[1])); full_adder_gate fa2 (.A(A[2]), .B(B[2]), .Cin(carry[1]), .S(S[2]), .Cout(carry[2])); full_adder_gate fa3 (.A(A[3]), .B(B[3]), .Cin(carry[2]), .S(S[3]), .Cout(carry[3])); assign Cout carry[3]; endmodule2.3 RCA的性能瓶颈分析为什么它“慢”RCA的设计非常清晰但其最大的问题在于关键路径延迟。所谓关键路径是指信号从输入到输出所经历的最长传播路径。对于RCA最坏情况发生在所有位都产生进位时例如 A4‘b1111, B4’b0001, Cin1。此时进位信号必须从FA0一直传递到FA3。假设一个全加器中从A/B/Cin到Cout的延迟为T_FA通常由与门、或门的延迟决定那么一个N位RCA的总延迟大约是N * T_FA。这是一个线性增长的关系。当位宽增加到16位、32位甚至64位时这个延迟将变得不可接受会严重限制整个系统所能运行的最高时钟频率。实操心得与仿真要点 在仿真RCA时除了验证常规的加法一定要构造最坏情况下的输入向量即全进位传播的情况并观察输出稳定的时间。在Modelsim或Vivado仿真中你可以清晰地看到进位信号像波浪一样逐级推进。这直观地展示了其“行波”之名也暴露了其速度短板。对于高速设计RCA通常只用于对速度不敏感的低位宽场景或是作为更高级加法器的组成部分。3. 超前进位加法器CLA用空间换时间打破进位传播链为了解决RCA进位传播慢的问题超前进位加法器的核心思想是不等待低位的进位结果而是直接通过加数A和B的各位预先计算出所有高位的进位。这是一种典型的“用空间更多逻辑门换时间更短延迟”的策略。3.1 进位生成G与进位传播P信号CLA的精髓在于两个中间信号生成信号Generate, GGi Ai Bi。如果本位两个加数都为1则无论有无低位进位本位都必定会生成一个进位Cout1。传播信号Propagate, PPi Ai ^ Bi。如果本位两个加数不同则本位的进位输出Cout将等于进位输入Cin。即低位进位可以“穿过”这一位。利用G和P我们可以将第i位的进位C_i表示为 C_i G_i | (P_i C_{i-1}) 这个公式是递归的。以4位CLA为例我们可以将其展开C0 Cin (假设为初始进位)C1 G0 | (P0 Cin)C2 G1 | (P1 G0) | (P1 P0 Cin)C3 G2 | (P2 G1) | (P2 P1 G0) | (P2 P1 P0 Cin)C4 G3 | (P3 G2) | (P3 P2 G1) | (P3 P2 P1 G0) | (P3 P2 P1 P0 Cin)看C1、C2、C3、C4都不再依赖于前一级FA的计算完成它们都可以直接由原始的A、B和Cin通过一层或多层组合逻辑并行计算出来3.2 4位CLA的Verilog实现下面我们用Verilog实现一个4位CLA。注意我们分为两步先并行计算所有G、P和进位C然后再用每个位的A、B、C计算和S。module cla_4bit ( input wire [3:0] A, input wire [3:0] B, input wire Cin, output wire [3:0] S, output wire Cout ); wire [3:0] G, P; wire [4:0] C; // C[0]用作CinC[4]是最终Cout // 1. 计算每一位的生成(G)和传播(P)信号 assign G A B; assign P A ^ B; // 2. 超前进位逻辑并行计算所有进位 assign C[0] Cin; assign C[1] G[0] | (P[0] C[0]); assign C[2] G[1] | (P[1] G[0]) | (P[1] P[0] C[0]); assign C[3] G[2] | (P[2] G[1]) | (P[2] P[1] G[0]) | (P[2] P[1] P[0] C[0]); assign C[4] G[3] | (P[3] G[2]) | (P[3] P[2] G[1]) | (P[3] P[2] P[1] G[0]) | (P[3] P[2] P[1] P[0] C[0]); // 3. 计算每一位的和 assign S P ^ C[3:0]; // S[i] P[i] ^ C[i] assign Cout C[4]; endmodule3.3 CLA的优势、代价与FPGA中的现实优势关键路径延迟大大缩短。对于4位CLA从A/B/Cin输入到所有进位C[1]~C[4]产生主要经过一级与或门实际因扇入过大可能需多层但远快于4级FA串联。计算和S的路径也很短。因此其延迟是对数级O(log N)增长远优于RCA的线性增长。代价电路复杂度显著增加。进位计算逻辑的扇入一个门的输入数量和扇出一个信号驱动后级门的数量随着位宽增加而急剧增大。例如C4的计算需要5个输入相或每个或项又是多个信号的与这会导致门延迟增加、布线困难并且占用更多的芯片面积和功耗。FPGA中的现实现代FPGA的综合工具非常智能。当你写assign sum a b c;时工具并不会简单地综合成一个巨大的RCA或一个扇入极大的单级CLA。相反它会根据目标器件的结构如查找表LUT的输入数量通常为4-6输入和时序约束自动将加法器分层。例如一个32位加法器工具可能会将其分解为多个4位或8位的CLA模块然后在模块间再次使用超前进位逻辑形成一种树状结构如Kogge-Stone、Brent-Kung等并行前缀结构。这些结构在速度、面积和功耗之间取得了更好的平衡。作为设计者我们通常不需要手动编写这些复杂结构的RTL但理解CLA原理能让你看懂综合报告中的关键路径并理解为什么工具会做出某些优化决策。4. 进位选择加法器Carry Select Adder另一种并行化思路进位选择加法器提供了另一种打破进位链的思路特别适合中等位宽且对速度有要求的场景。其核心思想是预先计算两种可能的结果等真实进位到来时只需一个多路选择器的延迟即可得到最终结果。4.1 工作原理预测与选择以16位加法器为例我们可以将其分为4个4位的块Block0, Block1, Block2, Block3。对于Block0最低4位它直接接收来自外部的进位Cin像普通RCA或CLA一样计算出一个结果S0[3:0]和一个进位C0。对于后面的块如Block1我们同时进行两次计算一次假设来自低位的进位是0。另一次假设来自低位的进位是1。这样对于Block1我们得到了两套可能的结果sum1_c0和sum1_c1以及两个可能的进位输出cout1_c0和cout1_c1。当Block0的实际进位C0计算出来后它作为选择信号通过一个多路选择器MUX立刻从Block1的两套结果中选择正确的那一套。以此类推Block2等待Block1的真实进位输出并选择自己的结果。4.2 Verilog实现示例以8位分为两个4位块为例module csa_8bit ( input wire [7:0] A, input wire [7:0] B, input wire Cin, output wire [7:0] S, output wire Cout ); wire [3:0] sum_low; wire carry_low; // 低位块 (bits 3:0)直接计算 cla_4bit low_block ( .A(A[3:0]), .B(B[3:0]), .Cin(Cin), .S(sum_low), .Cout(carry_low) ); // 高位块 (bits 7:4)进行两种假设的预计算 wire [3:0] sum_high_c0, sum_high_c1; wire carry_high_c0, carry_high_c1; // 假设进位输入为0 cla_4bit high_block_c0 ( .A(A[7:4]), .B(B[7:4]), .Cin(1b0), .S(sum_high_c0), .Cout(carry_high_c0) ); // 假设进位输入为1 cla_4bit high_block_c1 ( .A(A[7:4]), .B(B[7:4]), .Cin(1b1), .S(sum_high_c1), .Cout(carry_high_c1) ); // 根据低位块产生的真实进位进行选择 assign S[3:0] sum_low; assign S[7:4] (carry_low 1b0) ? sum_high_c0 : sum_high_c1; assign Cout (carry_low 1b0) ? carry_high_c0 : carry_high_c1; endmodule4.3 应用场景与权衡优势关键路径 最慢的一个块的计算时间 所有多路选择器的级联延迟。相比于一个完整的16位RCA速度提升显著。其结构规整易于用流水线进行进一步加速。代价硬件资源几乎翻倍每个高位块需要两套计算电路。面积和功耗的牺牲换来了速度的提升。适用场景在FPGA中由于LUT资源相对丰富进位选择结构是综合工具常用的优化手段之一尤其适用于位宽不是特别大如8-32位且处于关键路径上的加法操作。在手动优化时你可以通过调整“块”的大小来在速度和面积之间进行微调。块越小选择器级数越多但每个块的延迟越小块越大则反之。5. 进位保留加法器CSA与Wallace树专为乘法优化前面讨论的加法器都是针对两个操作数相加。但在很多场景下尤其是乘法器和大型累加器中我们需要将多个数相加。例如乘法器的部分积压缩。此时使用传统的进位传递加法器如RCA, CLA效率很低因为每次加法都需要等待进位链完成。进位保留加法器CSA就是为了高效处理多个操作数相加而生的。5.1 CSA的核心思想分离进位与和CSA的输入是三个操作数X, Y, Z输出是两个数S, C。其操作非常独特它将三个数在同一权位上的比特相加产生一个本位和Sum和一个进位Carry。关键点这个进位不移交给下一个高位而是作为输出C其权重要比S高一位左移一位。也就是说CSA完成了一次“3-2压缩”将三个数压缩为两个数且这两个数的和等于原来三个数的和。一个1位全加器本质上就是一个3:2压缩器它的三个输入是Xi, Yi, Zi输出Si是“和”位Cout是“进位”位且Cout左移一位后与Si相加等于三个输入的和。因此我们可以用一排全加器来构建一个多位的CSA。5.2 用CSA构建Wallace树进行多操作数求和Wallace树是一种利用CSA高效计算多个数相加的树形结构。我们以计算6个数的和为例将6个数排成一列所有位对齐。在第一级尽可能多地将每3个数一组送入CSA得到若干组S, C输出对。剩下的不足3个的数直接传递到下一级。在第二级将上一级产生的所有S和C它们现在都是二进制数连同上一级传递下来的数再次组成3个一组的集合送入新的CSA。重复这个过程直到最后只剩下两个数。最后用一个快速的进位传递加法器如CLA将这两个数相加得到最终结果。这个过程就像一棵树不断将多个分支操作数压缩合并最终汇聚到根节点最终的和。5.3 Verilog示例3操作数CSAmodule csa_4bit ( input wire [3:0] X, input wire [3:0] Y, input wire [3:0] Z, // 三个4位输入 output wire [3:0] S, // 和向量 output wire [3:0] C // 进位向量C[0]无用C[3:1]有效最终需左移一位 ); // 使用4个全加器并行处理 full_adder_gate fa0 (.A(X[0]), .B(Y[0]), .Cin(Z[0]), .S(S[0]), .Cout(C[1])); full_adder_gate fa1 (.A(X[1]), .B(Y[1]), .Cin(Z[1]), .S(S[1]), .Cout(C[2])); full_adder_gate fa2 (.A(X[2]), .B(Y[2]), .Cin(Z[2]), .S(S[2]), .Cout(C[3])); full_adder_gate fa3 (.A(X[3]), .B(Y[3]), .Cin(Z[3]), .S(S[3]), .Cout(C[4])); // C[4]是最高位进位 assign C[0] 1b0; // 最低位进位输出为0 endmodule // 注意最终结果 S {C[4:1], 1‘b0} 即C左移一位后与S相加实操心得你几乎不会在常规的加法代码中直接实例化CSA模块。但当你需要设计高性能乘法器、大型树形加法器如FIR滤波器的乘积累加或特定算法的加速单元时CSA和Wallace树的思想是无价之宝。在Vivado综合中如果你写了一个大的求和表达式如sum a b c d e;工具在优化时很可能在底层采用类似Wallace树的结构来压缩部分积以缩短关键路径。理解这一点你就能更好地解读时序报告并知道在哪些地方手动进行流水线切割会最有效。6. 在FPGA开发中如何选择与使用加法器了解了这么多加法器在实际的FPGA项目中我们到底该怎么用呢是每次都手动编写CLA或CSA吗绝大多数情况下答案是否定的。现代FPGA设计流程已经高度自动化综合工具如Vivado、Quartus II的优化算法非常强大。6.1 行为级描述信任你的工具对于绝大多数加法场景最推荐、最可维护的做法是直接使用行为级描述wire [31:0] a, b, sum; assign sum a b; // 无符号加法 reg [31:0] acc; always (posedge clk) begin acc acc data_in; // 累加器 end综合工具会根据以下因素自动选择最优的加法器实现结构目标器件架构例如Xilinx FPGA的Slice中有专用的进位链CARRY4/CARRY8工具会优先利用这些专用硬件资源来构建快速且面积高效的进位传递加法器。专用进位链的速度远快于用LUT搭建的等效逻辑。时序约束如果你对sum所在的路径设置了严格的时钟约束工具会努力优化加法器可能采用更并行的前缀树结构来满足时序。位宽和上下文工具会分析加法器所处的逻辑环境可能将其与其他操作如乘法、比较合并优化。6.2 何时需要手动干预尽管工具很智能但在一些极端或特殊场景下手动干预或理解底层结构是必要的关键路径优化当时序报告显示某个大的加法器是时序违例的关键路径时你可以插入流水线将一个大位宽的加法拆分成多个周期完成。这是最常用且有效的方法。例如将一个32位加法拆成两个16位加法中间用寄存器打拍。改变编码方式对于特定的DSP算法考虑使用冗余数制或进位保留格式避免长进位链。资源极度受限如果设计面积紧张你可以尝试强制工具使用更节省面积的RCA结构虽然慢。但通常工具在面积优化模式下会自动权衡。专用数据通路设计当你设计一个定制的算术逻辑单元ALU、加密算法核或信号处理数据通路时可能需要精确控制加法器的结构以匹配特定的数据流和流水线级数。这时手动实例化优化过的加法器模块如分块的进位选择加法器是合理的。异步或组合逻辑环路在纯组合逻辑中大的加法器会产生长延迟和毛刺。如果输出直接驱动了锁存器或异步电路必须非常小心。理解加法器的延迟模型有助于你评估风险。6.3 验证与调试加法器相关的常见坑符号位扩展处理有符号数signed加法时务必确保位宽足够防止溢出。Verilog中signed类型的运算会自动进行符号位扩展但混合signed和unsigned类型时容易出错。最稳妥的办法是明确使用$signed()转换或仔细规划位宽。进位与溢出无符号数的溢出看最高位进位Cout。有符号数的溢出判断更复杂通常规则是最高位进位与次高位进位不同。例如wire [7:0] a_s, b_s, sum_s; wire overflow; assign sum_s a_s b_s; assign overflow (a_s[7] b_s[7] ~sum_s[7]) | (~a_s[7] ~b_s[7] sum_s[7]); // 简化判断仿真与硬件差异行为级在仿真中是瞬间完成的没有延迟。但在实际硬件中它有组合逻辑延迟。如果你的设计对加法结果的时序有严格要求例如作为另一个模块的时钟门控信号就必须考虑这个延迟并通过时序仿真和静态时序分析STA来验证。利用综合属性/指令高级综合工具允许你使用属性Attribute或编译指令来指导优化。例如在Vivado中你可以尝试(* use_dsp48 “yes” *)}强制将加法器映射到DSP Slice上如果合适或者使用特定的优化策略。但这属于进阶用法需要对照文档和实验结果谨慎使用。从我个人的项目经验来看99%的情况下相信综合工具的优化能力写出干净、清晰的行为级代码是最好的选择。剩下的1%是当工具无法满足你苛刻的PPA要求时你对加法器原理的深刻理解将成为你进行手动优化、阅读综合报告、定位性能瓶颈的利器。这就像开车自动挡很方便但懂一点发动机原理能在车子出问题时帮你判断甚至能让你在赛道上开得更快。加法器就是FPGA数字引擎里的那个基础而重要的“发动机”。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门