From d97e74cbf4870c299dca80c7f214187460ea1068 Mon Sep 17 00:00:00 2001 From: modwizcode Date: Sun, 12 Dec 2021 21:43:20 -0600 Subject: [PATCH] sim: represent time internally as 1ps units Using floats to represent simulation time internally isn't ideal instead use 1ps internal units while continuing to use a floating point based interface for compatibility. Fixes #535. --- nmigen/sim/_pyclock.py | 2 +- nmigen/sim/_pycoro.py | 4 +++- nmigen/sim/core.py | 9 +++++++-- nmigen/sim/pysim.py | 15 +++++---------- tests/test_sim.py | 13 +++++++++++++ 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/nmigen/sim/_pyclock.py b/nmigen/sim/_pyclock.py index ca74fb3..816ef90 100644 --- a/nmigen/sim/_pyclock.py +++ b/nmigen/sim/_pyclock.py @@ -31,4 +31,4 @@ class PyClockProcess(BaseProcess): else: clk_state = self.state.slots[self.slot] clk_state.set(not clk_state.curr) - self.state.wait_interval(self, self.period / 2) + self.state.wait_interval(self, self.period // 2) diff --git a/nmigen/sim/_pycoro.py b/nmigen/sim/_pycoro.py index 554187c..6e8d696 100644 --- a/nmigen/sim/_pycoro.py +++ b/nmigen/sim/_pycoro.py @@ -95,7 +95,9 @@ class PyCoroProcess(BaseProcess): return elif type(command) is Delay: - self.state.wait_interval(self, command.interval) + # Internal timeline is in 1ps integeral units, intervals are public API and in floating point + interval = int(command.interval * 1e12) if command.interval is not None else None + self.state.wait_interval(self, interval) return elif type(command) is Passive: diff --git a/nmigen/sim/core.py b/nmigen/sim/core.py index 270f847..64a92be 100644 --- a/nmigen/sim/core.py +++ b/nmigen/sim/core.py @@ -130,12 +130,15 @@ class Simulator: if domain in self._clocked: raise ValueError("Domain {!r} already has a clock driving it" .format(domain.name)) + + # We represent times internally in 1 ps units, but users supply float quantities of seconds + period = int(period * 1e12) if phase is None: # By default, delay the first edge by half period. This causes any synchronous activity # to happen at a non-zero time, distinguishing it from the reset values in the waveform # viewer. - phase = period / 2 + phase = period // 2 self._engine.add_clock_process(domain.clk, phase=phase, period=period) self._clocked.add(domain) @@ -181,6 +184,8 @@ class Simulator: If the simulation stops advancing, this function will never return. """ + # Convert deadline in seconds into internal 1 ps units + deadline = deadline * 1e12 assert self._engine.now <= deadline while (self.advance() or run_passive) and self._engine.now < deadline: pass @@ -204,7 +209,7 @@ class Simulator: traces : iterable of Signal Signals to display traces for. """ - if self._engine.now != 0.0: + if self._engine.now != 0: for file in (vcd_file, gtkw_file): if hasattr(file, "close"): file.close() diff --git a/nmigen/sim/pysim.py b/nmigen/sim/pysim.py index 0d8b659..cc3d948 100644 --- a/nmigen/sim/pysim.py +++ b/nmigen/sim/pysim.py @@ -48,10 +48,6 @@ class _NameExtractor: class _VCDWriter: - @staticmethod - def timestamp_to_vcd(timestamp): - return timestamp * (10 ** 10) # 1/(100 ps) - @staticmethod def decode_to_vcd(signal, value): return signal.decoder(value).expandtabs().replace(" ", "_") @@ -65,7 +61,7 @@ class _VCDWriter: self.vcd_vars = SignalDict() self.vcd_file = vcd_file self.vcd_writer = vcd_file and VCDWriter(self.vcd_file, - timescale="100 ps", comment="Generated by nMigen") + timescale="1 ps", comment="Generated by nMigen") self.gtkw_names = SignalDict() self.gtkw_file = gtkw_file @@ -127,16 +123,15 @@ class _VCDWriter: if vcd_var is None: return - vcd_timestamp = self.timestamp_to_vcd(timestamp) if signal.decoder: var_value = self.decode_to_vcd(signal, value) else: var_value = value - self.vcd_writer.change(vcd_var, vcd_timestamp, var_value) + self.vcd_writer.change(vcd_var, timestamp, var_value) def close(self, timestamp): if self.vcd_writer is not None: - self.vcd_writer.close(self.timestamp_to_vcd(timestamp)) + self.vcd_writer.close(timestamp) if self.gtkw_save is not None: self.gtkw_save.dumpfile(self.vcd_file.name) @@ -158,11 +153,11 @@ class _VCDWriter: class _Timeline: def __init__(self): - self.now = 0.0 + self.now = 0 self.deadlines = dict() def reset(self): - self.now = 0.0 + self.now = 0 self.deadlines.clear() def at(self, run_at, process): diff --git a/tests/test_sim.py b/tests/test_sim.py index 8bf100a..94b1141 100644 --- a/tests/test_sim.py +++ b/tests/test_sim.py @@ -571,6 +571,19 @@ class SimulatorIntegrationTestCase(FHDLTestCase): self.fail() sim.add_process(process) + def test_run_until_fail(self): + m = Module() + s = Signal() + m.d.sync += s.eq(0) + with self.assertRaises(AssertionError): + with self.assertSimulation(m, deadline=100e-6) as sim: + sim.add_clock(1e-6) + def process(): + for _ in range(99): + yield Delay(1e-6) + self.fail() + sim.add_process(process) + def test_add_process_wrong(self): with self.assertSimulation(Module()) as sim: with self.assertRaisesRegex(TypeError, -- 2.30.2