ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

电路交换NoC路由器设计与实现:从架构到FPGA验证

电路交换NoC路由器设计与实现:从架构到FPGA验证 简介面向片上网络与集成电路设计方向的研究者、高年级本科生及工程师这份PDF提供了一篇关于电路交换NoC路由器设计与实现的完整技术文献。内容以西安邮电学院相关研究为基础详细论述了在Mesh拓扑下采用5输入×5输出端口、支持48Gbit/s交换容量且具备100%无阻塞能力的电路交换路由器设计方法并涵盖多播与广播配置、Verilog HDL代码编写、仿真验证以及Altera FPGA硬件实现等关键环节。压缩包内共1个PDF文件整体大小仅696KB便于下载后离线精读与打印参考。目前已有134人学习适合正在开展片上网络课程设计、SoC通信架构研究或FPGA路由器开发实践的人群。通过研读该文读者可掌握电路交换路由器的功能定义与实现路径理解NoC中确定性通信保障的设计思路并能借鉴其仿真与FPGA验证流程为自身工程或论文工作提供直接参考。 最近花了几周时间把手头“基于电路交换的NoC路由器设计与实现”这个方案从架构文档一路推进到了可综合的RTL代码最后在FPGA上把多路并发数据流完整跑通。NoC片上网络通俗点说就是芯片内部给处理器核、存储控制器、加速器之间做数据互通的“立交桥”而路由器就是立交桥上的匝道口和红绿灯。常见的NoC实现大多基于包交换但这个项目里我们要搬运的是大块、高带宽、对抖动敏感的数据逐跳仲裁的包交换模式反倒不划算所以我把电路交换的思路完整落地了一遍。这篇就专门拆解这套电路交换NoC路由器的设计与实现过程适合正在做多核互连、流式加速器数据通路或者想从另一个角度理解NoC的工程师参考。1. 为什么做电路交换NoC场景里的“即时接通”思路1.1 包交换与电路交换的本质区别这两个词的差异用打电话和寄快递来比喻最直接。包交换是寄快递每个数据包都带着完整的目的地信息在每一级路由器上停下来查地址、排队、转发同一个网络里的不同包可以走完全不同的路。电路交换是打电话通话之前先拨号整条链路沿途的设备都留出专用通道拨通之后这条通道在这一通电话期间只给你一个人用中间不再有任何决策。落到NoC里区别就更具体了。包交换路由器每个输入端口需要FIFO或者队列缓存每个flit流控单元到达后都要做一次路由计算和交叉开关仲裁而电路交换路由器在数据真正流动之前先把从源节点到目的节点这条路径上的所有交叉开关都“锁死”数据一进来就走直线中间既不查路由表也不参与仲裁物理上就是一组MUX一路导通的直连。这里有个容易混淆的点NoC的电路交换和传统电信网络里的电路交换并不是一回事。NoC里“电路”代表的是片上点对点互连资源交叉开关通路、链路带宽的一种临时预留没有电话线那种纯物理的独占概念但在“先建立、再使用、后释放”这件事上是同一个思想。1.2 我为什么没有随大流选包交换选电路交换之前我其实花了不少时间对比两种方案。包交换最大的优势是灵活性高、链路利用率高短小的控制报文混在长数据流里也能被及时转发而且某个通路拥塞时多路径可以绕开。但它的代价也很直接每个路由器都要有缓冲、仲裁、路由计算单元面积和功耗都上去了数据在中转过程中还可能出现排队抖动延迟不确定。这个项目的数据特征非常固定节点之间搬运的是视频帧、矩阵分块、传感器连续采样的数据块一次传输动辄上千拍中间几乎不需要穿插细碎的控制消息。对这类场景电路交换的优点全打在了痛点上。首先是延迟和确定性。路径一旦建立从源到目的地的每一拍都稳定通过没有排队等待最坏情况延迟和平均情况几乎相等这对硬实时任务非常重要。其次是面积和时序。电路交换路由器不需要大数据缓冲输入端最多放很浅的同步寄存器交叉开关也更简单。第三是功耗数据通道全程无仲裁翻转动态功耗比包交换低不少。当然电路交换的短板我也清楚建链需要额外时间链路在没有数据期间依然是独占空闲短消息如果也走这套流程建链开销会直接把收益吃光。所以这个设计从一开始就把适用边界定在“大块数据、长流传输”这也是想抄作业的人最需要先确认的一点。1.3 设计目标与指标我给自己定的第一版设计目标拓扑4x4 Mesh2D网格这也是NoC里最常用的拓扑路由算法简单布线规整。路由器端口东、南、西、北、本地共5个方向每个方向独立收发。数据位宽64bit数据加valid/ready握手。目标时钟FPGA原型上做到200MHzASIC综合目标250MHz。建链时间4x4 Mesh单条路径从发起请求到建链完成不超过16个时钟周期。并发连接每个路由器最多同时维持16条已建立的电路。支持多种路由策略至少实现XY路由先X方向后Y方向预留配置寄存器可切静态路由表。这些指标不是拍脑袋定的。4x4 Mesh是片上网络最经典的演示规模既能体现多跳路径的复杂性又不会因为规模太大让调试周期失控。16条并发连接是因为4080这种规模的mesh最坏情况下单路由器会有多个方向同时请求太低容易饿死局部流量。2. 路由器架构设计与三阶段连接流程2.1 模块划分与信号接口整个路由器我做成了5个模块RTR_TOP顶层、ROUTING_UNIT路由计算、SWITCH_ALLOCATOR交叉开关分配器、CROSSBAR交叉开关本体、CONFIG_TABLE连接配置表。顶层对外接口用统一的flit级握手信号每个方向一组module router #( parameter DATA_WIDTH 64, parameter ID_WIDTH 4, parameter PORT_NUM 5 )( input logic clk, input logic rst_n, // 本地配置端口用于修改路由表等控制信息 input logic [7:0] cfg_addr, input logic [31:0] cfg_wdata, input logic cfg_wen, // 5个方向的输入输出 axi_stream_if.slave in_n, axi_stream_if.slave in_e, axi_stream_if.slave in_s, axi_stream_if.slave in_w, axi_stream_if.slave in_l, axi_stream_if.master out_n, axi_stream_if.master out_e, axi_stream_if.master out_s, axi_stream_if.master out_w, axi_stream_if.master out_l );我把端口做成参数化后续要扩到6端口、8端口只需要改参数和拓扑连接脚本不用重写核心逻辑。这也是整个项目里我认为值得坚持的一点NoC这种多实例化的结构参数化带来的收益远大于一开始那点写代码的工作量。2.2 三阶段协议建立、传输、释放电路交换的核心动作就三件事建立连接、传输数据、释放连接。这里我把协议细节展开讲。建立阶段用RTSRequest To Send信号启动这个信号是一个特殊的控制flit里面包含源节点ID、目的节点ID和连接ID。路由器收到RTS后ROUTING_UNIT算出下一跳方向SWITCH_ALLOCATOR尝试申请对应输出端口申请成功后就把交叉开关的对应通路锁住同时把RTS继续往下一跳传。下一跳同样操作直到RTS到达目的节点。目的节点回一个ACK信号ACK再逐跳原路返回源节点收到ACK才正式认为链路建立完成。传输阶段就好办了。数据flit进入路由器后不再经过任何仲裁逻辑直接根据CONFIG_TABLE里锁好的连接ID走交叉开关导通路径每拍都可以进一个新flit带宽完全不丢。释放阶段由源节点发送EOSEnd Of Stream信号。这个信号从源头开始逐跳清除沿途交叉开关的连接配置全部清完这条路径才算真正释放。如果源节点因为错误直接消失释放信号没人发也不行所以我在每个路由器上加了一个看门狗定时器连接如果超过设定周期没有任何数据活动自动强制释放。为什么释放必须逐跳做而不是全网络广播因为交叉开关的状态是分布在各路由器内部的只有经过的节点知道自己哪个通路被占用了。如果只做广播节点压根不知道自己需要清哪条通路反而容易误清其他活跃连接逐跳释放虽然慢一点但目标明确不会发生误操作。2.3 路由计算与死锁、活锁处理路由计算这块我用了最简单的确定性XY路由每个包从源出发先沿X方向走到目标列再沿Y方向走到目标行。这样做的好处很直接它是无环的只要每个节点都遵守同样的规则就不会出现环状等待天然免死锁。XY路由唯一的死锁风险在于建链请求之间互相竞争资源时可能出现循环等待A节点想用的通路被B占着B想用的通路又被C占着C又等A。解决方法是让RTS请求的优先级固定比如始终对东、南方向的请求优先级高于西、北同时在SWITCH_ALLOCATOR做FIFO仲裁同一个输出方向同时有多个请求时按到达先后排序。优先级固定加FIFO仲裁配合XY无环路由这个项目里基本没有死锁和活锁的生存空间。如果后面要上更复杂的拓扑比如Torus或者不规则网那就得用更完整的死锁避免策略比如turn model或者虚通道分隔这是后续扩展的关键点。3. RTL实现的关键细节状态机、交叉开关与时序优化3.1 连接状态机的RTL形态每个路由器端口我实现了一个连接状态机用来管理对应方向的电路状态和转换状态定义如下typedef enum logic [2:0] { IDLE, // 空闲没有活动连接 SETUP_REQ, // 发起了RTS等待下一跳响应 SETUP_WAIT, // RTS已命中本节点等待下游ACK回传 TRANSMIT, // 链路已建立数据直通 RELEASE_WAIT // 收到释放请求等待通路彻底清空 } conn_state_t;状态转移逻辑比一般的协议状态机简单但有几个容易踩坑的地方。首先是SETUP_REQ状态下如果收到下游的NACK或者超时需要回到IDLE并向上游回NACK同时把本节点已经锁住的交叉开关通路立即释放。这个回滚动作必须做干净不然会留下死连接后面所有源节点的请求都会被这个僵尸连接挡住。状态机的输出时序我额外加了一级流水寄存器。交叉开关的配置信号不直接驱动数据通路上的MUX选通而是先落到配置寄存器数据flit在下一拍才真正进入交叉开关。这样牺牲了一拍延迟但保证了配置信号和传输数据之间不会出现竞争这在时钟跑到200MHz以上时会省掉大量时序问题。3.2 交叉开关的实现选择与优化交叉开关是路由器里最影响时序和面积的元件。5x5的交叉开关需要25个交叉点每个交叉点本质上就是一个方向选择MUX。朴素写法是直接在代码里写25个case分支综合器能工作但面积和路径延迟都不理想。我最终用的是“两级MUXOne-Hot选通”的结构。第一级每个输入方向先做目的端口方向选择选出5个候选数据第二级再把5个方向的数据扇出到目标输出端口。同时数据路径上我插了一级转发寄存器把整个交叉开关切成两段前段是输入方向译码和一级MUX后段是扇出寄存器和二级MUX。代价是数据多了1拍流水延迟但关键路径长度降到了原先的60%左右FPGA时序从跑不到160MHz直接提到了200MHz以上。这里有个取舍要讲清楚流水切分牺牲了“零转发延迟”让数据每一跳固定多打一拍对延迟敏感的应用不友好。但换来的收益是整个设计能在更高频率下工作通量反而提升了。工程上不能既要又要得分清哪些指标是系统真正需要的。3.3 参数化可复用设计我最终把这些参数全部做成编译期可配NUM_PORTS路由器端口数默认5最大支持8。DATA_WIDTH数据通路位宽项目里用64bit也测过32bit。ROUTE_MODE路由策略模式0表示XY路由1表示查静态路由表。CONFIG_DEPTH连接配置表深度默认16。ARB_TYPE仲裁策略0表示FIFO1表示固定优先级2表示轮询。参数化带来的直接好处是我在验证阶段写了两个拓扑的测试环境2x2 Mesh和4x4 Mesh跑同一个testbench只需要改参数就能把整个设计回归一遍。这种改动在项目后期几乎天天发生没有参数化每次改动都要人肉改一遍顶层连线非常容易埋雷。4. 验证、实测结果与踩坑记录4.1 验证环境和测试策略验证环境用的SystemVerilog搭的验证策略分三路并行定向测试单条链路建链/传输/释放双链路并发相同方向双链路冲突方向竞争同一输出端口这些场景把协议状态机的每个转移和安全路径都覆盖到。随机测试随机生成源节点、目的节点、数据长度、流间隔每次跑10000个激励连续跑了几小时没出死锁和活锁。断言覆盖在握手信号上加了SVA断言只要valid拉高但ready没过或者建链失败但连接表还是非空都会立刻报错。断言的效果是帮我在早期就抓到了至少3个状态机回滚不干净的问题。定向测试里最有价值的一条是“连线冲突”两个源节点同时向同一个目的节点发起建链请求并且路径在中间某个路由器上争抢同一个输出端口。这个场景一开始总是失败因为简单FIFO仲裁只保证了先到先得但后到的那个请求如果回滚释放不及时会挡住后面所有新请求。最后我加了一个建链超时重试机制RTS发出后如果在8个周期内没等到ACK要么是路径已被占要么是链路拥塞直接回滚并向上游回NACK源节点收到NACK后走指数退避重试。这个机制虽然增加了平均建链时间但把系统从“可能活锁”变成了“必然有界延迟”。4.2 实测性能数据在FPGA上量到的数据如下表所有数字都是实验平台实测值会随位宽、拓扑规模、时钟频率变化但量级上能说明问题指标数值说明单条链路建立延迟8~16 cycles4x4 Mesh最远跳数为4包含握手和回传端到端吞吐64bit/cycle链路建立后每拍一个flit即12.8Gbps 200MHz交叉开关频率上限212MHzFPGA实测映射后最差路径在交叉开关第二级MUX路由器面积等效约28K门不含测试逻辑综合工具估算并发连接数16每路由器连接表深度超出返回NACK对比传统包交换路由器在同样场景下的表现包交换短消息延迟低但数据一旦长起来排队和仲裁会吃掉30%的吞吐。我在同样的FPGA上实现过一个简化包交换路由器做参照长流传输下吞吐只有电路交换方案的70%~80%而且延迟抖动大得多。4.3 常见问题速查表问题现象可能原因解决办法建链请求响应超时数据一直没过来中间节点连接表被僵尸连接占满加看门狗定时器超过周期无数据自动强制释放同一目标连续建链失败概率性死锁简单FIFO仲裁重试导致互相踩踏重试加指数退避仲裁改成固定方向优先级数据传输过程中偶发性丢flit握手时序中valid拉高但ready未到你已把数据弹出统一用AXI-Stream握手规则valid不能依赖ready下降沿时钟上不了200MHz交叉开关组合逻辑过长交叉开关两级MUX之间插流水寄存器释放路径卡住连接表一直非空EOS信号在数据通道里被当作普通数据吞了控制flit和数据flit用额外bit区分不能混用同一通路4.4 调时序踩过的真正深坑这里多聊一个我花了整整两天才定位的时序问题。交叉开关切到两级流水后数据延迟增加了1拍但我最初在释放信号EOS的处理上还是按“与数据同拍”来写的。结果就是数据通路已经进到第二级流水了EOS还在第一级释放逻辑先把连接配置清了剩下的半拍数据直接被打散出现亚稳态和丢数据。最后解决的方法是让EOS信号跟着数据走完全一样的流水级每级都打一拍寄存器并且规定只有到了目的节点的输出端口才允许触发连接表清除。这个“控制信号和数据通路保持严格同流水级”的原则后来在写所有控制逻辑时都被我列成了第一铁律类似的问题再也没有出现。最后分享一点个人体会这块项目做下来我最深的感受是设计一个NoC路由器协议定义比RTL代码重要得多。协议如果定义不清楚写代码的时候会反复返工协议一旦定义清楚了状态机、交叉开关、连接表这些模块写起来其实很快。建链、传输、释放三个阶段每个信号的时序关系都必须提前落到纸面上哪怕只是一个普通握手信号延迟都不知道会在哪一拍突然给你挖坑。另外就是交验并行非常重要我从写第一行RTL就开始搭断言环境后面每加一个功能都可以马上回归最后算下来调试时间反而比纯手推仿真少了很多。如果接下来有人跟着做我建议先跑通一个最小规模的2x2 Mesh再扩到4x4不要一上来就铺一个大矩阵NoC的很多bug是规模变大才会暴露的小规模把基本逻辑打牢后面都是体力活。本文还有配套的精品资源点击获取
返回列表