☰
FPGA底层揭秘:CARRY4进位链如何让加法器跑得更快?
2026/10/3 4:17:34 网站建设 项目流程

很多FPGA工程师用了很久的Vivado,看惯了密密麻麻的LUT和触发器,下意识觉得一个加法器就是“一堆LUT拼一拼”。真去做高速累加、计数器、比较器,或者高分辨率时间数字转换(TDC)这类设计时,你会发现一个名字大量出现在原理图和时序报告里:CARRY4。这篇笔记就专门聊聊这个很多人“用过但没深究”的原语,扒一扒它内部到底是怎么工作的,又是靠什么让加法跑得比普通逻辑快这么多。

文章适合正在学FPGA的初学者,也适合写了好几年RTL但没仔细看过底层网表的工程师。我会从它在整个芯片里的位置讲起,再拆解引脚和内部逻辑,接着用加法器做完整推演,最后聊几个和Carry4强相关的实战场景。看完之后,你再回头看时序报告和资源利用率,会有不一样的感觉。

1. 别小看这条专用走线:Carry4在FPGA里的真实地位

1.1 整个FPGA的算术逻辑是怎么拼起来的

FPGA的底层计算单元,大家最熟悉的是CLB(可配置逻辑块)。以Xilinx 7系列为例,一个CLB里含有2个Slice,每个Slice内大致有4个6输入查找表(LUT6)、8个触发器(FF),还有一些数据选择器和专用进位链。这块芯片能实现五花八门的逻辑,靠的就是LUT查表、FF存状态,以及大量可编程互连把资源串起来。

但有一个问题常常被忽略:如果只靠LUT和普通互连去做加法,速度上不去。普通互连走线延迟很大,而且你永远不知道这条线会被布线工具绕到哪儿去,路径延迟非常不稳定。所以芯片设计者在一开始就加入了专用的算术逻辑,专门负责进位传播,这就是CARRY4。它的作用可以理解为加法器里的“专用快车道”。

不只是加法,减法、比较、计数器、宽位宽选择器,甚至TDC里的延迟线,都会用到CARRY4。换句话说,你写的RTL里只要出现a + b这种运算,综合器几乎一定会把它映射到CARRY4上。你每天在资源报告里看到的LUT/FF利用率,只是明面上的一部分,Carry4才是隐藏的算术主力。

如果你想做一个连续累加模块,比如ADC数据求平均,或者图像像素累加直方图,那么每个采样周期都需要做一次加法。这些加法的位宽越大,CARRY4的级联就越长,对时序的影响也就越明显。从入门写的数码管动态扫描、交通灯计数,到后续的ISP流水线、TDC直方图,Carry4都贯穿始终。

1.2 为什么只看LUT和普通互连走不通

我们先想象一下,如果Xilinx偷懒,没有在Slice里放CARRY4,加法会怎么实现。一个N位加法器,最简单是把每个bit的进位从低位到高位逐级传递,每一级都需要计算“进位生成”和“进位传播”。用LUT确实能算出来,但问题在于:LUT的输出要经过通用互连网络,才能到达下一级LUT的输入。通用互连的延迟是随布局布线变化的,少则几百皮秒,多则几纳秒,而且布线越绕延迟越不可控。

N位加法的关键路径就是最低位进位逐级传到最高位的过程。如果是32位加法,进位要经过32次LUT输出、通用走线、再输入LUT,这个延迟在几百MHz时钟下大概率直接时序违例。就算你想改成超前进位加法器,在FPGA这种固定查找表结构上,也不一定比逐级进位更快,因为LUT数量增多,布线更复杂。

CARRY4的存在就是为了解决这个问题。它在Slice内部用专用金属线连接,进位信号从一个CARRY4传到下一个CARRY4,不需要经过通用互连网络,路径短、延迟低,而且每次传播的延迟非常均匀。正因为有了这条硬连线,Xilinx才能在同样工艺下把加法器跑到非常高的频率。

这也是为什么在综合后的原理图里,你经常能看到LUT和CARRY4交替出现:LUT负责生成中间结果,CARRY4负责把进位快速往前推。两个角色分工明确,谁都不替代谁。

2. Carry4的内部结构与关键信号定义

2.1 引脚说明与原语真值表

