Skip to content

Commit 3908bd4

Browse files
committed
hdl._mem: implement MemoryData._Row from RFC 62.
1 parent f71bee4 commit 3908bd4

File tree

9 files changed

+184
-71
lines changed

9 files changed

+184
-71
lines changed

amaranth/hdl/_dsl.py

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,90 @@
1313
from ._ir import *
1414
from ._cd import *
1515
from ._xfrm import *
16+
from ._mem import MemoryData
1617

1718

1819
__all__ = ["SyntaxError", "SyntaxWarning", "Module"]
1920

2021

22+
class _Visitor:
23+
def __init__(self):
24+
self.lhs_signals = SignalSet()
25+
26+
def visit_stmt(self, stmt):
27+
if isinstance(stmt, _StatementList):
28+
for s in stmt:
29+
self.visit_stmt(s)
30+
elif isinstance(stmt, Assign):
31+
self.visit_lhs(stmt.lhs)
32+
self.visit_rhs(stmt.rhs)
33+
elif isinstance(stmt, Print):
34+
for chunk in stmt.message._chunks:
35+
if not isinstance(chunk, str):
36+
obj, format_spec = chunk
37+
self.visit_rhs(obj)
38+
elif isinstance(stmt, Property):
39+
self.visit_rhs(stmt.test)
40+
if stmt.message is not None:
41+
for chunk in stmt.message._chunks:
42+
if not isinstance(chunk, str):
43+
obj, format_spec = chunk
44+
self.visit_rhs(obj)
45+
elif isinstance(stmt, Switch):
46+
self.visit_rhs(stmt.test)
47+
for _patterns, stmts, _src_loc in stmt.cases:
48+
self.visit_stmt(stmts)
49+
elif isinstance(stmt, _LateBoundStatement):
50+
pass
51+
else:
52+
assert False # :nocov:
53+
54+
def visit_lhs(self, value):
55+
if isinstance(value, Operator) and value.operator in ("u", "s"):
56+
self.visit_lhs(value.operands[0])
57+
elif isinstance(value, (Signal, ClockSignal, ResetSignal)):
58+
self.lhs_signals.add(value)
59+
elif isinstance(value, Slice):
60+
self.visit_lhs(value.value)
61+
elif isinstance(value, Part):
62+
self.visit_lhs(value.value)
63+
self.visit_rhs(value.offset)
64+
elif isinstance(value, Concat):
65+
for part in value.parts:
66+
self.visit_lhs(part)
67+
elif isinstance(value, SwitchValue):
68+
self.visit_rhs(value.test)
69+
for _patterns, elem in value.cases:
70+
self.visit_lhs(elem)
71+
elif isinstance(value, MemoryData._Row):
72+
raise ValueError(f"Value {value!r} can only be used in simulator processes")
73+
else:
74+
raise ValueError(f"Value {value!r} cannot be assigned to")
75+
76+
def visit_rhs(self, value):
77+
if isinstance(value, (Const, Signal, ClockSignal, ResetSignal, Initial, AnyValue)):
78+
pass
79+
elif isinstance(value, Operator):
80+
for op in value.operands:
81+
self.visit_rhs(op)
82+
elif isinstance(value, Slice):
83+
self.visit_rhs(value.value)
84+
elif isinstance(value, Part):
85+
self.visit_rhs(value.value)
86+
self.visit_rhs(value.offset)
87+
elif isinstance(value, Concat):
88+
for part in value.parts:
89+
self.visit_rhs(part)
90+
elif isinstance(value, SwitchValue):
91+
self.visit_rhs(value.test)
92+
for _patterns, elem in value.cases:
93+
self.visit_rhs(elem)
94+
elif isinstance(value, MemoryData._Row):
95+
raise ValueError(f"Value {value!r} can only be used in simulator processes")
96+
else:
97+
assert False # :nocov:
98+
99+
21100
class _ModuleBuilderProxy:
22101
def __init__(self, builder, depth):
23102
object.__setattr__(self, "_builder", builder)
@@ -545,15 +624,16 @@ def _add_statement(self, assigns, domain, depth):
545624

546625
stmt._MustUse__used = True
547626

