交换芯片微架构:Crossbar、VOQ与共享缓存设计

发布时间:2026/9/18 2:45:36
交换芯片微架构:Crossbar、VOQ与共享缓存设计 做网络芯片或者交换系统这行迟早会被一张数据通路框图卡住左边一排输入端口右边一排输出端口中间几个方块分别写着 Crossbar、VOQ、Shared Buffer、Cell Fabric。图看着干净真动手算一遍带宽、推一遍调度时序就会发现每个方块背后都是一堆硬约束下的取舍。交换芯片的微架构说白了就是回答三个问题数据从入口到出口走什么路径、在这条路径上在哪里排队、排队的东西以什么粒度被搬运。这三个问题的答案组合起来基本上就决定了这颗芯片的吞吐、时延、缓存利用率和能不能跑无损网络。这篇内容我打算把数据通路这条线从上到下拆一遍。适合刚入行的数字 IC 设计、交换系统软件、网络性能调优的同学也适合做了几年但一直停留在知道名词、不清楚为什么这么设计阶段的工程师。核心关键词交换芯片、Crossbar、VOQ、Shared Buffer、Cell Fabric这几个概念不是并列关系而是同一条数据通路上环环相扣的四层决策。下面按先算账、再选结构、再抠实现的顺序推进每一层都会给出具体的数字和算式尽量做到你拿着这篇能自己复现一遍设计推导。1. 先算清楚带宽账数据通路的整体设计思路数据通路设计的第一步不是选结构是把账算清楚。很多人一上来就讨论 Crossbar 多少路、缓存多少 MB结果发现内部带宽凑不出来回头返工。正确的顺序应该是先确定外部端口规格和交换容量再倒推内部数据通路的位宽和频率最后才轮到讨论用什么交换结构。1.1 从端口速率倒推内部带宽需求假设一颗芯片有 32 个 400 Gbps 端口那么标称交换容量是 32 × 400 Gbps 12.8 Tbps。但这只是外部线速容量内部真正要搬运的数据量比这个大得多原因有三个内部信元头开销、Crossbar 的加速比、以及存储器的读写各算一次。先说信元头。包进入芯片后通常会被切成长度固定的信元每个信元携带一段内部头里面至少要有目的端口号、优先级、源端口号、序列号或信元结束标志。这部分开销在 16 字节量级看起来不大但在小包场景下会被严重放大。再说 Crossbar 的加速比。为了避免输入侧排队导致的吞吐损失内部交换结构的带宽一般要设置为外部线速的 1.5 到 2 倍这个倍数就是加速比speedup。为什么需要它第 2 节会详细推。最后是存储器。只要数据在进入交换结构前落一次缓存、出去时再读一次那么同一份数据在 SRAM 侧就是一次写加一次读等效带宽翻倍。共享缓存架构尤其明显中央 SRAM 的总访问带宽约等于 2 倍的交换容量。把这三项叠起来看12.8 Tbps 的外部容量配 1.6 倍加速比再加上双向访问的系数内部数据通路的实际峰值吞吐很容易冲到 40 Tbps 以上。这个数字决定了后面所有物理实现的边界——总线位宽能做到多少、时钟能跑多快、SRAM 能不能分够 bank。提示算内部带宽时一定要把读写各一次和加速比分开算不要笼统地乘一个内部开销系数。这两个系数来源完全不同一个来自存储访问模型一个来自排队论混在一起算会导致后面对不上账。1.2 三种交换结构的取舍共享总线、共享内存、Crossbar交换结构在教科书上有三大类实际工程里都能见到影子。第一类是共享总线。所有端口挂到一条公共总线上某一时刻只有一个端口能发送其他端口等待。结构简单、面积小但总线频率和位宽受限容量很难超过几百 Gbps。它适合的场景是交换容量小、对成本极度敏感的场合比如一些接入侧的小容量交换芯片或者工业级交换模块。第二类是共享内存。所有端口把包写进同一块中央缓存输出端口从缓存里读走。它的最大优点是缓存利用率高——任何一个端口都能用到全池的缓存突发吸收能力极强。缺点是存储器带宽要跟着交换容量线性增长。我们要算一下3.2 Tbps 交换容量SRAM 需要大约 6.4 Tbps 的访问带宽如果 SRAM 工作频率 1 GHz需要 6400 bit ≈ 800 字节的单周期位宽靠多 bank 交错勉强能凑出来。但如果容量做到 12.8 Tbps 甚至 25.6 Tbps这个位宽会膨胀到 3200 甚至 6400 字节每周期物理上基本不可能。所以中央共享内存架构在容量上有一个天花板通常在几 Tbps 量级。第三类是 Crossbar。N 个输入和 N 个输出之间有 N² 个交叉点每个时隙通过调度器把输入输出两两配对配对上的路径可以同时传输。它的带宽随端口数线性增长不依赖单点的高频存储器因此成为大容量交换芯片的主流选择。代价是调度复杂度上升而且缓存必须分散到 Crossbar 两侧缓存利用率不如中央共享内存。1.3 为什么最终收敛到 Crossbar 加分布式缓存的组合把上面两条结论合起来大容量交换芯片的架构基本就唯一了Crossbar 负责高带宽搬运缓存分散放在输入侧和输出侧中央不再有一块巨型的共享 SRAM。但这里有个细节值得说清楚——很多芯片仍然会声称自己有Shared Buffer这个 Shared Buffer 往往指的是同一个端口组内、或者同一个 die 内的共享缓存池而不是全局所有端口共享一块。这是一个规模问题不是概念问题。再往下推一层缓存放在输入侧还是输出侧又会引出排队模型的选择。输出排队OQ理论上吞吐最优但对写入带宽的要求是端口速率的 N 倍不可实现。输入排队IQ写入是线速的但有队头阻塞问题。折中方案是虚拟输出队列VOQ配合加速比也就是组合输入输出排队CIOQ这是目前最普遍的形态。这几个模型的关系和切换逻辑就是接下来两节要展开的内容。2. Crossbar 交叉开关从物理结构到调度约束很多资料讲 Crossbar 只讲一张交叉点矩阵图然后说无阻塞。但实际设计里真正的难点不在交叉点本身而在于每个时隙怎么把输入输出配对、配对的速度能不能跟上数据速率。这一节把这两件事讲透。2.1 Crossbar 的物理结构和无阻塞条件最朴素的 Crossbar 是 N 条输入水平线、N 条输出垂直线交叉点放一个开关。任意时刻每个输入最多连一个输出每个输出最多被一个输入连。如果交叉点数量足够、每条路径的带宽等于端口线速那么这个结构叫严格无阻塞strictly non-blocking条件是交叉点满足一定的数量关系。但严格无阻塞有一个隐含前提同一条输入线上的数据必须以足够快的速度送到目标输出。如果内部链路速率和外部端口速率完全一样那么一旦某个输入同时要往多个输出发数据就只能分时复用带宽立刻不够。这就是为什么实际的 Crossbar 几乎都带加速比——内部链路速率高于外部端口速率允许同一输入在一个时隙内服务多于一个输出或者允许缓存吸收瞬时冲突。交叉点的实现也有讲究。早期用模拟开关或者多路选择器树现代高速设计基本用标准单元搭的多级 MUX 树配合流水线寄存器切分长路径。64×64 的 Crossbar如果每个交叉点都是独立的 MUX 结构面积和布线会非常可观所以实际设计会用分组grouping的方式比如按 8 端口一组组内先用小型交叉开关组间再用一级汇聚牺牲一点灵活性换取面积和时序的可控性。2.2 加速比怎么定从排队论到工程余量加速比不是一个拍脑袋的数字。理论上对于 VOQ 加迭代匹配调度器的 CIOQ 结构加速比为 2 时就能在任意流量模式下模拟输出排队的性能。这是文献里比较经典的结论。实际工程里加速比通常取 1.3 到 2 之间具体取决于几个因素。第一是流量模式。均匀随机流量下加速比 1 配合好的调度器就能接近 100% 吞吐。但在实际数据中心流量里总有几个热点输出端口被反复命中这时候输入侧队列会堆积需要额外的内部带宽来消化冲突。第二是调度器的收敛速度。单次迭代的匹配算法在均匀流量下表现不错在突发流量下匹配质量下降吞吐会掉。加速比给了一个缓冲空间让调度器可以有第二个时隙去补上没匹配上的信元。第三是信元大小和调度周期的比值。如果每个时隙能传一个信元的时间是 4 ns而调度器完成一次迭代需要 2 ns那你有余量做两次迭代如果只留了 1 ns就只能做单次迭代加速比就得往上调来补偿匹配质量。我个人的经验是加速比这个参数在架构评估阶段就要和调度器一起定不能先定加速比再选调度器。它们俩是耦合的分开调往往得到次优解。2.3 调度周期的时序约束反过来决定信元大小这是数据通路设计里最容易被忽略的一个反馈关系调度器的时序收敛能力反过来决定了信元能做多小。算一下。假设内部数据通路位宽 512 bit64 字节内部时钟 1.2 GHz。如果信元是 64 字节那么一个信元占一个时钟周期约 0.83 ns。调度器必须在 0.83 ns 内完成一轮请求—授予—接受的匹配还要留出发送授权和更新指针的时间。对现代工艺来说这个组合逻辑深度是做不到的。如果信元放大到 256 字节一个信元占 4 个时钟周期约 3.33 ns。调度器就有了 3.33 ns 的预算可以跑两级流水或者完成一次完整的迭代匹配。如果信元是 512 字节周期变成 6.67 ns调度器可以做三到四次迭代匹配质量显著提升代价是排队时延变大小包场景下的填充浪费也更严重。所以信元大小不是单纯从包长分布推出来的而是三个约束的折中调度器时序、时延要求、填充开销。这解释了为什么真实芯片的信元大小集中在 128 到 512 字节这个区间而不是有人想象的那种越小越灵活。信元大小单信元传输周期512bit1.2GHz调度预算适合场景64 字节1 周期约 0.83 ns极紧仅能流水小容量、低时延专用128 字节2 周期约 1.67 ns紧单次迭代中等容量256 字节4 周期约 3.33 ns宽松可两级迭代大容量主流512 字节8 周期约 6.67 ns充裕可三次迭代超大容量、时延不敏感提示做架构预研时先用调度时序倒推信元大小的可行区间再用包长分布去选具体值。反过来做很容易出现选了一个时延很漂亮的小信元结果调度器时序收不掉的情况。3. VOQ 虚拟输出队列把队头阻塞按下去理解了 Crossbar 的调度约束就能理解为什么缓存必须放在输入侧以及为什么输入侧不能是一个简单的 FIFO。这一节从队头阻塞的损失开始讲到 VOQ 的存储组织和调度实现。3.1 队头阻塞到底损失多少吞吐输入排队的经典结论是在均匀随机流量下当端口数趋于无穷时吞吐上限约为 58.6%。这个数字来自队头阻塞HOL blocking——输入端口只有一个 FIFO队头那个包的目的输出被占用整个队列就都堵住哪怕队里后面有很多包想去空闲的输出端口。具体的收敛过程是这样的2 端口时吞吐上限约 75%4 端口约 65.5%8 端口约 61.5%16 端口约 59.6%继续增大就无限接近 58.6%。换句话说端口越多单个 FIFO 的输入排队结构损失越大最终只能发挥一半多的交换容量。这个损失是结构性的靠增加缓存深度解决不了。堆再多缓存也只是让排队更长吞吐上限纹丝不动。必须把队列拆开让不同目的输出的包互不干扰这就是 VOQ 的出发点。3.2 VOQ 的队列组织和存储开销估算VOQ 的做法是每个输入端口为每一个可能的目的输出端口维护一个独立的队列。N 个输入、N 个输出就产生 N² 个队列。如果每端口还要区分 8 个优先级或者流量类那就是 N² × 8 个队列。算一下 64 端口、8 优先级的规模64 × 64 × 8 32768 个队列。每个队列需要维护队头指针、队尾指针、当前深度、配置门限等状态按 32 字节的队列描述符估算总共约 1 MB。这个量级放在片上 SRAM 里是可以接受的但有个关键约束——队列描述符的访问必须是随机的因为调度器每一轮要对所有非空队列发请求访问模式非常散。数据本身则不需要分这么多份。通常做法是把数据信元放在一个共享的信元缓存池里VOQ 只保存链表节点指针每个链表节点 8 字节左右指向下一个信元在缓存池里的位置。8 MB 缓存、256 字节信元能装 32768 个信元链表节点总共 256 KB。这样数据存储是共享的只有索引结构是按 VOQ 分开的空间效率高很多。项目规模估算说明VOQ 数量64 × 64 × 8 32768端口数平方乘优先级数队列描述符存储约 1 MB每队列 32 字节信元缓存池8 MB每信元 256 字节约 32768 个信元链表节点约 256 KB每信元 8 字节指针随机访问要求每信元周期内完成必须多 bank 交错有意思的是为了实现更精细的排队反而要额外花掉一部分本来可以做缓存的 SRAM。这是 VOQ 的隐性成本架构评估时一定要算进去不少方案在纸面上缓存很充裕实现时被队列描述符吃掉一大块。3.3 iSLIP 一类迭代匹配调度的实现要点有了 VOQ还需要一个调度器决定每个时隙哪些输入连哪些输出。最基础的做法是最大匹配但那类算法复杂度高硬件实现不现实。工程上主流的是迭代式轮询匹配iSLIP 是其中最有代表性的一类。它的单次迭代分三步请求阶段每个非空 VOQ 向它对应的目的输出发一个请求信号。授予阶段每个输出端口在自己的请求列表里用轮询指针选一个输入发出授予。接受阶段每个输入端口在收到的授予列表里用自己的轮询指针选一个输出确认接受。关键细节在指针更新规则输出的授予指针只有在它发出的授予被接受之后才移动到下一个位置。这个只在成功时滑动的规则是 iSLIP 和早期 PIM 算法的核心区别也是它在单次迭代下就能在均匀流量下达接近 100% 吞吐的原因。如果指针每次都无条件移动高负载下的匹配质量会明显下降。# 单次迭代的匹配流程伪码表示 for each input i: for each non-empty VOQ(i, j): assert request[i][j] 1 for each output j: candidates inputs with request[i][j] 1 granted[j] round_robin_pick(candidates, grant_ptr[j]) for each input i: candidates outputs j where granted[j] i accepted[i] round_robin_pick(candidates, accept_ptr[i]) # 指针更新关键 if granted[j] accepted[i]: grant_ptr[j] next_after(i) accept_ptr[i] next_after(j)硬件实现时这几步要在一个信元周期内跑完通常做成流水线第 k 个时隙的请求阶段和第 k-1 个时隙的授予阶段并行。迭代次数一般取 log2(N) 给我个经验参考64 端口就是 6 次迭代但受时序限制常常只能做 2 到 3 次剩下的靠加速比兜底。3.4 VOQ 常见的三个坑第一个坑是队列膨胀。N² × 优先级 的队列数在 64 端口以上会迅速膨胀128 端口就是 16384 个队列不含优先级。如果每个队列都要能独立做反压和门限管理控制平面的配置量会非常庞大写驱动的同学会很痛苦。实践中往往会对低优先级的 VOQ 做合并只保留关键优先级的独立队列。第二个坑是非公平性。轮询指针天然是公平的但一旦引入优先级低优先级队列可能被长期饿死。需要在调度器里加老化aging机制比如记录队头信元的等待时间超过阈值就临时提升优先级。这个逻辑看着简单实际调参很难阈值太小会影响高优先级业务太大低优先级又会超时。第三个坑是乱序。同一目的输出的不同 VOQ 之间没有顺序保证同一个流如果被散列到不同的输入端口到达出口时可能乱序。对 TCP 来说这是灾难。解决办法通常是限制同一目的输出的活跃输入端口数或者依赖更上层的重排序机制。我在实际项目里见过因为这个问题导致吞吐只有理论值一半的案例排查花了两周最后发现是同一条流被散到了两个输入端口。4. Shared Buffer 共享缓存内存管理和门限算法缓存是交换芯片里最贵的资源也是调优空间最大的部分。这一节讲清楚共享缓存的容量账、门限算法和硬件实现。4.1 共享缓存和独占缓存的容量账假设总缓存 8 MB64 个端口。如果按端口独占分配每个端口只有 128 KB。128 KB 在 400 Gbps 的端口上意味着什么400 Gbps 等于 50 GB/s128 KB 只够缓冲 2.56 微秒的线速数据。一个跨机架的往返时延就可能有 5 微秒独占缓存在突发场景下基本不够用。共享缓存的思路是把 8 MB 作为一个池子谁需要谁拿走。均匀流量下每个端口平均用到 128 KB但突发流量下某个端口可以独占几个 MB吸收掉长时间的拥塞。这就是共享缓存的统计复用收益——池子越大突发吸收能力越强。但共享缓存有个必须解决的问题不能让某个端口的突发把整个池子吃干净导致其他端口完全没缓存可用。所以必须有门限算法来限制单端口、单队列能占用的最大份额。4.2 从静态门限到动态门限最简单的方案是静态门限每个端口最多占用总缓存的 1/N再留一点共享余量。比如总缓存 8 MB64 端口每端口硬限 96 KB剩下的 1.8 MB 作为共享突发池。实现简单但效率低——流量均匀时大家用不满流量不均时又各自卡在硬限上。更常用的是动态门限也就是常说的 Alpha 算法思路。核心公式是threshold_i alpha × (free_buffer / active_ports)其中 free_buffer 是当前池子里剩余的空闲缓存量active_ports 是当前有数据排队的端口数alpha 是一个大于 1 的系数常见取值在 2 左右含义是允许单个端口占用平均份额的 alpha 倍。这个公式的行为很符合直觉当池子很空时空闲量大每个端口的门限自动抬高允许单端口吃下大突发当池子快满时空闲量变小门限自动收紧逼着各端口公平竞争。alpha 越大突发吸收能力越强但公平性越差alpha 越小公平性好但突发场景下容易触发丢弃。实际调参时的经验值是 alpha 取 2 到 4具体要看业务流量的突发特征。我有一个实际案例某批业务在 alpha 取 2 时偶发丢包把 alpha 提到 3 之后丢包消失但另一批时延敏感业务的 P99 时延上升了 40%。最后是按流量类分开配置关键业务用小的 alpha普通业务用大的 alpha。4.3 缓存管理的硬件实现Free List 与链表共享缓存池在硬件上就是一排固定大小的槽位每个槽位放一个信元。管理槽位的方式通常是两条链表一条是空闲链表free list把所有尚未使用的槽位串起来申请槽位就是从链表头摘一个释放槽位就是挂回链表头。另一条是每个 VOQ 的占用链表把头指针和尾指针存在队列描述符里。这里有个硬件上的细节free list 的操作必须是流水化的因为每个信元周期都要做一次申请和一次释放频率和交换速率一样高。实现时通常用一个大位宽的 SRAM 存链表指针配合多个 bank 交错访问避免读写冲突。链表节点的位宽取决于槽位总数——32768 个槽位需要 15 位地址加上一些标志位凑齐 16 位或者 32 位对齐更省事。还有一个容易被忽略的问题链表操作会产生依赖。同一时刻多个端口申请槽位都从链表头摘就会冲突。解决办法是把 free list 分成多个子链表每个输入端口对应一个子链表各自独立操作只有在某个子链表为空时才去全局池申请。提示做缓存管理的仿真时务必统计链表操作的最坏情况延迟。平均情况很快不代表最坏情况也快。我见过因为 free list 在某次多端口同时释放时出现 8 个周期的排队导致整条流水线停顿的案例。4.4 反压门限和流控余量的真实计算共享缓存的门限管理还有一层什么时候向对端发反压信号。在无损网络里这通常通过优先级流控PFC实现原理是当某个队列的占用超过 XOFF 门限时向对端发送暂停信号降到 XON 门限以下时再发恢复信号。这里最关键的是 XOFF 和 XON 之间的差值也就是余量headroom。余量的作用是在对端收到暂停信号之前它还会继续发送一段时间的数据这些数据必须被本地缓存吸收否则就丢包了。余量的计算公式是headroom 链路速率 × 往返响应时间算一下 400 Gbps 的场景。假设从本地发出暂停信号到对端停止发送整个过程包括信号传输、对端处理、以及物理层和 MAC 层的流水延迟保守估计 5 微秒。那么headroom 400 Gbps × 5 μs / 8 250 KB这是单个队列需要的余量。如果有 8 个无损优先级、64 个端口理论上需要的总余量是 250 KB × 8 × 64 128 MB。而片上缓存可能只有 8 MB 到 64 MB 这一量级。这个差距是真实存在的也是无损网络部署中必须面对的问题。工程上的应对手段主要有几种一是减少无损优先级的数量比如从 8 个减到 2 个二是缩短往返时间比如限制无损域只在同一个机架内三是让余量在端口间共享而不是每个端口独立预留代价是一致性风险四是使用更精细的流控机制比如基于队列对的粒度而不是端口粒度。链路速率往返响应时间单队列余量需求8 优先级 64 端口总需求100 Gbps4 μs50 KB25.6 MB200 Gbps4 μs100 KB51.2 MB400 Gbps5 μs250 KB128 MB800 Gbps5 μs500 KB256 MB这张表我觉得比任何文字说明都有说服力——为什么无损网络在高速端口下这么难做数字摆在这里。5. Cell Fabric 信元交换定长切片的收益和代价前面反复提到信元这一节专门讲为什么要切、怎么切、切了之后有什么代价。5.1 为什么要把变长包切定长信元交换内部处理定长信元有几个明确好处。第一是调度简化。Crossbar 的调度是按时隙进行的如果数据长度不一致每个时隙的长度就没法统一匹配算法要处理剩余长度逻辑复杂度会上去。定长信元让每个时隙的时长完全固定调度器只需要处理哪个输入连哪个输出不用管传多少。第二是缓存效率。变长包存储会产生外部碎片很难高效利用固定大小的槽位。定长信元正好一个信元占一个槽位零碎片free list 管理简单槽位利用率接近 100%。第三是时延确定性。定长信元的传输时间固定可以精确计算最坏时延。对时延敏感业务来说可预测性比低平均时延更重要。第四是内部流量整形方便。信元是天然的分片单位做背压、做信用量管理都更简单。代价也很直接变长包必须被切成信元切开就要有额外的头和尾还会出现填充浪费。切分和重组是两条流水线是芯片面积和功耗的大头。5.2 SAR 引擎和数据重组切分Segmentation和重组Reassembly通常合称 SAR。入口处的切分引擎负责把变长包按信元大小切片加上内部头标上序列号或者首末标志再送往交换结构。出口处的重组引擎按序列把信元拼回原始包去掉内部头还原成标准格式发出。重组引擎的关键难点是乱序处理。同一目的端口可能有多个输入端口在给它发信元它们的到达顺序不保证。重组引擎必须按序列号排好缺号时等待重传或者丢弃整个包。这里需要一块重组缓存大小取决于允许的最大乱序窗口。窗口开得大容忍度高缓存占用大窗口开得小缓存省但乱序容忍度差。实现上常见做法是每目的端口一组重组上下文每个上下文记录期望的下一个序列号收到信元时判断是当前期望的、还是超前的、还是重复的。这个逻辑听着简单但状态机分支不少容易出错。我在调试时遇到过序列号回绕处理不当导致整个包被丢的问题排查了很久才发现是 12 位序列号在 4096 个信元后回绕时比较逻辑用了有符号比较。5.3 信元开销的定量分析信元头开销和填充浪费是两项主要成本。假设信元总长 256 字节内部头 16 字节可用载荷 240 字节。按常见的混合流量分布来算12 个包7 个 64 字节、4 个 594 字节、1 个 1518 字节合计 4342 字节有效载荷64 字节包每个占 1 个信元共 7 个信元594 字节包每个需要 3 个信元240×2 114共 12 个信元1518 字节包每个需要 7 个信元240×6 78共 7 个信元总信元数 26 个占用 26 × 256 6656 字节的内部带宽而有效载荷只有 4342 字节效率约 65.2%。换句话说不做优化的话信元交换结构要跑 1.53 倍于外部流量的内部带宽。这个数字对小包特别不友好。单个 64 字节包占用一个 256 字节信元利用率只有 25%。解决办法是信元打包packing同一个 VOQ 里的多个小包可以拼进同一个信元只要它们的目的输出相同。打包后小包的利用率能拉回 90% 以上代价是切分引擎要维护一个组包缓冲和超时机制——如果一个包等太久还没凑够一个信元就得提前发出去否则时延不可控。包长信元数不打包有效载荷内部占用效率64 字节16425625.0%240 字节124025693.8%594 字节359476877.3%1518 字节71518179284.7%提示做信元大小的收益评估时一定要按实际流量分布加权算不能只看大包。数据中心的实际包长分布里小包占比往往在 50% 以上加权后的效率损失比直觉上大得多。5.4 信元模式和包模式的对比也不是所有芯片都用信元。有些中等容量的芯片直接用包模式packet mode不切分整个包作为一个调度单位在 Crossbar 上传输。包模式的好处是没有切分和重组的开销实现简单时延路径短。缺点是调度粒度粗——一个 1518 字节的包占用交叉开关的时间是小包的 20 多倍长包会阻塞后面的短包时延抖动大。而且变长包在缓存管理上会产生碎片。选择哪种主要看容量和对时延抖动的要求。大容量、多端口、要跑无损网络基本都选信元模式。中小容量、对时延抖动不敏感、成本敏感包模式也够用。维度信元模式包模式调度粒度固定时延可预测可变长包阻塞明显缓存管理零碎片链表简单有碎片管理复杂时延抖动小大切分重组开销有面积和功耗较大无适用容量大容量、多端口中小容量适用场景无损网络、时延敏感普通转发、成本优先6. 建模、验证与常见问题排查架构定下来不代表就完了数据通路这种东西必须经过建模验证否则上线后的问题会非常难查。这一节讲建模方法和实际踩过的坑。6.1 用流量模型验证数据通路验证一个数据通路设计最少要跑三类流量模型。第一类是均匀随机流量。每个输入以相同概率打向每个输出用来验证基础吞吐和公平性。这个模型下好的调度器应该能跑到接近 100% 的吞吐。如果跑不到先检查调度器的迭代次数和指针更新逻辑。第二类是热点流量。所有输入都指向同一个输出用来验证反压路径和缓存门限。这个模型下必然出现拥塞重点看的是缓存水位是否稳定、有没有出现饿死、反压信号有没有正确传递。第三类是真实流量回放。用抓包数据或者合成流量回放包长分布符合实际业务用来验证小包场景下的效率和信元打包逻辑。这一类最容易暴露问题因为小包的开销和打包超时机制只有在真实分布下才体现出来。建模工具上功能验证一般用事务级模型TLM跑在 SystemC 或者自研的 C 仿真器里速度快能跑上亿个包。性能模型则要算到周期级尤其是调度器和缓存管理这两块必须精确到每个周期。我个人的做法是先用 TLM 跑通功能和算法再用 RTL 或者周期级模型验证时序关键路径两者交叉比对。数据通路的仿真有一个必须监控的指标集每个 VOQ 的深度分布和最大深度共享缓存的水位曲线和峰值调度器的匹配率每个时隙成功匹配的输入输出对数反压信号的触发频率和持续时间端到端时延的分布尤其是 P99 和 P999各优先级的吞吐和丢包率少一个指标排查问题时就得多跑一轮仿真。6.2 常见问题速查表现象可能原因排查方向均匀流量下吞吐只有 60% 左右存在队头阻塞VOQ 没生效检查队列是否按目的输出拆分高负载下吞吐突然掉到 80%调度器指针更新规则错误确认授予被接受后才移动指针某个端口长期无输出优先级饿死缺少老化机制增加等待时间老化逻辑缓存水位持续在满位附近alpha 太大或反压门限配置不当调小 alpha检查 XOFF/XON 差值小包吞吐远低于大包信元填充浪费未做打包启用信元打包并检查超时配置出口出现乱序包同一流的信元走了不同输入端口检查散列策略和重排序逻辑反压触发后仍有丢包余量不足对端在途数据超预期重新计算 headroom加大 XON/XOFF 差值时延抖动大长包阻塞短包包模式评估是否切换到信元模式调度器时序不收敛信元太小调度周期过短增大信元或增加调度流水级数链表操作冲突频繁free list 未分 bank 或未分子链表检查存储分区策略这张表里的每一条我都至少在项目里遇到过一次其中调度器指针更新规则错误和余量不足导致丢包这两条最难查现象和原因离得很远需要从指标曲线上反推。6.3 踩坑记录几个反直觉的实测结果最后分享几个我在实际项目里踩过、并且和直觉相反的坑。第一个坑缓存加多了反而吞吐下降。某次调优时把共享缓存从 4 MB 加到 8 MB期望改善突发吸收结果端到端时延上去了某些流的吞吐反而掉了。原因是缓存变大之后排队深度增加反压信号触发得更晚对端的拥塞窗口一直被撑大整个网络的排队变得更长。这就是著名的缓冲区膨胀问题在交换芯片上的体现。缓存不是越大越好要和流控机制、上层协议一起调。第二个坑加速比调高之后匹配率反而下降。听起来反直觉——内部带宽多了匹配应该更容易。实际原因是加速比提高后一个时隙能传多个信元调度器的请求列表变长单次迭代的选择空间变大指针的轮询状态更容易被打乱匹配质量下降。解决办法是把迭代次数也相应提高或者改用更保守的调度策略。第三个坑信元打包的超时阈值设得太保守。为了追求打包效率把超时阈值设得比较大结果小包时延明显上升某些时延敏感业务直接超时重传。后来改成按优先级区分阈值关键业务不打包直接发普通业务才等打包。这一个改动让 P99 时延降了一半多。第四个坑VOQ 数量在仿真里没算进 SRAM 面积。功能仿真跑得很好综合之后发现面积超标原因是 32768 个队列描述符的 SRAM 实例化之后占用远超预期而且为了保证随机访问性能做了过度分 bank面积又翻了一截。后面把低优先级的 VOQ 合并才把面积压回来。这个教训是架构评估阶段一定要把 SRAM 实例的物理开销算进去不能只算容量。第五个坑乱序问题在低负载下不出现高负载下才暴露。低负载时同一流的包基本走同一个输入端口顺序正常高负载下散列分布被打破同一条流被分到了两个输入端口出口开始乱序TCP 疯狂重传吞吐直接腰斩。这个问题在实验室低负载测试里完全看不到上线后才爆。后来在散列逻辑里加了流的亲和性保证——同一个五元组必须映射到同一个输入端口问题才解决。我个人在实际操作中的体会是数据通路的设计问题里最难的不是把某个模块做到极致而是理解模块之间的耦合。加速比和调度器耦合缓存大小和流控耦合信元大小和调度时序耦合打包超时和时延耦合。每动一个参数都要把周边三四个参数重新看一遍。那些看起来多加一点总是好的的参数——缓存、加速比、打包窗口——恰恰是最容易出问题的地方。做架构评估时我现在的习惯是先画一张参数耦合关系图把每个参数的上下游影响都标出来再动手调这比逐个试参数效率高得多。