在Vivado里,CARRY4是一个可以直接例化的原语。它一共有6组端口,我习惯把它理解成一个“4bit进位处理单元”。

先看端口定义:

端口名方向位宽作用
CYINIT输入1进位链第0位的初始进位值,通常接0或1
CI输入1来自上一级CARRY4的进位输入,用于级联
DI输入4数据输入,用来决定进位链是“生成进位”还是“传播进位”
S输入4选择输入,通常来自LUT输出,参与求和与进位选择
O输出4算术结果输出
CO输出4进位输出,CO[3]用于连接下一级CARRY4

CARRY4内部每一级的基本逻辑,可以简化为两个公式:

O[i] = S[i] XOR CI CO[i] = DI[i] ? CI : S[i]

这里的CI在每一级内部会更新。第0级的进位输入是CYINIT或外部CI,第1级用的是第0级的CO[0],以此类推。这实际上是一个逐位进位结构,但因为它用芯片内部的专用路径,所以速度远快于普通互连。

用真值表看更直观。假设CI是当前进位输入,S和DI是已知信号,那么CO的输出如下:

DISCO
000
011
10CI
11CI

当DI为0时,进位输出由S决定;当DI为1时,进位输出等于输入进位CI,也就是说这一级把进位直接透传过去了。这就是“传播进位”的含义。

很多人第一次看这个端口会懵,为什么既要S又要DI?简单来说,S通常连接LUT的一部分输出,用来生成和位;DI提供另一个额外输入,决定当前bit是否对进位做“传播”。综合器会自动替你接好,不需要手动控制。但如果你想做TDC延迟线或者底层调试,就必须搞清楚这两个信号到底怎么配合。

2.2 每个基本单元里到底有什么

一个CARRY4本质上就是4个相同的进位单元,每个单元由两个核心构件组成:一个MUXCY和一个XORCY。名字都带CY,说明它们专为进位而生。

MUXCY负责进位选择,实现CO[i] = DI[i] ? CI : S[i]的逻辑。当DI为1,进位直接透传;当DI为0,CO变成S的输出。XORCY负责产生异或结果,用来计算当前位的“和”,对应O[i] = S[i] XOR CI。

这两个构件配合起来,正好覆盖了加法器的全部需求:每一位的和由异或门生成,每一位的进位由多路选择器生成。由于这些都是固定硬逻辑,不像LUT那样还需要查表,所以进位传播非常快。

我们可以把进位链想象成接力赛跑道。LUT把每一棒的“状态”算好,MUXCY是接力棒的传递点,XORCY是每个运动员冲刺时打出的时间点。正常布线的LUT间连接相当于运动员每次都要绕操场跑半圈再交棒,而CARRY4这条专用链,等于直接把下一棒递到队友手里。

这里有一个工程上很关键的点:CARRY4内部各级之间的延迟,基本是固定的,不随布局布线变动。所以你在静态时序分析报告里看到CARRY4级联路径时,它的延迟基本恒定,变数更多在外围LUT到CARRY4的连接线上。

2.3 LUT和Carry4的配合:谁算加法,谁传进位

写代码的时候你只需要写assign sum = a + b,综合器会自动决定什么信号接S、什么信号接DI。但在底层,这个分工其实很有讲究。

以最简单的1bit全加器为例,输入是a、b和进位cin,输出是sum和cout。LUT可以算出S = a XOR b,DI端则根据综合器的选择,可能接a AND b或者直接复用其他输入。当a XOR b为0时,说明a和b相同,这时需要生成一个新的进位;当a XOR b为1时,当前位会把cin直接透传到下一级。S和DI的不同组合,正好对应加法器里的“生成”和“传播”概念。

位宽更大的加法器也遵循同样的逻辑:LUT6有6个输入,能一次处理多位加法的部分逻辑,CARRY4则负责把这些位之间的进位串起来。用GNU Radio的朋友可能对“相位累加器”很熟,DDS里的相位累加器也是这么实现的,位宽长、进位链长,但整体频率依然可以很高。

所以你在综合后的原理图里看到的不再是“一个LUT算一位加法”,而是“LUT算局部结果,CARRY4算进位,O输出直接接D触发器或者继续往下游逻辑”。能不能看明白这张网表,基本等于你对FPGA算术逻辑的理解程度。

