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

参考

一、用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_apb_system_xc7z020.tcl

axi4full_apb_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 地址宽度、数据宽度和 ID 宽度。
    parameter P_S_AXI_ADDR_WIDTH = 32,
    parameter P_S_AXI_DATA_WIDTH = 32,
    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,

    // 可访问寄存器窗口大小:512 字节,即 128 个 u32。
    parameter P_S_MEM_SIZE       = 512,

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

    parameter P_CLK_FREQ         = 50_000_000
)(
    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,

    output wire                              o_led,
    output wire                              o_irq,
    output wire                              o_debug
);

    ////////////////////////////////////////////////////////////////////////////
    // AXI4-Full -> APB bridge 到 APB router
    ////////////////////////////////////////////////////////////////////////////
    wire [P_REG_ADDR_WIDTH-1:0]       w_apb_paddr;
    wire                              w_apb_psel;
    wire                              w_apb_penable;
    wire                              w_apb_pwrite;
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_apb_pwdata;
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_apb_prdata;
    wire                              w_apb_pready;
    wire                              w_apb_pslverr;

    ////////////////////////////////////////////////////////////////////////////
    // APB router 到 slot0/slot1
    ////////////////////////////////////////////////////////////////////////////
    wire [P_REG_ADDR_WIDTH-1:0]       w_slot_paddr;
    wire                              w_slot_penable;
    wire                              w_slot_pwrite;
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_slot_pwdata;

    wire                              w_slot0_psel;
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_slot0_prdata;
    wire                              w_slot0_pready;
    wire                              w_slot0_pslverr;

    wire                              w_slot1_psel;
    wire [P_S_AXI_DATA_WIDTH-1:0]     w_slot1_prdata;
    wire                              w_slot1_pready;
    wire                              w_slot1_pslverr;

    ////////////////////////////////////////////////////////////////////////////
    // AXI4-Full 从接口转 APB 主接口
    ////////////////////////////////////////////////////////////////////////////
    axi4full_slave_apb_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_APB_ADDR_WIDTH  (P_REG_ADDR_WIDTH),
        .P_APB_DATA_WIDTH  (P_S_AXI_DATA_WIDTH)
    ) u_axi4full_slave_apb_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),

        .m_apb_paddr    (w_apb_paddr),
        .m_apb_psel     (w_apb_psel),
        .m_apb_penable  (w_apb_penable),
        .m_apb_pwrite   (w_apb_pwrite),
        .m_apb_pwdata   (w_apb_pwdata),
        .m_apb_prdata   (w_apb_prdata),
        .m_apb_pready   (w_apb_pready),
        .m_apb_pslverr  (w_apb_pslverr)
    );

    ////////////////////////////////////////////////////////////////////////////
    // APB slot 路由
    //
    // 地址映射:
    //   slot0:reg0..reg64,CTRL_STATUS + 64 个实验数据寄存器
    //   slot1:reg65..reg80,16 个普通测试寄存器
    ////////////////////////////////////////////////////////////////////////////
    apb_slot_router #(
        .P_APB_ADDR_WIDTH(P_REG_ADDR_WIDTH),
        .P_APB_DATA_WIDTH(P_S_AXI_DATA_WIDTH),
        .P_SLOT0_BASE    (7'd0),
        .P_SLOT0_SIZE    (7'd65),
        .P_SLOT1_BASE    (7'd65),
        .P_SLOT1_SIZE    (7'd16),
        .P_UNMAPPED_OK   (1)
    ) u_apb_slot_router (
        .s_apb_paddr     (w_apb_paddr),
        .s_apb_psel      (w_apb_psel),
        .s_apb_penable   (w_apb_penable),
        .s_apb_pwrite    (w_apb_pwrite),
        .s_apb_pwdata    (w_apb_pwdata),
        .s_apb_prdata    (w_apb_prdata),
        .s_apb_pready    (w_apb_pready),
        .s_apb_pslverr   (w_apb_pslverr),

        .m_apb_paddr     (w_slot_paddr),
        .m_apb_penable   (w_slot_penable),
        .m_apb_pwrite    (w_slot_pwrite),
        .m_apb_pwdata    (w_slot_pwdata),

        .m_slot0_psel    (w_slot0_psel),
        .m_slot0_prdata  (w_slot0_prdata),
        .m_slot0_pready  (w_slot0_pready),
        .m_slot0_pslverr (w_slot0_pslverr),

        .m_slot1_psel    (w_slot1_psel),
        .m_slot1_prdata  (w_slot1_prdata),
        .m_slot1_pready  (w_slot1_pready),
        .m_slot1_pslverr (w_slot1_pslverr)
    );

    slot0 #(
        .P_APB_ADDR_WIDTH(P_REG_ADDR_WIDTH),
        .P_APB_DATA_WIDTH(P_S_AXI_DATA_WIDTH),
        .P_SLOT_BASE     (7'd0),
        .P_REG_COUNT     (65)
    ) u_slot0 (
        .s_apb_pclk      (s_axi_aclk),
        .s_apb_presetn   (s_axi_aresetn),
        .s_apb_paddr     (w_slot_paddr),
        .s_apb_psel      (w_slot0_psel),
        .s_apb_penable   (w_slot_penable),
        .s_apb_pwrite    (w_slot_pwrite),
        .s_apb_pwdata    (w_slot_pwdata),
        .s_apb_prdata    (w_slot0_prdata),
        .s_apb_pready    (w_slot0_pready),
        .s_apb_pslverr   (w_slot0_pslverr),
        .o_irq           (o_irq),
        .o_debug         (o_debug)
    );

    slot1 #(
        .P_APB_ADDR_WIDTH(P_REG_ADDR_WIDTH),
        .P_APB_DATA_WIDTH(P_S_AXI_DATA_WIDTH),
        .P_SLOT_BASE     (7'd65),
        .P_REG_COUNT     (16)
    ) u_slot1 (
        .s_apb_pclk      (s_axi_aclk),
        .s_apb_presetn   (s_axi_aresetn),
        .s_apb_paddr     (w_slot_paddr),
        .s_apb_psel      (w_slot1_psel),
        .s_apb_penable   (w_slot_penable),
        .s_apb_pwrite    (w_slot_pwrite),
        .s_apb_pwdata    (w_slot_pwdata),
        .s_apb_prdata    (w_slot1_prdata),
        .s_apb_pready    (w_slot1_pready),
        .s_apb_pslverr   (w_slot1_pslverr),
        .o_led           (o_led)
    );

    wire w_unused_clk_freq = |P_CLK_FREQ;

endmodule

axi4full_slave_apb_bridge.v

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

////////////////////////////////////////////////////////////////////////////////
// AXI4-Full 从接口转 APB 主接口桥接模块
//
// APB 本身不支持 burst。本模块接收 AXI4-Full burst 后,把每个 AXI beat
// 拆成一次 APB 单次访问。外部仍然是 AXI4-Full,内部变成 APB。
////////////////////////////////////////////////////////////////////////////////
module axi4full_slave_apb_bridge #(
    parameter P_S_AXI_ADDR_WIDTH = 32,
    parameter P_S_AXI_DATA_WIDTH = 32,
    parameter P_S_AXI_ID_WIDTH   = 1,
    parameter [P_S_AXI_ADDR_WIDTH-1:0] P_S_BASE_ADDR = 32'h43c0_0000,
    parameter P_S_MEM_SIZE       = 512,
    parameter P_APB_ADDR_WIDTH   = 9,
    parameter P_APB_DATA_WIDTH   = 32
)(
    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 reg  [P_S_AXI_DATA_WIDTH-1:0]     s_axi_rdata,
    output reg  [1:0]                        s_axi_rresp,
    output wire                              s_axi_rlast,
    output reg                               s_axi_rvalid,
    input  wire                              s_axi_rready,

    // APB 主接口。
    output reg  [P_APB_ADDR_WIDTH-1:0]       m_apb_paddr,
    output reg                               m_apb_psel,
    output reg                               m_apb_penable,
    output reg                               m_apb_pwrite,
    output reg  [P_APB_DATA_WIDTH-1:0]       m_apb_pwdata,
    input  wire [P_APB_DATA_WIDTH-1:0]       m_apb_prdata,
    input  wire                              m_apb_pready,
    input  wire                              m_apb_pslverr
);

    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

    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;

    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];

    localparam [2:0] ST_IDLE          = 3'd0;
    localparam [2:0] ST_WR_WAIT_W     = 3'd1;
    localparam [2:0] ST_WR_APB_SETUP  = 3'd2;
    localparam [2:0] ST_WR_APB_ACCESS = 3'd3;
    localparam [2:0] ST_RD_APB_SETUP  = 3'd4;
    localparam [2:0] ST_RD_APB_ACCESS = 3'd5;
    localparam [2:0] ST_RD_RESP       = 3'd6;

    reg [2:0]                    r_state;

    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_error;
    reg                          r_wr_pending_last;
    reg                          r_wr_pending_error;

    reg [P_S_AXI_ADDR_WIDTH-1:0] r_rd_addr;
    reg [7:0]                    r_rd_len;
    reg [7:0]                    r_rd_count;
    reg [1:0]                    r_rd_burst;
    reg                          r_rd_error;

    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;

    // 写地址转换:支持绝对地址和相对 offset。
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_wr_abs_offset = r_wr_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_wr_rel_range  = (r_wr_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                          w_wr_addr_ok    = w_wr_abs_range || w_wr_rel_range;

    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_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;

    // AR 首地址转换。
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_ar_abs_offset = s_axi_araddr - P_S_BASE_ADDR;
    wire                          w_ar_abs_range  = (s_axi_araddr >= P_S_BASE_ADDR) &&
                                                    (w_ar_abs_offset < P_S_MEM_SIZE);
    wire                          w_ar_rel_range  = (s_axi_araddr < P_S_MEM_SIZE);
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_ar_offset     = w_ar_abs_range ? w_ar_abs_offset : s_axi_araddr;
    wire                          w_ar_addr_ok    = w_ar_abs_range || w_ar_rel_range;
    wire                          w_ar_burst_ok   = (s_axi_arburst == C_AXI_BURST_FIXED) ||
                                                    (s_axi_arburst == C_AXI_BURST_INCR);
    wire                          w_ar_size_ok    = (s_axi_arsize == C_AXI_SIZE);
    wire                          w_ar_access_ok  = w_ar_addr_ok && w_ar_burst_ok && w_ar_size_ok;

    // 当前读 beat 和下一个读 beat 的地址转换。
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_rd_abs_offset = r_rd_addr - P_S_BASE_ADDR;
    wire                          w_rd_abs_range  = (r_rd_addr >= P_S_BASE_ADDR) &&
                                                    (w_rd_abs_offset < P_S_MEM_SIZE);
    wire                          w_rd_rel_range  = (r_rd_addr < P_S_MEM_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;
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_rd_next_abs_offset = w_rd_next_addr - P_S_BASE_ADDR;
    wire                          w_rd_next_abs_range  = (w_rd_next_addr >= P_S_BASE_ADDR) &&
                                                         (w_rd_next_abs_offset < P_S_MEM_SIZE);
    wire                          w_rd_next_rel_range  = (w_rd_next_addr < P_S_MEM_SIZE);
    wire [P_S_AXI_ADDR_WIDTH-1:0] w_rd_next_offset     = w_rd_next_abs_range ? w_rd_next_abs_offset : w_rd_next_addr;
    wire                          w_rd_next_addr_ok    = w_rd_next_abs_range || w_rd_next_rel_range;

    assign s_axi_awready = s_axi_aresetn && (r_state == ST_IDLE) && !s_axi_bvalid && !s_axi_rvalid;
    assign s_axi_wready  = s_axi_aresetn && (r_state == ST_WR_WAIT_W);
    assign s_axi_arready = s_axi_aresetn && (r_state == ST_IDLE) && !s_axi_awvalid && !s_axi_bvalid && !s_axi_rvalid;
    assign s_axi_rlast   = s_axi_rvalid && w_rd_last;

    always @(posedge s_axi_aclk or negedge s_axi_aresetn) begin
        if (!s_axi_aresetn) begin
            r_state            <= ST_IDLE;
            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_error         <= 1'b0;
            r_wr_pending_last  <= 1'b0;
            r_wr_pending_error <= 1'b0;
            r_rd_addr          <= {P_S_AXI_ADDR_WIDTH{1'b0}};
            r_rd_len           <= 8'd0;
            r_rd_count         <= 8'd0;
            r_rd_burst         <= C_AXI_BURST_INCR;
            r_rd_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;
            s_axi_rid          <= {P_S_AXI_ID_WIDTH{1'b0}};
            s_axi_rdata        <= {P_S_AXI_DATA_WIDTH{1'b0}};
            s_axi_rresp        <= C_AXI_RESP_OKAY;
            s_axi_rvalid       <= 1'b0;
            m_apb_paddr        <= {P_APB_ADDR_WIDTH{1'b0}};
            m_apb_psel         <= 1'b0;
            m_apb_penable      <= 1'b0;
            m_apb_pwrite       <= 1'b0;
            m_apb_pwdata       <= {P_APB_DATA_WIDTH{1'b0}};
        end else begin
            if (w_b_fire) begin
                s_axi_bvalid <= 1'b0;
            end

            case (r_state)
                ST_IDLE: begin
                    m_apb_psel    <= 1'b0;
                    m_apb_penable <= 1'b0;

                    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_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;
                        r_state    <= ST_WR_WAIT_W;
                    end else if (w_ar_fire) begin
                        r_rd_addr  <= s_axi_araddr;
                        r_rd_len   <= s_axi_arlen;
                        r_rd_count <= 8'd0;
                        r_rd_burst <= s_axi_arburst;
                        r_rd_error <= !w_ar_access_ok;
                        s_axi_rid  <= s_axi_arid;

                        if (w_ar_access_ok) begin
                            m_apb_paddr   <= w_ar_offset[P_APB_ADDR_WIDTH-1:0];
                            m_apb_pwrite  <= 1'b0;
                            m_apb_pwdata  <= {P_APB_DATA_WIDTH{1'b0}};
                            m_apb_psel    <= 1'b1;
                            m_apb_penable <= 1'b0;
                            r_state       <= ST_RD_APB_SETUP;
                        end else begin
                            s_axi_rdata  <= {P_S_AXI_DATA_WIDTH{1'b0}};
                            s_axi_rresp  <= C_AXI_RESP_SLVERR;
                            s_axi_rvalid <= 1'b1;
                            r_state      <= ST_RD_RESP;
                        end
                    end
                end

                ST_WR_WAIT_W: begin
                    if (w_w_fire) begin
                        r_wr_pending_last  <= w_wr_last_fire;
                        r_wr_pending_error <= w_wr_beat_error;

                        if (w_wr_access_ok) begin
                            m_apb_paddr   <= w_wr_offset[P_APB_ADDR_WIDTH-1:0];
                            m_apb_pwrite  <= 1'b1;
                            m_apb_pwdata  <= s_axi_wdata[P_APB_DATA_WIDTH-1:0];
                            m_apb_psel    <= 1'b1;
                            m_apb_penable <= 1'b0;
                            r_state       <= ST_WR_APB_SETUP;
                        end else if (w_wr_last_fire) begin
                            s_axi_bresp  <= C_AXI_RESP_SLVERR;
                            s_axi_bvalid <= 1'b1;
                            r_wr_error   <= 1'b0;
                            r_state      <= ST_IDLE;
                        end else begin
                            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

                ST_WR_APB_SETUP: begin
                    m_apb_penable <= 1'b1;
                    r_state       <= ST_WR_APB_ACCESS;
                end

                ST_WR_APB_ACCESS: begin
                    if (m_apb_pready) begin
                        m_apb_psel    <= 1'b0;
                        m_apb_penable <= 1'b0;

                        if (r_wr_pending_last) begin
                            s_axi_bresp  <= (r_wr_pending_error || m_apb_pslverr) ? C_AXI_RESP_SLVERR : C_AXI_RESP_OKAY;
                            s_axi_bvalid <= 1'b1;
                            r_wr_error   <= 1'b0;
                            r_state      <= ST_IDLE;
                        end else begin
                            r_wr_count <= r_wr_count + 8'd1;
                            r_wr_addr  <= w_wr_next_addr;
                            r_wr_error <= r_wr_pending_error || m_apb_pslverr;
                            r_state    <= ST_WR_WAIT_W;
                        end
                    end
                end

                ST_RD_APB_SETUP: begin
                    m_apb_penable <= 1'b1;
                    r_state       <= ST_RD_APB_ACCESS;
                end

                ST_RD_APB_ACCESS: begin
                    if (m_apb_pready) begin
                        m_apb_psel    <= 1'b0;
                        m_apb_penable <= 1'b0;
                        s_axi_rdata   <= m_apb_prdata[P_S_AXI_DATA_WIDTH-1:0];
                        s_axi_rresp   <= (r_rd_error || !w_rd_addr_ok || m_apb_pslverr) ? C_AXI_RESP_SLVERR : C_AXI_RESP_OKAY;
                        s_axi_rvalid  <= 1'b1;
                        r_state       <= ST_RD_RESP;
                    end
                end

                ST_RD_RESP: begin
                    if (w_r_fire) begin
                        if (w_rd_last) begin
                            s_axi_rvalid <= 1'b0;
                            r_rd_error   <= 1'b0;
                            r_state      <= ST_IDLE;
                        end else begin
                            r_rd_count <= r_rd_count + 8'd1;
                            r_rd_addr  <= w_rd_next_addr;

                            if (r_rd_error || !w_rd_next_addr_ok) begin
                                s_axi_rdata  <= {P_S_AXI_DATA_WIDTH{1'b0}};
                                s_axi_rresp  <= C_AXI_RESP_SLVERR;
                                s_axi_rvalid <= 1'b1;
                            end else begin
                                s_axi_rvalid <= 1'b0;
                                m_apb_paddr   <= w_rd_next_offset[P_APB_ADDR_WIDTH-1:0];
                                m_apb_pwrite  <= 1'b0;
                                m_apb_pwdata  <= {P_APB_DATA_WIDTH{1'b0}};
                                m_apb_psel    <= 1'b1;
                                m_apb_penable <= 1'b0;
                                r_state       <= ST_RD_APB_SETUP;
                            end
                        end
                    end
                end

                default: begin
                    r_state <= ST_IDLE;
                end
            endcase
        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

apb_slot_router.v

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

////////////////////////////////////////////////////////////////////////////////
// APB slot 路由模块
//
// 上游只有一路 APB,从地址判断访问落在哪个 slot。
// 下游 slot 共用 PADDR/PENABLE/PWRITE/PWDATA,每个 slot 有独立 PSEL。
////////////////////////////////////////////////////////////////////////////////
module apb_slot_router #(
    parameter P_APB_ADDR_WIDTH = 9,
    parameter P_APB_DATA_WIDTH = 32,
    parameter [P_APB_ADDR_WIDTH-3:0] P_SLOT0_BASE = 0,
    parameter [P_APB_ADDR_WIDTH-3:0] P_SLOT0_SIZE = 65,
    parameter [P_APB_ADDR_WIDTH-3:0] P_SLOT1_BASE = 65,
    parameter [P_APB_ADDR_WIDTH-3:0] P_SLOT1_SIZE = 16,
    parameter P_UNMAPPED_OK = 1
)(
    // 上游 APB 从接口。
    input  wire [P_APB_ADDR_WIDTH-1:0]       s_apb_paddr,
    input  wire                              s_apb_psel,
    input  wire                              s_apb_penable,
    input  wire                              s_apb_pwrite,
    input  wire [P_APB_DATA_WIDTH-1:0]       s_apb_pwdata,
    output wire [P_APB_DATA_WIDTH-1:0]       s_apb_prdata,
    output wire                              s_apb_pready,
    output wire                              s_apb_pslverr,

    // 下游 APB 公共信号。
    output wire [P_APB_ADDR_WIDTH-1:0]       m_apb_paddr,
    output wire                              m_apb_penable,
    output wire                              m_apb_pwrite,
    output wire [P_APB_DATA_WIDTH-1:0]       m_apb_pwdata,

    // slot0 独立片选和响应。
    output wire                              m_slot0_psel,
    input  wire [P_APB_DATA_WIDTH-1:0]       m_slot0_prdata,
    input  wire                              m_slot0_pready,
    input  wire                              m_slot0_pslverr,

    // slot1 独立片选和响应。
    output wire                              m_slot1_psel,
    input  wire [P_APB_DATA_WIDTH-1:0]       m_slot1_prdata,
    input  wire                              m_slot1_pready,
    input  wire                              m_slot1_pslverr
);
    wire [P_APB_ADDR_WIDTH-3:0] w_reg_u32_addr = s_apb_paddr[P_APB_ADDR_WIDTH-1:2];
    wire [P_APB_ADDR_WIDTH-3:0] w_slot0_end    = P_SLOT0_BASE + P_SLOT0_SIZE;
    wire [P_APB_ADDR_WIDTH-3:0] w_slot1_end    = P_SLOT1_BASE + P_SLOT1_SIZE;

    wire w_slot0_sel = s_apb_psel &&
                       (w_reg_u32_addr >= P_SLOT0_BASE) &&
                       (w_reg_u32_addr <  w_slot0_end);

    wire w_slot1_sel = s_apb_psel &&
                       (w_reg_u32_addr >= P_SLOT1_BASE) &&
                       (w_reg_u32_addr <  w_slot1_end);

    assign m_apb_paddr   = s_apb_paddr;
    assign m_apb_penable = s_apb_penable;
    assign m_apb_pwrite  = s_apb_pwrite;
    assign m_apb_pwdata  = s_apb_pwdata;
    assign m_slot0_psel  = w_slot0_sel;
    assign m_slot1_psel  = w_slot1_sel;

    assign s_apb_prdata = w_slot0_sel ? m_slot0_prdata :
                          w_slot1_sel ? m_slot1_prdata :
                          {P_APB_DATA_WIDTH{1'b0}};

    assign s_apb_pready = w_slot0_sel ? m_slot0_pready :
                          w_slot1_sel ? m_slot1_pready :
                          1'b1;

    assign s_apb_pslverr = w_slot0_sel ? m_slot0_pslverr :
                           w_slot1_sel ? m_slot1_pslverr :
                           (P_UNMAPPED_OK ? 1'b0 : (s_apb_psel && s_apb_penable));
endmodule

slot0.v

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

////////////////////////////////////////////////////////////////////////////////
// slot0 APB 寄存器块
//
//   reg0      : CTRL_STATUS
//   reg1..64  : DATA0..DATA63,共 64 个 u32
//   o_irq     : DONE && IRQ_EN
//   o_debug   : 写/读 DATA0 拉高,写/读 DATA63 拉低,用于外部测量传输时间
////////////////////////////////////////////////////////////////////////////////
module slot0 #(
    parameter P_APB_ADDR_WIDTH = 9,
    parameter P_APB_DATA_WIDTH = 32,
    parameter [P_APB_ADDR_WIDTH-3:0] P_SLOT_BASE = 0,
    parameter P_REG_COUNT = 65
)(
    input  wire                              s_apb_pclk,
    input  wire                              s_apb_presetn,
    input  wire [P_APB_ADDR_WIDTH-1:0]       s_apb_paddr,
    input  wire                              s_apb_psel,
    input  wire                              s_apb_penable,
    input  wire                              s_apb_pwrite,
    input  wire [P_APB_DATA_WIDTH-1:0]       s_apb_pwdata,
    output reg  [P_APB_DATA_WIDTH-1:0]       s_apb_prdata,
    output wire                              s_apb_pready,
    output wire                              s_apb_pslverr,
    output wire                              o_irq,
    output reg                               o_debug
);
    localparam [P_APB_ADDR_WIDTH-3:0] C_CTRL_LOCAL = 0;
    localparam [P_APB_ADDR_WIDTH-3:0] C_DATA_FIRST = 1;
    localparam [P_APB_ADDR_WIDTH-3:0] C_DATA_LAST  = 64;

    wire w_setup  = s_apb_psel && !s_apb_penable;
    wire w_access = s_apb_psel && s_apb_penable;
    wire w_write  = w_access && s_apb_pwrite;
    wire w_read   = w_access && !s_apb_pwrite;

    wire [P_APB_ADDR_WIDTH-3:0] w_reg_addr   = s_apb_paddr[P_APB_ADDR_WIDTH-1:2];
    wire [P_APB_ADDR_WIDTH-3:0] w_local_addr = w_reg_addr - P_SLOT_BASE;
    wire w_addr_hit = (w_reg_addr >= P_SLOT_BASE) &&
                      (w_reg_addr < (P_SLOT_BASE + P_REG_COUNT));
    wire w_local_is_ctrl = (w_local_addr == C_CTRL_LOCAL);
    wire w_local_is_data = (w_local_addr >= C_DATA_FIRST) && (w_local_addr <= C_DATA_LAST);

    reg [P_APB_DATA_WIDTH-1:0] r_data [0:63];
    reg                        r_busy;
    reg                        r_done;
    reg                        r_irq_en;
    reg                        r_ready;
    reg                        r_slverr;

    wire [P_APB_DATA_WIDTH-1:0] w_ctrl_status =
        {{(P_APB_DATA_WIDTH-4){1'b0}}, 1'b0, r_irq_en, r_done, r_busy};

    assign o_irq          = r_done && r_irq_en;
    assign s_apb_pready   = s_apb_psel && s_apb_penable && r_ready;
    assign s_apb_pslverr  = s_apb_pready && r_slverr;

    integer i;
    always @(posedge s_apb_pclk or negedge s_apb_presetn) begin
        if (!s_apb_presetn) begin
            for (i = 0; i < 64; i = i + 1) begin
                r_data[i] <= {P_APB_DATA_WIDTH{1'b0}};
            end
            r_busy        <= 1'b0;
            r_done        <= 1'b0;
            r_irq_en      <= 1'b0;
            r_ready       <= 1'b0;
            r_slverr      <= 1'b0;
            s_apb_prdata  <= {P_APB_DATA_WIDTH{1'b0}};
            o_debug       <= 1'b0;
        end else begin
            if (!s_apb_psel) begin
                r_ready  <= 1'b0;
                r_slverr <= 1'b0;
            end else if (w_setup) begin
                r_ready  <= 1'b1;
                r_slverr <= !w_addr_hit;

                if (!s_apb_pwrite) begin
                    if (!w_addr_hit) begin
                        s_apb_prdata <= {P_APB_DATA_WIDTH{1'b0}};
                    end else if (w_local_is_ctrl) begin
                        s_apb_prdata <= w_ctrl_status;
                    end else if (w_local_is_data) begin
                        s_apb_prdata <= r_data[w_local_addr - C_DATA_FIRST];
                    end else begin
                        s_apb_prdata <= {P_APB_DATA_WIDTH{1'b0}};
                    end
                end
            end else if (w_write && r_ready) begin
                if (w_addr_hit && w_local_is_ctrl) begin
                    r_irq_en <= s_apb_pwdata[2];
                    if (s_apb_pwdata[3]) begin
                        r_done <= 1'b0;
                        r_busy <= 1'b0;
                    end
                end else if (w_addr_hit && w_local_is_data) begin
                    // 数据处理当前为 +0,也就是原样保存。
                    r_data[w_local_addr - C_DATA_FIRST] <= s_apb_pwdata;

                    if (w_local_addr == C_DATA_FIRST) begin
                        r_busy  <= 1'b1;
                        r_done  <= 1'b0;
                        o_debug <= 1'b1;
                    end

                    if (w_local_addr == C_DATA_LAST) begin
                        r_busy  <= 1'b0;
                        r_done  <= 1'b1;
                        o_debug <= 1'b0;
                    end
                end
            end else if (w_read && r_ready && w_addr_hit && w_local_is_data) begin
                if (w_local_addr == C_DATA_FIRST) begin
                    o_debug <= 1'b1;
                end

                if (w_local_addr == C_DATA_LAST) begin
                    o_debug <= 1'b0;
                end
            end
        end
    end
endmodule

slot1.v

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

////////////////////////////////////////////////////////////////////////////////
// slot1 APB 测试寄存器块
//
// 挂载 16 个 u32,可读可写。
// 第 0 个寄存器 bit0 同时导出到 o_led。
////////////////////////////////////////////////////////////////////////////////
module slot1 #(
    parameter P_APB_ADDR_WIDTH = 9,
    parameter P_APB_DATA_WIDTH = 32,
    parameter [P_APB_ADDR_WIDTH-3:0] P_SLOT_BASE = 65,
    parameter P_REG_COUNT = 16
)(
    input  wire                              s_apb_pclk,
    input  wire                              s_apb_presetn,
    input  wire [P_APB_ADDR_WIDTH-1:0]       s_apb_paddr,
    input  wire                              s_apb_psel,
    input  wire                              s_apb_penable,
    input  wire                              s_apb_pwrite,
    input  wire [P_APB_DATA_WIDTH-1:0]       s_apb_pwdata,
    output reg  [P_APB_DATA_WIDTH-1:0]       s_apb_prdata,
    output wire                              s_apb_pready,
    output wire                              s_apb_pslverr,
    output reg                               o_led
);
    wire w_setup  = s_apb_psel && !s_apb_penable;
    wire w_access = s_apb_psel && s_apb_penable;
    wire w_write  = w_access && s_apb_pwrite;

    wire [P_APB_ADDR_WIDTH-3:0] w_reg_addr   = s_apb_paddr[P_APB_ADDR_WIDTH-1:2];
    wire [P_APB_ADDR_WIDTH-3:0] w_local_addr = w_reg_addr - P_SLOT_BASE;
    wire w_addr_hit = (w_reg_addr >= P_SLOT_BASE) &&
                      (w_reg_addr < (P_SLOT_BASE + P_REG_COUNT));

    reg [P_APB_DATA_WIDTH-1:0] r_mem [0:P_REG_COUNT-1];
    reg                        r_ready;
    reg                        r_slverr;

    assign s_apb_pready  = s_apb_psel && s_apb_penable && r_ready;
    assign s_apb_pslverr = s_apb_pready && r_slverr;

    integer i;
    always @(posedge s_apb_pclk or negedge s_apb_presetn) begin
        if (!s_apb_presetn) begin
            for (i = 0; i < P_REG_COUNT; i = i + 1) begin
                r_mem[i] <= {P_APB_DATA_WIDTH{1'b0}};
            end
            s_apb_prdata <= {P_APB_DATA_WIDTH{1'b0}};
            r_ready      <= 1'b0;
            r_slverr     <= 1'b0;
            o_led        <= 1'b0;
        end else begin
            if (!s_apb_psel) begin
                r_ready  <= 1'b0;
                r_slverr <= 1'b0;
            end else if (w_setup) begin
                r_ready  <= 1'b1;
                r_slverr <= !w_addr_hit;

                if (!s_apb_pwrite) begin
                    if (w_addr_hit) begin
                        s_apb_prdata <= r_mem[w_local_addr];
                    end else begin
                        s_apb_prdata <= {P_APB_DATA_WIDTH{1'b0}};
                    end
                end
            end else if (w_write && r_ready && w_addr_hit) begin
                r_mem[w_local_addr] <= s_apb_pwdata;
                if (w_local_addr == 0) begin
                    o_led <= s_apb_pwdata[0];
                end
            end
        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

相关推荐
早睡早起身体好1234 分钟前
为什么 Network 中会出现两个同名请求?——CORS 预检请求学习笔记
网络·笔记·学习·网络安全
菩提树下的打坐5 分钟前
测试左移的更深一层:用 DDD 战术设计反推测试边界
学习
casual~1 小时前
模逆元计算方法详解:扩展欧几里得算法与费马小定理
学习·算法·逆元
@Mike@1 小时前
06-数据库学习笔记(存储模型与数据压缩)
数据库·笔记·学习
我的xiaodoujiao2 小时前
快速学习Python基础知识详细图文教程14--模块
开发语言·python·学习·测试工具
wdfk_prog2 小时前
嵌入式面试真题学习笔记系列
笔记·学习·面试
懿路向前2 小时前
【HarmonyOS学习笔记】2026-07-30 | 小艺开放平台智能体接入实战
笔记·学习·harmonyos
吃好睡好便好2 小时前
MATLAB中图像的线性变换
开发语言·图像处理·学习·计算机视觉·matlab
YUS云生2 小时前
大模型学习·第41天:LangChain进阶——提示词模板与Chain链式调用
学习·langchain·c#
boppu3 小时前
酒店毛巾浴巾洗涤后手感评判标准
学习