欢迎光临
我们一直在努力

zynq高频小数据量PS闭环的三个实验-第1课:PL搭建

参考

zynq高频小数据量PS闭环的三个实验-第0课:目的和目标 zynq高频小数据量PS闭环的三个实验-第1课:PL搭建 zynq高频小数据量PS闭环的三个实验-第2课:Vitis裸机测试 zynq高频小数据量PS闭环的三个实验-第3课:Linux驱动测试

BD

cmp_gp_acp_system.tcl_xc7z020.tcl

cmp_gp_acp_system.tcl_xc7z010.tcl

在这里插入图片描述

AXI 地址映射表

NameInterfaceSlave SegmentMaster Base AddressRangeMaster High Address
Network 0
  └─ /processing_system7_0
      └─ /processing_system7_0/Data (32 address bits : 0x40000000 [ 1G ])
          └─ /top_0/s_axi s_axi reg0 0x43C0_0000 4K 0x43C0_0FFF
Network 1
  └─ /top_0
      └─ /top_0/m_axi (32 address bits : 4G)
          ├─ /processing_system7_0/S_AXI_ACP S_AXI_ACP ACP_DDR_LOWOCM 0x0000_0000 1G 0x3FFF_FFFF
          ├─ /processing_system7_0/S_AXI_ACP S_AXI_ACP ACP_QSPI_LINEAR 0xFC00_0000 16M 0xFCFF_FFFF
          ├─ /processing_system7_0/S_AXI_ACP S_AXI_ACP ACP_IOP 0xE000_0000 4M 0xE03F_FFFF
          └─ /processing_system7_0/S_AXI_ACP S_AXI_ACP ACP_M_AXI_GP0 0x4000_0000 1G 0x7FFF_FFFF

PIN.xdc

xc7z020

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

top.v

`timescale 1ns/1ps

/*
* top
* —
* v3 axi_acp_rtl 的封装顶层,包含两条逻辑通路:
*
* 1. AXI-Lite slave 控制面
* PS 通过 s_axi_* 访问 slot0/slot1 的 32-bit 简单寄存器空间。
*
* 2. AXI4-Full master 数据面
* slot0 内部实例化 axi4full_master,通过 m_axi_* 访问 PS DDR/ACP。
*
* 地址关系:
* – AXI-Lite 地址是字节地址,bridge 会转换成 32-bit 寄存器下标。
* – slot0[0..128] 占 129 个 u32 寄存器。
* – slot1 从下标 129 开始。
*/
module top #(
parameter P_S_AXI_ADDR_WIDTH = 32,
parameter P_S_AXI_DATA_WIDTH = 32,
parameter P_S_MEM_SIZE = 1024,
parameter P_REG_ADDR_WIDTH = 10,
parameter P_CLK_FREQ = 100_000_000,

parameter P_M_AXI_ADDR_WIDTH = 32,
parameter P_M_AXI_DATA_WIDTH = 64,
parameter P_M_AXI_ID_WIDTH = 1,
parameter P_SLOT0_AXI_BURST_LEN = 32,
/*
* slot0 ACP DDR 映像窗口。
* 这些默认值需要和裸机 slot0_acp.h 保持一致;如果 Vivado BD/IP 参数覆盖了这里,
* 软件也必须同步修改,否则 PL 会去另一个 DDR 地址读写。
*/
parameter [P_M_AXI_ADDR_WIDTH-1:0] P_SLOT0_DDR_RD_WINDOW_ADDR = 32'h3F10_0000,
parameter [P_M_AXI_ADDR_WIDTH-1:0] P_SLOT0_DDR_WR_WINDOW_ADDR = 32'h3F10_0100
)(
input wire s_axi_aclk,
input wire s_axi_aresetn,

input wire [P_S_AXI_ADDR_WIDTH-1:0] s_axi_awaddr,
input wire s_axi_awvalid,
output wire s_axi_awready,
input wire [P_S_AXI_DATA_WIDTH-1:0] s_axi_wdata,
input wire [(P_S_AXI_DATA_WIDTH/8)-1:0] s_axi_wstrb,
input wire s_axi_wvalid,
output wire s_axi_wready,
output wire [1:0] s_axi_bresp,
output wire s_axi_bvalid,
input wire s_axi_bready,

input wire [P_S_AXI_ADDR_WIDTH-1:0] s_axi_araddr,
input wire s_axi_arvalid,
output wire s_axi_arready,
output wire [P_S_AXI_DATA_WIDTH-1:0] s_axi_rdata,
output wire [1:0] s_axi_rresp,
output wire s_axi_rvalid,
input wire s_axi_rready,

output wire [P_M_AXI_ID_WIDTH-1:0] m_axi_awid,
output wire [P_M_AXI_ADDR_WIDTH-1:0] m_axi_awaddr,
output wire [7:0] m_axi_awlen,
output wire [2:0] m_axi_awsize,
output wire [1:0] m_axi_awburst,
output wire m_axi_awlock,
output wire [3:0] m_axi_awcache,
output wire [2:0] m_axi_awprot,
output wire [3:0] m_axi_awqos,
output wire [3:0] m_axi_awregion,
output wire m_axi_awvalid,
input wire m_axi_awready,

output wire [P_M_AXI_DATA_WIDTH-1:0] m_axi_wdata,
output wire [(P_M_AXI_DATA_WIDTH/8)-1:0] m_axi_wstrb,
output wire m_axi_wlast,
output wire m_axi_wvalid,
input wire m_axi_wready,

input wire [P_M_AXI_ID_WIDTH-1:0] m_axi_bid,
input wire [1:0] m_axi_bresp,
input wire m_axi_bvalid,
output wire m_axi_bready,

output wire [P_M_AXI_ID_WIDTH-1:0] m_axi_arid,
output wire [P_M_AXI_ADDR_WIDTH-1:0] m_axi_araddr,
output wire [7:0] m_axi_arlen,
output wire [2:0] m_axi_arsize,
output wire [1:0] m_axi_arburst,
output wire m_axi_arlock,
output wire [3:0] m_axi_arcache,
output wire [2:0] m_axi_arprot,
output wire [3:0] m_axi_arqos,
output wire [3:0] m_axi_arregion,
output wire m_axi_arvalid,
input wire m_axi_arready,

input wire [P_M_AXI_ID_WIDTH-1:0] m_axi_rid,
input wire [P_M_AXI_DATA_WIDTH-1:0] m_axi_rdata,
input wire [1:0] m_axi_rresp,
input wire m_axi_rlast,
input wire m_axi_rvalid,
output wire m_axi_rready,

output wire o_slot0_irq,
output wire o_led
);
/*
* AXI-Lite bridge 输出的是“字节地址低位裁剪后的内部地址”。
* C_REG_ADDR_WIDTH=10 可覆盖 1024 byte,也就是 256 个 32-bit 寄存器。
* slot 模块只关心寄存器下标,所以再去掉低 2 bit 得到 C_SLOT_ADDR_WIDTH。
*/
localparam C_REG_ADDR_WIDTH = 10;
localparam C_SLOT_ADDR_WIDTH = C_REG_ADDR_WIDTH – 2;

wire w_reg_wr_en;
wire w_reg_rd_en;
wire [C_REG_ADDR_WIDTH-1:0] w_reg_wr_addr;
wire [C_REG_ADDR_WIDTH-1:0] w_reg_rd_addr;
wire [P_S_AXI_DATA_WIDTH-1:0] w_reg_wr_data;
wire [P_S_AXI_DATA_WIDTH-1:0] w_reg_rd_data;

wire [C_SLOT_ADDR_WIDTH-1:0] w_slot_wr_addr;
wire [C_SLOT_ADDR_WIDTH-1:0] w_slot_rd_addr;
wire [P_S_AXI_DATA_WIDTH-1:0] w_slot_wr_data;

wire w_slot0_wr_en;
wire w_slot0_rd_en;
wire [P_S_AXI_DATA_WIDTH-1:0] w_slot0_rd_data;

wire w_slot1_wr_en;
wire w_slot1_rd_en;
wire [P_S_AXI_DATA_WIDTH-1:0] w_slot1_rd_data;

/*
* AXI-Lite 到简单寄存器总线的桥。
* bridge 只处理 AXI-Lite 握手、WSTRB 写掩码、读写响应;
* 具体哪个 slot 被访问由后面的 smmr_slot_router 判断。
*/
axilite_slave_smmr_bridge #(
.P_S_AXI_ADDR_WIDTH(P_S_AXI_ADDR_WIDTH),
.P_S_AXI_DATA_WIDTH(P_S_AXI_DATA_WIDTH),
.P_S_MEM_SIZE (P_S_MEM_SIZE),
.P_REG_ADDR_WIDTH (C_REG_ADDR_WIDTH)
) u_axilite_slave_smmr_bridge (
.s_axi_aclk (s_axi_aclk),
.s_axi_aresetn (s_axi_aresetn),
.s_axi_awaddr (s_axi_awaddr),
.s_axi_awvalid (s_axi_awvalid),
.s_axi_awready (s_axi_awready),
.s_axi_wdata (s_axi_wdata),
.s_axi_wstrb (s_axi_wstrb),
.s_axi_wvalid (s_axi_wvalid),
.s_axi_wready (s_axi_wready),
.s_axi_bresp (s_axi_bresp),
.s_axi_bvalid (s_axi_bvalid),
.s_axi_bready (s_axi_bready),
.s_axi_araddr (s_axi_araddr),
.s_axi_arvalid (s_axi_arvalid),
.s_axi_arready (s_axi_arready),
.s_axi_rdata (s_axi_rdata),
.s_axi_rresp (s_axi_rresp),
.s_axi_rvalid (s_axi_rvalid),
.s_axi_rready (s_axi_rready),
.o_reg_wr_en (w_reg_wr_en),
.o_reg_wr_addr (w_reg_wr_addr),
.o_reg_wr_data (w_reg_wr_data),
.o_reg_rd_en (w_reg_rd_en),
.o_reg_rd_addr (w_reg_rd_addr),
.i_reg_rd_data (w_reg_rd_data)
);

/*
* 简单内存映射寄存器路由:
* 0..128 -> slot0
* 129..132 -> slot1
*/
smmr_slot_router #(
.P_ADDR_WIDTH (C_REG_ADDR_WIDTH),
.P_DATA_WIDTH (P_S_AXI_DATA_WIDTH),
.P_SLOT0_BASE (0),
.P_SLOT0_SIZE (129),
.P_SLOT1_BASE (129),
.P_SLOT1_SIZE (4)
) u_smmr_slot_router (
.i_clk (s_axi_aclk),
.i_rst_n (s_axi_aresetn),
.i_reg_wr_en (w_reg_wr_en),
.i_reg_wr_addr (w_reg_wr_addr),
.i_reg_wr_data (w_reg_wr_data),
.i_reg_rd_en (w_reg_rd_en),
.i_reg_rd_addr (w_reg_rd_addr),
.o_reg_rd_data (w_reg_rd_data),
.o_slot_wr_addr (w_slot_wr_addr),
.o_slot_wr_data (w_slot_wr_data),
.o_slot_rd_addr (w_slot_rd_addr),
.o_slot0_wr_en (w_slot0_wr_en),
.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 是当前数据闭环核心:
* – PS 通过 AXI-Lite 读写 slot0[0..128]。
* – PL 通过 AXI4-Full 把 slot0[1..64] 写入 DDR read window。
* – PL 通过 AXI4-Full 把 DDR write window 拉回 slot0[65..128]。
*/
slot0 #(
.P_ADDR_WIDTH (C_SLOT_ADDR_WIDTH),
.P_DATA_WIDTH (P_S_AXI_DATA_WIDTH),
.P_SLOT_BASE (0),
.P_REG_COUNT (129),
.P_M_AXI_ADDR_WIDTH (P_M_AXI_ADDR_WIDTH),
.P_M_AXI_DATA_WIDTH (P_M_AXI_DATA_WIDTH),
.P_M_AXI_ID_WIDTH (P_M_AXI_ID_WIDTH),
.P_M_AXI_BURST_LEN (P_SLOT0_AXI_BURST_LEN),
.P_ACQ_CLK_FREQ_HZ (P_CLK_FREQ),
.P_DDR_RD_WINDOW_ADDR (P_SLOT0_DDR_RD_WINDOW_ADDR),
.P_DDR_WR_WINDOW_ADDR (P_SLOT0_DDR_WR_WINDOW_ADDR)
) 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_en (w_slot0_rd_en),
.i_reg_rd_addr (w_slot_rd_addr),
.o_reg_rd_data (w_slot0_rd_data),
.o_irq (o_slot0_irq),
.m_axi_awid (m_axi_awid),
.m_axi_awaddr (m_axi_awaddr),
.m_axi_awlen (m_axi_awlen),
.m_axi_awsize (m_axi_awsize),
.m_axi_awburst (m_axi_awburst),
.m_axi_awlock (m_axi_awlock),
.m_axi_awcache (m_axi_awcache),
.m_axi_awprot (m_axi_awprot),
.m_axi_awqos (m_axi_awqos),
.m_axi_awregion (m_axi_awregion),
.m_axi_awvalid (m_axi_awvalid),
.m_axi_awready (m_axi_awready),
.m_axi_wdata (m_axi_wdata),
.m_axi_wstrb (m_axi_wstrb),
.m_axi_wlast (m_axi_wlast),
.m_axi_wvalid (m_axi_wvalid),
.m_axi_wready (m_axi_wready),
.m_axi_bid (m_axi_bid),
.m_axi_bresp (m_axi_bresp),
.m_axi_bvalid (m_axi_bvalid),
.m_axi_bready (m_axi_bready),
.m_axi_arid (m_axi_arid),
.m_axi_araddr (m_axi_araddr),
.m_axi_arlen (m_axi_arlen),
.m_axi_arsize (m_axi_arsize),
.m_axi_arburst (m_axi_arburst),
.m_axi_arlock (m_axi_arlock),
.m_axi_arcache (m_axi_arcache),
.m_axi_arprot (m_axi_arprot),
.m_axi_arqos (m_axi_arqos),
.m_axi_arregion (m_axi_arregion),
.m_axi_arvalid (m_axi_arvalid),
.m_axi_arready (m_axi_arready),
.m_axi_rid (m_axi_rid),
.m_axi_rdata (m_axi_rdata),
.m_axi_rresp (m_axi_rresp),
.m_axi_rlast (m_axi_rlast),
.m_axi_rvalid (m_axi_rvalid),
.m_axi_rready (m_axi_rready)
);

/* slot1 当前只保留少量测试/LED 寄存器,和 ACP 数据路径无关。 */
slot1 #(
.P_ADDR_WIDTH (C_SLOT_ADDR_WIDTH),
.P_DATA_WIDTH (P_S_AXI_DATA_WIDTH),
.P_SLOT_BASE (129),
.P_REG_COUNT (4)
) u_slot1 (
.i_clk (s_axi_aclk),
.i_rst_n (s_axi_aresetn),
.i_reg_wr_en (w_slot1_wr_en),
.i_reg_wr_addr (w_slot_wr_addr),
.i_reg_wr_data (w_slot_wr_data),
.i_reg_rd_addr (w_slot_rd_addr),
.o_reg_rd_data (w_slot1_rd_data),
.o_led (o_led)
);

endmodule

axilite_slave_smmr_bridge.v

//AXI-Lite Slave 接口转 SMMR 简单寄存器接口 桥接模块
module axilite_slave_smmr_bridge #(
parameter P_S_AXI_ADDR_WIDTH = 32,
parameter P_S_AXI_DATA_WIDTH = 32,
parameter P_S_MEM_SIZE = 1024, // internal memory size in bytes
parameter P_REG_ADDR_WIDTH = 10
)(
// =========================
// AXI-Lite (PS -> PL)
// =========================
input wire s_axi_aclk,
input wire s_axi_aresetn,
input wire [P_S_AXI_ADDR_WIDTH-1:0] s_axi_awaddr,
input wire s_axi_awvalid,
output reg s_axi_awready,
input wire [P_S_AXI_DATA_WIDTH-1:0] s_axi_wdata,
input wire [(P_S_AXI_DATA_WIDTH/8)-1:0] s_axi_wstrb,
input wire s_axi_wvalid,
output reg s_axi_wready,
output reg [1:0] s_axi_bresp,
output reg s_axi_bvalid,
input wire s_axi_bready,
input wire [P_S_AXI_ADDR_WIDTH-1:0] s_axi_araddr,
input wire s_axi_arvalid,
output reg s_axi_arready,
output reg [P_S_AXI_DATA_WIDTH-1:0] s_axi_rdata,
output reg [1:0] s_axi_rresp,
output reg s_axi_rvalid,
input wire s_axi_rready,

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

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

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

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

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

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

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

endmodule

axi4full_master.v

`timescale 1ns/1ps

