zynq中axi_dma的三个实验-第1课:PL搭建

目录

BD

axidma_three_step_system.tcl_xc7z020.tcl

axidma_three_step_system.tcl_xc7z010.tcl

AXI 地址映射表

模块 接口 Slave Interface Address Segment Base Address Range High Address
axi_dma_0 Data_MM2S S_AXI_HP0 HP0_DDR_LOWOCM 0x0000_0000 1G 0x3FFF_FFFF
axi_dma_0 Data_S2MM S_AXI_HP0 HP0_DDR_LOWOCM 0x0000_0000 1G 0x3FFF_FFFF
processing_system7_0 axi_dma_0/S_AXI_LITE S_AXI_LITE Reg 0x4040_0000 64K 0x4040_FFFF
processing_system7_0 top_0/s_axi s_axi reg0 0x43C0_0000 4K 0x43C0_0FFF

PIN.xdc

xc7z020

bash 复制代码
## IIC
set_property -dict {PACKAGE_PIN T11 IOSTANDARD LVCMOS33} [get_ports IIC_EMIO_scl_io]
set_property -dict {PACKAGE_PIN V5 IOSTANDARD LVCMOS33} [get_ports IIC_EMIO_sda_io]


## GPIO_EMIO
set_property -dict {PACKAGE_PIN U5 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[0]]
set_property -dict {PACKAGE_PIN V7 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[1]]
set_property -dict {PACKAGE_PIN W8 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[2]]
set_property -dict {PACKAGE_PIN U9 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[3]]
set_property -dict {PACKAGE_PIN U10 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[4]]
set_property -dict {PACKAGE_PIN W6 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[5]]
set_property -dict {PACKAGE_PIN Y7 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[6]]
set_property -dict {PACKAGE_PIN Y9 IOSTANDARD LVCMOS33} [get_ports GPIO_EMIO_tri_io[7]]


# 中断和脉冲产生
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports O_LED]
set_property -dict {PACKAGE_PIN J14 IOSTANDARD LVCMOS33} [get_ports o_debug]

top.v

