Package: rapidstream-cocotbext-axi
AXI interface modules for CocotbGitHub repository:https://github.com/alexforencich/cocotbext-axiIntroductionAXI, AXI lite, and AXI stream simulation models forcocotb.InstallationInstallation from pip (release version, stable):$ pip install cocotbext-axiInstallation from git (latest development version, potentially unstable):$ pip install https://github.com/alexforencich/cocotbext-axi/archive/master.zipInstallation for active development:$ git clone https://github.com/alexforencich/cocotbext-axi
$ pip install -e cocotbext-axiDocumentation and usage examplesSee thetestsdirectory,verilog-axi, andverilog-axisfor complete testbenches using these modules.AXI and AXI lite masterTheAxiMasterandAxiLiteMasterclasses implement AXI masters and are capable of generating read and write operations against AXI slaves.  Requested operations will be split and aligned according to the AXI specification.  TheAxiMastermodule is capable of generating narrow bursts, handling multiple in-flight operations, and handling reordering and interleaving in responses across different transaction IDs.AxiMasterandAxiLiteMasterand related objects all extendRegion, so they can be attached toAddressSpaceobjects to handle memory operations in the specified region.TheAxiMasteris a wrapper aroundAxiMasterWriteandAxiMasterRead.  Similarly,AxiLiteMasteris a wrapper aroundAxiLiteMasterWriteandAxiLiteMasterRead.  If a read-only or write-only interface is required instead of a full interface, use the corresponding read-only or write-only variant, the usage and API are exactly the same.To use these modules, import the one you need and connect it to the DUT:from cocotbext.axi import AxiBus, AxiMaster