/*
* axi4full_master
* —————
* 一个小型 AXI4-Full single-burst master,用于 slot0 和 PS DDR 交换固定窗口数据。
*
* 用户侧接口:
* i_wr_start/i_wr_addr/i_wr_len/i_wr_data -> 发起一次 AXI burst write。
* i_rd_start/i_rd_addr/i_rd_len -> 发起一次 AXI burst read。
*
* 约束:
* – 每次只维护一个写 burst 和一个读 burst 的简单状态机。
* – i_wr_len/i_rd_len 表示 beat 数,0 会被钳成 1,超过 P_MAX_BURST_LEN 会被钳到最大值。
* – P_AXI_DATA_WIDTH 当前主要用于 32/64 bit;AWSIZE/ARSIZE 按这个宽度给出。
*
* 时序注意:
* 写通道不用 “wide_bus >> variable_index” 取 beat,因为那会综合出很宽的 barrel shifter。
* 本模块改为每成功发送一个 W beat 后,对 r_wr_data_buf 做固定宽度右移,
* 下一拍低位就是下一个 beat,路径更稳定。
*/
module axi4full_master #(
parameter P_AXI_ADDR_WIDTH = 32,
parameter P_AXI_DATA_WIDTH = 32,
parameter P_MAX_BURST_LEN = 64,
parameter P_ID_WIDTH = 1,
parameter [3:0] P_AWCACHE = 4'b1111,
parameter [3:0] P_ARCACHE = 4'b1111,
parameter [2:0] P_AWPROT = 3'b000,
parameter [2:0] P_ARPROT = 3'b000,
parameter [3:0] P_AWQOS = 4'b0000,
parameter [3:0] P_ARQOS = 4'b0000,
parameter [3:0] P_AWREGION = 4'b0000,
parameter [3:0] P_ARREGION = 4'b0000
)(
output reg [P_ID_WIDTH-1:0] m_axi_awid,
output reg [P_AXI_ADDR_WIDTH-1:0] m_axi_awaddr,
output reg [7:0] m_axi_awlen,
output reg [2:0] m_axi_awsize,
output reg [1:0] m_axi_awburst,
output reg m_axi_awlock,
output reg [3:0] m_axi_awcache,
output reg [2:0] m_axi_awprot,
output reg [3:0] m_axi_awqos,
output reg [3:0] m_axi_awregion,
output reg m_axi_awvalid,
input wire m_axi_awready,

output reg [P_AXI_DATA_WIDTH-1:0] m_axi_wdata,
output reg [(P_AXI_DATA_WIDTH/8)-1:0] m_axi_wstrb,
output reg m_axi_wlast,
output reg m_axi_wvalid,
input wire m_axi_wready,

input wire [P_ID_WIDTH-1:0] m_axi_bid,
input wire [1:0] m_axi_bresp,
input wire m_axi_bvalid,
output reg m_axi_bready,

output reg [P_ID_WIDTH-1:0] m_axi_arid,
output reg [P_AXI_ADDR_WIDTH-1:0] m_axi_araddr,
output reg [7:0] m_axi_arlen,
output reg [2:0] m_axi_arsize,
output reg [1:0] m_axi_arburst,
output reg m_axi_arlock,
output reg [3:0] m_axi_arcache,
output reg [2:0] m_axi_arprot,
output reg [3:0] m_axi_arqos,
output reg [3:0] m_axi_arregion,
output reg m_axi_arvalid,
input wire m_axi_arready,

input wire [P_ID_WIDTH-1:0] m_axi_rid,
input wire [P_AXI_DATA_WIDTH-1:0] m_axi_rdata,
input wire [1:0] m_axi_rresp,
input wire m_axi_rlast,
input wire m_axi_rvalid,
output reg m_axi_rready,

input wire i_clk,
input wire i_rst_n,

input wire i_wr_start,
input wire [P_AXI_ADDR_WIDTH-1:0] i_wr_addr,
input wire [7:0] i_wr_len,
input wire [P_AXI_DATA_WIDTH*P_MAX_BURST_LEN-1:0] i_wr_data,
output reg o_wr_busy,
output reg o_wr_done,
output reg [1:0] o_wr_resp,

input wire i_rd_start,
input wire [P_AXI_ADDR_WIDTH-1:0] i_rd_addr,
input wire [7:0] i_rd_len,
output reg [P_AXI_DATA_WIDTH*P_MAX_BURST_LEN-1:0] o_rd_data,
output reg o_rd_busy,
output reg o_rd_done,
output reg [1:0] o_rd_resp
);
/* 写通道:地址/数据两个通道可以独立握手,最后等待 B 响应。 */
localparam [1:0] WR_IDLE = 2'd0;
localparam [1:0] WR_RUN = 2'd1;
localparam [1:0] WR_RESP = 2'd2;
/* 读通道:先发 AR,再接收连续 R beat。 */
localparam [1:0] RD_IDLE = 2'd0;
localparam [1:0] RD_ADDR = 2'd1;
localparam [1:0] RD_DATA = 2'd2;