548-
if isinstance(stmt, Assign):
549-
for signal in stmt._lhs_signals():
550-
if signal not in self._driving:
551-
self._driving[signal] = domain
552-
elif self._driving[signal] != domain:
553-
cd_curr = self._driving[signal]
554-
raise SyntaxError(
555-
f"Driver-driver conflict: trying to drive {signal!r} from d.{domain}, but it is "
556-
f"already driven from d.{cd_curr}")
627+
visitor = _Visitor()
628+
visitor.visit_stmt(stmt)
629+
for signal in visitor.lhs_signals:
630+
if signal not in self._driving:
631+
self._driving[signal] = domain
632+
elif self._driving[signal] != domain:
633+
cd_curr = self._driving[signal]
634+
raise SyntaxError(
635+
f"Driver-driver conflict: trying to drive {signal!r} from d.{domain}, but it is "
636+
f"already driven from d.{cd_curr}")
557637

558638
self._statements.setdefault(domain, []).append(stmt)
559639

@@ -595,10 +675,14 @@ def elaborate(self, platform):
595675
for domain, statements in self._statements.items():
596676
statements = resolve_statements(statements)
597677
fragment.add_statements(domain, statements)
598-
for signal in statements._lhs_signals():
678+
visitor = _Visitor()
679+
visitor.visit_stmt(statements)
680+
for signal in visitor.lhs_signals:
599681
fragment.add_driver(signal, domain)
600682
fragment.add_statements("comb", self._top_comb_statements)
601-
for signal in self._top_comb_statements._lhs_signals():
683+
visitor = _Visitor()
684+
visitor.visit_stmt(self._top_comb_statements)
685+
for signal in visitor.lhs_signals:
602686
fragment.add_driver(signal, "comb")
603687
fragment.add_domains(self._domains.values())
604688
fragment.generated.update(self._generated)

amaranth/hdl/_mem.py

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,28 @@ def __repr__(self):
102102
return f"MemoryData.Init({self._elems!r}, shape={self._shape!r}, depth={self._depth})"
103103

104104

105+
@final
106+
class _Row(Value):
107+
def __init__(self, memory, index, *, src_loc_at=0):
108+
assert isinstance(memory, MemoryData)
109+
self._memory = memory
110+
self._index = operator.index(index)
111+
assert self._index in range(memory.depth)
112+
super().__init__(src_loc_at=src_loc_at)
113+
114+
def shape(self):
115+
return Shape.cast(self._memory.shape)
116+
117+
def _lhs_signals(self):
118+
# This value cannot ever appear in a design.
119+
raise NotImplementedError # :nocov:
120+
121+
_rhs_signals = _lhs_signals
122+
123+
def __repr__(self):
124+
return f"(memory-row {self._memory!r} {self._index})"
125+
126+
105127
def __init__(self, *, shape, depth, init, src_loc_at=0):
106128
# shape and depth validation is performed in MemoryData.Init()
107129
self._shape = shape
@@ -137,26 +159,14 @@ def __repr__(self):
137159
return f"(memory-data {self.name})"
138160

139161
def __getitem__(self, index):
140-
"""Simulation only."""
141-
return MemorySimRead(self, index)
142-
143-
144-
class MemorySimRead:
145-
def __init__(self, memory, addr):
146-
assert isinstance(memory, MemoryData)
147-
self._memory = memory
148-
self._addr = Value.cast(addr)
149-
150-
def eq(self, value):
151-
return MemorySimWrite(self._memory, self._addr, value)
152-
153-
154-
class MemorySimWrite:
155-
def __init__(self, memory, addr, data):
156-
assert isinstance(memory, MemoryData)
157-
self._memory = memory
158-
self._addr = Value.cast(addr)
159-
self._data = Value.cast(data)
162+
index = operator.index(index)
163+
if index not in range(self.depth):
164+
raise IndexError(f"Index {index} is out of bounds (memory has {self.depth} rows)")
165+
row = MemoryData._Row(self, index)
166+
if isinstance(self.shape, ShapeCastable):
167+
return self.shape(row)
168+
else:
169+
return row
160170

161171

162172
class MemoryInstance(Fragment):
@@ -312,8 +322,7 @@ def write_port(self, *, src_loc_at=0, **kwargs):
312322
return WritePort(self, src_loc_at=1 + src_loc_at, **kwargs)
313323

314324
def __getitem__(self, index):
315-
"""Simulation only."""
316-
return MemorySimRead(self._data, index)
325+
return self._data[index]
317326

318327
def elaborate(self, platform):
319328
f = MemoryInstance(data=self._data, attrs=self.attrs, src_loc=self.src_loc)

amaranth/lib/memory.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections.abc import MutableSequence
44