axi_master = AxiMaster(AxiBus.from_prefix(dut, "s_axi"), dut.clk, dut.rst)The first argument to the constructor accepts anAxiBusorAxiLiteBusobject, as appropriate.  These objects are containers for the interface signals and include class methods to automate connections.Once the module is instantiated, read and write operations can be initiated in a couple of different ways.First, operations can be carried out with async blockingread(),write(), and their associated word-access wrappers.  Multiple concurrent operations started from different coroutines are handled correctly, with results returned in the order that the operations complete.  For example:await axi_master.write(0x0000, b'test')
data = await axi_master.read(0x0000, 4)Additional parameters can be specified to control sideband signals and burst settings.  The transfer will be split into one or more bursts according to the AXI specification.  All bursts generated from the same call toread()orwrite()will use the same ID, which will be automatically generated if not specified.read()andwrite()returnnamedtupleobjects containingaddress,dataorlength, andresp.  This is the preferred style, and this is the only style supported by the word-access wrappers.Alternatively, operations can be initiated with non-blockinginit_read()andinit_write().  These functions returnEventobjects which are triggered when the operation completes, and the result can be retrieved fromEvent.data.  For example:write_op = axi_master.init_write(0x0000, b'test')
await write_op.wait()
resp = write_op.data
read_op = axi_master.init_read(0x0000, 4)
await read_op.wait()
resp = read_op.dataWith this method, it is possible to start multiple concurrent operations from the same coroutine.  It is also possible to use the events withCombine,First, andwith_timeout.AxiMasterandAxiLiteMasterconstructor parametersbus:AxiBusorAxiLiteBusobject containing AXI interface signalsclock: clock signalreset: reset signal (optional)reset_active_level: reset active level (optional, defaultTrue)Additional parameters forAxiMastermax_burst_len: maximum burst length in cycles, range 1-256, default 256.Methodsinit_read(address, length, ...): initiate readinglengthbytes, starting ataddress.  Returns anEventobject.init_write(address, data, ...): initiate writingdata(bytes), starting fromaddress.  Returns anEventobject.idle(): returnsTruewhen there are no outstanding operations in progresswait(): blocking wait until all outstanding operations completewait_read(): wait until all outstanding read operations completewait_write(): wait until all outstanding write operations completeread(address, length, ...): readlengthbytes, starting ataddressread_words(address, count, byteorder='little', ws=2, ...): readcountws-byte words, starting ataddressread_dwords(address, count, byteorder='little', ...): readcount4-byte dwords, starting ataddressread_qwords(address, count, byteorder='little', ...): readcount8-byte qwords, starting ataddressread_byte(address, ...): read single byte ataddressread_word(address, byteorder='little', ws=2, ...): read singlews-byte word ataddressread_dword(address, byteorder='little', ...): read single 4-byte dword ataddressread_qword(address, byteorder='little', ...): read single 8-byte qword ataddresswrite(address, data, ...): writedata(bytes), starting ataddresswrite_words(address, data, byteorder='little', ws=2, ...): writedata(ws-byte words), starting ataddresswrite_dwords(address, data, byteorder='little', ...): writedata(4-byte dwords), starting ataddresswrite_qwords(address, data, byteorder='little', ...): writedata(8-byte qwords), starting ataddresswrite_byte(address, data, ...): write single byte ataddresswrite_word(address, data, byteorder='little', ws=2, ...): write singlews-byte word ataddresswrite_dword(address, data, byteorder='little', ...): write single 4-byte dword ataddresswrite_qword(address, data, byteorder='little', ...): write single 8-byte qword ataddressAdditional optional arguments forAxiMasterarid,awid: AXI ID for bursts, default automatically assignedburst: AXI burst type, defaultAxiBurstType.INCRsize: AXI burst size, default maximum supported by interfacelock: AXI lock type, defaultAxiLockType.NORMALcache: AXI cache field, default0b0011prot: AXI protection flags, defaultAxiProt.NONSECUREqos: AXI QOS field, default0region: AXI region field, default0user: AXI user signal (awuser/aruser), default0wuser: AXI wuser signal, default0(write-related methods only)event:Eventobject used to wait on and retrieve result for specific operation, defaultNone.  The event will be triggered when the operation completes and the result returned viaEvent.data.  (init_read()andinit_write()only)Additional optional arguments forAxiLiteMasterprot: AXI protection flags, defaultAxiProt.NONSECUREevent:Eventobject used to wait on and retrieve result for specific operation, defaultNone.  The event will be triggered when the operation completes and the result returned viaEvent.data.  (init_read()andinit_write()only)AxiBusandAxiLiteBusobjectsTheAxiBus,AxiLiteBus, and related objects are containers for the interface signals.  These hold instances of bus objects for the individual channels, which are currently extensions ofcocotb_bus.bus.Bus.  Class methodsfrom_entityandfrom_prefixare provided to facilitate signal name matching.  For AXI interfaces useAxiBus,AxiReadBus, orAxiWriteBus, as appropriate.  For AXI lite interfaces, useAxiLiteBus,AxiLiteReadBus, orAxiLiteWriteBus, as appropriate.AXI and AXI lite slaveTheAxiSlaveandAxiLiteSlaveclasses implement AXI slaves and are capable of completing read and write operations from upstream AXI masters.  TheAxiSlavemodule is capable of handling narrow bursts.  These modules can either be used to perform memory reads and writes on aMemoryInterfaceon behalf of the DUT, or they can be extended to implement customized functionality.TheAxiSlaveis a wrapper aroundAxiSlaveWriteandAxiSlaveRead.  Similarly,AxiLiteSlaveis a wrapper aroundAxiLiteSlaveWriteandAxiLiteSlaveRead.  If a read-only or write-only interface is required instead of a full interface, use the corresponding read-only or write-only variant, the usage and API are exactly the same.To use these modules, import the one you need and connect it to the DUT:from cocotbext.axi import AxiBus, AxiSlave, MemoryRegion

axi_slave = AxiSlave(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst)
region = MemoryRegion(2**axi_slave.read_if.address_width)
axi_slave.target = regionThe first argument to the constructor accepts anAxiBusorAxiLiteBusobject.  These objects are containers for the interface signals and include class methods to automate connections.It is also possible to extend these modules; operation can be customized by overriding the internal_read()and_write()methods.  SeeAxiRamandAxiLiteRamfor an example.AxiSlaveandAxiLiteSlaveconstructor parametersbus:AxiBusorAxiLiteBusobject containing AXI interface signalsclock: clock signalreset: reset signal (optional)reset_active_level: reset active level (optional, defaultTrue)target: target region (optional, defaultNone)Attributes:target: target regionAXI and AXI lite RAMTheAxiRamandAxiLiteRamclasses implement AXI RAMs and are capable of completing read and write operations from upstream AXI masters.  TheAxiRammodule is capable of handling narrow bursts.  These modules are extensions of the correspondingAxiSlaveandAxiLiteSlavemodules.  Internally,SparseMemoryis used to support emulating very large memories.TheAxiRamis a wrapper aroundAxiRamWriteandAxiRamRead.  Similarly,AxiLiteRamis a wrapper aroundAxiLiteRamWriteandAxiLiteRamRead.  If a read-only or write-only interface is required instead of a full interface, use the corresponding read-only or write-only variant, the usage and API are exactly the same.To use these modules, import the one you need and connect it to the DUT:from cocotbext.axi import AxiBus, AxiRam

axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**32)The first argument to the constructor accepts anAxiBusorAxiLiteBusobject.  These objects are containers for the interface signals and include class methods to automate connections.Once the module is instantiated, the memory contents can be accessed in a couple of different ways.  First, themmapobject can be accessed directly via thememattribute.  Second,read(),write(), and various word-access wrappers are available.  Hex dump helper methods are also provided for debugging.  For example:axi_ram.write(0x0000, b'test')
data = axi_ram.read(0x0000, 4)
axi_ram.hexdump(0x0000, 4, prefix="RAM")Multi-port memories can be constructed by passing thememobject of the first instance to the other instances.  For example, here is how to create a four-port RAM:axi_ram_p1 = AxiRam(AxiBus.from_prefix(dut, "m00_axi"), dut.clk, dut.rst, size=2**32)
axi_ram_p2 = AxiRam(AxiBus.from_prefix(dut, "m01_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
axi_ram_p3 = AxiRam(AxiBus.from_prefix(dut, "m02_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
axi_ram_p4 = AxiRam(AxiBus.from_prefix(dut, "m03_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)AxiRamandAxiLiteRamconstructor parametersbus:AxiBusorAxiLiteBusobject containing AXI interface signalsclock: clock signalreset: reset signal (optional)reset_active_level: reset active level (optional, defaultTrue)size: memory size in bytes (optional, default2**64)mem:mmaporSparseMemorybacking object to use (optional, overridessize)Attributes:mem: directly access sharedmmaporSparseMemorybacking objectMethodsread(address, length): readlengthbytes, starting ataddressread_words(address, count, byteorder='little', ws=2): readcountws-byte words, starting ataddressread_dwords(address, count, byteorder='little'): readcount4-byte dwords, starting ataddressread_qwords(address, count, byteorder='little'): readcount8-byte qwords, starting ataddressread_byte(address): read single byte ataddressread_word(address, byteorder='little', ws=2): read singlews-byte word ataddressread_dword(address, byteorder='little'): read single 4-byte dword ataddressread_qword(address, byteorder='little'): read single 8-byte qword ataddresswrite(address, data): writedata(bytes), starting ataddresswrite_words(address, data, byteorder='little', ws=2): writedata(ws-byte words), starting ataddresswrite_dwords(address, data, byteorder='little'): writedata(4-byte dwords), starting ataddresswrite_qwords(address, data, byteorder='little'): writedata(8-byte qwords), starting ataddresswrite_byte(address, data): write single byte ataddresswrite_word(address, data, byteorder='little', ws=2): write singlews-byte word ataddresswrite_dword(address, data, byteorder='little'): write single 4-byte dword ataddresswrite_qword(address, data, byteorder='little'): write single 8-byte qword ataddresshexdump(address, length, prefix=''): print hex dump oflengthbytes starting fromaddress, prefix lines with optionalprefixhexdump_line(address, length, prefix=''): return hex dump (list of str) oflengthbytes starting fromaddress, prefix lines with optionalprefixhexdump_str(address, length, prefix=''): return hex dump (str) oflengthbytes starting fromaddress, prefix lines with optionalprefixAXI streamTheAxiStreamSource,AxiStreamSink, andAxiStreamMonitorclasses can be used to drive, receive, and monitor traffic on AXI stream interfaces.  TheAxiStreamSourcedrives all signals except fortreadyand can be used to drive AXI stream traffic into a design.  TheAxiStreamSinkdrives thetreadyline only and as such can receive AXI stream traffic and exert backpressure.  TheAxiStreamMonitordrives no signals and as such can be connected to AXI stream interfaces anywhere within a design to passively monitor traffic.To use these modules, import the one you need and connect it to the DUT:from cocotbext.axi import (AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor)

axis_source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "s_axis"), dut.clk, dut.rst)
axis_sink = AxiStreamSink(AxiStreamBus.from_prefix(dut, "m_axis"), dut.clk, dut.rst)
axis_mon= AxiStreamMonitor(AxiStreamBus.from_prefix(dut.inst, "int_axis"), dut.clk, dut.rst)The first argument to the constructor accepts anAxiStreamBusobject.  This object is a container for the interface signals and includes class methods to automate connections.To send data into a design with anAxiStreamSource, callsend()/send_nowait()orwrite()/write_nowait().  Accepted data types are iterables orAxiStreamFrameobjects.  Optionally, callwait()to wait for the transmit operation to complete.  Example:await axis_source.send(b'test data')
# wait for operation to complete (optional)
await axis_source.wait()It is also possible to wait for the transmission of a specific frame to complete by passing an event in the tx_complete field of theAxiStreamFrameobject, and then awaiting the event.  The frame, with simulation time fields set, will be returned in the event data.  Example:frame = AxiStreamFrame(b'test data', tx_complete=Event())
await axis_source.send(frame)
await frame.tx_complete.wait()
print(frame.tx_complete.data.sim_time_start)To receive data with anAxiStreamSinkorAxiStreamMonitor, callrecv()/recv_nowait()orread()/read_nowait().  Optionally callwait()to wait for new receive data.recv()is intended for use with a frame-oriented interface, and by default compactsAxiStreamFrames before returning them.read()is intended for non-frame-oriented streams.  Callingread()internally callsrecv()for all frames currently in the queue, then compacts and coalescestdatafrom all frames into a separate read queue, from which read data is returned.  All sideband data is discarded.data = await axis_sink.recv()Signalstdata: data, requiredtvalid: qualifies all other signals; optional, assumed1when absenttready: indicates sink is ready for data; optional, assumed1when absenttlast: marks the last cycle of a frame; optional, assumed1when absenttkeep: qualifies data byte, data bus width must be evenly divisible bytkeepsignal width; optional, assumed1when absenttid: ID signal, can be used for routing; optional, assumed0when absenttdest: destination signal, can be used for routing; optional, assumed0when absenttuser: additional user data; optional, assumed0when absentConstructor parameters:bus:AxiStreamBusobject containing AXI stream interface signalsclock: clock signalreset: reset signal (optional)reset_active_level: reset active level (optional, defaultTrue)byte_size: byte size (optional)byte_lanes: byte lane count (optional)Note:byte_size,byte_lanes,len(tdata), andlen(tkeep)are all related, in thatbyte_lanesis set fromtkeepif it is connected, andbyte_size*byte_lanes == len(tdata).  So, iftkeepis connected, bothbyte_sizeandbyte_laneswill be computed internally and cannot be overridden.  Iftkeepis not connected, then eitherbyte_sizeorbyte_lanescan be specified, and the other will be computed such thatbyte_size*byte_lanes == len(tdata).Attributes:pause: stall the interface (deasserttreadyortvalid) (source/sink only)queue_occupancy_bytes: number of bytes in queue (all)queue_occupancy_frames: number of frames in queue (all)queue_occupancy_limit_bytes: max number of bytes in queue allowed before backpressure is applied (source/sink only)queue_occupancy_limit_frames: max number of frames in queue allowed before backpressure is applied (source/sink only)Methodssend(frame): sendframe(blocking) (source)send_nowait(frame): sendframe(non-blocking) (source)write(data): senddata(alias of send) (blocking) (source)write_nowait(data): senddata(alias of send_nowait) (non-blocking) (source)recv(compact=True): receive a frame as aGmiiFrame(blocking) (sink)recv_nowait(compact=True): receive a frame as aGmiiFrame(non-blocking) (sink)read(count): readcountbytes from buffer (blocking) (sink/monitor)read_nowait(count): readcountbytes from buffer (non-blocking) (sink/monitor)count(): returns the number of items in the queue (all)empty(): returnsTrueif the queue is empty (all)full(): returnsTrueif the queue occupancy limits are met (source/sink)idle(): returnsTrueif no transfer is in progress (all) or if the queue is not empty (source)clear(): drop all data in queue (all)wait(): wait for idle (source)wait(timeout=0, timeout_unit='ns'): wait for frame received (sink)set_pause_generator(generator): set generator for pause signal, generator will be advanced on every clock cycle (source/sink)clear_pause_generator(): remove generator for pause signal (source/sink)AxiStreamBusobjectTheAxiStreamBusobject is a container for the interface signals.  Currently, it is an extension ofcocotb.bus.Bus.  Class methodsfrom_entityandfrom_prefixare provided to facilitate signal name matching.AxiStreamFrameobjectTheAxiStreamFrameobject is a container for a frame to be transferred via AXI stream.  Thetdatafield contains the packet data in the form of a list of bytes, which is either abytearrayif the byte size is 8 bits or alistofints otherwise.tkeep,tid,tdest, andtusercan either beNone, anint, or alistofints.Attributes:tdata: bytes, bytearray, or listtkeep: tkeep field, optional; list, each entry qualifies the corresponding entry intdata.  Can be used to insert gaps on the source side.tid: tid field, optional; int or list with one entry pertdata, last value used per cycle when sending.tdest: tdest field, optional; int or list with one entry pertdata, last value used per cycle when sending.tuser: tuser field, optional; int or list with one entry pertdata, last value used per cycle when sending.sim_time_start: simulation time of first transfer cycle of frame.sim_time_end: simulation time of last transfer cycle of frame.tx_complete: event or callable triggered when frame is transmitted.Methods:normalize(): packtkeep,tid,tdest, andtuserto the same length astdata, replicating last element if necessary, initializetkeepto list of1andtid,tdest, andtuserto list of0if not specified.compact(): removetdata,tid,tdest, andtuservalues based ontkeep, removetkeep, compacttid,tdest, andtuserto an int if all values are identical.Address space abstractionThe address space abstraction provides a framework for cross-connecting multiple memory-mapped interfaces for testing components that interface with complex systems, including components with DMA engines.MemoryInterfaceis the base class for all components in the address space abstraction.MemoryInterfaceprovides the coreread()andwrite()methods, which implement bounds checking, as well as word-access wrappers.  Methods for creatingWindowandWindowPoolobjects are also provided.  The functionget_absolute_address()translates addresses to the system address space.MemoryInterfacecan be extended to implement custom functionality by overriding_read()and_write().Windowobjects represent views onto a parent address space with some length and offset.read()andwrite()operations on aWindoware translated to the equivalent operations on the parent address space.  MultipleWindowinstances can overlap and access the same portion of address space.WindowPoolprovides a method for dynamically allocating windows from a section of address space.  It uses a standard memory management algorithm to provide naturally-alignedWindowobjects of the requested size.Regionis the base class for all components which implement a portion of address space.Regionobjects can be registered withAddressSpaceobjects to handleread()andwrite()operations in a specified region.Regioncan be extended by components that implement a portion of address space.MemoryRegionis an extension ofRegionthat uses anmmapinstance to handle memory operations.MemoryRegionalso provides hex dump methods as well as indexing and slicing.SparseMemoryRegionis similar toMemoryRegionbut is backed bySparseMemoryinstead ofmmapand as such can emulate extremely large regions of address space.PeripheralRegionis an extension ofRegionthat can wrap another object that implementsread()andwrite(), as an alternative to extendingRegion.AddressSpaceis the core object for handling address spaces.Regionobjects can be registered withAddressSpacewith specified base address, size, and offset.  TheAddressSpaceobject will then directread()andwrite()operations to the appropriateRegions, splitting requests appropriately when necessary and translating addresses.  Regions registered withoffsetother thanNoneare translated such that accesses to base address + N map to N + offset.  Regions registered with anoffsetofNoneare not translated.Regionobjects registered with the sameAddressSpacecannot overlap, however the sameRegioncan be registered multiple times.AddressSpacealso provides a method for creatingPoolobjects.Poolis an extension ofAddressSpacethat supports dynamic allocation ofMemoryRegions.  It uses a standard memory management algorithm to provide naturally-alignedMemoryRegionobjects of the requested size.ExampleThis is a simple example that shows how the address space abstraction components can be used to connect a DUT to a simulated host system, including simulated RAM, an AXI interface from the DUT for DMA, and an AXI lite interface to the DUT for control.from cocotbext.axi import AddressSpace, SparseMemoryRegion
from cocotbext.axi import AxiBus, AxiLiteMaster, AxiSlave

# system address space
address_space = AddressSpace(2**32)

# RAM
ram = SparseMemoryRegion(2**24)
address_space.register_region(ram, 0x0000_0000)
ram_pool = address_space.create_window_pool(0x0000_0000, 2**20)

# DUT control register interface
axil_master = AxiLiteMaster(AxiLiteBus.from_prefix(dut, "s_axil_ctrl"), dut.clk, dut.rst)
address_space.register_region(axil_master, 0x8000_0000)
ctrl_regs = address_space.create_window(0x8000_0000, axil_master.size)

# DMA from DUT
axi_slave = AxiSlave(AxiBus.from_prefix(dut, "m_axi_dma"), dut.clk, dut.rst, target=address_space)

# exercise DUT DMA functionality
src_block = ram_pool.alloc_window(1024)
dst_block = ram_pool.alloc_window(1024)

test_data = b'test data'
await src_block.write(0, test_data)

await ctrl_regs.write_dword(DMA_SRC_ADDR, src_block.get_absolute_address(0))
await ctrl_regs.write_dword(DMA_DST_ADDR, dst_block.get_absolute_address(0))
await ctrl_regs.write_dword(DMA_LEN, len(test_data))
await ctrl_regs.write_dword(DMA_CONTROL, 1)

while await ctrl_regs.read_dword(DMA_STATUS) == 0:
    pass

assert await dst_block.read(0, len(test_data)) == test_dataAXI signalsWrite address channelawid: transaction IDawaddr: addressawlen: burst length (cycles)awsize: burst size (bytes/cycle)awburst: burst typeawlock: lock typeawcache: cache controlawprot: protection bitsawqos: QoS fieldawregion: region fieldawuser: additional user sideband dataawvalid: valid signal, qualifies all channel fieldsawready: ready signal, back-pressure from sinkWrite data channelwdata: write datawstrb: write strobewlast: end of burst flagwuser: additional user sideband datawvalid: valid signal, qualifies all channel fieldswready: ready signal, back-pressure from sinkWrite response channelbid: transaction IDbresp: write responsebuser: additional user sideband databvalid: valid signal, qualifies all channel fieldsbready: ready signal, back-pressure from sinkRead address channelarid: transaction IDaraddr: addressarlen: burst length (cycles)arsize: burst size (bytes/cycle)arburst: burst typearlock: lock typearcache: cache controlarprot: protection bitsarqos: QoS fieldarregion: region fieldaruser: additional user sideband dataarvalid: valid signal, qualifies all channel fieldsarready: ready signal, back-pressure from sinkRead data channelrid: transaction IDrdata: read datarresp: read responserlast: end of burst flagruser: additional user sideband datarvalid: valid signal, qualifies all channel fieldsrready: ready signal, back-pressure from sinkAXI lite signalsWrite address channelawaddr: addressawprot: protection bitsawvalid: valid signal, qualifies all channel fieldsawready: ready signal, back-pressure from sinkWrite data channelwdata: write datawstrb: write strobewvalid: valid signal, qualifies all channel fieldswready: ready signal, back-pressure from sinkWrite response channelbresp: write responsebvalid: valid signal, qualifies all channel fieldsbready: ready signal, back-pressure from sinkRead address channelaraddr: addressarprot: protection bitsarvalid: valid signal, qualifies all channel fieldsarready: ready signal, back-pressure from sinkRead data channelrdata: read datarresp: read responservalid: valid signal, qualifies all channel fieldsrready: ready signal, back-pressure from sinkAXI stream signalstdata: datatvalid: qualifies all other signalstready: indicates sink is ready for datatlast: marks the last cycle of a frametkeep: qualifies data bytes intdatatid: ID signal, can be used for routingtdest: destination signal, can be used for routingtuser: additional sideband data