Skip to content

Commit 4a56232

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

File tree

11 files changed

+214
-83
lines changed

11 files changed

+214
-83
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.driven_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.driven_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.driven_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.driven_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.driven_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):

amaranth/sim/core.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from ..hdl._cd import *
66
from ..hdl._ir import *
77
from ..hdl._ast import Value, ValueLike
8+
from ..hdl._mem import MemoryData
89
from ._base import BaseEngine
910

1011

@@ -242,6 +243,8 @@ def write_vcd(self, vcd_file, gtkw_file=None, *, traces=(), fs_per_delta=0):
242243
for trace in traces:
243244
if isinstance(trace, ValueLike):
244245
trace_cast = Value.cast(trace)
246+
if isinstance(trace_cast, MemoryData._Row):
247+
continue
245248
for trace_signal in trace_cast._rhs_signals():
246249
if trace_signal.name == "":
247250
if trace_signal is trace:

amaranth/sim/pysim.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,28 @@ def __init__(self, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=
8282
for trace in traces:
8383
if isinstance(trace, ValueLike):
8484
trace = Value.cast(trace)
85-
for trace_signal in trace._rhs_signals():
86-
if trace_signal not in signal_names:
87-
if trace_signal.name not in assigned_names:
88-
name = trace_signal.name
85+
if isinstance(trace, MemoryData._Row):
86+
memory = trace._memory
87+
if not memory in memories:
88+
if memory.name not in assigned_names:
89+
name = memory.name
8990
else:
90-
name = f"{trace_signal.name}${len(assigned_names)}"
91+
name = f"{memory.name}${len(assigned_names)}"
9192
assert name not in assigned_names
92-
trace_names[trace_signal] = {("bench", name)}
93+
memories[memory] = ("bench", name)
9394
assigned_names.add(name)
94-
self.traces.append(trace_signal)
95+
self.traces.append(trace)
96+
else:
97+
for trace_signal in trace._rhs_signals():
98+
if trace_signal not in signal_names:
99+
if trace_signal.name not in assigned_names:
100+
name = trace_signal.name
101+
else:
102+
name = f"{trace_signal.name}${len(assigned_names)}"
103+
assert name not in assigned_names
104+
trace_names[trace_signal] = {("bench", name)}
105+
assigned_names.add(name)
106+
self.traces.append(trace_signal)
95107
elif isinstance(trace, MemoryData):
96108
if not trace in memories:
97109
if trace.name not in assigned_names:
@@ -223,13 +235,16 @@ def close(self, timestamp):
223235
self.gtkw_save.dumpfile_size(self.vcd_file.tell())
224236

225237
self.gtkw_save.treeopen("top")
226-
for signal in self.traces:
227-
if isinstance(signal, Signal):
228-
for name in self.gtkw_signal_names[signal]:
238+
for trace in self.traces:
239+
if isinstance(trace, Signal):
240+
for name in self.gtkw_signal_names[trace]:
229241
self.gtkw_save.trace(name)
230-
elif isinstance(signal, MemoryIdentity):
231-
for name in self.gtkw_memory_names[signal]:
242+
elif isinstance(trace, MemoryData):
243+
for name in self.gtkw_memory_names[trace]:
232244
self.gtkw_save.trace(name)
245+
elif isinstance(trace, MemoryData._Row):
246+
name = self.gtkw_memory_names[trace._memory][trace._index]
247+
self.gtkw_save.trace(name)
233248
else:
234249
assert False # :nocov:
235250

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

0 commit comments

Comments
 (0)