Skip to content

Commit d8fb72a

Browse files
zypwhitequark
authored andcommitted
sim: Use Period to represent time.
1 parent fcbb0d3 commit d8fb72a

14 files changed

+187
-133
lines changed

amaranth/sim/_async.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Annoyingly, this means that the `sphinxcontrib.napoleon` style docstrings cannot be used, and
33
# the Sphinx style docstrings must be used instead. I'm sorry.
44

5+
import warnings
56
import typing
67
import operator
78
from contextlib import contextmanager
@@ -72,9 +73,18 @@ def __init__(self, interval):
7273
# Note: even though it is likely to be a bad idea, ``await ctx.delay(0)`` is accepted.
7374
# This is because, if disallowed, people are likely to do even worse things, such as
7475
# `await ctx.delay(1e-15)` instead.
75-
if interval < 0:
76+
77+
if not isinstance(interval, Period):
78+
# TODO(amaranth-0.7): remove
79+
warnings.warn(
80+
f"Per RFC 66, the `interval` argument of `DelayTrigger()` will only accept a `Period`"
81+
f" in the future.",
82+
DeprecationWarning, stacklevel=1)
83+
interval = Period(s=interval)
84+
85+
if interval < Period():
7686
raise ValueError(f"Delay cannot be negative")
77-
self.interval_fs = round(float(interval) * 1e15)
87+
self.interval = interval
7888

7989

8090
class TriggerCombination:

amaranth/sim/_pycoro.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ def src_loc(coroutine):
102102
raise TypeError(f"Command {command!r} is not allowed in testbenches")
103103

104104
elif type(command) is Settle:
105-
await context.delay(0)
105+
await context.delay(Period())
106106

107107
elif type(command) is Delay:
108-
await context.delay(command.interval or 0)
108+
await context.delay(Period(s=command.interval or 0))
109109

110110
elif type(command) is Passive:
111111
context._process.critical = False

amaranth/sim/core.py

+32-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import warnings
33

44
from .._utils import deprecated
5-
from ..hdl import Value, ValueLike, MemoryData, ClockDomain, Fragment
5+
from ..hdl import Value, ValueLike, MemoryData, ClockDomain, Fragment, Period
66
from ..hdl._ir import DriverConflict
77
from ._base import BaseEngine
88
from ._async import DomainReset, BrokenTrigger
@@ -35,8 +35,8 @@ class Simulator:
3535
3636
2. Processes, clocks, and testbenches are added as necessary: ::
3737
38-
sim.add_clock(1e-6)
39-
sim.add_clock(1e-7, domain="fast")
38+
sim.add_clock(Period(MHz=1))
39+
sim.add_clock(Period(MHz=10), domain="fast")
4040
sim.add_process(process_instr_decoder)
4141
sim.add_testbench(testbench_cpu_execute)
4242
@@ -118,12 +118,25 @@ def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
118118
if domain in self._clocked:
119119
raise DriverConflict(f"Domain {domain.name!r} already has a clock driving it")
120120

121-
period_fs = _seconds_to_femtos(period)
121+
if not isinstance(period, Period):
122+
# TODO(amaranth-0.7): remove
123+
warnings.warn(
124+
f"Per RFC 66, the `period` argument of `add_clock()` will only accept a `Period`"
125+
f" in the future.",
126+
DeprecationWarning, stacklevel=1)
127+
period = Period(s=period)
128+
122129
if phase is None:
123-
phase_fs = _seconds_to_femtos(period / 2)
124-
else:
125-
phase_fs = _seconds_to_femtos(phase)
126-
self._engine.add_clock_process(domain.clk, phase=phase_fs, period=period_fs)
130+
phase = period / 2
131+
elif not isinstance(phase, Period):
132+
# TODO(amaranth-0.7): remove
133+
warnings.warn(
134+
f"Per RFC 66, the `phase` argument of `add_clock()` will only accept a `Period`"
135+
f" (or `None`) in the future.",
136+
DeprecationWarning, stacklevel=1)
137+
phase = Period(s=phase)
138+
139+
self._engine.add_clock_process(domain.clk, phase=phase.femtoseconds, period=period.femtoseconds)
127140
self._clocked.add(domain)
128141