verilog 复制代码
`timescale 1ns/1ps
module top #(
    // AXI-Lite 地址宽度,常见 FPGA SoC 系统中一般为 32 位。
    parameter P_S_AXI_ADDR_WIDTH = 32,

    // AXI-Lite 数据宽度,本项目使用 32 位寄存器访问。
    parameter P_S_AXI_DATA_WIDTH = 32,

    // AXI-Lite 可访问的寄存器窗口大小,单位是字节。
    // 1024 字节可覆盖 slot0 的 129 个 32 位寄存器和后续 slot。
    parameter P_S_MEM_SIZE       = 1024,

    // 内部 SMMR 字节地址宽度。
    // 10 位地址可以覆盖 1024 字节空间。
    parameter P_REG_ADDR_WIDTH   = 10,

    // 系统时钟频率。
    // 当前顶层主要把 AXI 时钟传给各级模块,此参数预留给需要时序计算的模块使用。
    parameter P_CLK_FREQ         = 50_000_000
)(
    // AXI-Lite 全局时钟和低有效复位。
    input  wire                              s_axi_aclk,
    input  wire                              s_axi_aresetn,

    // AXI-Lite 写地址通道。
    input  wire [P_S_AXI_ADDR_WIDTH-1:0]     s_axi_awaddr,
    input  wire                              s_axi_awvalid,
    output wire                              s_axi_awready,

    // AXI-Lite 写数据通道。
    input  wire [P_S_AXI_DATA_WIDTH-1:0]     s_axi_wdata,
    input  wire [(P_S_AXI_DATA_WIDTH/8)-1:0] s_axi_wstrb,
    input  wire                              s_axi_wvalid,
    output wire                              s_axi_wready,

    // AXI-Lite 写响应通道。
    output wire [1:0]                        s_axi_bresp,
    output wire                              s_axi_bvalid,
    input  wire                              s_axi_bready,

    // AXI-Lite 读地址通道。
    input  wire [P_S_AXI_ADDR_WIDTH-1:0]     s_axi_araddr,
    input  wire                              s_axi_arvalid,
    output wire                              s_axi_arready,

    // AXI-Lite 读数据通道。
    output wire [P_S_AXI_DATA_WIDTH-1:0]     s_axi_rdata,
    output wire [1:0]                        s_axi_rresp,
    output wire                              s_axi_rvalid,
    input  wire                              s_axi_rready,

    // slot0 AXI-Stream 数据输入,接 AXI DMA MM2S。
    input  wire [P_S_AXI_DATA_WIDTH-1:0]     s_axis_tdata,
    input  wire                              s_axis_tvalid,
    output wire                              s_axis_tready,
    input  wire                              s_axis_tlast,
    input  wire [(P_S_AXI_DATA_WIDTH/8)-1:0] s_axis_tkeep,

    // slot0 AXI-Stream 数据输出,接 AXI DMA S2MM 或 AXI-Stream FIFO。
    output wire [P_S_AXI_DATA_WIDTH-1:0]     m_axis_tdata,
    output wire                              m_axis_tvalid,
    input  wire                              m_axis_tready,
    output wire                              m_axis_tlast,
    output wire [(P_S_AXI_DATA_WIDTH/8)-1:0] m_axis_tkeep,


    // 示例外设输出。
    // 该信号由 slot1 内部的控制寄存器驱动。
    output wire                              o_led
);

    localparam C_SLOT_ADDR_WIDTH = P_REG_ADDR_WIDTH - 2;

    ////////////////////////////////////////////////////////////////////////////
    // AXI-Lite 桥接模块到 SMMR 路由模块之间的信号
    ////////////////////////////////////////////////////////////////////////////

    // AXI-Lite 写事务完成后产生的单周期写脉冲。
    wire                              w_reg_wr_en;

    // SMMR 写地址和读地址。
    // 这里仍然是字节地址,后面由 smmr_slot_router 转换成寄存器索引。
    wire [P_REG_ADDR_WIDTH-1:0]       w_reg_wr_addr;
    wire [P_REG_ADDR_WIDTH-1:0]       w_reg_rd_addr;

    // SMMR 写数据和读数据。
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_reg_wr_data;
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_reg_rd_data;

    ////////////////////////////////////////////////////////////////////////////
    // SMMR 路由模块到各个 slot 外设之间的信号
    ////////////////////////////////////////////////////////////////////////////

    // slot 地址以 32 位寄存器为单位,因此比字节地址少 2 位。
    // 例如字节地址 20 右移 2 位后变成寄存器地址 5。
    wire [C_SLOT_ADDR_WIDTH-1:0]      w_slot_wr_addr;
    wire [C_SLOT_ADDR_WIDTH-1:0]      w_slot_rd_addr;

    // 写数据会同时送到所有 slot,真正写入哪个 slot 由对应写使能决定。
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_slot_wr_data;

    // slot0 的写使能和读数据。
    wire                              w_slot0_wr_en;
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_slot0_rd_data;

    // slot1 的写使能和读数据。
    wire                              w_slot1_wr_en;
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_slot1_rd_data;

    ////////////////////////////////////////////////////////////////////////////
    // AXI-Lite 从接口转 SMMR 桥接模块
    //
    // 该模块负责处理 AXI-Lite 的地址、数据、响应和握手信号。
    // 对内部模块来说,它只输出简单的 SMMR 寄存器读写接口。
    ////////////////////////////////////////////////////////////////////////////
    axilite_slave_smmr_bridge #(
        .P_S_AXI_ADDR_WIDTH(P_S_AXI_ADDR_WIDTH),
        .P_S_AXI_DATA_WIDTH(P_S_AXI_DATA_WIDTH),
        .P_S_MEM_SIZE      (P_S_MEM_SIZE),
        .P_REG_ADDR_WIDTH  (P_REG_ADDR_WIDTH)
    ) u_axilite_slave_smmr_bridge (
        .s_axi_aclk     (s_axi_aclk),
        .s_axi_aresetn  (s_axi_aresetn),

        .s_axi_awaddr   (s_axi_awaddr),
        .s_axi_awvalid  (s_axi_awvalid),
        .s_axi_awready  (s_axi_awready),

        .s_axi_wdata    (s_axi_wdata),
        .s_axi_wstrb    (s_axi_wstrb),
        .s_axi_wvalid   (s_axi_wvalid),
        .s_axi_wready   (s_axi_wready),

        .s_axi_bresp    (s_axi_bresp),
        .s_axi_bvalid   (s_axi_bvalid),
        .s_axi_bready   (s_axi_bready),

        .s_axi_araddr   (s_axi_araddr),
        .s_axi_arvalid  (s_axi_arvalid),
        .s_axi_arready  (s_axi_arready),

        .s_axi_rdata    (s_axi_rdata),
        .s_axi_rresp    (s_axi_rresp),
        .s_axi_rvalid   (s_axi_rvalid),
        .s_axi_rready   (s_axi_rready),

        .o_reg_wr_en    (w_reg_wr_en),
        .o_reg_wr_addr  (w_reg_wr_addr),
        .o_reg_wr_data  (w_reg_wr_data),
        .o_reg_rd_addr  (w_reg_rd_addr),
        .i_reg_rd_data  (w_reg_rd_data)
    );

    ////////////////////////////////////////////////////////////////////////////
    // SMMR slot 路由模块
    //
    // 该模块根据 SMMR 地址范围选择目标 slot。
    // 写操作时,它给对应 slot 产生写使能。
    // 读操作时,它把被选中 slot 的读数据返回给 AXI-Lite 桥接模块。
    //
    // 当前地址映射:
    //   slot0:寄存器地址 0 到 128
    //          0 保留,1..64 为读区,65..128 为写区
    //   slot1:寄存器地址 129 到 132
    ////////////////////////////////////////////////////////////////////////////
    smmr_slot_router #(
        .P_ADDR_WIDTH (P_REG_ADDR_WIDTH),
        .P_DATA_WIDTH (P_S_AXI_DATA_WIDTH),
        .P_SLOT0_BASE (0),
        .P_SLOT0_SIZE (129),
        .P_SLOT1_BASE (129),
        .P_SLOT1_SIZE (4)
    ) u_smmr_slot_router (
        .i_clk          (s_axi_aclk),
        .i_rst_n        (s_axi_aresetn),

        .i_reg_wr_en    (w_reg_wr_en),
        .i_reg_wr_addr  (w_reg_wr_addr),
        .i_reg_wr_data  (w_reg_wr_data),
        .i_reg_rd_addr  (w_reg_rd_addr),
        .o_reg_rd_data  (w_reg_rd_data),

        .o_slot_wr_addr (w_slot_wr_addr),
        .o_slot_wr_data (w_slot_wr_data),
        .o_slot_rd_addr (w_slot_rd_addr),

        .o_slot0_wr_en  (w_slot0_wr_en),
        .i_slot0_rd_data(w_slot0_rd_data),

        .o_slot1_wr_en  (w_slot1_wr_en),
        .i_slot1_rd_data(w_slot1_rd_data)
    );

    ////////////////////////////////////////////////////////////////////////////
    // slot0 外设
    //
    // slot0 是一个简单寄存器块。
    // 它可以作为新增 SMMR 外设的模板:只需要接入写使能、地址、写数据、
    // 读地址和读数据,就可以挂到系统中。
    ////////////////////////////////////////////////////////////////////////////
    slot0 #(
        .P_ADDR_WIDTH (C_SLOT_ADDR_WIDTH),
        .P_DATA_WIDTH (P_S_AXI_DATA_WIDTH),
        .P_SLOT_BASE  (0),
        .P_REG_COUNT  (129)
    ) u_slot0 (
        .i_clk          (s_axi_aclk),
        .i_rst_n        (s_axi_aresetn),
        .i_reg_wr_en    (w_slot0_wr_en),
        .i_reg_wr_addr  (w_slot_wr_addr),
        .i_reg_wr_data  (w_slot_wr_data),
        .i_reg_rd_addr  (w_slot_rd_addr),
        .o_reg_rd_data  (w_slot0_rd_data),

        .s_axis_tdata   (s_axis_tdata),
        .s_axis_tvalid  (s_axis_tvalid),
        .s_axis_tready  (s_axis_tready),
        .s_axis_tlast   (s_axis_tlast),
        .s_axis_tkeep   (s_axis_tkeep),

        .m_axis_tdata   (m_axis_tdata),
        .m_axis_tvalid  (m_axis_tvalid),
        .m_axis_tready  (m_axis_tready),
        .m_axis_tlast   (m_axis_tlast),
        .m_axis_tkeep   (m_axis_tkeep)
    );

    ////////////////////////////////////////////////////////////////////////////
    // slot1 外设
    //
    // slot1 也是一个 SMMR 寄存器块,同时多了一个实际硬件输出 o_led。
    // 这展示了如何通过寄存器写入来控制 FPGA 内部或外部硬件信号。
    ////////////////////////////////////////////////////////////////////////////
    slot1 #(
        .P_ADDR_WIDTH (C_SLOT_ADDR_WIDTH),
        .P_DATA_WIDTH (P_S_AXI_DATA_WIDTH),
        .P_SLOT_BASE  (129),
        .P_REG_COUNT  (4)
    ) u_slot1 (
        .i_clk          (s_axi_aclk),
        .i_rst_n        (s_axi_aresetn),
        .i_reg_wr_en    (w_slot1_wr_en),
        .i_reg_wr_addr  (w_slot_wr_addr),
        .i_reg_wr_data  (w_slot_wr_data),
        .i_reg_rd_addr  (w_slot_rd_addr),
        .o_reg_rd_data  (w_slot1_rd_data),
        .o_led          (o_led)
    );
endmodule

axilite_master.v

verilog 复制代码
`timescale 1ns/1ps

