【IC】NoC设计入门 -- 网络接口NI Slave

我们终于来到了"拼图"的最后一块,也是最精妙的一块:设计"收货方",即"Slave NI" (SNI)

(在一些文献中,它也被称为"Target NI"或TNI)。

这是一个了不起的时刻。建造完这个模块后,我们就拥有了端到端 (End-to-End) 发送数据所需的全套"积木"!

1. 🎯 "收货方" (Slave NI) 的挑战

这个模块是master_ni的"镜像"。它的工作是:

  1. 扮演"NoC Slave" :在NoC一侧,它"被动地"接收来自router的Flits。
  2. 扮演"AXI Master":在AXI一侧,它**"主动地"**扮演一个"CPU"(AXI Master),向"下游"的真正"从设备"(如内存控制器)发起一个标准的AXI写操作。

这是它最难的地方: 它必须"接收"一种协议(NoC Flits),然后"重新生成"另一种完全不同的协议(AXI-Master)。

2. 简化与假设(MVP)

为了让这个任务在"教学"上可行,我们必须做出与master_ni (MNI) **"对称"**的简化:

  1. "对称"的包格式 :我们的MNI发送 (1 Head + 4 Body + 1 Tail) 的包。因此,我们的SNI必须能**"理解"**这个6-Flit的包。
  2. "对称"的简化 :我们只实现"写操作"的接收。
  3. 最重要的"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会把它存起来------但这会使状态机复杂得多。)

3. "解耦"是关键

我们的SNI会面临"上游 "(NoC Router)和"下游 "(AXI Memory)的双重"反压" (Backpressure):

  1. NoC router 可能会以"背靠背"的速度(m_noc_ready=1时)连续发来6个Flits。
  2. 但"下游"的AXI memory 可能很"慢"(s_axi_wready=0)。

我们不能 在"接收"Flit的同时"发送"AXI WDATA,因为memory可能会"卡住"我们,导致我们无法接收router发来的下一个Flit,进而导致router的FIFO变满,造成NoC网络拥堵

解决方案: 和MNI一样,我们必须在SNI内部放一个**"解耦FIFO"**。

SNI的核心工作流(两阶段):

  1. 阶段1 (接收): 从NoC"收货",把 Body Flits(货物)全部存入内部的FIFO"缓存区"。
  2. 阶段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(片上系统)**所需的全套"积木":

  1. router.v :"智能十字路口",负责"路由"。
  2. master_ni.v :"发货方"(连接CPU),负责"打包"(AXI -> Flit)。
  3. slave_ni.v :"收货方"(连接Memory),负责"拆包"(Flit -> AXI)。

你已经远远 超越了"理论",你亲手解决了真实世界中"协议转换 "和"时钟域/速率解耦"(通过FIFO)的核心工程难题。


相关推荐
IT_陈寒3 小时前
React性能优化:10个90%开发者不知道的useEffect正确使用姿势
前端·人工智能·后端
赵小川3 小时前
告别“切图仔”?我用一个神器,让Figma设计稿自动生成前端代码!
前端
Apifox3 小时前
如何在 Apifox 中使用 OpenAPI 的 discriminator?
前端·后端·测试
叉歪3 小时前
实现在 UnoCSS 中使用任意深度颜色的预设
前端·css
一 乐3 小时前
二手车销售|汽车销售|基于SprinBoot+vue的二手车交易系统(源码+数据库+文档)
java·前端·数据库·vue.js·后端·汽车
Giant1003 小时前
如果要做优化,CSS提高性能的方法有哪些?
前端
dllxhcjla4 小时前
html初学
前端·javascript·html
只会写Bug的程序员4 小时前
【职业方向】2026小目标,从web开发转型web3开发【一】
前端·web3
LBuffer4 小时前
破解入门学习笔记题二十五
服务器·前端·microsoft