129142
@staticmethod
@@ -315,9 +328,17 @@ def run_until(self, deadline, *, run_passive=None):
315328
f"The `run_passive` argument of `run_until()` has been removed as a part of "
316329
f"transition to RFC 36.",
317330
DeprecationWarning, stacklevel=1)
318-
deadline_fs = _seconds_to_femtos(deadline)
319-
assert self._engine.now <= deadline_fs
320-
while self._engine.now < deadline_fs:
331+
332+
if not isinstance(deadline, Period):
333+
# TODO(amaranth-0.7): remove
334+
warnings.warn(
335+
f"Per RFC 66, the `deadline` argument of `run_until()` will only accept a `Period`"
336+
f" in the future.",
337+
DeprecationWarning, stacklevel=1)
338+
deadline = Period(s=deadline)
339+
340+
assert self._engine.now <= deadline.femtoseconds
341+
while self._engine.now < deadline.femtoseconds:
321342
self.advance()
322343

323344
def advance(self):

amaranth/sim/pysim.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -551,8 +551,8 @@ def waker():
551551
return
552552
self._triggers_hit.add(trigger)
553553
self.activate()
554-
self._engine.state.set_delay_waker(trigger.interval_fs, waker)
555-
self._delay_wakers[waker] = trigger.interval_fs
554+
self._engine.state.set_delay_waker(trigger.interval.femtoseconds, waker)
555+
self._delay_wakers[waker] = trigger.interval.femtoseconds
556556

557557
def activate(self):
558558
if self._combination._process.waits_on is self:

docs/_code/up_counter.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def elaborate(self, platform):
4343

4444
return m
4545
# --- TEST ---
46-
from amaranth.sim import Simulator
46+
from amaranth.sim import Simulator, Period
4747

4848

4949
dut = UpCounter(25)
@@ -68,7 +68,7 @@ async def bench(ctx):
6868

6969

7070
sim = Simulator(dut)
71-
sim.add_clock(1e-6) # 1 MHz
71+
sim.add_clock(Period(MHz=1))
7272
sim.add_testbench(bench)
7373
with sim.write_vcd("up_counter.vcd"):
7474
sim.run()

docs/simulator.rst

+12-12
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Simulating a design always requires the three basic steps: constructing the :abb
4848

4949
.. testcode::
5050

51-
from amaranth.sim import Simulator
51+
from amaranth.sim import Simulator, Period
5252

5353
dut = Counter()
5454
sim = Simulator(dut)
@@ -69,9 +69,9 @@ The following code simulates a design and capture the values of all the signals
6969

7070
dut = Counter()
7171
sim = Simulator(dut)
72-
sim.add_clock(1e-6) # 1 µs period, or 1 MHz
72+
sim.add_clock(Period(MHz=1)) # 1 µs period, or 1 MHz
7373
with sim.write_vcd("example1.vcd"):
74-
sim.run_until(1e-6 * 15) # 15 periods of the clock
74+
sim.run_until(Period(MHz=1) * 15) # 15 periods of the clock
7575

7676
The captured data is saved to a :abbr:`VCD` file :file:`example1.vcd`, which can be displayed with a *waveform viewer* such as Surfer_ or GTKWave_:
7777

@@ -122,10 +122,10 @@ The following example simulates a counter and verifies that it can be stopped us
122122
ctx.set(dut.en, True) # assert `dut.en`, enabling the counter again
123123

124124
sim = Simulator(dut)
125-
sim.add_clock(1e-6)
125+
sim.add_clock(Period(MHz=1))
126126
sim.add_testbench(testbench_example2) # add the testbench; run_until() calls the function
127127
with sim.write_vcd("example2.vcd"):
128-
sim.run_until(1e-6 * 15)
128+
sim.run_until(Period(MHz=1) * 15)
129129

130130
Since this circuit is synchronous, and the :meth:`ctx.tick() <SimulatorContext.tick>` method waits until after the circuit has reacted to the clock edge, the change to the :py:`en` input affects the behavior of the circuit on the next clock cycle after the change:
131131

@@ -155,17 +155,17 @@ The following example simulates an adder:
155155
dut = Adder()
156156

157157
async def testbench_example3(ctx):
158-
await ctx.delay(1e-6)
158+
await ctx.delay(Period(us=1))
159159
ctx.set(dut.a, 2)
160160
ctx.set(dut.b, 2)
161161
assert ctx.get(dut.o) == 4
162162

163-
await ctx.delay(1e-6)
163+
await ctx.delay(Period(us=1))
164164
ctx.set(dut.a, 1717)
165165
ctx.set(dut.b, 420)
166166
assert ctx.get(dut.o) == 2137
167167