3. 从加法器出发,看Carry4的完整工作过程

3.1 一个LUT为什么能同时处理两位加法

很多初学者会有个疑问:一个加法器不是每一位都需要一个LUT吗?怎么Xilinx看起来这么节省,一个LUT能管两位?

关键在LUT6的灵活性。6输入LUT其实可以拆成两个5输入LUT,或者按Xilinx的配置方式,用来同时实现两个独立函数。在加法器的映射中,综合器让一个LUT同时处理a[i]、b[i]、cin以及a[i+1]、b[i+1]这两组输入,分别产出两个S信号,分别给到CARRY4的两个S端口。

你可以这样理解:加法器里真正需要“查表”的部分,是把两个输入和进位做逻辑化简。6输入LUT刚好可以装下两位加法的所有输入组合,所以一个LUT能产出两个bit的候选结果。而进位是否真的传递、传递到哪里,则由CARRY4内部搞定。

这也是CARRY4设计成4bit一组的原因:它正好对应一个Slice里4个LUT的S输出。综合器会尽量把4个相邻bit的加法操作放到同一个CARRY4里,让进位链在Slice内部就能完成4级传播,然后再走到下一个CARRY4。

实际写RTL时,不用关心这些底层映射细节。但当你开始分析时序报告,看到关键路径上一串CARRY4的级联,就要知道:这条路径是进位逐级传过去造成的,和你在C语言里写循环累加不是一回事。它的延迟基本等于“每级CARRY4进位延迟 × 级联个数”,这个数字非常直观。

3.2 32位加法器到底消耗多少资源

我们来算一笔具体的账。按常见做法,每个LUT6处理两位加法,那么32位加法器大约需要16个LUT6。每4bit进位需要一个CARRY4,32位就需要8个CARRY4。一个Slice内有4个LUT6和2个CARRY4,所以这32位加法器大约占用4个Slice的算术资源,另外还要加上最后输出寄存器的8个FF。

不同位宽的加法器资源估算大概如下表:

加法位宽LUT6数量CARRY4数量
8 bit42
16 bit84
32 bit168
64 bit3216

这个表格是根据常见综合结果估算的,实际会受周边逻辑影响,但量级是准的。我见过有人做资源评估只盯着LUT和FF,结果CARRY4已经被占满了却毫无察觉,最后布局布线阶段报错,才回头查资源报告。做FPGA选型和资源评估时,CARRY4的占用必须纳入考虑。

你可能会问,DSP48也能做加法,为什么还要用CARRY4?因为DSP48内部虽然也有算术单元,但它更擅长乘加运算。普通的位宽加法、计数器、比较器如果都塞给DSP48,DSP48的数量和连接灵活性都是问题。CARRY4分布在每个Slice里,数量多、位置广,更适合作为通用的算术执行单元。

3.3 用Vivado实际看一下CARRY4长什么样

理论讲再多,不如自己打开Vivado看一眼。你可以写一个最简单的加法模块,综合后打开Schematic,然后按名称过滤“CARRY4”。

在Tcl控制台,也可以用这条命令把所有CARRY4实例抓出来:

get_cells -hier -filter {PRIMITIVE_TYPE =~ *.CARRY4}

或者看当前设计里有多少CARRY4:

get_property PRIMITIVE_TYPE [get_cells u_adder/carry4_0]

我第一次认真看这个原理图时,印象最深的是CARRY4的O输出直连旁边的D触发器,而CO输出则像一条链条一样连到下一个CARRY4的CI。LUT6的输出分成两路,一路给CARRY4的S端口,一路给O端口的异或逻辑。这个结构和我们前面讲的完全一致。

这里有个实操建议:不要手动例化CARRY4,除非你明确知道自己在干什么。Vivado综合器已经非常聪明,它会自动判断你的代码是应该走CARRY4、LUT还是DSP48。手动例化不仅会让代码可读性变差,还可能因为S和DI接错导致功能完全不对。除非你在做TDC延迟线这一类特殊设计,或者要做结构化的算术单元IP,才考虑底层例化。

4. Carry4在典型场景里的应用:计数器、宽MUX与TDC直方图

4.1 计数器与比较器为什么离不开进位链

