Files
cocotbext-axi/cocotbext/axi/address_space.py
Alex Forencich da00960112 Update copyright dates
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-07 15:28:21 -07:00

363 lines
13 KiB
Python

"""
Copyright (c) 2021-2025 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.
"""
import mmap
from .buddy_allocator import BuddyAllocator
from .sparse_memory import SparseMemory
from .utils import hexdump, hexdump_lines, hexdump_str
class MemoryInterface:
def __init__(self, size, base=0, parent=None, **kwargs):
self._parent = parent
self._size = size
self._base = base
self.window_type = Window
self.window_pool_type = WindowPool
super().__init__(**kwargs)
@property
def parent(self):
return self._parent
@property
def size(self):
return self._size
@property
def base(self):
return self._base
def check_range(self, address, length=0):
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")
def get_absolute_address(self, address):
if self.base is None:
return None
self.check_range(address)
return address+self.base
async def _read(self, address, length, **kwargs):
raise NotImplementedError()
async def read(self, address, length, **kwargs):
self.check_range(address, length)
return await self._read(address, length, **kwargs)
async def read_words(self, address, count, byteorder='little', ws=2, **kwargs):
data = bytes(await self.read(address, count*ws, **kwargs))
words = []
for k in range(count):
words.append(int.from_bytes(data[ws*k:ws*(k+1)], byteorder))
return words
async def read_dwords(self, address, count, byteorder='little', **kwargs):
return await self.read_words(address, count, byteorder, 4, **kwargs)
async def read_qwords(self, address, count, byteorder='little', **kwargs):
return await self.read_words(address, count, byteorder, 8, **kwargs)
async def read_byte(self, address, **kwargs):
return (await self.read(address, 1, **kwargs)).data[0]
async def read_word(self, address, byteorder='little', ws=2, **kwargs):
return (await self.read_words(address, 1, byteorder, ws, **kwargs))[0]
async def read_dword(self, address, byteorder='little', **kwargs):
return (await self.read_dwords(address, 1, byteorder, **kwargs))[0]
async def read_qword(self, address, byteorder='little', **kwargs):
return (await self.read_qwords(address, 1, byteorder, **kwargs))[0]
async def _write(self, address, data, **kwargs):
raise NotImplementedError()
async def write(self, address, data, **kwargs):
self.check_range(address, len(data))
await self._write(address, data, **kwargs)
async def write_words(self, address, data, byteorder='little', ws=2, **kwargs):
words = data
data = bytearray()
for w in words:
data.extend(w.to_bytes(ws, byteorder))
await self.write(address, data, **kwargs)
async def write_dwords(self, address, data, byteorder='little', **kwargs):
await self.write_words(address, data, byteorder, 4, **kwargs)
async def write_qwords(self, address, data, byteorder='little', **kwargs):
await self.write_words(address, data, byteorder, 8, **kwargs)
async def write_byte(self, address, data, **kwargs):
await self.write(address, [data], **kwargs)
async def write_word(self, address, data, byteorder='little', ws=2, **kwargs):
await self.write_words(address, [data], byteorder, ws, **kwargs)
async def write_dword(self, address, data, byteorder='little', **kwargs):
await self.write_dwords(address, [data], byteorder, **kwargs)
async def write_qword(self, address, data, byteorder='little', **kwargs):
await self.write_qwords(address, [data], byteorder, **kwargs)
def create_window(self, offset, size=None, window_type=None):
if not size or size < 0:
size = self.size - offset
window_type = window_type or self.window_type or Window
self.check_range(offset, size)
return window_type(self, offset, size, base=self.get_absolute_address(offset))
def create_window_pool(self, offset=None, size=None, window_pool_type=None, window_type=None):
if offset is None:
offset = 0
if size is None:
size = self.size - offset
window_pool_type = window_pool_type or self.window_pool_type or WindowPool
window_type = window_type or self.window_type
self.check_range(offset, size)
return window_pool_type(self, offset, size, base=self.get_absolute_address(offset), window_type=window_type)
def __len__(self):
return self._size
class Window(MemoryInterface):
def __init__(self, parent, offset, size, base=0, **kwargs):
super().__init__(size, base=base, parent=parent, **kwargs)
self._offset = offset
@property
def offset(self):
return self._offset
def get_parent_address(self, address):
if address < 0 or address >= self.size:
raise ValueError("address out of range")
return address+self.offset
async def _read(self, address, length, **kwargs):
return await self.parent.read(self.get_parent_address(address), length, **kwargs)
async def _write(self, address, data, **kwargs):
await self.parent.write(self.get_parent_address(address), data, **kwargs)
class WindowPool(Window):
def __init__(self, parent, offset, size, base=None, window_type=None, **kwargs):
super().__init__(parent, offset, size, base=base, **kwargs)
self.window_type = window_type or Window
self.allocator = BuddyAllocator(size)
def alloc_window(self, size, window_type=None):
return self.create_window(self.allocator.alloc(size), size, window_type)
class Region(MemoryInterface):
def __init__(self, size, **kwargs):
super().__init__(size, **kwargs)
class MemoryRegion(Region):
def __init__(self, size=4096, mem=None, **kwargs):
super().__init__(size, **kwargs)
if mem is None:
mem = mmap.mmap(-1, size)
self.mem = mem
async def _read(self, address, length, **kwargs):
return self.mem[address:address+length]
async def _write(self, address, data, **kwargs):
self.mem[address:address+len(data)] = data
def hexdump(self, address, length, prefix=""):
hexdump(self.mem[address:address+length], prefix=prefix, offset=address)
def hexdump_lines(self, address, length, prefix=""):
return hexdump_lines(self.mem[address:address+length], prefix=prefix, offset=address)
def hexdump_str(self, address, length, prefix=""):
return hexdump_str(self.mem[address:address+length], prefix=prefix, offset=address)
def __getitem__(self, key):
return self.mem[key]
def __setitem__(self, key, value):
self.mem[key] = value
def __bytes__(self):
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):
def __init__(self, obj, size, **kwargs):
super().__init__(size, **kwargs)
self.obj = obj
async def _read(self, address, length, **kwargs):
try:
return await self.obj.read(address, length, **kwargs)
except TypeError:
return self.obj.read(address, length, **kwargs)
async def _write(self, address, data, **kwargs):
try:
await self.obj.write(address, data, **kwargs)
except TypeError:
self.obj.write(address, data, **kwargs)
class AddressSpace(Region):
def __init__(self, size=2**64, base=0, parent=None, **kwargs):
super().__init__(size=size, base=base, parent=parent, **kwargs)
self.pool_type = Pool
self.regions = []
def find_regions(self, address, length=1):
regions = []
if address < 0 or address >= self.size:
raise ValueError("address out of range")
if length < 0:
raise ValueError("invalid length")
length = max(length, 1)
for (base, size, translate, region) in self.regions:
if address < base+size and base < address+length:
regions.append((base, size, translate, region))
regions.sort()
return regions
def register_region(self, region, base, size=None, offset=0):
if size is None:
size = region.size
if self.find_regions(base, size):
raise ValueError("overlaps existing region")
region._parent = self
if offset == 0:
region._base = self.get_absolute_address(base)
else:
region._base = None
self.regions.append((base, size, offset, region))
async def read(self, address, length, **kwargs):
regions = self.find_regions(address, length)
data = bytearray()
if not regions:
raise Exception("Invalid address")
for base, size, offset, region in regions:
if base > address:
raise Exception("Invalid address")
seg_addr = address - base
seg_len = min(size-seg_addr, length)
if offset is None:
seg_addr = address
offset = 0
data.extend(bytes(await region.read(seg_addr+offset, seg_len, **kwargs)))
address += seg_len
length -= seg_len
if length > 0:
raise Exception("Invalid address")
return bytes(data)
async def write(self, address, data, **kwargs):
start = 0
length = len(data)
regions = self.find_regions(address, length)
if not regions:
raise Exception("Invalid address")
for base, size, offset, region in regions:
if base > address:
raise Exception("Invalid address")
seg_addr = address - base
seg_len = min(size-seg_addr, length)
if offset is None:
seg_addr = address
offset = 0
await region.write(seg_addr+offset, data[start:start+seg_len], **kwargs)
address += seg_len
start += seg_len
length -= seg_len
if length > 0:
raise Exception("Invalid address")
def create_pool(self, base=None, size=None, pool_type=None, region_type=None):
if base is None:
base = 0
if size is None:
size = self.size - base
pool_type = pool_type or self.pool_type or Pool
self.check_range(base, size)
pool = pool_type(self, base, size, region_type=region_type)
self.register_region(pool, base, size)
return pool
class Pool(AddressSpace):
def __init__(self, parent, base, size, region_type=None, **kwargs):
super().__init__(parent=parent, base=base, size=size, **kwargs)
self.region_type = region_type or MemoryRegion
self.allocator = BuddyAllocator(size)
def alloc_region(self, size, region_type=None):
region_type = region_type or self.region_type or MemoryRegion
base = self.allocator.alloc(size)
region = region_type(size)
self.register_region(region, base)
return region