hdl._mem: add MemoryData
class.
This implements half of RFC 62. The `MemoryData._Row` class will be implemented later, as a follow-up.
This commit is contained in:
parent
1deaf70ad9
commit
09d5540430
|
@ -7,7 +7,7 @@ from ._dsl import SyntaxError, SyntaxWarning, Module
|
|||
from ._cd import DomainError, ClockDomain
|
||||
from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment
|
||||
from ._ir import Instance, IOBufferInstance
|
||||
from ._mem import MemoryIdentity, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
|
||||
from ._mem import MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
|
||||
from ._rec import Record
|
||||
from ._xfrm import DomainRenamer, ResetInserter, EnableInserter
|
||||
|
||||
|
@ -27,7 +27,7 @@ __all__ = [
|
|||
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
|
||||
"Instance", "IOBufferInstance",
|
||||
# _mem
|
||||
"MemoryIdentity", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
|
||||
"MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
|
||||
# _rec
|
||||
"Record",
|
||||
# _xfrm
|
||||
|
|
|
@ -1134,9 +1134,9 @@ class NetlistEmitter:
|
|||
|
||||
def emit_memory(self, module_idx: int, fragment: '_mem.MemoryInstance', name: str):
|
||||
cell = _nir.Memory(module_idx,
|
||||
width=fragment._width,
|
||||
depth=fragment._depth,
|
||||
init=fragment._init,
|
||||
width=_ast.Shape.cast(fragment._data._shape).width,
|
||||
depth=fragment._data._depth,
|
||||
init=fragment._data._init._raw,
|
||||
name=name,
|
||||
attributes=fragment._attrs,
|
||||
src_loc=fragment.src_loc,
|
||||
|
|
|
@ -1,33 +1,160 @@
|
|||
import operator
|
||||
from collections import OrderedDict
|
||||
from collections.abc import MutableSequence
|
||||
|
||||
from .. import tracer
|
||||
from ._ast import *
|
||||
from ._ir import Elaboratable, Fragment
|
||||
from ..utils import ceil_log2
|
||||
from .._utils import deprecated
|
||||
from .._utils import deprecated, final
|
||||
|
||||
|
||||
__all__ = ["Memory", "ReadPort", "WritePort", "DummyPort"]
|
||||
__all__ = ["MemoryData", "Memory", "ReadPort", "WritePort", "DummyPort"]
|
||||
|
||||
|
||||
class MemoryIdentity: pass
|
||||
@final
|
||||
class FrozenError(Exception):
|
||||
"""This exception is raised when ports are added to a :class:`Memory` or its
|
||||
:attr:`~Memory.init` attribute is changed after it has been elaborated once.
|
||||
"""
|
||||
|
||||
|
||||
@final
|
||||
class MemoryData:
|
||||
@final
|
||||
class Init(MutableSequence):
|
||||
"""Memory initialization data.
|
||||
|
||||
This is a special container used only for initial contents of memories. It is similar
|
||||
to :class:`list`, but does not support inserting or deleting elements; its length is always
|
||||
the same as the depth of the memory it belongs to.
|
||||
|
||||
If :py:`shape` is a :ref:`custom shape-castable object <lang-shapecustom>`, then:
|
||||
|
||||
* Each element must be convertible to :py:`shape` via :meth:`.ShapeCastable.const`, and
|
||||
* Elements that are not explicitly initialized default to :py:`shape.const(None)`.
|
||||
|
||||
Otherwise (when :py:`shape` is a :class:`.Shape`):
|
||||
|
||||
* Each element must be an :class:`int`, and
|
||||
* Elements that are not explicitly initialized default to :py:`0`.
|
||||
"""
|
||||
def __init__(self, elems, *, shape, depth):
|
||||
Shape.cast(shape)
|
||||
if not isinstance(depth, int) or depth < 0:
|
||||
raise TypeError("Memory depth must be a non-negative integer, not {!r}"
|
||||
.format(depth))
|
||||
self._shape = shape
|
||||
self._depth = depth
|
||||
self._frozen = False
|
||||
|
||||
if isinstance(shape, ShapeCastable):
|
||||
self._elems = [None] * depth
|
||||
self._raw = [Const.cast(Const(None, shape)).value] * depth
|
||||
else:
|
||||
self._elems = [0] * depth
|
||||
self._raw = self._elems # intentionally mutably aliased
|
||||
elems = list(elems)
|
||||
if len(elems) > depth:
|
||||
raise ValueError(f"Memory initialization value count exceeds memory depth ({len(elems)} > {depth})")
|
||||
for index, item in enumerate(elems):
|
||||
try:
|
||||
self[index] = item
|
||||
except (TypeError, ValueError) as e:
|
||||
raise type(e)(f"Memory initialization value at address {index:x}: {e}") from None
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._elems[index]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
if self._frozen:
|
||||
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
|
||||
|
||||
if isinstance(index, slice):
|
||||
indices = range(*index.indices(len(self._elems)))
|
||||
if len(value) != len(indices):
|
||||
raise ValueError("Changing length of Memory.init is not allowed")
|
||||
for actual_index, actual_value in zip(indices, value):
|
||||
self[actual_index] = actual_value
|
||||
else:
|
||||
if isinstance(self._shape, ShapeCastable):
|
||||
self._raw[index] = Const.cast(Const(value, self._shape)).value
|
||||
else:
|
||||
value = operator.index(value)
|
||||
# self._raw[index] assigned by the following line
|
||||
self._elems[index] = value
|
||||
|
||||
def __delitem__(self, index):
|
||||
raise TypeError("Deleting elements from Memory.init is not allowed")
|
||||
|
||||
def insert(self, index, value):
|
||||
""":meta private:"""
|
||||
raise TypeError("Inserting elements into Memory.init is not allowed")
|
||||
|
||||
def __len__(self):
|
||||
return self._depth
|
||||
|
||||
def __repr__(self):
|
||||
return f"MemoryData.Init({self._elems!r}, shape={self._shape!r}, depth={self._depth})"
|
||||
|
||||
|
||||
def __init__(self, *, shape, depth, init, src_loc_at=0):
|
||||
# shape and depth validation is performed in MemoryData.Init()
|
||||
self._shape = shape
|
||||
self._depth = depth
|
||||
self._init = MemoryData.Init(init, shape=shape, depth=depth)
|
||||
self.src_loc = tracer.get_src_loc(src_loc_at=src_loc_at)
|
||||
self.name = tracer.get_var_name(depth=2+src_loc_at, default="$memory")
|
||||
self._frozen = False
|
||||
|
||||
def freeze(self):
|
||||
self._frozen = True
|
||||
self._init._frozen = True
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
@property
|
||||
def depth(self):
|
||||
return self._depth
|
||||
|
||||
@property
|
||||
def init(self):
|
||||
return self._init
|
||||
|
||||
@init.setter
|
||||
def init(self, init):
|
||||
if self._frozen:
|
||||
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
|
||||
self._init = MemoryData.Init(init, shape=self._shape, depth=self._depth)
|
||||
|
||||
def __repr__(self):
|
||||
return f"(memory-data {self.name})"
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Simulation only."""
|
||||
return MemorySimRead(self, index)
|
||||
|
||||
|
||||
class MemorySimRead:
|
||||
def __init__(self, identity, addr):
|
||||
assert isinstance(identity, MemoryIdentity)
|
||||
self._identity = identity
|
||||
def __init__(self, memory, addr):
|
||||
assert isinstance(memory, MemoryData)
|
||||
self._memory = memory
|
||||
self._addr = Value.cast(addr)
|
||||
|
||||
def eq(self, value):
|
||||
return MemorySimWrite(self._identity, self._addr, value)
|
||||
return MemorySimWrite(self._memory, self._addr, value)
|
||||
|
||||
|
||||
class MemorySimWrite:
|
||||
def __init__(self, identity, addr, data):
|
||||
assert isinstance(identity, MemoryIdentity)
|
||||
self._identity = identity
|
||||
def __init__(self, memory, addr, data):
|
||||
assert isinstance(memory, MemoryData)
|
||||
self._memory = memory
|
||||
self._addr = Value.cast(addr)
|
||||
self._data = Value.cast(data)
|
||||
|
||||
|
@ -66,26 +193,20 @@ class MemoryInstance(Fragment):
|
|||
return len(self._data) // len(self._en)
|
||||
|
||||
|
||||
def __init__(self, *, identity, width, depth, init=None, attrs=None, src_loc=None):
|
||||
def __init__(self, *, data, attrs=None, src_loc=None):
|
||||
super().__init__(src_loc=src_loc)
|
||||
assert isinstance(identity, MemoryIdentity)
|
||||
self._identity = identity
|
||||
self._width = operator.index(width)
|
||||
self._depth = operator.index(depth)
|
||||
mask = (1 << self._width) - 1
|
||||
self._init = tuple(item & mask for item in init) if init is not None else ()
|
||||
assert len(self._init) <= self._depth
|
||||
self._init += (0,) * (self._depth - len(self._init))
|
||||
for x in self._init:
|
||||
assert isinstance(x, int)
|
||||
assert isinstance(data, MemoryData)
|
||||
data.freeze()
|
||||
self._data = data
|
||||
self._attrs = attrs or {}
|
||||
self._read_ports: "list[MemoryInstance._ReadPort]" = []
|
||||
self._write_ports: "list[MemoryInstance._WritePort]" = []
|
||||
|
||||
def read_port(self, *, domain, addr, data, en, transparent_for):
|
||||
port = self._ReadPort(domain=domain, addr=addr, data=data, en=en, transparent_for=transparent_for)
|
||||
assert len(port._data) == self._width
|
||||
assert len(port._addr) == ceil_log2(self._depth)
|
||||
shape = Shape.cast(self._data.shape)
|
||||
assert len(port._data) == shape.width
|
||||
assert len(port._addr) == ceil_log2(self._data.depth)
|
||||
for idx in port._transparent_for:
|
||||
assert isinstance(idx, int)
|
||||
assert idx in range(len(self._write_ports))
|
||||
|
@ -96,8 +217,9 @@ class MemoryInstance(Fragment):
|
|||
|
||||
def write_port(self, *, domain, addr, data, en):
|
||||
port = self._WritePort(domain=domain, addr=addr, data=data, en=en)
|
||||
assert len(port._data) == self._width
|
||||
assert len(port._addr) == ceil_log2(self._depth)
|
||||
shape = Shape.cast(self._data.shape)
|
||||
assert len(port._data) == shape.width
|
||||
assert len(port._addr) == ceil_log2(self._data.depth)
|
||||
self._write_ports.append(port)
|
||||
return len(self._write_ports) - 1
|
||||
|
||||
|
@ -145,28 +267,17 @@ class Memory(Elaboratable):
|
|||
self.depth = depth
|
||||
self.attrs = OrderedDict(() if attrs is None else attrs)
|
||||
|
||||
self.init = init
|
||||
self._read_ports = []
|
||||
self._write_ports = []
|
||||
self._identity = MemoryIdentity()
|
||||
self._data = MemoryData(shape=width, depth=depth, init=init or [])
|
||||
|
||||
@property
|
||||
def init(self):
|
||||
return self._init
|
||||
return self._data.init
|
||||
|
||||
@init.setter
|
||||
def init(self, new_init):
|
||||
self._init = [] if new_init is None else list(new_init)
|
||||
if len(self.init) > self.depth:
|
||||
raise ValueError("Memory initialization value count exceed memory depth ({} > {})"
|
||||
.format(len(self.init), self.depth))
|
||||
|
||||
try:
|
||||
for addr, val in enumerate(self._init):
|
||||
operator.index(val)
|
||||
except TypeError as e:
|
||||
raise TypeError("Memory initialization value at address {:x}: {}"
|
||||
.format(addr, e)) from None
|
||||
self._data.init = new_init
|
||||
|
||||
def read_port(self, *, src_loc_at=0, **kwargs):
|
||||
"""Get a read port.
|
||||
|
@ -202,10 +313,10 @@ class Memory(Elaboratable):
|
|||
|
||||
def __getitem__(self, index):
|
||||
"""Simulation only."""
|
||||
return MemorySimRead(self._identity, index)
|
||||
return MemorySimRead(self._data, index)
|
||||
|
||||
def elaborate(self, platform):
|
||||
f = MemoryInstance(identity=self._identity, width=self.width, depth=self.depth, init=self.init, attrs=self.attrs, src_loc=self.src_loc)
|
||||
f = MemoryInstance(data=self._data, attrs=self.attrs, src_loc=self.src_loc)
|
||||
write_ports = {}
|
||||
for port in self._write_ports:
|
||||
port._MustUse__used = True
|
||||
|
|
|
@ -272,10 +272,7 @@ class FragmentTransformer:
|
|||
def on_fragment(self, fragment):
|
||||
if isinstance(fragment, MemoryInstance):
|
||||
new_fragment = MemoryInstance(
|
||||
identity=fragment._identity,
|
||||
width=fragment._width,
|
||||
depth=fragment._depth,
|
||||
init=fragment._init,
|
||||
data=fragment._data,
|
||||
attrs=fragment._attrs,
|
||||
src_loc=fragment.src_loc
|
||||
)
|
||||
|
|
|
@ -2,8 +2,8 @@ import operator
|
|||
from collections import OrderedDict
|
||||
from collections.abc import MutableSequence
|
||||
|
||||
from ..hdl import MemoryIdentity, MemoryInstance, Shape, ShapeCastable, Const
|
||||
from ..hdl._mem import MemorySimRead
|
||||
from ..hdl import MemoryData, MemoryInstance, Shape, ShapeCastable, Const
|
||||
from ..hdl._mem import MemorySimRead, FrozenError
|
||||
from ..utils import ceil_log2
|
||||
from .._utils import final
|
||||
from .. import tracer
|
||||
|
@ -13,13 +13,6 @@ from . import wiring, data
|
|||
__all__ = ["Memory", "ReadPort", "WritePort"]
|
||||
|
||||
|
||||
@final
|
||||
class FrozenError(Exception):
|
||||
"""This exception is raised when ports are added to a :class:`Memory` or its
|
||||
:attr:`~Memory.init` attribute is changed after it has been elaborated once.
|
||||
"""
|
||||
|
||||
|
||||
class Memory(wiring.Component):
|
||||
"""Addressable array of rows.
|
||||
|
||||
|
@ -55,116 +48,55 @@ class Memory(wiring.Component):
|
|||
:class:`Memory`, e.g. to instantiate library cells directly.
|
||||
"""
|
||||
|
||||
class Init(MutableSequence):
|
||||
"""Memory initialization data.
|
||||
Init = MemoryData.Init
|
||||
|
||||
This is a special container used only for the :attr:`Memory.init` attribute. It is similar
|
||||
to :class:`list`, but does not support inserting or deleting elements; its length is always
|
||||
the same as the depth of the memory it belongs to.
|
||||
|
||||
If :py:`shape` is a :ref:`custom shape-castable object <lang-shapecustom>`, then:
|
||||
|
||||
* Each element must be convertible to :py:`shape` via :meth:`.ShapeCastable.const`, and
|
||||
* Elements that are not explicitly initialized default to :py:`shape.const(None)`.
|
||||
|
||||
Otherwise (when :py:`shape` is a :class:`.Shape`):
|
||||
|
||||
* Each element must be an :class:`int`, and
|
||||
* Elements that are not explicitly initialized default to :py:`0`.
|
||||
"""
|
||||
def __init__(self, elems, *, shape, depth):
|
||||
Shape.cast(shape)
|
||||
if not isinstance(depth, int) or depth < 0:
|
||||
raise TypeError("Memory depth must be a non-negative integer, not {!r}"
|
||||
.format(depth))
|
||||
self._shape = shape
|
||||
self._depth = depth
|
||||
self._frozen = False
|
||||
|
||||
if isinstance(shape, ShapeCastable):
|
||||
self._elems = [None] * depth
|
||||
self._raw = [Const.cast(Const(None, shape)).value] * depth
|
||||
else:
|
||||
self._elems = [0] * depth
|
||||
self._raw = self._elems # intentionally mutably aliased
|
||||
try:
|
||||
for index, item in enumerate(elems):
|
||||
self[index] = item
|
||||
except (TypeError, ValueError) as e:
|
||||
raise type(e)("Memory initialization value at address {:x}: {}"
|
||||
.format(index, e)) from None
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._elems[index]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
if self._frozen:
|
||||
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
|
||||
|
||||
if isinstance(index, slice):
|
||||
indices = range(*index.indices(len(self._elems)))
|
||||
if len(value) != len(indices):
|
||||
raise ValueError("Changing length of Memory.init is not allowed")
|
||||
for actual_index, actual_value in zip(indices, value):
|
||||
self[actual_index] = actual_value
|
||||
else:
|
||||
if isinstance(self._shape, ShapeCastable):
|
||||
self._raw[index] = Const.cast(Const(value, self._shape)).value
|
||||
else:
|
||||
value = operator.index(value)
|
||||
# self._raw[index] assigned by the following line
|
||||
self._elems[index] = value
|
||||
|
||||
def __delitem__(self, index):
|
||||
raise TypeError("Deleting elements from Memory.init is not allowed")
|
||||
|
||||
def insert(self, index, value):
|
||||
""":meta private:"""
|
||||
raise TypeError("Inserting elements into Memory.init is not allowed")
|
||||
|
||||
def __len__(self):
|
||||
return self._depth
|
||||
|
||||
def __repr__(self):
|
||||
return f"Memory.Init({self._elems!r}, shape={self._shape!r}, depth={self._depth})"
|
||||
|
||||
|
||||
def __init__(self, *, shape, depth, init, attrs=None, src_loc_at=0):
|
||||
# shape and depth validation is performed in Memory.Init()
|
||||
self._shape = shape
|
||||
self._depth = depth
|
||||
self._init = Memory.Init(init, shape=shape, depth=depth)
|
||||
def __init__(self, data=None, *, shape=None, depth=None, init=None, attrs=None, src_loc_at=0):
|
||||
if data is None:
|
||||
if shape is None:
|
||||
raise ValueError("Either 'data' or 'shape' needs to be given")
|
||||
if depth is None:
|
||||
raise ValueError("Either 'data' or 'depth' needs to be given")
|
||||
if init is None:
|
||||
raise ValueError("Either 'data' or 'init' needs to be given")
|
||||
data = MemoryData(shape=shape, depth=depth, init=init, src_loc_at=1 + src_loc_at)
|
||||
else:
|
||||
if not isinstance(data, MemoryData):
|
||||
raise TypeError(f"'data' must be a MemoryData instance, not {data!r}")
|
||||
if shape is not None:
|
||||
raise ValueError("'data' and 'shape' cannot be given at the same time")
|
||||
if depth is not None:
|
||||
raise ValueError("'data' and 'depth' cannot be given at the same time")
|
||||
if init is not None:
|
||||
raise ValueError("'data' and 'init' cannot be given at the same time")
|
||||
self._data = data
|
||||
self._attrs = {} if attrs is None else dict(attrs)
|
||||
self.src_loc = tracer.get_src_loc(src_loc_at=src_loc_at)
|
||||
|
||||
self._identity = MemoryIdentity()
|
||||
self._read_ports: "list[ReadPort]" = []
|
||||
self._write_ports: "list[WritePort]" = []
|
||||
self._frozen = False
|
||||
|
||||
super().__init__(wiring.Signature({}))
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
return self._data.shape
|
||||
|
||||
@property
|
||||
def depth(self):
|
||||
return self._depth
|
||||
return self._data.depth
|
||||
|
||||
@property
|
||||
def init(self):
|
||||
return self._init
|
||||
return self._data.init
|
||||
|
||||
@init.setter
|
||||
def init(self, init):
|
||||
if self._frozen:
|
||||
raise FrozenError("Cannot set 'init' on a memory that has already been elaborated")
|
||||
self._init = Memory.Init(init, shape=self._shape, depth=self._depth)
|
||||
self._data.init = init
|
||||
|
||||
@property
|
||||
def attrs(self):
|
||||
|
@ -245,13 +177,12 @@ class Memory(wiring.Component):
|
|||
|
||||
def elaborate(self, platform):
|
||||
self._frozen = True
|
||||
self._init._frozen = True
|
||||
self._data.freeze()
|
||||
if hasattr(platform, "get_memory"):
|
||||
return platform.get_memory(self)
|
||||
|
||||
shape = Shape.cast(self.shape)
|
||||
instance = MemoryInstance(identity=self._identity, width=shape.width, depth=self.depth,
|
||||
init=self.init._raw, attrs=self.attrs, src_loc=self.src_loc)
|
||||
instance = MemoryInstance(data=self._data, attrs=self.attrs, src_loc=self.src_loc)
|
||||
write_ports = {}
|
||||
for port in self._write_ports:
|
||||
write_ports[port] = instance.write_port(
|
||||
|
@ -265,7 +196,7 @@ class Memory(wiring.Component):
|
|||
|
||||
def __getitem__(self, index):
|
||||
"""Simulation only."""
|
||||
return MemorySimRead(self._identity, index)
|
||||
return self._data[index]
|
||||
|
||||
|
||||
class ReadPort:
|
||||
|
|
|
@ -46,6 +46,9 @@ class BaseSimulation:
|
|||
def get_signal(self, signal):
|
||||
raise NotImplementedError # :nocov:
|
||||
|
||||
def get_memory(self, memory):
|
||||
raise NotImplementedError # :nocov:
|
||||
|
||||
slots = NotImplemented
|
||||
|
||||
def add_trigger(self, process, signal, *, trigger=None):
|
||||
|
@ -54,10 +57,10 @@ class BaseSimulation:
|
|||
def remove_trigger(self, process, signal):
|
||||
raise NotImplementedError # :nocov:
|
||||
|
||||
def add_memory_trigger(self, process, identity):
|
||||
def add_memory_trigger(self, process, memory):
|
||||
raise NotImplementedError # :nocov:
|
||||
|
||||
def remove_memory_trigger(self, process, identity):
|
||||
def remove_memory_trigger(self, process, memory):
|
||||
raise NotImplementedError # :nocov:
|
||||
|
||||
def wait_interval(self, process, interval):
|
||||
|
|
|
@ -135,7 +135,7 @@ class PyCoroProcess(BaseProcess):
|
|||
exec(_RHSValueCompiler.compile(self.state, command._addr, mode="curr"),
|
||||
self.exec_locals)
|
||||
addr = Const(self.exec_locals["result"], command._addr.shape()).value
|
||||
index = self.state.memories[command._identity]
|
||||
index = self.state.get_memory(command._memory)
|
||||
state = self.state.slots[index]
|
||||
assert isinstance(state, BaseMemoryState)
|
||||
response = state.read(addr)
|
||||
|
@ -147,7 +147,7 @@ class PyCoroProcess(BaseProcess):
|
|||
exec(_RHSValueCompiler.compile(self.state, command._data, mode="curr"),
|
||||
self.exec_locals)
|
||||
data = Const(self.exec_locals["result"], command._data.shape()).value
|
||||
index = self.state.memories[command._identity]
|
||||
index = self.state.get_memory(command._memory)
|
||||
state = self.state.slots[index]
|
||||
assert isinstance(state, BaseMemoryState)
|
||||
state.write(addr, data)
|
||||
|
|
|
@ -481,7 +481,6 @@ class _FragmentCompiler:
|
|||
domains = set(fragment.statements)
|
||||
|
||||
if isinstance(fragment, MemoryInstance):
|
||||
self.state.add_memory(fragment)
|
||||
for port in fragment._read_ports:
|
||||
domains.add(port._domain)
|
||||
for port in fragment._write_ports:
|
||||
|
@ -510,8 +509,8 @@ class _FragmentCompiler:
|
|||
_StatementCompiler(self.state, emitter, inputs=inputs)(domain_stmts)
|
||||
|
||||
if isinstance(fragment, MemoryInstance):
|
||||
self.state.add_memory_trigger(domain_process, fragment._identity)
|
||||
memory_index = self.state.memories[fragment._identity]
|
||||
self.state.add_memory_trigger(domain_process, fragment._data)
|
||||
memory_index = self.state.get_memory(fragment._data)
|
||||
rhs = _RHSValueCompiler(self.state, emitter, mode="curr", inputs=inputs)
|
||||
lhs = _LHSValueCompiler(self.state, emitter, rhs=rhs)
|
||||
|
||||
|
@ -554,7 +553,7 @@ class _FragmentCompiler:
|
|||
emitter.append(f"next_{signal_index} = {signal.init}")
|
||||
|
||||
if isinstance(fragment, MemoryInstance):
|
||||
memory_index = self.state.memories[fragment._identity]
|
||||
memory_index = self.state.get_memory(fragment._data)
|
||||
rhs = _RHSValueCompiler(self.state, emitter, mode="curr")
|
||||
lhs = _LHSValueCompiler(self.state, emitter, rhs=rhs)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import os.path
|
|||
|
||||
from ..hdl import *
|
||||
from ..hdl._repr import *
|
||||
from ..hdl._mem import MemoryInstance, MemoryIdentity
|
||||
from ..hdl._mem import MemoryInstance
|
||||
from ..hdl._ast import SignalDict, Slice, Operator
|
||||
from ._base import *
|
||||
from ._pyrtl import _FragmentCompiler
|
||||
|
@ -75,7 +75,7 @@ class _VCDWriter:
|
|||
signal_names[signal] = set()
|
||||
signal_names[signal].add((*fragment_name, signal_name))
|
||||
if isinstance(fragment, MemoryInstance):
|
||||
memories[fragment._identity] = (fragment, fragment_name)
|
||||
memories[fragment._data] = fragment_name
|
||||
|
||||
trace_names = SignalDict()
|
||||
assigned_names = set()
|
||||
|
@ -92,10 +92,16 @@ class _VCDWriter:
|
|||
trace_names[trace_signal] = {("bench", name)}
|
||||
assigned_names.add(name)
|
||||
self.traces.append(trace_signal)
|
||||
elif hasattr(trace, "_identity") and isinstance(trace._identity, MemoryIdentity):
|
||||
if not trace._identity in memories:
|
||||
raise ValueError(f"{trace!r} is a memory not part of the elaborated design")
|
||||
self.traces.append(trace._identity)
|
||||
elif isinstance(trace, MemoryData):
|
||||
if not trace in memories:
|
||||
if trace.name not in assigned_names:
|
||||
name = trace.name
|
||||
else:
|
||||
name = f"{trace.name}${len(assigned_names)}"
|
||||
assert name not in assigned_names
|
||||
memories[trace] = ("bench", name)
|
||||
assigned_names.add(name)
|
||||
self.traces.append(trace)
|
||||
else:
|
||||
raise TypeError(f"{trace!r} is not a traceable object")
|
||||
|
||||
|
@ -146,19 +152,20 @@ class _VCDWriter:
|
|||
|
||||
self.vcd_signal_vars[signal].append((vcd_var, repr))
|
||||
|
||||
for memory, memory_name in memories.values():
|
||||
self.vcd_memory_vars[memory._identity] = vcd_vars = []
|
||||
self.gtkw_memory_names[memory._identity] = gtkw_names = []
|
||||
if memory._width > 1:
|
||||
suffix = f"[{memory._width - 1}:0]"
|
||||
for memory, memory_name in memories.items():
|
||||
self.vcd_memory_vars[memory] = vcd_vars = []
|
||||
self.gtkw_memory_names[memory] = gtkw_names = []
|
||||
width = Shape.cast(memory.shape).width
|
||||
if width > 1:
|
||||
suffix = f"[{width - 1}:0]"
|
||||
else:
|
||||
suffix = ""
|
||||
for idx, init in enumerate(memory._init):
|
||||
for idx, init in enumerate(memory._init._raw):
|
||||
field_name = "\\" + memory_name[-1] + f"[{idx}]"
|
||||
var_scope = memory_name[:-1]
|
||||
vcd_var = self.vcd_writer.register_var(
|
||||
scope=var_scope, name=field_name,
|
||||
var_type="wire", size=memory._width, init=init,
|
||||
var_type="wire", size=width, init=init,
|
||||
)
|
||||
vcd_vars.append(vcd_var)
|
||||
gtkw_field_name = field_name + suffix
|
||||
|
@ -194,7 +201,7 @@ class _VCDWriter:
|
|||
self.vcd_writer.change(vcd_var, timestamp, var_value)
|
||||
|
||||
def update_memory(self, timestamp, memory, addr, value):
|
||||
vcd_var = self.vcd_memory_vars[memory._identity][addr]
|
||||
vcd_var = self.vcd_memory_vars[memory][addr]
|
||||
self.vcd_writer.change(vcd_var, timestamp, value)
|
||||
|
||||
def update_process(self, timestamp, process, command):
|
||||
|
@ -325,7 +332,7 @@ class _PyMemoryState(BaseMemoryState):
|
|||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.data = list(self.memory._init)
|
||||
self.data = list(self.memory._init._raw)
|
||||
self.write_queue = []
|
||||
|
||||
def commit(self):
|
||||
|
@ -344,19 +351,19 @@ class _PyMemoryState(BaseMemoryState):
|
|||
return awoken_any
|
||||
|
||||
def read(self, addr):
|
||||
if addr not in range(self.memory._depth):
|
||||
if addr not in range(self.memory.depth):
|
||||
return 0
|
||||
|
||||
return self.data[addr]
|
||||
|
||||
def write(self, addr, value, mask=None):
|
||||
if addr not in range(self.memory._depth):
|
||||
if addr not in range(self.memory.depth):
|
||||
return
|
||||
if mask == 0:
|
||||
return
|
||||
|
||||
if mask is None:
|
||||
mask = (1 << self.memory._width) - 1
|
||||
mask = (1 << Shape.cast(self.memory.shape).width) - 1
|
||||
|
||||
self.write_queue.append((addr, value, mask))
|
||||
self.pending.add(self)
|
||||
|
@ -370,10 +377,6 @@ class _PySimulation(BaseSimulation):
|
|||
self.slots = []
|
||||
self.pending = set()
|
||||
|
||||
def add_memory(self, fragment):
|
||||
self.memories[fragment._identity] = len(self.slots)
|
||||
self.slots.append(_PyMemoryState(fragment, self.pending))
|
||||
|
||||
def reset(self):
|
||||
self.timeline.reset()
|
||||
for signal, index in self.signals.items():
|
||||
|
@ -395,6 +398,15 @@ class _PySimulation(BaseSimulation):
|
|||
self.signals[signal] = index
|
||||
return index
|
||||
|
||||
def get_memory(self, memory):
|
||||
try:
|
||||
return self.memories[memory]
|
||||
except KeyError:
|
||||
index = len(self.slots)
|
||||
self.slots.append(_PyMemoryState(memory, self.pending))
|
||||
self.memories[memory] = index
|
||||
return index
|
||||
|
||||
def add_trigger(self, process, signal, *, trigger=None):
|
||||
index = self.get_signal(signal)
|
||||
assert (process not in self.slots[index].waiters or
|
||||
|
@ -406,12 +418,12 @@ class _PySimulation(BaseSimulation):
|
|||
assert process in self.slots[index].waiters
|
||||
del self.slots[index].waiters[process]
|
||||
|
||||
def add_memory_trigger(self, process, identity):
|
||||
index = self.memories[identity]
|
||||
def add_memory_trigger(self, process, memory):
|
||||
index = self.get_memory(memory)
|
||||
self.slots[index].waiters[process] = None
|
||||
|
||||
def remove_memory_trigger(self, process, identity):
|
||||
index = self.memories[identity]
|
||||
def remove_memory_trigger(self, process, memory):
|
||||
index = self.get_memory(memory)
|
||||
assert process in self.slots[index].waiters
|
||||
del self.slots[index].waiters[process]
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ Implemented RFCs
|
|||
.. _RFC 51: https://amaranth-lang.org/rfcs/0051-const-from-bits.html
|
||||
.. _RFC 53: https://amaranth-lang.org/rfcs/0053-ioport.html
|
||||
.. _RFC 55: https://amaranth-lang.org/rfcs/0055-lib-io.html
|
||||
.. _RFC 62: https://amaranth-lang.org/rfcs/0062-memory-data.html
|
||||
|
||||
* `RFC 17`_: Remove ``log2_int``
|
||||
* `RFC 27`_: Testbench processes for the simulator
|
||||
|
@ -75,6 +76,7 @@ Language changes
|
|||
* Added: :class:`Format` objects, :class:`Print` statements, messages in :class:`Assert`, :class:`Assume` and :class:`Cover`. (`RFC 50`_)
|
||||
* Added: :meth:`ShapeCastable.from_bits` method. (`RFC 51`_)
|
||||
* Added: IO values, :class:`IOPort` objects, :class:`IOBufferInstance` objects. (`RFC 53`_)
|
||||
* Added: :class:`MemoryData` objects. (`RFC 62`_)
|
||||
* Changed: ``m.Case()`` with no patterns is never active instead of always active. (`RFC 39`_)
|
||||
* Changed: ``Value.matches()`` with no patterns is ``Const(0)`` instead of ``Const(1)``. (`RFC 39`_)
|
||||
* Changed: ``Signal(range(stop), init=stop)`` warning has been changed into a hard error and made to trigger on any out-of range value.
|
||||
|
|
|
@ -35,12 +35,12 @@ class MemoryTestCase(FHDLTestCase):
|
|||
def test_init(self):
|
||||
with _ignore_deprecated():
|
||||
m = Memory(width=8, depth=4, init=range(4))
|
||||
self.assertEqual(m.init, [0, 1, 2, 3])
|
||||
self.assertEqual(list(m.init), [0, 1, 2, 3])
|
||||
|
||||
def test_init_wrong_count(self):
|
||||
with _ignore_deprecated():
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Memory initialization value count exceed memory depth \(8 > 4\)$"):
|
||||
r"^Memory initialization value count exceeds memory depth \(8 > 4\)$"):
|
||||
m = Memory(width=8, depth=4, init=range(8))
|
||||
|
||||
def test_init_wrong_type(self):
|
||||
|
|
|
@ -287,13 +287,23 @@ class MemoryTestCase(FHDLTestCase):
|
|||
self.assertEqual(m.init.shape, 8)
|
||||
self.assertEqual(len(m.init), 4)
|
||||
self.assertEqual(m.attrs, {})
|
||||
self.assertIsInstance(m.init, memory.Memory.Init)
|
||||
self.assertIsInstance(m.init, MemoryData.Init)
|
||||
self.assertEqual(list(m.init), [1, 2, 3, 0])
|
||||
self.assertEqual(m.init._raw, [1, 2, 3, 0])
|
||||
self.assertRepr(m.init, "Memory.Init([1, 2, 3, 0], shape=8, depth=4)")
|
||||
self.assertRepr(m.init, "MemoryData.Init([1, 2, 3, 0], shape=8, depth=4)")
|
||||
self.assertEqual(m.read_ports, ())
|
||||
self.assertEqual(m.write_ports, ())
|
||||
|
||||
data = MemoryData(shape=8, depth=4, init=[1, 2, 3])
|
||||
m = memory.Memory(data)
|
||||
self.assertIs(m.data, data)
|
||||
self.assertEqual(m.shape, 8)
|
||||
self.assertEqual(m.depth, 4)
|
||||
self.assertEqual(m.init.shape, 8)
|
||||
self.assertEqual(len(m.init), 4)
|
||||
self.assertEqual(m.attrs, {})
|
||||
self.assertEqual(list(m.init), [1, 2, 3, 0])
|
||||
|
||||
def test_constructor_shapecastable(self):
|
||||
init = [
|
||||
{"a": 0, "b": 1},
|
||||
|
@ -303,7 +313,7 @@ class MemoryTestCase(FHDLTestCase):
|
|||
self.assertEqual(m.shape, MyStruct)
|
||||
self.assertEqual(m.depth, 4)
|
||||
self.assertEqual(m.attrs, {"ram_style": "block"})
|
||||
self.assertIsInstance(m.init, memory.Memory.Init)
|
||||
self.assertIsInstance(m.init, MemoryData.Init)
|
||||
self.assertEqual(list(m.init), [{"a": 0, "b": 1}, {"a": 2, "b": 3}, None, None])
|
||||
self.assertEqual(m.init._raw, [8, 0x1a, 0, 0])
|
||||
|
||||
|
@ -321,6 +331,28 @@ class MemoryTestCase(FHDLTestCase):
|
|||
(r"^Memory initialization value at address 1: "
|
||||
r"'str' object cannot be interpreted as an integer$")):
|
||||
memory.Memory(shape=8, depth=4, init=[1, "0"])
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Either 'data' or 'shape' needs to be given$"):
|
||||
memory.Memory(depth=4, init=[])
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Either 'data' or 'depth' needs to be given$"):
|
||||
memory.Memory(shape=8, init=[])
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Either 'data' or 'init' needs to be given$"):
|
||||
memory.Memory(shape=8, depth=4)
|
||||
data = MemoryData(shape=8, depth=4, init=[])
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^'data' and 'shape' cannot be given at the same time$"):
|
||||
memory.Memory(data, shape=8)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^'data' and 'depth' cannot be given at the same time$"):
|
||||
memory.Memory(data, depth=4)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^'data' and 'init' cannot be given at the same time$"):
|
||||
memory.Memory(data, init=[])
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^'data' must be a MemoryData instance, not 'abc'$"):
|
||||
memory.Memory("abc")
|
||||
|
||||
def test_init_set(self):
|
||||
m = memory.Memory(shape=8, depth=4, init=[])
|
||||
|
@ -385,10 +417,8 @@ class MemoryTestCase(FHDLTestCase):
|
|||
rp1 = m.read_port(domain="comb")
|
||||
f = m.elaborate(None)
|
||||
self.assertIsInstance(f, MemoryInstance)
|
||||
self.assertIs(f._identity, m._identity)
|
||||
self.assertEqual(f._depth, 4)
|
||||
self.assertEqual(f._width, 5)
|
||||
self.assertEqual(f._init, (0x11, 0, 0, 0))
|
||||
self.assertIs(f._data, m.data)
|
||||
self.assertEqual(f._data._init._raw, [0x11, 0, 0, 0])
|
||||
self.assertEqual(f._write_ports[0]._domain, "sync")
|
||||
self.assertEqual(f._write_ports[0]._granularity, 5)
|
||||
self.assertIs(f._write_ports[0]._addr, wp.addr)
|
||||
|
|
Loading…
Reference in a new issue