计数器本质上就是“当前值 + 1”的加法器。一位一位的增量从低位传到高位,所以计数器的位宽越大,进位链就越长。交通灯计秒、出租车计价器里的里程脉冲累加、串口波特率分频,本质上都是这类累加操作。

比较器比大家想象中更依赖进位链。硬件上实现a > b,常见办法是计算a - b,看结果是否为负/是否借位。减法器在做二进制补码转换后,本质上又变成了一次加法,进位链同样参与其中。所以不要以为比较器只是几个门电路拼一拼,在FPGA里它同样会消耗CARRY4资源。

做时序优化时,如果发现关键路径是一长串CARRY4,可以尝试把累加器拆成多个部分并行计算,最后再合到一起。比如一个64位计数器,如果直接写cnt <= cnt + 1'b1,进位链就是64级,路径较长。可以拆成高位和低位两个独立的计数块,配合进位标志传递到高位块,不过这种情况要处理进位跨时钟域或组合路径,需要结合具体场景评估。

4.2 宽MUX和MUXCY的隐藏用法

CARRY4里的MUXCY不仅用于加法进位,还能用来做宽位选择器。比如一个多路数据选择,如果写成很长的if-else或者case,综合器可能会在其中插入MUXCY,用一条进位链实现选择链。

这种实现的好处是延迟相对固定,不好的地方是它同样消耗CARRY4资源。早期FPGA资源紧张时,工程师会故意用MUXCY来做大型选择器,节省LUT。现在综合器策略更成熟,一般会自动判断怎么做更优。

如果你发现自己的设计里CARRY4占用率高得离谱,但加减乘除并不多,那就去查查是不是有大量多路选择逻辑被映射到了MUXCY上。有时候改写分支结构,比如用one-hot编码或者平衡选择树,能让CARRY4占用降下来,布线也更容易收敛。

4.3 TDC直方图:把进位链当成“精密延迟线”

在FPGA实现TDC(时间数字转换器)时,CARRY4是很多设计方案里的主角。TDC要测量两个脉冲信号之间的时间差,需要的分辨率往往远高于系统时钟周期,所以在FPGA里常用延迟线法。信号在进位链上传播,每经过一级CARRY4都会产生几十皮秒量级的延迟,这刚好可以用来量化极小的时间差。

典型的做法是让被测信号进入一条由CARRY4级联构成的延迟线,同时用多个D触发器把链上每个节点的状态存下来。当参考时钟边沿到来时,如果被测信号还没到达某个节点,那这个节点触发的锁存值就是0;已经传播过去的节点值为1。通过统计“1到0”跳变点的位置,就能反推出信号到达的相对时间。

这里有个听起来很妙但实际上需要谨慎的地方:CARRY4的延迟并不是绝对均匀的,不同位置的Slice,甚至同一个Slice内不同bit,延迟都有细微差别。如果直接把二进制的进位链位置当作时间线性刻度,误差会比较大。所以做TDC时,常用“码密度校准”法,统计大量随机时间戳落在每个bin(每个进位节点对应的时间窗口)的次数,再推算出每个bin的实际宽度,生成直方图校准表。

和TDC密切相关的场景是PET、激光测距、雷达等系统,要求在FPGA里同时统计大量时间戳,输出直方图。这时CARRY4的延迟链用法就会大规模出现,资源占用上看,一个TDC通道往往要几十上百个CARRY4,相当可观。

4.4 图像处理、MIPI和ISP流水线里的隐形成本

看热搜词里的“fpga图像处理”“fpga isp去马赛克”,其实这些场景的算术量极大。ISP算法无论是去马赛克插值、色彩校正、白平衡、gamma矫正,还是常见的直方图均衡,都需要大量的加法和乘法。乘法如果幸运可以用DSP48,但紧接着的累加、地址计算、直方图统计,几乎每一步都在用CARRY4。

举个例子,一个1080P60的图像直方图统计模块,需要对每个像素做亮度分量计算,然后在一个256级的直方图桶里做累加。假设每个桶是一个24bit计数器,那就是256个24bit加法进位链在工作。如果设计不小心把所有累加器串联到一个循环里,综合器可能给你拉开一条很长的CARRY4链,时序直接爆掉。正确做法是并行统计,或者分时复用并做好流水线切分。

