Compare commits

...

2 Commits

Author SHA1 Message Date
4dac1789c8 test: interface comaptibiliy prototype 2026-07-10 13:49:44 +03:00
f3450cb22a added axi dma wrapper 2026-06-25 13:42:31 +03:00
4 changed files with 914 additions and 225 deletions

453
axi/rtl/axi_dma_wrapper.sv Normal file
View File

@ -0,0 +1,453 @@
// SPDX-License-Identifier: MIT
//
// SystemVerilog interface wrapper around alexforencich/verilog-axi axi_dma.v.
//
// AXI memory, AXI-Stream data, DMA descriptor, and DMA status channels are all
// exposed through compact interfaces. The original Forencich core remains
// untouched and is connected through local flat wires.
`default_nettype none
// DMA Specific wrappers & converters
module axi_dma_wrapper #(
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
)(
input logic clk,
input logic rst,
axis_if.slave s_axis_read_desc,
axis_if.master m_axis_read_desc_status,
axis_if.master m_axis_read_data,
axis_if.slave s_axis_write_desc,
axis_if.master m_axis_write_desc_status,
axis_if.slave s_axis_write_data,
axi4_if.master m_axi,
/*
* Configuration.
*/
input logic read_enable,
input logic write_enable,
input logic write_abort
);
// --------------------------------------------------------------------------
// Flat wires connected to original alexforencich axi_dma.v
// --------------------------------------------------------------------------
logic [AXI_ADDR_WIDTH-1:0] dma_s_axis_read_desc_addr;
logic [LEN_WIDTH-1:0] dma_s_axis_read_desc_len;
logic [TAG_WIDTH-1:0] dma_s_axis_read_desc_tag;
logic [AXIS_ID_WIDTH-1:0] dma_s_axis_read_desc_id;
logic [AXIS_DEST_WIDTH-1:0] dma_s_axis_read_desc_dest;
logic [AXIS_USER_WIDTH-1:0] dma_s_axis_read_desc_user;
logic dma_s_axis_read_desc_valid;
logic dma_s_axis_read_desc_ready;
logic [TAG_WIDTH-1:0] dma_m_axis_read_desc_status_tag;
logic [3:0] dma_m_axis_read_desc_status_error;
logic dma_m_axis_read_desc_status_valid;
logic [AXI_ADDR_WIDTH-1:0] dma_s_axis_write_desc_addr;
logic [LEN_WIDTH-1:0] dma_s_axis_write_desc_len;
logic [TAG_WIDTH-1:0] dma_s_axis_write_desc_tag;
logic dma_s_axis_write_desc_valid;
logic dma_s_axis_write_desc_ready;
logic [LEN_WIDTH-1:0] dma_m_axis_write_desc_status_len;
logic [TAG_WIDTH-1:0] dma_m_axis_write_desc_status_tag;
logic [AXIS_ID_WIDTH-1:0] dma_m_axis_write_desc_status_id;
logic [AXIS_DEST_WIDTH-1:0] dma_m_axis_write_desc_status_dest;
logic [AXIS_USER_WIDTH-1:0] dma_m_axis_write_desc_status_user;
logic [3:0] dma_m_axis_write_desc_status_error;
logic dma_m_axis_write_desc_status_valid;
logic [AXIS_DATA_WIDTH-1:0] dma_m_axis_read_data_tdata;
logic [AXIS_KEEP_WIDTH-1:0] dma_m_axis_read_data_tkeep;
logic dma_m_axis_read_data_tvalid;
logic dma_m_axis_read_data_tready;
logic dma_m_axis_read_data_tlast;
logic [AXIS_ID_WIDTH-1:0] dma_m_axis_read_data_tid;
logic [AXIS_DEST_WIDTH-1:0] dma_m_axis_read_data_tdest;
logic [AXIS_USER_WIDTH-1:0] dma_m_axis_read_data_tuser;
logic [AXIS_DATA_WIDTH-1:0] dma_s_axis_write_data_tdata;
logic [AXIS_KEEP_WIDTH-1:0] dma_s_axis_write_data_tkeep;
logic [AXIS_KEEP_WIDTH-1:0] unused_s_axis_write_data_tstrb;
logic dma_s_axis_write_data_tvalid;
logic dma_s_axis_write_data_tready;
logic dma_s_axis_write_data_tlast;
logic [AXIS_ID_WIDTH-1:0] dma_s_axis_write_data_tid;
logic [AXIS_DEST_WIDTH-1:0] dma_s_axis_write_data_tdest;
logic [AXIS_USER_WIDTH-1:0] dma_s_axis_write_data_tuser;
logic [AXI_ID_WIDTH-1:0] dma_m_axi_awid;
logic [AXI_ADDR_WIDTH-1:0] dma_m_axi_awaddr;
logic [7:0] dma_m_axi_awlen;
logic [2:0] dma_m_axi_awsize;
logic [1:0] dma_m_axi_awburst;
logic dma_m_axi_awlock;
logic [3:0] dma_m_axi_awcache;
logic [2:0] dma_m_axi_awprot;
logic dma_m_axi_awvalid;
logic dma_m_axi_awready;
logic [AXI_DATA_WIDTH-1:0] dma_m_axi_wdata;
logic [AXI_STRB_WIDTH-1:0] dma_m_axi_wstrb;
logic dma_m_axi_wlast;
logic dma_m_axi_wvalid;
logic dma_m_axi_wready;
logic [AXI_ID_WIDTH-1:0] dma_m_axi_bid;
logic [1:0] dma_m_axi_bresp;
logic dma_m_axi_bvalid;
logic dma_m_axi_bready;
logic [AXI_ID_WIDTH-1:0] dma_m_axi_arid;
logic [AXI_ADDR_WIDTH-1:0] dma_m_axi_araddr;
logic [7:0] dma_m_axi_arlen;
logic [2:0] dma_m_axi_arsize;
logic [1:0] dma_m_axi_arburst;
logic dma_m_axi_arlock;
logic [3:0] dma_m_axi_arcache;
logic [2:0] dma_m_axi_arprot;
logic dma_m_axi_arvalid;
logic dma_m_axi_arready;
logic [AXI_ID_WIDTH-1:0] dma_m_axi_rid;
logic [AXI_DATA_WIDTH-1:0] dma_m_axi_rdata;
logic [1:0] dma_m_axi_rresp;
logic dma_m_axi_rlast;
logic dma_m_axi_rvalid;
logic dma_m_axi_rready;
logic [AXI_USER_WIDTH-1:0] unused_m_axi_buser;
logic [AXI_USER_WIDTH-1:0] unused_m_axi_ruser;
// Original DMA: flat ports only.
axi_dma #(
.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_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)
) i_axi_dma (
.clk (clk),
.rst (rst),
.s_axis_read_desc_addr (dma_s_axis_read_desc_addr),
.s_axis_read_desc_len (dma_s_axis_read_desc_len),
.s_axis_read_desc_tag (dma_s_axis_read_desc_tag),
.s_axis_read_desc_id (dma_s_axis_read_desc_id),
.s_axis_read_desc_dest (dma_s_axis_read_desc_dest),
.s_axis_read_desc_user (dma_s_axis_read_desc_user),
.s_axis_read_desc_valid (dma_s_axis_read_desc_valid),
.s_axis_read_desc_ready (dma_s_axis_read_desc_ready),
.m_axis_read_desc_status_tag (dma_m_axis_read_desc_status_tag),
.m_axis_read_desc_status_error (dma_m_axis_read_desc_status_error),
.m_axis_read_desc_status_valid (dma_m_axis_read_desc_status_valid),
.m_axis_read_data_tdata (dma_m_axis_read_data_tdata),
.m_axis_read_data_tkeep (dma_m_axis_read_data_tkeep),
.m_axis_read_data_tvalid (dma_m_axis_read_data_tvalid),
.m_axis_read_data_tready (dma_m_axis_read_data_tready),
.m_axis_read_data_tlast (dma_m_axis_read_data_tlast),
.m_axis_read_data_tid (dma_m_axis_read_data_tid),
.m_axis_read_data_tdest (dma_m_axis_read_data_tdest),
.m_axis_read_data_tuser (dma_m_axis_read_data_tuser),
.s_axis_write_desc_addr (dma_s_axis_write_desc_addr),
.s_axis_write_desc_len (dma_s_axis_write_desc_len),
.s_axis_write_desc_tag (dma_s_axis_write_desc_tag),
.s_axis_write_desc_valid (dma_s_axis_write_desc_valid),
.s_axis_write_desc_ready (dma_s_axis_write_desc_ready),
.m_axis_write_desc_status_len (dma_m_axis_write_desc_status_len),
.m_axis_write_desc_status_tag (dma_m_axis_write_desc_status_tag),
.m_axis_write_desc_status_id (dma_m_axis_write_desc_status_id),
.m_axis_write_desc_status_dest (dma_m_axis_write_desc_status_dest),
.m_axis_write_desc_status_user (dma_m_axis_write_desc_status_user),
.m_axis_write_desc_status_error (dma_m_axis_write_desc_status_error),
.m_axis_write_desc_status_valid (dma_m_axis_write_desc_status_valid),
.s_axis_write_data_tdata (dma_s_axis_write_data_tdata),
.s_axis_write_data_tkeep (dma_s_axis_write_data_tkeep),
.s_axis_write_data_tvalid (dma_s_axis_write_data_tvalid),
.s_axis_write_data_tready (dma_s_axis_write_data_tready),
.s_axis_write_data_tlast (dma_s_axis_write_data_tlast),
.s_axis_write_data_tid (dma_s_axis_write_data_tid),
.s_axis_write_data_tdest (dma_s_axis_write_data_tdest),
.s_axis_write_data_tuser (dma_s_axis_write_data_tuser),
.m_axi_awid (dma_m_axi_awid),
.m_axi_awaddr (dma_m_axi_awaddr),
.m_axi_awlen (dma_m_axi_awlen),
.m_axi_awsize (dma_m_axi_awsize),
.m_axi_awburst (dma_m_axi_awburst),
.m_axi_awlock (dma_m_axi_awlock),
.m_axi_awcache (dma_m_axi_awcache),
.m_axi_awprot (dma_m_axi_awprot),
.m_axi_awvalid (dma_m_axi_awvalid),
.m_axi_awready (dma_m_axi_awready),
.m_axi_wdata (dma_m_axi_wdata),
.m_axi_wstrb (dma_m_axi_wstrb),
.m_axi_wlast (dma_m_axi_wlast),
.m_axi_wvalid (dma_m_axi_wvalid),
.m_axi_wready (dma_m_axi_wready),
.m_axi_bid (dma_m_axi_bid),
.m_axi_bresp (dma_m_axi_bresp),
.m_axi_bvalid (dma_m_axi_bvalid),
.m_axi_bready (dma_m_axi_bready),
.m_axi_arid (dma_m_axi_arid),
.m_axi_araddr (dma_m_axi_araddr),
.m_axi_arlen (dma_m_axi_arlen),
.m_axi_arsize (dma_m_axi_arsize),
.m_axi_arburst (dma_m_axi_arburst),
.m_axi_arlock (dma_m_axi_arlock),
.m_axi_arcache (dma_m_axi_arcache),
.m_axi_arprot (dma_m_axi_arprot),
.m_axi_arvalid (dma_m_axi_arvalid),
.m_axi_arready (dma_m_axi_arready),
.m_axi_rid (dma_m_axi_rid),
.m_axi_rdata (dma_m_axi_rdata),
.m_axi_rresp (dma_m_axi_rresp),
.m_axi_rlast (dma_m_axi_rlast),
.m_axi_rvalid (dma_m_axi_rvalid),
.m_axi_rready (dma_m_axi_rready),
.read_enable (read_enable),
.write_enable (write_enable),
.write_abort (write_abort)
);
// local read descriptor interface -> DMA flat input
axis_if_to_flat #(
.DATA_W (AXI_ADDR_WIDTH + LEN_WIDTH + TAG_WIDTH),
.KEEP_W ('0),
.ID_W (AXIS_ID_WIDTH),
.DEST_W (AXIS_DEST_WIDTH),
.USER_W (AXIS_USER_WIDTH)
) i_read_desc_cmd_i2f (
.s_axis (s_axis_read_desc),
.m_axis_tdata ({dma_s_axis_read_desc_addr, dma_s_axis_read_desc_len, dma_s_axis_read_desc_tag}),
.m_axis_tkeep (),
.m_axis_tstrb (),
.m_axis_tlast (),
.m_axis_tid (dma_s_axis_read_desc_id),
.m_axis_tdest (dma_s_axis_read_desc_tdest),
.m_axis_tuser (dma_s_axis_read_desc_user),
.m_axis_tvalid (dma_s_axis_read_desc_valid),
.m_axis_tready (dma_s_axis_read_desc_ready)
);
// DMA read descriptor status flat output -> local status interface
axis_flat_to_if #(
.DATA_W (TAG_WIDTH + 4),
.KEEP_W ('0),
.ID_W ('0),
.DEST_W ('0),
.USER_W ('0)
) i_read_desc_status_f2i(
.s_axis_tdata ({dma_m_axis_read_desc_status_tag, dma_m_axis_read_desc_status_error}),
.s_axis_tkeep (),
.s_axis_tstrb (),
.s_axis_tlast (),
.s_axis_tid (),
.s_axis_tdest (),
.s_axis_tuser (),
.s_axis_tvalid (dma_m_axis_read_desc_status_valid),
.s_axis_tready (),
.m_axis (m_axis_read_desc_status)
);
// local write descriptor interface -> DMA flat input
axis_if_to_flat #(
.DATA_W (AXI_ADDR_WIDTH + LEN_WIDTH + TAG_WIDTH),
.KEEP_W ('0),
.ID_W (AXIS_ID_WIDTH),
.DEST_W (AXIS_DEST_WIDTH),
.USER_W (AXIS_USER_WIDTH)
) i_write_desc_cmd_i2f (
.s_axis (s_axis_write_desc),
.m_axis_tdata ({dma_s_axis_write_desc_addr, dma_s_axis_write_desc_len, dma_s_axis_write_desc_tag}),
.m_axis_tkeep (),
.m_axis_tstrb (),
.m_axis_tlast (),
.m_axis_tid (dma_s_axis_write_desc_id),
.m_axis_tdest (dma_s_axis_write_desc_tdest),
.m_axis_tuser (dma_s_axis_write_desc_user),
.m_axis_tvalid (dma_s_axis_write_desc_valid),
.m_axis_tready (dma_s_axis_write_desc_ready)
);
// DMA write descriptor status flat output -> local status interface
axis_flat_to_if #(
.DATA_W (TAG_WIDTH + LEN_WIDTH + 4),
.KEEP_W ('0),
.ID_W (AXIS_ID_WIDTH),
.DEST_W (AXIS_DEST_WIDTH),
.USER_W (AXIS_USER_WIDTH)
) i_write_desc_status_f2i(
.s_axis_tdata ({dma_m_axis_write_desc_status_len, dma_m_axis_write_desc_status_tag, dma_m_axis_write_desc_status_error}),
.s_axis_tkeep (),
.s_axis_tstrb (),
.s_axis_tlast (),
.s_axis_tid (dma_m_axis_write_desc_status_id),
.s_axis_tdest (dma_m_axis_write_desc_status_dest),
.s_axis_tuser (dma_m_axis_write_desc_status_user),
.s_axis_tvalid (dma_m_axis_write_desc_status_valid),
.s_axis_tready (),
.m_axis (m_axis_write_desc_status)
);
// DMA read data flat output -> local axis_if.master
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_m_axis_read_data_flat_to_if (
.s_axis_tdata (dma_m_axis_read_data_tdata),
.s_axis_tkeep (dma_m_axis_read_data_tkeep),
.s_axis_tstrb (dma_m_axis_read_data_tkeep), // axi_dma has no tstrb; mirror tkeep
.s_axis_tlast (dma_m_axis_read_data_tlast),
.s_axis_tid (dma_m_axis_read_data_tid),
.s_axis_tdest (dma_m_axis_read_data_tdest),
.s_axis_tuser (dma_m_axis_read_data_tuser),
.s_axis_tvalid (dma_m_axis_read_data_tvalid),
.s_axis_tready (dma_m_axis_read_data_tready),
.m_axis (m_axis_read_data)
);
// local axis_if.slave -> DMA write data flat input
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_s_axis_write_data_if_to_flat (
.s_axis (s_axis_write_data),
.m_axis_tdata (dma_s_axis_write_data_tdata),
.m_axis_tkeep (dma_s_axis_write_data_tkeep),
.m_axis_tstrb (unused_s_axis_write_data_tstrb),
.m_axis_tlast (dma_s_axis_write_data_tlast),
.m_axis_tid (dma_s_axis_write_data_tid),
.m_axis_tdest (dma_s_axis_write_data_tdest),
.m_axis_tuser (dma_s_axis_write_data_tuser),
.m_axis_tvalid(dma_s_axis_write_data_tvalid),
.m_axis_tready(dma_s_axis_write_data_tready)
);
// DMA AXI master flat output -> local axi4_if.master
axi4_flat_to_if #(
.ADDR_W (AXI_ADDR_WIDTH),
.DATA_W (AXI_DATA_WIDTH),
.ID_W (AXI_ID_WIDTH),
.USER_W (AXI_USER_WIDTH)
) u_m_axi_flat_to_if (
.s_axi_awid (dma_m_axi_awid),
.s_axi_awaddr (dma_m_axi_awaddr),
.s_axi_awlen (dma_m_axi_awlen),
.s_axi_awsize (dma_m_axi_awsize),
.s_axi_awburst (dma_m_axi_awburst),
.s_axi_awlock (dma_m_axi_awlock),
.s_axi_awcache (dma_m_axi_awcache),
.s_axi_awprot (dma_m_axi_awprot),
.s_axi_awqos (4'd0),
.s_axi_awregion (4'd0),
.s_axi_awuser ({AXI_USER_WIDTH{1'b0}}),
.s_axi_awvalid (dma_m_axi_awvalid),
.s_axi_awready (dma_m_axi_awready),
.s_axi_wdata (dma_m_axi_wdata),
.s_axi_wstrb (dma_m_axi_wstrb),
.s_axi_wlast (dma_m_axi_wlast),
.s_axi_wuser ({AXI_USER_WIDTH{1'b0}}),
.s_axi_wvalid (dma_m_axi_wvalid),
.s_axi_wready (dma_m_axi_wready),
.s_axi_bid (dma_m_axi_bid),
.s_axi_bresp (dma_m_axi_bresp),
.s_axi_buser (unused_m_axi_buser),
.s_axi_bvalid (dma_m_axi_bvalid),
.s_axi_bready (dma_m_axi_bready),
.s_axi_arid (dma_m_axi_arid),
.s_axi_araddr (dma_m_axi_araddr),
.s_axi_arlen (dma_m_axi_arlen),
.s_axi_arsize (dma_m_axi_arsize),
.s_axi_arburst (dma_m_axi_arburst),
.s_axi_arlock (dma_m_axi_arlock),
.s_axi_arcache (dma_m_axi_arcache),
.s_axi_arprot (dma_m_axi_arprot),
.s_axi_arqos (4'd0),
.s_axi_arregion (4'd0),
.s_axi_aruser ({AXI_USER_WIDTH{1'b0}}),
.s_axi_arvalid (dma_m_axi_arvalid),
.s_axi_arready (dma_m_axi_arready),
.s_axi_rid (dma_m_axi_rid),
.s_axi_rdata (dma_m_axi_rdata),
.s_axi_rresp (dma_m_axi_rresp),
.s_axi_rlast (dma_m_axi_rlast),
.s_axi_ruser (unused_m_axi_ruser),
.s_axi_rvalid (dma_m_axi_rvalid),
.s_axi_rready (dma_m_axi_rready),
.m_axi (m_axi)
);
endmodule : axi_dma_if_wrapper
`default_nettype wire

