from math import ceil
from typing import Optional, Union
from hwt.hdl.transTmpl import TransTmpl
from hwt.hdl.types.array import HArray
from hwt.hdl.types.bits import HBits
from hwt.hdl.types.bitsConst import HBitsConst
from hwt.hdl.types.struct import HStruct
from hwt.math import shiftIntArray
from hwt.pyUtils.arrayQuery import grouper
from pyMathBitPrecise.bit_utils import mask, get_bit_range, int_list_to_int
[docs]
class AllocationError(Exception):
"""
Exception which says that requested allocation can not be performed
"""
pass
[docs]
def reshapedInitItems(actualCellSize, requestedCellSize, values):
"""
Convert array item size and items cnt while size of array remains unchanged
:param actualCellSize: actual size of item in array
:param requestedCellSize: requested size of item in array
:param values: input array
:return: generator of new items of specified characteristic
"""
if (actualCellSize < requestedCellSize
and requestedCellSize % actualCellSize == 0):
itemsInCell = requestedCellSize // actualCellSize
for itemsInWord in grouper(itemsInCell, values, padvalue=0):
yield int_list_to_int(itemsInWord, actualCellSize * 8)
else:
raise NotImplementedError(
f"Reshaping of array from cell size {actualCellSize:d} to {requestedCellSize:d}")
[docs]
class SimRam():
"""
Dense memory for simulation purposes with data pump interfaces
:ivar ~.data: memory dict
"""
[docs]
def __init__(self, cellSize:int, parent=None):
"""
:param cellSize: specifies the number of bytes of word
(byte is unit of the addres and it does not have to be 8b)
:param clk: clk signal for synchronization
:param parent: parent instance of SimRam
(memory will be shared with this instance)
"""
self.parent = parent
if parent is None:
self.data: dict[int, Union[int, HBitsConst]] = {}
else:
self.data: dict[int, Union[int, HBitsConst]] = parent.data
self.cellSize = cellSize
self.prevAllocatedAddrEnd = 0
[docs]
def malloc(self, size, keepOut=None):
"""
Allocates a block of memory of size and initialize it
with None (invalid value)
:param size: Size of memory block to allocate.
:param keepOut: optional memory spacing between this memory region
and lastly allocated
:return: address of allocated memory
"""
addr = self.prevAllocatedAddrEnd
k = self.data.keys()
if k:
addr = (max(k) + 1) * self.cellSize
if keepOut:
addr += keepOut
indx = addr // self.cellSize
if indx * self.cellSize != addr:
NotImplementedError(
f"unaligned allocations not implemented (0x{addr:x})")
d = self.data
for i in range(size // self.cellSize):
tmp = indx + i
if tmp in d.keys():
raise AllocationError(
"Address 0x%x is already occupied" % (tmp * self.cellSize))
d[tmp] = None
self.prevAllocatedAddrEnd = addr
return addr
[docs]
def calloc(self, num, size, keepOut=None, initValues=None) -> int:
"""
Allocates a block of memory for an array of num elements, each of them
size bytes long, and initializes all its bits to zero.
:param num: Number of elements to allocate.
:param size: Size of each element.
:param keepOut: optional memory spacing between this memory region
and lastly allocated (number of bit between last allocated segment to avoid)
:param initValues: iterable of word values to init memory with
:return: address (byte step) of allocated memory
"""
addr = self.prevAllocatedAddrEnd
k = self.data.keys()
if k:
addr = (max(k) + 1) * self.cellSize
if keepOut:
addr += keepOut
indx = addr // self.cellSize
shift = addr % self.cellSize
wordCnt = ceil((num * size) / self.cellSize)
if shift:
# shift all data in init values
initValues = shiftIntArray(initValues, self.cellSize * 8, shift * 8)
wordCnt += 1
if initValues is not None:
if size != self.cellSize:
initValues = list(reshapedInitItems(
size, self.cellSize, initValues))
assert len(initValues) == wordCnt, (len(initValues), wordCnt)
d = self.data
for i in range(wordCnt):
tmp = indx + i
if tmp in d.keys():
raise AllocationError(
"Address 0x%x is already occupied" % (tmp * self.cellSize))
if initValues is None:
d[tmp] = 0
else:
d[tmp] = initValues[i]
self.prevAllocatedAddrEnd = (indx + wordCnt) * self.cellSize
return addr
[docs]
def getArray(self, addr: int, item_size: int, item_cnt: int):
"""
Get array stored in memory
"""
baseIndex = addr // self.cellSize
if item_size != self.cellSize or baseIndex * self.cellSize != addr:
return self._getArray(addr * 8, TransTmpl(HBits(item_size * 8)[item_cnt]))
else:
out = []
for i in range(baseIndex, baseIndex + item_cnt):
try:
v = self.data[i]
except KeyError:
v = None
out.append(v)
return out
[docs]
def getBits(self, start: int, end: int, sign:Optional[bool]) -> HBitsConst:
"""
Gets value of bits between selected range from memory
:param start: bit address of start of bit of bits
:param end: bit address of first bit behind bits
:return: instance of BitsVal (derived from SimBits type)
which contains copy of selected bits
"""
wordWidth = self.cellSize * 8
inFieldOffset = 0
allMask = mask(wordWidth)
value = HBits(end - start, signed=sign).from_py(None)
while start != end:
assert start < end, (start, end)
dataWordIndex = start // wordWidth
v = self.data.get(dataWordIndex, None)
endOfWord = (dataWordIndex + 1) * wordWidth
width = min(end, endOfWord) - start
offset = start % wordWidth
if v is None:
val = 0
vld_mask = 0
elif isinstance(v, int):
val = get_bit_range(v, offset, width)
vld_mask = allMask
else:
val = get_bit_range(v.val, offset, width)
vld_mask = get_bit_range(v.vld_mask, offset, width)
m = mask(width)
value.val |= (val & m) << inFieldOffset
value.vld_mask |= (vld_mask & m) << inFieldOffset
inFieldOffset += width
start += width
return value
[docs]
def _getArray(self, offset, transTmpl):
"""
:param offset: global offset of this transTmpl (and struct)
:param transTmpl: instance of TransTmpl which specifies items in array
"""
t = transTmpl.dtype
c = transTmpl.children
arr_offset = offset
item_width = c.bitAddrEnd - c.bitAddr
value = []
if not isinstance(t.element_t, HBits):
raise NotImplementedError(t.element_t)
for _ in range(t.size):
v = self.getBits(arr_offset, arr_offset + item_width, None)
value.append(v)
arr_offset += item_width
return value
[docs]
def _getStruct(self, offset, transTmpl):
"""
:param offset: global offset of this transTmpl (and struct)
:param transTmpl: instance of TransTmpl which specifies items in struct
"""
dataDict = {}
for subTmpl in transTmpl.children:
t = subTmpl.dtype
name = subTmpl.origin[-1].name
if isinstance(t, HBits):
value = self.getBits(
subTmpl.bitAddr + offset, subTmpl.bitAddrEnd + offset, t.signed)
elif isinstance(t, HArray):
value = self._getArray(subTmpl.bitAddr + offset, subTmpl)
elif isinstance(t, HStruct):
value = self._getStruct(offset, subTmpl)
else:
raise NotImplementedError(t)
dataDict[name] = value
return transTmpl.dtype.from_py(dataDict)
[docs]
def getStruct(self, addr, structT, bitAddr=None):
"""
Get HStruct from memory
:param addr: address where get struct from
:param structT: instance of HStruct or FrameTmpl generated
from it to resolve structure of data
:param bitAddr: optional bit precise address is is not None
param addr has to be None
"""
if bitAddr is None:
assert bitAddr is None
bitAddr = addr * 8
else:
assert addr is not None
if isinstance(structT, TransTmpl):
transTmpl = structT
structT = transTmpl.origin
else:
assert isinstance(structT, HStruct)
transTmpl = TransTmpl(structT)
return self._getStruct(bitAddr, transTmpl)