【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)的核心工程难题。


相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
YUJIANYUE10 小时前
PHP纹路验证码
开发语言·php
剪刀石头布啊10 小时前
jwt介绍
前端