ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从总线到NoC:片上网络如何成为系统架构的接口层?

从总线到NoC:片上网络如何成为系统架构的接口层? 很多做芯片级系统设计的朋友第一次接触“On-Chip Networks”时都会下意识把它当作一个单纯的物理互连方案也就是“把一堆线布好、能让数据从A点走到B点”。但真正把一个多核SoC或者Chiplet项目跑起来之后你会发现这种理解远远不够。片上网络在系统架构层面扮演的角色远比“互连”两个字要深得多——它其实是整个系统的接口层负责把计算单元、缓存、内存控制器、外设IP全部拉进同一个通信秩序里。这篇文章我会结合自己实际做过的项目把On-Chip Networks作为系统架构接口这件事拆开讲清楚包括它到底解决什么问题、核心元件如何工作、实际搭建时怎么选型、以及那些文档里不会写的踩坑经验。1. 从总线到NoC为什么会多出“接口”这一层1.1 一个多核项目的性能瓶颈现象先从我遇到的一个实际项目说起。当时我们做一颗16核的处理器芯片早期版本用的还是传统的总线互连架构。跑benchmark的时候发现一个很诡异的现象单核性能正常4核以内性能扩展也还算线性但一旦超过8个核性能不仅不涨偶尔还会出现整体倒退的情况。起初我们怀疑是缓存一致性协议出了问题但排查了很久最终定位到的瓶颈竟然只是互连结构本身——总线仲裁器的冲突变得极其严重大量的周期都消耗在等总线授权上。那个阶段我们才真正意识到当处理器核数量到一个量级之后“怎么把多核连起来”这件事已经不再是简单的版图问题而是系统架构问题。总线架构天然是共享带宽的所有主设备通过仲裁去抢同一条通信链路核越多抢得越凶。交叉开关Crossbar虽然能提供多路并行但端口数是平方级增长面积和功耗都扛不住。正是在这个背景下我们开始认真评估片上网络方案也就是NoC。有意思的是当我向团队引入NoC这个概念时很多同事的第一反应是“这不就是把交叉开关换成路由器组网嘛”。这个理解没错但如果只停留在这个层面后面会遇到数不清的架构设计问题——因为你真正需要面对的不是物理结构而是“接口”。1.2 总线、交叉开关与NoC的本质差异先给大家一个直观的类比总线就像一条单车道公路所有车都在同一条路上走路口多了自然堵交叉开关像立体车库入口和出口之间可以有直通通道但每个新入口都要加一堆机械结构成本极高NoC则更像一张城市路网——有主干道、有支路、有路口红绿灯车辆从A点到B点可以有多条路径每个路口只需要管理自己周边的车流。具体到架构层面三者的本质差异体现在三个方面。第一是扩展性总线和交叉开关对节点数量敏感NoC则可以通过增加路由节点的方式近乎线性扩展。第二是并发能力NoC天然支持多对通信同时进行而总线在任意时刻只能允许一对通信占用。第三是设计解耦——这一点与本文主题直接相关总线架构下每个新IP的接入都需要重新评估时序和带宽整个系统的耦合度非常高而NoC通过标准化的接口让IP的设计与互连设计能够并行推进。也是从第三点开始我意识到NoC的价值已经超出了物理通信本身它在架构上承担了一个隐形但关键的职责——定义系统各组件之间的通信契约。换句话说它就是系统架构的接口层。2. 为什么说NoC是系统架构的“接口层”而不是单纯的互连2.1 网络接口把协议、地址和信号包起来如果你拆开一个NoC子系统来看除了路由器Router和物理链路Link之外还有一个非常关键的模块叫网络接口Network InterfaceNI有些厂商也叫它NICNetwork Interface Controller。这个模块正是NoC作为“接口层”的核心载体。我举个具体的例子。假设你的SoC里有一颗ARM CPU核它发出的读请求是AXI协议——有ARADDR、ARVALID、ARREADY这些信号有突发传输burst有ID编号。而你的NoC内部传输的数据格式是报文flit/packet包含路由头、负载、校验等信息。网络接口要做的就是在这两种完全不同的话语体系之间做翻译把AXI的事务拆成NoC认识的报文格式塞进网络里传输到达目的地之后再翻译回AXI协议。翻译这件事听起来简单做起来全是坑。比如AXI事务的乱序返回CPU发出多个读请求后并不要求返回顺序与发起顺序一致但每个请求都要用ID来标记。NoC要把这个ID映射到报文的某个字段里还要保证不同ID的报文在NoC里可以乱序走、到端之后再按ID把结果正确匹配回去。如果做过PCIe或者以太网控制器的朋友对这个“标签-还原”的机制应该不陌生但NoC是片上实现面积和功耗预算极其紧张所以设计上会抠得非常死。2.2 地址映射与路由报文从哪里来到哪里去网络接口层的第二个关键职责是地址映射。系统里每个从设备内存控制器、外设寄存器、远程核的私有缓存都有自己的一段地址空间。当CPU发出一个地址访问请求时网络接口需要判断这个地址落在哪个从设备区域然后决定把报文从哪个端口送进网络。这个逻辑在总线时代是直接靠地址译码器Decoder实现的——总线仲裁器看到地址直接拉高对应的片选信号。但在NoC里地址映射不再是一个简单的“查表选通”它要和路由策略绑定根据地址段确定目标节点ID再结合当前节点和目标节点的位置关系查路由表或者计算路由方向决定报文往哪个方向转发。实际工程中这里最容易出现的问题是多地址段映射到同一目标时的QoS处理。比如系统里有两个主设备同时高频访问DDR控制器的同一段地址网络接口不做优先级管理的话会造成DDR端报文的严重拥塞进而拖慢整个系统的响应。所以很多设计会在网络接口这一层就完成QoS标记和仲裁优先级分配而不是把所有管理都丢给路由器。2.3 协议多样性支持AXI、CHI、TileLink如何共存当NoC真正承担起接口层的职责后一个衍生价值就是它让不同协议之间的互操作变成了可能。一个复杂的SoC里很可能CPU侧走CHI协议ARM的缓存一致性协议GPU侧走AXI某个自研加速器走TileLink或者自定义协议。如果没有NoC这一层做协议转换每一对IP之间都需要专门的桥接模块模块数量会呈指数爆炸。有了NoC只需要每个IP对接自己的协议转换网络接口网络内部统一用一套报文格式传输协议差异被限制在了接口边界处。这就像国际航班到港后统一走海关就算你来自不同国家流程都是对接同一个入境系统而不需要每个国家之间专门修建一条专属通道。这套思路在Chiplet设计里尤其重要。各芯粒Die内部可以用各自的NoCDie与Die之间通过标准化接口比如UCIe对接而协议上的差异就靠网络接口去做适配。可以说没有NoC作为标准化的接口层Chiplet架构的“乐高式”组装根本不可能落地。3. 核心架构拆解路由器、流控与拓扑选型3.1 路由器五级流水线的具体工作过程要真正理解NoC怎么运作绕不开路由器。一个典型的路由器内部可以看作一条五级流水线每一级承担的职责完全不同分别是路由计算Route Computation、虚拟通道分配VC Allocation、开关分配Switch Allocation、交叉开关传输Switch Traversal、链路传输Link Traversal。我先解释一个概念虚拟通道Virtual ChannelVC。工程上它解决的核心问题是物理上只有一条链路但逻辑上我们可以把它切分成多条独立的缓存队列每条队列对应一条“虚拟”通道不同虚拟通道共享物理链路带宽但缓冲空间相互独立。为什么要这么做因为当某个报文在下一跳路由器被阻塞时如果所有报文都挤在同一条缓冲队列里后面想要超车的报文也只能干等着这叫头阻塞Head-of-Line Blocking。多条虚拟通道可以让不同类别的流量各走各的队列一个阻塞不影响另一个。路由计算级要做的事是确定当前报文该从哪个输出端口继续转发。最简单的实现是查表把目标节点ID作为索引查一个预计算好的路由表也可以走算法计算比如2D Mesh里常见的XY路由算法——先沿X方向走到目标列再沿Y方向走到目标行算法简单到只需要几次比较运算。XY路由最大的好处是天然无死锁因为所有报文的转向都是统一规则不会出现两个报文互相等对方让路的情况。虚拟通道分配和开关分配这两级是整个路由器里最复杂的控制逻辑。虚拟通道分配的输入是多个输入端口在竞争同一个输出端口的不同虚拟通道输出是每个报文拿到哪个VC开关分配则是决定哪个输入端口在下一个周期能连到哪个输出端口。这里的仲裁器如果取舍不好会直接影响网络吞吐。我们实际测试过不同仲裁策略最朴素的轮询Round Robin在小报文场景下表现不错但在长短报文混合的场景下尾部阻塞问题很严重后来换成了基于信用的优先级仲裁整体性能提升了约23%但面积也增加了不少。交叉开关传输和链路传输就比较直白了一个是报文穿过路由器内部的交叉开关矩阵另一个是沿着物理导线传到下一跳。这两个阶段是纯数据传输时序压力最大稍微做过后端的人都知道长走线才是片上网络总延迟里的真正大头。3.2 流控机制credit和ON/OFF的区别流控是决定NoC性能和稳定性的另一根支柱。它的作用是防止发送端的报文把接收端的缓存冲爆。业界最常用的两种方案是Credit-Based基于信用和ON/OFF基于开关阈值。先说Credit-Based原理很像餐厅等位制度。接收端每腾出一个缓存空间就向发送端发一个“信用”Credit发送端每发送一个报文就扣掉一个信用当信用为0时发送端必须停手。这套机制的好处是精确缓存利用效率高报文在任何时候都不会产生透支行为。缺点是每个周期都要带信用信息在窄链路比如32-bit宽度上会占用不少可用带宽。ON/OFF机制则简单粗暴得多接收端设定一个高水位和一个低水位阈值缓存占用超过高水位就发“OFF”信号让发送端停跌回低水位再发“ON”信号允许继续。实现简单、控制比特少但代价是有滞后性——OFF信号传到发送端时链路里可能还飞着一批在途报文接收端必须预留这部分缓冲。从我实际接触过的商用NoC IP来看大部分选择credit方案作为默认配置。但有一个细节很多人会忽略credit信号本身在跨时钟域传输时是需要同步器的同步器带来的延迟会让credit反馈滞后好几个周期实际可用缓存比理论上要少。设计时如果不能把这个“信用空洞”Credit Bubble算清楚高负载下极易出现性能悬崖。3.3 拓扑对比Mesh、Ring、Crossbar怎么选拓扑选型是NoC设计里最纠结的部分没有绝对最优只有相对适合。我先列一张常用拓扑的对比表再展开讲选型逻辑。拓扑类型延迟带宽面积可扩展性典型场景总线低共享低最小差8节点简单SoC控制平面交叉开关低高平方增长中16节点中规模多核Ring中中小中50节点缓存一致性域2D Mesh中高高线性增长好千核级大规模多核/众核Torus高更高中高好HPC加速器集群很多人选Mesh的时候只看它扩展性好却忽略了一个关键点——Mesh的平均跳数会随着节点数增加而增加直接导致平均延迟上升。做一个32x32的Mesh最边上两个节点通信要走62跳延迟和功耗都非常感人了。Ring拓扑其实被低估了它在延迟一致性上表现非常优秀——环上任意两点之间的跳数上限固定不像Mesh最坏情况那么丑。但Ring的问题是带宽天花板低环上所有流量都共享带宽一个节点堵塞整个环都受影响。我自己的选型思路是这样的先看通信流的局部性。如果一个系统里大部分通信都发生在邻近节点之间比如近内存计算架构Mesh的局部性优势明显如果是广播型流量比较多比如缓存一致性中的snoop请求环或者带广播能力的总线可能更合适如果节点数量小、性能要求高、不差面积直接用交叉开关最省心。当然实际工程里不会只用单一拓扑很多情况下会用层次化结构局部走交叉开关或小Mesh全局走Ring或Mesh。这里的取舍本质上是用拓扑换“接口复杂度”——越规整的拓扑路由越简单网络接口需要处理的分发逻辑也越少。4. 一次完整的片上网络搭建实录4.1 从规格定义到工具选型为了让大家有更具体的体感我拿一个8x8规模的Mesh NoC项目作为例子走一遍从规格到验证的完整流程。项目背景是某个边缘AI芯片包含64个计算单元、4个DDR通道和1个管理CPU核。原始需求只有一句话“计算单元之间要能高效通信和DDR之间延迟要低”。这句话展开成NoC规格后出现了大量需要拍板的信息我列一下当时定的核心参数报文宽度128-bitflit宽度缓存深度每虚拟通道8个flit虚拟通道数量2条一条用于请求一条用于响应路由算法XY确定性路由第一版先求稳定不做任何自适应时钟频率目标800MHz端到端延迟本地通信不超过20周期跨Die最远不超过100周期选型上我们当时有几个选择商用NoC IP比如Arteris、Synopsys的、开源NoC比如Torus处理器项目的SomeNoC或者学术界的OpenSMART、自己写RTL。商用IP成熟、性能可以、但授权费用高且很多细节是黑盒开源NoC适合快速做原型验证、但代码质量和覆盖率参差自研NoC周期长但可控性最强方便后续针对具体业务做定制。最终我们选择了先引入一个商用评估版跑通整个SoC集成流程同时在Lab里自己写了一个简化版NoC用于微架构实验。双线并行的好处是商用版作为性能标杆自研版作为灵活探索对象两边对比能快速定位性能差距来源。4.2 RTL实现中的队列管理与乒乓缓存自研NoC的RTL实现里工作量最大的不是路由器而是输入端的队列管理。Mesh里每个路由器有5个输入端口东、南、西、北、本地每个端口又有双虚拟通道合起来就是10条独立的缓冲区队列。我们用乒乓缓存Ping-Pong Buffer来做队列的物理实现两块SRAM轮换使用一个写一个读交替进行。好处是读写可以并行不需要额外Dual-Port SRAM面积能省不少。代价是队列深度必须是偶数而且队空判断要额外加状态机来管理“当前该读哪一块”。这里有一个很重要的工程细节队列深度的选择需要和credit回传延迟匹配。我们当时的链路物理长度预估在2mm左右800MHz下大约5拍传播延迟加上同步器延迟3拍一个credit从接收端发到发送端需要约10拍。如果队列深度小于这个往返延迟发送端会频繁欠credit实际吞吐大打折扣。我们最后把每VC深度定在8个flit刚好能覆盖往返延迟并留出一些余量。代码层面路由器状态机用两级流水实现。第一级做路由计算和VC分配第二级做开关分配和数据传输。相比教科书上的五级流水两层流水在CLPCritical Path Length上更好收敛但代价是blocking行为更明显——后一拍必须等前一拍的仲裁结果出来才能继续。实测下来我们的流水级减到2级后在400MHz下综合频率提升了12%但饱和吞吐下降了约8%。这个取舍没有标准答案完全取决于项目的瓶颈在频率还是带宽。4.3 仿真验证怎么证明你的NoC是能用的NoC验证的难点和普通IP很不一样。普通IP的输入输出关系是确定的给一组激励就能比对预期NoC则是多输入多输出的动态系统一个报文从入口进去走的路径由路由器动态决策决定结果验证需要全局观察。我们的做法分三层。第一层是模块级定向测试针对单路由器的5个端口写随机激励验证路由计算、仲裁、转发功能正确性以及是否有丢包和错包。第二层是网络级压力测试构造各种pattern的流量注入uniform random、 transpose、bit-reversal这些benchmark经典模式观察整体吞吐和延迟分布是否与理论模型吻合。第三层是SoC级应用仿真跑真实软件的负载模型验证访存延迟时序是否满足应用需求。仿真平台的搭建上我们没有全部自己写testbench而是引入了SystemC/TLM的快速仿真模型与RTL形成双轨验证体系。TLM模型跑一遍全网流量仿真只需要几分钟而RTL仿真跑同样的场景要几十个小时。先用TLM把量大的带宽、延迟趋势算清楚再用RTL精仿真验证关键path上的时序和交互细节。验证的一个坑性能数据必须在饱和吞吐点附近多采集几个点。很多刚入行的朋友喜欢只报一个“最大带宽”数字但其实NoC的延迟-负载曲线远比这个数字重要。曲线越平缓说明网络在重负载下依然稳健曲线一旦出现悬崖式上升说明网络正处在拥塞崩溃的边缘。这个曲线特征直接反映了流控和缓冲区设计的健康度。5. 调试片上网络时那些折磨人的问题5.1 死锁最隐蔽的系统级故障死锁是NoC设计里让人头皮发麻的问题之一。它的发生场景很经典节点A在等节点B释放缓冲节点B在等节点C释放缓冲节点C又在等节点A释放缓冲——三方互等谁也无法推进。我们当时项目里出现过一次诡异的现象系统跑一段时间后某个计算单元的访存请求QoS突然恶化到几乎不响应但其他单元看起来都正常。刚开始以为是某个单元的IP出了问题后来检查NoC内部链路状态才发现某一对路由器之间的两条VC全部被占用且不释放形成了一对一死锁。排查下来根因让人哭笑不得某个自定义协议的网络接口在做重传处理时错误地把一个重试请求标成了新的请求导致同一笔事务在网络里同时存在两份报文。这两份报文在特定路径上相遇互相占着对方需要的VC死锁就此产生。从这次经历里学到的教训是NoC的死锁设计不能只靠路由算法保证协议级和接口级的一致性设计同样重要。任何允许事务在网络中重复存在的场景都需要在协议层面对重复报文的处理做显式定义否则即使XY路由本身无死锁也会被上层协议打破这个不变量。5.2 跨时钟域的亚稳态NoC天然是异步时钟域的聚集地。SoC里CPU通常在高频时钟域DDR控制器有自己的时钟外设更是五花八门。NoC内部如果用单一全局时钟跨域同步的麻烦还没有那么大一旦采用多时钟域NoC设计比如每个路由节点独立时钟来逃时序麻烦就大了。我们踩过的一个典型问题出在credit信号的跨域同步上。credit来自接收端时钟域需要同步到发送端时钟域。按教科书方法两级触发器同步器是标配但两级同步器解决的问题是把亚稳态概率压到足够低并不能消除延迟。问题出在“连续credit流”的场景下接收端连续腾出缓存时会连续发多个credit每个credit之间间隔很短。如果同步器在采样的过程中丢了一个credit或者两个相邻credit被合并成一个发送端就会认为可用缓存比实际少导致性能下降。这类问题定位起来极其痛苦因为不是功能性错误而是概率性性能衰减有时跑很久才出现一次。最后我们用了一个相对朴素的方案把credit计数改成“多bit同步格雷码编码”让同一时刻只有一个bit翻转彻底避免多bit计数同步时的采样错乱问题。再把单一信用信号改成批处理模式——每发N个可用缓存发一个credit降低同步频率。5.3 仿真和样片的行为不一致最后一个陷阱我觉得特别值得分享仿真通过不代表样片就能跑。我们在项目里有过一次“仿真全绿、样片翻车”的经历问题出在NoC路由状态机的一个边界条件上。RTL仿真环境里我们用了相对理想化的时序模型信号跳变都是理想的0和1。但样片回来后某些长走线在特定PVT下出现了建立时间违规导致路由控制信号被采到了错误的沿上。整个系统的表现是冷片一切正常温度一升高或者电压一拉低NoC就间歇性丢包。这个问题的痛苦在于逻辑仿真根本没有覆盖到物理层时序失效的场景。后来我们引入了门级仿真Gate-Level Simulation加上延时注释SDF back-annotation来复现才看到了那些亚稳态和时序违规的波形。从那以后我们的NoC验证流程里就固定增加了一步在签名归档前必须跑一轮带时序信息的门级仿真尤其是针对跨时钟域和控制信号密集的模块。6. 给正在选型和上手NoC的同行一些建议如果说前面几节是“技术解剖”那最后这段我想聊点更偏向项目决策层面的经验也是我在几个项目里摸爬滚打后的真切体会。如果你是在评估是否引入NoC我建议先别急着看IP指标和benchmark数据而是先回答一个问题你的系统里是否存在真正的“多对多通信需求”如果只是单主单从或者主从明确、并发流量很小那老老实实用总线或者交叉开关就够了NoC带来的是额外面积和功耗没有任何收益。但如果系统里有多个主设备同时发起通信、访问多个从设备而且流量模式会随着软件运行而变化那NoC的并发和动态路由优势就体现出来了。如果你确定了要走NoC路线架构设计时有一条我特别想强调的原则先定协议契约再定微架构。很多团队上来就先选拓扑、定路由器架构然后才考虑怎么接AXI或者CHI协议。这个顺序其实反了。NoC的微架构设计必须始于网络接口的协议定义——每个字段、每个突发、每种事务类型都要先想清楚怎么映射进报文格式因为协议契约一旦定了路由器和拓扑优化的空间才算真正被锁定下来。协议层没有想清楚就动微架构后面多半要返工。关于团队能力建设我想说NoC设计对工程师的综合素养要求其实比一般IP要高。做一个UART控制器你只需要懂串行协议做一个NoC你需要懂计算机体系结构缓存一致性、虚拟缓存、计算机网络路由、流控、拥塞控制、数字IC设计时序收敛、跨时钟域甚至系统软件地址映射、驱动接口。所以如果团队里没有具备这三种背景的成员尽量别自研完整NoC先拿商用IP或者开源IP做集成会是更稳妥的选择。最后提一个容易被忽视的点NoC的功耗分析一定要做在架构级而不是等RTL出来之后再做。因为NoC的功耗大头不在逻辑而在长走线的翻转功耗。拓扑决定了走线长度路由策略决定了活动因子这两个变量在架构阶段就定了。等到RTL出来再用工具做功耗分析能优化的空间已经很小了。我们当时在架构阶段用脚本估算各种拓扑下的平均跳跃次数和对应功耗选择了一个比Mesh多10%面积但功耗低24%的混合拓扑这个收益在物理实现阶段再想追回来几乎不可能。说回最初那个问题On-Chip Networks什么时候变成了“系统架构接口”而不是单纯的互连答案其实已经藏在整个设计过程里了。它把共享总线的“天梯式”通信改造成了网状路由的“城市交通”让不同频率、不同协议、不同通信模式的计算组件都能在同一个通信秩序下高效协作。就是这种系统性解耦的能力让它从底层物理连接工具升格为整个芯片架构中最具决定性的设计节点之一。希望这篇文章能帮你在做架构决策时少走一些弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进