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

我们迎来了第三阶段第四阶段之间最宏伟、最复杂的“桥梁”工程:设计“网络接口” (Network Interface, NI)

在之前的“理论课”中,我们称它为“翻译官”和“物流中心”。现在,我们要亲手来建造它。

我必须在一开始就说明白:这是我们目前为止要建造的、最复杂的单一模块。

  • router (路由器) 很复杂,但它的工作是“固定的”。
  • ni (网络接口) 更复杂,因为它是一个“状态机”的“怪兽”。它必须能“理解”一种完全不同的、高级的“语言”——AXI总线协议——并将其“实时翻译”成我们NoC的“Flit语言”。

鉴于这个任务的极端复杂性,我们不可能一次性造出一个“全功能”的NI。我们将采用和你我一起建造router时相同的策略:

  1. 极大简化:我们将设计一个**“最小可行产品” (MVP)** 版本的NI。
  2. 明确目标:我们只设计**“Master NI”** (也叫 Initiator NI)。
    • “Master NI”:它连接到一个“Master设备”(如CPU)。它在AXI总线一侧扮演“Slave”(接收CPU的命令),在NoC一侧扮演“Master”(向NoC注入Flits)。
  3. 简化协议
    • 我们只实现**“写操作” (Write-Only)**。
    • 我们只实现**“固定长度” (Fixed-Length)** 的“突发”(Burst)。比如,我们规定“所有AXI突发必须是4拍 (4 data beats)”。
    • 我们暂不处理NoC返回的“写响应” (BVALID)。

我们的目标是:证明我们可以接收一个AXI写突发,将其打包成 (1个头 + 4个体 + 1个尾) Flits,然后成功注入到我们的router中。


1. 📦 NI 的“翻译”工作

“物流中心” (Master NI) 的工作流:

  1. 接收订单 (AXI AW/W): CPU(AXI Master)发来一个“写地址” (AWADDR) 和一个“写请求” (AWVALID)。紧接着,它发来“货物” (WDATA) 和“货物有效” (WVALID)。
  2. 翻译地址 (Address Translation): NI 内部有一张“地址映射表”。
    • 它看到地址 0xA000_1000
    • 它查表:“哦,这个地址属于‘内存’,它的‘NoC坐标’在 (3, 3)。”
  3. 打包 (Packetization): NI 开始组装“火车” (Packet)。
    • Head Flit: 目的地 (3, 3),类型 (写),长度 (4)。
    • Body Flits (x4): 4拍的 WDATA 货物。
    • Tail Flit: 标记“结束”。
  4. 发货 (Injection): NI 遵守“Valid/Ready”握手协议,把这 6 个 Flits (1+4+1) 一个接一个地“注入” (Inject) 到它所连接的router的“本地”输入端口。

2. 📐 NI 的“蓝图” (Module Interface)

这个模块是“双面”的。

/*
 * 模块: 最小可行 Master NI (MVP Master Network Interface)
 * 功能: AXI4-Lite (简化版) -> NoC Flits (写操作)
 * 假设: - 只支持写操作
 * - 所有突发(Burst)固定为 4 拍 (DATA_BEATS = 4)
 * - AXI Master 必须先发AW, 再发W
 */
module master_ni #(
    parameter FLIT_WIDTH  = 32,
    parameter COORD_WIDTH = 4,
    parameter DATA_BEATS  = 4  // 我们的“简化”:所有Burst都是4拍
) (
    input clk,
    input rst_n,

    // ---- AXI4-Slave 接口 (来自 CPU) ----
    // -- AW (Address Write) Channel --
    input      [31:0]           s_axi_awaddr,  // CPU 写的地址
    input                       s_axi_awvalid, // CPU 的地址有效
    output reg                  s_axi_awready, // "NI: 地址我收到了"
    
    // -- W (Write Data) Channel --
    input      [FLIT_WIDTH-1:0] s_axi_wdata,   // CPU 写的 32-bit 数据
    input                       s_axi_wlast,   // CPU 说:"这是最后一个数据"
    input                       s_axi_wvalid,  // CPU 的数据有效
    output reg                  s_axi_wready,  // "NI: 数据我收到了"

    // -- B (Write Response) Channel --
    // (在我们的MVP中,我们"假装"总是成功,并立即回复)
    output reg                  s_axi_bvalid,  // "NI: 写入成功!"
    input                       s_axi_bready,  // CPU 收到"成功"

    // ---- NoC Master 接口 (去往 Router) ----
    output reg [FLIT_WIDTH-1:0] m_noc_flit,  // 发给Router的Flit
    output reg                  m_noc_valid, // "NI: 我的Flit有效"
    input                       m_noc_ready  // "Router: 我准备好了"
);

    // TODO: 地址翻译器
    // TODO: 核心状态机
    
