FPGA 专家 29:网络加速与 SmartNIC——在几个时钟周期里决定一个包的命运
本文是
fpga/专家层第 32 篇。以太网基础见 进阶 18,10G/线速卸载见 专题 01,主机驱动/DPDK 见 专家 29。
一、从 NIC 到 SmartNIC/DPU

传统:网线 ─ NIC ─ 中断/协议栈 ─ CPU 跑 vSwitch/安全/存储 ─ 业务核被吃掉
SmartNIC:网线 ─ FPGA 管线(解析/匹配/动作/调度) ─ 直接进 VM/存储
▲ P4Runtime/控制面 agent 下发规则二、核心数据通路(cut-through 流水线)

MAC/PCS → PARSER → MATCH(流表) → ACTION → 流量管理(队列/调度/整形) → MAC
以太网 5元组/隧道 NAT/封装 QoS/多端口
VLAN conntrack 计量/丢弃
IP/L4 BRAM/TCAM 元数据边走边改设计纪律:
- 每级预算固定 1~2 拍,绝不先缓存整包;包体数据与元数据(头字段、命中结果)分两条轨并行;
- 多表并行查找;精确匹配 BRAM,前缀/通配 TCAM;
- 全链 valid/ready 反压,入口缓冲吸收突发;
- 时钟与线速反推:先算"每包拍数预算"再定架构。
三、P4 on FPGA

声明 header / parser / table(match-action) / control,编译器生成流水线,P4Runtime 动态下发表项。适合标准转发逻辑快速迭代;极限时延与自定义动作仍需 RTL,常见 P4 骨架 + RTL 动作模块混合。
四、与主机的协同边界
- 首包/未命中上送 CPU 建流(slow path),命中后硬件转发(fast path),表项老化由控制面管;
- SR-IOV/virtio、OVS offload、RDMA/RoCE、IPsec 卸载各有分工,FPGA 管数据面,主机驱动管异常与管理面。
五、可观测性是线速设计的一部分
逐级计数器 + 丢包原因位 + 队列水位峰值 + 镜像口 + 段延迟直方图;排查沿"谁拉低 ready→谁缓冲满→丢包原因位"逆向追溯。
六、线速流水线的时序预算:每一级只有一个报文头的时间
线速(wire-speed)网络数据通路的设计哲学,是把“处理一个报文”摊到每一个时钟周期里,而不是“收完一个报文再处理”。理解这一点的关键是算一笔严苛的时间账:在一条 100Gbps、时钟 322MHz、数据总线 512 位宽(约 64 字节)的通路上,几乎每个时钟周期都有一个新的数据 beat 到达,一个 64 字节的最小报文只占一个 beat 左右。这意味着报文头解析、表项查找、修改、转发决策这些逻辑,不能先把整个报文缓存下来慢慢算——它们必须在报文头流过的那几个周期内、由不同流水线级并行完成。任何一级处理不过来导致停顿,都意味着在无阻塞线速要求下丢包。
每级的时间预算由此被钉死:一个报文头能在某一级停留的周期数,等于该报文头在输入速率下“占用”的周期数,且流水线不能反压到入端口(在线速 cut-through 设计中,入端口以链路速率来数据,你无法让光慢下来)。因此复杂操作必须切成多级流水:第一级解析以太网/IP/TCP 头并抽取关键字,第二级并行发起多路表查找,第三级收集查找结果做决策,第四级改写报文头、更新校验,第五级送交换/输出仲裁。单级逻辑一旦超过周期预算(比如一个巨大的多级查表组合路径),要么再切级,要么降位宽换更高并行,绝不能靠“这一拍算不完下一拍再算”——下一拍已经是下一个报文的数据。
cut-through 与 store-and-forward 的取舍要在这个预算下理解。store-and-forward 先收齐整个报文再处理,逻辑简单、可在转发前发现错误,但延迟至少是一个完整报文时间、且要缓冲整个报文(在高速下缓冲成本巨大)。cut-through 一收到报文头、查完表就开始转发,延迟只有报文头处理时间、缓冲只需几 KB,是 SmartNIC 数据通路的主流;代价是一旦开始转发就无法反悔(比如输出端口突然拥塞),且对流水线的头处理速度要求极严。很多设计混合使用:正常走 cut-through,异常/需要重组(如 GRO/GSO、IP 重组)的流量旁路到慢速缓冲区路径。
最小报文与报文间间隙(IPG) 给出了最恶劣情况(worst case),时序和缓冲设计必须按它而不是平均包长。小包线速时每秒报文数(PPS)最高,查表、分配、回收、中断的频率都被推到极限——一个能线速处理大包的设计,常死在小包 PPS 上。因此评估表查找速率、描述符/缓冲管理、DMA 描述符产生速率、主机中断频率时,都要用最小包长的 PPS 来压测,而不是用满帧 1500 字节的吞吐数字自欺。线速能力必须按 Mpps(百万包每秒) 和 Gbps 两个指标同时声明,缺一个都会在真实流量(大量小包的握手/查询流量)下露馅。
固定延迟 vs 低延迟 也要分清。流水线天然带来若干级的固定处理延迟,cut-through 下这个延迟与报文长度无关、且抖动极小,这正是确定性网络设备需要的特性;优化目标是把级数压到满足时序和功能的最小值、且不引入与负载相关的可变等待(如在数据通路里等待软件、等待 DRAM 查表)。凡是需要在报文路径上等待慢介质(DDR 查找、主机决策)的功能,要么用缓存预测、要么把结果预取到片上、要么把这类报文重定向到慢路径,确保快路径上的延迟像导线一样确定。这套“每个头只有固定几个周期、按最坏 PPS 预算、延迟固定且与负载无关”的纪律,是线速网络流水线区别于普通流处理的根本。
七、解析器与逆解析器:协议无关转发的工程核心

