diff --git a/axi/rtl/axi_dma_compat.sv b/axi/rtl/axi_dma_compat.sv new file mode 100644 index 0000000..d3bcf07 --- /dev/null +++ b/axi/rtl/axi_dma_compat.sv @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: MIT +// +// Compatibility wrapper over axi_dma_if_wrapper. +// +// Uses 4 axis_if channels for statuses and descs +// +// payload packing is LSB-first and one descriptor/status occupies one AXIS beat. +// tkeep/tstrb/tlast/id/dest/user on descriptor inputs are ignored. +// status outputs drive tkeep/tstrb to all ones, tlast to 1, and id/dest/user to 0. +// +// !!!!!: the original alexforencich axi_dma status outputs have +// no ready/backpressure signal. This wrapper adds a oneentry holding register +// for each status axis output, but if a downstream is not ready long enough for a +// second status to arrive while the first one is still pending, the core cannot +// be backpressured. In normal use keep status sinks ready, or add an external +// FIFO if long stalls are possible. + +`default_nettype none + +module 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, + + // Payload widths for descriptor/status AXIS streams. + parameter int unsigned READ_DESC_PAYLOAD_WIDTH = AXI_ADDR_WIDTH + LEN_WIDTH + TAG_WIDTH + AXIS_ID_WIDTH + AXIS_DEST_WIDTH + AXIS_USER_WIDTH, + parameter int unsigned READ_DESC_AXIS_DATA_WIDTH = ((READ_DESC_PAYLOAD_WIDTH + 7) / 8) * 8, + parameter int unsigned READ_DESC_STATUS_PAYLOAD_WIDTH = TAG_WIDTH + 4, + parameter int unsigned READ_DESC_STATUS_AXIS_DATA_WIDTH = ((READ_DESC_STATUS_PAYLOAD_WIDTH + 7) / 8) * 8, + parameter int unsigned WRITE_DESC_PAYLOAD_WIDTH = AXI_ADDR_WIDTH + LEN_WIDTH + TAG_WIDTH, + parameter int unsigned WRITE_DESC_AXIS_DATA_WIDTH = ((WRITE_DESC_PAYLOAD_WIDTH + 7) / 8) * 8, + parameter int unsigned WRITE_DESC_STATUS_PAYLOAD_WIDTH = LEN_WIDTH + TAG_WIDTH + AXIS_ID_WIDTH + AXIS_DEST_WIDTH + AXIS_USER_WIDTH + 4, + parameter int unsigned WRITE_DESC_STATUS_AXIS_DATA_WIDTH = ((WRITE_DESC_STATUS_PAYLOAD_WIDTH + 7) / 8) * 8 +)( + input logic clk, + input logic rst, + + /* + * AXI read descriptor input, encoded as one AXIS beat. + * tdata layout, LSB first: + * addr | len | tag | id | dest | user + */ + axis_if.slave s_axis_read_desc, + + /* + * AXI read descriptor status output, encoded as one AXIS beat. + * tdata layout, LSB first: + * tag | error + */ + axis_if.master m_axis_read_desc_status, + + /* + * AXI stream read data output. + */ + axis_if.master m_axis_read_data, + + /* + * AXI write descriptor input, encoded as one AXIS beat. + * tdata layout, LSB first: + * addr | len | tag + */ + axis_if.slave s_axis_write_desc, + + /* + * AXI write descriptor status output, encoded as one AXIS beat. + * tdata layout, LSB first: + * len | tag | id | dest | user | error + */ + axis_if.master m_axis_write_desc_status, + + /* + * AXI stream write data input. + */ + axis_if.slave s_axis_write_data, + + /* + * AXI memory master interface. + */ + axi4_if.master m_axi, + + /* + * Configuration. + */ + input logic read_enable, + input logic write_enable, + input logic write_abort +); + + 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; + + localparam int unsigned READ_DESC_AXIS_KEEP_WIDTH = READ_DESC_AXIS_DATA_WIDTH / 8; + localparam int unsigned READ_DESC_STATUS_AXIS_KEEP_WIDTH = READ_DESC_STATUS_AXIS_DATA_WIDTH / 8; + localparam int unsigned WRITE_DESC_AXIS_KEEP_WIDTH = WRITE_DESC_AXIS_DATA_WIDTH / 8; + localparam int unsigned WRITE_DESC_STATUS_AXIS_KEEP_WIDTH = WRITE_DESC_STATUS_AXIS_DATA_WIDTH / 8; + + logic rstn; + assign rstn = ~rst; + + // -------------------------------------------------------------------------- + // Internal DMA-specific descriptor/status interfaces expected by + // axi_dma_if_wrapper. + // -------------------------------------------------------------------------- + + axi_dma_read_desc_if #( + .ADDR_W (AXI_ADDR_WIDTH), + .LEN_W (LEN_WIDTH), + .TAG_W (TAG_WIDTH), + .ID_W (AXIS_ID_WIDTH), + .DEST_W (AXIS_DEST_WIDTH), + .USER_W (AXIS_USER_WIDTH) + ) read_desc_if ( + .aclk (clk), + .aresetn (rstn) + ); + + axi_dma_read_desc_status_if #( + .TAG_W (TAG_WIDTH) + ) read_desc_status_if ( + .aclk (clk), + .aresetn (rstn) + ); + + axi_dma_write_desc_if #( + .ADDR_W (AXI_ADDR_WIDTH), + .LEN_W (LEN_WIDTH), + .TAG_W (TAG_WIDTH) + ) write_desc_if ( + .aclk (clk), + .aresetn (rstn) + ); + + axi_dma_write_desc_status_if #( + .LEN_W (LEN_WIDTH), + .TAG_W (TAG_WIDTH), + .ID_W (AXIS_ID_WIDTH), + .DEST_W (AXIS_DEST_WIDTH), + .USER_W (AXIS_USER_WIDTH) + ) write_desc_status_if ( + .aclk (clk), + .aresetn (rstn) + ); + + // -------------------------------------------------------------------------- + // AXIS read descriptor input -> DMA-specific read descriptor interface. + // -------------------------------------------------------------------------- + + assign read_desc_if.req.addr = s_axis_read_desc.req.t.data[RD_ADDR_LSB +: AXI_ADDR_WIDTH]; + assign read_desc_if.req.len = s_axis_read_desc.req.t.data[RD_LEN_LSB +: LEN_WIDTH]; + assign read_desc_if.req.tag = s_axis_read_desc.req.t.data[RD_TAG_LSB +: TAG_WIDTH]; + assign read_desc_if.req.id = s_axis_read_desc.req.t.data[RD_ID_LSB +: AXIS_ID_WIDTH]; + assign read_desc_if.req.dest = s_axis_read_desc.req.t.data[RD_DEST_LSB +: AXIS_DEST_WIDTH]; + assign read_desc_if.req.user = s_axis_read_desc.req.t.data[RD_USER_LSB +: AXIS_USER_WIDTH]; + assign read_desc_if.req.valid = s_axis_read_desc.req.t.valid; + assign s_axis_read_desc.resp.ready = read_desc_if.resp.ready; + + // -------------------------------------------------------------------------- + // AXIS write descriptor input -> DMA-specific write descriptor interface. + // -------------------------------------------------------------------------- + + assign write_desc_if.req.addr = s_axis_write_desc.req.t.data[WD_ADDR_LSB +: AXI_ADDR_WIDTH]; + assign write_desc_if.req.len = s_axis_write_desc.req.t.data[WD_LEN_LSB +: LEN_WIDTH]; + assign write_desc_if.req.tag = s_axis_write_desc.req.t.data[WD_TAG_LSB +: TAG_WIDTH]; + assign write_desc_if.req.valid = s_axis_write_desc.req.t.valid; + assign s_axis_write_desc.resp.ready = write_desc_if.resp.ready; + + // -------------------------------------------------------------------------- + // DMA-specific read status -> AXIS read status output. + // -------------------------------------------------------------------------- + + logic [READ_DESC_STATUS_AXIS_DATA_WIDTH-1:0] read_status_payload; + logic [READ_DESC_STATUS_AXIS_DATA_WIDTH-1:0] read_status_data_q; + logic read_status_valid_q; + + always_comb begin + read_status_payload = '0; + read_status_payload[RDS_TAG_LSB +: TAG_WIDTH] = read_desc_status_if.req.tag; + read_status_payload[RDS_ERROR_LSB +: 4] = read_desc_status_if.req.error; + end + + always_ff @(posedge clk) begin + if (rst) begin + read_status_valid_q <= 1'b0; + read_status_data_q <= '0; + end else begin + if (!read_status_valid_q || m_axis_read_desc_status.resp.ready) begin + read_status_valid_q <= read_desc_status_if.req.valid; + if (read_desc_status_if.req.valid) begin + read_status_data_q <= read_status_payload; + end else begin + read_status_data_q <= '0; + end + end + end + end + + always_comb begin + m_axis_read_desc_status.req.t.data = '0; + m_axis_read_desc_status.req.t.keep = '0; + m_axis_read_desc_status.req.t.strb = '0; + m_axis_read_desc_status.req.t.last = read_status_valid_q; + m_axis_read_desc_status.req.t.id = '0; + m_axis_read_desc_status.req.t.dest = '0; + m_axis_read_desc_status.req.t.user = '0; + m_axis_read_desc_status.req.t.valid = read_status_valid_q; + + m_axis_read_desc_status.req.t.data[READ_DESC_STATUS_AXIS_DATA_WIDTH-1:0] = read_status_data_q; + m_axis_read_desc_status.req.t.keep[READ_DESC_STATUS_AXIS_KEEP_WIDTH-1:0] = {READ_DESC_STATUS_AXIS_KEEP_WIDTH{read_status_valid_q}}; + m_axis_read_desc_status.req.t.strb[READ_DESC_STATUS_AXIS_KEEP_WIDTH-1:0] = {READ_DESC_STATUS_AXIS_KEEP_WIDTH{read_status_valid_q}}; + end + + // -------------------------------------------------------------------------- + // DMA-specific write status -> AXIS write status output. + // -------------------------------------------------------------------------- + + logic [WRITE_DESC_STATUS_AXIS_DATA_WIDTH-1:0] write_status_payload; + logic [WRITE_DESC_STATUS_AXIS_DATA_WIDTH-1:0] write_status_data_q; + logic write_status_valid_q; + + always_comb begin + write_status_payload = '0; + write_status_payload[WDS_LEN_LSB +: LEN_WIDTH] = write_desc_status_if.req.len; + write_status_payload[WDS_TAG_LSB +: TAG_WIDTH] = write_desc_status_if.req.tag; + write_status_payload[WDS_ID_LSB +: AXIS_ID_WIDTH] = write_desc_status_if.req.id; + write_status_payload[WDS_DEST_LSB +: AXIS_DEST_WIDTH] = write_desc_status_if.req.dest; + write_status_payload[WDS_USER_LSB +: AXIS_USER_WIDTH] = write_desc_status_if.req.user; + write_status_payload[WDS_ERROR_LSB +: 4] = write_desc_status_if.req.error; + end + + always_ff @(posedge clk) begin + if (rst) begin + write_status_valid_q <= 1'b0; + write_status_data_q <= '0; + end else begin + if (!write_status_valid_q || m_axis_write_desc_status.resp.ready) begin + write_status_valid_q <= write_desc_status_if.req.valid; + if (write_desc_status_if.req.valid) begin + write_status_data_q <= write_status_payload; + end else begin + write_status_data_q <= '0; + end + end + end + end + + always_comb begin + m_axis_write_desc_status.req.t.data = '0; + m_axis_write_desc_status.req.t.keep = '0; + m_axis_write_desc_status.req.t.strb = '0; + m_axis_write_desc_status.req.t.last = write_status_valid_q; + m_axis_write_desc_status.req.t.id = '0; + m_axis_write_desc_status.req.t.dest = '0; + m_axis_write_desc_status.req.t.user = '0; + m_axis_write_desc_status.req.t.valid = write_status_valid_q; + + m_axis_write_desc_status.req.t.data[WRITE_DESC_STATUS_AXIS_DATA_WIDTH-1:0] = write_status_data_q; + m_axis_write_desc_status.req.t.keep[WRITE_DESC_STATUS_AXIS_KEEP_WIDTH-1:0] = {WRITE_DESC_STATUS_AXIS_KEEP_WIDTH{write_status_valid_q}}; + m_axis_write_desc_status.req.t.strb[WRITE_DESC_STATUS_AXIS_KEEP_WIDTH-1:0] = {WRITE_DESC_STATUS_AXIS_KEEP_WIDTH{write_status_valid_q}}; + end + + // -------------------------------------------------------------------------- + // Lower-level wrapper: DMA-specific descriptor/status interfaces inside, + // ordinary AXIS data and AXI memory interfaces unchanged. + // -------------------------------------------------------------------------- + + axi_dma_if_wrapper #( + .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_axi_dma_if_wrapper ( + .clk (clk), + .rst (rst), + + .s_axis_read_desc (read_desc_if), + .m_axis_read_desc_status (read_desc_status_if), + .m_axis_read_data (m_axis_read_data), + + .s_axis_write_desc (write_desc_if), + .m_axis_write_desc_status (write_desc_status_if), + .s_axis_write_data (s_axis_write_data), + + .m_axi (m_axi), + + .read_enable (read_enable), + .write_enable (write_enable), + .write_abort (write_abort) + ); + +endmodule : axi_dma_axis_compat + +`default_nettype wire diff --git a/axi/tb/axi_dma_axis_compat/Makefile b/axi/tb/axi_dma_axis_compat/Makefile new file mode 100644 index 0000000..b1b303d --- /dev/null +++ b/axi/tb/axi_dma_axis_compat/Makefile @@ -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 diff --git a/axi/tb/axi_dma_axis_compat/tb_axi_dma_axis_compat.sv b/axi/tb/axi_dma_axis_compat/tb_axi_dma_axis_compat.sv new file mode 100644 index 0000000..92dadd1 --- /dev/null +++ b/axi/tb/axi_dma_axis_compat/tb_axi_dma_axis_compat.sv @@ -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 diff --git a/axi/tb/axi_dma_axis_compat/test_axi_dma_axis_compat.py b/axi/tb/axi_dma_axis_compat/test_axi_dma_axis_compat.py new file mode 100644 index 0000000..6d65463 --- /dev/null +++ b/axi/tb/axi_dma_axis_compat/test_axi_dma_axis_compat.py @@ -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, + )