rtl: add axiS dma wrapper & tests

This commit is contained in:
Phil
2026-06-23 18:17:28 +03:00
parent 844c7ea25f
commit 3cc1235eea
4 changed files with 1268 additions and 0 deletions

View File

@ -0,0 +1,84 @@
# Simple cocotb makefile for tb_axi_dma_axis_compat
TOPLEVEL_LANG = verilog
SIM ?= verilator
PWD := $(shell pwd)
PROJECT_ROOT ?= $(abspath $(PWD)/../../..)
AXI_IF_RTL_DIR ?= $(PROJECT_ROOT)/axi/rtl
FORENCICH_AXI_RTL_DIR ?= $(PROJECT_ROOT)/external/verilog-axi/rtl
TB_DIR ?= $(PWD)
TOPLEVEL = tb_axi_dma_axis_compat
MODULE = test_axi_dma_axis_compat
export PYTHONPATH := $(TB_DIR):$(PYTHONPATH)
AXI_DATA_WIDTH ?= 32
AXI_ADDR_WIDTH ?= 16
AXI_ID_WIDTH ?= 8
AXI_USER_WIDTH ?= 1
AXI_MAX_BURST_LEN ?= 16
ENABLE_UNALIGNED ?= 0
AXI_STRB_WIDTH := $(shell expr $(AXI_DATA_WIDTH) / 8)
AXIS_DATA_WIDTH ?= $(AXI_DATA_WIDTH)
AXIS_KEEP_WIDTH := $(shell expr $(AXIS_DATA_WIDTH) / 8)
AXIS_KEEP_ENABLE := $(shell [ $(AXIS_DATA_WIDTH) -gt 8 ] && echo 1 || echo 0)
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axi_pkg.sv
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axi_if.sv
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axis_if.sv
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axi4_flat_to_if.sv
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axi4_if_to_flat.sv
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axis_flat_to_if.sv
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axis_if_to_flat.sv
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axi_dma_desc_if.sv
VERILOG_SOURCES += $(FORENCICH_AXI_RTL_DIR)/axi_dma.v
VERILOG_SOURCES += $(FORENCICH_AXI_RTL_DIR)/axi_dma_rd.v
VERILOG_SOURCES += $(FORENCICH_AXI_RTL_DIR)/axi_dma_wr.v
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axi_dma_if_wrapper.sv
VERILOG_SOURCES += $(AXI_IF_RTL_DIR)/axi_dma_compat.sv
VERILOG_SOURCES += $(TB_DIR)/tb_axi_dma_axis_compat.sv
COMPILE_ARGS += -I$(AXI_IF_RTL_DIR)
COMPILE_ARGS += -I$(FORENCICH_AXI_RTL_DIR)
# took this from forencich to silence 100+ warnings
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
ifeq ($(SIM),verilator)
EXTRA_ARGS += --trace
EXTRA_ARGS += --trace-structs
EXTRA_ARGS += -GAXI_DATA_WIDTH=$(AXI_DATA_WIDTH)
EXTRA_ARGS += -GAXI_ADDR_WIDTH=$(AXI_ADDR_WIDTH)
EXTRA_ARGS += -GAXI_STRB_WIDTH=$(AXI_STRB_WIDTH)
EXTRA_ARGS += -GAXI_ID_WIDTH=$(AXI_ID_WIDTH)
EXTRA_ARGS += -GAXI_USER_WIDTH=$(AXI_USER_WIDTH)
EXTRA_ARGS += -GAXI_MAX_BURST_LEN=$(AXI_MAX_BURST_LEN)
EXTRA_ARGS += -GAXIS_DATA_WIDTH=$(AXIS_DATA_WIDTH)
EXTRA_ARGS += -GAXIS_KEEP_ENABLE=$(AXIS_KEEP_ENABLE)
EXTRA_ARGS += -GAXIS_KEEP_WIDTH=$(AXIS_KEEP_WIDTH)
EXTRA_ARGS += -GAXIS_LAST_ENABLE=1
EXTRA_ARGS += -GAXIS_ID_ENABLE=1
EXTRA_ARGS += -GAXIS_ID_WIDTH=8
EXTRA_ARGS += -GAXIS_DEST_ENABLE=0
EXTRA_ARGS += -GAXIS_DEST_WIDTH=8
EXTRA_ARGS += -GAXIS_USER_ENABLE=1
EXTRA_ARGS += -GAXIS_USER_WIDTH=1
EXTRA_ARGS += -GLEN_WIDTH=20
EXTRA_ARGS += -GTAG_WIDTH=8
EXTRA_ARGS += -GENABLE_SG=0
EXTRA_ARGS += -GENABLE_UNALIGNED=$(ENABLE_UNALIGNED)
endif
export PARAM_AXI_DATA_WIDTH=$(AXI_DATA_WIDTH)
export PARAM_ENABLE_UNALIGNED=$(ENABLE_UNALIGNED)
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@ -0,0 +1,477 @@
// another wrapper...
`default_nettype none
module tb_axi_dma_axis_compat #(
parameter int unsigned AXI_DATA_WIDTH = 32,
parameter int unsigned AXI_ADDR_WIDTH = 16,
parameter int unsigned AXI_STRB_WIDTH = AXI_DATA_WIDTH / 8,
parameter int unsigned AXI_ID_WIDTH = 8,
parameter int unsigned AXI_USER_WIDTH = 1,
parameter int unsigned AXI_MAX_BURST_LEN = 16,
parameter int unsigned AXIS_DATA_WIDTH = AXI_DATA_WIDTH,
parameter int unsigned AXIS_KEEP_ENABLE = AXIS_DATA_WIDTH > 8,
parameter int unsigned AXIS_KEEP_WIDTH = AXIS_DATA_WIDTH / 8,
parameter int unsigned AXIS_LAST_ENABLE = 1,
parameter int unsigned AXIS_ID_ENABLE = 1,
parameter int unsigned AXIS_ID_WIDTH = 8,
parameter int unsigned AXIS_DEST_ENABLE = 0,
parameter int unsigned AXIS_DEST_WIDTH = 8,
parameter int unsigned AXIS_USER_ENABLE = 1,
parameter int unsigned AXIS_USER_WIDTH = 1,
parameter int unsigned LEN_WIDTH = 20,
parameter int unsigned TAG_WIDTH = 8,
parameter int unsigned ENABLE_SG = 0,
parameter int unsigned ENABLE_UNALIGNED = 0
)();
// cocotb drives these directly
logic clk;
logic rst;
logic rstn;
assign rstn = ~rst;
// --------------------------------------------------------------------------
// Descriptor/status payload widths and tdata layouts.
// --------------------------------------------------------------------------
localparam int unsigned READ_DESC_PAYLOAD_WIDTH = AXI_ADDR_WIDTH + LEN_WIDTH + TAG_WIDTH + AXIS_ID_WIDTH + AXIS_DEST_WIDTH + AXIS_USER_WIDTH;
localparam int unsigned READ_DESC_AXIS_DATA_WIDTH = ((READ_DESC_PAYLOAD_WIDTH + 7) / 8) * 8;
localparam int unsigned READ_DESC_AXIS_KEEP_WIDTH = READ_DESC_AXIS_DATA_WIDTH / 8;
localparam int unsigned READ_DESC_STATUS_PAYLOAD_WIDTH = TAG_WIDTH + 4;
localparam int unsigned READ_DESC_STATUS_AXIS_DATA_WIDTH = ((READ_DESC_STATUS_PAYLOAD_WIDTH + 7) / 8) * 8;
localparam int unsigned READ_DESC_STATUS_AXIS_KEEP_WIDTH = READ_DESC_STATUS_AXIS_DATA_WIDTH / 8;
localparam int unsigned WRITE_DESC_PAYLOAD_WIDTH = AXI_ADDR_WIDTH + LEN_WIDTH + TAG_WIDTH;
localparam int unsigned WRITE_DESC_AXIS_DATA_WIDTH = ((WRITE_DESC_PAYLOAD_WIDTH + 7) / 8) * 8;
localparam int unsigned WRITE_DESC_AXIS_KEEP_WIDTH = WRITE_DESC_AXIS_DATA_WIDTH / 8;
localparam int unsigned WRITE_DESC_STATUS_PAYLOAD_WIDTH = LEN_WIDTH + TAG_WIDTH + AXIS_ID_WIDTH + AXIS_DEST_WIDTH + AXIS_USER_WIDTH + 4;
localparam int unsigned WRITE_DESC_STATUS_AXIS_DATA_WIDTH = ((WRITE_DESC_STATUS_PAYLOAD_WIDTH + 7) / 8) * 8;
localparam int unsigned WRITE_DESC_STATUS_AXIS_KEEP_WIDTH = WRITE_DESC_STATUS_AXIS_DATA_WIDTH / 8;
localparam int unsigned RD_ADDR_LSB = 0;
localparam int unsigned RD_LEN_LSB = RD_ADDR_LSB + AXI_ADDR_WIDTH;
localparam int unsigned RD_TAG_LSB = RD_LEN_LSB + LEN_WIDTH;
localparam int unsigned RD_ID_LSB = RD_TAG_LSB + TAG_WIDTH;
localparam int unsigned RD_DEST_LSB = RD_ID_LSB + AXIS_ID_WIDTH;
localparam int unsigned RD_USER_LSB = RD_DEST_LSB + AXIS_DEST_WIDTH;
localparam int unsigned RDS_TAG_LSB = 0;
localparam int unsigned RDS_ERROR_LSB = RDS_TAG_LSB + TAG_WIDTH;
localparam int unsigned WD_ADDR_LSB = 0;
localparam int unsigned WD_LEN_LSB = WD_ADDR_LSB + AXI_ADDR_WIDTH;
localparam int unsigned WD_TAG_LSB = WD_LEN_LSB + LEN_WIDTH;
localparam int unsigned WDS_LEN_LSB = 0;
localparam int unsigned WDS_TAG_LSB = WDS_LEN_LSB + LEN_WIDTH;
localparam int unsigned WDS_ID_LSB = WDS_TAG_LSB + TAG_WIDTH;
localparam int unsigned WDS_DEST_LSB = WDS_ID_LSB + AXIS_ID_WIDTH;
localparam int unsigned WDS_USER_LSB = WDS_DEST_LSB + AXIS_DEST_WIDTH;
localparam int unsigned WDS_ERROR_LSB = WDS_USER_LSB + AXIS_USER_WIDTH;
// --------------------------------------------------------------------------
// Flat descriptor/status ports visible to cocotb.
// --------------------------------------------------------------------------
logic [AXI_ADDR_WIDTH-1:0] s_axis_read_desc_addr;
logic [LEN_WIDTH-1:0] s_axis_read_desc_len;
logic [TAG_WIDTH-1:0] s_axis_read_desc_tag;
logic [AXIS_ID_WIDTH-1:0] s_axis_read_desc_id;
logic [AXIS_DEST_WIDTH-1:0] s_axis_read_desc_dest;
logic [AXIS_USER_WIDTH-1:0] s_axis_read_desc_user;
logic s_axis_read_desc_valid;
logic s_axis_read_desc_ready;
logic [TAG_WIDTH-1:0] m_axis_read_desc_status_tag;
logic [3:0] m_axis_read_desc_status_error;
logic m_axis_read_desc_status_valid;
logic [AXI_ADDR_WIDTH-1:0] s_axis_write_desc_addr;
logic [LEN_WIDTH-1:0] s_axis_write_desc_len;
logic [TAG_WIDTH-1:0] s_axis_write_desc_tag;
logic s_axis_write_desc_valid;
logic s_axis_write_desc_ready;
logic [LEN_WIDTH-1:0] m_axis_write_desc_status_len;
logic [TAG_WIDTH-1:0] m_axis_write_desc_status_tag;
logic [AXIS_ID_WIDTH-1:0] m_axis_write_desc_status_id;
logic [AXIS_DEST_WIDTH-1:0] m_axis_write_desc_status_dest;
logic [AXIS_USER_WIDTH-1:0] m_axis_write_desc_status_user;
logic [3:0] m_axis_write_desc_status_error;
logic m_axis_write_desc_status_valid;
// --------------------------------------------------------------------------
// Flat AXIS data ports visible to cocotb.
// --------------------------------------------------------------------------
logic [AXIS_DATA_WIDTH-1:0] m_axis_read_data_tdata;
logic [AXIS_KEEP_WIDTH-1:0] m_axis_read_data_tkeep;
logic [AXIS_KEEP_WIDTH-1:0] m_axis_read_data_tstrb;
logic m_axis_read_data_tvalid;
logic m_axis_read_data_tready;
logic m_axis_read_data_tlast;
logic [AXIS_ID_WIDTH-1:0] m_axis_read_data_tid;
logic [AXIS_DEST_WIDTH-1:0] m_axis_read_data_tdest;
logic [AXIS_USER_WIDTH-1:0] m_axis_read_data_tuser;
logic [AXIS_DATA_WIDTH-1:0] s_axis_write_data_tdata;
logic [AXIS_KEEP_WIDTH-1:0] s_axis_write_data_tkeep;
logic [AXIS_KEEP_WIDTH-1:0] s_axis_write_data_tstrb;
logic s_axis_write_data_tvalid;
logic s_axis_write_data_tready;
logic s_axis_write_data_tlast;
logic [AXIS_ID_WIDTH-1:0] s_axis_write_data_tid;
logic [AXIS_DEST_WIDTH-1:0] s_axis_write_data_tdest;
logic [AXIS_USER_WIDTH-1:0] s_axis_write_data_tuser;
// --------------------------------------------------------------------------
// Flat AXI memory master ports visible to cocotb AxiRam.
// --------------------------------------------------------------------------
logic [AXI_ID_WIDTH-1:0] m_axi_awid;
logic [AXI_ADDR_WIDTH-1:0] m_axi_awaddr;
logic [7:0] m_axi_awlen;
logic [2:0] m_axi_awsize;
logic [1:0] m_axi_awburst;
logic m_axi_awlock;
logic [3:0] m_axi_awcache;
logic [2:0] m_axi_awprot;
logic [3:0] m_axi_awqos;
logic [3:0] m_axi_awregion;
logic [AXI_USER_WIDTH-1:0] m_axi_awuser;
logic m_axi_awvalid;
logic m_axi_awready;
logic [AXI_DATA_WIDTH-1:0] m_axi_wdata;
logic [AXI_STRB_WIDTH-1:0] m_axi_wstrb;
logic m_axi_wlast;
logic [AXI_USER_WIDTH-1:0] m_axi_wuser;
logic m_axi_wvalid;
logic m_axi_wready;
logic [AXI_ID_WIDTH-1:0] m_axi_bid;
logic [1:0] m_axi_bresp;
logic [AXI_USER_WIDTH-1:0] m_axi_buser;
logic m_axi_bvalid;
logic m_axi_bready;
logic [AXI_ID_WIDTH-1:0] m_axi_arid;
logic [AXI_ADDR_WIDTH-1:0] m_axi_araddr;
logic [7:0] m_axi_arlen;
logic [2:0] m_axi_arsize;
logic [1:0] m_axi_arburst;
logic m_axi_arlock;
logic [3:0] m_axi_arcache;
logic [2:0] m_axi_arprot;
logic [3:0] m_axi_arqos;
logic [3:0] m_axi_arregion;
logic [AXI_USER_WIDTH-1:0] m_axi_aruser;
logic m_axi_arvalid;
logic m_axi_arready;
logic [AXI_ID_WIDTH-1:0] m_axi_rid;
logic [AXI_DATA_WIDTH-1:0] m_axi_rdata;
logic [1:0] m_axi_rresp;
logic m_axi_rlast;
logic [AXI_USER_WIDTH-1:0] m_axi_ruser;
logic m_axi_rvalid;
logic m_axi_rready;
// Configuration visible to cocotb
logic read_enable;
logic write_enable;
logic write_abort;
// --------------------------------------------------------------------------
// Local interface instances.
// --------------------------------------------------------------------------
axis_if #(
.DATA_W (READ_DESC_AXIS_DATA_WIDTH),
.KEEP_W (READ_DESC_AXIS_KEEP_WIDTH),
.ID_W (1),
.DEST_W (1),
.USER_W (1)
) s_axis_read_desc_axis_if (
.aclk (clk),
.aresetn (rstn)
);
axis_if #(
.DATA_W (READ_DESC_STATUS_AXIS_DATA_WIDTH),
.KEEP_W (READ_DESC_STATUS_AXIS_KEEP_WIDTH),
.ID_W (1),
.DEST_W (1),
.USER_W (1)
) m_axis_read_desc_status_axis_if (
.aclk (clk),
.aresetn (rstn)
);
axis_if #(
.DATA_W (WRITE_DESC_AXIS_DATA_WIDTH),
.KEEP_W (WRITE_DESC_AXIS_KEEP_WIDTH),
.ID_W (1),
.DEST_W (1),
.USER_W (1)
) s_axis_write_desc_axis_if (
.aclk (clk),
.aresetn (rstn)
);
axis_if #(
.DATA_W (WRITE_DESC_STATUS_AXIS_DATA_WIDTH),
.KEEP_W (WRITE_DESC_STATUS_AXIS_KEEP_WIDTH),
.ID_W (1),
.DEST_W (1),
.USER_W (1)
) m_axis_write_desc_status_axis_if (
.aclk (clk),
.aresetn (rstn)
);
axis_if #(
.DATA_W (AXIS_DATA_WIDTH),
.KEEP_W (AXIS_KEEP_WIDTH),
.ID_W (AXIS_ID_WIDTH),
.DEST_W (AXIS_DEST_WIDTH),
.USER_W (AXIS_USER_WIDTH)
) m_axis_read_data_if (
.aclk (clk),
.aresetn (rstn)
);
axis_if #(
.DATA_W (AXIS_DATA_WIDTH),
.KEEP_W (AXIS_KEEP_WIDTH),
.ID_W (AXIS_ID_WIDTH),
.DEST_W (AXIS_DEST_WIDTH),
.USER_W (AXIS_USER_WIDTH)
) s_axis_write_data_if (
.aclk (clk),
.aresetn (rstn)
);
axi4_if #(
.ADDR_W (AXI_ADDR_WIDTH),
.DATA_W (AXI_DATA_WIDTH),
.ID_W (AXI_ID_WIDTH),
.USER_W (AXI_USER_WIDTH)
) m_axi_if (
.aclk (clk),
.aresetn (rstn)
);
// --------------------------------------------------------------------------
// Flat descriptor/status <-> AXIS descriptor/status packing.
// --------------------------------------------------------------------------
always_comb begin
s_axis_read_desc_axis_if.req.t.data = '0;
s_axis_read_desc_axis_if.req.t.keep = {READ_DESC_AXIS_KEEP_WIDTH{s_axis_read_desc_valid}};
s_axis_read_desc_axis_if.req.t.strb = {READ_DESC_AXIS_KEEP_WIDTH{s_axis_read_desc_valid}};
s_axis_read_desc_axis_if.req.t.last = s_axis_read_desc_valid;
s_axis_read_desc_axis_if.req.t.id = '0;
s_axis_read_desc_axis_if.req.t.dest = '0;
s_axis_read_desc_axis_if.req.t.user = '0;
s_axis_read_desc_axis_if.req.t.valid = s_axis_read_desc_valid;
s_axis_read_desc_axis_if.req.t.data[RD_ADDR_LSB +: AXI_ADDR_WIDTH] = s_axis_read_desc_addr;
s_axis_read_desc_axis_if.req.t.data[RD_LEN_LSB +: LEN_WIDTH] = s_axis_read_desc_len;
s_axis_read_desc_axis_if.req.t.data[RD_TAG_LSB +: TAG_WIDTH] = s_axis_read_desc_tag;
s_axis_read_desc_axis_if.req.t.data[RD_ID_LSB +: AXIS_ID_WIDTH] = s_axis_read_desc_id;
s_axis_read_desc_axis_if.req.t.data[RD_DEST_LSB +: AXIS_DEST_WIDTH] = s_axis_read_desc_dest;
s_axis_read_desc_axis_if.req.t.data[RD_USER_LSB +: AXIS_USER_WIDTH] = s_axis_read_desc_user;
end
assign s_axis_read_desc_ready = s_axis_read_desc_axis_if.resp.ready;
always_comb begin
m_axis_read_desc_status_tag = m_axis_read_desc_status_axis_if.req.t.data[RDS_TAG_LSB +: TAG_WIDTH];
m_axis_read_desc_status_error = m_axis_read_desc_status_axis_if.req.t.data[RDS_ERROR_LSB +: 4];
m_axis_read_desc_status_valid = m_axis_read_desc_status_axis_if.req.t.valid;
end
assign m_axis_read_desc_status_axis_if.resp.ready = 1'b1;
always_comb begin
s_axis_write_desc_axis_if.req.t.data = '0;
s_axis_write_desc_axis_if.req.t.keep = {WRITE_DESC_AXIS_KEEP_WIDTH{s_axis_write_desc_valid}};
s_axis_write_desc_axis_if.req.t.strb = {WRITE_DESC_AXIS_KEEP_WIDTH{s_axis_write_desc_valid}};
s_axis_write_desc_axis_if.req.t.last = s_axis_write_desc_valid;
s_axis_write_desc_axis_if.req.t.id = '0;
s_axis_write_desc_axis_if.req.t.dest = '0;
s_axis_write_desc_axis_if.req.t.user = '0;
s_axis_write_desc_axis_if.req.t.valid = s_axis_write_desc_valid;
s_axis_write_desc_axis_if.req.t.data[WD_ADDR_LSB +: AXI_ADDR_WIDTH] = s_axis_write_desc_addr;
s_axis_write_desc_axis_if.req.t.data[WD_LEN_LSB +: LEN_WIDTH] = s_axis_write_desc_len;
s_axis_write_desc_axis_if.req.t.data[WD_TAG_LSB +: TAG_WIDTH] = s_axis_write_desc_tag;
end
assign s_axis_write_desc_ready = s_axis_write_desc_axis_if.resp.ready;
always_comb begin
m_axis_write_desc_status_len = m_axis_write_desc_status_axis_if.req.t.data[WDS_LEN_LSB +: LEN_WIDTH];
m_axis_write_desc_status_tag = m_axis_write_desc_status_axis_if.req.t.data[WDS_TAG_LSB +: TAG_WIDTH];
m_axis_write_desc_status_id = m_axis_write_desc_status_axis_if.req.t.data[WDS_ID_LSB +: AXIS_ID_WIDTH];
m_axis_write_desc_status_dest = m_axis_write_desc_status_axis_if.req.t.data[WDS_DEST_LSB +: AXIS_DEST_WIDTH];
m_axis_write_desc_status_user = m_axis_write_desc_status_axis_if.req.t.data[WDS_USER_LSB +: AXIS_USER_WIDTH];
m_axis_write_desc_status_error = m_axis_write_desc_status_axis_if.req.t.data[WDS_ERROR_LSB +: 4];
m_axis_write_desc_status_valid = m_axis_write_desc_status_axis_if.req.t.valid;
end
assign m_axis_write_desc_status_axis_if.resp.ready = 1'b1;
// --------------------------------------------------------------------------
// Flat write stream -> interface.
// --------------------------------------------------------------------------
axis_flat_to_if #(
.DATA_W (AXIS_DATA_WIDTH),
.KEEP_W (AXIS_KEEP_WIDTH),
.ID_W (AXIS_ID_WIDTH),
.DEST_W (AXIS_DEST_WIDTH),
.USER_W (AXIS_USER_WIDTH)
) u_s_axis_write_data_flat_to_if (
.s_axis_tdata (s_axis_write_data_tdata),
.s_axis_tkeep (s_axis_write_data_tkeep),
.s_axis_tstrb (s_axis_write_data_tstrb),
.s_axis_tlast (s_axis_write_data_tlast),
.s_axis_tid (s_axis_write_data_tid),
.s_axis_tdest (s_axis_write_data_tdest),
.s_axis_tuser (s_axis_write_data_tuser),
.s_axis_tvalid (s_axis_write_data_tvalid),
.s_axis_tready (s_axis_write_data_tready),
.m_axis (s_axis_write_data_if)
);
// Wrapper read stream interface -> flat stream.
axis_if_to_flat #(
.DATA_W (AXIS_DATA_WIDTH),
.KEEP_W (AXIS_KEEP_WIDTH),
.ID_W (AXIS_ID_WIDTH),
.DEST_W (AXIS_DEST_WIDTH),
.USER_W (AXIS_USER_WIDTH)
) u_m_axis_read_data_if_to_flat (
.s_axis (m_axis_read_data_if),
.m_axis_tdata (m_axis_read_data_tdata),
.m_axis_tkeep (m_axis_read_data_tkeep),
.m_axis_tstrb (m_axis_read_data_tstrb),
.m_axis_tlast (m_axis_read_data_tlast),
.m_axis_tid (m_axis_read_data_tid),
.m_axis_tdest (m_axis_read_data_tdest),
.m_axis_tuser (m_axis_read_data_tuser),
.m_axis_tvalid (m_axis_read_data_tvalid),
.m_axis_tready (m_axis_read_data_tready)
);
// Wrapper AXI interface -> flat AXI memory bus.
axi4_if_to_flat #(
.ADDR_W (AXI_ADDR_WIDTH),
.DATA_W (AXI_DATA_WIDTH),
.ID_W (AXI_ID_WIDTH),
.USER_W (AXI_USER_WIDTH)
) u_m_axi_if_to_flat (
.s_axi (m_axi_if),
.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_awuser (m_axi_awuser),
.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_wuser (m_axi_wuser),
.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_buser (m_axi_buser),
.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_aruser (m_axi_aruser),
.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_ruser (m_axi_ruser),
.m_axi_rvalid (m_axi_rvalid),
.m_axi_rready (m_axi_rready)
);
axi_dma_axis_compat #(
.AXI_DATA_WIDTH (AXI_DATA_WIDTH),
.AXI_ADDR_WIDTH (AXI_ADDR_WIDTH),
.AXI_STRB_WIDTH (AXI_STRB_WIDTH),
.AXI_ID_WIDTH (AXI_ID_WIDTH),
.AXI_USER_WIDTH (AXI_USER_WIDTH),
.AXI_MAX_BURST_LEN (AXI_MAX_BURST_LEN),
.AXIS_DATA_WIDTH (AXIS_DATA_WIDTH),
.AXIS_KEEP_ENABLE (AXIS_KEEP_ENABLE),
.AXIS_KEEP_WIDTH (AXIS_KEEP_WIDTH),
.AXIS_LAST_ENABLE (AXIS_LAST_ENABLE),
.AXIS_ID_ENABLE (AXIS_ID_ENABLE),
.AXIS_ID_WIDTH (AXIS_ID_WIDTH),
.AXIS_DEST_ENABLE (AXIS_DEST_ENABLE),
.AXIS_DEST_WIDTH (AXIS_DEST_WIDTH),
.AXIS_USER_ENABLE (AXIS_USER_ENABLE),
.AXIS_USER_WIDTH (AXIS_USER_WIDTH),
.LEN_WIDTH (LEN_WIDTH),
.TAG_WIDTH (TAG_WIDTH),
.ENABLE_SG (ENABLE_SG),
.ENABLE_UNALIGNED (ENABLE_UNALIGNED)
) u_dut (
.clk (clk),
.rst (rst),
.s_axis_read_desc (s_axis_read_desc_axis_if),
.m_axis_read_desc_status (m_axis_read_desc_status_axis_if),
.m_axis_read_data (m_axis_read_data_if),
.s_axis_write_desc (s_axis_write_desc_axis_if),
.m_axis_write_desc_status (m_axis_write_desc_status_axis_if),
.s_axis_write_data (s_axis_write_data_if),
.m_axi (m_axi_if),
.read_enable (read_enable),
.write_enable (write_enable),
.write_abort (write_abort)
);
endmodule : tb_axi_dma_axis_compat
`default_nettype wire

