Axi4Full转SMMR突发读写和单次读写对比

参考

一、用AtShell制作4个命令

用这4个命令结合ila测试axi突发读写和单次读写的差异

参考 zynq中裸机环境下axi4full寄存器读写.csdn

  1. read: 单次读
  2. write 单次写
  3. full_write 突发写64个u32
  4. full_read突发读64个u32

二、slot0.v寄存器映射

整个 IP 提供 64 个 u32 数据寄存器

地址索引 名称 属性 说明
0 CTRL_STATUS R/W 控制与状态寄存器
1~63 DATAx R/W 64 个数据寄存器

TOP

BD

axi4full_ssmr_system_xc7z020.tcl

axi4full_ssmr_system_xc7z010.tcl

AXI 地址映射表

Name Interface Slave Segment Master Base Address Range Master High Address
/axi_lite_ns_sync_pul_0/s_axi s_axi reg0 0x43C0_0000 4K 0x43C0_0FFF

PIN.xdc

xc7z020

bash 复制代码
## IIC
## 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 #(
    // AXI4-Full 地址宽度,常见 FPGA SoC 系统中一般为 32 位。
    parameter P_S_AXI_ADDR_WIDTH = 32,

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

    // AXI4-Full ID 宽度。Zynq PS 侧连接时可按 BD 自动生成的宽度调整。
    parameter P_S_AXI_ID_WIDTH   = 1,

    // AXI4-Full slave 基地址,和 Vitis 软件里的 BASE_ADDR 保持一致。
    parameter [P_S_AXI_ADDR_WIDTH-1:0] P_S_BASE_ADDR = 32'h43c0_0000,

    // AXI4-Full 可访问的寄存器窗口大小,单位是字节。
    // 512 字节等价于 128 个 32 位寄存器。
    parameter P_S_MEM_SIZE       = 512,

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

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

    // AXI4-Full 写地址通道。
    input  wire [P_S_AXI_ID_WIDTH-1:0]       s_axi_awid,
    input  wire [P_S_AXI_ADDR_WIDTH-1:0]     s_axi_awaddr,
    input  wire [7:0]                        s_axi_awlen,
    input  wire [2:0]                        s_axi_awsize,
    input  wire [1:0]                        s_axi_awburst,
    input  wire                              s_axi_awlock,
    input  wire [3:0]                        s_axi_awcache,
    input  wire [2:0]                        s_axi_awprot,
    input  wire [3:0]                        s_axi_awqos,
    input  wire                              s_axi_awvalid,
    output wire                              s_axi_awready,

    // AXI4-Full 写数据通道。
    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_wlast,
    input  wire                              s_axi_wvalid,
    output wire                              s_axi_wready,

    // AXI4-Full 写响应通道。
    output wire [P_S_AXI_ID_WIDTH-1:0]       s_axi_bid,
    output wire [1:0]                        s_axi_bresp,
    output wire                              s_axi_bvalid,
    input  wire                              s_axi_bready,

    // AXI4-Full 读地址通道。
    input  wire [P_S_AXI_ID_WIDTH-1:0]       s_axi_arid,
    input  wire [P_S_AXI_ADDR_WIDTH-1:0]     s_axi_araddr,
    input  wire [7:0]                        s_axi_arlen,
    input  wire [2:0]                        s_axi_arsize,
    input  wire [1:0]                        s_axi_arburst,
    input  wire                              s_axi_arlock,
    input  wire [3:0]                        s_axi_arcache,
    input  wire [2:0]                        s_axi_arprot,
    input  wire [3:0]                        s_axi_arqos,
    input  wire                              s_axi_arvalid,
    output wire                              s_axi_arready,

    // AXI4-Full 读数据通道。
    output wire [P_S_AXI_ID_WIDTH-1:0]       s_axi_rid,
    output wire [P_S_AXI_DATA_WIDTH-1:0]     s_axi_rdata,
    output wire [1:0]                        s_axi_rresp,
    output wire                              s_axi_rlast,
    output wire                              s_axi_rvalid,
    input  wire                              s_axi_rready,

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

    // slot0 实验输出。
    output wire                              o_irq,
    output wire                              o_debug
);

    ////////////////////////////////////////////////////////////////////////////
    // AXI4-Full 桥接模块到 SMMR 路由模块之间的信号
    ////////////////////////////////////////////////////////////////////////////

    // AXI4-Full 每个写 beat 被接收后产生的单周期写脉冲。
    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;
    wire                              w_reg_rd_en;

    // 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 [P_REG_ADDR_WIDTH-3:0]       w_slot_wr_addr;
    wire [P_REG_ADDR_WIDTH-3: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                              w_slot0_rd_en;
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_slot0_rd_data;

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

    ////////////////////////////////////////////////////////////////////////////
    // AXI4-Full 从接口转 SMMR 桥接模块
    //
    // 该模块负责处理 AXI4-Full 的地址、数据、响应、burst 和握手信号。
    // 对内部模块来说,它只输出简单的 SMMR 寄存器读写接口。
    ////////////////////////////////////////////////////////////////////////////
    axi4full_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_AXI_ID_WIDTH  (P_S_AXI_ID_WIDTH),
        .P_S_BASE_ADDR     (P_S_BASE_ADDR),
        .P_S_MEM_SIZE      (P_S_MEM_SIZE),
        .P_REG_ADDR_WIDTH  (P_REG_ADDR_WIDTH)
    ) u_axi4full_slave_smmr_bridge (
        .s_axi_aclk     (s_axi_aclk),
        .s_axi_aresetn  (s_axi_aresetn),

        .s_axi_awid     (s_axi_awid),
        .s_axi_awaddr   (s_axi_awaddr),
        .s_axi_awlen    (s_axi_awlen),
        .s_axi_awsize   (s_axi_awsize),
        .s_axi_awburst  (s_axi_awburst),
        .s_axi_awlock   (s_axi_awlock),
        .s_axi_awcache  (s_axi_awcache),
        .s_axi_awprot   (s_axi_awprot),
        .s_axi_awqos    (s_axi_awqos),
        .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_wlast    (s_axi_wlast),
        .s_axi_wvalid   (s_axi_wvalid),
        .s_axi_wready   (s_axi_wready),

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

        .s_axi_arid     (s_axi_arid),
        .s_axi_araddr   (s_axi_araddr),
        .s_axi_arlen    (s_axi_arlen),
        .s_axi_arsize   (s_axi_arsize),
        .s_axi_arburst  (s_axi_arburst),
        .s_axi_arlock   (s_axi_arlock),
        .s_axi_arcache  (s_axi_arcache),
        .s_axi_arprot   (s_axi_arprot),
        .s_axi_arqos    (s_axi_arqos),
        .s_axi_arvalid  (s_axi_arvalid),
        .s_axi_arready  (s_axi_arready),

        .s_axi_rid      (s_axi_rid),
        .s_axi_rdata    (s_axi_rdata),
        .s_axi_rresp    (s_axi_rresp),
        .s_axi_rlast    (s_axi_rlast),
        .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),
        .o_reg_rd_en    (w_reg_rd_en),
        .i_reg_rd_data  (w_reg_rd_data)
    );

    ////////////////////////////////////////////////////////////////////////////
    // SMMR slot 路由模块
    //
    // 该模块根据 SMMR 地址范围选择目标 slot。
    // 写操作时,它给对应 slot 产生写使能。
    // 读操作时,它把被选中 slot 的读数据返回给 AXI4-Full 桥接模块。
    //
    // 当前地址映射:
    //   slot0:寄存器地址 0 到 64,CTRL_STATUS + 64 个实验数据寄存器
    //   slot1:寄存器地址 65 到 80,16 个普通测试寄存器
    ////////////////////////////////////////////////////////////////////////////
    smmr_slot_router u_smmr_slot_hub (
        .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),
        .i_reg_rd_en    (w_reg_rd_en),
        .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),
        .o_slot0_rd_en  (w_slot0_rd_en),
        .i_slot0_rd_data(w_slot0_rd_data),

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

    ////////////////////////////////////////////////////////////////////////////
    // slot0 外设
    //
    // slot0 是一个简单寄存器块。
    // 它可以作为新增 SMMR 外设的模板:只需要接入写使能、地址、写数据、
    // 读地址和读数据,就可以挂到系统中。
    ////////////////////////////////////////////////////////////////////////////
    slot0 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),
        .i_reg_rd_en    (w_slot0_rd_en),
        .o_reg_rd_data  (w_slot0_rd_data),
        .o_irq          (o_irq),
        .o_debug        (o_debug)
    );

    ////////////////////////////////////////////////////////////////////////////
    // slot1 外设
    //
    // slot1 也是一个 SMMR 寄存器块,同时多了一个实际硬件输出 o_led。
    // 这展示了如何通过寄存器写入来控制 FPGA 内部或外部硬件信号。
    ////////////////////////////////////////////////////////////////////////////
    slot1 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),
        .i_reg_rd_en    (w_slot1_rd_en),
        .o_reg_rd_data  (w_slot1_rd_data),
        .o_led          (o_led)
    );

