交换芯片数据通路设计:Crossbar、VOQ、Shared Buffer与iSLIP仲裁
交换芯片这个领域很多人第一次接触时会被一堆术语砸晕Crossbar、VOQ、Shared Buffer、Cell Fabric、iSLIP每个词拆开都认识合在一起就不知道它们在芯片里到底怎么协作。我当年从软件转发转到芯片微架构最大的感受是——数据通路的设计本质上是在回答一个问题当多个入口同时要把数据送到同一个出口时你打算让谁等、在哪里等、等多久。这个问题的答案决定了这颗芯片是能跑满线速还是会在突发流量下丢包丢到怀疑人生。这篇内容适合两类人看一类是做网络软件、驱动或SDK想搞清楚底层硬件到底怎么搬数据的工程师另一类是刚入行做交换芯片设计或验证需要把教科书概念和真实微架构对应起来的人。我会围绕数据通路这条主线把Crossbar的仲裁逻辑、VOQ为什么必须存在、Shared Buffer的共享边界、Cell Fabric的切分方式以及iSLIP这类调度算法串起来讲尽量把每个设计选择背后的“不得已”说清楚。1. 从“谁先走”这个问题理解交换芯片数据通路1.1 一个最朴素的交换场景暴露出的核心矛盾假设你有一颗4端口的交换芯片每个端口速率一样。某一时刻端口1要把数据发给端口3端口2也要把数据发给端口3端口4要发给端口1。这时候问题立刻出现了端口3同一时刻只能接收一份数据端口1和端口2必须有一个先等。如果端口1和端口2的数据都先被收进芯片内部那它们存在哪里存多少等多久这就是数据通路要解决的全部矛盾。最原始的做法是共享一条总线所有端口分时复用。但总线方案在端口数一多、速率一高就崩了因为总带宽是固定的N个端口抢一条总线平均每个端口只能拿到总带宽的1/N。交换芯片要的是“任意端口到任意端口都能同时通信”这就要求内部互联结构必须支持多对端口并行传输Crossbar就是干这个的。1.2 Crossbar不是一根线而是一个可编程的交叉点阵列很多人把Crossbar想象成一根很宽的线其实它是一组交叉点开关。N个输入端口和N个输出端口之间有N×N个交叉点。每个交叉点可以独立打开或关闭打开就表示这个输入连到了这个输出。理想情况下N个输入可以同时连到N个不同的输出实现N倍加速。但这里有个关键限制每个输出端口同一时刻只能被一个输入端口占用。所以Crossbar的调度问题就变成了——在每个时隙开始前决定哪些交叉点闭合。这个决定就是仲裁仲裁做得好不好直接决定了Crossbar的吞吐率能不能逼近100%。我见过不少刚接触的人以为Crossbar是“硬件自动搞定”的实际上仲裁逻辑才是设计难点。一个N×N的Crossbar如果每个输入都独立随机选择输出冲突概率会随着N增大而急剧上升。有研究数据表明纯随机选择下N16时吞吐率就掉到60%左右了。所以必须引入集中式的调度算法iSLIP就是在这个背景下被提出来的。1.3 数据通路的四个层次入队、排队、调度、出队把数据通路拆开看其实就四个动作。第一数据从入口进来先决定它该去哪个出口这叫路由查找。第二根据出口把数据放进对应的队列这叫入队。第三调度器决定当前时隙哪个队列的数据可以通过Crossbar这叫仲裁。第四数据穿过Crossbar到达出口这叫出队。这四个动作里入队和排队的位置选择直接引出了VOQ和Shared Buffer的分野。调度算法的选择引出了iSLIP和它的各种变种。而数据在内部传输的格式引出了Cell Fabric的概念。下面几节我会逐个拆开讲。2. Crossbar仲裁iSLIP到底在解决什么实际问题2.1 朴素轮询为什么不够用最简单的仲裁是轮询每个输入端口轮流获得发送权。但轮询有个致命问题——它不考虑输出端口的冲突。假设输入1和输入2都想发给输出3轮询让输入1先发输入2等下一轮。下一轮输入2想发给输出3但输入3也想发给输出3又冲突。轮询只保证了输入之间的公平没有解决输出冲突。更糟的是轮询在流量不均匀时效率极低。如果输入1的数据全部要去输出1输入2的数据全部要去输出2本来可以并行传输但轮询可能让输入1先发、输入2等待白白浪费了并行能力。2.2 iSLIP的三步迭代请求、授权、接受iSLIP的核心思想是让输入和输出之间进行多轮协商。每个时隙开始时所有有数据要发的输入端口向它们的目标输出端口发出请求。每个输出端口收到多个请求后用自己的轮询指针选一个输入发出授权。输入端口收到多个授权后用自己的轮询指针选一个输出发出接受。这一轮结束后没被接受的请求进入下一轮迭代通常迭代log2(N)次就能收敛。这个过程中有两个指针输入端的授权指针和输出端的接受指针。指针只在成功匹配后更新这样保证了长期公平性。我实测过iSLIP在均匀流量下的吞吐率能到100%在突发流量下也能到90%以上比纯轮询好太多。但iSLIP不是没有代价。它的迭代次数和端口数相关N越大迭代越多调度延迟越大。而且iSLIP假设每个输入端口只有一个队列这在多播场景下会出问题。所以实际芯片里往往用iSLIP的变种比如带优先级的iSLIP、或者结合VOQ的分布式调度。2.3 从iSLIP到实际芯片的调度器设计真实交换芯片里调度器不会只有一个iSLIP。通常会有两级甚至三级调度。第一级在入口侧决定哪个VOQ可以参与Crossbar仲裁。第二级是Crossbar仲裁本身用iSLIP或类似算法。第三级在出口侧决定哪个队列的数据先出去。为什么要这么复杂因为入口侧的VOQ数量可能远大于端口数。比如一个32端口的芯片每个端口有32个VOQ总共1024个队列。如果让这1024个队列直接参与Crossbar仲裁迭代次数会爆炸。所以入口侧先做一轮筛选把每个入口端口的多个VOQ压缩成一个候选再参与全局仲裁。这里有个实操经验调度器的指针初始化状态会影响收敛速度。如果所有指针都从0开始第一个时隙的冲突概率最高。有些设计会在初始化时把指针打散让它们均匀分布这样第一轮匹配的成功率就能提高不少。3. VOQ为什么入口必须按出口分开排队3.1 头阻塞一个队列堵住所有流量假设每个输入端口只有一个FIFO队列。端口1的队列头部是一个要去端口3的数据包但端口3当前正忙。这时候端口1的队列里即使有要去端口2的数据包也发不出去因为FIFO只能从头部取。这就是头阻塞HOL Blocking。HOL阻塞的杀伤力有多大在均匀流量下单FIFO的吞吐率上限是58.6%。这个数字是经过严格推导的不是拍脑袋。也就是说你花大价钱买的交换芯片如果入口只用一个队列理论吞吐率连60%都到不了。3.2 VOQ的拆分逻辑每个入口为每个出口维护独立队列VOQVirtual Output Queue的思路很直接既然头阻塞是因为不同出口的数据混在一个队列里那就按出口拆开。端口1为端口2、端口3、端口4各维护一个队列。端口1要发给端口3的数据放在VOQ(1,3)里要发给端口2的放在VOQ(1,2)里。这样端口3忙的时候端口1可以从VOQ(1,2)取数据发给端口2完全不受影响。VOQ把吞吐率从58.6%拉到了接近100%。代价是队列数量变成了N×N。对于32端口芯片就是1024个队列。每个队列都需要独立的存储空间和状态管理芯片面积和功耗都会上升。3.3 VOQ的存储代价与工程折中1024个队列听起来吓人但实际芯片里不会给每个VOQ都分配同样大的空间。常见做法是共享缓存加动态阈值。所有VOQ共享一块Buffer但每个VOQ能占用的最大空间由阈值决定。阈值可以是静态的也可以是动态调整的。动态阈值的好处是能适应流量变化。如果某个VOQ长时间空闲它的阈值可以调低把空间让给活跃的VOQ。但动态阈值需要额外的统计和计算逻辑设计复杂度更高。我见过一些芯片用“最大最小公平”算法来分配Buffer效果不错但实现起来需要迭代计算对时序有压力。还有一个折中是VOQ的粒度。不一定每个出口一个VOQ可以每两个或四个出口一组。这样队列数减少但头阻塞只是缓解不是消除。具体怎么选要看目标场景的流量模式。数据中心场景下流量往往集中在少数几个热点出口VOQ粒度粗了容易出问题。4. Shared Buffer共享到哪里边界在哪里4.1 共享Buffer的基本模型与收益Shared Buffer是指所有端口的队列共享同一块物理存储。相比每个端口独立分配Buffer共享方案在统计复用上更有优势。因为流量突发是随机的独立Buffer需要按最坏情况给每个端口预留空间而共享Buffer可以让空闲端口的空间被活跃端口借用。举个具体数字假设8个端口每个端口独立Buffer为1MB总存储8MB。如果流量集中在端口1和端口2它们各自只能用1MB超过就丢包。但如果8MB是共享的端口1和端口2可以各自用到接近4MB丢包率大幅下降。4.2 共享Buffer的公平性难题一个端口能占多少共享Buffer最大的问题是公平性。如果没有限制一个高速端口可能把整个Buffer占满其他端口无空间可用。所以必须设置阈值。阈值的设计有三种常见策略第一种是静态阈值每个端口或每个队列固定一个上限。简单但不够灵活流量模式变化时效率低。第二种是动态阈值根据当前空闲空间和活跃端口数动态计算。比如空闲空间多时放宽阈值空闲空间少时收紧。这种策略能提高利用率但计算逻辑复杂。第三种是预留加共享每个端口先预留一小块保底空间剩下的作为共享池。保底空间保证基本通信不丢包共享池提高突发吸收能力。这是实际芯片里最常见的方案。4.3 共享Buffer与VOQ的配合方式Shared Buffer和VOQ不是对立的而是配合的。VOQ定义了逻辑队列的结构Shared Buffer定义了物理存储的分配方式。一个典型的组合是入口侧用VOQ做逻辑排队物理存储用Shared Buffer每个VOQ的入队和出队由链表管理。链表管理的关键是空闲块的管理。Buffer被切成固定大小的Cell每个Cell有一个指针。空闲Cell组成空闲链表入队时从空闲链表取Cell出队时把Cell还回去。这个链表操作必须在每个Cell的周期内完成对时序要求很高。我见过一些设计用多级流水线来隐藏链表操作的延迟但流水线深度增加会带来额外的存储和复杂度。还有一个容易忽略的点是Cell的大小选择。Cell太小链表操作频繁开销大Cell太大内部碎片多小包浪费严重。常见的选择是64字节或128字节和以太网最小包长对齐。但如果是变长包最后一个Cell可能装不满需要额外的字节计数来标记有效长度。5. Cell Fabric为什么内部传输要切成定长单元5.1 变长包直接过Crossbar的问题如果数据包直接以变长形式通过Crossbar会带来几个麻烦。第一仲裁周期不固定。一个64字节的包和一个1500字节的包传输时间差20多倍调度器很难做时隙对齐。第二Crossbar的交叉点开关需要保持闭合直到整个包传完这期间其他输入不能使用这个输出浪费严重。第三Buffer管理复杂变长存储需要连续空间容易产生碎片。所以实际芯片几乎都把变长包切成定长Cell以Cell为单位做仲裁和传输。这就是Cell Fabric的由来。5.2 Cell的切分与重组入口切片、出口重组入口侧收到一个包后先按固定长度切成Cell。每个Cell带上头部信息包括源端口、目的端口、包序号、Cell序号等。这些Cell独立通过Crossbar到达出口后按包序号和Cell序号重组。重组逻辑需要处理乱序到达。因为不同Cell可能走不同的路径或者被不同的调度周期选中到达出口的顺序可能和发送顺序不一致。出口侧需要维护一个重组缓冲区按序号把Cell排好等所有Cell到齐后再组成完整包发出。这里有个设计选择是在入口侧等整个包收完再切片还是边收边切。边收边切能降低延迟但需要处理包尾不足一个Cell的情况。常见做法是最后一个Cell用有效字节数标记重组时按有效字节数截断。5.3 Cell Fabric的加速比与内部带宽计算Cell Fabric的带宽通常要大于外部端口带宽之和这个比值叫加速比Speedup。为什么需要加速比因为Crossbar仲裁不可能100%无冲突总有一些时隙浪费。加速比就是用来补偿这些浪费的。加速比的计算有个经验公式Speedup 1 / (1 - 冲突率)。如果冲突率是10%加速比至少要1.11。实际设计中考虑到突发和调度开销加速比通常取1.5到2之间。但加速比越高内部时钟频率和功耗也越高需要权衡。我参与过的一个项目里外部端口是100G内部Cell Fabric跑在1.5倍加速比下Crossbar的时钟频率比端口时钟高50%。这个比例下功耗增加了约30%但吞吐率从85%提升到了98%。值不值得要看目标场景对丢包的容忍度。6. 把Crossbar、VOQ、Shared Buffer、Cell Fabric串起来看6.1 一个数据包从入口到出口的完整旅程现在把前面几节串起来。一个包从端口1进来首先做路由查找确定目的端口是端口3。然后包被切成Cell每个Cell带上目的端口信息。根据目的端口Cell被放入VOQ(1,3)。VOQ(1,3)的物理存储在Shared Buffer里通过链表管理。调度器在每个时隙开始时让所有非空VOQ参与iSLIP仲裁。VOQ(1,3)如果被选中它的一个Cell就从Shared Buffer读出通过Crossbar送到端口3。端口3收到Cell后放入重组缓冲区等同一个包的所有Cell到齐后组成完整包从端口3发出。这个流程里每个环节都有优化空间。路由查找可以用TCAM或算法查找VOQ的阈值可以动态调整iSLIP的迭代次数可以配置Cell的大小可以调重组缓冲区的深度可以设。每个参数都会影响最终的吞吐率、延迟和丢包率。6.2 各组件之间的耦合关系与设计约束这些组件不是独立的它们之间有强耦合。比如VOQ的数量决定了调度器的复杂度调度器的迭代次数又决定了Crossbar的加速比需求。Shared Buffer的Cell大小决定了链表操作的频率链表操作的延迟又限制了Crossbar的时隙长度。一个常见的约束是Crossbar的时隙长度必须大于等于最慢的VOQ读取加链表更新时间。如果时隙太短链表操作来不及完成就会出错。所以设计时通常先确定Cell大小和链表操作延迟再反推时隙长度最后确定Crossbar的时钟频率。另一个约束是重组缓冲区的深度。如果深度不够乱序Cell到达时可能被丢弃导致重传。深度太大又浪费存储。通常根据Crossbar的最大乱序程度来估算而乱序程度又和调度算法、加速比有关。这是一个循环依赖实际设计中往往先给一个经验值再通过仿真调整。6.3 实际芯片中常见的折中方案真实芯片里没有完美的方案只有折中。我见过的一些折中包括用VOQ的粗粒度减少队列数用Shared Buffer的动态阈值提高利用率用iSLIP的简化版降低调度延迟用Cell的固定大小简化链表管理。还有一个折中是分级Crossbar。对于大端口数芯片单个Crossbar的交叉点太多功耗和面积都受不了。所以做成两级第一级把端口分组组内用小Crossbar第二级用另一个Crossbar连接各组。这样交叉点总数从N²降到N²/2左右但调度变成两级复杂度上升。选择哪种折中取决于目标市场的需求。数据中心芯片追求高吞吐和低延迟可能用全VOQ加高加速比企业级芯片追求成本和功耗可能用粗粒度VOQ加低加速比。没有绝对的好坏只有适不适合。7. 几个容易踩的坑和实操建议7.1 VOQ数量爆炸时的存储管理技巧当端口数到64甚至128时VOQ数量是4096到16384。如果每个VOQ都维护独立的头尾指针和计数器光状态存储就不得了。一个技巧是用链表把空闲VOQ串起来只给活跃VOQ分配状态。另一个技巧是用哈希表把VOQ索引映射到物理队列减少索引存储。但哈希有冲突冲突时可能需要多个VOQ共享一个物理队列又引入了头阻塞。所以哈希函数的设计很关键要尽量把同一入口不同出口的VOQ映射到不同桶。我见过用异或哈希的效果比取模好因为异或对低位变化更敏感。7.2 iSLIP指针初始化的实际影响前面提过指针初始化这里展开说。iSLIP的指针如果全部从0开始第一个时隙所有输入都请求输出0冲突最大。如果指针随机分布第一轮匹配成功率能提高30%以上。但随机分布需要额外的随机数生成器或者用端口号做种子做伪随机。更简单的做法是用端口号本身做初始指针。比如输入i的指针初始为i mod N输出j的指针初始为j mod N。这样指针天然分散不需要随机数。实测下来这种初始化方式在均匀流量下和随机初始化效果差不多但实现简单得多。7.3 Cell大小选择的量化依据Cell大小不是拍脑袋定的。要考虑三个因素最小包长、链表操作开销、内部碎片率。以太网最小包是64字节如果Cell小于64字节一个最小包要切多个Cell链表操作次数增加。如果Cell大于64字节最小包只占一个Cell的一部分碎片浪费。常见的选择是64字节或128字节。64字节的碎片率对最小包是0%对1500字节包是4%左右。128字节的碎片率对最小包是50%对1500字节包是8%左右。所以64字节更优但链表操作频率是128字节的两倍。如果链表操作能在一个时隙内完成64字节是更好的选择。7.4 重组缓冲区深度的估算方法重组缓冲区的深度决定了能容忍多大的乱序。乱序程度和Crossbar的调度有关。如果调度器保证同一包的Cell按序发送乱序程度就低。但iSLIP不保证按序所以乱序程度可能较高。一个估算方法是最大乱序程度约等于Crossbar的加速比乘以端口数。比如加速比1.5端口数32最大乱序约48个Cell。重组缓冲区深度至少要是这个数的两倍留出余量。实际设计中通常用仿真来确定因为流量模式对乱序影响很大。7.5 验证阶段最容易忽略的边界场景验证数据通路时有几个边界场景容易被忽略。第一所有端口同时向同一个端口发包这是最坏冲突场景考验调度器的公平性和Buffer的阈值。第二包长从最小到最大连续变化考验Cell切分和重组的正确性。第三长时间满负荷运行考验指针的公平性和Buffer的泄漏。还有一个场景是背靠背小包。小包切成的Cell少调度周期短容易暴露时隙对齐问题。我见过一个bug就是背靠背小包时重组缓冲区指针回绕出错导致数据错位。这种bug在随机流量下很难复现必须专门构造测试用例。8. 从微架构到实现axi4 crossbar的落地考量8.1 AXI4 Crossbar与交换芯片Crossbar的区别最近axi4 crossbar实现是个热词很多人把AXI4的Crossbar和交换芯片的Crossbar混为一谈。两者虽然都叫Crossbar但设计目标不同。AXI4 Crossbar是片上总线互联主要解决多个Master和多个Slave之间的连接强调协议兼容和低延迟。交换芯片的Crossbar是数据平面互联强调高吞吐和公平仲裁。AXI4 Crossbar通常用简单的轮询或固定优先级仲裁因为片上Master数量少冲突不严重。交换芯片的Crossbar要用iSLIP这类迭代仲裁因为端口多、流量大、冲突严重。所以不能直接把AXI4 Crossbar的设计套到交换芯片上。8.2 用AXI4实现Crossbar时的仲裁器设计如果要用AXI4实现一个类似交换芯片的Crossbar仲裁器是核心。AXI4的仲裁通常在每个周期做一次可以用组合逻辑实现。但iSLIP需要多轮迭代组合逻辑实现不现实需要时序逻辑加状态机。一个可行的方案是用两级仲裁第一级用AXI4的轮询仲裁做粗筛第二级用简化的iSLIP做精调。粗筛把每个Master的多个请求压缩成一个精调在压缩后的请求之间做迭代匹配。这样既利用了AXI4的协议兼容性又引入了交换芯片的调度优势。8.3 时序收敛与面积优化的实际经验AXI4 Crossbar的时序收敛是个大问题。交叉点开关的扇出很大N×N的Crossbar每个输出要连N个输入扇出是N。N16时扇出16还能接受N32时扇出32时序就很紧张了。优化方法有几种一是用流水线把Crossbar切成多级每级扇出减小二是用多路选择器树代替交叉点阵列减少长线三是用寄存器切割关键路径。我试过流水线方案把Crossbar切成两级每级扇出从32降到16时序从500MHz提升到了800MHz但延迟增加了一个周期。面积优化方面交叉点开关的面积和N²成正比。N32时1024个交叉点每个交叉点即使只有几个门总面积也不小。用多路选择器树可以把面积降到N×logN级别但布线复杂度上升。实际选择要看工艺和布线资源。8.4 从RTL到门级验证Crossbar功能的关键用例验证Crossbar功能时关键用例包括所有输入同时请求同一个输出检查仲裁公平性所有输入请求不同输出检查并行传输能力输入请求动态变化检查指针更新正确性长时间运行检查指针回绕和公平性。还有一个用例是背压测试。当输出端口暂时无法接收时Crossbar要能正确反压输入不能丢数据。这个用例考验的是流控逻辑和仲裁逻辑是分开的但两者必须协同工作。9. 写在最后数据通路设计的取舍哲学做了这么多年交换芯片我越来越觉得数据通路设计是一门取舍的艺术。Crossbar的端口数、VOQ的粒度、Shared Buffer的阈值、Cell的大小、iSLIP的迭代次数、加速比的高低每一个参数都在吞吐率、延迟、面积、功耗之间做权衡。没有一套参数能通吃所有场景。我的经验是先明确目标场景的流量特征。如果是数据中心东西向流量热点集中VOQ粒度要细Shared Buffer阈值要动态加速比要高。如果是企业接入流量分散VOQ粒度可以粗一些加速比可以低一些省功耗。定好场景再选参数比盲目追求高指标要靠谱得多。还有一个体会是仿真和实测的差距往往出在边界场景。均匀流量下跑得再好不代表突发流量下没问题。所以验证阶段一定要构造极端用例把所有端口往一个端口打把包长拉到最小和最大跑长时间稳定性。这些用例暴露的问题才是真正决定芯片能不能商用的关键。