可编程数据通路(P4、智能网卡流水线)里最能体现“硬件如何理解软件世界的协议”的两个模块,是解析器(parser)和逆解析器(deparser)。它们负责在报文的字节流与流水线内部的结构化“头部字段”表示之间双向转换,是协议无关转发能够成立的关键,也是资源、时序和正确性问题的集中地。
解析器要在报文头流过的固定周期内,把一串字节识别成分层的协议头。以太网头在固定偏移,但之后是 VLAN 还是 IPv4/IPv6?IP 头里 protocol 字段又指向 TCP、UDP 还是隧道?隧道里面还可能再嵌一层以太网/IP(VXLAN/Geneve/GRE)。解析器因此是一个数据相关的有限状态机:解析完当前层,读取“下一头类型”字段,决定下一状态去按哪种格式解析,同时抽取需要的字段送进后续流水线,并记录每层头的偏移和长度供修改时使用。实现上常用一个可配置的解析状态机/解析图(P4 parser state 的综合结果),每个周期或几个周期推进一层;约束是总解析时间不能超过前面算的报文头时间预算,且要支持的最大嵌套层数、头部宽度都受资源限制。这也是为什么可编程交换机/网卡对“解析多深、支持多少层封装”有明确规格——解析状态、偏移计算和多路字段提取都是要吃 LUT/BRAM 的真硬件。
解析器的正确性边界很多:非对齐(协议头不从总线边界开始,需要跨 beat 的旋转对齐网络)、变长头(IP IHL、VLAN 堆叠、TCP 选项)、畸形/截断报文(声称的长度超过实际报文)、未知协议、TTL/校验和联动。健壮的解析器对每一种畸形都要有确定行为:要么按默认路径转发、要么送慢路径、要么丢包并计数,绝不能因为一个畸形头产生非法偏移去读越界地址或让状态机跑飞。线速设备直接面对公网恶意流量,解析器事实上是安全边界,fuzzing(随机畸形报文)测试是必做项,这与软件协议栈的健壮性要求一致,只是错误发生在固定周期的硬件状态机里,代价更高。
逆解析器做反向工作:把流水线可能修改过的结构化头部重新序列化回字节流。转发路径常要改头:改目的 MAC、减 IP TTL(并增量更新 IP 头校验和)、改端口做 NAT(更新 TCP/UDP 校验和的伪头)、压入/弹出 VLAN 或 MPLS 标签、封装/解封装隧道头。逆解析器按解析阶段记录的布局,把修改后的字段写回正确偏移、重算校验和、按新的头部顺序和长度重组字节流。压入/弹出头还会改变后续所有内容的偏移和整体长度,需要和缓冲读出地址、输出调度联动。校验和通常做增量更新(只根据变化字段的差值调整,而不是对整个报文重算),否则每个报文对全载荷求和会吃掉大量周期和带宽——这是把软件里随手调用的“算个 checksum”变成线速硬件时必须做的算法级改造。
协议无关(protocol-independent)的真正含义,是解析/逆解析的“协议长什么样”由程序(P4 的 parse graph、header 定义)描述并编译进硬件结构,而数据通路的其余部分(match-action 流水线)操作的是抽象字段,不关心字节布局。这让同一套硬件能够通过重新加载程序支持新封装(如新增一种隧道),而不必重写 RTL——但代价是硬件必须为“可能的解析图规模、字段宽度、表数量”预留可编程资源,且并非任意协议都能装进固定资源和时序预算。理解 parser/deparser,就理解了可编程数据平面的能力边界:它擅长“已知形态、有限深度、字段可枚举”的协议处理,不擅长任意长度、深度递归或需要跨多包复杂重组的解析(后者仍应甩给慢路径/主机软件)。把这条边界划对,是用 P4/FPGA 做网络卸载时避免“以为什么协议都能线速解析”的关键。
八、表项更新与控制平面一致性:在线换表而不停线速
数据平面按表行动,表由控制平面(软件、SDN 控制器)不断更新:路由收敛、ARP/MAC 学习、ACL 下发、NAT 会话建立与老化、隧道端点变化。如何在数据平面以每秒上亿次查找持续转发的同时,安全地更新表项而不丢包、不误转发、不造成时序毛刺,是网络加速器区别于“跑固定功能的管道”的核心工程,本质上是一个高并发读写、强一致要求的内存系统问题。
查找结构的硬件实现决定了更新能力的下限。精确匹配(如五元组哈希、MAC 表)用哈希表,要处理冲突(开放寻址/多 way 哈希/cuckoo),查找需在固定周期内完成,因此常把哈希表放 BRAM 并多 way 并行;最长前缀匹配(LPM,路由表)传统用 trie/树或多步查找,现代常用 DCFL/方向前缀 trie 或把查找拆成固定级数的流水结构;通配/范围匹配(ACL)用 TCAM 或编译成多阶段哈希/决策森林。每一种结构都在“查找速度、表容量、更新代价、面积功耗”之间权衡:TCAM 更新快、可并行掩码但昂贵功耗高;算法化结构省资源但一次软件表项更新可能要重排多个硬件表项(如 cuckoo 哈希的踢链、trie 的重构),更新本身要被组织成不打断查找的序列。
读多写一/多写场景的一致性是要害。数据平面每拍都在读表,控制平面偶尔写;直接“读一半被写”可能导致一个报文用到半新半旧的字段。常见机制:双缓冲/影子表(在副本上改好再原子切换指针,延迟和容量翻倍)、版本号与两阶段提交、利用流水线特性做单表项原子写(一条表项存在同一拍可读的位置,写操作保证原子)、以及多表/多字段关联更新的事务性(一个逻辑表项可能展开到解析、匹配、action、计数多个物理位置,要保证报文不会看到“匹配已更新但 action 还是旧的”这种撕裂状态)。复杂更新通常打包成一个由硬件状态机执行的更新事务,控制平面下发一批写,硬件在保证不撕裂的提交点生效。
更新速率与收敛时间必须纳入设计指标。大规模网络事件(如链路翻动导致成百万路由更新、突发建立大量会话)会在短时间灌入海量更新;若硬件更新跟不上,表会长期落后于控制平面,造成黑洞或环路。要估算更新路径(软件算好→驱动→DMA→硬件更新引擎→表)的端到端更新 PPS,并为更新风暴设计批处理和背压,同时保证更新不侵占数据通路的时序裕量(更新逻辑不能把关键路径拖长)。会话类表还有**老化/淘汰(aging)**问题:硬件要周期性扫描超时表项或用空闲/idle 位反馈给软件回收,软硬件对“会话是否还活着”的认知要一致,否则表会被死会话占满,新连接无法建立。这又是一条需要软硬件协同的状态管理通路。
控制平面与数据平面的契约应当清晰且可观测:下发是否成功(容量满、硬件不支持的掩码组合)要回执;表项要有硬件命中计数回送给控制平面做决策和清理;软件表(权威副本)与硬件表(工作副本)要有对账机制(定期比对/校验,发现漂移能重建);重启/重配置后要有快速重建表的流程而不是冷等学习。把数据平面当成一个需要事务、回执、对账和容量管理的外部高并发数据库来设计控制接口,而不是一组“随时可写的寄存器”,才能在真实的、表项持续变化的网络里长期稳定运行。这部分做得粗糙的数据通路,实验室打流能线速,一上生产网络就在路由收敛和会话老化时出问题。
九、典型坑与设计评审清单
线速网络加速的坑集中在 PPS、协议健壮性、表更新和系统协同。汇总如下。
按平均包长做预算:用 1500 字节测出线速就宣称线速,实际小包流量 PPS 爆表,查表/描述符/中断跟不上而丢包。正解是按最小包长的最坏 PPS 设计和压测,同时给出 Gbps 和 Mpps。
快路径里等慢介质:在报文处理中读 DDR 表、等主机决策、做不可流水的长运算,导致延迟随负载抖动、线速破功。正解是热表和热状态进片上、慢操作预取或旁路、快路径延迟固定。
解析器只测合法流量:畸形/截断/深度隧道/超长选项报文使解析状态机越界或跑飞。正解是解析器按安全边界设计,所有畸形有确定处理,fuzzing 随机报文长期回归。
校验和与改头错误:改 TTL/端口后全量重算校验(太慢)或忘记更新(报文被对端丢弃);压弹 VLAN/隧道后偏移错乱。正解是增量校验和、解析阶段记录布局、逆解析按新布局重组并做边界测试。
反压与丢包语义不清:入端口不能停却没有足够缓冲,下游拥塞时静默丢包且无计数;或反压环路时序违规。正解是明确 cut-through/缓冲策略、按最坏情况配置片上/外部缓冲、所有丢弃都有原因计数器。
表更新撕裂/跟不上:更新中读到半新半旧表项、关联表不一致、更新风暴时硬件落后软件、死会话占满表。正解是原子/事务化更新、更新 PPS 有指标、idle 老化、软硬件表对账与重建。
可观测性缺失:只看总吞吐,没有逐端口/逐队列计数、没有时延直方图、丢包无原因,线上无法定位。正解是把计数器、镜像、时间戳、in-band network telemetry 作为数据通路的内生功能(呼应正文“可观测性是线速设计的一部分”)。
与主机协同失配:零拷贝/描述符环没喂满、每包中断、内核协议栈重复处理、卸载功能与软件状态不一致。正解是控制面/数据面清晰分工,批量、聚合中断、卸载能力与内核/驱动版本对齐。
设计评审清单:①按最小包长 PPS 给出吞吐与时序预算,每级逻辑在预算内;②快路径无慢介质依赖、延迟固定且与负载无关;③解析器支持的封装深度/宽度/畸形行为明确且经 fuzzing;④逆解析器改头、偏移、增量校验和正确;⑤表查找结构的容量、更新代价、命中时序已量化;⑥表更新原子/事务、更新 PPS、老化、对账齐备;⑦缓冲与反压按最坏情况设计,丢弃全部可计数归因;⑧计数器/镜像/时戳/遥测内建;⑨与主机的零拷贝、批量、中断聚合路径贯通;⑩资源(LUT/BRAM/URAM)、频率、多端口/多队列扩展性在预算内。逐条通过再投实现,能把绝大多数“实验室线速、生产出事”的风险挡在前面。
十、缓冲管理与流量控制:线速设备的内存经济学