168-
await ctx.delay(2e-6)
168+
await ctx.delay(Period(us=2))
169169

170170
sim = Simulator(dut)
171171
sim.add_testbench(testbench_example3)
@@ -234,7 +234,7 @@ The following code replaces the :py:`Counter` elaboratable with the equivalent P
234234
ctx.set(en, True)
235235

236236
sim = Simulator(m)
237-
sim.add_clock(1e-6)
237+
sim.add_clock(Period(MHz=1))
238238
sim.add_process(process_example4)
239239
sim.add_testbench(testbench_example4)
240240
with sim.write_vcd("example4.vcd", traces=(cd_sync.clk, cd_sync.rst, en, count)):
@@ -262,17 +262,17 @@ The following code replaces the :py:`Adder` elaboratable with the equivalent Pyt
262262
ctx.set(o, a_value + b_value)
263263

264264
async def testbench_example5(ctx):
265-
await ctx.delay(1e-6)
265+
await ctx.delay(Period(us=1))
266266
ctx.set(a, 2)
267267
ctx.set(b, 2)
268268
assert ctx.get(o) == 4
269269

270-
await ctx.delay(1e-6)
270+
await ctx.delay(Period(us=1))
271271
ctx.set(a, 1717)
272272
ctx.set(b, 420)
273273
assert ctx.get(o) == 2137
274274

275-
await ctx.delay(2e-6)
275+
await ctx.delay(Period(us=2))
276276

277277
sim = Simulator(m)
278278
sim.add_process(process_example5)

docs/stdlib/io.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ All of the following examples assume that one of the built-in FPGA platforms is
6060

6161
.. testcode::
6262

63-
from amaranth.sim import Simulator
63+
from amaranth.sim import Simulator, Period
6464
from amaranth.lib import io, wiring, stream
6565
from amaranth.lib.wiring import In, Out
6666

@@ -249,7 +249,7 @@ For example, consider a simple serializer that accepts a stream of multi-bit dat
249249
assert dout_value == bit, "DUT drives the wrong value on data output"
250250

251251
sim = Simulator(dut)
252-
sim.add_clock(1e-6)
252+
sim.add_clock(Period(MHz=1))
253253
sim.add_testbench(testbench_write_data)
254254
sim.add_testbench(testbench_sample_output)
255255
sim.run()

docs/stdlib/stream.rst

+5-5
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ The pipeline is tested using the :doc:`built-in simulator </simulator>` and the
5959

6060
.. testcode::
6161

62-
from amaranth.sim import Simulator
62+
from amaranth.sim import Simulator, Period
6363

6464
async def stream_get(ctx, stream):
6565
ctx.set(stream.ready, 1)
@@ -150,7 +150,7 @@ In this example, the external device does not provide a way to pause data transm
150150
f"{payload & 0xff:08b} != {expected_word & 0xff:08b} (expected)"
151151

152152
sim = Simulator(dut)
153-
sim.add_clock(1e-6)
153+
sim.add_clock(Period(MHz=1))
154154
sim.add_testbench(testbench_input)
155155
sim.add_testbench(testbench_output)
156156
with sim.write_vcd("stream_serial_receiver.vcd"):
@@ -244,7 +244,7 @@ The serial transmitter accepts a stream of words and provides it to the serial i
244244
await ctx.tick()
245245

246246
sim = Simulator(dut)
247-
sim.add_clock(1e-6)
247+
sim.add_clock(Period(MHz=1))
248248
sim.add_testbench(testbench_input)
249249
sim.add_testbench(testbench_output)
250250
with sim.write_vcd("stream_serial_transmitter.vcd"):
@@ -293,7 +293,7 @@ The value negator accepts a stream of words, negates the 2's complement value of
293293
assert await stream_get(ctx, dut.o_stream) == -17
294294

295295
sim = Simulator(dut)
296-
sim.add_clock(1e-6)
296+
sim.add_clock(Period(MHz=1))
297297
sim.add_testbench(testbench_input)
298298
sim.add_testbench(testbench_output)
299299
with sim.write_vcd("stream_value_negator.vcd"):
@@ -388,7 +388,7 @@ The complete pipeline consists of a serial receiver, a value negator, a FIFO que
388388
ctx.set(dut.o_ssel, 0)
389389

