Skip to content

Allow visualizing delta cycles in VCD dumps #1232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion amaranth/sim/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,5 @@ def now(self):
def advance(self):
raise NotImplementedError # :nocov:

def write_vcd(self, *, vcd_file, gtkw_file, traces):
def write_vcd(self, *, vcd_file, gtkw_file, traces, fs_per_delta):
raise NotImplementedError # :nocov:
4 changes: 2 additions & 2 deletions amaranth/sim/_pycoro.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ def run(self):
return False # no assignments

elif type(command) is Delay:
# 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
# Internal timeline is in 1 fs integeral units, intervals are public API and in floating point
interval = int(command.interval * 1e15) if command.interval is not None else None
self.state.wait_interval(self, interval)
return False # no assignments

Expand Down
15 changes: 8 additions & 7 deletions amaranth/sim/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,16 @@ def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
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)
# We represent times internally in 1 fs units, but users supply float quantities of seconds
period = int(period * 1e15)

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 initial values in the waveform
# viewer.
phase = period // 2
else:
phase = int(phase * 1e12) + period // 2
phase = int(phase * 1e15) + period // 2
self._engine.add_clock_process(domain.clk, phase=phase, period=period)
self._clocked.add(domain)

Expand Down Expand Up @@ -207,13 +207,13 @@ def run_until(self, deadline, *, run_passive=False):

If the simulation stops advancing, this function will never return.
"""
# Convert deadline in seconds into internal 1 ps units
deadline = deadline * 1e12
# Convert deadline in seconds into internal 1 fs units
deadline = deadline * 1e15
assert self._engine.now <= deadline
while (self.advance() or run_passive) and self._engine.now < deadline:
pass

def write_vcd(self, vcd_file, gtkw_file=None, *, traces=()):
def write_vcd(self, vcd_file, gtkw_file=None, *, traces=(), fs_per_delta=0):
"""Write waveforms to a Value Change Dump file, optionally populating a GTKWave save file.

This method returns a context manager. It can be used as: ::
Expand All @@ -238,4 +238,5 @@ def write_vcd(self, vcd_file, gtkw_file=None, *, traces=()):
file.close()
raise ValueError("Cannot start writing waveforms after advancing simulation time")

return self._engine.write_vcd(vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces)
return self._engine.write_vcd(vcd_file=vcd_file, gtkw_file=gtkw_file,
traces=traces, fs_per_delta=fs_per_delta)
55 changes: 32 additions & 23 deletions amaranth/sim/pysim.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ def eval_field(field, signal, value):
sub = _VCDWriter.eval_field(field.operands[0], signal, value)
return Const(sub, field.shape()).value
else:
raise NotImplementedError
raise NotImplementedError # :nocov:

def __init__(self, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=0):
self.fs_per_delta = fs_per_delta

def __init__(self, design, *, vcd_file, gtkw_file=None, traces=()):
# Although pyvcd is a mandatory dependency, be resilient and import it as needed, so that
# the simulator is still usable if it's not installed for some reason.
import vcd, vcd.gtkw
Expand All @@ -54,7 +56,7 @@ def __init__(self, design, *, vcd_file, gtkw_file=None, traces=()):
self.vcd_memory_vars = {}
self.vcd_file = vcd_file
self.vcd_writer = vcd_file and vcd.VCDWriter(self.vcd_file,
timescale="1 ps", comment="Generated by Amaranth")
timescale="1 fs", comment="Generated by Amaranth")

self.gtkw_signal_names = SignalDict()
self.gtkw_memory_names = {}
Expand Down Expand Up @@ -410,6 +412,7 @@ def __init__(self, design):
self._design = design
self._processes = _FragmentCompiler(self._state)(self._design.fragment)
self._testbenches = []
self._delta_cycles = 0
self._vcd_writers = []

def add_clock_process(self, clock, *, phase, period):
Expand All @@ -429,10 +432,12 @@ def reset(self):
for process in self._processes:
process.reset()

def _step_rtl(self, changed):
def _step_rtl(self):
# Performs the two phases of a delta cycle in a loop:
converged = False
while not converged:
changed = set() if self._vcd_writers else None

# 1. eval: run and suspend every non-waiting process once, queueing signal changes
for process in self._processes:
if process.runnable:
Expand All @@ -442,11 +447,24 @@ def _step_rtl(self, changed):
# 2. commit: apply every queued signal change, waking up any waiting processes
converged = self._state.commit(changed)

def _step_tb(self):
changed = set() if self._vcd_writers else None
for vcd_writer in self._vcd_writers:
now_plus_deltas = self._now_plus_deltas(vcd_writer)
for change in changed:
if isinstance(change, _PySignalState):
signal_state = change
vcd_writer.update_signal(now_plus_deltas,
signal_state.signal, signal_state.curr)
elif isinstance(change, _PyMemoryChange):
vcd_writer.update_memory(now_plus_deltas, change.state.memory,
change.addr, change.state.data[change.addr])
else:
assert False # :nocov:

self._delta_cycles += 1

def _step_tb(self):
# Run processes waiting for an interval to expire (mainly `add_clock_process()``)
self._step_rtl(changed)
self._step_rtl()

# Run testbenches waiting for an interval to expire, or for a signal to change state
converged = False
Expand All @@ -459,19 +477,7 @@ def _step_tb(self):
while testbench.run():
# Testbench has changed simulation state; run processes triggered by that
converged = False
self._step_rtl(changed)

for vcd_writer in self._vcd_writers:
for change in changed:
if isinstance(change, _PySignalState):
signal_state = change
vcd_writer.update_signal(self._timeline.now,
signal_state.signal, signal_state.curr)
elif isinstance(change, _PyMemoryChange):
vcd_writer.update_memory(self._timeline.now, change.state.memory,
change.addr, change.state.data[change.addr])
else:
assert False # :nocov:
self._step_rtl()

def advance(self):
self._step_tb()
Expand All @@ -482,13 +488,16 @@ def advance(self):
def now(self):
return self._timeline.now

def _now_plus_deltas(self, vcd_writer):
return self._timeline.now + self._delta_cycles * vcd_writer.fs_per_delta

@contextmanager
def write_vcd(self, *, vcd_file, gtkw_file, traces):
def write_vcd(self, *, vcd_file, gtkw_file, traces, fs_per_delta):
vcd_writer = _VCDWriter(self._design,
vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces)
vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces, fs_per_delta=fs_per_delta)
try:
self._vcd_writers.append(vcd_writer)
yield
finally:
vcd_writer.close(self._timeline.now)
vcd_writer.close(self._now_plus_deltas(vcd_writer))
self._vcd_writers.remove(vcd_writer)
3 changes: 2 additions & 1 deletion tests/test_sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -1188,7 +1188,8 @@ def testbench_2():
sim = Simulator(Module())
sim.add_testbench(testbench_1)
sim.add_testbench(testbench_2)
sim.run()
with sim.write_vcd("test.vcd", fs_per_delta=1):
sim.run()


class SimulatorRegressionTestCase(FHDLTestCase):
Expand Down