endmodule

3. 🧠 核心逻辑:状态机 (FSM)

这是NI的“大脑”。它控制着“翻译”的每一步。

    // ---------------------------------------------
    // 内部寄存器
    // ---------------------------------------------
    reg [COORD_WIDTH-1:0]   dest_x;       // 锁存的目的地X
    reg [COORD_WIDTH-1:0]   dest_y;       // 锁存的目的地Y
    reg [DATA_BEATS-1:0]    data_counter; // 0-3, 计数收了多少WDATA
    
    // 我们用 FIFO 来“缓存”AXI WDATA (这是健壮设计的关键)
    // 实例化一个我们之前造的 FIFO!
    wire [FLIT_WIDTH-1:0] w_fifo_data;
    wire                  w_fifo_empty;
    wire                  w_fifo_full;
    wire                  w_fifo_read_en;
    
    simple_fifo #(
        .FLIT_WIDTH(FLIT_WIDTH),
        .FIFO_DEPTH(DATA_BEATS) // 深度刚好等于Burst长度
    ) w_data_buffer (
        .clk        (clk),
        .rst_n      (rst_n),
        .i_write_en (s_axi_wvalid && s_axi_wready), // 当AXI握手成功时, 写入FIFO
        .i_data     (s_axi_wdata),
        .o_full     (w_fifo_full),
        .i_read_en  (w_fifo_read_en), // 由状态机控制“何时读”
        .o_data     (w_fifo_data),
        .o_empty    (w_fifo_empty)
    );

    // ---------------------------------------------
    // 状态机定义
    // ---------------------------------------------
    localparam S_IDLE        = 3'd0; // 0. 等待AXI命令
    localparam S_RECV_DATA   = 3'd1; // 1. 正在接收 WDATA (填满FIFO)
    localparam S_TRANSLATE   = 3'd2; // 2. (简化) 地址翻译
    localparam S_INJECT_HEAD = 3'd3; // 3. 注入 "Head Flit"
    localparam S_INJECT_BODY = 3'd4; // 4. 注入 "Body Flits" (从FIFO读)
    localparam S_INJECT_TAIL = 3'd5; // 5. 注入 "Tail Flit"
    localparam S_SEND_BRESP  = 3'd6; // 6. (简化) 回复AXI "OK"

    reg [2:0] state, next_state;

    // ---------------------------------------------
    // 地址翻译器 (简化的组合逻辑)
    // ---------------------------------------------
    // 在 "真实" 设计中, 这会是一个复杂的查表逻辑
    always @(*) begin
        if (s_axi_awaddr == 32'hA000_1000) begin // 假设这是"内存A"
            dest_x = 4'd3;
            dest_y = 4'd3;
        end else if (s_axi_awaddr == 32'hB000_1000) begin // 假设这是"内存B"
            dest_x = 4'd1;
            dest_y = 4'd2;
        end else begin // 默认
            dest_x = 4'd0;
            dest_y = 4'd0;
        end
    end

    // ---------------------------------------------
    // 状态机 (时序逻辑)
    // ---------------------------------------------
    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;
        s_axi_awready  = 1'b0;
        s_axi_wready   = 1'b0;
        s_axi_bvalid   = 1'b0;
        m_noc_valid    = 1'b0;
        m_noc_flit     = 0;
        w_fifo_read_en = 1'b0;
        
        // --- 主状态机 ---
        case (state)
            // 0. 等待AXI命令
            S_IDLE: begin
                s_axi_awready = 1'b1; // 总是准备好接收新地址
                if (s_axi_awvalid) begin
                    // (我们在这里"锁存"了地址, 因为 'dest_x'/'dest_y' 是组合逻辑)
                    next_state = S_RECV_DATA; // 下一拍, 去收数据
                    data_counter = 0; // 计数器清零
                end
            end

            // 1. 正在接收 WDATA (填满FIFO)
            S_RECV_DATA: begin
                // "我准备好接收" = "我的数据FIFO还没满"
                s_axi_wready = !w_fifo_full; 
                
                if (s_axi_wvalid && s_axi_wready) begin
                    data_counter = data_counter + 1;
                    if (s_axi_wlast) begin // CPU说"这是最后一个"
                        if (data_counter == DATA_BEATS-1) begin // 且数量也对
                            next_state = S_INJECT_HEAD; // 数据收齐, 开始发包
                        end else begin
                            // 错误!CPU发的长度和我们约定的(4)不符
                            next_state = S_IDLE; // (真实NI会报一个Error)
                        end
                    end
                end
            end
            
            // 3. 注入 "Head Flit"
            S_INJECT_HEAD: begin
                m_noc_valid = 1'b1;
                m_noc_flit[DEST_X_MSB:DEST_X_LSB] = dest_x; // 用上锁存的地址
                m_noc_flit[DEST_Y_MSB:DEST_Y_LSB] = dest_y;
                // m_noc_flit[... 'type' ...] = HEAD; (简化, 暂不实现)
                
                // 握手:等"下游" (Router) 准备好
                if (m_noc_ready) begin
                    next_state = S_INJECT_BODY;
                    data_counter = 0; // 重置计数器, 用于"发送"
                end
            end

            // 4. 注入 "Body Flits" (从FIFO读)
            S_INJECT_BODY: begin
                // "我的Flit有效" = "我的FIFO里有货"
                m_noc_valid = !w_fifo_empty;
                m_noc_flit  = w_fifo_data; // Flit = FIFO的输出
                
                // "读FIFO" = "Flit有效" AND "下游Router准备好了"
                w_fifo_read_en = m_noc_valid && m_noc_ready; 
                
                if (w_fifo_read_en) begin // 成功发送一个Body Flit
                    data_counter = data_counter + 1;
                    if (data_counter == DATA_BEATS - 1) begin // 发完了?
                        next_state = S_INJECT_TAIL;
                    end
                end
            end
            
            // 5. 注入 "Tail Flit"
            S_INJECT_TAIL: begin
                m_noc_valid = 1'b1;
                // m_noc_flit[... 'type' ...] = TAIL; (简化)
                
                if (m_noc_ready) begin
                    // (在我们的MVP中, 突发结束, 立即回复OK)
                    next_state = S_SEND_BRESP; 
                end
            end
            
            // 6. (简化) 回复AXI "OK"
            S_SEND_BRESP: begin
                s_axi_bvalid = 1'b1; // "NI: 写入成功!"
                
                if (s_axi_bready) begin // CPU 确认收到?
                    next_state = S_IDLE; // 完成闭环, 返回空闲
                end
            end

            default: begin
                next_state = S_IDLE;
            end
        endcase
    end

endmodule

🔍 代码解析与“关键点”

  1. “解耦” (Decoupling) 是灵魂

    • 我们在NI内部放了一个 w_data_buffer (FIFO)。这是至关重要的。
    • 为什么? AXI总线 (s_axi_wvalid) 和 NoC (m_noc_ready) 的“吞吐能力”是独立的。
    • 比如,CPU可能一口气(连续4个周期)把4个WDATA全发过来,而我们的NoC可能在INJECT_HEAD时被“下游”的router“反压”m_noc_ready=0)卡住了。
    • 如果没有这个FIFO,WDATA就会丢失。
    • 这个FIFO起到了“削峰填谷”的**“解耦”作用:它让我们能“快速”接收CPU的数据(s_axi_wready = !w_fifo_full),然后再“慢慢地”**按照NoC的节奏发送出去。
  2. 双重反压 (Dual Backpressure)

    • s_axi_wready:它向“上游”的CPU提供了反压。
    • m_noc_valid:它向“下游”的Router提供了数据。
    • S_INJECT_BODY 状态,w_fifo_read_en = m_noc_valid && m_noc_ready 这行代码,完美地把“下游NoC的反压”(m_noc_ready) 传递 给了“上游AXI总线”(s_axi_wready)(因为如果NoC卡住,FIFO会变满,s_axi_wready会变0)。

恭喜!你造出了“物流中心”

虽然这是一个“简化版”的NI,但它已经100%抓住了NI设计的核心精髓状态机、协议翻译、地址解码、数据解耦

你现在拥有了:

  1. router.v:智能十字路口。
  2. master_ni.v:CPU侧的“物流中心”。

我们还缺“拼图”的最后一块。

我们有了“发货方” (master_ni),我们还需要一个“收货方”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值