MIPI接收端虽然更多用ISERDES和位对齐逻辑,但在把lane数据拼成像素并写入DDR时,地址生成、帧同步、统计校验等模块同样需要大量计数器。所以你看整个FPGA图像处理工程,CARRY4利用率往往不低。看资源报告时,除了看LUT和FF,一定要看CARRY4的百分比,否则后面布局布线会发现很多隐性瓶颈。

5. 实际工程中容易被坑的细节与排查经验

5.1 高扇出和布线拥塞:Carry4也会成为“堵点”

很多工程师对高扇出信号很敏感,看到时钟、异步复位扇出高了会赶紧做复制,但很少有人会想到CARRY4的级联也会导致布线拥塞。当一个模块里有很多加法器,而且它们的位宽彼此接近,综合器倾向于把CARRY4放在相邻位置,方便进位链连接。如果设计中多个进位链挤在一个区域,布线资源就会不够用,出现局部拥塞。

我在一个多通道累加项目里就遇到过类似问题。16个ADC通道,每通道一个高精度积分累加器,资源看起来并不算多,但布局之后拥塞严重,时序一直收敛不了。后来我把累加器分散到不同bank区域,甚至插入几级流水切分,整个设计的布线压力明显下降。

处理这类问题的两个方向:一是调整代码结构,让进位链不要过长或过密;二是使用综合选项、布局约束,或者Pblock,把算术模块限定到合适的区域。不要一开始就上Pblock,先把代码里明显的集中式大加法拆散,往往更有效。

5.2 时序违例排查:怎么从关键路径看出进位链太长

假设你看到时序报告里,关键路径是一条LUT到CARRY4到CARRY4再到CARRY4的链路。这时就要数一数它级联了多少个CARRY4。如果达到几十级,说明加法器或计数器的位宽太大,进位传播路径太长。

排查步骤很简单:先看Critical Path里的逻辑单元列表,统计CARRY4数量。如果问题出在一个大位宽累加器,考虑把它变成多级流水,或者用数字信号处理里的“拆分累加”技巧:把N位加法拆成高低位两部分,高位的进位不必等低位全部算完才触发,而是单独做进位预测。FPGA里有DSP48时,也可以把部分累加放进DSP48来缩短进位链。

特别提醒,用for循环写大位宽加法时,综合器未必会按照你期望的方式展开。最好直接写位宽明确的sum <= a + b,或者让综合器自动处理。一些看似聪明的“手动展开”,反而会打乱综合器的优化。

还有一类问题是复位信号和进位链的异步复位同步释放处理不当,导致复位释放瞬间,计数器和加法器里的寄存器出现亚稳态,现象是偶发计数跳变。这种情况不是Carry4本身的问题,而是你把复位加到了所有寄存器上,复位释放瞬间如果靠近时钟沿,进位链上的中间寄存器可能锁到不确定值。

5.3 资源预估误区:别只盯着LUT和FF

新手选型时最喜欢问:“这款FPGA有多少LUT?”但实际做个图像或雷达信号处理,你会发现DSP48、BRAM、CARRY4往往更早成为瓶颈。CARRY4的数量和Slice数量挂钩,Xilinx 7系列的CARRY4资源在很多器件手册里有明确列表,但很多人不看。

我建议做资源评估时,把CARRY4和其他资源一样单独列一列。可以用一条Tcl命令快速看到当前设计里CARRY4的使用情况:

report_utilization -hierarchical -file ./util_hier.rpt

然后grep一下CARRY4。如果占用超过60%,要非常警惕后续迭代会增加逻辑时的布线风险。

如果项目还没定芯片,建议直接在目标型号上用实际代码跑一版综合,不要只看纸上规格。不同工具版本对算术逻辑的映射策略有差异,但CARRY4的资源占用通常不会有数量级变化,是可以提前预估的。

我自己第一次意识到CARRY4的价值是在做多通道TDC的时候。当时为了追求高分辨率,用了一长条进位链当延迟线,每天跟非线性、温度漂移和布局绕动较劲。后来回头去看普通的加法器和计数器,才发现原来在FPGA里,最不起眼的进位链支撑着绝大部分高速算术功能。每次打开Vivado资源报告,看到CARRY4那一栏不为零,我都知道:你的设计里又有一堆看不见的加法在陪着芯片一起跳动。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询