390390
sim = Simulator(dut)
391-
sim.add_clock(1e-6)
391+
sim.add_clock(Period(MHz=1))
392392
sim.add_testbench(testbench_input)
393393
sim.add_testbench(testbench_output)
394394
with sim.write_vcd("stream_example_pipeline.vcd"):

examples/basic/ctr_en.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def elaborate(self, platform):
2525
print(verilog.convert(ctr))
2626

2727
sim = Simulator(ctr)
28-
sim.add_clock(1e-6)
28+
sim.add_clock(Period(MHz=1))
2929
async def testbench_ce(ctx):
3030
await ctx.tick().repeat(3)
3131
ctx.set(ctr.en, 1)
@@ -35,4 +35,4 @@ async def testbench_ce(ctx):
3535
ctx.set(ctr.en,1)
3636
sim.add_testbench(testbench_ce)
3737
with sim.write_vcd("ctrl.vcd", "ctrl.gtkw", traces=[ctr.en, ctr.v, ctr.o]):
38-
sim.run_until(100e-6)
38+
sim.run_until(Period(us=100))

examples/basic/uart.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ def elaborate(self, platform):
104104

105105
args = parser.parse_args()
106106
if args.action == "simulate":
107-
from amaranth.sim import Simulator, Passive
107+
from amaranth.sim import Simulator, Passive, Period
108108

109109
sim = Simulator(uart)
110-
sim.add_clock(1e-6)
110+
sim.add_clock(Period(MHz=1))
111111

112112
async def testbench_loopback(ctx):
113113
async for val in ctx.changed(uart.tx_o):

tests/test_lib_cdc.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_basic(self):
2222
frag = FFSynchronizer(i, o)
2323

2424
sim = Simulator(frag)
25-
sim.add_clock(1e-6)
25+
sim.add_clock(Period(MHz=1))
2626
async def testbench(ctx):
2727
self.assertEqual(ctx.get(o), 0)
2828
ctx.set(i, 1)
@@ -39,7 +39,7 @@ def test_init_value(self):
3939
frag = FFSynchronizer(i, o, init=1)
4040

4141
sim = Simulator(frag)
42-
sim.add_clock(1e-6)
42+
sim.add_clock(Period(MHz=1))
4343
async def testbench(ctx):
4444
self.assertEqual(ctx.get(o), 1)
4545
ctx.set(i, 0)
@@ -58,7 +58,7 @@ def test_reset_value(self):
5858
frag = FFSynchronizer(i, o, reset=1)
5959

6060
sim = Simulator(frag)
61-
sim.add_clock(1e-6)
61+
sim.add_clock(Period(MHz=1))
6262
async def testbench(ctx):
6363
self.assertEqual(ctx.get(o), 1)
6464
ctx.set(i, 0)
@@ -107,7 +107,7 @@ def test_pos_edge(self):
107107
m.submodules += AsyncFFSynchronizer(i, o)
108108

109109
sim = Simulator(m)
110-
sim.add_clock(1e-6)
110+
sim.add_clock(Period(MHz=1))
111111
async def testbench(ctx):
112112
# initial reset
113113
self.assertEqual(ctx.get(i), 0)
@@ -144,7 +144,7 @@ def test_neg_edge(self):
144144
m.submodules += AsyncFFSynchronizer(i, o, async_edge="neg")
145145

146146
sim = Simulator(m)
147-
sim.add_clock(1e-6)
147+
sim.add_clock(Period(MHz=1))
148148
async def testbench(ctx):
149149
# initial reset
150150
self.assertEqual(ctx.get(i), 1)
@@ -192,7 +192,7 @@ def test_basic(self):
192192
m.d.sync += s.eq(0)
193193

194194
sim = Simulator(m)
195-
sim.add_clock(1e-6)
195+
sim.add_clock(Period(MHz=1))
196196
async def testbench(ctx):
197197
# initial reset
198198
self.assertEqual(ctx.get(s), 1)
@@ -237,7 +237,7 @@ def test_smoke(self):
237237
ps = m.submodules.dut = PulseSynchronizer("sync", "sync")
238238

239239
sim = Simulator(m)
240-
sim.add_clock(1e-6)
240+
sim.add_clock(Period(MHz=1))
241241
async def testbench(ctx):
242242
ctx.set(ps.i, 0)
243243
# TODO: think about reset

0 commit comments

Comments
 (0)