def test_gtkw_savefile_none(): sio = six.StringIO() sio.name = '/some/path' gtkw = GTKWSave(sio) gtkw.savefile() assert sio.getvalue() == '[savefile] "{}"\n'.format( os.path.abspath('/some/path'))
def __enter__(self): # pyvcd: https://pyvcd.readthedocs.io/en/latest/vcd.gtkw.html from vcd.gtkw import GTKWSave self.file = open(self.savefile, "w") self.gtkw = GTKWSave(self.file) self.gtkw.dumpfile(self.dumpfile) modules = self.prefix.rstrip(".").split(".") for i in range(len(modules)): self.gtkw.treeopen(".".join(modules[:i + 1])) self.gtkw.sst_expanded(True) return self
def _add_sig(g: gtkw.GTKWSave, root_group, signals): i = 0 while i < len(signals): sig = signals[i] if sig[:len(root_group)] == root_group: # in this group if len(sig) - len(root_group) > 1: new_group = sig[len(root_group)] with g.group(new_group, closed=True): i += _add_sig(g, root_group + [new_group], signals[i:]) else: g.trace(".".join(sig)) i += 1 else: break return i
def after_run(self, sim): if self.draw_graph: g = graph(outdir=reg['results-dir'], node_filter=lambda g: not g.child) else: g = None blocking_gears = set() cosim_name = None for sim_gear in sim.sim_gears: if isinstance(sim_gear, SimSocket): cosim_name = sim_gear.gear.name break if cosim_name and self.cosim_check: self.cosim_activity(g, cosim_name) self.sim_gears_activity(g, sim, blocking_gears) if self.draw_graph: outdir = reg['results-dir'] g.graph.write_svg(os.path.join(outdir, 'proba.svg')) try: vcd_writer = reg['VCD'] except KeyError: return with open(os.path.join(outdir, 'issue.gtkw'), 'w') as f: gtkw = GTKWSave(f) for module in blocking_gears: module_sav(gtkw, module, vcd_writer.vcd_vars)
def tcl2gtkw(tcl_wave, tcl_init_files: List[str], gtkw, ghw: Path) -> List[str]: hierarchy = ghw_parse.parse(ghw) with open(gtkw, 'wt') as f: gtkw = GTKWSave(f) gtkw.zoom_markers(-27.0) c = TclFuncs(gtkw, hierarchy) c.tcl.eval( 'set SILENT_SANITY "false"') # TODO: propagate from YML config c.tcl.createcommand('vunit_help', lambda: None) for tcl in tcl_init_files: c.source(tcl) c.tcl.createcommand('run_simulation', lambda: None) c.source(tcl_wave) c.finalize() used_signals = sorted(c.used_signals) return used_signals
def __init__(self, signal_names, *, vcd_file, gtkw_file=None, traces=()): if isinstance(vcd_file, str): vcd_file = open(vcd_file, "wt") if isinstance(gtkw_file, str): gtkw_file = open(gtkw_file, "wt") 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") self.gtkw_names = SignalDict() self.gtkw_file = gtkw_file self.gtkw_save = gtkw_file and GTKWSave(self.gtkw_file) self.traces = [] trace_names = SignalDict() for trace in traces: if trace not in signal_names: trace_names[trace] = trace.name self.traces.append(trace) if self.vcd_writer is None: return for signal, names in itertools.chain(signal_names.items(), trace_names.items()): if signal.decoder: var_type = "string" var_size = 1 var_init = self.decode_to_vcd(signal, signal.reset) else: var_type = "wire" var_size = signal.width var_init = signal.reset for (*var_scope, var_name) in names: suffix = None while True: try: if suffix is None: var_name_suffix = var_name else: var_name_suffix = "{}${}".format(var_name, suffix) vcd_var = self.vcd_writer.register_var( scope=var_scope, name=var_name_suffix, var_type=var_type, size=var_size, init=var_init) break except KeyError: suffix = (suffix or 0) + 1 if signal not in self.vcd_vars: self.vcd_vars[signal] = set() self.vcd_vars[signal].add(vcd_var) if signal not in self.gtkw_names: self.gtkw_names[signal] = (*var_scope, var_name_suffix)
def pre_init(cls, env): analog_kwargs = { 'datafmt': 'dec', 'color': 'cycle', 'extraflags': ['analog_step'] } with open(env.config['sim.gtkw.file'], 'w') as gtkw_file: gtkw = GTKWSave(gtkw_file) gtkw.dumpfile(env.config['sim.vcd.dump_file'], abspath=False) gtkw.trace('customers.active', **analog_kwargs) for i in range(env.config['grocery.num_lanes']): gtkw.trace('grocery.lane{}.queue'.format(i), **analog_kwargs)
def __exit__(self, *args): if not self._run_called: warnings.warn("Simulation created, but not run", UserWarning) if self._vcd_writer: vcd_timestamp = (self._timestamp + self._delta) / self._epsilon self._vcd_writer.close(vcd_timestamp) if self._vcd_file and self._gtkw_file: gtkw_save = GTKWSave(self._gtkw_file) if hasattr(self._vcd_file, "name"): gtkw_save.dumpfile(self._vcd_file.name) if hasattr(self._vcd_file, "tell"): gtkw_save.dumpfile_size(self._vcd_file.tell()) gtkw_save.treeopen("top") gtkw_save.zoom_markers(math.log(self._epsilon / self._fastest_clock) - 14) def add_trace(signal, **kwargs): signal_slot = self._signal_slots[signal] if self._vcd_names[signal_slot] is not None: if len(signal) > 1: suffix = "[{}:0]".format(len(signal) - 1) else: suffix = "" gtkw_save.trace(self._vcd_names[signal_slot] + suffix, **kwargs) for domain, cd in self._domains.items(): with gtkw_save.group("d.{}".format(domain)): if cd.rst is not None: add_trace(cd.rst) add_trace(cd.clk) for signal in self._traces: add_trace(signal) if self._vcd_file: self._vcd_file.close() if self._gtkw_file: self._gtkw_file.close()
def _regex_map(sig: SigTrace, patterns: Sequence[Regex], on_match: Callable[[SigTrace, Regex], Optional[SigTrace]], on_no_match: Callable[[SigTrace], Optional[SigTrace]], remove_bits: bool = True) -> Optional[SigTrace]: # Given `patterns` return `on_match(sig, pattern)` if any pattern matches or else `on_no_match(sig)` alias = sig.alias if remove_bits: # get rid of signal bits (e.g. wb_adr[29:0]) alias = GTKWSave._strip_bits(alias) for pattern in patterns: if pattern.search(alias): return on_match(sig, pattern) return on_no_match(sig)
def _emit_gtkw(self, filename, dump_filename, *, add_clock=True): """ Emits a GTKWave save file to accompany a generated VCD. Parameters: filename -- The filename to write the GTKW save to. dump_filename -- The filename of the VCD that should be opened with this save. add_clock -- True iff a clock signal should be added to the GTKW save. """ with open(filename, 'w') as f: gtkw = GTKWSave(f) # Comments / context. gtkw.comment("Generated by the LUNA ILA.") # Add a reference to the dumpfile we're working with. gtkw.dumpfile(dump_filename) # If we're adding a clock, add it to the top of the view. gtkw.trace('ila.ila_clock') # Add each of our signals to the file. for signal in self.ila.signals: gtkw.trace(f"ila.{signal.name}")
def pre_init(cls, env): # Compose a GTKWave save file that lays-out the various VCD signals in # a meaningful manner. This must be done at pre-init time to allow # sim.gtkw.live to work. analog_kwargs = { 'datafmt': 'dec', 'color': 'cycle', 'extraflags': ['analog_step'], } with open(env.config['sim.gtkw.file'], 'w') as gtkw_file: gtkw = GTKWSave(gtkw_file) gtkw.dumpfile(env.config['sim.vcd.dump_file'], abspath=False) gtkw.treeopen('grocery') gtkw.signals_width(300) gtkw.trace('customers.active', **analog_kwargs) for i in range(env.config['grocery.num_lanes']): with gtkw.group(f'Lane{i}'): scope = f'grocery.lane{i}' gtkw.trace(f'{scope}.customer_queue', **analog_kwargs) gtkw.trace(f'{scope}.feed_belt', **analog_kwargs) gtkw.trace(f'{scope}.bag_area', **analog_kwargs) gtkw.trace(f'{scope}.baggers', **analog_kwargs)
def __exit__(self, *args): if not self._run_called: warnings.warn("Simulation created, but not run", UserWarning) if self._vcd_writer: vcd_timestamp = (self._timestamp + self._delta) / self._epsilon self._vcd_writer.close(vcd_timestamp) if self._vcd_file and self._gtkw_file: gtkw_save = GTKWSave(self._gtkw_file) if hasattr(self._vcd_file, "name"): gtkw_save.dumpfile(self._vcd_file.name) if hasattr(self._vcd_file, "tell"): gtkw_save.dumpfile_size(self._vcd_file.tell()) gtkw_save.treeopen("top") gtkw_save.zoom_markers(math.log(self._epsilon / self._fastest_clock) - 14) def add_trace(signal, **kwargs): signal_slot = self._signal_slots[signal] if self._vcd_names[signal_slot] is not None: if len(signal) > 1 and not signal.decoder: suffix = "[{}:0]".format(len(signal) - 1) else: suffix = "" gtkw_save.trace(self._vcd_names[signal_slot] + suffix, **kwargs) for domain in self._domains: with gtkw_save.group("d.{}".format(domain.name)): if domain.rst is not None: add_trace(domain.rst) add_trace(domain.clk) for signal in self._traces: add_trace(signal) if self._vcd_file: self._vcd_file.close() if self._gtkw_file: self._gtkw_file.close()
class GTKWSave: """Generator of pretty GTKWave savefiles from SoC signals Usage example: ``` builder = Builder(soc, **builder_kwargs) vns = builder.build(run=False, **build_kwargs) with GTKWSave(vns, savefile=savefile, dumpfile=dumpfile) as gtkw: gtkw.clocks() gtkw.fsm_states(soc) gtkw.add(soc.bus.slaves["main_ram"]) ``` """ def __init__(self, vns: Namespace, savefile: str, dumpfile: str, filtersdir: str = None, treeopen: bool = True, prefix: str = "TOP.sim."): """Crate savefile generator for the namespace. `prefix` is prepended to all signal names and defaults to the one used by Litex simulator. """ self.vns = vns # Namespace output of Builder.build, required to resolve signal names self.prefix = prefix self.savefile = savefile self.dumpfile = dumpfile self.filtersdir = filtersdir self.treeopen = treeopen if self.filtersdir is None: self.filtersdir = os.path.dirname(self.savefile) def __enter__(self): # pyvcd: https://pyvcd.readthedocs.io/en/latest/vcd.gtkw.html from vcd.gtkw import GTKWSave self.file = open(self.savefile, "w") self.gtkw = GTKWSave(self.file) self.gtkw.dumpfile(self.dumpfile) if self.treeopen: modules = self.prefix.rstrip(".").split(".") for i in range(len(modules)): if modules[i]: self.gtkw.treeopen(".".join(modules[:i + 1])) self.gtkw.sst_expanded(True) return self def __exit__(self, type, value, traceback): self.file.close() print("\nGenerated GTKWave save file at: {}\n".format(self.savefile)) def name(self, sig: Signal) -> str: bits = "" if len(sig) > 1: bits = "[{}:0]".format(len(sig) - 1) return self.vns.get_name(sig) + bits def signal(self, signal: Signal): self.gtkw.trace(self.prefix + self.name(signal)) def common_prefix(self, names: Sequence[str]) -> str: prefix = os.path.commonprefix(names) last_underscore = prefix.rfind("_") return prefix[:last_underscore + 1] def group(self, signals: Sequence[Signal], group_name: str = None, alias: bool = True, closed: bool = True, mappers: Optional[Sequence[SigMapper]] = None, translation_files: Optional[Sequence[str]] = None, **kwargs): mappers = mappers or [] translation_files = translation_files or {} if len(signals) == 1: return self.signal(signals[0]) names = [self.name(s) for s in signals] common = self.common_prefix(names) make_alias = (lambda n: n[len(common):]) if alias else (lambda n: n) sigs = [ SigTrace(name=n, signal=s, alias=make_alias(n)) for i, (s, n) in enumerate(zip(signals, names)) ] for sig, file in zip(sigs, translation_files): sig.filter_file = file for mapper in mappers: sigs = list(mapper(sigs)) with self.gtkw.group(group_name or common.strip("_"), closed=closed): for s in sigs: self.gtkw.trace(self.prefix + s.name, alias=s.alias, color=s.color, translate_filter_file=s.filter_file, **kwargs) def by_regex(self, regex: Regex, **kwargs): pattern = re.compile(regex) signals = list( filter(lambda sig: pattern.search(self.vns.pnd[sig]), self.vns.pnd.keys())) assert len(signals) > 0, "No match found for {}".format(regex) return self.group(signals, **kwargs) def clocks(self, **kwargs): clks = [cd.clk for cd in self.vns.clock_domains] self.group(clks, group_name="clocks", alias=False, closed=False, **kwargs) def add(self, obj: Any, no_defaults=False, **kwargs): # TODO: add automatic default handlers for Litex types (e.g. WishBone, AXI, ...) def default_mappers(types, mappers): if not no_defaults and isinstance(obj, types): kwargs["mappers"] = DEFAULT_ENDPOINT_MAPPERS + kwargs.get( "mappers", []) if isinstance(obj, Record): # automatic settings for supported Record types default_mappers(stream.Endpoint, DEFAULT_ENDPOINT_MAPPERS) self.group([s for s, _ in obj.iter_flat()], **kwargs) elif isinstance(obj, Signal): self.signal(obj) elif self._is_module_with_attrs(obj, ["sink", "source"], types=stream.Endpoint, required=any): self._add_groupped_attrs( obj, ["sink", "source"], **kwargs) # recurse to Record->Endpoint handler else: raise NotImplementedError(type(obj), obj) def _add_groupped_attrs(self, obj, attrs, **kwargs): # add given attributes of an object in an encapsulating group, with attribute names as subgroup names with self.gtkw.group(kwargs["group_name"], closed=kwargs.get("closed", True)): for attr in attrs: if hasattr(obj, attr): new_kwargs = kwargs.copy() new_kwargs["group_name"] = attr self.add(getattr(obj, attr), **new_kwargs) def make_fsm_state_translation(self, fsm: FSM) -> str: # generate filter file from vcd.gtkw import make_translation_filter translations = list(fsm.decoding.items()) filename = "filter__{}.txt".format( self._strip_bits(self.name(fsm.state))) filepath = os.path.join(self.filtersdir, filename) with open(filepath, 'w') as f: f.write(make_translation_filter(translations, size=len(fsm.state))) return filepath def iter_submodules(self, parent: Module) -> Generator[Module, None, None]: for name, module in getattr(parent, "_submodules", []): yield module yield from self.iter_submodules(module) def make_fsm_state_alias(self, state: Signal): # Try to improve state name, as the defaults are usually hard to decipher. # This will make sure to include the name of the module that has the FSM, # but still there are some issues, e.g. we always add number to all names. alias = "" for name, num in reversed(state.backtrace): if alias.startswith(name): continue if name == "subfragments": break alias = "{}{}_{}".format(name, num, alias) return alias.strip("_") def fsm_states(self, soc: Module, alias: bool = True, **kwargs): fsms = list( filter(lambda module: isinstance(module, FSM), self.iter_submodules(soc))) states = [fsm.state for fsm in fsms] files = [self.make_fsm_state_translation(fsm) for fsm in fsms] if alias: aliases = { state: self.make_fsm_state_alias(state) for state in states } def add_alias(sig): sig.alias = aliases.get(sig.signal, None) return sig kwargs["mappers"] = [lambda sigs: map(add_alias, sigs) ] + kwargs.get("mappers", []) self.group(states, group_name="FSM states", translation_files=files, **kwargs) @staticmethod def _strip_bits(name: str) -> str: if name.endswith("]") and "[" in name: name = name[:name.rfind("[")] return name @staticmethod def _is_module_with_attrs(obj, attrs, types, required) -> bool: if not isinstance(obj, Module): return False available = map( lambda a: hasattr(obj, a) and isinstance(getattr(obj, a), types), attrs) return required(available)
def pre_init(cls, env): # Compose a GTKWave save file that lays-out the various VCD signals in # a meaningful manner. This must be done at pre-init time to allow # sim.gtkw.live to work. analog_kwargs = {'datafmt': 'dec', 'color': 'cycle', 'extraflags': ['analog_step']} with open(env.config['sim.gtkw.file'], 'w') as gtkw_file: gtkw = GTKWSave(gtkw_file) gtkw.dumpfile(env.config['sim.vcd.dump_file'], abspath=False) gtkw.treeopen('grocery') gtkw.signals_width(300) gtkw.trace('customers.active', **analog_kwargs) for i in range(env.config['grocery.num_lanes']): with gtkw.group('Lane{}'.format(i)): scope = 'grocery.lane{}'.format(i) gtkw.trace(scope + '.customer_queue', **analog_kwargs) gtkw.trace(scope + '.feed_belt', **analog_kwargs) gtkw.trace(scope + '.bag_area', **analog_kwargs) gtkw.trace(scope + '.baggers', **analog_kwargs)
def __init__(self, fragment, *, vcd_file, gtkw_file=None, traces=()): if isinstance(vcd_file, str): vcd_file = open(vcd_file, "wt") if isinstance(gtkw_file, str): gtkw_file = open(gtkw_file, "wt") 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") self.gtkw_names = SignalDict() self.gtkw_file = gtkw_file self.gtkw_save = gtkw_file and GTKWSave(self.gtkw_file) self.traces = [] signal_names = _NameExtractor()(fragment) trace_names = SignalDict() for trace in traces: if trace not in signal_names: trace_names[trace] = {("top", trace.name)} self.traces.append(trace) if self.vcd_writer is None: return for signal, names in itertools.chain(signal_names.items(), trace_names.items()): if signal.decoder: var_type = "string" var_size = 1 var_init = self.decode_to_vcd(signal, signal.reset) else: var_type = "wire" var_size = signal.width var_init = signal.reset for (*var_scope, var_name) in names: if ' ' in var_name: raise ValueError( "Variable '{}' cannot contain a space.".format( var_name)) suffix = None while True: try: if suffix is None: var_name_suffix = var_name else: var_name_suffix = "{}${}".format(var_name, suffix) if signal not in self.vcd_vars: vcd_var = self.vcd_writer.register_var( scope=var_scope, name=var_name_suffix, var_type=var_type, size=var_size, init=var_init) self.vcd_vars[signal] = vcd_var else: self.vcd_writer.register_alias( scope=var_scope, name=var_name_suffix, var=self.vcd_vars[signal]) break except KeyError: suffix = (suffix or 0) + 1 if signal not in self.gtkw_names: self.gtkw_names[signal] = (*var_scope, var_name_suffix)