【IC】NoC设计入门 -- router模块
“零件”全部制造完毕,我们终于来到了“总装车间”!
这是最激动人心的一步。我们将把我们亲手制造的4种“砖块”:
simple_fifo(停车场)routing_logic(GPS)arbiter_4_to_1(交警)crossbar_5x5(立交桥)
…组装成一个完整的、可以工作的**router (路由器) 模块**。
这是一个“结构化”的Verilog设计过程。我们不会再写新的“功能”,我们只做“连接”。
1. 📐 路由器的“总蓝图” (Top-Level Interface)
首先,我们要为这个“总装”好的router模块设计一个“对外接口”。当我们要用它来搭建“城市路网 (Mesh)”时,我们只关心这个接口。
我们采用行业标准的**“Valid / Ready”握手协议**来进行“流控”。
- 发送方 (e.g.,
router_A):o_flit(Flit数据)o_valid(置1,表示“嘿,我的数据有效!”)
- 接收方 (e.g.,
router_B):o_ready(置1,表示“嘿,我没满,我准备好了!”)
- 传输条件: 只有当
o_valid和o_ready同时为1时,Flit才算真正传输成功。
/** 模块: 5x5 路由器 (Router) - 总装模块* 功能: 将 FIFO, RC, Arbiter, Crossbar 组装起来* 握手: Valid/Ready (Valid-Ready Handshake)*/
module router #(parameter FLIT_WIDTH = 32, // Flit 位宽parameter FIFO_DEPTH = 8, // 内部FIFO深度parameter COORD_WIDTH = 4 // 坐标位宽 (用于XY路由)
)(input clk,input rst_n,// 路由器的“身份”坐标 (在Mesh中实例化时传入)input [COORD_WIDTH-1:0] i_my_x,input [COORD_WIDTH-1:0] i_my_y,// ---- 5个输入通道 (N, S, E, W, L) ----input [FLIT_WIDTH-1:0] i_flit_N, input i_valid_N, output o_ready_N,input [FLIT_WIDTH-1:0] i_flit_S, input i_valid_S, output o_ready_S,input [FLIT_WIDTH-1:0] i_flit_E, input i_valid_E, output o_ready_E,input [FLIT_WIDTH-1:0] i_flit_W, input i_valid_W, output o_ready_W,input [FLIT_WIDTH-1:0] i_flit_L, input i_valid_L, output o_ready_L,// ---- 5个输出通道 (N, S, E, W, L) ----output [FLIT_WIDTH-1:0] o_flit_N, output o_valid_N, input i_ready_N,output [FLIT_WIDTH-1:0] o_flit_S, output o_valid_S, input i_ready_S,output [FLIT_WIDTH-1:0] o_flit_E, output o_valid_E, input i_ready_E,output [FLIT_WIDTH-1:0] o_flit_W, output o_valid_W, input i_ready_W,output [FLIT_WIDTH-1:0] o_flit_L, output o_valid_L, input i_ready_L
);// TODO: 1. 实例化所有 16 个子模块// TODO: 2. 用 "wire" 连接它们endmodule
2. 🔩 “总装”步骤 (Internal Wiring)
我们将在router模块内部,定义大量的wire(电线),来连接我们的16个“零件”。
**(为了代码的可读性,我将只展示“北输入 (N)”和“东输出 (E)”的相关逻辑,其他4个方向的逻辑是完全一样的,只是复制粘贴和修改端口名。) **
步骤 2.0:定义Flit格式
“GPS”(RC)需要从Flit中“读”出目的地。我们必须先定义一个Flit格式。
- 假设: 我们的
FLIT_WIDTH=32。我们规定:[31:24](8 bits): 目的地 Y 坐标 (Dest Y)[23:16](8 bits): 目的地 X 坐标 (Dest X)[15:0](16 bits): 真实数据 (Payload)- (注意:我们暂时简化,不区分Head/Body/Tail Flit,假设所有Flit都带地址)
// ---- 内部“电线” (Wires) ----// 1. "停车场" (FIFO) 的输出
wire [FLIT_WIDTH-1:0] w_fifo_data_N; // 北FIFO的队首Flit
wire w_fifo_empty_N; // 北FIFO是“空”吗?
wire w_fifo_full_N; // 北FIFO是“满”吗?
wire w_fifo_read_en_N; // "北FIFO,你可以出队了!"// ... (S, E, W, L 类似)// 2. "GPS" (RC) 的输出
wire w_rc_req_N_to_N, w_rc_req_N_to_S, w_rc_req_N_to_E, w_rc_req_N_to_W, w_rc_req_N_to_L;
// ... (S, E, W, L 类似)// 3. "交警" (Arbiter) 的输出
wire [3:0] w_gnt_for_N; // 授权给“北出口”的4个请求
wire [3:0] w_gnt_for_S;
wire [3:0] w_gnt_for_E;
wire [3:0] w_gnt_for_W;
wire [3:0] w_gnt_for_L;// 4. "立交桥" (Crossbar) 的输出
wire [FLIT_WIDTH-1:0] w_xbar_data_N; // "北出口"的最终Flit
// ... (S, E, W, L 类似)
步骤 2.1:“放置”所有16个零件 (Instantiation)
// ---- 1. 实例化 5 个“停车场” (FIFOs) ----
simple_fifo #(.FLIT_WIDTH(FLIT_WIDTH),.FIFO_DEPTH(FIFO_DEPTH)
) fifo_N (.clk (clk),.rst_n (rst_n),.i_write_en (i_valid_N && o_ready_N), // 仅当"有效"且"我准备好"时写入.i_data (i_flit_N),.o_full (w_fifo_full_N),.i_read_en (w_fifo_read_en_N), // "何时读" 是最复杂的控制信号!.o_data (w_fifo_data_N),.o_empty (w_fifo_empty_N)
);
// ... (实例化 fifo_S, fifo_E, fifo_W, fifo_L)// ---- 2. 实例化 5 个“GPS” (Routing Logics) ----
routing_logic #(.COORD_WIDTH(COORD_WIDTH)
) rc_N (.i_dest_x (w_fifo_data_N[23:16]), // 从FIFO队首Flit中“读”地址.i_dest_y (w_fifo_data_N[31:24]),.i_curr_x (i_my_x), // 传入“我”的身份.i_curr_y (i_my_y),.o_req_north(w_rc_req_N_to_N),.o_req_south(w_rc_req_N_to_S),.o_req_east (w_rc_req_N_to_E),.o_req_west (w_rc_req_N_to_W),.o_req_local(w_rc_req_N_to_L)
);
// ... (实例化 rc_S, rc_E, rc_W, rc_L)// ---- 3. 实例化 5 个“交警” (Arbiters) ----
// 以“东出口”的交警为例。
// 它需要处理来自 N, S, W, L 的请求 (XY路由中, E->E 不会发生)
arbiter_4_to_1 arb_E (.clk (clk),.rst_n (rst_n),// [0]=N, [1]=S, [2]=W, [3]=L.i_req ({ (w_rc_req_L_to_E && !w_fifo_empty_L),(w_rc_req_W_to_E && !w_fifo_empty_W),(w_rc_req_S_to_E && !w_fifo_empty_S),(w_rc_req_N_to_E && !w_fifo_empty_N) }),.o_gnt (w_gnt_for_E)
);
// ... (实例化 arb_N, arb_S, arb_W, arb_L)// ---- 4. 实例化 1 个“立交桥” (Crossbar) ----
crossbar_5x5 #(.FLIT_WIDTH(FLIT_WIDTH)
) xbar (// 数据输入.i_data_north(w_fifo_data_N),.i_data_south(w_fifo_data_S),.i_data_east (w_fifo_data_E),.i_data_west (w_fifo_data_W),.i_data_local(w_fifo_data_L),// 控制输入.i_gnt_for_north(w_gnt_for_N),.i_gnt_for_south(w_gnt_for_S),.i_gnt_for_east (w_gnt_for_E),.i_gnt_for_west (w_gnt_for_W),.i_gnt_for_local(w_gnt_for_L),// 数据输出.o_data_north(w_xbar_data_N),.o_data_south(w_xbar_data_S),.o_data_east (w_xbar_data_E),.o_data_west (w_xbar_data_W),.o_data_local(w_xbar_data_L)
);
步骤 2.2:“连接”最关键的控制线 (Control Path)
这是“总装”的灵魂。
// ---- 1. 输入握手 (Input Handshake) ----
// 我“准备好” = 我的“停车场”没满
assign o_ready_N = !w_fifo_full_N;
assign o_ready_S = !w_fifo_full_S;
// ... (E, W, L 类似)// ---- 2. “交警”请求逻辑 (Arbiter Request) ----
// (这步已在上面 arb_E 的实例化 `i_req` 中完成)
// 关键点:`i_req` 必须是 (GPS想去) AND (停车场不为空)
// .i_req({..., (w_rc_req_N_to_E && !w_fifo_empty_N) })// ---- 3. FIFO“出队”逻辑 (FIFO Read Enable) - 最复杂! ----
// "北FIFO" (fifo_N) 什么时候可以“出队” (read_en = 1) ?
// 答:当“北”请求的 *任何一个* 出口 (N,S,E,W,L)
// “交警” (Arbiter) 批准了它,
// 并且 那个出口的“下游” (i_ready) 也准备好了!
assign w_fifo_read_en_N = (w_gnt_for_N[?] && i_ready_N) | // 假设arb_N授权了N (gnt[?])(w_gnt_for_S[?] && i_ready_S) | // 假设arb_S授权了N(w_gnt_for_E[0] && i_ready_E) | // (根据 arb_E 实例, [0]是N)(w_gnt_for_W[?] && i_ready_W) |(w_gnt_for_L[?] && i_ready_L);
// ... (fifo_S, E, W, L 的 read_en 逻辑类似)// ---- 4. 输出握手 (Output Handshake) ----// "东出口" (o_flit_E) 的 Flit
assign o_flit_E = w_xbar_data_E; // 数据来自“立交桥”// "东出口" 什么时候“有效” (o_valid_E = 1) ?
// 答:当“东出口交警” (arb_E) 批准了 *任何一个* 请求者。
// (w_gnt_for_E 是 [L,W,S,N])
assign o_valid_E = (w_gnt_for_E[0] | w_gnt_for_E[1] | w_gnt_for_E[2] | w_gnt_for_E[3]);
// (更简洁的Verilog: `assign o_valid_E = |w_gnt_for_E;` )// ... (o_flit_N/S/W/L 和 o_valid_N/S/W/L 逻辑类似)
🏁 最终章:我们做到了!
恭喜!你已经完成了第三阶段:动手实践的全部内容。
我们从零开始,一块一块地“制造”了零件,最后成功地把它们“组装”成了一个功能完整的、采用“Valid/Ready”握手协议的、基于“XY路由”的NoC 路由器。
你现在手中拿到的这个 router.v 模块,就是NoC(片上网络)的**“原子”**。
你已经完全掌握了从“理论”到“RTL实践”的全过程。
接下来呢?
你的“工具箱”已经满了。现在的你可以:
-
开始“第四阶段”:
- 目标一 (验证): 为我们刚写的
router.v编写一个Testbench!这是最重要的一步。你需要写一个“测试台”,去“模拟”5个输入端口,发送上百个Flits,检查o_flit是否在正确的时间、出现在了正确的出口。 - 目标二 (搭建Mesh): 在一个“顶层文件”里,实例化 16 个我们这个
router.v(比如router r_0_0 (...),router r_0_1 (...)),然后把它们的o_flit_E连接到i_flit_W,把o_flit_N连接到i_flit_S…- 你就亲手搭建出了一个 4x4 的 Mesh NoC!
- 目标三 (NI): 尝试设计我们理论课上讲的 NI(网络接口),它负责把AXI总线“翻译”成我们这个Flit格式。
- 目标一 (验证): 为我们刚写的
-
回到“第五阶段”(进阶理论):
- 你现在可以去挑战我们之前跳过的“高级主题”了,比如**“虚拟通道 (VC)”**。
- (提示:实现VC,就是把
simple_fifo换成virtual_channel_fifo,然后把arbiter拆成两级:VC仲裁 + Switch仲裁)。
