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:
Wanda 2024-03-28 03:06:39 +01:00 committed by Catherine
parent 1deaf70ad9
commit 09d5540430
12 changed files with 280 additions and 195 deletions

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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
)

View file

@ -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:

View file

@ -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):

View file

@ -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)

View file

@ -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)

View file

@ -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]

View file

@ -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.

View file

@ -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):

View file

@ -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)