Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50cf2af49f | ||
|
|
ddfa1e3c92 | ||
|
|
5e8b246159 | ||
|
|
62c2eef4ec | ||
|
|
ad6012aea5 | ||
|
|
432bd81011 | ||
|
|
bde123e05f | ||
|
|
8604017159 | ||
|
|
e21b9ffcc8 | ||
|
|
47cd74eb6c | ||
|
|
4bf5945aa3 |
2
.github/workflows/regression-tests.yml
vendored
2
.github/workflows/regression-tests.yml
vendored
@@ -5,7 +5,7 @@ on: [push, pull_request]
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Python ${{matrix.python-version}}
|
name: Python ${{matrix.python-version}}
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -155,7 +155,7 @@ It is also possible to extend these modules; operation can be customized by over
|
|||||||
|
|
||||||
### AXI and AXI lite RAM
|
### AXI and AXI lite RAM
|
||||||
|
|
||||||
The `AxiRam` and `AxiLiteRam` classes implement AXI RAMs and are capable of completing read and write operations from upstream AXI masters. The `AxiRam` module is capable of handling narrow bursts. These modules are extensions of the corresponding `AxiSlave` and `AxiLiteSlave` modules.
|
The `AxiRam` and `AxiLiteRam` classes implement AXI RAMs and are capable of completing read and write operations from upstream AXI masters. The `AxiRam` module is capable of handling narrow bursts. These modules are extensions of the corresponding `AxiSlave` and `AxiLiteSlave` modules. Internally, `SparseMemory` is used to support emulating very large memories.
|
||||||
|
|
||||||
The `AxiRam` is a wrapper around `AxiRamWrite` and `AxiRamRead`. Similarly, `AxiLiteRam` is a wrapper around `AxiLiteRamWrite` and `AxiLiteRamRead`. 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.
|
The `AxiRam` is a wrapper around `AxiRamWrite` and `AxiRamRead`. Similarly, `AxiLiteRam` is a wrapper around `AxiLiteRamWrite` and `AxiLiteRamRead`. 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.
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ To use these modules, import the one you need and connect it to the DUT:
|
|||||||
|
|
||||||
from cocotbext.axi import AxiBus, AxiRam
|
from cocotbext.axi import AxiBus, AxiRam
|
||||||
|
|
||||||
axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**16)
|
axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**32)
|
||||||
|
|
||||||
The first argument to the constructor accepts an `AxiBus` or `AxiLiteBus` object. These objects are containers for the interface signals and include class methods to automate connections.
|
The first argument to the constructor accepts an `AxiBus` or `AxiLiteBus` object. These objects are containers for the interface signals and include class methods to automate connections.
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ Once the module is instantiated, the memory contents can be accessed in a couple
|
|||||||
|
|
||||||
Multi-port memories can be constructed by passing the `mem` object of the first instance to the other instances. For example, here is how to create a four-port RAM:
|
Multi-port memories can be constructed by passing the `mem` object 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**16)
|
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_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_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)
|
axi_ram_p4 = AxiRam(AxiBus.from_prefix(dut, "m03_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||||
@@ -186,12 +186,12 @@ Multi-port memories can be constructed by passing the `mem` object of the first
|
|||||||
* _clock_: clock signal
|
* _clock_: clock signal
|
||||||
* _reset_: reset signal (optional)
|
* _reset_: reset signal (optional)
|
||||||
* _reset_active_level_: reset active level (optional, default `True`)
|
* _reset_active_level_: reset active level (optional, default `True`)
|
||||||
* _size_: memory size in bytes (optional, default 1024)
|
* _size_: memory size in bytes (optional, default `2**64`)
|
||||||
* _mem_: mmap object to use (optional, overrides _size_)
|
* _mem_: `mmap` or `SparseMemory` backing object to use (optional, overrides _size_)
|
||||||
|
|
||||||
#### Attributes:
|
#### Attributes:
|
||||||
|
|
||||||
* _mem_: directly access shared `mmap` object
|
* _mem_: directly access shared `mmap` or `SparseMemory` backing object
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
@@ -334,6 +334,8 @@ The address space abstraction provides a framework for cross-connecting multiple
|
|||||||
|
|
||||||
`MemoryRegion` is an extension of `Region` that uses an `mmap` instance to handle memory operations. `MemoryRegion` also provides hex dump methods as well as indexing and slicing.
|
`MemoryRegion` is an extension of `Region` that uses an `mmap` instance to handle memory operations. `MemoryRegion` also provides hex dump methods as well as indexing and slicing.
|
||||||
|
|
||||||
|
`SparseMemoryRegion` is similar to `MemoryRegion` but is backed by `SparseMemory` instead of `mmap` and as such can emulate extremely large regions of address space.
|
||||||
|
|
||||||
`PeripheralRegion` is an extension of `Region` that can wrap another object that implements `read()` and `write()`, as an alternative to extending `Region`.
|
`PeripheralRegion` is an extension of `Region` that can wrap another object that implements `read()` and `write()`, as an alternative to extending `Region`.
|
||||||
|
|
||||||
`AddressSpace` is the core object for handling address spaces. `Region` objects can be registered with `AddressSpace` with specified base address, size, and offset. The `AddressSpace` object will then direct `read()` and `write()` operations to the appropriate `Region`s, splitting requests appropriately when necessary and translating addresses. Regions registered with `offset` other than `None` are translated such that accesses to base address + N map to N + offset. Regions registered with an `offset` of `None` are not translated. `Region` objects registered with the same `AddressSpace` cannot overlap, however the same `Region` can be registered multiple times. `AddressSpace` also provides a method for creating `Pool` objects.
|
`AddressSpace` is the core object for handling address spaces. `Region` objects can be registered with `AddressSpace` with specified base address, size, and offset. The `AddressSpace` object will then direct `read()` and `write()` operations to the appropriate `Region`s, splitting requests appropriately when necessary and translating addresses. Regions registered with `offset` other than `None` are translated such that accesses to base address + N map to N + offset. Regions registered with an `offset` of `None` are not translated. `Region` objects registered with the same `AddressSpace` cannot overlap, however the same `Region` can be registered multiple times. `AddressSpace` also provides a method for creating `Pool` objects.
|
||||||
@@ -344,14 +346,14 @@ The address space abstraction provides a framework for cross-connecting multiple
|
|||||||
|
|
||||||
This 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.
|
This 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, MemoryRegion
|
from cocotbext.axi import AddressSpace, SparseMemoryRegion
|
||||||
from cocotbext.axi import AxiBus, AxiLiteMaster, AxiSlave
|
from cocotbext.axi import AxiBus, AxiLiteMaster, AxiSlave
|
||||||
|
|
||||||
# system address space
|
# system address space
|
||||||
address_space = AddressSpace(2**32)
|
address_space = AddressSpace(2**32)
|
||||||
|
|
||||||
# RAM
|
# RAM
|
||||||
ram = MemoryRegion(2**24)
|
ram = SparseMemoryRegion(2**24)
|
||||||
address_space.register_region(ram, 0x0000_0000)
|
address_space.register_region(ram, 0x0000_0000)
|
||||||
ram_pool = address_space.create_window_pool(0x0000_0000, 2**20)
|
ram_pool = address_space.create_window_pool(0x0000_0000, 2**20)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from .version import __version__
|
|||||||
from .constants import AxiBurstType, AxiBurstSize, AxiLockType, AxiCacheBit, AxiProt, AxiResp
|
from .constants import AxiBurstType, AxiBurstSize, AxiLockType, AxiCacheBit, AxiProt, AxiResp
|
||||||
|
|
||||||
from .address_space import MemoryInterface, Window, WindowPool
|
from .address_space import MemoryInterface, Window, WindowPool
|
||||||
from .address_space import Region, MemoryRegion, PeripheralRegion
|
from .address_space import Region, MemoryRegion, SparseMemoryRegion, PeripheralRegion
|
||||||
from .address_space import AddressSpace, Pool
|
from .address_space import AddressSpace, Pool
|
||||||
|
|
||||||
from .axis import AxiStreamFrame, AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
from .axis import AxiStreamFrame, AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ THE SOFTWARE.
|
|||||||
import mmap
|
import mmap
|
||||||
|
|
||||||
from .buddy_allocator import BuddyAllocator
|
from .buddy_allocator import BuddyAllocator
|
||||||
|
from .sparse_memory import SparseMemory
|
||||||
from .utils import hexdump, hexdump_lines, hexdump_str
|
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||||
|
|
||||||
|
|
||||||
@@ -216,6 +217,35 @@ class MemoryRegion(Region):
|
|||||||
return bytes(self.mem)
|
return bytes(self.mem)
|
||||||
|
|
||||||
|
|
||||||
|
class SparseMemoryRegion(Region):
|
||||||
|
def __init__(self, size=2**64, mem=None, **kwargs):
|
||||||
|
super().__init__(size, **kwargs)
|
||||||
|
if mem is None:
|
||||||
|
mem = SparseMemory(size)
|
||||||
|
self.mem = mem
|
||||||
|
|
||||||
|
async def _read(self, address, length, **kwargs):
|
||||||
|
return self.mem.read(address, length)
|
||||||
|
|
||||||
|
async def _write(self, address, data, **kwargs):
|
||||||
|
self.mem.write(address, data)
|
||||||
|
|
||||||
|
def hexdump(self, address, length, prefix=""):
|
||||||
|
self.mem.hexdump(address, length, prefix=prefix)
|
||||||
|
|
||||||
|
def hexdump_lines(self, address, length, prefix=""):
|
||||||
|
return self.mem.hexdump_lines(address, length, prefix=prefix)
|
||||||
|
|
||||||
|
def hexdump_str(self, address, length, prefix=""):
|
||||||
|
return self.mem.hexdump_str(address, length, prefix=prefix)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.mem[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.mem[key] = value
|
||||||
|
|
||||||
|
|
||||||
class PeripheralRegion(Region):
|
class PeripheralRegion(Region):
|
||||||
def __init__(self, obj, size, **kwargs):
|
def __init__(self, obj, size, **kwargs):
|
||||||
super().__init__(size, **kwargs)
|
super().__init__(size, **kwargs)
|
||||||
|
|||||||
@@ -291,6 +291,9 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
if isinstance(data, int):
|
if isinstance(data, int):
|
||||||
raise ValueError("Expected bytes or bytearray for data")
|
raise ValueError("Expected bytes or bytearray for data")
|
||||||
|
|
||||||
|
if burst != AxiBurstType.FIXED and address+len(data) >= 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if awid is None or awid < 0:
|
if awid is None or awid < 0:
|
||||||
awid = None
|
awid = None
|
||||||
elif awid > self.id_count:
|
elif awid > self.id_count:
|
||||||
@@ -357,6 +360,9 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
if isinstance(data, int):
|
if isinstance(data, int):
|
||||||
raise ValueError("Expected bytes or bytearray for data")
|
raise ValueError("Expected bytes or bytearray for data")
|
||||||
|
|
||||||
|
if burst != AxiBurstType.FIXED and address+len(data) >= 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if awid is None or awid < 0:
|
if awid is None or awid < 0:
|
||||||
awid = None
|
awid = None
|
||||||
elif awid > self.id_count:
|
elif awid > self.id_count:
|
||||||
@@ -563,7 +569,9 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
|
|
||||||
await self.w_channel.send(w)
|
await self.w_channel.send(w)
|
||||||
|
|
||||||
if k == 0:
|
if cmd.burst == AxiBurstType.FIXED:
|
||||||
|
cur_addr = cmd.address
|
||||||
|
elif k == 0:
|
||||||
cur_addr = aligned_addr + num_bytes
|
cur_addr = aligned_addr + num_bytes
|
||||||
else:
|
else:
|
||||||
cur_addr += num_bytes
|
cur_addr += num_bytes
|
||||||
@@ -715,6 +723,9 @@ class AxiMasterRead(Region, Reset):
|
|||||||
if length < 0:
|
if length < 0:
|
||||||
raise ValueError("Read length must be positive")
|
raise ValueError("Read length must be positive")
|
||||||
|
|
||||||
|
if burst != AxiBurstType.FIXED and address+length >= 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if arid is None or arid < 0:
|
if arid is None or arid < 0:
|
||||||
arid = None
|
arid = None
|
||||||
elif arid > self.id_count:
|
elif arid > self.id_count:
|
||||||
@@ -769,6 +780,9 @@ class AxiMasterRead(Region, Reset):
|
|||||||
if length < 0:
|
if length < 0:
|
||||||
raise ValueError("Read length must be positive")
|
raise ValueError("Read length must be positive")
|
||||||
|
|
||||||
|
if burst != AxiBurstType.FIXED and address+length >= 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if arid is None or arid < 0:
|
if arid is None or arid < 0:
|
||||||
arid = None
|
arid = None
|
||||||
elif arid > self.id_count:
|
elif arid > self.id_count:
|
||||||
@@ -918,7 +932,9 @@ class AxiMasterRead(Region, Reset):
|
|||||||
self.log.info("Read burst start arid: 0x%x araddr: 0x%08x arlen: %d arsize: %d arprot: %s",
|
self.log.info("Read burst start arid: 0x%x araddr: 0x%08x arlen: %d arsize: %d arprot: %s",
|
||||||
arid, cur_addr, burst_length-1, cmd.size, cmd.prot)
|
arid, cur_addr, burst_length-1, cmd.size, cmd.prot)
|
||||||
|
|
||||||
if k == 0:
|
if cmd.burst == AxiBurstType.FIXED:
|
||||||
|
cur_addr = cmd.address
|
||||||
|
elif k == 0:
|
||||||
cur_addr = aligned_addr + num_bytes
|
cur_addr = aligned_addr + num_bytes
|
||||||
else:
|
else:
|
||||||
cur_addr += num_bytes
|
cur_addr += num_bytes
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from .memory import Memory
|
|||||||
|
|
||||||
|
|
||||||
class AxiRamWrite(AxiSlaveWrite, Memory):
|
class AxiRamWrite(AxiSlaveWrite, Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
||||||
|
|
||||||
async def _write(self, address, data):
|
async def _write(self, address, data):
|
||||||
@@ -35,7 +35,7 @@ class AxiRamWrite(AxiSlaveWrite, Memory):
|
|||||||
|
|
||||||
|
|
||||||
class AxiRamRead(AxiSlaveRead, Memory):
|
class AxiRamRead(AxiSlaveRead, Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
||||||
|
|
||||||
async def _read(self, address, length):
|
async def _read(self, address, length):
|
||||||
@@ -43,7 +43,7 @@ class AxiRamRead(AxiSlaveRead, Memory):
|
|||||||
|
|
||||||
|
|
||||||
class AxiRam(Memory):
|
class AxiRam(Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
self.write_if = None
|
self.write_if = None
|
||||||
self.read_if = None
|
self.read_if = None
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,9 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
if isinstance(data, int):
|
if isinstance(data, int):
|
||||||
raise ValueError("Expected bytes or bytearray for data")
|
raise ValueError("Expected bytes or bytearray for data")
|
||||||
|
|
||||||
|
if address+len(data) >= 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if not self.awprot_present and prot != AxiProt.NONSECURE:
|
if not self.awprot_present and prot != AxiProt.NONSECURE:
|
||||||
raise ValueError("awprot sideband signal value specified, but signal is not connected")
|
raise ValueError("awprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
@@ -419,6 +422,12 @@ class AxiLiteMasterRead(Region, Reset):
|
|||||||
if address < 0 or address >= 2**self.address_width:
|
if address < 0 or address >= 2**self.address_width:
|
||||||
raise ValueError("Address out of range")
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if length < 0:
|
||||||
|
raise ValueError("Read length must be positive")
|
||||||
|
|
||||||
|
if address+length >= 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if not self.arprot_present and prot != AxiProt.NONSECURE:
|
if not self.arprot_present and prot != AxiProt.NONSECURE:
|
||||||
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from .memory import Memory
|
|||||||
|
|
||||||
|
|
||||||
class AxiLiteRamWrite(AxiLiteSlaveWrite, Memory):
|
class AxiLiteRamWrite(AxiLiteSlaveWrite, Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
||||||
|
|
||||||
async def _write(self, address, data):
|
async def _write(self, address, data):
|
||||||
@@ -35,7 +35,7 @@ class AxiLiteRamWrite(AxiLiteSlaveWrite, Memory):
|
|||||||
|
|
||||||
|
|
||||||
class AxiLiteRamRead(AxiLiteSlaveRead, Memory):
|
class AxiLiteRamRead(AxiLiteSlaveRead, Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
||||||
|
|
||||||
async def _read(self, address, length):
|
async def _read(self, address, length):
|
||||||
@@ -43,7 +43,7 @@ class AxiLiteRamRead(AxiLiteSlaveRead, Memory):
|
|||||||
|
|
||||||
|
|
||||||
class AxiLiteRam(Memory):
|
class AxiLiteRam(Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
self.write_if = None
|
self.write_if = None
|
||||||
self.read_if = None
|
self.read_if = None
|
||||||
|
|
||||||
|
|||||||
@@ -22,27 +22,24 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import mmap
|
from .sparse_memory import SparseMemory
|
||||||
|
|
||||||
from .utils import hexdump, hexdump_lines, hexdump_str
|
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||||
|
|
||||||
|
|
||||||
class Memory:
|
class Memory:
|
||||||
def __init__(self, size=1024, mem=None, **kwargs):
|
def __init__(self, size=2**64, mem=None, **kwargs):
|
||||||
if mem is not None:
|
if mem is not None:
|
||||||
self.mem = mem
|
self.mem = mem
|
||||||
else:
|
else:
|
||||||
self.mem = mmap.mmap(-1, size)
|
self.mem = SparseMemory(size)
|
||||||
self.size = len(self.mem)
|
self.size = len(self.mem)
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def read(self, address, length):
|
def read(self, address, length):
|
||||||
self.mem.seek(address)
|
return self.mem[address:address+length]
|
||||||
return self.mem.read(length)
|
|
||||||
|
|
||||||
def write(self, address, data):
|
def write(self, address, data):
|
||||||
self.mem.seek(address)
|
self.mem[address:address+len(data)] = data
|
||||||
self.mem.write(bytes(data))
|
|
||||||
|
|
||||||
def write_words(self, address, data, byteorder='little', ws=2):
|
def write_words(self, address, data, byteorder='little', ws=2):
|
||||||
words = data
|
words = data
|
||||||
|
|||||||
111
cocotbext/axi/sparse_memory.py
Normal file
111
cocotbext/axi/sparse_memory.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2023 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||||
|
|
||||||
|
|
||||||
|
class SparseMemory:
|
||||||
|
def __init__(self, size):
|
||||||
|
self.size = size
|
||||||
|
self.segs = {}
|
||||||
|
|
||||||
|
def read(self, address, length, **kwargs):
|
||||||
|
if address < 0 or address >= self.size:
|
||||||
|
raise ValueError("address out of range")
|
||||||
|
if length < 0:
|
||||||
|
raise ValueError("invalid length")
|
||||||
|
if address+length > self.size:
|
||||||
|
raise ValueError("operation out of range")
|
||||||
|
data = bytearray()
|
||||||
|
while length > 0:
|
||||||
|
block_offset = address & 0xfff
|
||||||
|
block_addr = address - block_offset
|
||||||
|
block_len = min(4096 - block_offset, length)
|
||||||
|
try:
|
||||||
|
block = self.segs[block_addr]
|
||||||
|
except KeyError:
|
||||||
|
block = b'\x00'*4096
|
||||||
|
data.extend(block[block_offset:block_offset+block_len])
|
||||||
|
address += block_len
|
||||||
|
length -= block_len
|
||||||
|
return bytes(data)
|
||||||
|
|
||||||
|
def write(self, address, data, **kwargs):
|
||||||
|
if address < 0 or address >= self.size:
|
||||||
|
raise ValueError("address out of range")
|
||||||
|
if address+len(data) > self.size:
|
||||||
|
raise ValueError("operation out of range")
|
||||||
|
offset = 0
|
||||||
|
length = len(data)
|
||||||
|
while length > 0:
|
||||||
|
block_offset = address & 0xfff
|
||||||
|
block_addr = address - block_offset
|
||||||
|
block_len = min(4096 - block_offset, length)
|
||||||
|
try:
|
||||||
|
block = self.segs[block_addr]
|
||||||
|
except KeyError:
|
||||||
|
block = bytearray(4096)
|
||||||
|
self.segs[block_addr] = block
|
||||||
|
block[block_offset:block_offset+block_len] = data[offset:offset+block_len]
|
||||||
|
address += block_len
|
||||||
|
offset += block_len
|
||||||
|
length -= block_len
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.segs.clear()
|
||||||
|
|
||||||
|
def hexdump(self, address, length, prefix=""):
|
||||||
|
hexdump(self.read(address, length), prefix=prefix, offset=address)
|
||||||
|
|
||||||
|
def hexdump_lines(self, address, length, prefix=""):
|
||||||
|
return hexdump_lines(self.read(address, length), prefix=prefix, offset=address)
|
||||||
|
|
||||||
|
def hexdump_str(self, address, length, prefix=""):
|
||||||
|
return hexdump_str(self.read(address, length), prefix=prefix, offset=address)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.size
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, int):
|
||||||
|
return self.read(key, 1)[0]
|
||||||
|
elif isinstance(key, slice):
|
||||||
|
start, stop, step = key.indices(self.size)
|
||||||
|
if step == 1:
|
||||||
|
return self.read(start, stop-start)
|
||||||
|
else:
|
||||||
|
raise IndexError("specified step size is not supported")
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if isinstance(key, int):
|
||||||
|
self.write(key, [value])
|
||||||
|
elif isinstance(key, slice):
|
||||||
|
start, stop, step = key.indices(self.size)
|
||||||
|
if step == 1:
|
||||||
|
value = bytes(value)
|
||||||
|
if stop-start != len(value):
|
||||||
|
raise IndexError("slice assignment is wrong size")
|
||||||
|
return self.write(start, value)
|
||||||
|
else:
|
||||||
|
raise IndexError("specified step size is not supported")
|
||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.20"
|
__version__ = "0.1.22"
|
||||||
|
|||||||
@@ -45,15 +45,7 @@ export PARAM_RUSER_WIDTH ?= 1
|
|||||||
ifeq ($(SIM), icarus)
|
ifeq ($(SIM), icarus)
|
||||||
PLUSARGS += -fst
|
PLUSARGS += -fst
|
||||||
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).STRB_WIDTH=$(PARAM_STRB_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ID_WIDTH=$(PARAM_ID_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).AWUSER_WIDTH=$(PARAM_AWUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).WUSER_WIDTH=$(PARAM_WUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).BUSER_WIDTH=$(PARAM_BUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ARUSER_WIDTH=$(PARAM_ARUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).RUSER_WIDTH=$(PARAM_RUSER_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
ifeq ($(WAVES), 1)
|
||||||
VERILOG_SOURCES += iverilog_dump.v
|
VERILOG_SOURCES += iverilog_dump.v
|
||||||
@@ -62,15 +54,7 @@ ifeq ($(SIM), icarus)
|
|||||||
else ifeq ($(SIM), verilator)
|
else ifeq ($(SIM), verilator)
|
||||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
|
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
|
||||||
|
|
||||||
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||||
COMPILE_ARGS += -GADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
|
||||||
COMPILE_ARGS += -GSTRB_WIDTH=$(PARAM_STRB_WIDTH)
|
|
||||||
COMPILE_ARGS += -GID_WIDTH=$(PARAM_ID_WIDTH)
|
|
||||||
COMPILE_ARGS += -GAWUSER_WIDTH=$(PARAM_AWUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -GWUSER_WIDTH=$(PARAM_WUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -GBUSER_WIDTH=$(PARAM_BUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -GARUSER_WIDTH=$(PARAM_ARUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -GRUSER_WIDTH=$(PARAM_RUSER_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
ifeq ($(WAVES), 1)
|
||||||
COMPILE_ARGS += --trace-fst
|
COMPILE_ARGS += --trace-fst
|
||||||
|
|||||||
@@ -39,9 +39,7 @@ export PARAM_STRB_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
|||||||
ifeq ($(SIM), icarus)
|
ifeq ($(SIM), icarus)
|
||||||
PLUSARGS += -fst
|
PLUSARGS += -fst
|
||||||
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).STRB_WIDTH=$(PARAM_STRB_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
ifeq ($(WAVES), 1)
|
||||||
VERILOG_SOURCES += iverilog_dump.v
|
VERILOG_SOURCES += iverilog_dump.v
|
||||||
@@ -50,9 +48,7 @@ ifeq ($(SIM), icarus)
|
|||||||
else ifeq ($(SIM), verilator)
|
else ifeq ($(SIM), verilator)
|
||||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||||
|
|
||||||
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||||
COMPILE_ARGS += -GADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
|
||||||
COMPILE_ARGS += -GSTRB_WIDTH=$(PARAM_STRB_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
ifeq ($(WAVES), 1)
|
||||||
COMPILE_ARGS += --trace-fst
|
COMPILE_ARGS += --trace-fst
|
||||||
|
|||||||
@@ -41,11 +41,7 @@ export PARAM_USER_WIDTH ?= 1
|
|||||||
ifeq ($(SIM), icarus)
|
ifeq ($(SIM), icarus)
|
||||||
PLUSARGS += -fst
|
PLUSARGS += -fst
|
||||||
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).KEEP_WIDTH=$(PARAM_KEEP_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ID_WIDTH=$(PARAM_ID_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).DEST_WIDTH=$(PARAM_DEST_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).USER_WIDTH=$(PARAM_USER_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
ifeq ($(WAVES), 1)
|
||||||
VERILOG_SOURCES += iverilog_dump.v
|
VERILOG_SOURCES += iverilog_dump.v
|
||||||
@@ -54,11 +50,7 @@ ifeq ($(SIM), icarus)
|
|||||||
else ifeq ($(SIM), verilator)
|
else ifeq ($(SIM), verilator)
|
||||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||||
|
|
||||||
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||||
COMPILE_ARGS += -GKEEP_WIDTH=$(PARAM_KEEP_WIDTH)
|
|
||||||
COMPILE_ARGS += -GID_WIDTH=$(PARAM_ID_WIDTH)
|
|
||||||
COMPILE_ARGS += -GDEST_WIDTH=$(PARAM_DEST_WIDTH)
|
|
||||||
COMPILE_ARGS += -GUSER_WIDTH=$(PARAM_USER_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
ifeq ($(WAVES), 1)
|
||||||
COMPILE_ARGS += --trace-fst
|
COMPILE_ARGS += --trace-fst
|
||||||
|
|||||||
Reference in New Issue
Block a user