View File

@ -0,0 +1,430 @@
# proxy to match cocotb and custom axi4_if
import logging
from cocotb.types import LogicArray
from cocotbext.axi.axi_channels import (
AxiAWBus,
AxiWBus,
AxiBBus,
AxiARBus,
AxiRBus,
AxiBus,
)
def install_cocotbext_axi_slice_compat():
"""
cocotbext-axi StreamMonitor uses RisingEdge(self.valid/self.ready).
That works only for real cocotb LogicObject handles.
Our SliceSignal is a Python proxy over packed req/resp vector, so
RisingEdge(SliceSignal) fails in cocotb 2.x.
This patch makes StreamMonitor wake on signal.value_change instead.
For SliceSignal this should return parent packed vector value_change.
"""
# this sucks...
from cocotbext.axi import stream as axi_stream
async def _run_valid_monitor_value_change(self):
while True:
await self.valid.value_change
self.wake_event.set()
async def _run_ready_monitor_value_change(self):
while True:
await self.ready.value_change
self.wake_event.set()
axi_stream.StreamMonitor._run_valid_monitor = _run_valid_monitor_value_change
axi_stream.StreamMonitor._run_ready_monitor = _run_ready_monitor_value_change
def _safe_int(value) -> int:
try:
return int(value)
except Exception:
s = str(value).upper()
bits = []
for ch in s:
if ch in "01":
bits.append(ch)
elif ch in "XZUW-":
bits.append("0")
if not bits:
return 0
return int("".join(bits), 2)
def _logic_array(value: int, width: int):
value &= (1 << width) - 1
return LogicArray.from_unsigned(value, width)
def _param_int(obj, name: str, override):
if override is not None:
return int(override)
p = getattr(obj, name)
try:
return int(p)
except Exception:
pass
try:
return int(p.value)
except Exception:
pass
raise RuntimeError(f"Cannot read parameter {name} from {obj!r}")
def _packed_offsets(fields):
"""
de-construct SV signal order.
"""
total = sum(width for _, width in fields)
pos = total
offsets = {}
for name, width in fields:
pos -= width
offsets[name] = (pos, width)
return total, offsets
class PackedVector:
def __init__(self, handle, name=None):
self.handle = handle
self.name = name or getattr(handle, "_path", repr(handle))
self.width = len(handle)
self.mask = (1 << self.width) - 1
self.shadow = _safe_int(handle.value) & self.mask
self.dirty = False
def read(self, lo: int, width: int) -> int:
src = self.shadow if self.dirty else (
_safe_int(self.handle.value) & self.mask)
return (src >> lo) & ((1 << width) - 1)
def write(self, lo: int, width: int, value, immediate=False):
value = _safe_int(value)
field_mask = ((1 << width) - 1) << lo
self.shadow &= ~field_mask
self.shadow |= (value << lo) & field_mask
self.shadow &= self.mask
self.dirty = True
v = _logic_array(self.shadow, self.width)
if immediate:
self.handle.setimmediatevalue(v)
else:
self.handle.value = v
class SliceSignal:
"""
cocotbext-axi proxy
looks like:
sig.value
sig.value = ...
sig.setimmediatevalue(...)
len(sig)
"""
def __init__(self, name: str, parent: PackedVector, lo: int, width: int):
self._name = name
self._path = name
self._log = logging.getLogger(f"cocotb.{name}")
self.parent = parent
self.lo = lo
self.width = width
def __len__(self):
return self.width
def __repr__(self):
return f"<SliceSignal {self._path}[{self.lo + self.width - 1}:{self.lo}]>"
@property
def value(self):
return _logic_array(self.parent.read(self.lo, self.width), self.width)
@value.setter
def value(self, value):
self.parent.write(self.lo, self.width, value, immediate=False)
def setimmediatevalue(self, value):
self.parent.write(self.lo, self.width, value, immediate=True)
def set(self, value):
self.value = value
def get(self):
return self.value
@property
def value_change(self):
# dirty
return self.parent.handle.value_change
class ProxyEntity:
def __init__(self, name: str, **signals):
self._name = name
self._path = name
self._log = logging.getLogger(f"cocotb.{name}")
self.__dict__.update(signals)
def __dir__(self):
return list(self.__dict__.keys())
def _sig(name, parent, base_lo, layout, field):
lo, width = layout[field]
return SliceSignal(name, parent, base_lo + lo, width)
def axi4_bus_from_packed_if(
iface,
name="axi",
*,
addr_width=None,
data_width=None,
id_width=None,
user_width=None,
):
"""
Build cocotbext-axi AxiBus from custom packed axi4_if.
iface should be instance axi4_if:
dut.axi_in
dut.axi_out
example:
master = AxiMaster(
axi4_bus_from_packed_if(dut.axi_in, "s_axi"),
dut.clk,
dut.rst,
)
ram = AxiRam(
axi4_bus_from_packed_if(dut.axi_out, "m_axi"),
dut.clk,
dut.rst,
size=2**20,
)
"""
ADDR_W = _param_int(iface, "ADDR_W", addr_width)
DATA_W = _param_int(iface, "DATA_W", data_width)
ID_W = _param_int(iface, "ID_W", id_width)
USER_W = _param_int(iface, "USER_W", user_width)
if DATA_W % 8 != 0:
raise ValueError(f"AXI DATA_W must be divisible by 8, got {DATA_W}")
STRB_W = DATA_W // 8
aw_total, aw_layout = _packed_offsets([
("id", ID_W),
("addr", ADDR_W),
("len", 8),
("size", 3),
("burst", 2),
("lock", 1),
("cache", 4),
("prot", 3),
("qos", 4),
("region", 4),
("user", USER_W),
("valid", 1),
])
w_total, w_layout = _packed_offsets([
("data", DATA_W),
("strb", STRB_W),
("last", 1),
("user", USER_W),
("valid", 1),
])
b_total, b_layout = _packed_offsets([
("id", ID_W),
("resp", 2),
("user", USER_W),
("valid", 1),
])
ar_total, ar_layout = _packed_offsets([
("id", ID_W),
("addr", ADDR_W),
("len", 8),
("size", 3),
("burst", 2),
("lock", 1),
("cache", 4),
("prot", 3),
("qos", 4),
("region", 4),
("user", USER_W),
("valid", 1),
])
r_total, r_layout = _packed_offsets([
("id", ID_W),
("data", DATA_W),
("resp", 2),
("last", 1),
("user", USER_W),
("valid", 1),
])
req_total, req_layout = _packed_offsets([
("aw", aw_total),
("w", w_total),
("b_ready", 1),
("ar", ar_total),
("r_ready", 1),
])
resp_total, resp_layout = _packed_offsets([
("aw_ready", 1),
("w_ready", 1),
("b", b_total),
("ar_ready", 1),
("r", r_total),
])
req = PackedVector(iface.req, f"{name}.req")
resp = PackedVector(iface.resp, f"{name}.resp")
if req_total != req.width:
raise RuntimeError(
f"{name}.req layout mismatch: calculated {req_total} bits, "
f"simulator has {req.width} bits"
)
if resp_total != resp.width:
raise RuntimeError(
f"{name}.resp layout mismatch: calculated {resp_total} bits, "
f"simulator has {resp.width} bits"
)
aw_lo, _ = req_layout["aw"]
w_lo, _ = req_layout["w"]
ar_lo, _ = req_layout["ar"]
b_lo, _ = resp_layout["b"]
r_lo, _ = resp_layout["r"]
aw = AxiAWBus.from_entity(ProxyEntity(
f"{name}_aw",
awid=_sig(f"{name}_awid", req, aw_lo, aw_layout, "id"),
awaddr=_sig(f"{name}_awaddr", req, aw_lo, aw_layout, "addr"),
awlen=_sig(f"{name}_awlen", req, aw_lo, aw_layout, "len"),
awsize=_sig(f"{name}_awsize", req, aw_lo, aw_layout, "size"),
awburst=_sig(f"{name}_awburst", req, aw_lo, aw_layout, "burst"),
awlock=_sig(f"{name}_awlock", req, aw_lo, aw_layout, "lock"),
awcache=_sig(f"{name}_awcache", req, aw_lo, aw_layout, "cache"),
awprot=_sig(f"{name}_awprot", req, aw_lo, aw_layout, "prot"),
awqos=_sig(f"{name}_awqos", req, aw_lo, aw_layout, "qos"),
awregion=_sig(f"{name}_awregion", req, aw_lo, aw_layout, "region"),
awuser=_sig(f"{name}_awuser", req, aw_lo, aw_layout, "user"),
awvalid=_sig(f"{name}_awvalid", req, aw_lo, aw_layout, "valid"),
awready=SliceSignal(
f"{name}_awready",
resp,
*resp_layout["aw_ready"],
),
))
w = AxiWBus.from_entity(ProxyEntity(
f"{name}_w",
wdata=_sig(f"{name}_wdata", req, w_lo, w_layout, "data"),
wstrb=_sig(f"{name}_wstrb", req, w_lo, w_layout, "strb"),
wlast=_sig(f"{name}_wlast", req, w_lo, w_layout, "last"),
wuser=_sig(f"{name}_wuser", req, w_lo, w_layout, "user"),
wvalid=_sig(f"{name}_wvalid", req, w_lo, w_layout, "valid"),
wready=SliceSignal(
f"{name}_wready",
resp,
*resp_layout["w_ready"],
),
))
b = AxiBBus.from_entity(ProxyEntity(
f"{name}_b",
bid=_sig(f"{name}_bid", resp, b_lo, b_layout, "id"),
bresp=_sig(f"{name}_bresp", resp, b_lo, b_layout, "resp"),
buser=_sig(f"{name}_buser", resp, b_lo, b_layout, "user"),
bvalid=_sig(f"{name}_bvalid", resp, b_lo, b_layout, "valid"),
bready=SliceSignal(
f"{name}_bready",
req,
*req_layout["b_ready"],
),
))
ar = AxiARBus.from_entity(ProxyEntity(
f"{name}_ar",
arid=_sig(f"{name}_arid", req, ar_lo, ar_layout, "id"),
araddr=_sig(f"{name}_araddr", req, ar_lo, ar_layout, "addr"),
arlen=_sig(f"{name}_arlen", req, ar_lo, ar_layout, "len"),
arsize=_sig(f"{name}_arsize", req, ar_lo, ar_layout, "size"),
arburst=_sig(f"{name}_arburst", req, ar_lo, ar_layout, "burst"),
arlock=_sig(f"{name}_arlock", req, ar_lo, ar_layout, "lock"),
arcache=_sig(f"{name}_arcache", req, ar_lo, ar_layout, "cache"),
arprot=_sig(f"{name}_arprot", req, ar_lo, ar_layout, "prot"),
arqos=_sig(f"{name}_arqos", req, ar_lo, ar_layout, "qos"),
arregion=_sig(f"{name}_arregion", req, ar_lo, ar_layout, "region"),
aruser=_sig(f"{name}_aruser", req, ar_lo, ar_layout, "user"),
arvalid=_sig(f"{name}_arvalid", req, ar_lo, ar_layout, "valid"),
arready=SliceSignal(
f"{name}_arready",
resp,
*resp_layout["ar_ready"],
),
))
r = AxiRBus.from_entity(ProxyEntity(
f"{name}_r",
rid=_sig(f"{name}_rid", resp, r_lo, r_layout, "id"),
rdata=_sig(f"{name}_rdata", resp, r_lo, r_layout, "data"),
rresp=_sig(f"{name}_rresp", resp, r_lo, r_layout, "resp"),
rlast=_sig(f"{name}_rlast", resp, r_lo, r_layout, "last"),
ruser=_sig(f"{name}_ruser", resp, r_lo, r_layout, "user"),
rvalid=_sig(f"{name}_rvalid", resp, r_lo, r_layout, "valid"),
rready=SliceSignal(
f"{name}_rready",
req,
*req_layout["r_ready"],
),
))
return AxiBus.from_channels(aw, w, b, ar, r)

