diff --git a/amaranth/__init__.py b/amaranth/__init__.py index b9689589d..f9c20d8fa 100644 --- a/amaranth/__init__.py +++ b/amaranth/__init__.py @@ -20,6 +20,5 @@ "Module", "ClockDomain", "Elaboratable", "Fragment", "Instance", - "Memory", "DomainRenamer", "ResetInserter", "EnableInserter", ] diff --git a/amaranth/hdl/__init__.py b/amaranth/hdl/__init__.py index 0eab602a8..0a1f7532f 100644 --- a/amaranth/hdl/__init__.py +++ b/amaranth/hdl/__init__.py @@ -8,7 +8,7 @@ from ._cd import DomainError, ClockDomain from ._ir import AlreadyElaborated, UnusedElaboratable, Elaboratable, DriverConflict, Fragment from ._ir import Instance, IOBufferInstance -from ._mem import MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort +from ._mem import MemoryData, MemoryInstance from ._nir import CombinationalCycle from ._xfrm import DomainRenamer, ResetInserter, EnableInserter @@ -31,7 +31,7 @@ # _nir "CombinationalCycle", # _mem - "MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort", + "MemoryData", "MemoryInstance", # _xfrm "DomainRenamer", "ResetInserter", "EnableInserter", ] diff --git a/amaranth/hdl/_mem.py b/amaranth/hdl/_mem.py index add1a896f..0554f738f 100644 --- a/amaranth/hdl/_mem.py +++ b/amaranth/hdl/_mem.py @@ -1,15 +1,14 @@ import operator -from collections import OrderedDict from collections.abc import MutableSequence from .. import tracer from ._ast import * -from ._ir import Elaboratable, Fragment, AlreadyElaborated +from ._ir import Fragment, AlreadyElaborated from ..utils import ceil_log2 -from .._utils import deprecated, final +from .._utils import final -__all__ = ["MemoryData", "Memory", "ReadPort", "WritePort", "DummyPort"] +__all__ = ["MemoryData", "MemoryInstance"] @final @@ -270,260 +269,3 @@ def write_port(self, *, domain, addr, data, en): assert len(port._addr) == ceil_log2(self._data.depth) self._write_ports.append(port) return len(self._write_ports) - 1 - - -class Memory(Elaboratable): - """A word addressable storage. - - Parameters - ---------- - width : int - Access granularity. Each storage element of this memory is ``width`` bits in size. - depth : int - Word count. This memory contains ``depth`` storage elements. - init : list of int - Initial values. At power on, each storage element in this memory is initialized to - the corresponding element of ``init``, if any, or to zero otherwise. - Uninitialized memories are not currently supported. - name : str - Name hint for this memory. If ``None`` (default) the name is inferred from the variable - name this ``Signal`` is assigned to. - attrs : dict - Dictionary of synthesis attributes. - - Attributes - ---------- - width : int - depth : int - init : list of int - attrs : dict - """ - # TODO(amaranth-0.6): remove - @deprecated("`amaranth.hdl.Memory` is deprecated, use `amaranth.lib.memory.Memory` instead") - def __init__(self, *, width, depth, init=None, name=None, attrs=None, simulate=True): - if not isinstance(width, int) or width < 0: - raise TypeError("Memory width must be a non-negative integer, not {!r}" - .format(width)) - if not isinstance(depth, int) or depth < 0: - raise TypeError("Memory depth must be a non-negative integer, not {!r}" - .format(depth)) - - self.name = name or tracer.get_var_name(depth=3, default="$memory") - self.src_loc = tracer.get_src_loc(src_loc_at=1) - - self.width = width - self.depth = depth - self.attrs = OrderedDict(() if attrs is None else attrs) - - self._read_ports = [] - self._write_ports = [] - self._data = MemoryData(shape=width, depth=depth, init=init or []) - - @property - def init(self): - return self._data.init - - @init.setter - def init(self, new_init): - self._data.init = new_init - - def read_port(self, *, src_loc_at=0, **kwargs): - """Get a read port. - - See :class:`ReadPort` for details. - - Arguments - --------- - domain : str - transparent : bool - - Returns - ------- - An instance of :class:`ReadPort` associated with this memory. - """ - return ReadPort(self, src_loc_at=1 + src_loc_at, **kwargs) - - def write_port(self, *, src_loc_at=0, **kwargs): - """Get a write port. - - See :class:`WritePort` for details. - - Arguments - --------- - domain : str - granularity : int - - Returns - ------- - An instance of :class:`WritePort` associated with this memory. - """ - return WritePort(self, src_loc_at=1 + src_loc_at, **kwargs) - - def __getitem__(self, index): - return self._data[index] - - def elaborate(self, platform): - 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 - iport = f.write_port(domain=port.domain, addr=port.addr, data=port.data, en=port.en) - write_ports.setdefault(port.domain, []).append(iport) - for port in self._read_ports: - port._MustUse__used = True - if port.domain == "comb": - f.read_port(domain="comb", addr=port.addr, data=port.data, en=Const(1), transparent_for=()) - else: - transparent_for = [] - if port.transparent: - transparent_for = write_ports.get(port.domain, []) - f.read_port(domain=port.domain, addr=port.addr, data=port.data, en=port.en, transparent_for=transparent_for) - return f - - -class ReadPort(Elaboratable): - """A memory read port. - - Parameters - ---------- - memory : :class:`Memory` - Memory associated with the port. - domain : str - Clock domain. Defaults to ``"sync"``. If set to ``"comb"``, the port is asynchronous. - Otherwise, the read data becomes available on the next clock cycle. - transparent : bool - Port transparency. If set (default), a read at an address that is also being written to in - the same clock cycle will output the new value. Otherwise, the old value will be output - first. This behavior only applies to ports in the same domain. - - Attributes - ---------- - memory : :class:`Memory` - domain : str - transparent : bool - addr : Signal(range(memory.depth)), in - Read address. - data : Signal(memory.width), out - Read data. - en : Signal or Const, in - Read enable. If asserted, ``data`` is updated with the word stored at ``addr``. - - Exceptions - ---------- - Raises :exn:`ValueError` if the read port is simultaneously asynchronous and non-transparent. - """ - def __init__(self, memory, *, domain="sync", transparent=True, src_loc_at=0): - if domain == "comb" and not transparent: - raise ValueError("Read port cannot be simultaneously asynchronous and non-transparent") - - self.memory = memory - self.domain = domain - self.transparent = transparent - - self.addr = Signal(range(memory.depth), - name=f"{memory.name}_r_addr", src_loc_at=1 + src_loc_at) - self.data = Signal(memory.width, - name=f"{memory.name}_r_data", src_loc_at=1 + src_loc_at) - if self.domain != "comb": - self.en = Signal(name=f"{memory.name}_r_en", init=1, - src_loc_at=1 + src_loc_at) - else: - self.en = Const(1) - - memory._read_ports.append(self) - - def elaborate(self, platform): - if self is self.memory._read_ports[0]: - return self.memory - else: - return Fragment() - - -class WritePort(Elaboratable): - """A memory write port. - - Parameters - ---------- - memory : :class:`Memory` - Memory associated with the port. - domain : str - Clock domain. Defaults to ``"sync"``. Writes have a latency of 1 clock cycle. - granularity : int - Port granularity. Defaults to ``memory.width``. Write data is split evenly in - ``memory.width // granularity`` chunks, which can be updated independently. - - Attributes - ---------- - memory : :class:`Memory` - domain : str - granularity : int - addr : Signal(range(memory.depth)), in - Write address. - data : Signal(memory.width), in - Write data. - en : Signal(memory.width // granularity), in - Write enable. Each bit selects a non-overlapping chunk of ``granularity`` bits on the - ``data`` signal, which is written to memory at ``addr``. Unselected chunks are ignored. - - Exceptions - ---------- - Raises :exn:`ValueError` if the write port granularity is greater than memory width, or does not - divide memory width evenly. - """ - def __init__(self, memory, *, domain="sync", granularity=None, src_loc_at=0): - if granularity is None: - granularity = memory.width - if not isinstance(granularity, int) or granularity < 0: - raise TypeError("Write port granularity must be a non-negative integer, not {!r}" - .format(granularity)) - if granularity > memory.width: - raise ValueError("Write port granularity must not be greater than memory width " - "({} > {})" - .format(granularity, memory.width)) - if memory.width // granularity * granularity != memory.width: - raise ValueError("Write port granularity must divide memory width evenly") - - self.memory = memory - self.domain = domain - self.granularity = granularity - - self.addr = Signal(range(memory.depth), - name=f"{memory.name}_w_addr", src_loc_at=1 + src_loc_at) - self.data = Signal(memory.width, - name=f"{memory.name}_w_data", src_loc_at=1 + src_loc_at) - self.en = Signal(memory.width // granularity, - name=f"{memory.name}_w_en", src_loc_at=1 + src_loc_at) - - memory._write_ports.append(self) - - def elaborate(self, platform): - if not self.memory._read_ports and self is self.memory._write_ports[0]: - return self.memory - else: - return Fragment() - - -class DummyPort: - """Dummy memory port. - - This port can be used in place of either a read or a write port for testing and verification. - It does not include any read/write port specific attributes, i.e. none besides ``"domain"``; - any such attributes may be set manually. - """ - # TODO(amaranth-0.6): remove - @deprecated("`DummyPort` is deprecated, use `amaranth.lib.memory.ReadPort` or " - "`amaranth.lib.memory.WritePort` instead") - def __init__(self, *, data_width, addr_width, domain="sync", name=None, granularity=None): - self.domain = domain - - if granularity is None: - granularity = data_width - if name is None: - name = tracer.get_var_name(depth=3, default="dummy") - - self.addr = Signal(addr_width, - name=f"{name}_addr", src_loc_at=1) - self.data = Signal(data_width, - name=f"{name}_data", src_loc_at=1) - self.en = Signal(data_width // granularity, - name=f"{name}_en", src_loc_at=1) diff --git a/docs/changes.rst b/docs/changes.rst index 40876866e..d53a9c178 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -29,6 +29,7 @@ Language changes .. currentmodule:: amaranth.hdl * Removed: (deprecated in 0.4) :class:`Record`. +* Removed: (deprecated in 0.5) :class:`Memory` (`RFC 45`_) * Removed: (deprecated in 0.5) public submodules of :mod:`amaranth.hdl`. * Removed: (deprecated in 0.5) :meth:`Value.implies`. diff --git a/tests/test_hdl_mem.py b/tests/test_hdl_mem.py index f165622de..504e5984d 100644 --- a/tests/test_hdl_mem.py +++ b/tests/test_hdl_mem.py @@ -2,7 +2,6 @@ from amaranth.hdl import * from amaranth.hdl._mem import * -from amaranth._utils import _ignore_deprecated from .utils import * @@ -32,160 +31,3 @@ def test_row_elab(self): with self.assertRaisesRegex(ValueError, r"^Value \(memory-row \(memory-data data\) 0\) can only be used in simulator processes$"): m.d.comb += data[0].eq(1) - -class MemoryTestCase(FHDLTestCase): - def test_name(self): - with _ignore_deprecated(): - m1 = Memory(width=8, depth=4) - self.assertEqual(m1.name, "m1") - m2 = [Memory(width=8, depth=4)][0] - self.assertEqual(m2.name, "$memory") - m3 = Memory(width=8, depth=4, name="foo") - self.assertEqual(m3.name, "foo") - - def test_geometry(self): - with _ignore_deprecated(): - m = Memory(width=8, depth=4) - self.assertEqual(m.width, 8) - self.assertEqual(m.depth, 4) - - def test_geometry_wrong(self): - with _ignore_deprecated(): - with self.assertRaisesRegex(TypeError, - r"^Memory width must be a non-negative integer, not -1$"): - m = Memory(width=-1, depth=4) - with self.assertRaisesRegex(TypeError, - r"^Memory depth must be a non-negative integer, not -1$"): - m = Memory(width=8, depth=-1) - - def test_init(self): - with _ignore_deprecated(): - m = Memory(width=8, depth=4, init=range(4)) - 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 exceeds memory depth \(8 > 4\)$"): - m = Memory(width=8, depth=4, init=range(8)) - - def test_init_wrong_type(self): - with _ignore_deprecated(): - with self.assertRaisesRegex(TypeError, - (r"^Memory initialization value at address 1: " - r"'str' object cannot be interpreted as an integer$")): - m = Memory(width=8, depth=4, init=[1, "0"]) - - def test_attrs(self): - with _ignore_deprecated(): - m1 = Memory(width=8, depth=4) - self.assertEqual(m1.attrs, {}) - m2 = Memory(width=8, depth=4, attrs={"ram_block": True}) - self.assertEqual(m2.attrs, {"ram_block": True}) - - def test_read_port_transparent(self): - with _ignore_deprecated(): - mem = Memory(width=8, depth=4) - rdport = mem.read_port() - self.assertEqual(rdport.memory, mem) - self.assertEqual(rdport.domain, "sync") - self.assertEqual(rdport.transparent, True) - self.assertEqual(len(rdport.addr), 2) - self.assertEqual(len(rdport.data), 8) - self.assertEqual(len(rdport.en), 1) - self.assertIsInstance(rdport.en, Signal) - self.assertEqual(rdport.en.init, 1) - - def test_read_port_non_transparent(self): - with _ignore_deprecated(): - mem = Memory(width=8, depth=4) - rdport = mem.read_port(transparent=False) - self.assertEqual(rdport.memory, mem) - self.assertEqual(rdport.domain, "sync") - self.assertEqual(rdport.transparent, False) - self.assertEqual(len(rdport.en), 1) - self.assertIsInstance(rdport.en, Signal) - self.assertEqual(rdport.en.init, 1) - - def test_read_port_asynchronous(self): - with _ignore_deprecated(): - mem = Memory(width=8, depth=4) - rdport = mem.read_port(domain="comb") - self.assertEqual(rdport.memory, mem) - self.assertEqual(rdport.domain, "comb") - self.assertEqual(rdport.transparent, True) - self.assertEqual(len(rdport.en), 1) - self.assertIsInstance(rdport.en, Const) - self.assertEqual(rdport.en.value, 1) - - def test_read_port_wrong(self): - with _ignore_deprecated(): - mem = Memory(width=8, depth=4) - with self.assertRaisesRegex(ValueError, - r"^Read port cannot be simultaneously asynchronous and non-transparent$"): - mem.read_port(domain="comb", transparent=False) - - def test_write_port(self): - with _ignore_deprecated(): - mem = Memory(width=8, depth=4) - wrport = mem.write_port() - self.assertEqual(wrport.memory, mem) - self.assertEqual(wrport.domain, "sync") - self.assertEqual(wrport.granularity, 8) - self.assertEqual(len(wrport.addr), 2) - self.assertEqual(len(wrport.data), 8) - self.assertEqual(len(wrport.en), 1) - - def test_write_port_granularity(self): - with _ignore_deprecated(): - mem = Memory(width=8, depth=4) - wrport = mem.write_port(granularity=2) - self.assertEqual(wrport.memory, mem) - self.assertEqual(wrport.domain, "sync") - self.assertEqual(wrport.granularity, 2) - self.assertEqual(len(wrport.addr), 2) - self.assertEqual(len(wrport.data), 8) - self.assertEqual(len(wrport.en), 4) - - def test_write_port_granularity_wrong(self): - with _ignore_deprecated(): - mem = Memory(width=8, depth=4) - with self.assertRaisesRegex(TypeError, - r"^Write port granularity must be a non-negative integer, not -1$"): - mem.write_port(granularity=-1) - with self.assertRaisesRegex(ValueError, - r"^Write port granularity must not be greater than memory width \(10 > 8\)$"): - mem.write_port(granularity=10) - with self.assertRaisesRegex(ValueError, - r"^Write port granularity must divide memory width evenly$"): - mem.write_port(granularity=3) - - def test_deprecated(self): - with self.assertWarnsRegex(DeprecationWarning, - r"^`amaranth.hdl.Memory` is deprecated.*$"): - mem = Memory(width=8, depth=4) - - -class DummyPortTestCase(FHDLTestCase): - def test_name(self): - with _ignore_deprecated(): - p1 = DummyPort(data_width=8, addr_width=2) - self.assertEqual(p1.addr.name, "p1_addr") - p2 = [DummyPort(data_width=8, addr_width=2)][0] - self.assertEqual(p2.addr.name, "dummy_addr") - p3 = DummyPort(data_width=8, addr_width=2, name="foo") - self.assertEqual(p3.addr.name, "foo_addr") - - def test_sizes(self): - with _ignore_deprecated(): - p1 = DummyPort(data_width=8, addr_width=2) - self.assertEqual(p1.addr.width, 2) - self.assertEqual(p1.data.width, 8) - self.assertEqual(p1.en.width, 1) - p2 = DummyPort(data_width=8, addr_width=2, granularity=2) - self.assertEqual(p2.en.width, 4) - - def test_deprecated(self): - with self.assertWarnsRegex(DeprecationWarning, - r"^`DummyPort` is deprecated.*$"): - DummyPort(data_width=8, addr_width=2) diff --git a/tests/test_hdl_xfrm.py b/tests/test_hdl_xfrm.py index 0ab2eeeaa..4c2ff63d4 100644 --- a/tests/test_hdl_xfrm.py +++ b/tests/test_hdl_xfrm.py @@ -6,7 +6,7 @@ from amaranth.hdl._ir import * from amaranth.hdl._xfrm import * from amaranth.hdl._mem import * -from amaranth.hdl._mem import MemoryInstance +from amaranth.lib.memory import Memory from .utils import * from amaranth._utils import _ignore_deprecated @@ -120,7 +120,7 @@ def test_rename_cd_subfragment(self): def test_rename_mem_ports(self): m = Module() with _ignore_deprecated(): - mem = Memory(depth=4, width=16) + mem = Memory(depth=4, shape=16, init=[]) m.submodules.mem = mem mem.read_port(domain="a") mem.read_port(domain="b") @@ -362,23 +362,23 @@ def test_enable_subfragment(self): def test_enable_read_port(self): with _ignore_deprecated(): - mem = Memory(width=8, depth=4) - mem.read_port(transparent=False) + mem = Memory(shape=8, depth=4, init=[]) + mem_r = mem.read_port() f = EnableInserter(self.c1)(mem).elaborate(platform=None) self.assertRepr(f._read_ports[0]._en, """ - (& (sig mem_r_en) (sig c1)) + (& (sig mem_r__en) (sig c1)) """) def test_enable_write_port(self): with _ignore_deprecated(): - mem = Memory(width=8, depth=4) - mem.write_port(granularity=2) + mem = Memory(shape=8, depth=4, init=[]) + mem_w = mem.write_port(granularity=2) f = EnableInserter(self.c1)(mem).elaborate(platform=None) self.assertRepr(f._write_ports[0]._en, """ (switch-value (sig c1) (case 0 (const 4'd0)) - (default (sig mem_w_en)) + (default (sig mem_w__en)) ) """)