Skip to content

Commit 6a2e789

Browse files
committed
sim: forbid adding stimuli to a running simulation.
Fixes #1368.
1 parent c649045 commit 6a2e789

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

amaranth/sim/core.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def __init__(self, toplevel, *, engine="pysim"):
8181
self._design = Fragment.get(toplevel, platform=None).prepare()
8282
self._engine = engine(self._design)
8383
self._clocked = set()
84+
self._running = False
8485

8586
def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
8687
"""Add a clock to the simulation.
@@ -103,14 +104,17 @@ def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
103104
a clock domain with that name, and :py:`if_exists` is :py:`False`.
104105
:exc:`~amaranth.hdl.DriverConflict`
105106
If :py:`domain` already has a clock driving it.
107+
:exc:`RuntimeError`
108+
If the simulation has been advanced since its creation or last reset.
106109
"""
110+
if self._running:
111+
raise RuntimeError(r"Cannot add a clock to a running simulation")
107112
if isinstance(domain, ClockDomain):
108113
if (domain.name in self._design.fragment.domains and
109114
domain is not self._design.fragment.domains[domain.name]):
110115
warnings.warn(
111-
f"Adding a clock process that drives a clock domain object "
112-
f"named {domain.name!r}, which is distinct from an identically named domain "
113-
f"in the simulated design",
116+
f"Adding a clock that drives a clock domain object named {domain.name!r}, "
117+
f"which is distinct from an identically named domain in the simulated design",
114118
UserWarning, stacklevel=2)
115119
elif domain in self._design.fragment.domains:
116120
domain = self._design.fragment.domains[domain]
@@ -166,7 +170,14 @@ async def testbench(ctx):
166170
At each point in time, all of the non-waiting testbenches are executed in the order in
167171
which they were added. If two testbenches share state, or must manipulate the design in
168172
a coordinated way, they may rely on this execution order for correctness.
173+
174+
Raises
175+
------
176+
:exc:`RuntimeError`
177+
If the simulation has been advanced since its creation or last reset.
169178
"""
179+
if self._running:
180+
raise RuntimeError(r"Cannot add a testbench to a running simulation")
170181
constructor = self._check_function(constructor, kind="testbench")
171182
if inspect.iscoroutinefunction(constructor):
172183
self._engine.add_async_testbench(self, constructor, background=background)
@@ -216,7 +227,14 @@ async def process(ctx):
216227
if it is not intended to be a part of a circuit), with access to it synchronized using
217228
:py:`await ctx.tick().sample(...)`. Such state is visible in a waveform viewer,
218229
simplifying debugging.
230+
231+
Raises
232+
------
233+
:exc:`RuntimeError`
234+
If the simulation has been advanced since its creation or last reset.
219235
"""
236+
if self._running:
237+
raise RuntimeError(r"Cannot add a process to a running simulation")
220238
process = self._check_function(process, kind="process")
221239
if inspect.iscoroutinefunction(process):
222240
self._engine.add_async_process(self, process)
@@ -311,6 +329,7 @@ def advance(self):
311329
Returns :py:`True` if the simulation contains any critical testbenches or processes, and
312330
:py:`False` otherwise.
313331
"""
332+
self._running = True
314333
return self._engine.advance()
315334

316335
def write_vcd(self, vcd_file, gtkw_file=None, *, traces=(), fs_per_delta=0):
@@ -390,3 +409,4 @@ def reset(self):
390409
* Each clock, testbench, and process is restarted.
391410
"""
392411
self._engine.reset()
412+
self._running = False

tests/test_sim.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,8 +1472,8 @@ def test_bug_566(self):
14721472
dut.d.sync += Signal().eq(0)
14731473
sim = Simulator(dut)
14741474
with self.assertWarnsRegex(UserWarning,
1475-
r"^Adding a clock process that drives a clock domain object named 'sync', "
1476-
r"which is distinct from an identically named domain in the simulated design$"):
1475+
r"^Adding a clock that drives a clock domain object named 'sync', which is "
1476+
r"distinct from an identically named domain in the simulated design$"):
14771477
sim.add_clock(1e-6, domain=ClockDomain("sync"))
14781478

14791479
def test_bug_826(self):
@@ -2009,3 +2009,40 @@ def test_bug_1363(self):
20092009
async def testbench():
20102010
yield Delay()
20112011
sim.add_testbench(testbench())
2012+
2013+
def test_issue_1368(self):
2014+
sim = Simulator(Module())
2015+
async def testbench(ctx):
2016+
sim.add_clock(1e-6)
2017+
sim.add_testbench(testbench)
2018+
with self.assertRaisesRegex(RuntimeError,
2019+
r"^Cannot add a clock to a running simulation$"):
2020+
sim.run()
2021+
2022+
sim = Simulator(Module())
2023+
async def testbench(ctx):
2024+
async def testbench2(ctx):
2025+
pass
2026+
sim.add_testbench(testbench2)
2027+
sim.add_testbench(testbench)
2028+
with self.assertRaisesRegex(RuntimeError,
2029+
r"^Cannot add a testbench to a running simulation$"):
2030+
sim.run()
2031+
2032+
sim = Simulator(Module())
2033+
async def process(ctx):
2034+
async def process2(ctx):
2035+
pass
2036+
sim.add_process(process2)
2037+
sim.add_process(process)
2038+
with self.assertRaisesRegex(RuntimeError,
2039+
r"^Cannot add a process to a running simulation$"):
2040+
sim.run()
2041+
2042+
async def process_empty(ctx):
2043+
pass
2044+
sim = Simulator(Module())
2045+
sim.run()
2046+
sim.reset()
2047+
sim.add_process(process_empty) # should succeed
2048+
sim.run() # suppress 'coroutine was never awaited' warning

0 commit comments

Comments
 (0)