55
from ..hdl import MemoryData, MemoryInstance, Shape, ShapeCastable, Const
6-
from ..hdl._mem import MemorySimRead, FrozenError
6+
from ..hdl._mem import FrozenError
77
from ..utils import ceil_log2
88
from .._utils import final
99
from .. import tracer
@@ -194,10 +194,6 @@ def elaborate(self, platform):
194194
transparent_for=transparent_for)
195195
return instance
196196

197-
def __getitem__(self, index):
198-
"""Simulation only."""
199-
return self._data[index]
200-
201197

202198
class ReadPort:
203199
"""A read memory port.

amaranth/sim/_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class BaseMemoryState:
3535
def read(self, addr):
3636
raise NotImplementedError # :nocov:
3737

38-
def write(self, addr, value):
38+
def write(self, addr, value, mask=None):
3939
raise NotImplementedError # :nocov:
4040

4141

amaranth/sim/_pycoro.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from ..hdl import *
44
from ..hdl._ast import Statement, Assign, SignalSet, ValueCastable
5-
from ..hdl._mem import MemorySimRead, MemorySimWrite
65
from .core import Tick, Settle, Delay, Passive, Active
76
from ._base import BaseProcess, BaseMemoryState
87
from ._pyeval import eval_value, eval_assign
@@ -123,23 +122,6 @@ def run(self):
123122
elif type(command) is Active:
124123
self.passive = False
125124

126-
elif type(command) is MemorySimRead:
127-
addr = eval_value(self.state, command._addr)
128-
index = self.state.get_memory(command._memory)
129-
state = self.state.slots[index]
130-
assert isinstance(state, BaseMemoryState)
131-
response = state.read(addr)
132-
133-
elif type(command) is MemorySimWrite:
134-
addr = eval_value(self.state, command._addr)
135-
data = eval_value(self.state, command._data)
136-
index = self.state.get_memory(command._memory)
137-
state = self.state.slots[index]
138-
assert isinstance(state, BaseMemoryState)
139-
state.write(addr, data)
140-
if self.testbench:
141-
return True # assignment; run a delta cycle
142-
143125
elif command is None: # only possible if self.default_cmd is None
144126
raise TypeError("Received default command from process {!r} that was added "
145127
"with add_process(); did you mean to use Tick() instead?"

amaranth/sim/_pyeval.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from amaranth.hdl._ast import *
2+
from amaranth.hdl._mem import MemoryData
23

34

45
def _eval_matches(test, patterns):
@@ -118,6 +119,9 @@ def eval_value(sim, value):
118119
elif isinstance(value, Signal):
119120
slot = sim.get_signal(value)
120121
return sim.slots[slot].curr
122+
elif isinstance(value, MemoryData._Row):
123+
slot = sim.get_memory(value._memory)
124+
return sim.slots[slot].read(value._index)
121125
elif isinstance(value, (ResetSignal, ClockSignal, AnyValue, Initial)):
122126
raise ValueError(f"Value {value!r} cannot be used in simulation")
123127
else:
@@ -142,6 +146,15 @@ def _eval_assign_inner(sim, lhs, lhs_start, rhs, rhs_len):
142146
if lhs._signed and (value & (1 << (len(lhs) - 1))):
143147
value |= -1 << (len(lhs) - 1)
144148
sim.slots[slot].set(value)
149+
elif isinstance(lhs, MemoryData._Row):
150+
lhs_stop = lhs_start + rhs_len
151+
if lhs_stop > len(lhs):
152+
lhs_stop = len(lhs)
153+
if lhs_start >= len(lhs):
154+
return
155+
slot = sim.get_memory(lhs._memory)
156+
mask = (1 << lhs_stop) - (1 << lhs_start)
157+
sim.slots[slot].write(lhs._index, rhs << lhs_start, mask)
145158
elif isinstance(lhs, Slice):
146159
_eval_assign_inner(sim, lhs.value, lhs_start + lhs.start, rhs, rhs_len)
147160
elif isinstance(lhs, Concat):

docs/changes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Implemented RFCs
6767
* `RFC 51`_: Add ``ShapeCastable.from_bits`` and ``amaranth.lib.data.Const``
6868
* `RFC 53`_: Low-level I/O primitives
6969
* `RFC 59`_: Get rid of upwards propagation of clock domains
70+
* `RFC 62`_: The `MemoryData`` class
7071

7172

7273
Language changes

tests/test_hdl_mem.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
11
# amaranth: UnusedElaboratable=no
22

3-
from amaranth.hdl._ast import *
3+
from amaranth.hdl import *
44
from amaranth.hdl._mem import *
55
from amaranth._utils import _ignore_deprecated
66

77
from .utils import *
88

99

10+
class MemoryDataTestCase(FHDLTestCase):
11+
def test_repr(self):
12+
data = MemoryData(shape=8, depth=4, init=[])
13+
self.assertRepr(data, "(memory-data data)")
14+
15+
def test_row(self):
16+
data = MemoryData(shape=8, depth=4, init=[])
17+
self.assertRepr(data[2], "(memory-row (memory-data data) 2)")
18+
19+
def test_row_wrong(self):
20+
data = MemoryData(shape=8, depth=4, init=[])
21+
with self.assertRaisesRegex(IndexError,
22+
r"^Index 4 is out of bounds \(memory has 4 rows\)$"):
23+
data[4]
24+
25+
def test_row_elab(self):
26+
data = MemoryData(shape=8, depth=4, init=[])
27+
m = Module()
28+
a = Signal(8)
29+
with self.assertRaisesRegex(ValueError,
30+
r"^Value \(memory-row \(memory-data data\) 0\) can only be used in simulator processes$"):
31+
m.d.comb += a.eq(data[0])
32+
with self.assertRaisesRegex(ValueError,
33+
r"^Value \(memory-row \(memory-data data\) 0\) can only be used in simulator processes$"):
34+
m.d.comb += data[0].eq(1)
35+
1036
class MemoryTestCase(FHDLTestCase):
1137
def test_name(self):
1238
with _ignore_deprecated():

tests/test_sim.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,17 +1043,19 @@ def test_memory_access(self):
10431043
self.setUp_memory()
10441044
with self.assertSimulation(self.m) as sim:
10451045
def process():
1046-
self.assertEqual((yield self.memory[1]), 0x55)
1047-
self.assertEqual((yield self.memory[Const(1)]), 0x55)
1048-
self.assertEqual((yield self.memory[Const(2)]), 0x00)
1049-
yield self.memory[Const(1)].eq(Const(0x33))
1050-
self.assertEqual((yield self.memory[Const(1)]), 0x33)
1046+
self.assertEqual((yield self.memory.data[1]), 0x55)
1047+
self.assertEqual((yield self.memory.data[1]), 0x55)
1048+
self.assertEqual((yield self.memory.data[2]), 0x00)
1049+
yield self.memory.data[1].eq(Const(0x33))
1050+
self.assertEqual((yield self.memory.data[1]), 0x33)
1051+
yield self.memory.data[1][2:5].eq(Const(0x7))
1052+
self.assertEqual((yield self.memory.data[1]), 0x3f)
10511053
yield self.wrport.addr.eq(3)
10521054
yield self.wrport.data.eq(0x22)
10531055
yield self.wrport.en.eq(1)
1054-
self.assertEqual((yield self.memory[Const(3)]), 0)
1056+
self.assertEqual((yield self.memory.data[3]), 0)
10551057
yield Tick()
1056-
self.assertEqual((yield self.memory[Const(3)]), 0x22)
1058+
self.assertEqual((yield self.memory.data[3]), 0x22)
10571059

10581060
sim.add_clock(1e-6)
10591061
sim.add_testbench(process)
@@ -1062,13 +1064,13 @@ def test_memory_access_sync(self):
10621064
self.setUp_memory()
10631065
with self.assertSimulation(self.m) as sim:
10641066
def process():
1065-
self.assertEqual((yield self.memory[1]), 0x55)
1066-
self.assertEqual((yield self.memory[Const(1)]), 0x55)
1067-
self.assertEqual((yield self.memory[Const(2)]), 0x00)
1068-
yield self.memory[Const(1)].eq(Const(0x33))
1069-
self.assertEqual((yield self.memory[Const(1)]), 0x55)
1067+
self.assertEqual((yield self.memory.data[1]), 0x55)
1068+
self.assertEqual((yield self.memory.data[1]), 0x55)
1069+
self.assertEqual((yield self.memory.data[2]), 0x00)
1070+
yield self.memory.data[1].eq(Const(0x33))
1071+
self.assertEqual((yield self.memory.data[1]), 0x55)
10701072
yield Tick()
1071-
self.assertEqual((yield self.memory[Const(1)]), 0x33)
1073+
self.assertEqual((yield self.memory.data[1]), 0x33)
10721074

10731075
sim.add_clock(1e-6)
10741076
sim.add_process(process)

0 commit comments

Comments
 (0)