View File

@ -0,0 +1,362 @@
import itertools
import logging
import os
import cocotb
try:
import pytest
except ImportError: # pytest is only needed for the optional cocotb-test entrypoint
pytest = None
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge
from cocotb.regression import TestFactory
from cocotbext.axi import AxiBus, AxiRam
from cocotbext.axi import AxiStreamBus, AxiStreamFrame, AxiStreamSource, AxiStreamSink
from cocotbext.axi.stream import define_stream
DescBus, DescTransaction, DescSource, DescSink, DescMonitor = define_stream(
"Desc",
signals=["addr", "len", "tag", "valid", "ready"],
optional_signals=["id", "dest", "user"],
)
DescStatusBus, DescStatusTransaction, DescStatusSource, DescStatusSink, DescStatusMonitor = define_stream(
"DescStatus",
signals=["tag", "error", "valid"],
optional_signals=["len", "id", "dest", "user"],
)
class TB:
def __init__(self, dut):
self.dut = dut
self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG)
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
# Cocotb still uses the original flat Forencich descriptor/status prefixes;
# the SV top packs/unpacks these into ordinary axis_if streams.
self.read_desc_source = DescSource(
DescBus.from_prefix(dut, "s_axis_read_desc"), dut.clk, dut.rst
)
self.read_desc_status_sink = DescStatusSink(
DescStatusBus.from_prefix(
dut, "m_axis_read_desc_status"), dut.clk, dut.rst
)
self.write_desc_source = DescSource(
DescBus.from_prefix(dut, "s_axis_write_desc"), dut.clk, dut.rst
)
self.write_desc_status_sink = DescStatusSink(
DescStatusBus.from_prefix(
dut, "m_axis_write_desc_status"), dut.clk, dut.rst
)
# Data streams are flat from cocotb's point of view, but the SV top
# sends them through axis_flat_to_if/axis_if_to_flat before/after DUT.
self.read_data_sink = AxiStreamSink(
AxiStreamBus.from_prefix(dut, "m_axis_read_data"), dut.clk, dut.rst
)
self.write_data_source = AxiStreamSource(
AxiStreamBus.from_prefix(
dut, "s_axis_write_data"), dut.clk, dut.rst
)
# AXI memory model. The SV top routes this through the axi4_if adapters.
self.axi_ram = AxiRam(AxiBus.from_prefix(
dut, "m_axi"), dut.clk, dut.rst, size=2**16)
dut.read_enable.setimmediatevalue(0)
dut.write_enable.setimmediatevalue(0)
dut.write_abort.setimmediatevalue(0)
def set_idle_generator(self, generator=None):
if generator:
self.write_desc_source.set_pause_generator(generator())
self.write_data_source.set_pause_generator(generator())
self.read_desc_source.set_pause_generator(generator())
self.axi_ram.write_if.b_channel.set_pause_generator(generator())
self.axi_ram.read_if.r_channel.set_pause_generator(generator())
def set_backpressure_generator(self, generator=None):
if generator:
self.read_data_sink.set_pause_generator(generator())
self.axi_ram.write_if.aw_channel.set_pause_generator(generator())
self.axi_ram.write_if.w_channel.set_pause_generator(generator())
self.axi_ram.read_if.ar_channel.set_pause_generator(generator())
async def cycle_reset(self):
self.dut.rst.setimmediatevalue(0)
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
self.dut.rst.value = 1
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
self.dut.rst.value = 0
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
async def run_test_write(dut, data_in=None, idle_inserter=None, backpressure_inserter=None):
"""DMA write path stress test adapted from Forencich's axi_dma test."""
tb = TB(dut)
byte_lanes = tb.axi_ram.write_if.byte_lanes
step_size = 1 if int(
os.getenv("PARAM_ENABLE_UNALIGNED", "0")) else byte_lanes
tag_count = 2 ** len(tb.write_desc_source.bus.tag)
cur_tag = 1
await tb.cycle_reset()
tb.set_idle_generator(idle_inserter)
tb.set_backpressure_generator(backpressure_inserter)
dut.write_enable.value = 1
for length in list(range(1, byte_lanes * 4 + 1)) + [128]:
offsets = list(range(0, byte_lanes * 2, step_size))
offsets += list(range(4096 - byte_lanes * 2, 4096, step_size))
for offset in offsets:
for diff in [-8, -2, -1, 0, 1, 2, 8]:
if length + diff < 1:
continue
tb.log.info("write: length=%d offset=%d diff=%d",
length, offset, diff)
addr = offset + 0x1000
expected_data = bytearray([x % 256 for x in range(length)])
stream_data = bytearray(
[x % 256 for x in range(length + diff)])
tb.axi_ram.write(addr - 128, b"\xaa" *
(len(expected_data) + 256))
await tb.write_desc_source.send(
DescTransaction(addr=addr, len=len(
expected_data), tag=cur_tag)
)
await tb.write_data_source.send(AxiStreamFrame(stream_data, tid=cur_tag))
status = await tb.write_desc_status_sink.recv()
tb.log.info("write status: %s", status)
transferred_len = min(len(expected_data), len(stream_data))
assert int(status.len) == transferred_len
assert int(status.tag) == cur_tag
assert int(status.id) == cur_tag
assert int(status.error) == 0
tb.log.debug(
"%s",
tb.axi_ram.hexdump_str(
(addr & ~0xF) - 16,
(((addr & 0xF) + length - 1) & ~0xF) + 48,
),
)
if len(expected_data) <= len(stream_data):
assert tb.axi_ram.read(addr - 8, len(expected_data) + 16) == (
b"\xaa" * 8 + expected_data + b"\xaa" * 8
)
else:
assert tb.axi_ram.read(addr - 8, len(stream_data) + 16) == (
b"\xaa" * 8 + stream_data + b"\xaa" * 8
)
cur_tag = (cur_tag + 1) % tag_count
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
async def run_test_read(dut, data_in=None, idle_inserter=None, backpressure_inserter=None):
"""DMA read path stress test adapted from Forencich's axi_dma test."""
tb = TB(dut)
byte_lanes = tb.axi_ram.read_if.byte_lanes
step_size = 1 if int(
os.getenv("PARAM_ENABLE_UNALIGNED", "0")) else byte_lanes
tag_count = 2 ** len(tb.read_desc_source.bus.tag)
cur_tag = 1
await tb.cycle_reset()
tb.set_idle_generator(idle_inserter)
tb.set_backpressure_generator(backpressure_inserter)
dut.read_enable.value = 1
for length in list(range(1, byte_lanes * 4 + 1)) + [128]:
offsets = list(range(0, byte_lanes * 2, step_size))
offsets += list(range(4096 - byte_lanes * 2, 4096, step_size))
for offset in offsets:
tb.log.info("read: length=%d offset=%d", length, offset)
addr = offset + 0x1000
test_data = bytearray([x % 256 for x in range(length)])
tb.axi_ram.write(addr - 128, b"\xaa" * (len(test_data) + 256))
tb.axi_ram.write(addr, test_data)
tb.log.debug(
"%s",
tb.axi_ram.hexdump_str(
(addr & ~0xF) - 16,
(((addr & 0xF) + length - 1) & ~0xF) + 48,
),
)
await tb.read_desc_source.send(
DescTransaction(addr=addr, len=len(
test_data), tag=cur_tag, id=cur_tag)
)
status = await tb.read_desc_status_sink.recv()
read_data = await tb.read_data_sink.recv()
tb.log.info("read status: %s", status)
tb.log.info("read data: %s", read_data)
assert int(status.tag) == cur_tag
assert int(status.error) == 0
assert read_data.tdata == test_data
assert int(read_data.tid) == cur_tag
cur_tag = (cur_tag + 1) % tag_count
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
def cycle_pause():
return itertools.cycle([1, 1, 1, 0])
# When imported by cocotb inside a simulator, generate the actual cocotb tests.
if cocotb.SIM_NAME:
for test in [run_test_write, run_test_read]:
factory = TestFactory(test)
factory.add_option("idle_inserter", [None, cycle_pause])
factory.add_option("backpressure_inserter", [None, cycle_pause])
factory.generate_tests()
# -----------------------------------------------------------------------------
# Optional pytest entrypoint via cocotb-test.
# Run from this directory with: pytest -q test_axi_dma_axis_compat.py
# -----------------------------------------------------------------------------
def _sanitize_node_name(name):
return name.replace("[", "-").replace("]", "").replace("/", "_")
if pytest is not None:
@pytest.mark.parametrize("axi_data_width", [8, 16, 32])
@pytest.mark.parametrize("unaligned", [0, 1])
def test_axi_dma_axis_compat_pytest(request, axi_data_width, unaligned):
import cocotb_test.simulator
tests_dir = os.path.abspath(os.path.dirname(__file__))
project_root = os.path.abspath(os.path.join(tests_dir, "..", ".."))
axi_if_rtl_dir = os.environ.get(
"AXI_IF_RTL_DIR", os.path.join(project_root, "rtl", "axi")
)
dma_if_rtl_dir = os.environ.get(
"DMA_IF_RTL_DIR", os.path.join(project_root, "rtl", "dma")
)
wrapper_rtl_dir = os.environ.get(
"WRAPPER_RTL_DIR", os.path.join(project_root, "rtl", "wrappers")
)
forencich_rtl_dir = os.environ.get(
"FORENCICH_AXI_RTL_DIR",
os.path.join(project_root, "external", "verilog-axi", "rtl"),
)
dut = "tb_axi_dma_axis_compat"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
parameters = {
"AXI_DATA_WIDTH": axi_data_width,
"AXI_ADDR_WIDTH": 16,
"AXI_ID_WIDTH": 8,
"AXI_USER_WIDTH": 1,
"AXI_MAX_BURST_LEN": 16,
"AXIS_ID_ENABLE": 1,
"AXIS_ID_WIDTH": 8,
"AXIS_DEST_ENABLE": 0,
"AXIS_DEST_WIDTH": 8,
"AXIS_USER_ENABLE": 1,
"AXIS_USER_WIDTH": 1,
"LEN_WIDTH": 20,
"TAG_WIDTH": 8,
"ENABLE_SG": 0,
"ENABLE_UNALIGNED": unaligned,
}
parameters["AXI_STRB_WIDTH"] = parameters["AXI_DATA_WIDTH"] // 8
parameters["AXIS_DATA_WIDTH"] = parameters["AXI_DATA_WIDTH"]
parameters["AXIS_KEEP_ENABLE"] = int(parameters["AXIS_DATA_WIDTH"] > 8)
parameters["AXIS_KEEP_WIDTH"] = parameters["AXIS_DATA_WIDTH"] // 8
parameters["AXIS_LAST_ENABLE"] = 1
verilog_sources = [
os.path.join(axi_if_rtl_dir, "axi_pkg.sv"),
os.path.join(axi_if_rtl_dir, "axi_if.sv"),
os.path.join(axi_if_rtl_dir, "axis_if.sv"),
os.path.join(axi_if_rtl_dir, "axi4_flat_to_if.sv"),
os.path.join(axi_if_rtl_dir, "axi4_if_to_flat.sv"),
os.path.join(axi_if_rtl_dir, "axis_flat_to_if.sv"),
os.path.join(axi_if_rtl_dir, "axis_if_to_flat.sv"),
os.path.join(dma_if_rtl_dir, "axi_dma_desc_if.sv"),
os.path.join(dma_if_rtl_dir, "axi_dma_read_desc_flat_to_if.sv"),
os.path.join(dma_if_rtl_dir, "axi_dma_read_desc_if_to_flat.sv"),
os.path.join(dma_if_rtl_dir,
"axi_dma_read_desc_status_flat_to_if.sv"),
os.path.join(dma_if_rtl_dir,
"axi_dma_read_desc_status_if_to_flat.sv"),
os.path.join(dma_if_rtl_dir, "axi_dma_write_desc_flat_to_if.sv"),
os.path.join(dma_if_rtl_dir, "axi_dma_write_desc_if_to_flat.sv"),
os.path.join(dma_if_rtl_dir,
"axi_dma_write_desc_status_flat_to_if.sv"),
os.path.join(dma_if_rtl_dir,
"axi_dma_write_desc_status_if_to_flat.sv"),
os.path.join(forencich_rtl_dir, "axi_dma.v"),
os.path.join(forencich_rtl_dir, "axi_dma_rd.v"),
os.path.join(forencich_rtl_dir, "axi_dma_wr.v"),
os.path.join(wrapper_rtl_dir, "axi_dma_if_wrapper.sv"),
os.path.join(wrapper_rtl_dir, "axi_dma_axis_compat.sv"),
os.path.join(tests_dir, "tb_axi_dma_axis_compat.sv"),
]
extra_env = {f"PARAM_{k}": str(v) for k, v in parameters.items()}
sim_build = os.path.join(
tests_dir, "sim_build", _sanitize_node_name(request.node.name))
extra_args = []
if os.getenv("SIM", "verilator") == "verilator":
extra_args += ["--trace-structs"]
cocotb_test.simulator.run(
python_search=[tests_dir],
verilog_sources=verilog_sources,
includes=[axi_if_rtl_dir, dma_if_rtl_dir,
wrapper_rtl_dir, forencich_rtl_dir],
toplevel=toplevel,
module=module,
parameters=parameters,
sim_build=sim_build,
extra_env=extra_env,
waves=bool(int(os.getenv("WAVES", "0"))),
extra_args=extra_args,
)