module axilite_master #(
    parameter P_AXI_ADDR_WIDTH = 32,
    parameter P_AXI_DATA_WIDTH = 32
)(
    output reg  [P_AXI_ADDR_WIDTH-1:0]       m_axi_awaddr,
    output reg                               m_axi_awvalid,
    input  wire                              m_axi_awready,
    output reg  [P_AXI_DATA_WIDTH-1:0]       m_axi_wdata,
    output reg  [(P_AXI_DATA_WIDTH/8)-1:0]   m_axi_wstrb,
    output reg                               m_axi_wvalid,
    input  wire                              m_axi_wready,
    input  wire [1:0]                        m_axi_bresp,
    input  wire                              m_axi_bvalid,
    output reg                               m_axi_bready,
    output reg  [P_AXI_ADDR_WIDTH-1:0]       m_axi_araddr,
    output reg                               m_axi_arvalid,
    input  wire                              m_axi_arready,
    input  wire [P_AXI_DATA_WIDTH-1:0]       m_axi_rdata,
    input  wire [1:0]                        m_axi_rresp,
    input  wire                              m_axi_rvalid,
    output reg                               m_axi_rready,

    input  wire                              i_clk,
    input  wire                              i_rst_n,
    input  wire                              i_wr_start,
    input  wire [P_AXI_ADDR_WIDTH-1:0]       i_wr_addr,
    input  wire [P_AXI_DATA_WIDTH-1:0]       i_wr_data,
    output reg                               o_wr_busy,
    output reg                               o_wr_done,
    output reg  [1:0]                        o_wr_resp,
    input  wire                              i_rd_start,
    input  wire [P_AXI_ADDR_WIDTH-1:0]       i_rd_addr,
    output reg                               o_rd_busy,
    output reg                               o_rd_done,
    output reg  [P_AXI_DATA_WIDTH-1:0]       o_rd_data,
    output reg  [1:0]                        o_rd_resp
);
    localparam [1:0] WR_IDLE = 2'd0, WR_ADDR = 2'd1, WR_RESP = 2'd2;
    localparam [1:0] RD_IDLE = 2'd0, RD_ADDR = 2'd1, RD_DATA = 2'd2;
    reg [1:0] r_wr_state;
    reg [1:0] r_rd_state;

    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            r_wr_state <= WR_IDLE; o_wr_busy <= 1'b0; o_wr_done <= 1'b0; o_wr_resp <= 2'b00;
            m_axi_awaddr <= {P_AXI_ADDR_WIDTH{1'b0}}; m_axi_awvalid <= 1'b0;
            m_axi_wdata <= {P_AXI_DATA_WIDTH{1'b0}}; m_axi_wstrb <= {(P_AXI_DATA_WIDTH/8){1'b0}}; m_axi_wvalid <= 1'b0;
            m_axi_bready <= 1'b0;
        end else begin
            o_wr_done <= 1'b0;
            case (r_wr_state)
                WR_IDLE: begin
                    o_wr_busy <= 1'b0; m_axi_awvalid <= 1'b0; m_axi_wvalid <= 1'b0; m_axi_bready <= 1'b0;
                    if (i_wr_start) begin
                        o_wr_busy <= 1'b1; m_axi_awaddr <= i_wr_addr; m_axi_awvalid <= 1'b1;
                        m_axi_wdata <= i_wr_data; m_axi_wstrb <= {(P_AXI_DATA_WIDTH/8){1'b1}}; m_axi_wvalid <= 1'b1;
                        r_wr_state <= WR_ADDR;
                    end
                end
                WR_ADDR: begin
                    if (m_axi_awvalid && m_axi_awready) m_axi_awvalid <= 1'b0;
                    if (m_axi_wvalid && m_axi_wready) m_axi_wvalid <= 1'b0;
                    if ((!m_axi_awvalid || m_axi_awready) && (!m_axi_wvalid || m_axi_wready)) begin
                        m_axi_bready <= 1'b1; r_wr_state <= WR_RESP;
                    end
                end
                WR_RESP: begin
                    if (m_axi_bvalid) begin
                        o_wr_resp <= m_axi_bresp; o_wr_done <= 1'b1; o_wr_busy <= 1'b0; m_axi_bready <= 1'b0; r_wr_state <= WR_IDLE;
                    end
                end
                default: r_wr_state <= WR_IDLE;
            endcase
        end
    end

    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            r_rd_state <= RD_IDLE; o_rd_busy <= 1'b0; o_rd_done <= 1'b0; o_rd_data <= {P_AXI_DATA_WIDTH{1'b0}}; o_rd_resp <= 2'b00;
            m_axi_araddr <= {P_AXI_ADDR_WIDTH{1'b0}}; m_axi_arvalid <= 1'b0; m_axi_rready <= 1'b0;
        end else begin
            o_rd_done <= 1'b0;
            case (r_rd_state)
                RD_IDLE: begin
                    o_rd_busy <= 1'b0; m_axi_arvalid <= 1'b0; m_axi_rready <= 1'b0;
                    if (i_rd_start) begin
                        o_rd_busy <= 1'b1; m_axi_araddr <= i_rd_addr; m_axi_arvalid <= 1'b1; r_rd_state <= RD_ADDR;
                    end
                end
                RD_ADDR: begin
                    if (m_axi_arvalid && m_axi_arready) begin
                        m_axi_arvalid <= 1'b0; m_axi_rready <= 1'b1; r_rd_state <= RD_DATA;
                    end
                end
                RD_DATA: begin
                    if (m_axi_rvalid) begin
                        o_rd_data <= m_axi_rdata; o_rd_resp <= m_axi_rresp; o_rd_done <= 1'b1; o_rd_busy <= 1'b0; m_axi_rready <= 1'b0; r_rd_state <= RD_IDLE;
                    end
                end
                default: r_rd_state <= RD_IDLE;
            endcase
        end
    end
endmodule

axilite_slave_smmr_bridge.v

verilog 复制代码
//AXI-Lite Slave 接口转 SMMR 简单寄存器接口 桥接模块
module axilite_slave_smmr_bridge #(
    parameter P_S_AXI_ADDR_WIDTH = 32,
    parameter P_S_AXI_DATA_WIDTH = 32,
    parameter P_S_MEM_SIZE       = 512, //内部存储器大小(单位:字节),对应128个32位寄存器
    parameter P_REG_ADDR_WIDTH   = 9
)(
    // =========================
    // AXI-Lite (PS -> PL)
    // =========================
    input  wire                              s_axi_aclk,
    input  wire                              s_axi_aresetn,
    input  wire [P_S_AXI_ADDR_WIDTH-1:0]     s_axi_awaddr,
    input  wire                              s_axi_awvalid,
    output reg                               s_axi_awready,
    input  wire [P_S_AXI_DATA_WIDTH-1:0]     s_axi_wdata,
    input  wire [(P_S_AXI_DATA_WIDTH/8)-1:0] s_axi_wstrb,
    input  wire                              s_axi_wvalid,
    output reg                               s_axi_wready,
    output reg  [1:0]                        s_axi_bresp,
    output reg                               s_axi_bvalid,
    input  wire                              s_axi_bready,
    input  wire [P_S_AXI_ADDR_WIDTH-1:0]     s_axi_araddr,
    input  wire                              s_axi_arvalid,
    output reg                               s_axi_arready,
    output reg  [P_S_AXI_DATA_WIDTH-1:0]     s_axi_rdata,
    output reg  [1:0]                        s_axi_rresp,
    output reg                               s_axi_rvalid,
    input  wire                              s_axi_rready,

    // =========================
    // Simple register bus
    // =========================
    output reg                               o_reg_wr_en,
    output reg  [P_REG_ADDR_WIDTH-1:0]       o_reg_wr_addr,
    output reg  [P_S_AXI_DATA_WIDTH-1:0]     o_reg_wr_data,
    output reg  [P_REG_ADDR_WIDTH-1:0]       o_reg_rd_addr,
    input  wire [P_S_AXI_DATA_WIDTH-1:0]     i_reg_rd_data
);


    reg [P_S_AXI_ADDR_WIDTH-1:0] r_write_addr;   // 写地址锁存:AW 通道先到、W 通道后到时,暂存本次写地址。
    reg [P_S_AXI_DATA_WIDTH-1:0] r_write_data;   // 写数据锁存:W 通道先到、AW 通道后到时,暂存本次写数据。
    reg                          r_aw_valid;     // 写地址锁存有效标志:AWADDR 已锁存,等待 WDATA 到齐后写寄存器。
    reg                          r_w_valid;      // 写数据锁存有效标志:WDATA 已锁存,等待 AWADDR 到齐后写寄存器。
    reg                          r_read_pending;   // 读请求处理中标志:ARADDR 发给下游后一拍,再采样读数据返回 AXI R 通道。

    wire w_aw_fire = s_axi_awvalid && s_axi_awready; // 写地址通道握手完成,本拍接收 AWADDR。
    wire w_w_fire  = s_axi_wvalid  && s_axi_wready;  // 写数据通道握手完成,本拍接收 WDATA。
    wire w_ar_fire = s_axi_arvalid && s_axi_arready; // 读地址通道握手完成,本拍接收 ARADDR。
    // 写地址和写数据可能不同拍到达;这里先判断本拍是否已经具备完整写事务。
    wire w_write_addr_ready = r_aw_valid || w_aw_fire;
    wire w_write_data_ready = r_w_valid  || w_w_fire;
    wire w_write_ready      = !s_axi_bvalid && w_write_addr_ready && w_write_data_ready;
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_write_addr = w_aw_fire ? s_axi_awaddr : r_write_addr;
    wire [P_S_AXI_DATA_WIDTH-1:0] w_write_data = w_w_fire  ? s_axi_wdata  : r_write_data;
    // 读写通道独立仲裁。AXI-Lite 允许读写同时发起,这里不让读事务等待写事务结束。
    always @(*) begin
        s_axi_awready = s_axi_aresetn && !r_aw_valid && !s_axi_bvalid;
        s_axi_wready  = s_axi_aresetn && !r_w_valid  && !s_axi_bvalid;
        s_axi_arready = s_axi_aresetn && !r_read_pending && !s_axi_rvalid;
    end

    always @(posedge s_axi_aclk or negedge s_axi_aresetn) begin
        if (!s_axi_aresetn) begin
            s_axi_bresp   <= 2'b00;
            s_axi_bvalid  <= 1'b0;
            s_axi_rdata   <= {P_S_AXI_DATA_WIDTH{1'b0}};
            s_axi_rresp   <= 2'b00;
            s_axi_rvalid  <= 1'b0;
            r_write_addr  <= {P_S_AXI_ADDR_WIDTH{1'b0}};
            r_write_data  <= {P_S_AXI_DATA_WIDTH{1'b0}};
            r_aw_valid <= 1'b0;
            r_w_valid  <= 1'b0;
            r_read_pending <= 1'b0;
            o_reg_wr_en     <= 1'b0;
            o_reg_wr_addr   <= {P_REG_ADDR_WIDTH{1'b0}};
            o_reg_wr_data   <= {P_S_AXI_DATA_WIDTH{1'b0}};
            o_reg_rd_addr   <= {P_REG_ADDR_WIDTH{1'b0}};
        end else begin
            o_reg_wr_en <= 1'b0;
            if (w_aw_fire) begin
                r_aw_valid <= 1'b1;
                r_write_addr <= s_axi_awaddr;
            end

            if (w_w_fire) begin
                r_w_valid <= 1'b1;
                r_write_data <= s_axi_wdata;
            end

            if (w_write_ready) begin
                r_aw_valid <= 1'b0;
                r_w_valid  <= 1'b0;
                s_axi_bresp  <= 2'b00;
                s_axi_bvalid <= 1'b1;
                o_reg_wr_en   <= 1'b1;
                o_reg_wr_addr <= w_write_addr[P_REG_ADDR_WIDTH-1:0];
                o_reg_wr_data <= w_write_data;
            end else if (s_axi_bvalid && s_axi_bready) begin
                s_axi_bvalid <= 1'b0;
            end

            if (w_ar_fire) begin
                r_read_pending <= 1'b1;
                o_reg_rd_addr  <= s_axi_araddr[P_REG_ADDR_WIDTH-1:0];
            end else if (r_read_pending) begin
                r_read_pending <= 1'b0;
                s_axi_rdata    <= i_reg_rd_data;
                s_axi_rresp    <= 2'b00;
                s_axi_rvalid   <= 1'b1;
            end else if (s_axi_rvalid && s_axi_rready) begin
                s_axi_rvalid <= 1'b0;
            end
        end
    end

endmodule

smmr_slot_router.v

verilog 复制代码
////////////////////////////////////////////////////////////////////////////////
// 简单内存映射寄存器路由模块
// - 将字节地址转换成 32 位寄存器索引
// - 将一路 SMMR 接口路由到多个寄存器槽
////////////////////////////////////////////////////////////////////////////////
module smmr_slot_router #(
    // AXI-Lite 侧使用字节地址,寄存器槽侧使用寄存器索引。
    parameter P_ADDR_WIDTH = 10,
    parameter P_DATA_WIDTH = 32,
    // 寄存器槽地址范围以 32 位寄存器为单位。
    parameter P_SLOT0_BASE = 0,
    parameter P_SLOT0_SIZE = 129,
    parameter P_SLOT1_BASE = 129,
    parameter P_SLOT1_SIZE = 4
)(
    input  wire                    i_clk,
    input  wire                    i_rst_n,

    // 上游简单寄存器总线,地址为字节地址。
    input  wire                    i_reg_wr_en,
    input  wire [P_ADDR_WIDTH-1:0] i_reg_wr_addr,
    input  wire [P_DATA_WIDTH-1:0] i_reg_wr_data,
    input  wire [P_ADDR_WIDTH-1:0] i_reg_rd_addr,
    output reg  [P_DATA_WIDTH-1:0] o_reg_rd_data,

    // 下游寄存器槽总线,地址为 32 位寄存器索引。
    output wire [P_ADDR_WIDTH-3:0] o_slot_wr_addr,
    output wire [P_DATA_WIDTH-1:0] o_slot_wr_data,
    output wire [P_ADDR_WIDTH-3:0] o_slot_rd_addr,
    output wire                    o_slot0_wr_en,
    input  wire [P_DATA_WIDTH-1:0] i_slot0_rd_data,
    output wire                    o_slot1_wr_en,
    input  wire [P_DATA_WIDTH-1:0] i_slot1_rd_data
);
    wire [P_ADDR_WIDTH-3:0] w_reg_wr_addr_u32 = i_reg_wr_addr >> 2;
    wire [P_ADDR_WIDTH-3:0] w_reg_rd_addr_u32 = i_reg_rd_addr >> 2;

    reg r_slot0_wr_sel;
    reg r_slot1_wr_sel;
    reg r_slot0_rd_sel;
    reg r_slot1_rd_sel;

    always @(*) begin
        r_slot0_wr_sel = 1'b0;
        r_slot1_wr_sel = 1'b0;

        if ((w_reg_wr_addr_u32 >= P_SLOT0_BASE) && (w_reg_wr_addr_u32 < (P_SLOT0_BASE + P_SLOT0_SIZE))) begin
            r_slot0_wr_sel = 1'b1;
        end else if ((w_reg_wr_addr_u32 >= P_SLOT1_BASE) && (w_reg_wr_addr_u32 < (P_SLOT1_BASE + P_SLOT1_SIZE))) begin
            r_slot1_wr_sel = 1'b1;
        end
    end

    always @(*) begin
        r_slot0_rd_sel = 1'b0;
        r_slot1_rd_sel = 1'b0;

        if ((w_reg_rd_addr_u32 >= P_SLOT0_BASE) && (w_reg_rd_addr_u32 < (P_SLOT0_BASE + P_SLOT0_SIZE))) begin
            r_slot0_rd_sel = 1'b1;
        end else if ((w_reg_rd_addr_u32 >= P_SLOT1_BASE) && (w_reg_rd_addr_u32 < (P_SLOT1_BASE + P_SLOT1_SIZE))) begin
            r_slot1_rd_sel = 1'b1;
        end
    end

    assign o_slot0_wr_en  = i_reg_wr_en && r_slot0_wr_sel;
    assign o_slot1_wr_en  = i_reg_wr_en && r_slot1_wr_sel;
    assign o_slot_wr_addr = w_reg_wr_addr_u32;
    assign o_slot_wr_data = i_reg_wr_data;
    assign o_slot_rd_addr = w_reg_rd_addr_u32;

    always @(*) begin
        if (r_slot0_rd_sel) begin
            o_reg_rd_data = i_slot0_rd_data;
        end else if (r_slot1_rd_sel) begin
            o_reg_rd_data = i_slot1_rd_data;
        end else begin
            o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
        end
    end

endmodule

slot0.v

verilog 复制代码
//------------------------------------------------------------------------------
// slot0
//------------------------------------------------------------------------------
// 功能概述:
//   这是一个同时支持三种访问方式的寄存器槽:
//     1. 简单寄存器总线读写:PS 通过 AXI-Lite -> SMMR 路由访问 slot0 寄存器。
//     2. AXI-Stream 从接口 s_axis:接收 AXI DMA MM2S 送来的 64 个 32bit 数据,
//        写入 slot0[65]..slot0[128]。
//     3. AXI-Stream 主接口 m_axis:把 slot0[1]..slot0[64] 作为 64 个 32bit
//        数据输出给 AXI DMA S2MM。
//
// 寄存器约定:
//   slot0[0]      :控制触发寄存器,不作为普通数据保存。
//                   bit0 = 1:启动一次 m_axis 64-word 输出包。
//                   bit1 = 1:清零 s_axis 写入索引。
//   slot0[1..64]  :读窗口,m_axis 每次从这里顺序输出 64 word。
//   slot0[65..128]:写窗口,s_axis 每次顺序写入这里 64 word。
//
// 关键时序点:
//   m_axis_tvalid 不能常高。若 DMA S2MM 还没有正式启动,而 tvalid 常高,
//   DMA 或互连在空闲期短暂拉高 tready 时会提前消耗数据,导致读指针错位。
//   所以这里使用 r_m_axis_active 控制 tvalid,只在软件写 slot0[0].bit0 后
//   输出一包 64 word,最后一拍 tlast 后自动停住并把读指针回到 0。
//------------------------------------------------------------------------------
module slot0 #(
    // 槽内部寄存器索引宽度。当前顶层传入 8bit,可覆盖 0..255 个 32bit 寄存器索引。
    parameter P_ADDR_WIDTH = 8,
    // 数据宽度。当前工程使用 32bit,与 AXI-Lite 数据宽度和 AXI-Stream tdata 一致。
    parameter P_DATA_WIDTH = 32,
    // 本 slot 在全局寄存器索引空间中的起始地址。slot0 当前固定从 0 开始。
    parameter [P_ADDR_WIDTH-1:0] P_SLOT_BASE = 0,
    // slot0 寄存器数量:0..128,共 129 个 32bit 寄存器索引。
    parameter P_REG_COUNT  = 129
)(
    // 与 AXI-Lite/SMMR 和 AXI-Stream 共用同一时钟域。
    input  wire                    i_clk,
    // 低有效复位。
    input  wire                    i_rst_n,

    // 简单寄存器写接口:上游 AXI-Lite 桥在完成一次写事务时给一个写使能脉冲。
    input  wire                    i_reg_wr_en,
    // 寄存器索引地址,不是字节地址。比如 slot0[65] 的地址就是 65。
    input  wire [P_ADDR_WIDTH-1:0] i_reg_wr_addr,
    input  wire [P_DATA_WIDTH-1:0] i_reg_wr_data,
    // 简单寄存器读接口:组合读,地址命中时直接返回 r_mem 中的数据。
    input  wire [P_ADDR_WIDTH-1:0] i_reg_rd_addr,
    output reg  [P_DATA_WIDTH-1:0] o_reg_rd_data,

    // AXI-Stream Slave:接 AXI DMA 的 MM2S 输出,数据方向 DDR/PS -> DMA -> slot0。
    input  wire [P_DATA_WIDTH-1:0]       s_axis_tdata,
    input  wire                          s_axis_tvalid,
    output wire                          s_axis_tready,
    input  wire                          s_axis_tlast,
    input  wire [(P_DATA_WIDTH/8)-1:0]   s_axis_tkeep,

    // AXI-Stream Master:接 AXI DMA 的 S2MM 输入,数据方向 slot0 -> DMA -> DDR/PS。
    output wire [P_DATA_WIDTH-1:0]       m_axis_tdata,
    output wire                          m_axis_tvalid,
    input  wire                          m_axis_tready,
    output wire                          m_axis_tlast,
    output wire [(P_DATA_WIDTH/8)-1:0]   m_axis_tkeep
);

    // slot0[0]:控制触发寄存器。变量名仍叫 RSVD,是为了保持原结构,
    // 但现在 bit0/bit1 已作为一次性控制位使用,不保存到 r_mem[0]。
    localparam [P_ADDR_WIDTH-1:0] C_RSVD_REG       = P_SLOT_BASE;
    // slot0[1]:m_axis 输出窗口起始寄存器。
    localparam [P_ADDR_WIDTH-1:0] C_READ_REG_BASE  = P_SLOT_BASE + 1;
    // slot0[65]:s_axis 输入窗口起始寄存器。
    localparam [P_ADDR_WIDTH-1:0] C_WRITE_REG_BASE = P_SLOT_BASE + 65;
    // 64 word AXI-Stream 包的最后一个索引:0..63。
    localparam [5:0]              C_AXIS_LAST_IDX  = 6'd63;

    // 实际寄存器存储区。索引 0 对应 slot0[0],索引 128 对应 slot0[128]。
    reg [P_DATA_WIDTH-1:0] r_mem [0:P_REG_COUNT-1];
    // s_axis 当前写入 slot0[65..128] 的偏移,范围 0..63。
    reg [5:0] r_s_axis_wr_idx;
    // m_axis 当前读取 slot0[1..64] 的偏移,范围 0..63。
    reg [5:0] r_m_axis_rd_idx;
    // m_axis 一包数据发送中的状态标志。为 1 时才允许 tvalid 拉高。
    reg       r_m_axis_active;

    // 写 slot0[0] 时认为是控制写,不进入普通数据寄存器写路径。
    wire w_reg_ctrl_wr = i_reg_wr_en && (i_reg_wr_addr == C_RSVD_REG);
    // 普通寄存器写命中:slot0[1]..slot0[128] 可写,slot0[0] 被排除。
    wire w_reg_wr_hit = (i_reg_wr_addr > C_RSVD_REG) &&
                        (i_reg_wr_addr < (P_SLOT_BASE + P_REG_COUNT));
    // 寄存器读命中:slot0[0]..slot0[128] 都可读。slot0[0] 当前读到的是 r_mem[0]。
    wire w_reg_rd_hit = (i_reg_rd_addr >= P_SLOT_BASE) &&
                        (i_reg_rd_addr < (P_SLOT_BASE + P_REG_COUNT));
    // AXI-Stream 握手成功的一拍:valid 和 ready 同时为 1 时数据才被消费。
    wire w_s_axis_fire = s_axis_tvalid && s_axis_tready;
    wire w_m_axis_fire = m_axis_tvalid && m_axis_tready;

    // 根据 tkeep 按字节合并写入数据。
    // keep[b]=1 表示写入对应字节;keep[b]=0 表示保留旧字节。
    // 对于常见 32bit 全字写入,tkeep 通常是 4'b1111。
    function [P_DATA_WIDTH-1:0] f_keep_merge;
        input [P_DATA_WIDTH-1:0] old_data;
        input [P_DATA_WIDTH-1:0] new_data;
        input [(P_DATA_WIDTH/8)-1:0] keep;
        integer b;
        begin
            f_keep_merge = old_data;
            for (b = 0; b < (P_DATA_WIDTH/8); b = b + 1) begin
                if (keep[b]) begin
                    f_keep_merge[b*8 +: 8] = new_data[b*8 +: 8];
                end
            end
        end
    endfunction

    // s_axis 接收端当前没有背压逻辑,复位释放后始终 ready。
    // AXI DMA MM2S 连续发送 64 word 时,每个有效拍都会被接收。
    assign s_axis_tready = i_rst_n;

    // m_axis 只有在软件写 slot0[0].bit0 触发后才输出一包数据。
    // 这样可以避免 DMA 尚未启动时,由 tready 空闲抖动导致读指针提前递增。
    assign m_axis_tvalid = r_m_axis_active;
    // 当前输出数据来自 slot0[1..64],由 r_m_axis_rd_idx 顺序选择。
    assign m_axis_tdata  = r_mem[(C_READ_REG_BASE - P_SLOT_BASE) + r_m_axis_rd_idx];
    // 第 64 个 word 时拉高 tlast,通知 AXI DMA S2MM 一包结束。
    assign m_axis_tlast  = r_m_axis_active && (r_m_axis_rd_idx == C_AXIS_LAST_IDX);
    // m_axis 每拍都输出完整 32bit,所以 tkeep 全 1。
    assign m_axis_tkeep  = {(P_DATA_WIDTH/8){1'b1}};

    // 主时序逻辑:
    //   - 复位时清空寄存器和两个 AXI-Stream 索引。
    //   - 普通寄存器写:写 slot0[1..128]。
    //   - s_axis 收到数据:顺序写 slot0[65..128]。
    //   - 写 slot0[0].bit0:启动一次 m_axis 输出 slot0[1..64]。
    //   - m_axis 发完第 64 个 word:停止输出并把读索引归零。
    integer i;
    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            for (i = 0; i < P_REG_COUNT; i = i + 1) begin
                r_mem[i] <= {P_DATA_WIDTH{1'b0}};
            end
            r_s_axis_wr_idx <= 6'd0;
            r_m_axis_rd_idx <= 6'd0;
            r_m_axis_active <= 1'b0;
        end else begin
            // 控制寄存器 bit1:手动清零 s_axis 写索引。
            // 主要用于调试或异常恢复,正常 64 word 包结束时也会自动归零。
            if (w_reg_ctrl_wr && i_reg_wr_data[1]) begin
                r_s_axis_wr_idx <= 6'd0;
            end

            // PS/CPU 通过 AXI-Lite 直接写 slot0[1..128]。
            // slot0[0] 不在这里写入,避免控制触发值污染普通数据区。
            if (i_reg_wr_en && w_reg_wr_hit) begin
                r_mem[i_reg_wr_addr-P_SLOT_BASE] <= i_reg_wr_data;
            end

            // AXI DMA MM2S -> slot0:
            // 每收到一个 stream word,就写入 slot0[65 + r_s_axis_wr_idx]。
            // 若收到 tlast,或者已经写到第 64 个 word,则下一包从 65 重新开始。
            if (w_s_axis_fire) begin
                r_mem[(C_WRITE_REG_BASE - P_SLOT_BASE) + r_s_axis_wr_idx] <=
                    f_keep_merge(r_mem[(C_WRITE_REG_BASE - P_SLOT_BASE) + r_s_axis_wr_idx],
                                 s_axis_tdata,
                                 s_axis_tkeep);

                if (s_axis_tlast || (r_s_axis_wr_idx == C_AXIS_LAST_IDX)) begin
                    r_s_axis_wr_idx <= 6'd0;
                end else begin
                    r_s_axis_wr_idx <= r_s_axis_wr_idx + 6'd1;
                end
            end

            // 写 slot0[0].bit0:启动 m_axis 一次 64 word 输出。
            // 启动时强制读索引回到 0,保证每次 dma_read 都从 slot0[1] 开始。
            if (w_reg_ctrl_wr && i_reg_wr_data[0]) begin
                r_m_axis_active <= 1'b1;
                r_m_axis_rd_idx <= 6'd0;
            end else if (w_m_axis_fire) begin
                // AXI DMA S2MM 真正接收一个 word 后,读索引才递增。
                // 最后一拍发出后停止 valid,等待下一次 slot0[0].bit0 触发。
                if (r_m_axis_rd_idx == C_AXIS_LAST_IDX) begin
                    r_m_axis_rd_idx <= 6'd0;
                    r_m_axis_active <= 1'b0;
                end else begin
                    r_m_axis_rd_idx <= r_m_axis_rd_idx + 6'd1;
                end
            end
        end
    end

    // 寄存器组合读:
    //   命中 slot0 地址范围时返回对应 r_mem。
    //   超出范围时返回 0,避免上游读到不确定值。
    always @(*) begin
        if (w_reg_rd_hit) begin
            o_reg_rd_data = r_mem[i_reg_rd_addr-P_SLOT_BASE];
        end else begin
            o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
        end
    end

endmodule

slot1.v

verilog 复制代码
module slot1 #(
    parameter P_ADDR_WIDTH = 8,
    parameter P_DATA_WIDTH = 32,
    parameter [P_ADDR_WIDTH-1:0] P_SLOT_BASE = 129,
    parameter P_REG_COUNT  = 4
)(
    input  wire                    i_clk,
    input  wire                    i_rst_n,

    input  wire                    i_reg_wr_en,
    input  wire [P_ADDR_WIDTH-1:0] i_reg_wr_addr,
    input  wire [P_DATA_WIDTH-1:0] i_reg_wr_data,
    input  wire [P_ADDR_WIDTH-1:0] i_reg_rd_addr,
    output reg  [P_DATA_WIDTH-1:0] o_reg_rd_data,
    output reg                     o_led
);
    // 控制寄存器:bit0:o_led
    localparam [P_ADDR_WIDTH-1:0] ADDR_CONTROL  = P_SLOT_BASE;
    reg [P_DATA_WIDTH-1:0] r_mem [0:P_REG_COUNT-1];

    integer i;
    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            for (i = 0; i < P_REG_COUNT; i = i + 1) begin
                r_mem[i] <= {P_DATA_WIDTH{1'b0}};
            end
            o_led<=0;
        end else if (i_reg_wr_en && (i_reg_wr_addr >= P_SLOT_BASE) && (i_reg_wr_addr < (P_SLOT_BASE + P_REG_COUNT))) begin
               r_mem[i_reg_wr_addr-P_SLOT_BASE] <= i_reg_wr_data;
               case (i_reg_wr_addr)
                    ADDR_CONTROL: begin
                        o_led <= i_reg_wr_data[0];
                    end
             endcase
        end
    end
    always @(*) begin
        if ((i_reg_rd_addr >= P_SLOT_BASE) && (i_reg_rd_addr < (P_SLOT_BASE + P_REG_COUNT))) begin
            o_reg_rd_data = r_mem[i_reg_rd_addr-P_SLOT_BASE];
        end else begin
            o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
        end
    end

endmodule

tb.v

verilog 复制代码
`timescale 1ns/1ps

module tb;
    localparam P_AXI_ADDR_WIDTH = 32;
    localparam P_AXI_DATA_WIDTH = 32;

    reg i_clk;
    reg i_rst_n;
    reg                         r_wr_start;
    reg  [P_AXI_ADDR_WIDTH-1:0] r_wr_addr;
    reg  [P_AXI_DATA_WIDTH-1:0] r_wr_data;
    wire                        w_wr_busy;
    wire                        w_wr_done;
    wire [1:0]                  w_wr_resp;
    reg                         r_rd_start;
    reg  [P_AXI_ADDR_WIDTH-1:0] r_rd_addr;
    wire                        w_rd_busy;
    wire                        w_rd_done;
    wire [P_AXI_DATA_WIDTH-1:0] w_rd_data;
    wire [1:0]                  w_rd_resp;
    wire [P_AXI_ADDR_WIDTH-1:0] w_axi_awaddr;
    wire                        w_axi_awvalid;
    wire                        w_axi_awready;
    wire [P_AXI_DATA_WIDTH-1:0] w_axi_wdata;
    wire [(P_AXI_DATA_WIDTH/8)-1:0] w_axi_wstrb;
    wire                        w_axi_wvalid;
    wire                        w_axi_wready;
    wire [1:0]                  w_axi_bresp;
    wire                        w_axi_bvalid;
    wire                        w_axi_bready;
    wire [P_AXI_ADDR_WIDTH-1:0] w_axi_araddr;
    wire                        w_axi_arvalid;
    wire                        w_axi_arready;
    wire [P_AXI_DATA_WIDTH-1:0] w_axi_rdata;
    wire [1:0]                  w_axi_rresp;
    wire                        w_axi_rvalid;
    wire                        w_axi_rready;
    reg  [P_AXI_DATA_WIDTH-1:0] r_s_axis_tdata;
    reg                         r_s_axis_tvalid;
    wire                        w_s_axis_tready;
    reg                         r_s_axis_tlast;
    reg  [(P_AXI_DATA_WIDTH/8)-1:0] r_s_axis_tkeep;
    wire [P_AXI_DATA_WIDTH-1:0] w_m_axis_tdata;
    wire                        w_m_axis_tvalid;
    reg                         r_m_axis_tready;
    wire                        w_m_axis_tlast;
    wire [(P_AXI_DATA_WIDTH/8)-1:0] w_m_axis_tkeep;
    wire                        w_led;

    axilite_master u_axilite_master (
        .i_clk(i_clk), .i_rst_n(i_rst_n),
        .i_wr_start(r_wr_start), .i_wr_addr(r_wr_addr), .i_wr_data(r_wr_data),
        .o_wr_busy(w_wr_busy), .o_wr_done(w_wr_done), .o_wr_resp(w_wr_resp),
        .i_rd_start(r_rd_start), .i_rd_addr(r_rd_addr),
        .o_rd_busy(w_rd_busy), .o_rd_done(w_rd_done), .o_rd_data(w_rd_data), .o_rd_resp(w_rd_resp),
        .m_axi_awaddr(w_axi_awaddr), .m_axi_awvalid(w_axi_awvalid), .m_axi_awready(w_axi_awready),
        .m_axi_wdata(w_axi_wdata), .m_axi_wstrb(w_axi_wstrb), .m_axi_wvalid(w_axi_wvalid), .m_axi_wready(w_axi_wready),
        .m_axi_bresp(w_axi_bresp), .m_axi_bvalid(w_axi_bvalid), .m_axi_bready(w_axi_bready),
        .m_axi_araddr(w_axi_araddr), .m_axi_arvalid(w_axi_arvalid), .m_axi_arready(w_axi_arready),
        .m_axi_rdata(w_axi_rdata), .m_axi_rresp(w_axi_rresp), .m_axi_rvalid(w_axi_rvalid), .m_axi_rready(w_axi_rready)
    );

    top u_top (
        .s_axi_aclk(i_clk), .s_axi_aresetn(i_rst_n),
        .s_axi_awaddr(w_axi_awaddr), .s_axi_awvalid(w_axi_awvalid), .s_axi_awready(w_axi_awready),
        .s_axi_wdata(w_axi_wdata), .s_axi_wstrb(w_axi_wstrb), .s_axi_wvalid(w_axi_wvalid), .s_axi_wready(w_axi_wready),
        .s_axi_bresp(w_axi_bresp), .s_axi_bvalid(w_axi_bvalid), .s_axi_bready(w_axi_bready),
        .s_axi_araddr(w_axi_araddr), .s_axi_arvalid(w_axi_arvalid), .s_axi_arready(w_axi_arready),
        .s_axi_rdata(w_axi_rdata), .s_axi_rresp(w_axi_rresp), .s_axi_rvalid(w_axi_rvalid), .s_axi_rready(w_axi_rready),
        .s_axis_tdata(r_s_axis_tdata), .s_axis_tvalid(r_s_axis_tvalid), .s_axis_tready(w_s_axis_tready),
        .s_axis_tlast(r_s_axis_tlast), .s_axis_tkeep(r_s_axis_tkeep),
        .m_axis_tdata(w_m_axis_tdata), .m_axis_tvalid(w_m_axis_tvalid), .m_axis_tready(r_m_axis_tready),
        .m_axis_tlast(w_m_axis_tlast), .m_axis_tkeep(w_m_axis_tkeep),
        .o_led(w_led)
    );

    initial begin
        i_clk = 1'b0;
        forever #10 i_clk = ~i_clk;
    end

    task reset_dut;
        begin
            i_rst_n <= 1'b0;
            r_wr_start <= 1'b0; r_wr_addr <= 32'd0; r_wr_data <= 32'd0;
            r_rd_start <= 1'b0; r_rd_addr <= 32'd0;
            r_s_axis_tdata <= 32'd0; r_s_axis_tvalid <= 1'b0; r_s_axis_tlast <= 1'b0;
            r_s_axis_tkeep <= {(P_AXI_DATA_WIDTH/8){1'b1}};
            r_m_axis_tready <= 1'b0;
            repeat (5) @(posedge i_clk);
            i_rst_n <= 1'b1;
            repeat (2) @(posedge i_clk);
        end
    endtask

    task axi_write;
        input [P_AXI_ADDR_WIDTH-1:0] addr;
        input [P_AXI_DATA_WIDTH-1:0] data;
        begin
            @(posedge i_clk);
            r_wr_addr <= addr; r_wr_data <= data; r_wr_start <= 1'b1;
            @(posedge i_clk);
            r_wr_start <= 1'b0;
            wait (w_wr_done);
            $display("[%0t ns] AXI WRITE addr=0x%08x data=0x%08x resp=%0d", $time, addr, data, w_wr_resp);
            if (w_wr_resp != 2'b00) $fatal(1, "AXI write response error");
        end
    endtask

    task axi_read_check;
        input [P_AXI_ADDR_WIDTH-1:0] addr;
        input [P_AXI_DATA_WIDTH-1:0] exp_data;
        begin
            @(posedge i_clk);
            r_rd_addr <= addr; r_rd_start <= 1'b1;
            @(posedge i_clk);
            r_rd_start <= 1'b0;
            wait (w_rd_done);
            $display("[%0t ns] AXI READ  addr=0x%08x data=0x%08x resp=%0d led=%0b", $time, addr, w_rd_data, w_rd_resp, w_led);
            if (w_rd_resp != 2'b00) $fatal(1, "AXI read response error");
            if (w_rd_data !== exp_data) $fatal(1, "AXI read mismatch addr=0x%08x exp=0x%08x got=0x%08x", addr, exp_data, w_rd_data);
        end
    endtask

    task axis_write64;
        integer j;
        begin
            for (j = 0; j < 64; j = j + 1) begin
                @(negedge i_clk);
                r_s_axis_tdata  = 32'hA000_0000 + j;
                r_s_axis_tvalid = 1'b1;
                r_s_axis_tlast  = (j == 63);
                r_s_axis_tkeep  = {(P_AXI_DATA_WIDTH/8){1'b1}};
                @(posedge i_clk);
                if (!w_s_axis_tready) $fatal(1, "S_AXIS not ready");
            end
            @(negedge i_clk);
            r_s_axis_tvalid = 1'b0;
            r_s_axis_tlast  = 1'b0;
        end
    endtask

    task axis_read64_check;
        integer j;
        begin
            @(negedge i_clk);
            r_m_axis_tready = 1'b1;
            for (j = 0; j < 64; j = j + 1) begin
                @(posedge i_clk);
                if (!w_m_axis_tvalid) $fatal(1, "M_AXIS not valid");
                if ((j == 0) && (w_m_axis_tdata !== 32'hB000_0001)) begin
                    $fatal(1, "M_AXIS first word mismatch got=0x%08x", w_m_axis_tdata);
                end
                if ((j == 63) && (w_m_axis_tdata !== 32'hB000_0040)) begin
                    $fatal(1, "M_AXIS last word mismatch got=0x%08x", w_m_axis_tdata);
                end
                if (w_m_axis_tlast !== (j == 63)) begin
                    $fatal(1, "M_AXIS tlast mismatch index=%0d tlast=%0b", j, w_m_axis_tlast);
                end
            end
            @(negedge i_clk);
            r_m_axis_tready = 1'b0;
        end
    endtask

    task axis_idle_check;
        integer k;
        begin
            for (k = 0; k < 4; k = k + 1) begin
                @(negedge i_clk);
                r_m_axis_tready = 1'b1;
                @(posedge i_clk);
                if (w_m_axis_tvalid !== 1'b0) $fatal(1, "M_AXIS valid while idle");
            end
            @(negedge i_clk);
            r_m_axis_tready = 1'b0;
        end
    endtask

    initial begin
        reset_dut;
        axi_write(32'd1 << 2, 32'd123);
        axi_read_check(32'd1 << 2, 32'd123);
        axis_write64;
        axi_read_check(32'd65 << 2, 32'hA000_0000);
        axi_read_check(32'd128 << 2, 32'hA000_003f);
        axi_write(32'd1 << 2, 32'hB000_0001);
        axi_write(32'd64 << 2, 32'hB000_0040);
        axis_idle_check;
        axi_write(32'd0 << 2, 32'd1);
        axis_read64_check;
        axi_write(32'd129 << 2, 32'd1);
        axi_read_check(32'd129 << 2, 32'd1);
        repeat (5) @(posedge i_clk);
        $display("AXI-Lite top test PASS");
        $finish;
    end
endmodule

测试

bash 复制代码
$:0
AtShell commands:
 0.help                 - list cmd
 1.clean                - clean screen
 2.read                 - read reg
 3.write                - write reg
 4.dma_read             - dma_read
 5.dma_write            - dma_write
 6.app_init_reg         - app_init_reg

# AXI_DMA读64个u32耗时4479ns
$:4
dma_read: regs=1..64 words=64 bytes=256 ns=4479
# 轮询读64个u32耗时34197ns
$:2
read: src=0x43C00000 words=129 ns=34197
相关推荐
xianrenli382 小时前
Android14自动时区更新
android·学习·framework·aosp14·自动时区
LiaoWL1232 小时前
【SpringCloud合集-06】Spring Cloud Gateway网关学习笔记
笔记·学习·spring cloud
Lhappy嘻嘻2 小时前
网络(二)|Java Socket 编程全实战:UDP 回显 / 词典服务器、TCP 单线程→多线程线程池演进
java·开发语言·网络·学习·面试·udp·tcp
鱼听禅2 小时前
ubuntu学习笔记-使用ls-ml-toolkit工具识别Label Studio数据数据训练yolo模型
笔记·学习·ubuntu
xian_wwq13 小时前
【学习笔记】模型权重管理:Safetensors与私有化Hub(23/35)
笔记·学习
其实防守也摸鱼13 小时前
运维--学习阶段问题解答(1)(自测)
linux·运维·服务器·数据库·学习·自动化·命令模式
ctrl_v助手13 小时前
Halcon学习笔记2
人工智能·笔记·学习
YUS云生14 小时前
Python学习笔记·第31天:FastAPI入门——路由、路径参数、查询参数与请求体
笔记·python·学习
铅笔侠_小龙虾15 小时前
Rust 学习目录
开发语言·学习·rust