线速通路里,缓冲(FIFO 和包缓冲)的组织方式直接决定丢包率、延迟和成本,是数据通路设计中最需要算账的部分。共享内存式交换结构里,所有端口的包进入统一的包缓冲,描述符/指针在队列间流转;其核心难题是缓冲是稀缺资源,分配策略决定拥塞时谁被牺牲。最简单的尾丢弃(队列满即丢新来的包)在多端口拥塞时会产生不公平和全局同步;成熟的做法包括每队列最小保证配额加可借用的共享池、加权随机早期检测(在将满时按概率标记/丢弃以配合端到端拥塞控制)、以及按优先级的丢弃策略(关键控制流绝不与背景流争同一份缓冲)。所有丢弃都必须计数并归因到端口、队列、原因,否则线上“丢包”永远查不清。
背压与流控要区分层级:芯片内流水线用 ready/valid 逐拍反压,代价是停顿会向入口传播;片间/设备间则用链路级流控(暂停帧、PFC 优先级流控、credit)阻止对端继续发送。PFC 这类逐跳无损机制要极其谨慎——它把拥塞从一个端口扩散到上游甚至形成环路拥塞树(head-of-line blocking 蔓延),数据中心因此还需要端到端拥塞通知配合。设计准则是:反压只在明确的信任边界和短距离内使用,广域不可控网络必须允许丢包并由协议恢复;任何流控帧的发送都要有速率限制和超时,防止故障对端把自己永久暂停。
队头阻塞(head-of-line blocking)是缓冲设计里反复出现的结构性敌人:一个队头被慢速/拥塞目的端口堵住的包,会挡住它后面去往畅通端口的包。解法是把队列粒度做细——虚拟输出队列(输入端按输出端口分队列)、按流分队列、以及在 cut-through 结构里尽快把包导向与输出端口绑定的短缓冲。延迟敏感的设计刻意把缓冲做小(浅缓冲),用丢包换确定性延迟;吞吐敏感的设计才用深缓冲吸收突发。这是一个产品定位问题而非纯技术问题:金融场景选择浅缓冲加优先级隔离,存储/通用网络才需要深包缓冲。
还要管理描述符与载荷的分离:数据通路搬运定长的指针/描述符,载荷在大包缓冲里只存一次(零拷贝转发),多播时复制的是指针并配引用计数。引用计数错误(少减导致泄漏、多减导致提前回收)是交换数据通路最隐蔽的 bug 类,必须配合压力测试和“缓冲永不耗尽、计数最终归零”的不变量断言(随机压测方法见 专家 39)。缓冲水位要成为一等遥测指标:水位分布能直接告诉你队列深度是否足够、PFC 是否频繁触发、哪个端口在成为瓶颈。
十一、换协议不换架构:数据通路的复用与参数化
网络加速团队的长期效率,很大程度取决于能否把一个项目的验证成果复用到下一个协议。表面上以太网、定制背板协议、嵌入式总线差别很大,但在数据通路层面它们共享同一组抽象:帧化的字节流、头部与载荷的分界、关键字段的抽取、基于关键字的查表动作、有保证的握手、逐跳或端到端的流控。围绕这些抽象构建参数化的数据通路骨架,协议之间的迁移就从“重写”变成“换解析图、换表格式、换动作集”。
具体做法是分层固化:最底层是协议无关的流处理骨架(定宽数据总线、旁路旁路、反压、定长节拍内的字段切片原语);其上是可配置的解析/逆解析层(用描述表定义字段偏移、长度、下一层选择,对应 P4 parser 的思路);再上是 match-action 流水线(表宽、表项格式、动作数参数化);顶层才是协议特有的控制面和表项格式。这样,从以太网交换到定制遥测协议,骨架和大部分检查器、流控逻辑、缓冲管理器不动,只替换解析描述和动作定义。验证资产(事务监视器、随机流量生成器、scoreboard、乱序/丢包检查器)也因此能复用——这是比 RTL 复用更大的收益,因为验证通常占项目三分之二的工作量。
参数化要克制:真正随产品变化的维度(端口数、数据位宽、表深度、队列数、流ID宽度)做成参数;不怎么变的东西不要过度抽象,否则一个到处是 generate 和泛型握手的“万能通路”会比专用通路更难收敛时序、更难调试。实用判据是:至少有两个具体目标需要不同取值时才参数化,并且每个参数组合都要在 CI 里有构建覆盖(配置矩阵思想见 专家 46),否则未被编译的参数分支等于未验证代码。
协议演进(新封装、新字段、更长的头)要在结构上预留:解析器支持有限状态扩展、关键字选择器留有余量、动作总线允许新增动作类型而不破坏旧表项格式。新增一个协议变体的理想成本是“加描述、加表、加动作模块”,而不是改流水线寄存器布局。把易变的策略面(怎么分类、做什么动作)和稳定的机制面(怎么搬字节、怎么排队、怎么保证不丢)分开,是数据通路可复用设计的总纲。
十二、网络加速的边界:什么不该放进 FPGA 通路
知道什么不做,和知道做什么同样重要。FPGA 通路最适合协议分层明确、每包处理有界、可流水化、状态适度的工作;以下几类工作强行放进来通常以失败告终,应当明确划给主机软件或慢路径。
第一类是复杂、易变、需要完整协议状态的控制逻辑:TCP 栈的完整实现(重传、拥塞控制、乱序重组的全部边界)、TLS 握手与证书验证、路由协议的邻接与策略计算。这些代码量大、频繁随标准变化、分支密集,硬件化的验证成本远高于收益;正确分工是硬件做线速的快操作(卸载校验和、分段/聚合、有序快路径、对称加密/哈希原语),把建连、协商、异常恢复留给软件。经验法则:状态爆炸或标准半年一变的,别烧进流水线。
第二类是需要大表、随机访问和复杂数据结构的工作:百万级带超时和复杂淘汰的连接表、深度包检测的正则集合、大规模 NAT 的软状态。片上放不下就必须走外部存储,而外部随机访问的延迟破坏线速流水线;折中方案是硬件只做缓存热项(如连接跟踪快表),未命中送软件慢路径建立,之后转发到硬件——这又要求连接建立的首包延迟可接受。把表容量需求在架构期用真实流量画像(表项规模、命中率、建立速率)量化,是避免“硬件里塞了个半吊子数据库”的唯一方法。
第三类是批量极小、规则稀疏的功能:为了某客户一个罕见需求在数据通路里加分支,会增加所有人的时序压力和验证面。这类需求放软件或 FPGA 里独立的旁路核(软核做慢路径,见 专家 28)更合适。第四类是没有线速要求的管理面:CLI、SNMP、遥测聚合、日志格式化,全部应在处理器上实现,硬件只提供计数和事件导出(见 专家 45)。
判断一个功能该在哪的统一标准仍然是量化:处理每包的时间预算(是否能在节拍内完成)、状态规模(是否放得下片上存储)、变化频率(会不会要求频繁重新综合)、失效率与异常路径占比(慢路径是否足够稀疏而不影响快路径线速)。FPGA 网络加速器的理想形态是“一条又窄又深、功能克制但每个功能都线速的快路径,加一个什么复杂情况都能兜住的软件慢路径”,试图让快路径什么都懂,往往是什么都做不快、也做不可靠。
总结
SmartNIC 用 FPGA 的 cut-through 解析-匹配-动作-调度流水线,在每包十几个周期内完成原来吃 CPU 的虚拟交换/隧道/安全/存储处理;P4 换开发速度、RTL 换极限效率,定位问题靠埋在每一级的计数器和反压追踪。