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.
This commit is contained in:
modwizcode 2021-12-12 21:43:20 -06:00 committed by Catherine
parent fab9fb1fea
commit 1ee2482c6b
5 changed files with 29 additions and 14 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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()

View file

@ -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 Amaranth")
timescale="1 ps", comment="Generated by Amaranth")
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):

View file

@ -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,