手把手教你用Verilog实现一个简单的NoC路由器(含虚拟通道与仲裁器代码)

张开发
2026/6/16 17:56:08 15 分钟阅读
手把手教你用Verilog实现一个简单的NoC路由器(含虚拟通道与仲裁器代码)
手把手教你用Verilog实现一个简单的NoC路由器含虚拟通道与仲裁器代码在当今多核处理器和系统级芯片(SoC)设计中片上网络(NoC)已成为解决核间通信瓶颈的关键技术。与传统的总线架构相比NoC通过分布式路由和交换机制提供了更高的带宽和更低的延迟。而作为NoC的核心组件路由器的设计质量直接决定了整个网络的性能表现。本文将带领读者从零开始用Verilog硬件描述语言实现一个支持虚拟通道的简化路由器模块。这个实践项目特别适合正在学习数字IC设计或对NoC实现感兴趣的工程师和学生。通过动手编码你将深入理解路由器微架构中的关键组件输入缓冲区管理、虚拟通道机制、仲裁器逻辑以及交叉开关设计。1. 路由器基础架构设计我们的路由器采用典型的五端口结构东、南、西、北四个方向端口和一个本地处理器端口支持虚拟通道机制来缓解队头阻塞问题。下面是顶层模块的定义module noc_router ( input wire clk, input wire reset, // 五个端口的输入输出接口 input wire [DATA_WIDTH-1:0] north_in, south_in, east_in, west_in, local_in, output wire [DATA_WIDTH-1:0] north_out, south_out, east_out, west_out, local_out, // 信用控制信号 input wire [VC_NUM-1:0] credit_in [0:PORT_NUM-1], output wire [VC_NUM-1:0] credit_out [0:PORT_NUM-1] ); parameter DATA_WIDTH 64; // 数据位宽 parameter FLIT_WIDTH 16; // 每个flit的位宽 parameter PORT_NUM 5; // 端口数量 parameter VC_NUM 4; // 每个端口的虚拟通道数 parameter BUFFER_DEPTH 8; // 每个VC的缓冲区深度 // 路由器内部主要组件 wire [FLIT_WIDTH-1:0] crossbar_input [0:PORT_NUM-1]; wire [FLIT_WIDTH-1:0] crossbar_output [0:PORT_NUM-1]; wire [PORT_NUM-1:0] switch_alloc_grant [0:PORT_NUM-1]; wire [VC_NUM-1:0] vc_alloc_grant [0:PORT_NUM-1]; // 实例化各功能模块 input_buffer input_buffers [0:PORT_NUM-1] (/* 端口连接 */); virtual_channel_allocator vc_alloc (/* 端口连接 */); switch_allocator sw_alloc (/* 端口连接 */); crossbar crossbar_switch (/* 端口连接 */); endmodule路由器的工作流程可以分为以下几个关键阶段输入缓冲到达的flit根据其虚拟通道ID被存入对应的缓冲区队列路由计算头flit确定目标输出端口本文使用简单的XY路由算法虚拟通道分配为数据包分配下游路由器的空闲VC交叉开关分配仲裁决定哪些flit可以进入交叉开关交叉开关传输将flit从输入端口传送到输出端口提示在实际设计中这些阶段通常被流水线化以提高时钟频率。但为简化实现我们的示例采用单周期完成所有步骤。2. 虚拟通道与输入缓冲区实现虚拟通道机制通过在物理链路上复用多个逻辑通道有效解决了队头阻塞问题。每个虚拟通道都有独立的缓冲区队列和状态机。下面是输入缓冲区模块的关键实现module input_buffer ( input wire clk, input wire reset, input wire [FLIT_WIDTH-1:0] flit_in, input wire flit_in_valid, output wire [FLIT_WIDTH-1:0] flit_out [0:VC_NUM-1], output wire [VC_NUM-1:0] flit_out_valid, // 与VC分配器和开关分配器的接口 input wire [VC_NUM-1:0] vc_alloc_grant, input wire switch_alloc_grant, output wire [VC_NUM-1:0] vc_request ); // 每个VC的缓冲区实现 reg [FLIT_WIDTH-1:0] buffer [0:VC_NUM-1][0:BUFFER_DEPTH-1]; reg [VC_NUM-1:0] vc_state; // 每个VC的状态IDLE, ROUTING, ACTIVE等 reg [2:0] head_ptr [0:VC_NUM-1]; // 每个缓冲区的头指针 reg [2:0] tail_ptr [0:VC_NUM-1]; // 每个缓冲区的尾指针 // 状态机参数 localparam IDLE 0; localparam ROUTING 1; localparam ACTIVE 2; always (posedge clk or posedge reset) begin if (reset) begin // 初始化所有缓冲区和状态 end else begin // 处理输入flit if (flit_in_valid) begin // 根据flit头中的VC ID存入对应缓冲区 buffer[vc_id][tail_ptr[vc_id]] flit_in; tail_ptr[vc_id] tail_ptr[vc_id] 1; // 头flit触发路由计算 if (flit_in[15]) begin // 头flit标志位 vc_state[vc_id] ROUTING; end end // 处理VC分配和开关分配 for (int i0; iVC_NUM; i) begin if (vc_alloc_grant[i] vc_state[i] ROUTING) begin vc_state[i] ACTIVE; end if (switch_alloc_grant vc_state[i] ACTIVE) begin // 从缓冲区取出flit输出 flit_out[i] buffer[i][head_ptr[i]]; flit_out_valid[i] 1b1; head_ptr[i] head_ptr[i] 1; // 如果是尾flit释放VC if (buffer[i][head_ptr[i]][14]) begin vc_state[i] IDLE; end end end end end endmodule虚拟通道的状态管理是设计中的关键点。每个VC通常有以下几种状态状态描述触发转换的条件IDLE空闲状态等待新数据包收到头flitROUTING正在计算路由完成VC分配ACTIVE可以参与开关分配发送尾flit缓冲区组织方式对性能有重要影响。我们的实现采用固定长度的独立队列设计每个VC有8个flit深度的缓冲区。这种设计简单直接但存在以下优化空间共享缓冲区池所有VC共享一个大缓冲区提高存储利用率动态VC分配根据流量模式动态调整各VC的缓冲区深度优先级VC为特定类型流量如控制报文保留高优先级VC3. 仲裁器设计与实现仲裁器负责在多个竞争请求中做出公平高效的决策。我们实现两种常用仲裁器Round-Robin和矩阵仲裁器。3.1 Round-Robin仲裁器Round-Robin是最常用的公平仲裁算法它确保每个请求者都能轮流获得服务机会。下面是Verilog实现module round_robin_arbiter #( parameter REQ_NUM 4 )( input wire clk, input wire reset, input wire [REQ_NUM-1:0] request, output wire [REQ_NUM-1:0] grant ); reg [REQ_NUM-1:0] priority; // 优先级寄存器 wire [REQ_NUM-1:0] masked_request; wire [REQ_NUM-1:0] pre_grant; // 生成掩码请求 assign masked_request request priority; // 优先级编码器 always_comb begin pre_grant {REQ_NUM{1b0}}; for (int i0; iREQ_NUM; i) begin if (masked_request[i]) begin pre_grant[i] 1b1; break; end end // 如果没有掩码请求检查原始请求 if (pre_grant 0) begin for (int i0; iREQ_NUM; i) begin if (request[i]) begin pre_grant[i] 1b1; break; end end end end assign grant pre_grant; // 更新优先级 always (posedge clk or posedge reset) begin if (reset) begin priority {1b1, {(REQ_NUM-1){1b0}}}; // 初始最高优先级给请求0 end else if (|request) begin // 如果有任何请求 // 将优先级循环左移 priority {grant[REQ_NUM-2:0], grant[REQ_NUM-1]}; end end endmoduleRound-Robin仲裁器的工作过程可以用以下步骤描述根据当前优先级寄存器生成掩码请求优先级编码器选择最高优先级的有效请求如果没有掩码请求则选择第一个原始请求时钟上升沿时将优先级循环左移使刚获得服务的请求变为最低优先级3.2 矩阵仲裁器矩阵仲裁器通过记录服务历史来实现更复杂的仲裁策略。下面是简化实现module matrix_arbiter #( parameter REQ_NUM 4 )( input wire clk, input wire reset, input wire [REQ_NUM-1:0] request, output wire [REQ_NUM-1:0] grant ); // 优先级矩阵 reg [REQ_NUM-1:0] priority [0:REQ_NUM-1]; wire [REQ_NUM-1:0] masked_request [0:REQ_NUM-1]; wire [REQ_NUM-1:0] pre_grant; // 初始化优先级矩阵 initial begin for (int i0; iREQ_NUM; i) begin for (int j0; jREQ_NUM; j) begin priority[i][j] (i j) ? 1b1 : 1b0; end end end // 生成掩码请求 always_comb begin for (int i0; iREQ_NUM; i) begin masked_request[i] request priority[i]; end end // 仲裁逻辑 always_comb begin pre_grant {REQ_NUM{1b0}}; for (int i0; iREQ_NUM; i) begin if (|masked_request[i]) begin for (int j0; jREQ_NUM; j) begin if (masked_request[i][j]) begin pre_grant[j] 1b1; break; end end break; end end end assign grant pre_grant; // 更新优先级矩阵 always (posedge clk or posedge reset) begin if (reset) begin // 重置矩阵 end else if (|request) begin for (int i0; iREQ_NUM; i) begin if (grant[i]) begin // 清除第i行 priority[i] {REQ_NUM{1b0}}; // 设置第i列 for (int j0; jREQ_NUM; j) begin priority[j][i] 1b1; end end end end end endmodule矩阵仲裁器相比Round-Robin有更高的实现复杂度但能提供更灵活的服务质量保证。两种仲裁器的性能对比如下特性Round-Robin矩阵仲裁器公平性严格公平基于历史服务记录实现复杂度低高适用场景均匀流量非均匀流量硬件开销小大延迟低中等4. 交叉开关设计与优化交叉开关是路由器的数据通路核心负责将flit从输入端口传送到输出端口。我们实现一个基于多路复用器的5x5交叉开关module crossbar #( parameter DATA_WIDTH 16, parameter PORT_NUM 5 )( input wire [DATA_WIDTH-1:0] data_in [0:PORT_NUM-1], input wire [PORT_NUM-1:0] sel [0:PORT_NUM-1], // 每个输出端口的选择信号 output wire [DATA_WIDTH-1:0] data_out [0:PORT_NUM-1] ); // 每个输出端口的多路复用器 genvar i; generate for (i0; iPORT_NUM; i) begin : output_mux reg [DATA_WIDTH-1:0] mux_out; integer j; always (*) begin mux_out {DATA_WIDTH{1b0}}; for (j0; jPORT_NUM; j) begin if (sel[i][j]) begin mux_out data_in[j]; end end end assign data_out[i] mux_out; end endgenerate endmodule这个基本实现可以工作但在实际芯片设计中需要考虑以下优化方向流水线设计将交叉开关分成多个阶段以提高时钟频率低功耗技术使用门控时钟和电源门控减少静态功耗布局优化考虑物理实现的布线拥塞和时序收敛加速技术通过增加内部带宽缓解仲裁压力一个实用的优化技巧是维度切分将单个5x5交叉开关分解为两个3x3交叉开关module sliced_crossbar ( // 端口定义 ); // X维度交叉开关 (处理东-西向流量) crossbar #(.PORT_NUM(3)) xbar_x ( .data_in({north_in, south_in, local_in}), .sel(x_sel), .data_out(x_out) ); // Y维度交叉开关 (处理南-北向流量) crossbar #(.PORT_NUM(3)) xbar_y ( .data_in({east_in, west_in, local_in}), .sel(y_sel), .data_out(y_out) ); // 连接两个交叉开关的输出 assign north_out x_out[0]; assign south_out x_out[1]; assign east_out y_out[0]; assign west_out y_out[1]; assign local_out x_out[2] | y_out[2]; // 合并本地端口输出 endmodule这种切分技术特别适合使用XY维度顺序路由的Mesh网络因为大部分流量只需要通过其中一个交叉开关从而降低了功耗和面积开销。5. 完整路由器集成与验证将各个组件集成到完整路由器后我们需要进行全面的功能验证。下面是推荐的验证策略单元测试对每个模块单独测试缓冲区管理的flit存储和检索仲裁器的公平性和正确性交叉开关的连接功能集成测试验证路由器整体功能单flit传输多flit数据包传输虚拟通道间的隔离性信用机制的流量控制性能测试评估路由器的吞吐量和延迟饱和吞吐量测试不同流量模式下的延迟分布队头阻塞缓解效果下面是一个简单的测试用例验证路由器的基本功能module router_tb; // 测试平台信号 reg clk, reset; wire [15:0] north_in, south_in, east_in, west_in, local_in; wire [15:0] north_out, south_out, east_out, west_out, local_out; // 实例化被测路由器 noc_router dut ( .clk(clk), .reset(reset), .north_in(north_in), .south_in(south_in), .east_in(east_in), .west_in(west_in), .local_in(local_in), .north_out(north_out), .south_out(south_out), .east_out(east_out), .west_out(west_out), .local_out(local_out) ); // 时钟生成 initial begin clk 0; forever #5 clk ~clk; end // 测试用例 initial begin // 复位 reset 1; north_in 0; south_in 0; east_in 0; west_in 0; local_in 0; #20 reset 0; // 测试1本地端口到北向端口的单flit传输 #10; local_in 16h8001; // 头flitVC0目标北向 #10; local_in 16h0000; // 体flit #10; local_in 16h4000; // 尾flit // 检查北向输出 #20; if (north_out ! 16h8001) $display(Test 1 failed!); // 测试2虚拟通道隔离 // ...更多测试用例... #100 $finish; end endmodule在实际项目中建议使用SystemVerilog和UVM等高级验证方法学来构建更全面的验证环境。特别是对于NoC路由器这种复杂设计需要覆盖以下典型场景边界条件缓冲区满、信用耗尽等极端情况并发冲突多个端口同时竞争相同输出端口错误恢复flit丢失或损坏时的行为性能瓶颈识别关键路径和吞吐量限制通过这个实践项目我们完整实现了一个支持虚拟通道的NoC路由器涵盖了从架构设计到RTL实现的关键环节。虽然这是一个简化版本但它包含了现代路由器设计的核心概念和技术。

更多文章