View File

@ -1,113 +1,11 @@
`timescale 1ns/1ps
module tb_axi4_loopback #(
parameter int unsigned ADDR_W = 32,
parameter int unsigned DATA_W = 64,
parameter int unsigned ID_W = 4,
parameter int unsigned USER_W = 1
)(
input logic clk,
input logic rst,
// slave-side flat AXI port (cocotb driven)
input logic [ID_W-1:0] s_axi_awid,
input logic [ADDR_W-1:0] s_axi_awaddr,
input logic [7:0] s_axi_awlen,
input logic [2:0] s_axi_awsize,
input logic [1:0] s_axi_awburst,
input logic s_axi_awlock,
input logic [3:0] s_axi_awcache,
input logic [2:0] s_axi_awprot,
input logic [3:0] s_axi_awqos,
input logic [3:0] s_axi_awregion,
input logic [USER_W-1:0] s_axi_awuser,
input logic s_axi_awvalid,
output logic s_axi_awready,
input logic [DATA_W-1:0] s_axi_wdata,
input logic [DATA_W/8-1:0] s_axi_wstrb,
input logic s_axi_wlast,
input logic [USER_W-1:0] s_axi_wuser,
input logic s_axi_wvalid,
output logic s_axi_wready,
output logic [ID_W-1:0] s_axi_bid,
output logic [1:0] s_axi_bresp,
output logic [USER_W-1:0] s_axi_buser,
output logic s_axi_bvalid,
input logic s_axi_bready,
input logic [ID_W-1:0] s_axi_arid,
input logic [ADDR_W-1:0] s_axi_araddr,
input logic [7:0] s_axi_arlen,
input logic [2:0] s_axi_arsize,
input logic [1:0] s_axi_arburst,
input logic s_axi_arlock,
input logic [3:0] s_axi_arcache,
input logic [2:0] s_axi_arprot,
input logic [3:0] s_axi_arqos,
input logic [3:0] s_axi_arregion,
input logic [USER_W-1:0] s_axi_aruser,
input logic s_axi_arvalid,
output logic s_axi_arready,
output logic [ID_W-1:0] s_axi_rid,
output logic [DATA_W-1:0] s_axi_rdata,
output logic [1:0] s_axi_rresp,
output logic s_axi_rlast,
output logic [USER_W-1:0] s_axi_ruser,
output logic s_axi_rvalid,
input logic s_axi_rready,
// master-side flat AXI port for coco-tb
output logic [ID_W-1:0] m_axi_awid,
output logic [ADDR_W-1:0] m_axi_awaddr,
output logic [7:0] m_axi_awlen,
output logic [2:0] m_axi_awsize,
output logic [1:0] m_axi_awburst,
output logic m_axi_awlock,
output logic [3:0] m_axi_awcache,
output logic [2:0] m_axi_awprot,
output logic [3:0] m_axi_awqos,
output logic [3:0] m_axi_awregion,
output logic [USER_W-1:0] m_axi_awuser,
output logic m_axi_awvalid,
input logic m_axi_awready,
output logic [DATA_W-1:0] m_axi_wdata,
output logic [DATA_W/8-1:0] m_axi_wstrb,
output logic m_axi_wlast,
output logic [USER_W-1:0] m_axi_wuser,
output logic m_axi_wvalid,
input logic m_axi_wready,
input logic [ID_W-1:0] m_axi_bid,
input logic [1:0] m_axi_bresp,
input logic [USER_W-1:0] m_axi_buser,
input logic m_axi_bvalid,
output logic m_axi_bready,
output logic [ID_W-1:0] m_axi_arid,
output logic [ADDR_W-1:0] m_axi_araddr,
output logic [7:0] m_axi_arlen,
output logic [2:0] m_axi_arsize,
output logic [1:0] m_axi_arburst,
output logic m_axi_arlock,
output logic [3:0] m_axi_arcache,
output logic [2:0] m_axi_arprot,
output logic [3:0] m_axi_arqos,
output logic [3:0] m_axi_arregion,
output logic [USER_W-1:0] m_axi_aruser,
output logic m_axi_arvalid,
input logic m_axi_arready,
input logic [ID_W-1:0] m_axi_rid,
input logic [DATA_W-1:0] m_axi_rdata,
input logic [1:0] m_axi_rresp,
input logic m_axi_rlast,
input logic [USER_W-1:0] m_axi_ruser,
input logic m_axi_rvalid,
output logic m_axi_rready
input logic clk,
input logic rst
);
logic aresetn;
@ -133,120 +31,9 @@ module tb_axi4_loopback #(
.aresetn(aresetn)
);
axi4_flat_to_if #(
.ADDR_W(ADDR_W),
.DATA_W(DATA_W),
.ID_W(ID_W),
.USER_W(USER_W)
) u_flat_to_if (
.s_axi_awid (s_axi_awid),
.s_axi_awaddr (s_axi_awaddr),
.s_axi_awlen (s_axi_awlen),
.s_axi_awsize (s_axi_awsize),
.s_axi_awburst (s_axi_awburst),
.s_axi_awlock (s_axi_awlock),
.s_axi_awcache (s_axi_awcache),
.s_axi_awprot (s_axi_awprot),
.s_axi_awqos (s_axi_awqos),
.s_axi_awregion (s_axi_awregion),
.s_axi_awuser (s_axi_awuser),
.s_axi_awvalid (s_axi_awvalid),
.s_axi_awready (s_axi_awready),
.s_axi_wdata (s_axi_wdata),
.s_axi_wstrb (s_axi_wstrb),
.s_axi_wlast (s_axi_wlast),
.s_axi_wuser (s_axi_wuser),
.s_axi_wvalid (s_axi_wvalid),
.s_axi_wready (s_axi_wready),
.s_axi_bid (s_axi_bid),
.s_axi_bresp (s_axi_bresp),
.s_axi_buser (s_axi_buser),
.s_axi_bvalid (s_axi_bvalid),
.s_axi_bready (s_axi_bready),
.s_axi_arid (s_axi_arid),
.s_axi_araddr (s_axi_araddr),
.s_axi_arlen (s_axi_arlen),
.s_axi_arsize (s_axi_arsize),
.s_axi_arburst (s_axi_arburst),
.s_axi_arlock (s_axi_arlock),
.s_axi_arcache (s_axi_arcache),
.s_axi_arprot (s_axi_arprot),
.s_axi_arqos (s_axi_arqos),
.s_axi_arregion (s_axi_arregion),
.s_axi_aruser (s_axi_aruser),
.s_axi_arvalid (s_axi_arvalid),
.s_axi_arready (s_axi_arready),
.s_axi_rid (s_axi_rid),
.s_axi_rdata (s_axi_rdata),
.s_axi_rresp (s_axi_rresp),
.s_axi_rlast (s_axi_rlast),
.s_axi_ruser (s_axi_ruser),
.s_axi_rvalid (s_axi_rvalid),
.s_axi_rready (s_axi_rready),
.m_axi (axi_in)
);
axi4_loopback #(
.ADDR_W(ADDR_W),
.DATA_W(DATA_W),
.ID_W(ID_W),
.USER_W(USER_W)
) dut (
axi4_loopback dut (
.s_axi(axi_in),
.m_axi(axi_out)
);
axi4_if_to_flat #(
.ADDR_W(ADDR_W),
.DATA_W(DATA_W),
.ID_W(ID_W),
.USER_W(USER_W)
) u_if_to_flat (
.s_axi (axi_out),
.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)
);
endmodule
endmodule

View File

@ -3,7 +3,12 @@ import itertools
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, Timer
from cocotbext.axi import AxiBus, AxiMaster, AxiRam
from cocotbext.axi import AxiMaster, AxiRam
from cocotbext.axi import AxiMaster, AxiRam
from axi4_proxy import axi4_bus_from_packed_if, install_cocotbext_axi_slice_compat
install_cocotbext_axi_slice_compat()
class TB:
@ -12,21 +17,28 @@ class TB:
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
# Forencich-style connection:
# s_axi: cocotb AXI master -> flat_to_if -> compact AXI interface
# m_axi: compact AXI interface -> if_to_flat -> cocotb AXI RAM
self.master = AxiMaster(AxiBus.from_prefix(
dut, "s_axi"), dut.clk, dut.rst)
self.ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"),
dut.clk, dut.rst, size=2**20)
self.master = AxiMaster(
axi4_bus_from_packed_if(dut.axi_in, "s_axi"),
dut.clk,
dut.rst,
)
self.ram = AxiRam(
axi4_bus_from_packed_if(dut.axi_out, "m_axi"),
dut.clk,
dut.rst,
size=2**20,
)
async def 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)
@ -36,6 +48,13 @@ def cycle_pause():
return itertools.cycle([1, 1, 1, 0])
@cocotb.test()
async def inspect_axi_if(dut):
dut._log.info("axi_in fields: %s", dir(dut.axi_in))
dut._log.info("axi_in.req fields: %s", dir(dut.axi_in.req))
dut._log.info("axi_in.resp fields: %s", dir(dut.axi_in.resp))
@cocotb.test()
async def run_basic_write_read_test(dut):
# simple loopback test..