Skip to content

Commit 285473b

Browse files
committed
sim._pycoro: coalesce delta cycles after assignments.
Before this commit, each `Assign()` yielded by the testbench coroutine iterated the design to fixed point. This is inefficient, and moreover interferes with effective visualization of delta cycles. After this commit, a yielded `Assign()` flags the coroutine stack frame as having mutated state, and iterates the design to fixed point before explicitly waiting or reading state.
1 parent b122484 commit 285473b

File tree

1 file changed

+27
-17
lines changed

1 file changed

+27
-17
lines changed

amaranth/sim/_pycoro.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def reset(self):
3232
**_ValueCompiler.helpers
3333
}
3434
self.waits_on = SignalSet()
35+
self.command = None
3536

3637
def src_loc(self):
3738
coroutine = self.coroutine
@@ -58,29 +59,38 @@ def run(self):
5859
if self.coroutine is None:
5960
return
6061

61-
self.clear_triggers()
62+
if self.command is None:
63+
self.clear_triggers()
6264

6365
response = None
6466
exception = None
67+
assigned = False
6568
while True:
66-
try:
67-
if exception is None:
68-
command = self.coroutine.send(response)
69-
else:
70-
command = self.coroutine.throw(exception)
71-
except StopIteration:
72-
self.passive = True
73-
self.coroutine = None
74-
return False # no assignment
69+
if self.command is not None:
70+
command, self.command = self.command, None
71+
else:
72+
try:
73+
if exception is None:
74+
command = self.coroutine.send(response)
75+
else:
76+
command = self.coroutine.throw(exception)
77+
except StopIteration:
78+
self.passive = True
79+
self.coroutine = None
80+
return assigned
7581

7682
try:
7783
if command is None:
7884
command = self.default_cmd
85+
if isinstance(command, ValueCastable):
86+
command = Value.cast(command)
7987
response = None
8088
exception = None
8189

82-
if isinstance(command, ValueCastable):
83-
command = Value.cast(command)
90+
if assigned and isinstance(command, (Value, Tick, Settle, Delay, MemorySimRead)):
91+
self.command = command
92+
return True
93+
8494
if isinstance(command, Value):
8595
exec(_RHSValueCompiler.compile(self.state, command, mode="curr"),
8696
self.exec_locals)
@@ -90,7 +100,7 @@ def run(self):
90100
exec(_StatementCompiler.compile(self.state, command),
91101
self.exec_locals)
92102
if isinstance(command, Assign) and self.testbench:
93-
return True # assignment; run a delta cycle
103+
assigned = True
94104

95105
elif type(command) is Tick:
96106
domain = command.domain
@@ -105,17 +115,17 @@ def run(self):
105115
self.add_trigger(domain.clk, trigger=1 if domain.clk_edge == "pos" else 0)
106116
if domain.rst is not None and domain.async_reset:
107117
self.add_trigger(domain.rst, trigger=1)
108-
return False # no assignments
118+
return False
109119

110120
elif type(command) is Settle:
111121
self.state.wait_interval(self, None)
112-
return False # no assignments
122+
return False
113123

114124
elif type(command) is Delay:
115125
# Internal timeline is in 1ps integeral units, intervals are public API and in floating point
116126
interval = int(command.interval * 1e12) if command.interval is not None else None
117127
self.state.wait_interval(self, interval)
118-
return False # no assignments
128+
return False
119129

120130
elif type(command) is Passive:
121131
self.passive = True
@@ -144,7 +154,7 @@ def run(self):
144154
assert isinstance(state, BaseMemoryState)
145155
state.write(addr, data)
146156
if self.testbench:
147-
return True # assignment; run a delta cycle
157+
assigned = True
148158

149159
elif command is None: # only possible if self.default_cmd is None
150160
raise TypeError("Received default command from process {!r} that was added "

0 commit comments

Comments
 (0)