11from contextlib import contextmanager
22import itertools
33import re
4+ import os .path
45
56from ..hdl import *
67from ..hdl ._repr import *
@@ -36,7 +37,7 @@ def eval_field(field, signal, value):
3637 else :
3738 raise NotImplementedError # :nocov:
3839
39- def __init__ (self , design , * , vcd_file , gtkw_file = None , traces = (), fs_per_delta = 0 ):
40+ def __init__ (self , design , * , vcd_file , gtkw_file = None , traces = (), fs_per_delta = 0 , processes = () ):
4041 self .fs_per_delta = fs_per_delta
4142
4243 # Although pyvcd is a mandatory dependency, be resilient and import it as needed, so that
@@ -165,6 +166,26 @@ def __init__(self, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=
165166 gtkw_names .append (gtkw_name )
166167
167168
169+ self .vcd_process_vars = {}
170+ if fs_per_delta == 0 :
171+ return # Not useful without delta cycle expansion.
172+ for index , process in enumerate (processes ):
173+ func_name = process .constructor .__name__
174+ func_file = os .path .basename (process .constructor .__code__ .co_filename )
175+ func_line = process .constructor .__code__ .co_firstlineno
176+ for name in (
177+ f"{ process .constructor .__name__ } " ,
178+ f"{ process .constructor .__name__ } !{ func_file } ;{ func_line } " ,
179+ f"{ process .constructor .__name__ } #{ index } " ,
180+ ):
181+ try :
182+ self .vcd_process_vars [process ] = self .vcd_writer .register_var (
183+ scope = ("debug" , "proc" ), name = name , var_type = "string" , size = None ,
184+ init = "(init)" )
185+ break
186+ except KeyError :
187+ pass # try another name
188+
168189 def update_signal (self , timestamp , signal , value ):
169190 for (vcd_var , repr ) in self .vcd_signal_vars .get (signal , ()):
170191 var_value = self .eval_field (repr .value , signal , value )
@@ -176,6 +197,16 @@ def update_memory(self, timestamp, memory, addr, value):
176197 vcd_var = self .vcd_memory_vars [memory ._identity ][addr ]
177198 self .vcd_writer .change (vcd_var , timestamp , value )
178199
200+ def update_process (self , timestamp , process , command ):
201+ try :
202+ vcd_var = self .vcd_process_vars [process ]
203+ except KeyError :
204+ return
205+ # Ensure that the waveform viewer displays a change point even if the previous command is
206+ # the same as the next one.
207+ self .vcd_writer .change (vcd_var , timestamp , "" )
208+ self .vcd_writer .change (vcd_var , timestamp , repr (command ))
209+
179210 def close (self , timestamp ):
180211 if self .vcd_writer is not None :
181212 self .vcd_writer .close (timestamp )
@@ -425,7 +456,7 @@ def add_coroutine_process(self, process, *, default_cmd):
425456
426457 def add_testbench_process (self , process ):
427458 self ._testbenches .append (PyCoroProcess (self ._state , self ._design .fragment .domains , process ,
428- testbench = True ))
459+ testbench = True , on_command = self . _debug_process ))
429460
430461 def reset (self ):
431462 self ._state .reset ()
@@ -462,6 +493,13 @@ def _step_rtl(self):
462493
463494 self ._delta_cycles += 1
464495
496+ def _debug_process (self , process , command ):
497+ for vcd_writer in self ._vcd_writers :
498+ now_plus_deltas = self ._now_plus_deltas (vcd_writer )
499+ vcd_writer .update_process (now_plus_deltas , process , command )
500+
501+ self ._delta_cycles += 1
502+
465503 def _step_tb (self ):
466504 # Run processes waiting for an interval to expire (mainly `add_clock_process()``)
467505 self ._step_rtl ()
@@ -494,7 +532,8 @@ def _now_plus_deltas(self, vcd_writer):
494532 @contextmanager
495533 def write_vcd (self , * , vcd_file , gtkw_file , traces , fs_per_delta ):
496534 vcd_writer = _VCDWriter (self ._design ,
497- vcd_file = vcd_file , gtkw_file = gtkw_file , traces = traces , fs_per_delta = fs_per_delta )
535+ vcd_file = vcd_file , gtkw_file = gtkw_file , traces = traces , fs_per_delta = fs_per_delta ,
536+ processes = self ._testbenches )
498537 try :
499538 self ._vcd_writers .append (vcd_writer )
500539 yield
0 commit comments