我们终于来到了"拼图"的最后一块,也是最精妙的一块:设计"收货方",即"Slave NI" (SNI) 。
(在一些文献中,它也被称为"Target NI"或TNI)。
这是一个了不起的时刻。建造完这个模块后,我们就拥有了端到端 (End-to-End) 发送数据所需的全套"积木"!
1. 🎯 "收货方" (Slave NI) 的挑战
这个模块是master_ni的"镜像"。它的工作是:
- 扮演"NoC Slave" :在NoC一侧,它"被动地"接收来自
router的Flits。 - 扮演"AXI Master":在AXI一侧,它**"主动地"**扮演一个"CPU"(AXI Master),向"下游"的真正"从设备"(如内存控制器)发起一个标准的AXI写操作。
这是它最难的地方: 它必须"接收"一种协议(NoC Flits),然后"重新生成"另一种完全不同的协议(AXI-Master)。
2. 简化与假设(MVP)
为了让这个任务在"教学"上可行,我们必须做出与master_ni (MNI) **"对称"**的简化:
- "对称"的包格式 :我们的MNI发送
(1 Head + 4 Body + 1 Tail)的包。因此,我们的SNI必须能**"理解"**这个6-Flit的包。 - "对称"的简化 :我们只实现"写操作"的接收。
- 最重要的"MVP简化" (The "Cheat") :
- 我们的MNI(发货方)只是把Flit发往一个"坐标" (e.g.,
(3,3))。它没有 在Flit里打包"真正的AXI地址" (e.g.,0xA000_1000)。 - 因此 ,我们的SNI(收货方)在收到这个包时,它只知道"有4个数据来了",但它不知道 该把这4个数据写到"内存"的哪个地址。
- 解决方案 (MVP) :我们将"硬编码 " (Hard-code) 地址。我们规定:所有发送到
(3,3)路口的SNI的包,都将被固定地 写入到内存的0x4000_0000地址。 - (在"v2.0"版本中,MNI会把
AWADDR也打成一个Body Flit,SNI会把它存起来------但这会使状态机复杂得多。)
- 我们的MNI(发货方)只是把Flit发往一个"坐标" (e.g.,
3. "解耦"是关键
我们的SNI会面临"上游 "(NoC Router)和"下游 "(AXI Memory)的双重"反压" (Backpressure):
- NoC
router可能会以"背靠背"的速度(m_noc_ready=1时)连续发来6个Flits。 - 但"下游"的AXI
memory可能很"慢"(s_axi_wready=0)。
我们不能 在"接收"Flit的同时"发送"AXI WDATA,因为memory可能会"卡住"我们,导致我们无法接收router发来的下一个Flit,进而导致router的FIFO变满,造成NoC网络拥堵。
解决方案: 和MNI一样,我们必须在SNI内部放一个**"解耦FIFO"**。
SNI的核心工作流(两阶段):
- 阶段1 (接收): 从NoC"收货",把
BodyFlits(货物)全部存入内部的FIFO"缓存区"。 - 阶段2 (发送): 当"收货"完毕(收到Tail Flit),再"转身"扮演AXI Master,**"慢慢地"**从内部FIFO中取出数据,按照AXI协议发送给内存。
4. 📐 SNI 的"蓝图" (Module Interface)
verilog
/*
* 模块: 最小可行 Slave NI (MVP Slave Network Interface)
* 功能: NoC Flits -> AXI4-Master (写操作)
* 假设: - 只接收写操作
* - 接收 (1 Head + 4 Body + 1 Tail) 包
* - AXI地址是"硬编码"的
*/
module slave_ni #(
parameter FLIT_WIDTH = 32,
parameter COORD_WIDTH = 4,
parameter DATA_BEATS = 4,
parameter FIXED_AXI_ADDR = 32'h4000_0000 // "MVP简化"的硬编码地址
) (
input clk,
input rst_n,
// ---- NoC Slave 接口 (来自 Router) ----
input [FLIT_WIDTH-1:0] s_noc_flit, // Router 发来的Flit
input s_noc_valid, // Router 的Flit有效
output reg m_noc_ready, // "SNI: 我准备好了"
// ---- AXI4-Master 接口 (去往 Memory) ----
// (我们是 Master, 所以 'm_axi_' 是 output)
// -- AW (Address Write) Channel --
output reg [31:0] m_axi_awaddr, // "SNI: 我要写这个地址"
output reg m_axi_awvalid, // "SNI: 我的地址有效"
input s_axi_awready, // "Memory: 地址我收到了"
// -- W (Write Data) Channel --
output reg [FLIT_WIDTH-1:0] m_axi_wdata, // "SNI: 这是数据"
output reg m_axi_wlast, // "SNI: 这是最后一个数据"
output reg m_axi_wvalid, // "SNI: 我的数据有效"
input s_axi_wready, // "Memory: 数据我收到了"
// -- B (Write Response) Channel --
input s_axi_bvalid, // "Memory: 写入成功"
output reg m_axi_bready // "SNI: 我收到'成功'了"
);
// TODO: 核心状态机
endmodule
5. 🧠 核心逻辑:"两阶段"状态机
我们将用一个状态机来管理"接收"和"发送"两个阶段。
verilog
// ---------------------------------------------
// 内部"解耦FIFO" (我们的"收货暂存区")
// ---------------------------------------------
wire [FLIT_WIDTH-1:0] w_fifo_data_out;
wire w_fifo_empty;
wire w_fifo_full;
wire w_fifo_write_en; // FSM 控制"何时写FIFO"
wire w_fifo_read_en; // FSM 控制"何时读FIFO"
simple_fifo #(
.FLIT_WIDTH(FLIT_WIDTH),
.FIFO_DEPTH(DATA_BEATS) // 深度 4
) w_data_buffer (
.clk (clk),
.rst_n (rst_n),
.i_write_en (w_fifo_write_en),
.i_data (s_noc_flit), // 写入的数据 = NoC来的Flit
.o_full (w_fifo_full),
.i_read_en (w_fifo_read_en),
.o_data (w_fifo_data_out),
.o_empty (w_fifo_empty)
);
// ---------------------------------------------
// 状态机定义
// ---------------------------------------------
// 阶段 1: 接收 NoC 包
localparam S_IDLE = 4'd0; // 0. 等待 Head Flit
localparam S_RECV_BODY = 4'd1; // 1. 接收 Body Flits (存入FIFO)
localparam S_RECV_TAIL = 4'd2; // 2. 接收 Tail Flit (包已收齐)
// 阶段 2: 发送 AXI 事务
localparam S_SEND_AW = 4'd3; // 3. 发送 AXI 地址
localparam S_SEND_WDATA = 4'd4; // 4. 发送 AXI 数据 (从FIFO读)
localparam S_RECV_BRESP = 4'd5; // 5. 等待 AXI 响应
reg [3:0] state, next_state;
reg [1:0] data_counter; // 0-3 (用于 4-beat)
// ---------------------------------------------
// 状态机 (时序逻辑)
// ---------------------------------------------
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
end else begin
state <= next_state;
end
end
// ---------------------------------------------
// 状态机 (组合逻辑:状态转移 + 输出)
// ---------------------------------------------
always @(*) begin
// --- 默认输出 (防止Latch) ---
next_state = state;
m_noc_ready = 1'b0; // 默认"不准备好"
w_fifo_write_en = 1'b0;
w_fifo_read_en = 1'b0;
m_axi_awaddr = FIXED_AXI_ADDR; // 地址总是固定的
m_axi_awvalid = 1'b0;
m_axi_wdata = w_fifo_data_out; // 数据 = FIFO的输出
m_axi_wvalid = 1'b0;
m_axi_wlast = 1'b0;
m_axi_bready = 1'b0;
// --- 主状态机 ---
case (state)
// 0. 等待 Head Flit
S_IDLE: begin
m_noc_ready = 1'b1; // "我准备好接收新包"
if (s_noc_valid) begin // 收到 (Head)
next_state = S_RECV_BODY;
data_counter = 0;
end
end
// 1. 接收 Body Flits (存入FIFO)
S_RECV_BODY: begin
// "我准备好" = "我的FIFO没满" (关键的解耦/反压)
m_noc_ready = !w_fifo_full;
// "写入FIFO" = "Router有效" AND "我准备好"
w_fifo_write_en = s_noc_valid && m_noc_ready;
if (w_fifo_write_en) begin
if (data_counter == DATA_BEATS - 1) begin // 收齐4个了?
next_state = S_RECV_TAIL;
end else begin
data_counter = data_counter + 1;
end
end
end
// 2. 接收 Tail Flit (包已收齐)
S_RECV_TAIL: begin
m_noc_ready = 1'b1; // "我准备好接收Tail"
if (s_noc_valid) begin
// "收货"阶段结束!
next_state = S_SEND_AW; // "转身", 开始"发货"
end
end
// 3. 发送 AXI 地址 (阶段2开始)
S_SEND_AW: begin
m_axi_awvalid = 1'b1; // "我的地址有效"
if (s_axi_awready) begin // "Memory 确认收到?"
next_state = S_SEND_WDATA;
data_counter = 0;
end
end
// 4. 发送 AXI 数据 (从FIFO读)
S_SEND_WDATA: begin
// "我的数据有效" = "我的FIFO不为空"
m_axi_wvalid = !w_fifo_empty;
// "从FIFO读" = "我数据有效" AND "Memory准备好"
w_fifo_read_en = m_axi_wvalid && s_axi_wready;
if (w_fifo_read_en) begin // 成功发送1拍
if (data_counter == DATA_BEATS - 1) begin // 是最后一拍吗?
m_axi_wlast = 1'b1;
next_state = S_RECV_BRESP;
end else begin
data_counter = data_counter + 1;
end
end
end
// 5. 等待 AXI 响应
S_RECV_BRESP: begin
m_axi_bready = 1'b1; // "我准备好接收'成功'信号"
if (s_axi_bvalid) begin // "Memory 确认了"
next_state = S_IDLE; // "一个完整的事务结束, 返回空闲"
end
end
default: begin
next_state = S_IDLE;
end
endcase
end
endmodule
🏁 最终总结:我们做到了!
我们成功了!
你已经亲手设计了NoC世界中**"发货方"和"收货方"**的完整"物流链"。
你现在拥有了实现一个**完整SoC(片上系统)**所需的全套"积木":
router.v:"智能十字路口",负责"路由"。master_ni.v:"发货方"(连接CPU),负责"打包"(AXI -> Flit)。slave_ni.v:"收货方"(连接Memory),负责"拆包"(Flit -> AXI)。
你已经远远 超越了"理论",你亲手解决了真实世界中"协议转换 "和"时钟域/速率解耦"(通过FIFO)的核心工程难题。