Skip to content

hdl: remove Memory. #1395

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion amaranth/__init__.py
Original file line number Diff line number Diff line change
@@ -20,6 +20,5 @@
"Module",
"ClockDomain",
"Elaboratable", "Fragment", "Instance",
"Memory",
"DomainRenamer", "ResetInserter", "EnableInserter",
]
4 changes: 2 additions & 2 deletions amaranth/hdl/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
264 changes: 3 additions & 261 deletions amaranth/hdl/_mem.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
@@ -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`.

158 changes: 0 additions & 158 deletions tests/test_hdl_mem.py
Original file line number Diff line number Diff line change
@@ -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)
16 changes: 8 additions & 8 deletions tests/test_hdl_xfrm.py
Original file line number Diff line number Diff line change
@@ -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))
)
""")