endmodule

axi4full_slave_smmr_bridge.v

verilog 复制代码
// AXI4-Full 从接口到简单内存映射寄存器总线(SMMR)的桥接模块。
// 内部 SMMR 总线保持和原 AXI-Lite 版本一致:
// 地址按字节寻址,每个有效写 beat 产生 1 个周期的 o_reg_wr_en。
module axi4full_slave_smmr_bridge #(
    // AXI 地址宽度、数据宽度和 ID 宽度。
    parameter P_S_AXI_ADDR_WIDTH = 32,
    parameter P_S_AXI_DATA_WIDTH = 32,
    parameter P_S_AXI_ID_WIDTH   = 1,
    // AXI 绝对基地址。主机既可以访问绝对地址,也可以访问 0 起始的相对偏移。
    parameter [P_S_AXI_ADDR_WIDTH-1:0] P_S_BASE_ADDR = 32'h43c0_0000,
    // 本桥接窗口大小,单位是字节。
    parameter P_S_MEM_SIZE       = 512,
    // 输出给 SMMR 侧的寄存器地址宽度。
    parameter P_REG_ADDR_WIDTH   = 9
)(
    input  wire                              s_axi_aclk,
    input  wire                              s_axi_aresetn,

    // =========================
    // AXI4-Full 写地址通道
    // =========================
    input  wire [P_S_AXI_ID_WIDTH-1:0]       s_axi_awid,
    input  wire [P_S_AXI_ADDR_WIDTH-1:0]     s_axi_awaddr,
    input  wire [7:0]                        s_axi_awlen,
    input  wire [2:0]                        s_axi_awsize,
    input  wire [1:0]                        s_axi_awburst,
    input  wire                              s_axi_awlock,
    input  wire [3:0]                        s_axi_awcache,
    input  wire [2:0]                        s_axi_awprot,
    input  wire [3:0]                        s_axi_awqos,
    input  wire                              s_axi_awvalid,
    output wire                              s_axi_awready,

    // =========================
    // AXI4-Full 写数据通道
    // =========================
    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_wlast,
    input  wire                              s_axi_wvalid,
    output wire                              s_axi_wready,

    // =========================
    // AXI4-Full 写响应通道
    // =========================
    output reg  [P_S_AXI_ID_WIDTH-1:0]       s_axi_bid,
    output reg  [1:0]                        s_axi_bresp,
    output reg                               s_axi_bvalid,
    input  wire                              s_axi_bready,

    // =========================
    // AXI4-Full 读地址通道
    // =========================
    input  wire [P_S_AXI_ID_WIDTH-1:0]       s_axi_arid,
    input  wire [P_S_AXI_ADDR_WIDTH-1:0]     s_axi_araddr,
    input  wire [7:0]                        s_axi_arlen,
    input  wire [2:0]                        s_axi_arsize,
    input  wire [1:0]                        s_axi_arburst,
    input  wire                              s_axi_arlock,
    input  wire [3:0]                        s_axi_arcache,
    input  wire [2:0]                        s_axi_arprot,
    input  wire [3:0]                        s_axi_arqos,
    input  wire                              s_axi_arvalid,
    output wire                              s_axi_arready,

    // =========================
    // AXI4-Full 读数据通道
    // =========================
    output reg  [P_S_AXI_ID_WIDTH-1:0]       s_axi_rid,
    output wire [P_S_AXI_DATA_WIDTH-1:0]     s_axi_rdata,
    output wire [1:0]                        s_axi_rresp,
    output wire                              s_axi_rlast,
    output reg                               s_axi_rvalid,
    input  wire                              s_axi_rready,

    // =========================
    // 简单寄存器总线(SMMR)
    // =========================
    // 写通道:每个有效写 beat 拉高 1 个周期。
    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,
    // 读通道:地址提前给出,主机真正接收该 beat 时 o_reg_rd_en 拉高。
    output wire [P_REG_ADDR_WIDTH-1:0]       o_reg_rd_addr,
    output wire                              o_reg_rd_en,
    input  wire [P_S_AXI_DATA_WIDTH-1:0]     i_reg_rd_data
);

    function integer clogb2;
        input integer value;
        integer i;
        begin
            value = value - 1;
            for (i = 0; value > 0; i = i + 1) begin
                value = value >> 1;
            end
            clogb2 = i;
        end
    endfunction

    // AXI 响应编码和本桥接支持的 burst 类型。
    localparam [1:0] C_AXI_RESP_OKAY  = 2'b00;
    localparam [1:0] C_AXI_RESP_SLVERR = 2'b10;
    localparam [1:0] C_AXI_BURST_FIXED = 2'b00;
    localparam [1:0] C_AXI_BURST_INCR  = 2'b01;
    // 当前设计按 32-bit 寄存器访问,AWSIZE/ARSIZE 必须等于数据总线字节数。
    localparam integer C_AXI_BYTE_NUM   = P_S_AXI_DATA_WIDTH / 8;
    localparam integer C_AXI_BYTE_SHIFT = clogb2(C_AXI_BYTE_NUM);
    localparam [2:0] C_AXI_SIZE         = C_AXI_BYTE_SHIFT[2:0];

    // 写事务状态:保存当前 burst 的地址、长度、计数、size、burst 类型和错误状态。
    reg [P_S_AXI_ADDR_WIDTH-1:0] r_wr_addr;
    reg [7:0]                    r_wr_len;
    reg [7:0]                    r_wr_count;
    reg [2:0]                    r_wr_size;
    reg [1:0]                    r_wr_burst;
    reg                          r_wr_active;
    reg                          r_wr_error;

    // 读事务状态:保存当前 burst 的地址、长度、计数、size、burst 类型和错误状态。
    reg [P_S_AXI_ADDR_WIDTH-1:0] r_rd_addr;
    reg [7:0]                    r_rd_len;
    reg [7:0]                    r_rd_count;
    reg [2:0]                    r_rd_size;
    reg [1:0]                    r_rd_burst;
    reg                          r_rd_active;
    reg                          r_rd_error;

    // AXI 握手命中信号:valid 和 ready 同周期为 1 时表示该通道完成一次传输。
    wire w_aw_fire = s_axi_awvalid && s_axi_awready;
    wire w_w_fire  = s_axi_wvalid  && s_axi_wready;
    wire w_b_fire  = s_axi_bvalid  && s_axi_bready;
    wire w_ar_fire = s_axi_arvalid && s_axi_arready;
    wire w_r_fire  = s_axi_rvalid  && s_axi_rready;

    // 地址支持两种形式:
    // 1. 绝对地址:P_S_BASE_ADDR + offset;
    // 2. 相对地址:直接使用 offset。
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_wr_abs_offset = r_wr_addr - P_S_BASE_ADDR;
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_rd_abs_offset = r_rd_addr - P_S_BASE_ADDR;
    wire                          w_wr_abs_range  = (r_wr_addr >= P_S_BASE_ADDR) &&
                                                    (w_wr_abs_offset < P_S_MEM_SIZE);
    wire                          w_rd_abs_range  = (r_rd_addr >= P_S_BASE_ADDR) &&
                                                    (w_rd_abs_offset < P_S_MEM_SIZE);
    wire                          w_wr_rel_range  = (r_wr_addr < P_S_MEM_SIZE);
    wire                          w_rd_rel_range  = (r_rd_addr < P_S_MEM_SIZE);
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_wr_offset     = w_wr_abs_range ? w_wr_abs_offset : r_wr_addr;
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_rd_offset     = w_rd_abs_range ? w_rd_abs_offset : r_rd_addr;
    wire [P_REG_ADDR_WIDTH-1:0]   w_wr_addr_offset = w_wr_offset[P_REG_ADDR_WIDTH-1:0];
    wire [P_REG_ADDR_WIDTH-1:0]   w_rd_addr_offset = w_rd_offset[P_REG_ADDR_WIDTH-1:0];

    // 写访问合法性检查:只支持 FIXED/INCR、完整 32-bit strobe、窗口范围内访问。
    wire w_wr_burst_ok      = (r_wr_burst == C_AXI_BURST_FIXED) || (r_wr_burst == C_AXI_BURST_INCR);
    wire w_wr_size_ok       = (r_wr_size == C_AXI_SIZE);
    wire w_wr_addr_ok       = w_wr_abs_range || w_wr_rel_range;
    wire w_wr_strb_full     = &s_axi_wstrb;
    wire w_wr_last_expected = (r_wr_count == r_wr_len);
    wire w_wr_last_fire     = w_w_fire && (s_axi_wlast || w_wr_last_expected);
    wire w_wr_last_error    = w_w_fire && (s_axi_wlast != w_wr_last_expected);
    wire w_wr_access_ok     = w_wr_burst_ok && w_wr_size_ok && w_wr_addr_ok && w_wr_strb_full;
    wire w_wr_beat_error    = r_wr_error || !w_wr_access_ok || w_wr_last_error;
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_wr_next_addr =
        (r_wr_burst == C_AXI_BURST_INCR) ? (r_wr_addr + C_AXI_BYTE_NUM) : r_wr_addr;

    // 读访问合法性检查:只支持 FIXED/INCR,并要求地址在本桥接窗口内。
    wire w_rd_burst_ok      = (r_rd_burst == C_AXI_BURST_FIXED) || (r_rd_burst == C_AXI_BURST_INCR);
    wire w_rd_size_ok       = (r_rd_size == C_AXI_SIZE);
    wire w_rd_addr_ok       = w_rd_abs_range || w_rd_rel_range;
    wire w_rd_last          = (r_rd_count == r_rd_len);
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_rd_next_addr =
        (r_rd_burst == C_AXI_BURST_INCR) ? (r_rd_addr + C_AXI_BYTE_NUM) : r_rd_addr;

    assign s_axi_awready = s_axi_aresetn && !r_wr_active && !s_axi_bvalid;
    assign s_axi_wready  = s_axi_aresetn && r_wr_active && !s_axi_bvalid;
    assign s_axi_arready = s_axi_aresetn && !r_rd_active && !s_axi_rvalid;

    // SMMR 读地址直接由当前 AXI 读地址换算得到。
    // o_reg_rd_en 在读数据 beat 被主机接收时拉高,用作"该读 beat 已消费"的脉冲。
    assign o_reg_rd_addr = w_rd_addr_offset;
    assign o_reg_rd_en   = w_r_fire;
    assign s_axi_rdata   = w_rd_addr_ok ? i_reg_rd_data : {P_S_AXI_DATA_WIDTH{1'b0}};
    assign s_axi_rresp   = (r_rd_error || !w_rd_addr_ok) ? C_AXI_RESP_SLVERR : C_AXI_RESP_OKAY;
    assign s_axi_rlast   = s_axi_rvalid && w_rd_last;

    // 写通道状态机:先接收 AW,再逐 beat 接收 W;每个合法 W beat 转成 SMMR 写脉冲。
    // burst 结束后返回 B 响应,若期间发生 size/burst/address/last/strobe 错误则返回 SLVERR。
    always @(posedge s_axi_aclk or negedge s_axi_aresetn) begin
        if (!s_axi_aresetn) begin
            r_wr_addr      <= {P_S_AXI_ADDR_WIDTH{1'b0}};
            r_wr_len       <= 8'd0;
            r_wr_count     <= 8'd0;
            r_wr_size      <= 3'd0;
            r_wr_burst     <= C_AXI_BURST_INCR;
            r_wr_active    <= 1'b0;
            r_wr_error     <= 1'b0;
            s_axi_bid      <= {P_S_AXI_ID_WIDTH{1'b0}};
            s_axi_bresp    <= C_AXI_RESP_OKAY;
            s_axi_bvalid   <= 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}};
        end else begin
            // 写使能默认拉低,只有合法写 beat 到来时拉高 1 个周期。
            o_reg_wr_en <= 1'b0;

            if (w_b_fire) begin
                s_axi_bvalid <= 1'b0;
            end

            if (w_aw_fire) begin
                r_wr_addr   <= s_axi_awaddr;
                r_wr_len    <= s_axi_awlen;
                r_wr_count  <= 8'd0;
                r_wr_size   <= s_axi_awsize;
                r_wr_burst  <= s_axi_awburst;
                r_wr_active <= 1'b1;
                r_wr_error  <= (s_axi_awsize != C_AXI_SIZE) ||
                               !((s_axi_awburst == C_AXI_BURST_FIXED) ||
                                 (s_axi_awburst == C_AXI_BURST_INCR));
                s_axi_bid   <= s_axi_awid;
            end

            if (w_w_fire) begin
                // 只有当前 beat 完全合法时才真正写 SMMR 寄存器。
                if (w_wr_access_ok) begin
                    o_reg_wr_en   <= 1'b1;
                    o_reg_wr_addr <= w_wr_addr_offset;
                    o_reg_wr_data <= s_axi_wdata;
                end

                if (w_wr_last_fire) begin
                    r_wr_active  <= 1'b0;
                    r_wr_error   <= 1'b0;
                    s_axi_bresp  <= w_wr_beat_error ? C_AXI_RESP_SLVERR : C_AXI_RESP_OKAY;
                    s_axi_bvalid <= 1'b1;
                end else begin
                    // INCR burst 递增地址,FIXED burst 保持地址不变。
                    r_wr_count <= r_wr_count + 8'd1;
                    r_wr_addr  <= w_wr_next_addr;
                    r_wr_error <= w_wr_beat_error;
                end
            end
        end
    end

    // 读通道状态机:接收 AR 后立即产生第一个 R beat;每个 R beat 被接收后推进计数和地址。
    // 对非法 size/burst/address 仍完成 AXI 响应,但 RRESP 返回 SLVERR,RDATA 返回 0。
    always @(posedge s_axi_aclk or negedge s_axi_aresetn) begin
        if (!s_axi_aresetn) begin
            r_rd_addr    <= {P_S_AXI_ADDR_WIDTH{1'b0}};
            r_rd_len     <= 8'd0;
            r_rd_count   <= 8'd0;
            r_rd_size    <= 3'd0;
            r_rd_burst   <= C_AXI_BURST_INCR;
            r_rd_active  <= 1'b0;
            r_rd_error   <= 1'b0;
            s_axi_rid    <= {P_S_AXI_ID_WIDTH{1'b0}};
            s_axi_rvalid <= 1'b0;
        end else begin
            if (w_ar_fire) begin
                r_rd_addr    <= s_axi_araddr;
                r_rd_len     <= s_axi_arlen;
                r_rd_count   <= 8'd0;
                r_rd_size    <= s_axi_arsize;
                r_rd_burst   <= s_axi_arburst;
                r_rd_active  <= 1'b1;
                r_rd_error   <= (s_axi_arsize != C_AXI_SIZE) ||
                                !((s_axi_arburst == C_AXI_BURST_FIXED) ||
                                  (s_axi_arburst == C_AXI_BURST_INCR));
                s_axi_rid    <= s_axi_arid;
                s_axi_rvalid <= 1'b1;
            end else if (w_r_fire) begin
                if (w_rd_last) begin
                    r_rd_active  <= 1'b0;
                    r_rd_error   <= 1'b0;
                    s_axi_rvalid <= 1'b0;
                end else begin
                    // INCR burst 递增地址,FIXED burst 保持地址不变。
                    r_rd_count <= r_rd_count + 8'd1;
                    r_rd_addr  <= w_rd_next_addr;
                end
            end
        end
    end

    // 将未使用的 AXI sideband 输入归并到一个 wire,避免部分 lint 流程报警未引用端口。
    wire w_unused_axi_sideband = s_axi_awlock ^ s_axi_arlock ^
                                 ^s_axi_awcache ^ ^s_axi_arcache ^
                                 ^s_axi_awprot  ^ ^s_axi_arprot  ^
                                 ^s_axi_awqos   ^ ^s_axi_arqos;

endmodule

smmr_slot_router.v

verilog 复制代码
////////////////////////////////////////////////////////////////////////////////
// 简单内存映射寄存器路由模块
// - 将字节地址转换成 32 位寄存器索引
// - 将一路 SMMR 接口路由到多个寄存器槽
////////////////////////////////////////////////////////////////////////////////
module smmr_slot_router #(
    // AXI4-Full/SMMR 侧使用字节地址,寄存器槽侧使用寄存器索引。
    parameter P_ADDR_WIDTH = 9,
    parameter P_DATA_WIDTH = 32,
    // 寄存器槽地址范围以 32 位寄存器为单位。
    // slot0: 65 个 u32,寄存器索引 0..64,用于 CTRL_STATUS + 64 个实验数据。
    parameter P_SLOT0_BASE = 7'd0,
    parameter P_SLOT0_SIZE = 7'd65,
    // slot1: 16 个 u32,寄存器索引 65..80,用于普通读写测试。
    parameter P_SLOT1_BASE = 7'd65,
    parameter P_SLOT1_SIZE = 7'd16
)(
    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,
    input  wire                    i_reg_rd_en,
    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,
    output wire                    o_slot0_rd_en,
    input  wire [P_DATA_WIDTH-1:0] i_slot0_rd_data,
    output wire                    o_slot1_wr_en,
    output wire                    o_slot1_rd_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_slot0_rd_en  = i_reg_rd_en && r_slot0_rd_sel;
    assign o_slot1_rd_en  = i_reg_rd_en && r_slot1_rd_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 复制代码
module slot0 #(
    parameter P_ADDR_WIDTH = 7,
    parameter P_DATA_WIDTH = 32,
    parameter [P_ADDR_WIDTH-1:0] P_SLOT_BASE = 7'd0,
    parameter P_REG_COUNT  = 65
)(
    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,
    input  wire                    i_reg_rd_en,
    output reg  [P_DATA_WIDTH-1:0] o_reg_rd_data,
    output reg                     o_irq,
    output reg                     o_debug
);

    localparam [P_ADDR_WIDTH-1:0] C_ADDR_CTRL      = P_SLOT_BASE;
    localparam [P_ADDR_WIDTH-1:0] C_ADDR_DATA_BASE = P_SLOT_BASE + 7'd1;
    localparam [P_ADDR_WIDTH-1:0] C_ADDR_DATA_LAST = P_SLOT_BASE + 7'd64;
    localparam [63:0]             C_ALL_DATA_SEEN  = {64{1'b1}};

    reg [P_DATA_WIDTH-1:0] r_data [0:63];
    reg [63:0]             r_data_seen;
    reg                    r_busy;
    reg                    r_done;
    reg                    r_irq_en;
    reg                    r_process_pending;

    wire w_ctrl_wr = i_reg_wr_en && (i_reg_wr_addr == C_ADDR_CTRL);
    wire w_data_wr = i_reg_wr_en &&
                     (i_reg_wr_addr >= C_ADDR_DATA_BASE) &&
                     (i_reg_wr_addr <= C_ADDR_DATA_LAST);
    wire w_data_rd = i_reg_rd_en &&
                     (i_reg_rd_addr >= C_ADDR_DATA_BASE) &&
                     (i_reg_rd_addr <= C_ADDR_DATA_LAST);

    wire [5:0]  w_wr_data_index = i_reg_wr_addr - C_ADDR_DATA_BASE;
    wire [5:0]  w_rd_data_index = i_reg_rd_addr - C_ADDR_DATA_BASE;
    wire [63:0] w_wr_data_bit   = 64'h1 << w_wr_data_index;
    wire [63:0] w_seen_base     = (w_wr_data_index == 6'd0) ? 64'd0 : r_data_seen;
    wire [63:0] w_seen_next     = w_seen_base | w_wr_data_bit;

    integer i;
    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            for (i = 0; i < 64; i = i + 1) begin
                r_data[i] <= {P_DATA_WIDTH{1'b0}};
            end
            r_data_seen       <= 64'd0;
            r_busy            <= 1'b0;
            r_done            <= 1'b0;
            r_irq_en          <= 1'b0;
            r_process_pending <= 1'b0;
            o_irq             <= 1'b0;
            o_debug           <= 1'b0;
        end else begin
            if (r_process_pending) begin
                for (i = 0; i < 64; i = i + 1) begin
                    r_data[i] <= r_data[i];
                end
                r_busy            <= 1'b0;
                r_done            <= 1'b1;
                r_process_pending <= 1'b0;
                if (r_irq_en) begin
                    o_irq <= 1'b1;
                end
            end

            if (w_ctrl_wr) begin
                r_irq_en <= i_reg_wr_data[2];
                if (i_reg_wr_data[3]) begin
                    r_done <= 1'b0;
                    o_irq  <= 1'b0;
                end
            end else if (w_data_wr && !r_process_pending) begin
                r_data[w_wr_data_index] <= i_reg_wr_data;
                r_data_seen             <= w_seen_next;

                if (w_wr_data_index == 6'd0) begin
                    r_done  <= 1'b0;
                    o_irq   <= 1'b0;
                    o_debug <= 1'b1;
                end

                if (w_seen_next == C_ALL_DATA_SEEN) begin
                    r_data_seen       <= 64'd0;
                    r_busy            <= 1'b1;
                    r_process_pending <= 1'b1;
                    o_debug           <= 1'b0;
                end
            end

            if (w_data_rd) begin
                if (w_rd_data_index == 6'd0) begin
                    o_debug <= 1'b1;
                end
                if (w_rd_data_index == 6'd63) begin
                    o_debug <= 1'b0;
                end
            end
        end
    end

    always @(*) begin
        if (i_reg_rd_addr == C_ADDR_CTRL) begin
            o_reg_rd_data = {{(P_DATA_WIDTH-4){1'b0}}, 1'b0, r_irq_en, r_done, r_busy};
        end else if ((i_reg_rd_addr >= C_ADDR_DATA_BASE) && (i_reg_rd_addr <= C_ADDR_DATA_LAST)) begin
            o_reg_rd_data = r_data[w_rd_data_index];
        end else begin
            o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
        end
    end

endmodule

slot1.v

verilog 复制代码
module slot1 #(
    parameter P_ADDR_WIDTH = 7,
    parameter P_DATA_WIDTH = 32,
    parameter [P_ADDR_WIDTH-1:0] P_SLOT_BASE = 7'd65,
    parameter P_REG_COUNT  = 16
)(
    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,
    input  wire                    i_reg_rd_en,
    output reg  [P_DATA_WIDTH-1:0] o_reg_rd_data,
    output reg                     o_led
);

    localparam [P_ADDR_WIDTH-1:0] C_ADDR_CONTROL = P_SLOT_BASE;

    reg [P_DATA_WIDTH-1:0] r_mem [0:P_REG_COUNT-1];

    wire w_wr_hit = i_reg_wr_en &&
                    (i_reg_wr_addr >= P_SLOT_BASE) &&
                    (i_reg_wr_addr < (P_SLOT_BASE + P_REG_COUNT));

    wire [P_ADDR_WIDTH-1:0] w_wr_index = i_reg_wr_addr - P_SLOT_BASE;
    wire [P_ADDR_WIDTH-1:0] w_rd_index = i_reg_rd_addr - P_SLOT_BASE;

    wire w_unused_rd_en = i_reg_rd_en;

    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 <= 1'b0;
        end else if (w_wr_hit) begin
            r_mem[w_wr_index] <= i_reg_wr_data;
            if (i_reg_wr_addr == C_ADDR_CONTROL) begin
                o_led <= i_reg_wr_data[0];
            end
        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[w_rd_index];
        end else begin
            o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
        end
    end

endmodule

axi4full_master.v

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

module axi4full_master #(
    parameter P_AXI_ADDR_WIDTH = 32, // AXI 地址总线宽度,单位 bit。
    parameter P_AXI_DATA_WIDTH = 32, // AXI 数据总线宽度,单位 bit。
    parameter P_AXI_ID_WIDTH   = 1   // AXI 事务 ID 宽度,单位 bit。
)(
    output reg  [P_AXI_ID_WIDTH-1:0]         m_axi_awid,        // 写地址通道 ID,随 AW 通道发送。
    output reg  [P_AXI_ADDR_WIDTH-1:0]       m_axi_awaddr,      // 写 burst 起始字节地址。
    output reg  [7:0]                        m_axi_awlen,       // 写 burst 长度,编码值为传输拍数减 1。
    output reg  [2:0]                        m_axi_awsize,      // 单拍写数据字节数,编码值为 log2(bytes per beat)。
    output reg  [1:0]                        m_axi_awburst,     // 写 burst 类型,本 master 固定发 INCR。
    output reg                               m_axi_awlock,      // 写独占/锁定访问属性,本 master 未使用,固定为 0。
    output reg  [3:0]                        m_axi_awcache,     // 写缓存属性边带信号。
    output reg  [2:0]                        m_axi_awprot,      // 写保护属性边带信号。
    output reg  [3:0]                        m_axi_awqos,       // 写服务质量属性边带信号。
    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,       // 写字节选通信号,每 bit 对应一个字节 lane。
    output reg                               m_axi_wlast,       // 写 burst 最后一拍标志。
    output reg                               m_axi_wvalid,      // 写数据有效握手信号。
    input  wire                              m_axi_wready,      // 从机返回的写数据就绪握手信号。

    input  wire [P_AXI_ID_WIDTH-1:0]         m_axi_bid,         // 从机返回的写响应 ID。
    input  wire [1:0]                        m_axi_bresp,       // 从机返回的写响应状态。
    input  wire                              m_axi_bvalid,      // 从机返回的写响应有效握手信号。
    output reg                               m_axi_bready,      // 写响应就绪握手信号。

    output reg  [P_AXI_ID_WIDTH-1:0]         m_axi_arid,        // 读地址通道 ID,随 AR 通道发送。
    output reg  [P_AXI_ADDR_WIDTH-1:0]       m_axi_araddr,      // 读 burst 起始字节地址。
    output reg  [7:0]                        m_axi_arlen,       // 读 burst 长度,编码值为传输拍数减 1。
    output reg  [2:0]                        m_axi_arsize,      // 单拍读数据字节数,编码值为 log2(bytes per beat)。
    output reg  [1:0]                        m_axi_arburst,     // 读 burst 类型,本 master 固定发 INCR。
    output reg                               m_axi_arlock,      // 读独占/锁定访问属性,本 master 未使用,固定为 0。
    output reg  [3:0]                        m_axi_arcache,     // 读缓存属性边带信号。
    output reg  [2:0]                        m_axi_arprot,      // 读保护属性边带信号。
    output reg  [3:0]                        m_axi_arqos,       // 读服务质量属性边带信号。
    output reg                               m_axi_arvalid,     // 读地址有效握手信号。
    input  wire                              m_axi_arready,     // 从机返回的读地址就绪握手信号。

    input  wire [P_AXI_ID_WIDTH-1:0]         m_axi_rid,         // 从机返回的读数据 ID。
    input  wire [P_AXI_DATA_WIDTH-1:0]       m_axi_rdata,       // 从机返回的当前读数据拍。
    input  wire [1:0]                        m_axi_rresp,       // 当前读数据拍对应的响应状态。
    input  wire                              m_axi_rlast,       // 读 burst 最后一拍标志。
    input  wire                              m_axi_rvalid,      // 从机返回的读数据有效握手信号。
    output reg                               m_axi_rready,      // 读数据就绪握手信号。

    input  wire                              i_clk,             // master 内部逻辑时钟。
    input  wire                              i_rst_n,           // 低有效异步复位。

    input  wire                              i_wr_start,        // 写命令启动脉冲,高 1 拍启动一次写 burst。
    input  wire [P_AXI_ADDR_WIDTH-1:0]       i_wr_addr,         // 写命令起始字节地址。
    input  wire [7:0]                        i_wr_len,          // 写命令 burst 长度,编码值为传输拍数减 1。
    input  wire [P_AXI_DATA_WIDTH-1:0]       i_wr_data,         // 写命令首拍数据,后续每拍自动加 1。
    output reg                               o_wr_busy,         // 写命令执行中标志。
    output reg                               o_wr_done,         // 写命令完成脉冲,高 1 拍。
    output reg  [1:0]                        o_wr_resp,         // 从 B 通道采集到的最终写响应。

    input  wire                              i_rd_start,        // 读命令启动脉冲,高 1 拍启动一次读 burst。
    input  wire [P_AXI_ADDR_WIDTH-1:0]       i_rd_addr,         // 读命令起始字节地址。
    input  wire [7:0]                        i_rd_len,          // 读命令 burst 长度,编码值为传输拍数减 1。
    output reg                               o_rd_busy,         // 读命令执行中标志。
    output reg                               o_rd_done,         // 读命令完成脉冲,高 1 拍。
    output reg  [P_AXI_DATA_WIDTH-1:0]       o_rd_data,         // 从 R 通道采集到的最后一拍读数据。
    output reg  [1:0]                        o_rd_resp,         // 整个读 burst 聚合后的响应状态。
    output reg                               o_rd_beat_valid,   // 每接收 1 拍读数据就拉高 1 拍。
    output reg  [7:0]                        o_rd_beat_index,   // 当前接收读数据拍的从 0 开始的序号。
    output reg  [P_AXI_DATA_WIDTH-1:0]       o_rd_beat_data,    // 当前接收读数据拍的数值。
    output reg                               o_rd_beat_last     // 当前接收读数据拍携带的 RLAST 标志。
);
    function integer clogb2;
        input integer value; // 需要计算向上取整 log2 的输入值。
        integer i;           // clogb2 函数内部使用的循环计数器。
        begin
            value = value - 1;
            for (i = 0; value > 0; i = i + 1) begin
                value = value >> 1;
            end
            clogb2 = i;
        end
    endfunction

    localparam [1:0] C_AXI_RESP_OKAY  = 2'b00; // AXI OKAY 响应编码。
    localparam [1:0] C_AXI_RESP_SLVERR = 2'b10; // AXI 从机错误响应编码。
    localparam [1:0] C_AXI_BURST_INCR  = 2'b01; // AXI 递增 burst 类型编码。
    localparam integer C_AXI_BYTE_NUM   = P_AXI_DATA_WIDTH / 8; // 每拍数据包含的字节 lane 数量。
    localparam integer C_AXI_BYTE_SHIFT = clogb2(C_AXI_BYTE_NUM); // 每拍地址递增对应的移位值。
    localparam [2:0] C_AXI_SIZE         = C_AXI_BYTE_SHIFT[2:0]; // 当前数据宽度对应的 AXI AxSIZE 编码。

    localparam [1:0] WR_IDLE = 2'd0; // 写状态机空闲,等待 i_wr_start。
    localparam [1:0] WR_ADDR = 2'd1; // 写状态机发送 AW 地址通道。
    localparam [1:0] WR_DATA = 2'd2; // 写状态机发送 W 数据通道各个 beat。
    localparam [1:0] WR_RESP = 2'd3; // 写状态机等待 B 响应通道。

    localparam [1:0] RD_IDLE = 2'd0; // 读状态机空闲,等待 i_rd_start。
    localparam [1:0] RD_ADDR = 2'd1; // 读状态机发送 AR 地址通道。
    localparam [1:0] RD_DATA = 2'd2; // 读状态机接收 R 数据通道各个 beat。

    reg [1:0] r_wr_state; // 当前写状态机状态。
    reg [7:0] r_wr_len; // 锁存的写 burst 长度,编码值为传输拍数减 1。
    reg [7:0] r_wr_count; // 从 0 开始计数的写数据拍计数器。
    reg [P_AXI_DATA_WIDTH-1:0] r_wr_base_data; // 锁存的首拍写数据。

    reg [1:0] r_rd_state; // 当前读状态机状态。
    reg [7:0] r_rd_len; // 锁存的读 burst 长度,编码值为传输拍数减 1。
    reg [7:0] r_rd_count; // 从 0 开始计数的读数据拍计数器。
    reg       r_rd_error; // 整个读 burst 中的粘滞错误标志。

    wire w_unused_axi_id = ^m_axi_bid ^ ^m_axi_rid; // 消耗响应 ID,避免综合/仿真工具报未使用告警。

    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            r_wr_state    <= WR_IDLE;
            r_wr_len      <= 8'd0;
            r_wr_count    <= 8'd0;
            r_wr_base_data <= {P_AXI_DATA_WIDTH{1'b0}};
            m_axi_awid    <= {P_AXI_ID_WIDTH{1'b0}};
            m_axi_awaddr  <= {P_AXI_ADDR_WIDTH{1'b0}};
            m_axi_awlen   <= 8'd0;
            m_axi_awsize  <= C_AXI_SIZE;
            m_axi_awburst <= C_AXI_BURST_INCR;
            m_axi_awlock  <= 1'b0;
            m_axi_awcache <= 4'b0011;
            m_axi_awprot  <= 3'b000;
            m_axi_awqos   <= 4'b0000;
            m_axi_awvalid <= 1'b0;
            m_axi_wdata   <= {P_AXI_DATA_WIDTH{1'b0}};
            m_axi_wstrb   <= {C_AXI_BYTE_NUM{1'b0}};
            m_axi_wlast   <= 1'b0;
            m_axi_wvalid  <= 1'b0;
            m_axi_bready  <= 1'b0;
            o_wr_busy     <= 1'b0;
            o_wr_done     <= 1'b0;
            o_wr_resp     <= C_AXI_RESP_OKAY;
        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;
                        r_wr_len       <= i_wr_len;
                        r_wr_count     <= 8'd0;
                        r_wr_base_data <= i_wr_data;
                        m_axi_awid     <= {P_AXI_ID_WIDTH{1'b0}};
                        m_axi_awaddr   <= i_wr_addr;
                        m_axi_awlen    <= i_wr_len;
                        m_axi_awsize   <= C_AXI_SIZE;
                        m_axi_awburst  <= C_AXI_BURST_INCR;
                        m_axi_awlock   <= 1'b0;
                        m_axi_awcache  <= 4'b0011;
                        m_axi_awprot   <= 3'b000;
                        m_axi_awqos    <= 4'b0000;
                        m_axi_awvalid  <= 1'b1;
                        r_wr_state     <= WR_ADDR;
                    end
                end

                WR_ADDR: begin
                    if (m_axi_awvalid && m_axi_awready) begin
                        m_axi_awvalid <= 1'b0;
                        m_axi_wdata   <= r_wr_base_data;
                        m_axi_wstrb   <= {C_AXI_BYTE_NUM{1'b1}};
                        m_axi_wlast   <= (r_wr_len == 8'd0);
                        m_axi_wvalid  <= 1'b1;
                        r_wr_state    <= WR_DATA;
                    end
                end

                WR_DATA: begin
                    if (m_axi_wvalid && m_axi_wready) begin
                        if (r_wr_count == r_wr_len) begin
                            m_axi_wvalid <= 1'b0;
                            m_axi_wlast  <= 1'b0;
                            m_axi_bready <= 1'b1;
                            r_wr_state   <= WR_RESP;
                        end else begin
                            r_wr_count   <= r_wr_count + 8'd1;
                            m_axi_wdata  <= r_wr_base_data + {{(P_AXI_DATA_WIDTH-8){1'b0}}, r_wr_count} + 1'b1;
                            m_axi_wlast  <= ((r_wr_count + 8'd1) == r_wr_len);
                        end
                    end
                end

                WR_RESP: begin
                    if (m_axi_bvalid && m_axi_bready) 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: begin
                    r_wr_state <= WR_IDLE;
                end
            endcase
        end
    end

    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            r_rd_state       <= RD_IDLE;
            r_rd_len         <= 8'd0;
            r_rd_count       <= 8'd0;
            r_rd_error       <= 1'b0;
            m_axi_arid       <= {P_AXI_ID_WIDTH{1'b0}};
            m_axi_araddr     <= {P_AXI_ADDR_WIDTH{1'b0}};
            m_axi_arlen      <= 8'd0;
            m_axi_arsize     <= C_AXI_SIZE;
            m_axi_arburst    <= C_AXI_BURST_INCR;
            m_axi_arlock     <= 1'b0;
            m_axi_arcache    <= 4'b0011;
            m_axi_arprot     <= 3'b000;
            m_axi_arqos      <= 4'b0000;
            m_axi_arvalid    <= 1'b0;
            m_axi_rready     <= 1'b0;
            o_rd_busy        <= 1'b0;
            o_rd_done        <= 1'b0;
            o_rd_data        <= {P_AXI_DATA_WIDTH{1'b0}};
            o_rd_resp        <= C_AXI_RESP_OKAY;
            o_rd_beat_valid  <= 1'b0;
            o_rd_beat_index  <= 8'd0;
            o_rd_beat_data   <= {P_AXI_DATA_WIDTH{1'b0}};
            o_rd_beat_last   <= 1'b0;
        end else begin
            o_rd_done       <= 1'b0;
            o_rd_beat_valid <= 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;
                        r_rd_len      <= i_rd_len;
                        r_rd_count    <= 8'd0;
                        r_rd_error    <= 1'b0;
                        o_rd_resp     <= C_AXI_RESP_OKAY;
                        m_axi_arid    <= {P_AXI_ID_WIDTH{1'b0}};
                        m_axi_araddr  <= i_rd_addr;
                        m_axi_arlen   <= i_rd_len;
                        m_axi_arsize  <= C_AXI_SIZE;
                        m_axi_arburst <= C_AXI_BURST_INCR;
                        m_axi_arlock  <= 1'b0;
                        m_axi_arcache <= 4'b0011;
                        m_axi_arprot  <= 3'b000;
                        m_axi_arqos   <= 4'b0000;
                        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 && m_axi_rready) begin
                        o_rd_data       <= m_axi_rdata;
                        o_rd_beat_valid <= 1'b1;
                        o_rd_beat_index <= r_rd_count;
                        o_rd_beat_data  <= m_axi_rdata;
                        o_rd_beat_last  <= m_axi_rlast;
                        if (m_axi_rresp != C_AXI_RESP_OKAY) begin
                            r_rd_error <= 1'b1;
                        end

                        if (m_axi_rlast || (r_rd_count == r_rd_len)) begin
                            o_rd_resp    <= (r_rd_error || (m_axi_rresp != C_AXI_RESP_OKAY) ||
                                             !m_axi_rlast || (r_rd_count != r_rd_len)) ?
                                            C_AXI_RESP_SLVERR : C_AXI_RESP_OKAY;
                            o_rd_done    <= 1'b1;
                            o_rd_busy    <= 1'b0;
                            m_axi_rready <= 1'b0;
                            r_rd_state   <= RD_IDLE;
                        end else begin
                            r_rd_count <= r_rd_count + 8'd1;
                        end
                    end
                end

                default: begin
                    r_rd_state <= RD_IDLE;
                end
            endcase
        end
    end

endmodule

tb.sv

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

module tb;
    localparam P_AXI_ADDR_WIDTH = 32;
    localparam P_AXI_DATA_WIDTH = 32;
    localparam P_AXI_ID_WIDTH   = 1;
    localparam [P_AXI_ADDR_WIDTH-1:0] C_BASE_ADDR       = 32'h43c0_0000;
    localparam [P_AXI_ADDR_WIDTH-1:0] C_SLOT0_CTRL_ADDR = C_BASE_ADDR + 32'h0000_0000;
    localparam [P_AXI_ADDR_WIDTH-1:0] C_SLOT0_DATA_ADDR = C_BASE_ADDR + 32'h0000_0004;
    localparam [P_AXI_ADDR_WIDTH-1:0] C_SLOT1_BASE_ADDR = C_BASE_ADDR + (32'd65 << 2);

    reg i_clk;
    reg i_rst_n;
    reg                         r_wr_start;
    reg  [P_AXI_ADDR_WIDTH-1:0] r_wr_addr;
    reg  [7:0]                  r_wr_len;
    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;
    reg  [7:0]                  r_rd_len;
    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                        w_rd_beat_valid;
    wire [7:0]                  w_rd_beat_index;
    wire [P_AXI_DATA_WIDTH-1:0] w_rd_beat_data;
    wire                        w_rd_beat_last;
    wire [P_AXI_ADDR_WIDTH-1:0] w_axi_awaddr;
    wire [P_AXI_ID_WIDTH-1:0]   w_axi_awid;
    wire [7:0]                  w_axi_awlen;
    wire [2:0]                  w_axi_awsize;
    wire [1:0]                  w_axi_awburst;
    wire                        w_axi_awlock;
    wire [3:0]                  w_axi_awcache;
    wire [2:0]                  w_axi_awprot;
    wire [3:0]                  w_axi_awqos;
    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_wlast;
    wire                        w_axi_wvalid;
    wire                        w_axi_wready;
    wire [P_AXI_ID_WIDTH-1:0]   w_axi_bid;
    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 [P_AXI_ID_WIDTH-1:0]   w_axi_arid;
    wire [7:0]                  w_axi_arlen;
    wire [2:0]                  w_axi_arsize;
    wire [1:0]                  w_axi_arburst;
    wire                        w_axi_arlock;
    wire [3:0]                  w_axi_arcache;
    wire [2:0]                  w_axi_arprot;
    wire [3:0]                  w_axi_arqos;
    wire                        w_axi_arvalid;
    wire                        w_axi_arready;
    wire [P_AXI_ID_WIDTH-1:0]   w_axi_rid;
    wire [P_AXI_DATA_WIDTH-1:0] w_axi_rdata;
    wire [1:0]                  w_axi_rresp;
    wire                        w_axi_rlast;
    wire                        w_axi_rvalid;
    wire                        w_axi_rready;
    wire                        w_led;
    wire                        w_irq;
    wire                        w_debug;

    axi4full_master #(
        .P_AXI_ADDR_WIDTH(P_AXI_ADDR_WIDTH),
        .P_AXI_DATA_WIDTH(P_AXI_DATA_WIDTH),
        .P_AXI_ID_WIDTH  (P_AXI_ID_WIDTH)
    ) u_axi4full_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_len(r_wr_len), .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), .i_rd_len(r_rd_len),
        .o_rd_busy(w_rd_busy), .o_rd_done(w_rd_done), .o_rd_data(w_rd_data), .o_rd_resp(w_rd_resp),
        .o_rd_beat_valid(w_rd_beat_valid), .o_rd_beat_index(w_rd_beat_index),
        .o_rd_beat_data(w_rd_beat_data), .o_rd_beat_last(w_rd_beat_last),
        .m_axi_awid(w_axi_awid), .m_axi_awaddr(w_axi_awaddr), .m_axi_awlen(w_axi_awlen),
        .m_axi_awsize(w_axi_awsize), .m_axi_awburst(w_axi_awburst), .m_axi_awlock(w_axi_awlock),
        .m_axi_awcache(w_axi_awcache), .m_axi_awprot(w_axi_awprot), .m_axi_awqos(w_axi_awqos),
        .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_wlast(w_axi_wlast),
        .m_axi_wvalid(w_axi_wvalid), .m_axi_wready(w_axi_wready),
        .m_axi_bid(w_axi_bid), .m_axi_bresp(w_axi_bresp), .m_axi_bvalid(w_axi_bvalid), .m_axi_bready(w_axi_bready),
        .m_axi_arid(w_axi_arid), .m_axi_araddr(w_axi_araddr), .m_axi_arlen(w_axi_arlen),
        .m_axi_arsize(w_axi_arsize), .m_axi_arburst(w_axi_arburst), .m_axi_arlock(w_axi_arlock),
        .m_axi_arcache(w_axi_arcache), .m_axi_arprot(w_axi_arprot), .m_axi_arqos(w_axi_arqos),
        .m_axi_arvalid(w_axi_arvalid), .m_axi_arready(w_axi_arready),
        .m_axi_rid(w_axi_rid), .m_axi_rdata(w_axi_rdata), .m_axi_rresp(w_axi_rresp),
        .m_axi_rlast(w_axi_rlast), .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_awid(w_axi_awid), .s_axi_awaddr(w_axi_awaddr), .s_axi_awlen(w_axi_awlen), .s_axi_awsize(w_axi_awsize),
        .s_axi_awburst(w_axi_awburst), .s_axi_awlock(w_axi_awlock), .s_axi_awcache(w_axi_awcache), .s_axi_awprot(w_axi_awprot),
        .s_axi_awqos(w_axi_awqos), .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_wlast(w_axi_wlast), .s_axi_wvalid(w_axi_wvalid), .s_axi_wready(w_axi_wready),
        .s_axi_bid(w_axi_bid), .s_axi_bresp(w_axi_bresp), .s_axi_bvalid(w_axi_bvalid), .s_axi_bready(w_axi_bready),
        .s_axi_arid(w_axi_arid), .s_axi_araddr(w_axi_araddr), .s_axi_arlen(w_axi_arlen), .s_axi_arsize(w_axi_arsize),
        .s_axi_arburst(w_axi_arburst), .s_axi_arlock(w_axi_arlock), .s_axi_arcache(w_axi_arcache), .s_axi_arprot(w_axi_arprot),
        .s_axi_arqos(w_axi_arqos), .s_axi_arvalid(w_axi_arvalid), .s_axi_arready(w_axi_arready),
        .s_axi_rid(w_axi_rid), .s_axi_rdata(w_axi_rdata), .s_axi_rresp(w_axi_rresp), .s_axi_rlast(w_axi_rlast),
        .s_axi_rvalid(w_axi_rvalid), .s_axi_rready(w_axi_rready),
        .o_led(w_led),
        .o_irq(w_irq),
        .o_debug(w_debug)
    );

    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_len <= 8'd0; r_wr_data <= 32'd0;
            r_rd_start <= 1'b0; r_rd_addr <= 32'd0; r_rd_len <= 8'd0;
            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_len <= 8'd0; 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_len <= 8'd0; 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 irq=%0b debug=%0b", $time, addr, w_rd_data, w_rd_resp, w_led, w_irq, w_debug);
            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 axi_burst_write;
        input [P_AXI_ADDR_WIDTH-1:0] addr;
        input [7:0] len;
        input [P_AXI_DATA_WIDTH-1:0] base_data;
        begin
            @(posedge i_clk);
            r_wr_addr <= addr; r_wr_len <= len; r_wr_data <= base_data; r_wr_start <= 1'b1;
            @(posedge i_clk);
            r_wr_start <= 1'b0;
            wait (w_wr_done);
            $display("[%0t ns] AXI BURST WRITE addr=0x%08x beats=%0d base=0x%08x resp=%0d", $time, addr, len + 1, base_data, w_wr_resp);
            if (w_wr_resp != 2'b00) $fatal(1, "AXI burst write response error");
        end
    endtask

    task axi_burst_read_check;
        input [P_AXI_ADDR_WIDTH-1:0] addr;
        input [7:0] len;
        input [P_AXI_DATA_WIDTH-1:0] base_data;
        integer beat;
        begin
            beat = 0;
            @(posedge i_clk);
            r_rd_addr <= addr; r_rd_len <= len; r_rd_start <= 1'b1;
            @(posedge i_clk);
            r_rd_start <= 1'b0;

            while (beat <= len) begin
                @(posedge i_clk);
                if (w_rd_beat_valid) begin
                    $display("[%0t ns] AXI BURST READ beat=%0d data=0x%08x last=%0b", $time, w_rd_beat_index, w_rd_beat_data, w_rd_beat_last);
                    if (w_rd_beat_index !== beat[7:0]) $fatal(1, "AXI burst read index mismatch exp=%0d got=%0d", beat, w_rd_beat_index);
                    if (w_rd_beat_data !== (base_data + beat)) $fatal(1, "AXI burst read data mismatch beat=%0d exp=0x%08x got=0x%08x", beat, base_data + beat, w_rd_beat_data);
                    if ((beat == len) && !w_rd_beat_last) $fatal(1, "AXI burst read missing last");
                    if ((beat != len) && w_rd_beat_last) $fatal(1, "AXI burst read early last beat=%0d", beat);
                    beat = beat + 1;
                end
            end

            wait (w_rd_done);
            if (w_rd_resp != 2'b00) $fatal(1, "AXI burst read response error");
        end
    endtask

    initial begin
        reset_dut;
        axi_write(C_SLOT0_CTRL_ADDR, 32'h0000_0004);
        axi_burst_write(C_SLOT0_DATA_ADDR, 8'd63, 32'h0000_2000);
        repeat (4) @(posedge i_clk);
        if (!w_irq) $fatal(1, "slot0 IRQ was not asserted after 64-data processing");
        if (w_debug) $fatal(1, "slot0 debug should be low after write burst end");
        axi_read_check(C_SLOT0_CTRL_ADDR, 32'h0000_0006);
        axi_burst_read_check(C_SLOT0_DATA_ADDR, 8'd63, 32'h0000_2000);
        repeat (2) @(posedge i_clk);
        if (w_debug) $fatal(1, "slot0 debug should be low after read burst end");
        axi_write(C_SLOT0_CTRL_ADDR, 32'h0000_000c);
        axi_read_check(C_SLOT0_CTRL_ADDR, 32'h0000_0004);
        if (w_irq) $fatal(1, "slot0 IRQ was not cleared");

        axi_write(C_SLOT1_BASE_ADDR, 32'd1);
        axi_read_check(C_SLOT1_BASE_ADDR, 32'd1);
        repeat (5) @(posedge i_clk);
        $display("AXI4-Full 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.full_write           - full_write
 5.full_read            - full_read

$:4
full_write OK dst=0x43C00004 words=64 burst=16 first=0x00000001 last=0x00000040 sum=0x00000820 ms=0

$:5
1               [1] :  1
2               [2] :  2
...
full_read: src=0x43C00004 words=64 burst=16 us=5
$:

在读取64个u32的过程中传输了4个地址,突发了4次

AXI4-Full 协议允许一次 burst 传 64 个 u32,

但 PS PL330 DMA 这个 master 的 burst 配置字段最大是 16 beat

相关推荐
三品吉他手会点灯2 小时前
嵌入式机器学习 - 学习笔记1.1.1 - 什么是机器学习?
c语言·人工智能·笔记·嵌入式硬件·学习·机器学习
程序猿小泓2 小时前
从 Claude Code 学 Agent Harness:一个前端工程师的 AI Agent 学习笔记
前端·人工智能·学习
换个昵称23 小时前
大模型学习8下-高性能推理引擎vLLM学习笔记
笔记·学习·vllm
报错小能手3 小时前
OpenTenBase学习——集群编译&部署
学习
渣渣灰飞4 小时前
MySQL 系统学习 第二阶段 第四章:DCL(Data Control Language)第二节:权限管理与角色(Role)
数据库·学习·mysql
霸道流氓气质4 小时前
Java 工程师 AI 智能体(Agent)完整学习路线
java·人工智能·学习
想要入门的程序猿5 小时前
摄影测量学习()
数码相机·学习
worilb5 小时前
Spring Cloud 学习与实践(13):使用 Seata 解决分布式事务问题
分布式·学习·spring cloud
渣渣灰飞5 小时前
MySQL 系统学习 第四阶段:MySQL 高级 第一节:索引(Index)
数据库·学习·mysql