localparam [7:0] C_MAX_BURST_LEN = P_MAX_BURST_LEN;
/* AXI size = log2(bytes_per_beat)。32-bit=4B=>2,64-bit=8B=>3。 */
localparam [2:0] C_AXI_SIZE = (P_AXI_DATA_WIDTH == 64) ? 3'd3 : 3'd2;

reg [1:0] r_wr_state;
reg [1:0] r_rd_state;
reg r_wr_aw_done;
reg r_wr_w_done;
reg [7:0] r_wr_len;
reg [7:0] r_wr_beat_idx;
/* 写数据移位缓存。低 P_AXI_DATA_WIDTH 位始终是下一拍要送出的 WDATA。 */
reg [P_AXI_DATA_WIDTH*P_MAX_BURST_LEN-1:0] r_wr_data_buf;
reg [7:0] r_rd_len;
reg [7:0] r_rd_beat_idx;

/* 运行时长度保护,防止外部传 0 或超过最大 burst 长度。 */
wire [7:0] w_wr_len = (i_wr_len == 8'd0) ? 8'd1 :
(i_wr_len > C_MAX_BURST_LEN) ? C_MAX_BURST_LEN : i_wr_len;
wire [7:0] w_rd_len = (i_rd_len == 8'd0) ? 8'd1 :
(i_rd_len > C_MAX_BURST_LEN) ? C_MAX_BURST_LEN : i_rd_len;

wire w_wr_aw_fire = (r_wr_state == WR_RUN) && m_axi_awvalid && m_axi_awready;
wire w_wr_w_fire = (r_wr_state == WR_RUN) && m_axi_wvalid && m_axi_wready;
wire w_wr_last_fire = w_wr_w_fire && (r_wr_beat_idx == (r_wr_len – 8'd1));

wire w_rd_ar_fire = (r_rd_state == RD_ADDR) && m_axi_arvalid && m_axi_arready;
wire w_rd_r_fire = (r_rd_state == RD_DATA) && m_axi_rvalid && m_axi_rready;

always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
m_axi_awid <= {P_ID_WIDTH{1'b0}};
m_axi_awaddr <= {P_AXI_ADDR_WIDTH{1'b0}};
m_axi_awlen <= 8'd0;
m_axi_awsize <= 3'd0;
m_axi_awburst <= 2'b01;
m_axi_awlock <= 1'b0;
m_axi_awcache <= P_AWCACHE;
m_axi_awprot <= P_AWPROT;
m_axi_awqos <= P_AWQOS;
m_axi_awregion <= P_AWREGION;
m_axi_awvalid <= 1'b0;
m_axi_wdata <= {P_AXI_DATA_WIDTH{1'b0}};
m_axi_wstrb <= {(P_AXI_DATA_WIDTH/8){1'b0}};
m_axi_wlast <= 1'b0;
m_axi_wvalid <= 1'b0;
m_axi_bready <= 1'b0;
m_axi_arid <= {P_ID_WIDTH{1'b0}};
m_axi_araddr <= {P_AXI_ADDR_WIDTH{1'b0}};
m_axi_arlen <= 8'd0;
m_axi_arsize <= 3'd0;
m_axi_arburst <= 2'b01;
m_axi_arlock <= 1'b0;
m_axi_arcache <= P_ARCACHE;
m_axi_arprot <= P_ARPROT;
m_axi_arqos <= P_ARQOS;
m_axi_arregion <= P_ARREGION;
m_axi_arvalid <= 1'b0;
m_axi_rready <= 1'b0;
o_wr_busy <= 1'b0;
o_wr_done <= 1'b0;
o_wr_resp <= 2'b00;
o_rd_data <= {P_AXI_DATA_WIDTH*P_MAX_BURST_LEN{1'b0}};
o_rd_busy <= 1'b0;
o_rd_done <= 1'b0;
o_rd_resp <= 2'b00;
r_wr_state <= WR_IDLE;
r_rd_state <= RD_IDLE;
r_wr_aw_done <= 1'b0;
r_wr_w_done <= 1'b0;
r_wr_len <= 8'd0;
r_wr_beat_idx <= 8'd0;
r_wr_data_buf <= {P_AXI_DATA_WIDTH*P_MAX_BURST_LEN{1'b0}};
r_rd_len <= 8'd0;
r_rd_beat_idx <= 8'd0;
end else begin
o_wr_done <= 1'b0;
o_rd_done <= 1'b0;

case (r_wr_state)
WR_IDLE: begin
/*
* 等待用户侧写启动。
* 启动时同时拉高 AWVALID 和 WVALID,允许 interconnect 分别接收地址和数据。
*/
o_wr_busy <= 1'b0;
m_axi_awvalid <= 1'b0;
m_axi_wvalid <= 1'b0;
m_axi_wlast <= 1'b0;
m_axi_bready <= 1'b0;
r_wr_aw_done <= 1'b0;
r_wr_w_done <= 1'b0;
if (i_wr_start) begin
r_wr_len <= w_wr_len;
r_wr_beat_idx <= 8'd0;
/*
* 第 0 个 beat 直接送到 m_axi_wdata。
* buffer 预先右移一个 beat,让 buffer 低位变成第 1 个 beat。
*/
r_wr_data_buf <= i_wr_data >> P_AXI_DATA_WIDTH;
o_wr_busy <= 1'b1;
m_axi_awid <= {P_ID_WIDTH{1'b0}};
m_axi_awaddr <= i_wr_addr;
m_axi_awlen <= w_wr_len – 8'd1;
m_axi_awsize <= C_AXI_SIZE;
m_axi_awburst <= 2'b01;
m_axi_awlock <= 1'b0;
m_axi_awcache <= P_AWCACHE;
m_axi_awprot <= P_AWPROT;
m_axi_awqos <= P_AWQOS;
m_axi_awregion<= P_AWREGION;
m_axi_awvalid <= 1'b1;
m_axi_wdata <= i_wr_data[P_AXI_DATA_WIDTH-1:0];
m_axi_wstrb <= {(P_AXI_DATA_WIDTH/8){1'b1}};
m_axi_wlast <= (w_wr_len == 8'd1);
m_axi_wvalid <= 1'b1;
r_wr_state <= WR_RUN;
end
end

WR_RUN: begin
/* AW 通道只需要握手一次;握手成功后撤销 AWVALID。 */
if (w_wr_aw_fire) begin
m_axi_awvalid <= 1'b0;
r_wr_aw_done <= 1'b1;
end
/* W 通道每个 beat 握手一次,最后一拍撤销 WVALID/WLAST。 */
if (w_wr_w_fire) begin
if (r_wr_beat_idx == (r_wr_len – 8'd1)) begin
m_axi_wvalid <= 1'b0;
m_axi_wlast <= 1'b0;
r_wr_w_done <= 1'b1;
end else begin
r_wr_beat_idx <= r_wr_beat_idx + 8'd1;
/* 固定宽度右移取下一拍,避免超宽可变移位影响时序。 */
m_axi_wdata <= r_wr_data_buf[P_AXI_DATA_WIDTH-1:0];
r_wr_data_buf <= r_wr_data_buf >> P_AXI_DATA_WIDTH;
m_axi_wlast <= ((r_wr_beat_idx + 8'd1) == (r_wr_len – 8'd1));
end
end
/* 地址和全部写数据都被接收后,进入 B 响应阶段。 */
if ((r_wr_aw_done || w_wr_aw_fire) && (r_wr_w_done || w_wr_last_fire)) begin
m_axi_bready <= 1'b1;
r_wr_state <= WR_RESP;
end
end

WR_RESP: begin
/* 收到 BVALID 表示写 burst 完成,保存 BRESP 并给用户侧一个 done 脉冲。 */
if (m_axi_bvalid) begin
o_wr_resp <= m_axi_bresp;
o_wr_done <= 1'b1;
o_wr_busy <= 1'b0;
m_axi_bready <= 1'b0;
r_wr_state <= WR_IDLE;
end
end

default: begin
r_wr_state <= WR_IDLE;
end
endcase

case (r_rd_state)
RD_IDLE: begin
/* 等待用户侧读启动;启动时发出 AR 地址请求。 */
o_rd_busy <= 1'b0;
m_axi_arvalid <= 1'b0;
m_axi_rready <= 1'b0;
if (i_rd_start) begin
r_rd_len <= w_rd_len;
r_rd_beat_idx <= 8'd0;
o_rd_data <= {P_AXI_DATA_WIDTH*P_MAX_BURST_LEN{1'b0}};
o_rd_busy <= 1'b1;
m_axi_arid <= {P_ID_WIDTH{1'b0}};
m_axi_araddr <= i_rd_addr;
m_axi_arlen <= w_rd_len – 8'd1;
m_axi_arsize <= C_AXI_SIZE;
m_axi_arburst <= 2'b01;
m_axi_arlock <= 1'b0;
m_axi_arcache <= P_ARCACHE;
m_axi_arprot <= P_ARPROT;
m_axi_arqos <= P_ARQOS;
m_axi_arregion<= P_ARREGION;
m_axi_arvalid <= 1'b1;
r_rd_state <= RD_ADDR;
end
end

RD_ADDR: begin
/* AR 握手成功后开始接收 R 数据流。 */
if (w_rd_ar_fire) begin
m_axi_arvalid <= 1'b0;
m_axi_rready <= 1'b1;
r_rd_state <= RD_DATA;
end
end

RD_DATA: begin
if (w_rd_r_fire) begin
/*
* 每个 R beat 按 beat_idx 写入输出宽总线。
* slot0 在 o_rd_done 后一次性把这些数据拆回 32-bit 寄存器。
*/
o_rd_data[r_rd_beat_idx*P_AXI_DATA_WIDTH +: P_AXI_DATA_WIDTH] <= m_axi_rdata;
o_rd_resp <= m_axi_rresp;
if (m_axi_rlast || (r_rd_beat_idx == (r_rd_len – 8'd1))) begin
/* 遇到 RLAST 或达到请求 beat 数即认为本次读 burst 完成。 */
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_beat_idx <= r_rd_beat_idx + 8'd1;
end
end
end

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

endmodule

axi4full_slave.v

`timescale 1ns/1ps

/*
* axi4full_slave
* ————–
* 一个用于仿真/简单寄存器后端的 AXI4-Full slave。
*
* 对外是 AXI4-Full slave 端口,对内拆成简单 32-bit 寄存器读写接口:
* – 写 burst:每个 AXI beat 按 32-bit word 拆成 o_reg_wr_* 脉冲。
* – 读 burst:按 32-bit word 向用户寄存器发 o_reg_rd_en,再组回 AXI RDATA。
*
* 当前没有实现复杂乱序、ID 重排、窄 burst 对齐等 DDR 控制器特性;
* 它的目标是给 axi4full_master/slot0 的 burst32、32/64-bit 场景做可读的测试模型。
*/
module axi4full_slave #(
parameter P_AXI_ADDR_WIDTH = 32,
parameter P_AXI_DATA_WIDTH = 64,
parameter P_ID_WIDTH = 1,
parameter P_REG_ADDR_WIDTH = P_AXI_ADDR_WIDTH – 2
)(
input wire i_clk,
input wire i_rst_n,

input wire [P_ID_WIDTH-1:0] s_axi_awid,
input wire [P_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 [3:0] s_axi_awregion,
input wire s_axi_awvalid,
output wire s_axi_awready,

input wire [P_AXI_DATA_WIDTH-1:0] s_axi_wdata,
input wire [(P_AXI_DATA_WIDTH/8)-1:0] s_axi_wstrb,
input wire s_axi_wlast,
input wire s_axi_wvalid,
output wire s_axi_wready,

output reg [P_ID_WIDTH-1:0] s_axi_bid,
output reg [1:0] s_axi_bresp,
output reg s_axi_bvalid,
input wire s_axi_bready,

input wire [P_ID_WIDTH-1:0] s_axi_arid,
input wire [P_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 [3:0] s_axi_arregion,
input wire s_axi_arvalid,
output wire s_axi_arready,

output reg [P_ID_WIDTH-1:0] s_axi_rid,
output reg [P_AXI_DATA_WIDTH-1:0] s_axi_rdata,
output reg [1:0] s_axi_rresp,
output reg s_axi_rlast,
output reg s_axi_rvalid,
input wire s_axi_rready,

output reg o_reg_wr_en,
output reg [P_REG_ADDR_WIDTH-1:0] o_reg_wr_addr,
output reg [31:0] o_reg_wr_data,
output reg [3:0] o_reg_wr_strb,

output reg o_reg_rd_en,
output reg [P_REG_ADDR_WIDTH-1:0] o_reg_rd_addr,
input wire [31:0] i_reg_rd_data
);
/* 一个 AXI beat 内含多少个 32-bit 用户寄存器。64-bit 时为 2。 */
localparam integer C_U32_PER_BEAT = P_AXI_DATA_WIDTH / 32;

/* 写通道先收 AW,再逐 beat 收 W,并拆成 32-bit 用户写。 */
localparam [1:0] WR_IDLE = 2'd0;
localparam [1:0] WR_DATA = 2'd1;
localparam [1:0] WR_SPLIT = 2'd2;
localparam [1:0] WR_RESP = 2'd3;

/* 读通道先收 AR,再捕获用户寄存器数据,最后发 R beat。 */
localparam [1:0] RD_IDLE = 2'd0;
localparam [1:0] RD_CAPTURE = 2'd1;
localparam [1:0] RD_SEND = 2'd2;

reg [1:0] r_wr_state;
reg [1:0] r_rd_state;

reg [P_ID_WIDTH-1:0] r_wr_id;
reg [P_REG_ADDR_WIDTH-1:0] r_wr_base_addr;
reg [7:0] r_wr_len;
reg [7:0] r_wr_beat_idx;
reg [7:0] r_wr_word_idx;
reg [P_AXI_DATA_WIDTH-1:0] r_wdata_buf;
reg [(P_AXI_DATA_WIDTH/8)-1:0] r_wstrb_buf;
reg r_wlast_buf;

reg [P_ID_WIDTH-1:0] r_rd_id;
reg [P_REG_ADDR_WIDTH-1:0] r_rd_base_addr;
reg [7:0] r_rd_len;
reg [7:0] r_rd_beat_idx;
reg [7:0] r_rd_word_idx;
reg [P_AXI_DATA_WIDTH-1:0] r_rdata_buf;

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;

assign s_axi_awready = i_rst_n && (r_wr_state == WR_IDLE);
assign s_axi_wready = i_rst_n && (r_wr_state == WR_DATA);
assign s_axi_arready = i_rst_n && (r_rd_state == RD_IDLE);

/*
* AXI 地址是字节地址,用户寄存器接口按 32-bit word 编址,
* 因此丢弃低 2 bit 得到 word index。
*/
wire [P_REG_ADDR_WIDTH-1:0] w_aw_base_addr =
s_axi_awaddr[P_REG_ADDR_WIDTH+1:2];
wire [P_REG_ADDR_WIDTH-1:0] w_ar_base_addr =
s_axi_araddr[P_REG_ADDR_WIDTH+1:2];

always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_wr_state <= WR_IDLE;
r_wr_id <= {P_ID_WIDTH{1'b0}};
r_wr_base_addr <= {P_REG_ADDR_WIDTH{1'b0}};
r_wr_len <= 8'd0;
r_wr_beat_idx <= 8'd0;
r_wr_word_idx <= 8'd0;
r_wdata_buf <= {P_AXI_DATA_WIDTH{1'b0}};
r_wstrb_buf <= {(P_AXI_DATA_WIDTH/8){1'b0}};
r_wlast_buf <= 1'b0;
s_axi_bid <= {P_ID_WIDTH{1'b0}};
s_axi_bresp <= 2'b00;
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 <= 32'd0;
o_reg_wr_strb <= 4'd0;
end else begin
o_reg_wr_en <= 1'b0;

case (r_wr_state)
WR_IDLE: begin
s_axi_bvalid <= 1'b0;
if (w_aw_fire) begin
/* 锁存写 burst 的起始 word 地址和 beat 数。AWLEN 是 beats-1。 */
r_wr_id <= s_axi_awid;
r_wr_base_addr <= w_aw_base_addr;
r_wr_len <= s_axi_awlen + 8'd1;
r_wr_beat_idx <= 8'd0;
r_wr_word_idx <= 8'd0;
r_wr_state <= WR_DATA;
end
end

WR_DATA: begin
if (w_w_fire) begin
/* 先锁存一个 AXI W beat,下一状态按 32-bit word 拆分。 */
r_wdata_buf <= s_axi_wdata;
r_wstrb_buf <= s_axi_wstrb;
r_wlast_buf <= s_axi_wlast;
r_wr_word_idx <= 8'd0;
r_wr_state <= WR_SPLIT;
end
end

WR_SPLIT: begin
/*
* 对当前 beat 内的每个 32-bit word 产生一次用户写脉冲。
* 地址 = burst base + beat_idx * words_per_beat + word_idx。
*/
o_reg_wr_addr <= r_wr_base_addr +
(r_wr_beat_idx * C_U32_PER_BEAT) +
r_wr_word_idx;
o_reg_wr_data <= r_wdata_buf[r_wr_word_idx*32 +: 32];
o_reg_wr_strb <= r_wstrb_buf[r_wr_word_idx*4 +: 4];
o_reg_wr_en <= |r_wstrb_buf[r_wr_word_idx*4 +: 4];

if (r_wr_word_idx == (C_U32_PER_BEAT – 1)) begin
/* 一个 beat 拆完后,判断是否整个 burst 完成。 */
if (r_wlast_buf || (r_wr_beat_idx == (r_wr_len – 8'd1))) begin
s_axi_bid <= r_wr_id;
s_axi_bresp <= 2'b00;
s_axi_bvalid <= 1'b1;
r_wr_state <= WR_RESP;
end else begin
r_wr_beat_idx <= r_wr_beat_idx + 8'd1;
r_wr_state <= WR_DATA;
end
end else begin
r_wr_word_idx <= r_wr_word_idx + 8'd1;
end
end

WR_RESP: begin
/* 用户侧全部写完后返回 OKAY B 响应。 */
if (w_b_fire) begin
s_axi_bvalid <= 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_id <= {P_ID_WIDTH{1'b0}};
r_rd_base_addr <= {P_REG_ADDR_WIDTH{1'b0}};
r_rd_len <= 8'd0;
r_rd_beat_idx <= 8'd0;
r_rd_word_idx <= 8'd0;
r_rdata_buf <= {P_AXI_DATA_WIDTH{1'b0}};
s_axi_rid <= {P_ID_WIDTH{1'b0}};
s_axi_rdata <= {P_AXI_DATA_WIDTH{1'b0}};
s_axi_rresp <= 2'b00;
s_axi_rlast <= 1'b0;
s_axi_rvalid <= 1'b0;
o_reg_rd_en <= 1'b0;
o_reg_rd_addr <= {P_REG_ADDR_WIDTH{1'b0}};
end else begin
o_reg_rd_en <= 1'b0;

case (r_rd_state)
RD_IDLE: begin
s_axi_rvalid <= 1'b0;
s_axi_rlast <= 1'b0;
if (w_ar_fire) begin
/* 收到 AR 后,先发起当前 beat 的第一个 32-bit 用户读。 */
r_rd_id <= s_axi_arid;
r_rd_base_addr <= w_ar_base_addr;
r_rd_len <= s_axi_arlen + 8'd1;
r_rd_beat_idx <= 8'd0;
r_rd_word_idx <= 8'd0;
r_rdata_buf <= {P_AXI_DATA_WIDTH{1'b0}};
o_reg_rd_addr <= w_ar_base_addr;
o_reg_rd_en <= 1'b1;
r_rd_state <= RD_CAPTURE;
end
end

RD_CAPTURE: begin
/*
* 捕获用户寄存器读数据并组装到 r_rdata_buf。
* 每个 32-bit word 占 RDATA 的一个固定切片。
*/
r_rdata_buf[r_rd_word_idx*32 +: 32] <= i_reg_rd_data;

if (r_rd_word_idx == (C_U32_PER_BEAT – 1)) begin
/* 当前 beat 的所有 word 都取完后,发出一个 AXI R beat。 */
s_axi_rid <= r_rd_id;
s_axi_rdata <= r_rdata_buf;
s_axi_rdata[r_rd_word_idx*32 +: 32] <= i_reg_rd_data;
s_axi_rresp <= 2'b00;
s_axi_rlast <= (r_rd_beat_idx == (r_rd_len – 8'd1));
s_axi_rvalid <= 1'b1;
r_rd_state <= RD_SEND;
end else begin
r_rd_word_idx <= r_rd_word_idx + 8'd1;
o_reg_rd_addr <= r_rd_base_addr +
(r_rd_beat_idx * C_U32_PER_BEAT) +
r_rd_word_idx + 8'd1;
o_reg_rd_en <= 1'b1;
end
end

RD_SEND: begin
if (w_r_fire) begin
s_axi_rvalid <= 1'b0;
if (s_axi_rlast) begin
/* 最后一拍被 master 接收,读 burst 完成。 */
s_axi_rlast <= 1'b0;
r_rd_state <= RD_IDLE;
end else begin
/* 准备下一个 R beat,从下一个 word 地址重新开始捕获。 */
r_rd_beat_idx <= r_rd_beat_idx + 8'd1;
r_rd_word_idx <= 8'd0;
r_rdata_buf <= {P_AXI_DATA_WIDTH{1'b0}};
o_reg_rd_addr <= r_rd_base_addr +
((r_rd_beat_idx + 8'd1) * C_U32_PER_BEAT);
o_reg_rd_en <= 1'b1;
r_rd_state <= RD_CAPTURE;
end
end
end

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

endmodule

smmr_slot_router.v

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

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

// 下游寄存器槽总线,地址为 32 位寄存器索引。
output wire [P_ADDR_WIDTH-3:0] o_slot_wr_addr,
output wire [P_DATA_WIDTH-1:0] o_slot_wr_data,
output wire [P_ADDR_WIDTH-3:0] o_slot_rd_addr,
output wire o_slot0_wr_en,
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

`timescale 1ns/1ps

/*
* slot0
* —–
* 这个模块把一个简单的 32-bit 寄存器窗口和一个 AXI4-Full master 绑在一起,
* 用来测试 GP/ACP 两种 PS<->PL 数据闭环方式。
*
* 软件可见寄存器:
* slot0[0] : 控制寄存器,bit 定义见 w_ctrl_rd_data。
* slot0[1..64] : PS 读窗口。ACP 周期采集时,PL 把这里的 64 个 u32 写到 DDR read window。
* slot0[65..128] : PS 写窗口。PS/DDR 准备好数据后,PL 从 DDR write window 拉回到这里。
*
* AXI4-Full 传输宽度由 P_M_AXI_DATA_WIDTH 配置,当前目标是 64-bit、burst len 32,
* 正好一次 burst 搬运 64 个 u32。内部寄存器仍保持 32-bit u32 组织。
*/
module slot0 #(
// 槽内部寄存器索引宽度。当前顶层传入 8bit,可覆盖 0..255 个 32bit 寄存器索引。
parameter P_ADDR_WIDTH = 8,
// slot0 内部寄存器固定按 32bit u32 组织。
parameter P_DATA_WIDTH = 32,
// 本 slot 在全局寄存器索引空间中的起始地址。slot0 当前固定从 0 开始。
parameter [P_ADDR_WIDTH-1:0] P_SLOT_BASE = 0,
// slot0 寄存器数量:0..128,共 129 个 32bit 寄存器索引。
parameter P_REG_COUNT = 129,

// ACP/AXI4-Full Master 配置。64bit 时一次 burst32 正好搬运 64 个 u32。
parameter P_M_AXI_ADDR_WIDTH = 32,
parameter P_M_AXI_DATA_WIDTH = 64,
parameter P_M_AXI_ID_WIDTH = 1,
parameter P_M_AXI_BURST_LEN = 32,
parameter integer P_ACQ_CLK_FREQ_HZ = 100_000_000,
parameter [P_M_AXI_ADDR_WIDTH-1:0] P_DDR_RD_WINDOW_ADDR = 32'h3F10_0000,
parameter [P_M_AXI_ADDR_WIDTH-1:0] P_DDR_WR_WINDOW_ADDR = 32'h3F10_0100
)(
input wire i_clk,
input wire i_rst_n,

// 简单寄存器写接口:地址是 32bit 寄存器索引,不是字节地址。
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,
// 简单寄存器读接口。i_reg_rd_en 用于实现 slot0[0].bit1 读后自动清零。
input wire i_reg_rd_en,
input wire [P_ADDR_WIDTH-1:0] i_reg_rd_addr,
output reg [P_DATA_WIDTH-1:0] o_reg_rd_data,

output wire o_irq,

output wire [P_M_AXI_ID_WIDTH-1:0] m_axi_awid,
output wire [P_M_AXI_ADDR_WIDTH-1:0] m_axi_awaddr,
output wire [7:0] m_axi_awlen,
output wire [2:0] m_axi_awsize,
output wire [1:0] m_axi_awburst,
output wire m_axi_awlock,
output wire [3:0] m_axi_awcache,
output wire [2:0] m_axi_awprot,
output wire [3:0] m_axi_awqos,
output wire [3:0] m_axi_awregion,
output wire m_axi_awvalid,
input wire m_axi_awready,

output wire [P_M_AXI_DATA_WIDTH-1:0] m_axi_wdata,
output wire [(P_M_AXI_DATA_WIDTH/8)-1:0] m_axi_wstrb,
output wire m_axi_wlast,
output wire m_axi_wvalid,
input wire m_axi_wready,

input wire [P_M_AXI_ID_WIDTH-1:0] m_axi_bid,
input wire [1:0] m_axi_bresp,
input wire m_axi_bvalid,
output wire m_axi_bready,

output wire [P_M_AXI_ID_WIDTH-1:0] m_axi_arid,
output wire [P_M_AXI_ADDR_WIDTH-1:0] m_axi_araddr,
output wire [7:0] m_axi_arlen,
output wire [2:0] m_axi_arsize,
output wire [1:0] m_axi_arburst,
output wire m_axi_arlock,
output wire [3:0] m_axi_arcache,
output wire [2:0] m_axi_arprot,
output wire [3:0] m_axi_arqos,
output wire [3:0] m_axi_arregion,
output wire m_axi_arvalid,
input wire m_axi_arready,

input wire [P_M_AXI_ID_WIDTH-1:0] m_axi_rid,
input wire [P_M_AXI_DATA_WIDTH-1:0] m_axi_rdata,
input wire [1:0] m_axi_rresp,
input wire m_axi_rlast,
input wire m_axi_rvalid,
output wire m_axi_rready
);
/*
* 一个 AXI beat 里包含多少个 32-bit slot word。
* 64-bit AXI 数据宽度时 C_WORDS_PER_BEAT=2,burst32 共传 64 个 u32。
*/
localparam integer C_WORDS_PER_BEAT = P_M_AXI_DATA_WIDTH / 32;
localparam integer C_TRANSFER_WORDS = P_M_AXI_BURST_LEN * C_WORDS_PER_BEAT;
localparam integer C_AXI_BUS_WIDTH = P_M_AXI_DATA_WIDTH * P_M_AXI_BURST_LEN;
localparam [7:0] C_AXI_BURST_LEN_U8 = P_M_AXI_BURST_LEN;

localparam integer C_CTRL_OFFSET = 0;
localparam integer C_READ_OFFSET = 1;
localparam integer C_WRITE_OFFSET = 65;
localparam integer C_SLOT_WORDS = 64;

/*
* 周期采集只支持固定频率档位。
* 这里的除法发生在 elaboration/constant folding 阶段,综合后是常量,
* 不会生成运行时除法器。计数值采用 >= 比较,所以存的是 period-1。
*/
localparam [31:0] C_PERIOD_10HZ =
(P_ACQ_CLK_FREQ_HZ <= 10) ? 32'd0 : ((P_ACQ_CLK_FREQ_HZ / 10) – 1);
localparam [31:0] C_PERIOD_50HZ =
(P_ACQ_CLK_FREQ_HZ <= 50) ? 32'd0 : ((P_ACQ_CLK_FREQ_HZ / 50) – 1);
localparam [31:0] C_PERIOD_100HZ =
(P_ACQ_CLK_FREQ_HZ <= 100) ? 32'd0 : ((P_ACQ_CLK_FREQ_HZ / 100) – 1);
localparam [31:0] C_PERIOD_1KHZ =
(P_ACQ_CLK_FREQ_HZ <= 1000) ? 32'd0 : ((P_ACQ_CLK_FREQ_HZ / 1000) – 1);
localparam [31:0] C_PERIOD_10KHZ =
(P_ACQ_CLK_FREQ_HZ <= 10000) ? 32'd0 : ((P_ACQ_CLK_FREQ_HZ / 10000) – 1);
localparam [31:0] C_PERIOD_20KHZ =
(P_ACQ_CLK_FREQ_HZ <= 20000) ? 32'd0 : ((P_ACQ_CLK_FREQ_HZ / 20000) – 1);

localparam [2:0] ST_IDLE = 3'd0;
localparam [2:0] ST_READ_START = 3'd1;
localparam [2:0] ST_READ_WAIT = 3'd2;
localparam [2:0] ST_COPY = 3'd3;
localparam [2:0] ST_WRITE_START = 3'd4;
localparam [2:0] ST_WRITE_WAIT = 3'd5;

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

reg r_acq_en;
reg r_acq_done_flag;
reg r_mode;
reg r_acp_error_flag;
reg [15:0] r_acq_freq;
reg [31:0] r_acq_period_cnt;
reg [31:0] r_acq_cnt;
/*
* 手动/ISR 请求锁存:
* r_ddr_pull_req : 从 DDR write window 拉数据到 slot0[65..128]。
* r_local_copy_req : GP 模式下只产生本地 done/irq,不访问 DDR。
* r_update_only : ACP ISR 的 ctrl_valid 路径只更新写窗口,不再发新的 done irq。
*/
reg r_ddr_pull_req;
reg r_local_copy_req;
reg r_update_only;
reg r_req_mode;
reg r_run_mode;
reg r_ctrl_rd_clear_pending;
reg [2:0] r_state;
reg r_acq_freq_valid;

reg r_axi_wr_start;
reg r_axi_rd_start;
reg [C_AXI_BUS_WIDTH-1:0] r_axi_wr_data;

wire [C_AXI_BUS_WIDTH-1:0] w_axi_rd_data;
wire w_axi_wr_busy;
wire w_axi_wr_done;
wire [1:0] w_axi_wr_resp;
wire w_axi_rd_busy;
wire w_axi_rd_done;
wire [1:0] w_axi_rd_resp;

wire [P_ADDR_WIDTH-1:0] w_ctrl_addr = P_SLOT_BASE + C_CTRL_OFFSET;
wire w_ctrl_wr = i_reg_wr_en && (i_reg_wr_addr == w_ctrl_addr);
wire w_ctrl_rd = i_reg_rd_en && (i_reg_rd_addr == w_ctrl_addr);

wire w_reg_wr_hit = (i_reg_wr_addr > w_ctrl_addr) &&
(i_reg_wr_addr < (P_SLOT_BASE + P_REG_COUNT));
wire w_reg_rd_hit = (i_reg_rd_addr >= P_SLOT_BASE) &&
(i_reg_rd_addr < (P_SLOT_BASE + P_REG_COUNT));
wire w_acp_busy = (r_state != ST_IDLE) || w_axi_wr_busy || w_axi_rd_busy;
wire w_acq_tick = r_acq_en && !r_acq_done_flag && r_acq_freq_valid &&
(r_acq_cnt >= r_acq_period_cnt);

/*
* slot0[0] 读值。读 slot0[0] 后,r_acq_done_flag 在下一拍自动清零,
* 所以 PS 读 ctrl 同时也是对中断源的 ACK。
*
* bit31:16 r_acq_freq
* bit5 r_acp_error_flag
* bit4 busy
* bit3 mode
* bit2 local/ctrl pending
* bit1 done irq flag
* bit0 acq_en
*/
wire [P_DATA_WIDTH-1:0] w_ctrl_rd_data = {
r_acq_freq,
10'd0,
r_acp_error_flag,
w_acp_busy,
r_mode,
r_local_copy_req,
r_acq_done_flag,
r_acq_en
};

assign o_irq = r_acq_done_flag;

/* 把软件写入的固定频率 Hz 映射成 PL 时钟计数周期。 */
function [31:0] f_freq_to_period_cnt;
input [15:0] freq_hz;
begin
case (freq_hz)
16'd10: f_freq_to_period_cnt = C_PERIOD_10HZ;
16'd50: f_freq_to_period_cnt = C_PERIOD_50HZ;
16'd100: f_freq_to_period_cnt = C_PERIOD_100HZ;
16'd1000: f_freq_to_period_cnt = C_PERIOD_1KHZ;
16'd10000: f_freq_to_period_cnt = C_PERIOD_10KHZ;
16'd20000: f_freq_to_period_cnt = C_PERIOD_20KHZ;
default: f_freq_to_period_cnt = 32'd0;
endcase
end
endfunction

/* 未列入档位的频率不产生周期采集 tick。 */
function f_freq_is_valid;
input [15:0] freq_hz;
begin
case (freq_hz)
16'd10,
16'd50,
16'd100,
16'd1000,
16'd10000,
16'd20000: f_freq_is_valid = 1'b1;
default: f_freq_is_valid = 1'b0;
endcase
end
endfunction

/*
* AXI4-Full master:
* write channel : slot0[1..64] -> DDR read window。
* read channel : DDR write window -> slot0[65..128]。
*
* 地址必须与裸机 slot0_acp.h 中的 SLOT0_DDR_*_WINDOW_ADDR 保持一致。
*/
axi4full_master #(
.P_AXI_ADDR_WIDTH(P_M_AXI_ADDR_WIDTH),
.P_AXI_DATA_WIDTH(P_M_AXI_DATA_WIDTH),
.P_MAX_BURST_LEN (P_M_AXI_BURST_LEN),
.P_ID_WIDTH (P_M_AXI_ID_WIDTH)
) u_axi4full_master (
.m_axi_awid (m_axi_awid),
.m_axi_awaddr (m_axi_awaddr),
.m_axi_awlen (m_axi_awlen),
.m_axi_awsize (m_axi_awsize),
.m_axi_awburst (m_axi_awburst),
.m_axi_awlock (m_axi_awlock),
.m_axi_awcache (m_axi_awcache),
.m_axi_awprot (m_axi_awprot),
.m_axi_awqos (m_axi_awqos),
.m_axi_awregion (m_axi_awregion),
.m_axi_awvalid (m_axi_awvalid),
.m_axi_awready (m_axi_awready),
.m_axi_wdata (m_axi_wdata),
.m_axi_wstrb (m_axi_wstrb),
.m_axi_wlast (m_axi_wlast),
.m_axi_wvalid (m_axi_wvalid),
.m_axi_wready (m_axi_wready),
.m_axi_bid (m_axi_bid),
.m_axi_bresp (m_axi_bresp),
.m_axi_bvalid (m_axi_bvalid),
.m_axi_bready (m_axi_bready),
.m_axi_arid (m_axi_arid),
.m_axi_araddr (m_axi_araddr),
.m_axi_arlen (m_axi_arlen),
.m_axi_arsize (m_axi_arsize),
.m_axi_arburst (m_axi_arburst),
.m_axi_arlock (m_axi_arlock),
.m_axi_arcache (m_axi_arcache),
.m_axi_arprot (m_axi_arprot),
.m_axi_arqos (m_axi_arqos),
.m_axi_arregion (m_axi_arregion),
.m_axi_arvalid (m_axi_arvalid),
.m_axi_arready (m_axi_arready),
.m_axi_rid (m_axi_rid),
.m_axi_rdata (m_axi_rdata),
.m_axi_rresp (m_axi_rresp),
.m_axi_rlast (m_axi_rlast),
.m_axi_rvalid (m_axi_rvalid),
.m_axi_rready (m_axi_rready),
.i_clk (i_clk),
.i_rst_n (i_rst_n),
.i_wr_start (r_axi_wr_start),
.i_wr_addr (P_DDR_RD_WINDOW_ADDR),
.i_wr_len (C_AXI_BURST_LEN_U8),
.i_wr_data (r_axi_wr_data),
.o_wr_busy (w_axi_wr_busy),
.o_wr_done (w_axi_wr_done),
.o_wr_resp (w_axi_wr_resp),
.i_rd_start (r_axi_rd_start),
.i_rd_addr (P_DDR_WR_WINDOW_ADDR),
.i_rd_len (C_AXI_BURST_LEN_U8),
.o_rd_data (w_axi_rd_data),
.o_rd_busy (w_axi_rd_busy),
.o_rd_done (w_axi_rd_done),
.o_rd_resp (w_axi_rd_resp)
);

integer seq_i;
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
for (seq_i = 0; seq_i < P_REG_COUNT; seq_i = seq_i + 1) begin
r_mem[seq_i] <= {P_DATA_WIDTH{1'b0}};
end
r_acq_en <= 1'b0;
r_acq_done_flag <= 1'b0;
r_mode <= 1'b0;
r_acp_error_flag <= 1'b0;
r_acq_freq <= 16'd0;
r_acq_period_cnt <= 32'd0;
r_acq_cnt <= 32'd0;
r_ddr_pull_req <= 1'b0;
r_local_copy_req <= 1'b0;
r_update_only <= 1'b0;
r_req_mode <= 1'b0;
r_run_mode <= 1'b0;
r_ctrl_rd_clear_pending <= 1'b0;
r_state <= ST_IDLE;
r_acq_freq_valid <= 1'b0;
r_axi_wr_start <= 1'b0;
r_axi_rd_start <= 1'b0;
r_axi_wr_data <= {C_AXI_BUS_WIDTH{1'b0}};
end else begin
r_axi_wr_start <= 1'b0;
r_axi_rd_start <= 1'b0;

if (r_ctrl_rd_clear_pending) begin
r_acq_done_flag <= 1'b0;
r_ctrl_rd_clear_pending <= 1'b0;
end

if (w_ctrl_rd) begin
r_ctrl_rd_clear_pending <= 1'b1;
end

if (w_ctrl_wr) begin
r_acq_freq <= i_reg_wr_data[31:16];
r_mode <= i_reg_wr_data[3];

/*
* 频率变化时同步更新周期计数。
* 这里是 case 查表,不是运行时除法;不支持的频率会让
* r_acq_freq_valid=0,周期采集保持静默。
*/
if (i_reg_wr_data[31:16] != r_acq_freq) begin
r_acq_cnt <= 32'd0;
r_acq_period_cnt <= f_freq_to_period_cnt(i_reg_wr_data[31:16]);
r_acq_freq_valid <= f_freq_is_valid(i_reg_wr_data[31:16]);
end

if (i_reg_wr_data[0]) begin
r_acq_en <= 1'b1;
end else if (!i_reg_wr_data[1] && !i_reg_wr_data[2]) begin
r_acq_en <= 1'b0;
end

/* 纯写 0 表示停止采集,并丢弃还没执行的手动请求。 */
if (!i_reg_wr_data[0] && !i_reg_wr_data[1] && !i_reg_wr_data[2]) begin
r_ddr_pull_req <= 1'b0;
r_local_copy_req <= 1'b0;
r_update_only <= 1'b0;
end

if (i_reg_wr_data[1]) begin
if (i_reg_wr_data[3]) begin
r_ddr_pull_req <= 1'b1;
end else begin
r_local_copy_req <= 1'b1;
end
r_req_mode <= i_reg_wr_data[3];
r_update_only <= 1'b0;
r_acq_done_flag <= 1'b0;
r_acp_error_flag <= 1'b0;
end

/*
* ctrl_valid(bit2) 是 PS/ISR 给 PL 的“写窗口数据有效”通知。
* ACP 模式下它会触发 DDR write window -> slot0[65..128],
* 并设置 update_only,避免拉回写窗口后再次产生 done 中断。
*/
if (i_reg_wr_data[2]) begin
if (i_reg_wr_data[3]) begin
r_ddr_pull_req <= 1'b1;
end else begin
r_local_copy_req <= 1'b1;
end
r_req_mode <= i_reg_wr_data[3];
r_update_only <= i_reg_wr_data[3];
r_acq_done_flag <= 1'b0;
r_acp_error_flag <= 1'b0;
end
end

/* PS 直接写 slot0[1..128] 时更新内部 32-bit 寄存器数组。 */
if (i_reg_wr_en && w_reg_wr_hit) begin
r_mem[i_reg_wr_addr-P_SLOT_BASE] <= i_reg_wr_data;
end

case (r_state)
ST_IDLE: begin
/*
* 状态机优先级:
* 1. 手动/ISR DDR 拉回请求。
* 2. 手动 GP 本地完成请求。
* 3. 周期采集 tick。
* 4. 普通计数。
*/
if (r_ddr_pull_req) begin
r_ddr_pull_req <= 1'b0;
r_run_mode <= 1'b1;
r_state <= ST_READ_START;
end else if (r_local_copy_req) begin
r_local_copy_req <= 1'b0;
r_run_mode <= r_req_mode;
r_acq_cnt <= 32'd0;
r_state <= ST_COPY;
end else if (w_acq_tick) begin
r_run_mode <= r_mode;
r_acq_cnt <= 32'd0;
r_update_only <= 1'b0;
r_state <= ST_COPY;
end else if (r_acq_en && !r_acq_done_flag) begin
r_acq_cnt <= r_acq_cnt + 32'd1;
end else begin
r_acq_cnt <= 32'd0;
end
end

ST_READ_START: begin
/* 发起 AXI4-Full read:DDR write window -> w_axi_rd_data。 */
r_axi_rd_start <= 1'b1;
r_state <= ST_READ_WAIT;
end

ST_READ_WAIT: begin
if (w_axi_rd_done) begin
if (w_axi_rd_resp != 2'b00) begin
r_acp_error_flag <= 1'b1;
end
/* read burst 完成后,把 64 个 u32 写入 slot0[65..128]。 */
for (seq_i = 0; seq_i < C_TRANSFER_WORDS; seq_i = seq_i + 1) begin
if (seq_i < C_SLOT_WORDS) begin
r_mem[C_WRITE_OFFSET + seq_i] <= w_axi_rd_data[seq_i*32 +: 32];
end
end
if (r_update_only) begin
r_update_only <= 1'b0;
r_state <= ST_IDLE;
end else begin
r_state <= ST_COPY;
end
end
end

ST_COPY: begin
if (r_update_only) begin
r_update_only <= 1'b0;
r_state <= ST_IDLE;
end else if (r_run_mode) begin
/*
* ACP 周期采集路径:打包 slot0[1..64],下一状态写 DDR read window。
* r_axi_wr_data 是寄存后的宽数据,避免把寄存器数组直接接到 AXI WDATA。
*/
for (seq_i = 0; seq_i < C_TRANSFER_WORDS; seq_i = seq_i + 1) begin
if (seq_i < C_SLOT_WORDS) begin
r_axi_wr_data[seq_i*32 +: 32] <= r_mem[C_READ_OFFSET + seq_i];
end else begin
r_axi_wr_data[seq_i*32 +: 32] <= 32'd0;
end
end
r_state <= ST_WRITE_START;
end else begin
/* GP 周期采集路径:不访问 DDR,只置 done 产生中断。 */
r_acq_done_flag <= 1'b1;
r_state <= ST_IDLE;
end
end

ST_WRITE_START: begin
/* 发起 AXI4-Full write:r_axi_wr_data -> DDR read window。 */
r_axi_wr_start <= 1'b1;
r_state <= ST_WRITE_WAIT;
end

ST_WRITE_WAIT: begin
if (w_axi_wr_done) begin
if (w_axi_wr_resp != 2'b00) begin
r_acp_error_flag <= 1'b1;
end
if (!r_update_only) begin
/* 周期 ACP 上行写 DDR 完成后置 done,通知 PS 进入 ISR。 */
r_acq_done_flag <= 1'b1;
end
r_update_only <= 1'b0;
r_state <= ST_IDLE;
end
end

default: begin
r_state <= ST_IDLE;
end
endcase
end
end

/*
* 简单组合读口。
* slot0[0] 返回控制状态,其余命中地址返回 r_mem[];未命中读 0。
*/
always @(*) begin
if (i_reg_rd_addr == w_ctrl_addr) begin
o_reg_rd_data = w_ctrl_rd_data;
end else if (w_reg_rd_hit) begin
o_reg_rd_data = r_mem[i_reg_rd_addr-P_SLOT_BASE];
end else begin
o_reg_rd_data = {P_DATA_WIDTH{1'b0}};
end
end

endmodule

slot1.v

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

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

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

endmodule

tb.sv

`timescale 1ns/1ps

module tb;
localparam P_REG_ADDR_WIDTH = 8;
localparam P_REG_DATA_WIDTH = 32;
localparam P_AXI_ADDR_WIDTH = 32;
localparam P_AXI_DATA_WIDTH = 64;
localparam P_AXI_ID_WIDTH = 1;
localparam P_AXI_BURST_LEN = 32;
localparam P_ACQ_CLK_FREQ_HZ = 1000;
localparam P_DDR_ADDR_WIDTH = 10;
localparam P_DDR_WORDS = 1024;
localparam P_DDR_RD_ADDR = 32'h0000_0000;
localparam P_DDR_WR_ADDR = 32'h0000_0100;

reg i_clk;
reg i_rst_n;

reg r_slot_wr_en;
reg [P_REG_ADDR_WIDTH-1:0] r_slot_wr_addr;
reg [P_REG_DATA_WIDTH-1:0] r_slot_wr_data;
reg r_slot_rd_en;
reg [P_REG_ADDR_WIDTH-1:0] r_slot_rd_addr;
wire [P_REG_DATA_WIDTH-1:0] w_slot_rd_data;
wire w_slot_irq;

wire [P_AXI_ID_WIDTH-1:0] w_axi_awid;
wire [P_AXI_ADDR_WIDTH-1:0] w_axi_awaddr;
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 [3:0] w_axi_awregion;
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_ID_WIDTH-1:0] w_axi_arid;
wire [P_AXI_ADDR_WIDTH-1:0] w_axi_araddr;
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 [3:0] w_axi_arregion;
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_ddr_wr_en;
wire [P_DDR_ADDR_WIDTH-1:0] w_ddr_wr_addr;
wire [31:0] w_ddr_wr_data;
wire [3:0] w_ddr_wr_strb;
wire w_ddr_rd_en;
wire [P_DDR_ADDR_WIDTH-1:0] w_ddr_rd_addr;
wire [31:0] w_ddr_rd_data;

slot0 #(
.P_ADDR_WIDTH (P_REG_ADDR_WIDTH),
.P_DATA_WIDTH (P_REG_DATA_WIDTH),
.P_SLOT_BASE (0),
.P_REG_COUNT (129),
.P_M_AXI_ADDR_WIDTH (P_AXI_ADDR_WIDTH),
.P_M_AXI_DATA_WIDTH (P_AXI_DATA_WIDTH),
.P_M_AXI_ID_WIDTH (P_AXI_ID_WIDTH),
.P_M_AXI_BURST_LEN (P_AXI_BURST_LEN),
.P_ACQ_CLK_FREQ_HZ (P_ACQ_CLK_FREQ_HZ),
.P_DDR_RD_WINDOW_ADDR(P_DDR_RD_ADDR),
.P_DDR_WR_WINDOW_ADDR(P_DDR_WR_ADDR)
) u_slot0 (
.i_clk (i_clk),
.i_rst_n (i_rst_n),
.i_reg_wr_en (r_slot_wr_en),
.i_reg_wr_addr (r_slot_wr_addr),
.i_reg_wr_data (r_slot_wr_data),
.i_reg_rd_en (r_slot_rd_en),
.i_reg_rd_addr (r_slot_rd_addr),
.o_reg_rd_data (w_slot_rd_data),
.o_irq (w_slot_irq),
.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_awregion (w_axi_awregion),
.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_arregion (w_axi_arregion),
.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)
);

axi4full_slave #(
.P_AXI_ADDR_WIDTH(P_AXI_ADDR_WIDTH),
.P_AXI_DATA_WIDTH(P_AXI_DATA_WIDTH),
.P_ID_WIDTH (P_AXI_ID_WIDTH),
.P_REG_ADDR_WIDTH(P_DDR_ADDR_WIDTH)
) u_axi4full_slave (
.i_clk (i_clk),
.i_rst_n (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_awregion (w_axi_awregion),
.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_arregion (w_axi_arregion),
.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_reg_wr_en (w_ddr_wr_en),
.o_reg_wr_addr (w_ddr_wr_addr),
.o_reg_wr_data (w_ddr_wr_data),
.o_reg_wr_strb (w_ddr_wr_strb),
.o_reg_rd_en (w_ddr_rd_en),
.o_reg_rd_addr (w_ddr_rd_addr),
.i_reg_rd_data (w_ddr_rd_data)
);

reg [31:0] r_ddr_mem [0:P_DDR_WORDS-1];
assign w_ddr_rd_data = r_ddr_mem[w_ddr_rd_addr];

function [31:0] f_strb32_merge;
input [31:0] old_data;
input [31:0] new_data;
input [3:0] strb;
integer b;
begin
f_strb32_merge = old_data;
for (b = 0; b < 4; b = b + 1) begin
if (strb[b]) begin
f_strb32_merge[b*8 +: 8] = new_data[b*8 +: 8];
end
end
end
endfunction

integer mem_i;
initial begin
for (mem_i = 0; mem_i < P_DDR_WORDS; mem_i = mem_i + 1) begin
r_ddr_mem[mem_i] = 32'd0;
end
end

always @(posedge i_clk) begin
if (i_rst_n && w_ddr_wr_en) begin
r_ddr_mem[w_ddr_wr_addr] <=
f_strb32_merge(r_ddr_mem[w_ddr_wr_addr], w_ddr_wr_data, w_ddr_wr_strb);
end
end

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

initial begin
repeat (10000) @(posedge i_clk);
$fatal(1, "simulation timeout");
end

always @(posedge i_clk) begin
if (i_rst_n && w_axi_awvalid && w_axi_awready) begin
if (w_axi_awaddr != P_DDR_RD_ADDR) $fatal(1, "AWADDR mismatch");
if (w_axi_awlen != 8'd31) $fatal(1, "AWLEN mismatch");
if (w_axi_awsize != 3'd3) $fatal(1, "AWSIZE mismatch");
if (w_axi_awburst != 2'b01) $fatal(1, "AWBURST mismatch");
end
if (i_rst_n && w_axi_arvalid && w_axi_arready) begin
if (w_axi_araddr != P_DDR_WR_ADDR) $fatal(1, "ARADDR mismatch");
if (w_axi_arlen != 8'd31) $fatal(1, "ARLEN mismatch");
if (w_axi_arsize != 3'd3) $fatal(1, "ARSIZE mismatch");
if (w_axi_arburst != 2'b01) $fatal(1, "ARBURST mismatch");
end
end

task reset_dut;
begin
i_rst_n <= 1'b0;
r_slot_wr_en <= 1'b0;
r_slot_wr_addr <= {P_REG_ADDR_WIDTH{1'b0}};
r_slot_wr_data <= 32'd0;
r_slot_rd_en <= 1'b0;
r_slot_rd_addr <= {P_REG_ADDR_WIDTH{1'b0}};
repeat (5) @(posedge i_clk);
i_rst_n <= 1'b1;
repeat (2) @(posedge i_clk);
end
endtask

task slot_write;
input [P_REG_ADDR_WIDTH-1:0] addr;
input [31:0] data;
begin
@(negedge i_clk);
r_slot_wr_addr = addr;
r_slot_wr_data = data;
r_slot_wr_en = 1'b1;
@(negedge i_clk);
r_slot_wr_en = 1'b0;
end
endtask

task slot_read;
input [P_REG_ADDR_WIDTH-1:0] addr;
output [31:0] data;
begin
@(negedge i_clk);
r_slot_rd_addr = addr;
r_slot_rd_en = 1'b1;
@(negedge i_clk);
data = w_slot_rd_data;
r_slot_rd_en = 1'b0;
end
endtask

task load_ddr_write_window;
input [31:0] base_value;
integer j;
integer base_idx;
begin
base_idx = P_DDR_WR_ADDR >> 2;
for (j = 0; j < 64; j = j + 1) begin
r_ddr_mem[base_idx + j] = base_value + j;
end
end
endtask

task load_slot_write_window;
input [31:0] base_value;
integer j;
begin
for (j = 0; j < 64; j = j + 1) begin
slot_write(8'd65 + j, base_value + j);
end
end
endtask

task load_slot_read_window;
input [31:0] base_value;
integer j;
begin
for (j = 0; j < 64; j = j + 1) begin
slot_write(8'd1 + j, base_value + j);
end
end
endtask

task wait_irq;
integer timeout_i;
begin
timeout_i = 0;
while (!w_slot_irq && (timeout_i < 5000)) begin
@(posedge i_clk);
timeout_i = timeout_i + 1;
end
if (!w_slot_irq) $fatal(1, "slot0 irq timeout");
end
endtask

task check_ddr_read_window;
input [31:0] base_value;
integer j;
integer base_idx;
begin
base_idx = P_DDR_RD_ADDR >> 2;
for (j = 0; j < 64; j = j + 1) begin
if (r_ddr_mem[base_idx + j] !== (base_value + j)) begin
$fatal(1, "DDR read window mismatch idx=%0d exp=0x%08x got=0x%08x",
base_idx + j,
base_value + j,
r_ddr_mem[base_idx + j]);
end
end
end
endtask

task check_slot_window;
input [P_REG_ADDR_WIDTH-1:0] base_addr;
input [31:0] base_value;
integer j;
reg [31:0] rd_data;
begin
for (j = 0; j < 64; j = j + 1) begin
slot_read(base_addr + j, rd_data);
if (rd_data !== (base_value + j)) begin
$fatal(1, "slot window mismatch addr=%0d exp=0x%08x got=0x%08x",
base_addr + j,
base_value + j,
rd_data);
end
end
end
endtask

reg [31:0] ctrl_data;
initial begin
reset_dut;

load_slot_write_window(32'hA000_0000);
slot_write(8'd0, 32'h0000_0004);
wait_irq;
check_slot_window(8'd65, 32'hA000_0000);
slot_read(8'd0, ctrl_data);
if (!ctrl_data[1]) $fatal(1, "done flag not set");
if (ctrl_data[3]) $fatal(1, "mode bit should be GP");
repeat (3) @(posedge i_clk);
slot_read(8'd0, ctrl_data);
if (ctrl_data[1]) $fatal(1, "done flag did not auto clear");
$display("[%0t] slot0 GP ctrl_valid irq without copy PASS", $time);

load_ddr_write_window(32'hB000_1000);
slot_write(8'd0, 32'h0000_000C);
repeat (200) @(posedge i_clk);
check_slot_window(8'd65, 32'hB000_1000);
slot_read(8'd0, ctrl_data);
if (!ctrl_data[3]) $fatal(1, "mode bit should be ACP");
if (ctrl_data[1]) $fatal(1, "ACP bit2 update should not set done flag");
$display("[%0t] slot0 ACP bit2 DDR pull to write-window only PASS", $time);

load_slot_read_window(32'hC000_2000);
slot_write(8'd0, 32'h0064_0009);
wait_irq;
check_slot_window(8'd1, 32'hC000_2000);
check_ddr_read_window(32'hC000_2000);
slot_write(8'd0, 32'h0000_0000);
slot_read(8'd0, ctrl_data);
if (!ctrl_data[1]) $fatal(1, "periodic ACP done flag not set");
repeat (3) @(posedge i_clk);
slot_read(8'd0, ctrl_data);
if (ctrl_data[1]) $fatal(1, "periodic ACP done flag did not auto clear");
$display("[%0t] slot0 periodic ACP acq_freq Hz write DDR PASS", $time);

repeat (5) @(posedge i_clk);
$display("slot0 axi4full_master DATA_WIDTH=64 BURST_LEN=32 test PASS");
$finish;
end
endmodule

赞(0)
未经允许不得转载:171主机测评 » zynq高频小数据量PS闭环的三个实验-第1课:PL搭建
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址