cocotbext-axi

repository·master·Indexed 43 days ago

https://github.com/alexforencich/cocotbext-axi

Simulation models for AXI, AXI lite, AXI stream, and APB protocols designed for use with the cocotb verification framework. It provides master, slave, and RAM implementations, as well as AXI Stream sources, sinks, and monitors. The library includes an Address Space abstraction for managing memory-mapped interfaces, supporting MemoryRegion, SparseMemoryRegion, and PeripheralRegion for complex system simulation.

Tokens
2.6K
Snippets
6
Records
7
Agent score
7%

What's inside cocotbext-axi

  1. How the Address Space abstraction works

    master

    The Address Space abstraction provides a framework for connecting multiple memory-mapped interfaces (like AXI masters and slaves) to simulate complex systems, such as a DUT with a DMA engine.

    Core Components

    • AddressSpace: The central coordinator. You register Region objects with it. It directs read() and write() operations to the correct region and handles address translation.
    • Region: Represents a portion of the address space.
      • MemoryRegion: Uses mmap for memory operations.
      • SparseMemoryRegion: Uses SparseMemory for extremely large address spaces.
      • PeripheralRegion: Wraps an existing object that implements read() and write().
    • Window: A view onto a parent address space with a specific length and offset. Operations on a Window are translated to the parent.
    • Pool / WindowPool: Provides dynamic allocation of MemoryRegion or Window objects, ensuring they are naturally aligned.

    Typical Workflow

    1. Create an AddressSpace.
    2. Register a SparseMemoryRegion as the main system RAM.
    3. Register an AxiLiteMaster (as a peripheral) into the AddressSpace.
    4. Register an AxiSlave (the DUT's DMA interface) into the AddressSpace.
    5. Use a WindowPool to allocate buffers for DMA transfers, then use those windows to write data that the DUT will later read via its DMA interface.
    from cocotbext.axi import AddressSpace, SparseMemoryRegion, AxiBus, AxiLiteMaster, AxiSlave
    
    # 1. Setup system address space
    address_space = AddressSpace(2**32)
    
    # 2. Setup RAM
    ram = SparseMemoryRegion(2**24)
    address_space.register_region(ram, 0x0000_0000)
    ram_pool = address_space.create_window_pool(0x0000_0000, 2**20)
    
    # 3. Setup DUT Control (AXI Lite)
    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)
    
    # 4. Setup DUT DMA (AXI Slave)
    # The target is the address space, so the DUT can DMA into the RAM
    axi_slave = AxiSlave(AxiBus.from_prefix(dut, "m_axi_dma"), dut.clk, dut.rst, target=address_space)
    
    # 5. Use the abstraction
    src_block = ram_pool.alloc_window(1024)
    await src_block.write(0, b'test data')
  2. Use AXI, AXI Lite, and APB Slaves

    master

    The AxiSlave, AxiLiteSlave, and ApbSlave classes implement slaves that can complete transactions from upstream masters.

    Connection and Target

    Slaves can be connected to a MemoryRegion to perform memory operations on behalf of the DUT, or they can be extended to implement custom logic by overriding _read() and _write().

    from cocotbext.axi import AxiBus, AxiSlave, MemoryRegion
    
    # Connect slave to DUT
    axi_slave = AxiSlave(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst)
    
    # Attach a memory region as the target
    region = MemoryRegion(2**axi_slave.read_if.address_width)
    axi_slave.target = region

    Customization

    To implement custom slave logic (e.g., a register bank), subclass the slave and override the internal _read() and _write() methods.

    from cocotbext.axi import AxiBus, AxiSlave, MemoryRegion
    
    # Connect slave to DUT
    axi_slave = AxiSlave(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst)
    
    # Attach a memory region as the target
    region = MemoryRegion(2**axi_slave.read_if.address_width)
    axi_slave.target = region
  3. Use AXI Stream Source, Sink, and Monitor

    master

    The AXI Stream modules allow you to drive, receive, or passively monitor AXI stream traffic.

    Components

    • AxiStreamSource: Drives all signals except tready. Use this to inject data into a design.
    • AxiStreamSink: Drives only tready. Use this to receive data and exert backpressure.
    • AxiStreamMonitor: Drives no signals. Use this to passively observe traffic.

    Driving Data (Source)

    Use send() or write() with iterables or AxiStreamFrame objects. You can await the completion of a specific frame using an Event in the tx_complete field.

    from cocotbext.axi import AxiStreamBus, AxiStreamSource, AxiStreamFrame
    
    axis_source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "s_axis"), dut.clk, dut.rst)
    
    # Simple send
    await axis_source.send(b'test data')
    
    # Send frame and wait for completion
    frame = AxiStreamFrame(b'test data', tx_complete=Event())
    await axis_source.send(frame)
    await frame.tx_complete.wait()

    Receiving Data (Sink/Monitor)

    Use recv() for frame-oriented interfaces (returns AxiStreamFrame) or read() for non-frame-oriented streams (returns raw bytes).

    # Receive a frame
    frame = await axis_sink.recv()
    
    # Read raw bytes
    data = await axis_sink.read(10)
    from cocotbext.axi import AxiStreamBus, AxiStreamSource, AxiStreamFrame
    
    axis_source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "s_axis"), dut.clk, dut.rst)
    
    # Simple send
    await axis_source.send(b'test data')
    
    # Send frame and wait for completion
    frame = AxiStreamFrame(b'test data', tx_complete=Event())
    await axis_source.send(frame)
    await frame.tx_complete.wait()
  4. Use AXI, AXI Lite, and APB Masters

    master

    The AxiMaster, AxiLiteMaster, and ApbMaster classes allow you to generate read and write operations against AXI, AXI-lite, or APB slaves.

    Connection

    To connect a master to your DUT, use the from_prefix method on the appropriate Bus object to automate signal matching:

    from cocotbext.axi import AxiBus, AxiMaster
    
    # Connects to signals with prefix 's_axi_'
    axi_master = AxiMaster(AxiBus.from_prefix(dut, "s_axi"), dut.clk, dut.rst)

    Performing Operations

    There are two primary ways to initiate transactions:

    1. Async Blocking: Use await with read() or write(). This is the preferred style. Results are returned as namedtuple objects containing _address_, _data_ or _length_, and _resp_.
      await axi_master.write(0x0000, b'test')
      data = await axi_master.read(0x0000, 4)
    2. Non-blocking: Use init_read() or init_write(). These return Event objects. You must await the event to retrieve the result from Event.data.
      write_op = axi_master.init_write(0x0000, b'test')
      await write_op.wait()
      resp = write_op.data

    Key Methods

    • read(address, length, ...) / write(address, data, ...): Standard byte-level access.
    • Word-access wrappers: read_byte, read_word, read_dword, read_qword, and their write counterparts for easier data handling.
    • wait(): Blocks until all outstanding operations complete.
    • idle(): Returns True if no operations are in progress.
    from cocotbext.axi import AxiBus, AxiMaster
    
    # Connects to signals with prefix 's_axi_'
    axi_master = AxiMaster(AxiBus.from_prefix(dut, "s_axi"), dut.clk, dut.rst)
    
    await axi_master.write(0x0000, b'test')
    data = await axi_master.read(0x0000, 4)
  5. Install cocotbext-axi

    master

    You can install cocotbext-axi using pip. Choose the method that matches your needs:

    • Stable release: Use the standard pip installation.
    • Latest development version: Install directly from the GitHub master branch.
    • Active development: Clone the repository and install in editable mode.
    # Install stable release
    $ pip install cocotbext-axi
    
    # Install latest development version
    $ pip install https://github.com/alexforencich/cocotbext-axi/archive/master.zip
    
    # Install for active development
    $ git clone https://github.com/alexforencich/cocotbext-axi
    $ pip install -e cocotbext-axi
  6. Use AXI, AXI Lite, and APB RAM

    master

    The AxiRam, AxiLiteRam, and ApbRam classes are specialized slaves that implement memory. They use SparseMemory internally to support emulating very large memory regions efficiently.

    Connection

    from cocotbext.axi import AxiBus, AxiRam
    
    # Create a 4GB RAM slave
    axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**32)

    Accessing Memory

    You can access memory via the mem attribute (the underlying mmap or SparseMemory object) or via standard master-like API calls:

    axi_ram.write(0x0000, b'test')
    data = axi_ram.read(0x0000, 4)
    # Debugging
    axi_ram.hexdump(0x0000, 4, prefix="RAM")

    Multi-port RAM

    To create a multi-port memory (e.g., for a dual-port RAM), pass the mem attribute of the first instance to subsequent instances:

    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)
    from cocotbext.axi import AxiBus, AxiRam
    
    # Create a 4GB RAM slave
    axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**32)
    
    # Accessing memory
    await axi_ram.write(0x0000, b'test')
    data = await axi_ram.read(0x0000, 4)
    axi_ram.hexdump(0x0000, 4, prefix="RAM")