Add sparse memory model
Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
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")
|
||||||
Reference in New Issue
Block a user