From b83e84c78a27c5799ef43afa3525d7fd1ef8929c Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Fri, 6 May 2022 16:04:24 +0200 Subject: [PATCH] gen/fhdl: Integrate namer from Migen to give us more flexibility on generated verilog names. --- litex/build/efinix/efinity.py | 1 - litex/build/sim/gtkwave.py | 3 +- litex/gen/fhdl/namer.py | 264 ++++++++++++++++++++++++++++++++++ litex/gen/fhdl/verilog.py | 2 +- litex/gen/sim/vcd.py | 3 +- 5 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 litex/gen/fhdl/namer.py diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index 74fcd9437..24d6b3db1 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -18,7 +18,6 @@ import xml.etree.ElementTree as et from migen.fhdl.structure import _Fragment from migen.fhdl.tools import * -from migen.fhdl.namer import build_namespace from migen.fhdl.simplify import FullMemoryWE from litex.build import tools diff --git a/litex/build/sim/gtkwave.py b/litex/build/sim/gtkwave.py index a15fd1ead..897657635 100644 --- a/litex/build/sim/gtkwave.py +++ b/litex/build/sim/gtkwave.py @@ -10,7 +10,8 @@ import re from typing import Optional, Sequence, Any, Callable, Generator, Dict, Tuple from migen import * -from migen.fhdl.namer import Namespace + +from litex.gen.fhdl.namer import Namespace from litex.soc.interconnect import stream diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py new file mode 100644 index 000000000..4586750cb --- /dev/null +++ b/litex/gen/fhdl/namer.py @@ -0,0 +1,264 @@ +# +# This file is part of LiteX (Adapted from Migen for LiteX usage). +# +# This file is Copyright (c) 2013-2014 Sebastien Bourdeauducq +# SPDX-License-Identifier: BSD-2-Clause + +from collections import OrderedDict +from itertools import combinations + +from migen.fhdl.structure import * + + +class _Node: + def __init__(self): + self.signal_count = 0 + self.numbers = set() + self.use_name = False + self.use_number = False + self.children = OrderedDict() + + +def _display_tree(filename, tree): + from migen.util.treeviz import RenderNode + + def _to_render_node(name, node): + children = [_to_render_node(k, v) for k, v in node.children.items()] + if node.use_name: + if node.use_number: + color = (0.5, 0.9, 0.8) + else: + color = (0.8, 0.5, 0.9) + else: + if node.use_number: + color = (0.9, 0.8, 0.5) + else: + color = (0.8, 0.8, 0.8) + label = "{0}\n{1} signals\n{2}".format(name, node.signal_count, node.numbers) + return RenderNode(label, children, color=color) + + top = _to_render_node("top", tree) + top.to_svg(filename) + + +def _build_tree(signals, basic_tree=None): + root = _Node() + for signal in signals: + current_b = basic_tree + current = root + current.signal_count += 1 + for name, number in signal.backtrace: + if basic_tree is None: + use_number = False + else: + current_b = current_b.children[name] + use_number = current_b.use_number + if use_number: + key = (name, number) + else: + key = name + try: + current = current.children[key] + except KeyError: + new = _Node() + current.children[key] = new + current = new + current.numbers.add(number) + if use_number: + current.all_numbers = sorted(current_b.numbers) + current.signal_count += 1 + return root + + +def _set_use_name(node, node_name=""): + cnames = [(k, _set_use_name(v, k)) for k, v in node.children.items()] + for (c1_prefix, c1_names), (c2_prefix, c2_names) in combinations(cnames, 2): + if not c1_names.isdisjoint(c2_names): + node.children[c1_prefix].use_name = True + node.children[c2_prefix].use_name = True + r = set() + for c_prefix, c_names in cnames: + if node.children[c_prefix].use_name: + for c_name in c_names: + r.add((c_prefix, ) + c_name) + else: + r |= c_names + + if node.signal_count > sum(c.signal_count for c in node.children.values()): + node.use_name = True + r.add((node_name, )) + + return r + + +def _name_signal(tree, signal): + elements = [] + treepos = tree + for step_name, step_n in signal.backtrace: + try: + treepos = treepos.children[(step_name, step_n)] + use_number = True + except KeyError: + treepos = treepos.children[step_name] + use_number = False + if treepos.use_name: + elname = step_name + if use_number: + elname += str(treepos.all_numbers.index(step_n)) + elements.append(elname) + return "_".join(elements) + + +def _build_pnd_from_tree(tree, signals): + return dict((signal, _name_signal(tree, signal)) for signal in signals) + + +def _invert_pnd(pnd): + inv_pnd = dict() + for k, v in pnd.items(): + inv_pnd[v] = inv_pnd.get(v, []) + inv_pnd[v].append(k) + return inv_pnd + + +def _list_conflicting_signals(pnd): + inv_pnd = _invert_pnd(pnd) + r = set() + for k, v in inv_pnd.items(): + if len(v) > 1: + r.update(v) + return r + + +def _set_use_number(tree, signals): + for signal in signals: + current = tree + for step_name, step_n in signal.backtrace: + current = current.children[step_name] + current.use_number = current.signal_count > len(current.numbers) and len(current.numbers) > 1 + +_debug = False + + +def _build_pnd_for_group(group_n, signals): + basic_tree = _build_tree(signals) + _set_use_name(basic_tree) + if _debug: + _display_tree("tree{0}_basic.svg".format(group_n), basic_tree) + pnd = _build_pnd_from_tree(basic_tree, signals) + + # If there are conflicts, try splitting the tree by numbers + # on paths taken by conflicting signals. + conflicting_signals = _list_conflicting_signals(pnd) + if conflicting_signals: + _set_use_number(basic_tree, conflicting_signals) + if _debug: + print("namer: using split-by-number strategy (group {0})".format(group_n)) + _display_tree("tree{0}_marked.svg".format(group_n), basic_tree) + numbered_tree = _build_tree(signals, basic_tree) + _set_use_name(numbered_tree) + if _debug: + _display_tree("tree{0}_numbered.svg".format(group_n), numbered_tree) + pnd = _build_pnd_from_tree(numbered_tree, signals) + else: + if _debug: + print("namer: using basic strategy (group {0})".format(group_n)) + + # ...then add number suffixes by DUID + inv_pnd = _invert_pnd(pnd) + duid_suffixed = False + for name, signals in inv_pnd.items(): + if len(signals) > 1: + duid_suffixed = True + for n, signal in enumerate(sorted(signals, key=lambda x: x.duid)): + pnd[signal] += str(n) + if _debug and duid_suffixed: + print("namer: using DUID suffixes (group {0})".format(group_n)) + + return pnd + + +def _build_signal_groups(signals): + r = [] + for signal in signals: + # build chain of related signals + related_list = [] + cur_signal = signal + while cur_signal is not None: + related_list.insert(0, cur_signal) + cur_signal = cur_signal.related + # add to groups + for _ in range(len(related_list) - len(r)): + r.append(set()) + for target_set, source_signal in zip(r, related_list): + target_set.add(source_signal) + # with the algorithm above and a list of all signals, + # a signal appears in all groups of a lower number than its. + # make signals appear only in their group of highest number. + for s1, s2 in zip(r, r[1:]): + s1 -= s2 + return r + + +def _build_pnd(signals): + groups = _build_signal_groups(signals) + gpnds = [_build_pnd_for_group(n, gsignals) for n, gsignals in enumerate(groups)] + + pnd = dict() + for gn, gpnd in enumerate(gpnds): + for signal, name in gpnd.items(): + result = name + cur_gn = gn + cur_signal = signal + while cur_signal.related is not None: + cur_signal = cur_signal.related + cur_gn -= 1 + result = gpnds[cur_gn][cur_signal] + "_" + result + pnd[signal] = result + + return pnd + + +def build_namespace(signals, reserved_keywords=set()): + pnd = _build_pnd(signals) + ns = Namespace(pnd, reserved_keywords) + # register signals with name_override + swno = {signal for signal in signals if signal.name_override is not None} + for signal in sorted(swno, key=lambda x: x.duid): + ns.get_name(signal) + return ns + + +class Namespace: + def __init__(self, pnd, reserved_keywords=set()): + self.counts = {k: 1 for k in reserved_keywords} + self.sigs = {} + self.pnd = pnd + self.clock_domains = dict() + + def get_name(self, sig): + if isinstance(sig, ClockSignal): + sig = self.clock_domains[sig.cd].clk + if isinstance(sig, ResetSignal): + sig = self.clock_domains[sig.cd].rst + if sig is None: + raise ValueError("Attempted to obtain name of non-existent " + "reset signal of domain "+sig.cd) + + if sig.name_override is not None: + sig_name = sig.name_override + else: + sig_name = self.pnd[sig] + try: + n = self.sigs[sig] + except KeyError: + try: + n = self.counts[sig_name] + except KeyError: + n = 0 + self.sigs[sig] = n + self.counts[sig_name] = n + 1 + if n: + return sig_name + "_" + str(n) + else: + return sig_name diff --git a/litex/gen/fhdl/verilog.py b/litex/gen/fhdl/verilog.py index 84282a53c..517727e15 100644 --- a/litex/gen/fhdl/verilog.py +++ b/litex/gen/fhdl/verilog.py @@ -23,10 +23,10 @@ import collections from migen.fhdl.structure import * from migen.fhdl.structure import _Operator, _Slice, _Assign, _Fragment from migen.fhdl.tools import * -from migen.fhdl.namer import build_namespace from migen.fhdl.conv_output import ConvOutput from migen.fhdl.specials import Memory +from litex.gen.fhdl.namer import build_namespace from litex.build.tools import get_litex_git_revision # ------------------------------------------------------------------------------------------------ # diff --git a/litex/gen/sim/vcd.py b/litex/gen/sim/vcd.py index 9c8ea13dd..60aa33d49 100644 --- a/litex/gen/sim/vcd.py +++ b/litex/gen/sim/vcd.py @@ -12,8 +12,7 @@ import os from collections import OrderedDict import shutil -from migen.fhdl.namer import build_namespace - +from litex.gen.fhdl.namer import build_namespace def vcd_codes(): codechars = [chr(i) for i in range(33, 127)]