From 1e805a8789114e36364e35c1470bb77ea55bb2bf Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 09:38:17 +0100 Subject: [PATCH 01/18] fhdl/namer: Remove debug and add docstring comments. --- litex/gen/fhdl/namer.py | 168 ++++++++++++++++++++++++++++++---------- 1 file changed, 125 insertions(+), 43 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 2596e17da..1a4b51fac 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -4,44 +4,37 @@ # 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: + """A node in a hierarchy tree used for signal name resolution. + + Attributes: + signal_count (int): The count of signals in this node. + numbers (set): A set containing numbers associated with this node. + use_name (bool): Flag to determine if the node's name should be used in signal naming. + use_number (bool): Flag to determine if the node's number should be used in signal naming. + children (dict): A dictionary of child nodes. + """ 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) - + self.children = {} def _build_tree(signals, basic_tree=None): + """Builds a hierarchical tree of nodes based on the provided signals. + + Parameters: + signals (iterable): An iterable of signals to be organized into a tree. + basic_tree (_Node, optional): A basic tree structure that the new tree is based upon. + + Returns: + _Node: The root node of the constructed hierarchical tree. + """ root = _Node() for signal in signals: current_b = basic_tree @@ -71,6 +64,15 @@ def _build_tree(signals, basic_tree=None): def _set_use_name(node, node_name=""): + """Determines whether names should be used in signal naming by examining child nodes. + + Parameters: + node (_Node): The current node in the tree. + node_name (str): The name of the node, used when the node's name needs to be included. + + Returns: + set: A set of tuples representing the names that are to be used. + """ 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): @@ -92,6 +94,15 @@ def _set_use_name(node, node_name=""): def _name_signal(tree, signal): + """Generates a hierarchical name for a given signal based on the tree structure. + + Parameters: + tree (_Node): The root node of the tree used for name resolution. + signal : The signal object whose name is to be generated. + + Returns: + str: The generated hierarchical name for the signal. + """ elements = [] treepos = tree for step_name, step_n in signal.backtrace: @@ -110,10 +121,27 @@ def _name_signal(tree, signal): def _build_pnd_from_tree(tree, signals): + """Builds a dictionary mapping signals to their hierarchical names from a tree. + + Parameters: + tree (_Node): The tree that contains naming information. + signals (iterable): An iterable of signals that need to be named. + + Returns: + dict: A dictionary where keys are signals and values are their hierarchical names. + """ return dict((signal, _name_signal(tree, signal)) for signal in signals) def _invert_pnd(pnd): + """Inverts a signal-to-name dictionary to a name-to-signals dictionary. + + Parameters: + pnd (dict): A dictionary mapping signals to names. + + Returns: + dict: An inverted dictionary where keys are names and values are lists of signals. + """ inv_pnd = dict() for k, v in pnd.items(): inv_pnd[v] = inv_pnd.get(v, []) @@ -122,6 +150,14 @@ def _invert_pnd(pnd): def _list_conflicting_signals(pnd): + """Lists signals that have conflicting names in the provided mapping. + + Parameters: + pnd (dict): A dictionary mapping signals to names. + + Returns: + set: A set of signals that have name conflicts. + """ inv_pnd = _invert_pnd(pnd) r = set() for k, v in inv_pnd.items(): @@ -131,38 +167,42 @@ def _list_conflicting_signals(pnd): def _set_use_number(tree, signals): + """Sets nodes in the tree to use numbers based on signal counts to resolve name conflicts. + + Parameters: + tree (_Node): The tree that contains naming information. + signals (iterable): An iterable of signals that may have name conflicts. + + Returns: + None + """ 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): + """Builds a signal-to-name dictionary for a specific group of signals. + + Parameters: + group_n (int): The group number. + signals (iterable): The signals within the group. + + Returns: + dict: A dictionary mapping signals to their hierarchical names. + """ 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 @@ -171,13 +211,18 @@ def _build_pnd_for_group(group_n, signals): 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): + """Organizes signals into related groups. + + Parameters: + signals (iterable): An iterable of all signals to be organized. + + Returns: + list: A list of sets, each containing related signals. + """ r = [] for signal in signals: # Build chain of related signals. @@ -199,6 +244,15 @@ def _build_signal_groups(signals): def _build_pnd(signals): + """Builds a complete signal-to-name dictionary using a hierarchical tree. + + Parameters: + signals (iterable): An iterable of all signals to be named. + tree (_Node): The root node of the tree used for name resolution. + + Returns: + dict: A complete dictionary mapping signals to their hierarchical names. + """ groups = _build_signal_groups(signals) gpnds = [_build_pnd_for_group(n, gsignals) for n, gsignals in enumerate(groups)] pnd = dict() @@ -216,6 +270,15 @@ def _build_pnd(signals): def build_namespace(signals, reserved_keywords=set()): + """Constructs a namespace where each signal is given a unique hierarchical name. + + Parameters: + signals (iterable): An iterable of all signals to be named. + reserved_keywords (set, optional): A set of keywords that cannot be used as signal names. + + Returns: + Namespace: An object that contains the mapping of signals to unique names and provides methods to access them. + """ pnd = _build_pnd(signals) ns = Namespace(pnd, reserved_keywords) # Register Signals with name_override. @@ -226,6 +289,25 @@ def build_namespace(signals, reserved_keywords=set()): class Namespace: + """ + A Namespace object manages unique naming for signals within a hardware design. + + It ensures that each signal has a unique, conflict-free name within the design's namespace. This + includes taking into account reserved keywords and handling signals that may share the same name + by default (due to being instances of the same hardware module or component). + + Attributes: + counts (dict): A dictionary to keep track of the number of times a particular name has been used. + sigs (dict): A dictionary mapping signals to a unique identifier to avoid name conflicts. + pnd (dict): The primary name dictionary that maps signals to their base names. + clock_domains (dict): A dictionary managing the names of clock signals within various clock domains. + + Methods: + get_name(sig): Returns a unique name for the given signal. If the signal is associated with a + clock domain, it handles naming appropriately, considering resets and clock signals. For + regular signals, it uses overridden names or constructs names based on the signal's + hierarchical structure. + """ def __init__(self, pnd, reserved_keywords=set()): self.counts = {k: 1 for k in reserved_keywords} self.sigs = {} From 6214aa69af2effa8a14ddeac3ace4c19388597fb Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 10:52:44 +0100 Subject: [PATCH 02/18] gen/fhdl/namer: Simplify _build_tree and add comments. --- litex/gen/fhdl/namer.py | 73 +++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 1a4b51fac..8ba87142e 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -25,43 +25,46 @@ class _Node: self.use_number = False self.children = {} -def _build_tree(signals, basic_tree=None): - """Builds a hierarchical tree of nodes based on the provided signals. +def _build_tree(signals, base_tree=None): + """ + Constructs a hierarchical tree from signals, where each signal's backtrace contributes to the tree structure. Parameters: - signals (iterable): An iterable of signals to be organized into a tree. - basic_tree (_Node, optional): A basic tree structure that the new tree is based upon. + - signals (list): A list of signals to process. + - base_tree (Node, optional): A base tree to refine with number usage information. Returns: - _Node: The root node of the constructed hierarchical tree. + - Node: The root node of the constructed tree. """ 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 + # Iterate over each signal to be included in the tree. + for signal in signals: + current = root + current.signal_count += 1 + current_base = base_tree + + # Traverse or build the hierarchy of nodes based on the signal's backtrace. + for name, number in signal.backtrace: + # Decide whether to use a numbered key based on the base tree. + use_number = False + if current_base: + current_base = current_base.children.get(name) + use_number = current_base.use_number if current_base else False + + # Create the appropriate key for the node. + key = (name, number) if use_number else name + # Use setdefault to either get the existing child node or create a new one. + current = current.children.setdefault(key, _Node()) + # Add the number to the set of numbers associated with this node. + current.numbers.add(number) + # Increment the count of signals that have traversed this node. + current.signal_count += 1 + # If numbering is used, sort and store all numbers associated with the base node. + if use_number: + current.all_numbers = sorted(current_base.numbers) + + return root def _set_use_name(node, node_name=""): """Determines whether names should be used in signal naming by examining child nodes. @@ -192,15 +195,15 @@ def _build_pnd_for_group(group_n, signals): Returns: dict: A dictionary mapping signals to their hierarchical names. """ - basic_tree = _build_tree(signals) - _set_use_name(basic_tree) - pnd = _build_pnd_from_tree(basic_tree, signals) + base_tree = _build_tree(signals) + _set_use_name(base_tree) + pnd = _build_pnd_from_tree(base_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) - numbered_tree = _build_tree(signals, basic_tree) + _set_use_number(base_tree, conflicting_signals) + numbered_tree = _build_tree(signals, base_tree) _set_use_name(numbered_tree) pnd = _build_pnd_from_tree(numbered_tree, signals) # ...then add number suffixes by DUID. From d28b7a1172fd126067b11530e83128dd09806fe9 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 11:32:52 +0100 Subject: [PATCH 03/18] gen/fhdl/namer: Simplify _set_use_name/_build_pnd_from_tree and add comments. --- litex/gen/fhdl/namer.py | 117 ++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 8ba87142e..5bc3fede5 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -67,74 +67,73 @@ def _build_tree(signals, base_tree=None): return root def _set_use_name(node, node_name=""): - """Determines whether names should be used in signal naming by examining child nodes. - - Parameters: - node (_Node): The current node in the tree. - node_name (str): The name of the node, used when the node's name needs to be included. - - Returns: - set: A set of tuples representing the names that are to be used. """ - 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) + Recursively determines if node names should be used to ensure unique signal naming. + """ + required_names = set() # This will accumulate all names that ensure unique identification of signals. + + # Recursively collect names from children, identifying if any naming conflicts occur. + child_name_sets = { + child_name: _set_use_name(child_node, child_name) + for child_name, child_node in node.children.items() + } + + # Check for naming conflicts between all pairs of children. + for (child1_name, names1), (child2_name, names2) in combinations(child_name_sets.items(), 2): + if names1 & names2: # If there's an intersection, we have a naming conflict. + node.children[child1_name].use_name = node.children[child2_name].use_name = True + + # Collect names, prepending child's name if necessary. + for child_name, child_names in child_name_sets.items(): + if node.children[child_name].use_name: + # Prepend the child's name to ensure uniqueness. + required_names.update((child_name,) + name for name in child_names) else: - r |= c_names + required_names.update(child_names) - if node.signal_count > sum(c.signal_count for c in node.children.values()): + # If this node has its own signals, ensure its name is used. + if node.signal_count > sum(child.signal_count for child in node.children.values()): node.use_name = True - r.add((node_name, )) - - return r - - -def _name_signal(tree, signal): - """Generates a hierarchical name for a given signal based on the tree structure. - - Parameters: - tree (_Node): The root node of the tree used for name resolution. - signal : The signal object whose name is to be generated. - - Returns: - str: The generated hierarchical name for the 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) + required_names.add((node_name,)) # Add this node's name only if it has additional signals. + return required_names def _build_pnd_from_tree(tree, signals): - """Builds a dictionary mapping signals to their hierarchical names from a tree. - - Parameters: - tree (_Node): The tree that contains naming information. - signals (iterable): An iterable of signals that need to be named. - - Returns: - dict: A dictionary where keys are signals and values are their hierarchical names. """ - return dict((signal, _name_signal(tree, signal)) for signal in signals) + Constructs a mapping of signals to their names derived from a tree structure. + This mapping is used to identify signals by their unique hierarchical path within the tree. The + tree structure has 'use_name' flags that influence the naming process. + """ + + # Initialize a dictionary to hold the signal names. + pnd = {} + + # Process each signal to build its hierarchical name. + for signal in signals: + # Collect name parts for the hierarchical name. + elements = [] + # Start traversing the tree from the root. + treepos = tree + + # Walk through the signal's history to assemble its name. + for step_name, step_n in signal.backtrace: + # Navigate the tree according to the signal's path. + treepos = treepos.children.get((step_name, step_n)) or treepos.children.get(step_name) + # Check if the number is part of the name based on the tree node. + use_number = step_n in treepos.all_numbers if hasattr(treepos, 'all_numbers') else False + + # If the tree node's name is to be used, add it to the elements. + if treepos.use_name: + # Create the name part, including the number if necessary. + element_name = step_name if not use_number else f"{step_name}{treepos.all_numbers.index(step_n)}" + elements.append(element_name) + + # Combine the name parts into the signal's full name. + pnd[signal] = "_".join(elements) + + # Return the completed name dictionary. + return pnd def _invert_pnd(pnd): """Inverts a signal-to-name dictionary to a name-to-signals dictionary. From 36e47052b2acf8cd00ee1a216d51ef45b2ec19d3 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 11:49:48 +0100 Subject: [PATCH 04/18] gen/fhdl/namer: Simplify _invert_pnd/_list_conflicting_signals/_set_use_number/_build_pnd_for_group and add comments. --- litex/gen/fhdl/namer.py | 99 +++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 5bc3fede5..ba3eac30d 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -136,21 +136,25 @@ def _build_pnd_from_tree(tree, signals): return pnd def _invert_pnd(pnd): - """Inverts a signal-to-name dictionary to a name-to-signals dictionary. + """ + Inverts a signal-to-name dictionary to a name-to-signals dictionary. Parameters: pnd (dict): A dictionary mapping signals to names. Returns: - dict: An inverted dictionary where keys are names and values are lists of signals. + dict: An inverted dictionary where keys are names and values are lists of signals with that name. """ - inv_pnd = dict() - for k, v in pnd.items(): - inv_pnd[v] = inv_pnd.get(v, []) - inv_pnd[v].append(k) + inv_pnd = {} + for signal, name in pnd.items(): + # Get the list of signals for the current name, or initialize it if not present. + signals_with_name = inv_pnd.get(name, []) + # Add the current signal to the list. + signals_with_name.append(signal) + # Place the updated list back in the dictionary. + inv_pnd[name] = signals_with_name return inv_pnd - def _list_conflicting_signals(pnd): """Lists signals that have conflicting names in the provided mapping. @@ -160,29 +164,43 @@ def _list_conflicting_signals(pnd): Returns: set: A set of signals that have name conflicts. """ + # Invert the signal-to-name mapping to a name-to-signals mapping. inv_pnd = _invert_pnd(pnd) - r = set() - for k, v in inv_pnd.items(): - if len(v) > 1: - r.update(v) - return r + # Prepare a set to hold signals with conflicting names. + conflicts = set() + + # Iterate through the inverted dictionary. + for name, signals in inv_pnd.items(): + # If there is more than one signal for this name, it means there is a conflict. + if len(signals) > 1: + # Add all conflicting signals to our set. + conflicts.update(signals) + + # Return the set of all signals that have name conflicts. + return conflicts def _set_use_number(tree, signals): - """Sets nodes in the tree to use numbers based on signal counts to resolve name conflicts. + """ + Updates nodes to use number suffixes to resolve naming conflicts when necessary. Parameters: - tree (_Node): The tree that contains naming information. - signals (iterable): An iterable of signals that may have name conflicts. + tree (_Node): The root node of the naming tree. + signals (iterable): Signals potentially causing naming conflicts. Returns: - None + None: Tree is modified in place. """ 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 + node = tree # Start traversal from the root node. + + # Traverse the signal's path and decide if numbering is needed. + for step_name, _ in signal.backtrace: + node = node.children[step_name] # Proceed to the next node. + # Set use_number if signal count exceeds unique identifiers. + if not node.use_number: + node.use_number = node.signal_count > len(node.numbers) > 1 + # Once use_number is True, it stays True. def _build_pnd_for_group(group_n, signals): """Builds a signal-to-name dictionary for a specific group of signals. @@ -194,26 +212,29 @@ def _build_pnd_for_group(group_n, signals): Returns: dict: A dictionary mapping signals to their hierarchical names. """ - base_tree = _build_tree(signals) - _set_use_name(base_tree) - pnd = _build_pnd_from_tree(base_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(base_tree, conflicting_signals) - numbered_tree = _build_tree(signals, base_tree) - _set_use_name(numbered_tree) - pnd = _build_pnd_from_tree(numbered_tree, signals) - # ...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) - return pnd + # Construct initial naming tree and name dictionary. + tree = _build_tree(signals) + _set_use_name(tree) + name_dict = _build_pnd_from_tree(tree, signals) + + # Address naming conflicts by introducing numbers. + conflicts = _list_conflicting_signals(name_dict) + if conflicts: + _set_use_number(tree, conflicts) + # Rebuild tree and name dictionary if there were conflicts. + tree = _build_tree(signals, tree) + _set_use_name(tree) + name_dict = _build_pnd_from_tree(tree, signals) + + # Disambiguate remaining conflicts using signal's unique identifier (DUID). + inv_name_dict = _invert_pnd(name_dict) + for names, sigs in inv_name_dict.items(): + if len(sigs) > 1: + for idx, sig in enumerate(sorted(sigs, key=lambda s: s.duid)): + name_dict[sig] += f"{idx}" + + return name_dict def _build_signal_groups(signals): From a65d471ed2ffb25cd0e86c7e24db0d411fdf8358 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 11:58:29 +0100 Subject: [PATCH 05/18] gen/fhdl/namer: Simplify _invert_pnd_build_signal_groups/_build_pnd and add comments. --- litex/gen/fhdl/namer.py | 75 +++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index ba3eac30d..6eda0e225 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -246,24 +246,29 @@ def _build_signal_groups(signals): Returns: list: A list of sets, each containing related signals. """ - r = [] + grouped_signals = [] + + # Create groups of related signals. 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 + chain = [] + # Trace back the chain of related signals. + while signal is not None: + chain.insert(0, signal) + signal = signal.related + + # Ensure there's a set for each level of relation. + while len(grouped_signals) < len(chain): + grouped_signals.append(set()) + + # Assign signals to their respective group. + for group, sig in zip(grouped_signals, chain): + group.add(sig) + + # Ensure signals only appear in their most specific group. + for i in range(len(grouped_signals) - 1): + grouped_signals[i] -= grouped_signals[i + 1] + + return grouped_signals def _build_pnd(signals): @@ -276,19 +281,31 @@ def _build_pnd(signals): Returns: dict: A complete dictionary mapping signals to their hierarchical names. """ + # Group the signals based on their relationships. 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 + + # Generate a name mapping for each group. + group_pnd_mappings = [_build_pnd_for_group(group_number, group_signals) + for group_number, group_signals in enumerate(groups)] + + # Create the final signal-to-name mapping. + pnd = {} + for group_number, group_pnd in enumerate(group_pnd_mappings): + for signal, name in group_pnd.items(): + # Build the full hierarchical name for each signal. + hierarchical_name = name + current_group_number = group_number + current_signal = signal + + # Traverse up the signal's group relationships to prepend parent names. + while current_signal.related is not None: + current_signal = current_signal.related + current_group_number -= 1 + hierarchical_name = f"{group_pnd_mappings[current_group_number][current_signal]}_{hierarchical_name}" + + # Map the signal to its full hierarchical name. + pnd[signal] = hierarchical_name + return pnd From 9548259a5c0c193b9e4ca86b2cfc4bc6d3f8ccf4 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 12:03:17 +0100 Subject: [PATCH 06/18] gen/fhdl/namer: Simplify build_namespace and add comments. --- litex/gen/fhdl/namer.py | 46 ++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 6eda0e225..d58504f74 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -8,6 +8,8 @@ from itertools import combinations from migen.fhdl.structure import * +# Private Classes/Helpers -------------------------------------------------------------------------- + class _Node: """A node in a hierarchy tree used for signal name resolution. @@ -308,25 +310,7 @@ def _build_pnd(signals): return pnd - -def build_namespace(signals, reserved_keywords=set()): - """Constructs a namespace where each signal is given a unique hierarchical name. - - Parameters: - signals (iterable): An iterable of all signals to be named. - reserved_keywords (set, optional): A set of keywords that cannot be used as signal names. - - Returns: - Namespace: An object that contains the mapping of signals to unique names and provides methods to access them. - """ - 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 - +# Public Classes/Helpers --------------------------------------------------------------------------- class Namespace: """ @@ -392,3 +376,27 @@ class Namespace: # Return Name. return sig_name + suffix + +def build_namespace(signals, reserved_keywords=set()): + """Constructs a namespace where each signal is given a unique hierarchical name. + + Parameters: + signals (iterable): An iterable of all signals to be named. + reserved_keywords (set, optional): A set of keywords that cannot be used as signal names. + + Returns: + Namespace: An object that contains the mapping of signals to unique names and provides methods to access them. + """ + + # Create the primary signal-to-name dictionary. + pnd = _build_pnd(signals) + + # Initialize the namespace with reserved keywords and the primary mapping. + namespace = Namespace(pnd, reserved_keywords) + + # Handle signals with overridden names, ensuring they are processed in a consistent order. + signals_with_name_override = filter(lambda s: s.name_override is not None, signals) + for signal in sorted(signals_with_name_override, key=lambda s: s.duid): + namespace.get_name(signal) + + return namespace \ No newline at end of file From 19a3ab2614395d8ffebb85ddcbedc530eda09f2e Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 12:51:28 +0100 Subject: [PATCH 07/18] gen/fhdl/namer: Improve class/variable names. --- litex/gen/fhdl/namer.py | 140 ++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index d58504f74..4b3c8f688 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -10,7 +10,7 @@ from migen.fhdl.structure import * # Private Classes/Helpers -------------------------------------------------------------------------- -class _Node: +class HierarchyNode: """A node in a hierarchy tree used for signal name resolution. Attributes: @@ -27,18 +27,18 @@ class _Node: self.use_number = False self.children = {} -def _build_tree(signals, base_tree=None): +def build_hierarchy_tree(signals, base_tree=None): """ Constructs a hierarchical tree from signals, where each signal's backtrace contributes to the tree structure. Parameters: - - signals (list): A list of signals to process. - - base_tree (Node, optional): A base tree to refine with number usage information. + - signals (list): A list of signals to process. + - base_tree (HierarchyNode, optional): A base tree to refine with number usage information. Returns: - - Node: The root node of the constructed tree. + - HierarchyNode: The root node of the constructed tree. """ - root = _Node() + root = HierarchyNode() # Iterate over each signal to be included in the tree. for signal in signals: @@ -57,7 +57,7 @@ def _build_tree(signals, base_tree=None): # Create the appropriate key for the node. key = (name, number) if use_number else name # Use setdefault to either get the existing child node or create a new one. - current = current.children.setdefault(key, _Node()) + current = current.children.setdefault(key, HierarchyNode()) # Add the number to the set of numbers associated with this node. current.numbers.add(number) # Increment the count of signals that have traversed this node. @@ -68,7 +68,7 @@ def _build_tree(signals, base_tree=None): return root -def _set_use_name(node, node_name=""): +def determine_name_usage(node, node_name=""): """ Recursively determines if node names should be used to ensure unique signal naming. """ @@ -76,7 +76,7 @@ def _set_use_name(node, node_name=""): # Recursively collect names from children, identifying if any naming conflicts occur. child_name_sets = { - child_name: _set_use_name(child_node, child_name) + child_name: determine_name_usage(child_node, child_name) for child_name, child_node in node.children.items() } @@ -100,7 +100,7 @@ def _set_use_name(node, node_name=""): return required_names -def _build_pnd_from_tree(tree, signals): +def build_signal_name_dict_from_tree(tree, signals): """ Constructs a mapping of signals to their names derived from a tree structure. @@ -109,7 +109,7 @@ def _build_pnd_from_tree(tree, signals): """ # Initialize a dictionary to hold the signal names. - pnd = {} + name_dict = {} # Process each signal to build its hierarchical name. for signal in signals: @@ -132,48 +132,48 @@ def _build_pnd_from_tree(tree, signals): elements.append(element_name) # Combine the name parts into the signal's full name. - pnd[signal] = "_".join(elements) + name_dict[signal] = "_".join(elements) # Return the completed name dictionary. - return pnd + return name_dict -def _invert_pnd(pnd): +def invert_signal_name_dict(name_dict): """ Inverts a signal-to-name dictionary to a name-to-signals dictionary. Parameters: - pnd (dict): A dictionary mapping signals to names. + name_dict (dict): A dictionary mapping signals to names. Returns: dict: An inverted dictionary where keys are names and values are lists of signals with that name. """ - inv_pnd = {} - for signal, name in pnd.items(): + inverted_dict = {} + for signal, name in name_dict.items(): # Get the list of signals for the current name, or initialize it if not present. - signals_with_name = inv_pnd.get(name, []) + signals_with_name = inverted_dict.get(name, []) # Add the current signal to the list. signals_with_name.append(signal) # Place the updated list back in the dictionary. - inv_pnd[name] = signals_with_name - return inv_pnd + inverted_dict[name] = signals_with_name + return inverted_dict -def _list_conflicting_signals(pnd): +def list_conflicting_signals(name_dict): """Lists signals that have conflicting names in the provided mapping. Parameters: - pnd (dict): A dictionary mapping signals to names. + name_dict (dict): A dictionary mapping signals to names. Returns: set: A set of signals that have name conflicts. """ # Invert the signal-to-name mapping to a name-to-signals mapping. - inv_pnd = _invert_pnd(pnd) + inverted_dict = invert_signal_name_dict(name_dict) # Prepare a set to hold signals with conflicting names. conflicts = set() # Iterate through the inverted dictionary. - for name, signals in inv_pnd.items(): + for name, signals in inverted_dict.items(): # If there is more than one signal for this name, it means there is a conflict. if len(signals) > 1: # Add all conflicting signals to our set. @@ -182,13 +182,13 @@ def _list_conflicting_signals(pnd): # Return the set of all signals that have name conflicts. return conflicts -def _set_use_number(tree, signals): +def set_number_usage(tree, signals): """ Updates nodes to use number suffixes to resolve naming conflicts when necessary. Parameters: - tree (_Node): The root node of the naming tree. - signals (iterable): Signals potentially causing naming conflicts. + tree (HierarchyNode): The root node of the naming tree. + signals (iterable): Signals potentially causing naming conflicts. Returns: None: Tree is modified in place. @@ -204,11 +204,11 @@ def _set_use_number(tree, signals): node.use_number = node.signal_count > len(node.numbers) > 1 # Once use_number is True, it stays True. -def _build_pnd_for_group(group_n, signals): +def build_signal_name_dict_for_group(group_number, signals): """Builds a signal-to-name dictionary for a specific group of signals. Parameters: - group_n (int): The group number. + group_number (int): The group number. signals (iterable): The signals within the group. Returns: @@ -216,21 +216,21 @@ def _build_pnd_for_group(group_n, signals): """ # Construct initial naming tree and name dictionary. - tree = _build_tree(signals) - _set_use_name(tree) - name_dict = _build_pnd_from_tree(tree, signals) + tree = build_hierarchy_tree(signals) + determine_name_usage(tree) + name_dict = build_signal_name_dict_from_tree(tree, signals) # Address naming conflicts by introducing numbers. - conflicts = _list_conflicting_signals(name_dict) + conflicts = list_conflicting_signals(name_dict) if conflicts: - _set_use_number(tree, conflicts) + set_number_usage(tree, conflicts) # Rebuild tree and name dictionary if there were conflicts. - tree = _build_tree(signals, tree) - _set_use_name(tree) - name_dict = _build_pnd_from_tree(tree, signals) + tree = build_hierarchy_tree(signals, tree) + determine_name_usage(tree) + name_dict = build_signal_name_dict_from_tree(tree, signals) # Disambiguate remaining conflicts using signal's unique identifier (DUID). - inv_name_dict = _invert_pnd(name_dict) + inv_name_dict = invert_signal_name_dict(name_dict) for names, sigs in inv_name_dict.items(): if len(sigs) > 1: for idx, sig in enumerate(sorted(sigs, key=lambda s: s.duid)): @@ -239,7 +239,7 @@ def _build_pnd_for_group(group_n, signals): return name_dict -def _build_signal_groups(signals): +def build_signal_groups(signals): """Organizes signals into related groups. Parameters: @@ -272,49 +272,48 @@ def _build_signal_groups(signals): return grouped_signals - -def _build_pnd(signals): +def build_signal_name_dict(signals): """Builds a complete signal-to-name dictionary using a hierarchical tree. Parameters: - signals (iterable): An iterable of all signals to be named. - tree (_Node): The root node of the tree used for name resolution. + signals (iterable): An iterable of all signals to be named. + tree (HierarchyNode): The root node of the tree used for name resolution. Returns: dict: A complete dictionary mapping signals to their hierarchical names. """ # Group the signals based on their relationships. - groups = _build_signal_groups(signals) + groups = build_signal_groups(signals) # Generate a name mapping for each group. - group_pnd_mappings = [_build_pnd_for_group(group_number, group_signals) - for group_number, group_signals in enumerate(groups)] + group_name_dict_mappings = [build_signal_name_dict_for_group(group_number, group_signals) + for group_number, group_signals in enumerate(groups)] # Create the final signal-to-name mapping. - pnd = {} - for group_number, group_pnd in enumerate(group_pnd_mappings): - for signal, name in group_pnd.items(): + name_dict = {} + for group_number, group_name_dict in enumerate(group_name_dict_mappings): + for signal, name in group_name_dict.items(): # Build the full hierarchical name for each signal. - hierarchical_name = name + hierarchical_name = name current_group_number = group_number - current_signal = signal + current_signal = signal # Traverse up the signal's group relationships to prepend parent names. while current_signal.related is not None: - current_signal = current_signal.related + current_signal = current_signal.related current_group_number -= 1 - hierarchical_name = f"{group_pnd_mappings[current_group_number][current_signal]}_{hierarchical_name}" + hierarchical_name = f"{group_name_dict_mappings[current_group_number][current_signal]}_{hierarchical_name}" # Map the signal to its full hierarchical name. - pnd[signal] = hierarchical_name + name_dict[signal] = hierarchical_name - return pnd + return name_dict # Public Classes/Helpers --------------------------------------------------------------------------- -class Namespace: +class SignalNamespace: """ - A Namespace object manages unique naming for signals within a hardware design. + A SignalNamespace object manages unique naming for signals within a hardware design. It ensures that each signal has a unique, conflict-free name within the design's namespace. This includes taking into account reserved keywords and handling signals that may share the same name @@ -323,7 +322,7 @@ class Namespace: Attributes: counts (dict): A dictionary to keep track of the number of times a particular name has been used. sigs (dict): A dictionary mapping signals to a unique identifier to avoid name conflicts. - pnd (dict): The primary name dictionary that maps signals to their base names. + name_dict (dict): The primary name dictionary that maps signals to their base names. clock_domains (dict): A dictionary managing the names of clock signals within various clock domains. Methods: @@ -332,10 +331,10 @@ class Namespace: regular signals, it uses overridden names or constructs names based on the signal's hierarchical structure. """ - def __init__(self, pnd, reserved_keywords=set()): - self.counts = {k: 1 for k in reserved_keywords} - self.sigs = {} - self.pnd = pnd + def __init__(self, name_dict, reserved_keywords=set()): + self.counts = {k: 1 for k in reserved_keywords} + self.sigs = {} + self.name_dict = name_dict self.clock_domains = dict() def get_name(self, sig): @@ -357,9 +356,9 @@ class Namespace: # Use Name's override when set... if sig.name_override is not None: sig_name = sig.name_override - # ... else get Name from pnd. + # ... else get Name from name_dict. else: - sig_name = self.pnd[sig] + sig_name = self.name_dict[sig] # Check/Add numbering suffix when required. # ----------------------------------------- @@ -370,16 +369,15 @@ class Namespace: n = self.counts[sig_name] except KeyError: n = 0 - self.sigs[sig] = n + self.sigs[sig] = n self.counts[sig_name] = n + 1 suffix = "" if n == 0 else f"_{n}" # Return Name. return sig_name + suffix -def build_namespace(signals, reserved_keywords=set()): +def build_signal_namespace(signals, reserved_keywords=set()): """Constructs a namespace where each signal is given a unique hierarchical name. - Parameters: signals (iterable): An iterable of all signals to be named. reserved_keywords (set, optional): A set of keywords that cannot be used as signal names. @@ -389,14 +387,16 @@ def build_namespace(signals, reserved_keywords=set()): """ # Create the primary signal-to-name dictionary. - pnd = _build_pnd(signals) + pnd = build_signal_name_dict(signals) # Initialize the namespace with reserved keywords and the primary mapping. - namespace = Namespace(pnd, reserved_keywords) + namespace = SignalNamespace(pnd, reserved_keywords) # Handle signals with overridden names, ensuring they are processed in a consistent order. signals_with_name_override = filter(lambda s: s.name_override is not None, signals) for signal in sorted(signals_with_name_override, key=lambda s: s.duid): namespace.get_name(signal) - return namespace \ No newline at end of file + return namespace + +build_namespace = build_signal_namespace \ No newline at end of file From 16804acaa8fb997da981dfef751d858761d4759f Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 13:19:19 +0100 Subject: [PATCH 08/18] gen/fhdl/namer: Add update_hierarchy_node function to reduce build_hierarchy_tree complexity. --- litex/gen/fhdl/namer.py | 43 +++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 4b3c8f688..e5c82756a 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -27,6 +27,34 @@ class HierarchyNode: self.use_number = False self.children = {} +def update_hierarchy_node(current, name, number, use_number, current_base): + """ + Updates or creates a hierarchy node based on the current position, name, and number. + If numbering is used, sorts and stores all numbers associated with the base node. + + Parameters: + curren t (HierarchyNode): The current node in the hierarchy. + name (str): The name of the current hierarchy level. + number (int): The number associated with the current hierarchy level. + use_number (bool): Flag indicating whether to use the number in the hierarchy. + current_base (HierarchyNode): The base node for number usage information. + + Returns: + HierarchyNode: The updated or created node. + """ + # Create the appropriate key for the node. + key = (name, number) if use_number else name + # Use setdefault to either get the existing child node or create a new one. + current = current.children.setdefault(key, HierarchyNode()) + # Add the number to the set of numbers associated with this node. + current.numbers.add(number) + # Increment the count of signals that have traversed this node. + current.signal_count += 1 + # If numbering is used, sort and store all numbers associated with the base node. + if use_number and current_base: + current.all_numbers = sorted(current_base.numbers) + return current + def build_hierarchy_tree(signals, base_tree=None): """ Constructs a hierarchical tree from signals, where each signal's backtrace contributes to the tree structure. @@ -52,19 +80,10 @@ def build_hierarchy_tree(signals, base_tree=None): use_number = False if current_base: current_base = current_base.children.get(name) - use_number = current_base.use_number if current_base else False + use_number = current_base.use_number if current_base else False - # Create the appropriate key for the node. - key = (name, number) if use_number else name - # Use setdefault to either get the existing child node or create a new one. - current = current.children.setdefault(key, HierarchyNode()) - # Add the number to the set of numbers associated with this node. - current.numbers.add(number) - # Increment the count of signals that have traversed this node. - current.signal_count += 1 - # If numbering is used, sort and store all numbers associated with the base node. - if use_number: - current.all_numbers = sorted(current_base.numbers) + # Update the current node in the hierarchy. + current = update_hierarchy_node(current, name, number, use_number, current_base) return root From 0efccae8b41bd4e6d586a418c1e9da20f8616a68 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 13:34:23 +0100 Subject: [PATCH 09/18] gen/fhdl/namer: Simplify/Remove some redundancies. --- litex/gen/fhdl/namer.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index e5c82756a..2b720e5c7 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -70,8 +70,7 @@ def build_hierarchy_tree(signals, base_tree=None): # Iterate over each signal to be included in the tree. for signal in signals: - current = root - current.signal_count += 1 + current = root current_base = base_tree # Traverse or build the hierarchy of nodes based on the signal's backtrace. @@ -285,10 +284,6 @@ def build_signal_groups(signals): for group, sig in zip(grouped_signals, chain): group.add(sig) - # Ensure signals only appear in their most specific group. - for i in range(len(grouped_signals) - 1): - grouped_signals[i] -= grouped_signals[i + 1] - return grouped_signals def build_signal_name_dict(signals): @@ -390,10 +385,11 @@ class SignalNamespace: n = 0 self.sigs[sig] = n self.counts[sig_name] = n + 1 - suffix = "" if n == 0 else f"_{n}" + if n > 0: + sig_name += f"_{n}" # Return Name. - return sig_name + suffix + return sig_name def build_signal_namespace(signals, reserved_keywords=set()): """Constructs a namespace where each signal is given a unique hierarchical name. @@ -413,8 +409,6 @@ def build_signal_namespace(signals, reserved_keywords=set()): # Handle signals with overridden names, ensuring they are processed in a consistent order. signals_with_name_override = filter(lambda s: s.name_override is not None, signals) - for signal in sorted(signals_with_name_override, key=lambda s: s.duid): - namespace.get_name(signal) return namespace From c0057672d6e88c16f92410bfe5073847deb9dd83 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 13:43:14 +0100 Subject: [PATCH 10/18] gen/fhdl/namer: Split build_signal_name_dict with build_hierarchical_name and update_name_dict_with_group. --- litex/gen/fhdl/namer.py | 64 ++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 2b720e5c7..b2a09c48b 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -286,6 +286,49 @@ def build_signal_groups(signals): return grouped_signals +def build_hierarchical_name(signal, group_number, group_name_dict_mappings): + """Builds the hierarchical name for a signal. + + Parameters: + signal (Signal): The signal to build the name for. + group_number (int): The group number of the signal. + group_name_dict_mappings (list): The list of all group name dictionaries. + + Returns: + str: The hierarchical name for the signal. + """ + hierarchical_name = group_name_dict_mappings[group_number][signal] + current_group_number = group_number + current_signal = signal + + # Traverse up the signal's group relationships to prepend parent names. + while current_signal.related is not None: + current_signal = current_signal.related + current_group_number -= 1 + parent_name = group_name_dict_mappings[current_group_number][current_signal] + hierarchical_name = f"{parent_name}_{hierarchical_name}" + + return hierarchical_name + +def update_name_dict_with_group(name_dict, group_number, group_name_dict, group_name_dict_mappings): + """Updates the name dictionary with hierarchical names for a specific group. + + Parameters: + name_dict (dict): The dictionary to update. + group_number (int): The current group number. + group_name_dict (dict): The name dictionary for the current group. + group_name_dict_mappings (list): The list of all group name dictionaries. + + Returns: + None: The name_dict is updated in place. + """ + for signal, name in group_name_dict.items(): + hierarchical_name = build_hierarchical_name( + signal, group_number, group_name_dict_mappings + ) + name_dict[signal] = hierarchical_name + + def build_signal_name_dict(signals): """Builds a complete signal-to-name dictionary using a hierarchical tree. @@ -300,26 +343,15 @@ def build_signal_name_dict(signals): groups = build_signal_groups(signals) # Generate a name mapping for each group. - group_name_dict_mappings = [build_signal_name_dict_for_group(group_number, group_signals) - for group_number, group_signals in enumerate(groups)] + group_name_dict_mappings = [ + build_signal_name_dict_for_group(group_number, group_signals) + for group_number, group_signals in enumerate(groups) + ] # Create the final signal-to-name mapping. name_dict = {} for group_number, group_name_dict in enumerate(group_name_dict_mappings): - for signal, name in group_name_dict.items(): - # Build the full hierarchical name for each signal. - hierarchical_name = name - current_group_number = group_number - current_signal = signal - - # Traverse up the signal's group relationships to prepend parent names. - while current_signal.related is not None: - current_signal = current_signal.related - current_group_number -= 1 - hierarchical_name = f"{group_name_dict_mappings[current_group_number][current_signal]}_{hierarchical_name}" - - # Map the signal to its full hierarchical name. - name_dict[signal] = hierarchical_name + update_name_dict_with_group(name_dict, group_number, group_name_dict, group_name_dict_mappings) return name_dict From c8a96b8d7918e6b5f88775c47c7e7a3e571b4625 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 13:52:02 +0100 Subject: [PATCH 11/18] gen/fhdl/namer: Add update method to HierarchyNode to replace update_hierarchy_node. --- litex/gen/fhdl/namer.py | 51 ++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index b2a09c48b..415c0be4e 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -27,33 +27,32 @@ class HierarchyNode: self.use_number = False self.children = {} -def update_hierarchy_node(current, name, number, use_number, current_base): - """ - Updates or creates a hierarchy node based on the current position, name, and number. - If numbering is used, sorts and stores all numbers associated with the base node. + def update(self, name, number, use_number, current_base=None): + """ + Updates or creates a hierarchy node based on the current position, name, and number. + If numbering is used, sorts and stores all numbers associated with the base node. - Parameters: - curren t (HierarchyNode): The current node in the hierarchy. - name (str): The name of the current hierarchy level. - number (int): The number associated with the current hierarchy level. - use_number (bool): Flag indicating whether to use the number in the hierarchy. - current_base (HierarchyNode): The base node for number usage information. + Parameters: + name (str): The name of the current hierarchy level. + number (int): The number associated with the current hierarchy level. + use_number (bool): Flag indicating whether to use the number in the hierarchy. + current_base (HierarchyNode, optional): The base node for number usage information. - Returns: - HierarchyNode: The updated or created node. - """ - # Create the appropriate key for the node. - key = (name, number) if use_number else name - # Use setdefault to either get the existing child node or create a new one. - current = current.children.setdefault(key, HierarchyNode()) - # Add the number to the set of numbers associated with this node. - current.numbers.add(number) - # Increment the count of signals that have traversed this node. - current.signal_count += 1 - # If numbering is used, sort and store all numbers associated with the base node. - if use_number and current_base: - current.all_numbers = sorted(current_base.numbers) - return current + Returns: + HierarchyNode: The updated or created child node. + """ + # Create the appropriate key for the node. + key = (name, number) if use_number else name + # Use setdefault to either get the existing child node or create a new one. + child = self.children.setdefault(key, HierarchyNode()) + # Add the number to the set of numbers associated with this node. + child.numbers.add(number) + # Increment the count of signals that have traversed this node. + child.signal_count += 1 + # If numbering is used, sort and store all numbers associated with the base node. + if use_number and current_base: + child.all_numbers = sorted(current_base.numbers) + return child def build_hierarchy_tree(signals, base_tree=None): """ @@ -82,7 +81,7 @@ def build_hierarchy_tree(signals, base_tree=None): use_number = current_base.use_number if current_base else False # Update the current node in the hierarchy. - current = update_hierarchy_node(current, name, number, use_number, current_base) + current = current.update(name, number, use_number, current_base) return root From 3df23a27f5d3e3215aadd73144d5f977c1997f08 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 13:56:25 +0100 Subject: [PATCH 12/18] gen/fhdl/namer: Avoid deep level of nesting on build_signal_name_dict_for_group. --- litex/gen/fhdl/namer.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 415c0be4e..0df49bc37 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -232,30 +232,37 @@ def build_signal_name_dict_for_group(group_number, signals): dict: A dictionary mapping signals to their hierarchical names. """ + def resolve_conflicts_and_rebuild_tree(): + conflicts = list_conflicting_signals(name_dict) + if conflicts: + set_number_usage(tree, conflicts) + return build_hierarchy_tree(signals, tree) + return tree + + def disambiguate_signals_with_duid(): + inv_name_dict = invert_signal_name_dict(name_dict) + for names, sigs in inv_name_dict.items(): + if len(sigs) > 1: + for idx, sig in enumerate(sorted(sigs, key=lambda s: s.duid)): + name_dict[sig] += f"{idx}" + # Construct initial naming tree and name dictionary. tree = build_hierarchy_tree(signals) determine_name_usage(tree) name_dict = build_signal_name_dict_from_tree(tree, signals) # Address naming conflicts by introducing numbers. - conflicts = list_conflicting_signals(name_dict) - if conflicts: - set_number_usage(tree, conflicts) - # Rebuild tree and name dictionary if there were conflicts. - tree = build_hierarchy_tree(signals, tree) - determine_name_usage(tree) - name_dict = build_signal_name_dict_from_tree(tree, signals) + tree = resolve_conflicts_and_rebuild_tree() + + # Re-determine name usage and rebuild the name dictionary. + determine_name_usage(tree) + name_dict = build_signal_name_dict_from_tree(tree, signals) # Disambiguate remaining conflicts using signal's unique identifier (DUID). - inv_name_dict = invert_signal_name_dict(name_dict) - for names, sigs in inv_name_dict.items(): - if len(sigs) > 1: - for idx, sig in enumerate(sorted(sigs, key=lambda s: s.duid)): - name_dict[sig] += f"{idx}" + disambiguate_signals_with_duid() return name_dict - def build_signal_groups(signals): """Organizes signals into related groups. From 9ce29224a1fb392437ad2476d88956c1a925f20c Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 15:36:08 +0100 Subject: [PATCH 13/18] gen/fhdl/namer: Add all_numbers to HierarchyNode to avoid hasattr use. --- litex/gen/fhdl/namer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 0df49bc37..26ba3eb87 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -26,6 +26,7 @@ class HierarchyNode: self.use_name = False self.use_number = False self.children = {} + self.all_numbers = [] def update(self, name, number, use_number, current_base=None): """ @@ -140,7 +141,7 @@ def build_signal_name_dict_from_tree(tree, signals): # Navigate the tree according to the signal's path. treepos = treepos.children.get((step_name, step_n)) or treepos.children.get(step_name) # Check if the number is part of the name based on the tree node. - use_number = step_n in treepos.all_numbers if hasattr(treepos, 'all_numbers') else False + use_number = step_n in treepos.all_numbers # If the tree node's name is to be used, add it to the elements. if treepos.use_name: From af508fddc51a00658ad8b3b433a30ace71ea8143 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 15:54:19 +0100 Subject: [PATCH 14/18] gen/fhdl/namer: Improve/Simplify SignalNamespace.get_name method. --- litex/gen/fhdl/namer.py | 44 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 26ba3eb87..d063facfc 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -391,18 +391,18 @@ class SignalNamespace: self.clock_domains = dict() def get_name(self, sig): - # Get name of a Clock Signal. - # --------------------------- - if isinstance(sig, ClockSignal): - sig = self.clock_domains[sig.cd].clk - - # Get name of a Reset Signal. - # --------------------------- - if isinstance(sig, ResetSignal): - sig = self.clock_domains[sig.cd].rst + # Handle Clock and Reset Signals. + # ------------------------------- + if isinstance(sig, (ClockSignal, ResetSignal)): + # Retrieve the clock domain from the dictionary. + domain = self.clock_domains.get(sig.cd) + if domain is None: + raise ValueError(f"Clock Domain '{sig.cd}' not found.") + # Assign the appropriate signal from the clock domain. + sig = domain.clk if isinstance(sig, ClockSignal) else domain.rst + # If the signal is None, the clock domain is missing a clock or reset. if sig is None: - msg = f"Clock Domain {sig.cd} is reset-less, can't obtain name" - raise ValueError(msg) + raise ValueError(f"Clock Domain '{sig.cd}' is reset-less, can't obtain name.") # Get name of a Regular Signal. # ----------------------------- @@ -411,19 +411,21 @@ class SignalNamespace: sig_name = sig.name_override # ... else get Name from name_dict. else: - sig_name = self.name_dict[sig] + sig_name = self.name_dict.get(sig) + # If the signal is not in the name_dict, raise an error. + if sig_name is None: + raise ValueError(f"Signal '{sig}' not found in name dictionary.") - # Check/Add numbering suffix when required. - # ----------------------------------------- - try: - n = self.sigs[sig] - except KeyError: - try: - n = self.counts[sig_name] - except KeyError: - n = 0 + + # Check/Add numbering when required. + # ---------------------------------- + # Retrieve the current count for the signal name, defaulting to 0. + n = self.sigs.get(sig) + if n is None: + n = self.counts.get(sig_name, 0) self.sigs[sig] = n self.counts[sig_name] = n + 1 + # If the count is greater than 0, append it to the signal name. if n > 0: sig_name += f"_{n}" From ef4235a5d9261c99830df78020846cc748610820 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 16:21:33 +0100 Subject: [PATCH 15/18] gen/fhdl/namer: Use _ for private functions and remove build_namespace. --- litex/gen/fhdl/namer.py | 119 ++++++++++++++++++++++---------------- litex/gen/fhdl/verilog.py | 8 +-- litex/gen/sim/vcd.py | 4 +- 3 files changed, 76 insertions(+), 55 deletions(-) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index d063facfc..678cd65b7 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -8,9 +8,9 @@ from itertools import combinations from migen.fhdl.structure import * -# Private Classes/Helpers -------------------------------------------------------------------------- +# Hierarchy Node Class ----------------------------------------------------------------------------- -class HierarchyNode: +class _HierarchyNode: """A node in a hierarchy tree used for signal name resolution. Attributes: @@ -34,18 +34,18 @@ class HierarchyNode: If numbering is used, sorts and stores all numbers associated with the base node. Parameters: - name (str): The name of the current hierarchy level. - number (int): The number associated with the current hierarchy level. - use_number (bool): Flag indicating whether to use the number in the hierarchy. - current_base (HierarchyNode, optional): The base node for number usage information. + name (str): The name of the current hierarchy level. + number (int): The number associated with the current hierarchy level. + use_number (bool): Flag indicating whether to use the number in the hierarchy. + current_base (_HierarchyNode, optional): The base node for number usage information. Returns: - HierarchyNode: The updated or created child node. + _HierarchyNode: The updated or created child node. """ # Create the appropriate key for the node. key = (name, number) if use_number else name # Use setdefault to either get the existing child node or create a new one. - child = self.children.setdefault(key, HierarchyNode()) + child = self.children.setdefault(key, _HierarchyNode()) # Add the number to the set of numbers associated with this node. child.numbers.add(number) # Increment the count of signals that have traversed this node. @@ -55,18 +55,20 @@ class HierarchyNode: child.all_numbers = sorted(current_base.numbers) return child -def build_hierarchy_tree(signals, base_tree=None): +# Build Hierarchy Tree Function -------------------------------------------------------------------- + +def _build_hierarchy_tree(signals, base_tree=None): """ Constructs a hierarchical tree from signals, where each signal's backtrace contributes to the tree structure. Parameters: - - signals (list): A list of signals to process. - - base_tree (HierarchyNode, optional): A base tree to refine with number usage information. + - signals (list): A list of signals to process. + - base_tree (_HierarchyNode, optional): A base tree to refine with number usage information. Returns: - - HierarchyNode: The root node of the constructed tree. + - _HierarchyNode: The root node of the constructed tree. """ - root = HierarchyNode() + root = _HierarchyNode() # Iterate over each signal to be included in the tree. for signal in signals: @@ -86,7 +88,9 @@ def build_hierarchy_tree(signals, base_tree=None): return root -def determine_name_usage(node, node_name=""): +# Determine Name Usage Function -------------------------------------------------------------------- + +def _determine_name_usage(node, node_name=""): """ Recursively determines if node names should be used to ensure unique signal naming. """ @@ -94,7 +98,7 @@ def determine_name_usage(node, node_name=""): # Recursively collect names from children, identifying if any naming conflicts occur. child_name_sets = { - child_name: determine_name_usage(child_node, child_name) + child_name: _determine_name_usage(child_node, child_name) for child_name, child_node in node.children.items() } @@ -118,7 +122,9 @@ def determine_name_usage(node, node_name=""): return required_names -def build_signal_name_dict_from_tree(tree, signals): +# Build Signal Name Dict From Tree Function -------------------------------------------------------- + +def _build_signal_name_dict_from_tree(tree, signals): """ Constructs a mapping of signals to their names derived from a tree structure. @@ -155,7 +161,9 @@ def build_signal_name_dict_from_tree(tree, signals): # Return the completed name dictionary. return name_dict -def invert_signal_name_dict(name_dict): +# Invert Signal Name Dict Function ----------------------------------------------------------------- + +def _invert_signal_name_dict(name_dict): """ Inverts a signal-to-name dictionary to a name-to-signals dictionary. @@ -175,7 +183,9 @@ def invert_signal_name_dict(name_dict): inverted_dict[name] = signals_with_name return inverted_dict -def list_conflicting_signals(name_dict): +# List Conflicting Signals Function ---------------------------------------------------------------- + +def _list_conflicting_signals(name_dict): """Lists signals that have conflicting names in the provided mapping. Parameters: @@ -185,7 +195,7 @@ def list_conflicting_signals(name_dict): set: A set of signals that have name conflicts. """ # Invert the signal-to-name mapping to a name-to-signals mapping. - inverted_dict = invert_signal_name_dict(name_dict) + inverted_dict = _invert_signal_name_dict(name_dict) # Prepare a set to hold signals with conflicting names. conflicts = set() @@ -200,13 +210,15 @@ def list_conflicting_signals(name_dict): # Return the set of all signals that have name conflicts. return conflicts -def set_number_usage(tree, signals): +# Set Number Usage Function ------------------------------------------------------------------------ + +def _set_number_usage(tree, signals): """ Updates nodes to use number suffixes to resolve naming conflicts when necessary. Parameters: - tree (HierarchyNode): The root node of the naming tree. - signals (iterable): Signals potentially causing naming conflicts. + tree (_HierarchyNode): The root node of the naming tree. + signals (iterable): Signals potentially causing naming conflicts. Returns: None: Tree is modified in place. @@ -222,7 +234,9 @@ def set_number_usage(tree, signals): node.use_number = node.signal_count > len(node.numbers) > 1 # Once use_number is True, it stays True. -def build_signal_name_dict_for_group(group_number, signals): +# Build Signal Name Dict For Group Function -------------------------------------------------------- + +def _build_signal_name_dict_for_group(group_number, signals): """Builds a signal-to-name dictionary for a specific group of signals. Parameters: @@ -234,37 +248,39 @@ def build_signal_name_dict_for_group(group_number, signals): """ def resolve_conflicts_and_rebuild_tree(): - conflicts = list_conflicting_signals(name_dict) + conflicts = _list_conflicting_signals(name_dict) if conflicts: - set_number_usage(tree, conflicts) - return build_hierarchy_tree(signals, tree) + _set_number_usage(tree, conflicts) + return _build_hierarchy_tree(signals, tree) return tree def disambiguate_signals_with_duid(): - inv_name_dict = invert_signal_name_dict(name_dict) + inv_name_dict = _invert_signal_name_dict(name_dict) for names, sigs in inv_name_dict.items(): if len(sigs) > 1: for idx, sig in enumerate(sorted(sigs, key=lambda s: s.duid)): name_dict[sig] += f"{idx}" # Construct initial naming tree and name dictionary. - tree = build_hierarchy_tree(signals) - determine_name_usage(tree) - name_dict = build_signal_name_dict_from_tree(tree, signals) + tree = _build_hierarchy_tree(signals) + _determine_name_usage(tree) + name_dict = _build_signal_name_dict_from_tree(tree, signals) # Address naming conflicts by introducing numbers. tree = resolve_conflicts_and_rebuild_tree() # Re-determine name usage and rebuild the name dictionary. - determine_name_usage(tree) - name_dict = build_signal_name_dict_from_tree(tree, signals) + _determine_name_usage(tree) + name_dict = _build_signal_name_dict_from_tree(tree, signals) # Disambiguate remaining conflicts using signal's unique identifier (DUID). disambiguate_signals_with_duid() return name_dict -def build_signal_groups(signals): +# Build Signal Groups Function --------------------------------------------------------------------- + +def _build_signal_groups(signals): """Organizes signals into related groups. Parameters: @@ -293,7 +309,9 @@ def build_signal_groups(signals): return grouped_signals -def build_hierarchical_name(signal, group_number, group_name_dict_mappings): +# Build Hierarchical Name Function ----------------------------------------------------------------- + +def _build_hierarchical_name(signal, group_number, group_name_dict_mappings): """Builds the hierarchical name for a signal. Parameters: @@ -317,7 +335,9 @@ def build_hierarchical_name(signal, group_number, group_name_dict_mappings): return hierarchical_name -def update_name_dict_with_group(name_dict, group_number, group_name_dict, group_name_dict_mappings): +# Update Name Dict With Group Function ------------------------------------------------------------- + +def _update_name_dict_with_group(name_dict, group_number, group_name_dict, group_name_dict_mappings): """Updates the name dictionary with hierarchical names for a specific group. Parameters: @@ -330,43 +350,44 @@ def update_name_dict_with_group(name_dict, group_number, group_name_dict, group_ None: The name_dict is updated in place. """ for signal, name in group_name_dict.items(): - hierarchical_name = build_hierarchical_name( + hierarchical_name = _build_hierarchical_name( signal, group_number, group_name_dict_mappings ) name_dict[signal] = hierarchical_name +# Build Signal Name Dict Function ------------------------------------------------------------------ -def build_signal_name_dict(signals): +def _build_signal_name_dict(signals): """Builds a complete signal-to-name dictionary using a hierarchical tree. Parameters: - signals (iterable): An iterable of all signals to be named. - tree (HierarchyNode): The root node of the tree used for name resolution. + signals (iterable): An iterable of all signals to be named. + tree (_HierarchyNode): The root node of the tree used for name resolution. Returns: dict: A complete dictionary mapping signals to their hierarchical names. """ # Group the signals based on their relationships. - groups = build_signal_groups(signals) + groups = _build_signal_groups(signals) # Generate a name mapping for each group. group_name_dict_mappings = [ - build_signal_name_dict_for_group(group_number, group_signals) + _build_signal_name_dict_for_group(group_number, group_signals) for group_number, group_signals in enumerate(groups) ] # Create the final signal-to-name mapping. name_dict = {} for group_number, group_name_dict in enumerate(group_name_dict_mappings): - update_name_dict_with_group(name_dict, group_number, group_name_dict, group_name_dict_mappings) + _update_name_dict_with_group(name_dict, group_number, group_name_dict, group_name_dict_mappings) return name_dict -# Public Classes/Helpers --------------------------------------------------------------------------- +# Signal Namespace Class --------------------------------------------------------------------------- -class SignalNamespace: +class _SignalNamespace: """ - A SignalNamespace object manages unique naming for signals within a hardware design. + A _SignalNamespace object manages unique naming for signals within a hardware design. It ensures that each signal has a unique, conflict-free name within the design's namespace. This includes taking into account reserved keywords and handling signals that may share the same name @@ -432,6 +453,8 @@ class SignalNamespace: # Return Name. return sig_name +# Build Signal Namespace function ------------------------------------------------------------------ + def build_signal_namespace(signals, reserved_keywords=set()): """Constructs a namespace where each signal is given a unique hierarchical name. Parameters: @@ -443,14 +466,12 @@ def build_signal_namespace(signals, reserved_keywords=set()): """ # Create the primary signal-to-name dictionary. - pnd = build_signal_name_dict(signals) + pnd = _build_signal_name_dict(signals) # Initialize the namespace with reserved keywords and the primary mapping. - namespace = SignalNamespace(pnd, reserved_keywords) + namespace = _SignalNamespace(pnd, reserved_keywords) # Handle signals with overridden names, ensuring they are processed in a consistent order. signals_with_name_override = filter(lambda s: s.name_override is not None, signals) return namespace - -build_namespace = build_signal_namespace \ No newline at end of file diff --git a/litex/gen/fhdl/verilog.py b/litex/gen/fhdl/verilog.py index ce52fa355..7fe572100 100644 --- a/litex/gen/fhdl/verilog.py +++ b/litex/gen/fhdl/verilog.py @@ -26,7 +26,7 @@ from migen.fhdl.conv_output import ConvOutput from migen.fhdl.specials import Instance, Memory from litex.gen import LiteXContext -from litex.gen.fhdl.namer import build_namespace +from litex.gen.fhdl.namer import build_signal_namespace from litex.gen.fhdl.hierarchy import LiteXHierarchyExplorer from litex.build.tools import get_litex_git_revision @@ -611,9 +611,9 @@ def convert(f, ios=set(), name="top", platform=None, if io_name: io.name_override = io_name - # Build NameSpace. - # ---------------- - ns = build_namespace( + # Build Signal Namespace. + # ---------------------- + ns = build_signal_namespace( signals = ( list_signals(f) | list_special_ios(f, ins=True, outs=True, inouts=True) | diff --git a/litex/gen/sim/vcd.py b/litex/gen/sim/vcd.py index 60aa33d49..1c3f27ad8 100644 --- a/litex/gen/sim/vcd.py +++ b/litex/gen/sim/vcd.py @@ -12,7 +12,7 @@ import os from collections import OrderedDict import shutil -from litex.gen.fhdl.namer import build_namespace +from litex.gen.fhdl.namer import build_signal_namespace def vcd_codes(): codechars = [chr(i) for i in range(33, 127)] @@ -71,7 +71,7 @@ class VCDWriter: # write vcd header header = "" - ns = build_namespace(self.codes.keys()) + ns = build_signal_namespace(self.codes.keys()) for signal, code in self.codes.items(): name = ns.get_name(signal) header += "$var wire {len} {code} {name} $end\n".format(name=name, code=code, len=len(signal)) From 33fd7742c9be837e012a6a416b5e2d0cf36d2b53 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 16:49:54 +0100 Subject: [PATCH 16/18] interconnect/stream/ClockDomainCrossing: Use DUID for clock_domain id to allow deterministic builds. --- litex/soc/interconnect/stream.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/litex/soc/interconnect/stream.py b/litex/soc/interconnect/stream.py index c3759641e..c7d521221 100644 --- a/litex/soc/interconnect/stream.py +++ b/litex/soc/interconnect/stream.py @@ -244,8 +244,9 @@ class AsyncFIFO(_FIFOWrapper): # ClockDomainCrossing ------------------------------------------------------------------------------ -class ClockDomainCrossing(LiteXModule): +class ClockDomainCrossing(LiteXModule, DUID): def __init__(self, layout, cd_from="sys", cd_to="sys", depth=None, buffered=False, with_common_rst=False): + DUID.__init__(self) self.sink = Endpoint(layout) self.source = Endpoint(layout) @@ -259,7 +260,7 @@ class ClockDomainCrossing(LiteXModule): else: if with_common_rst: # Create intermediate Clk Domains and generate a common Rst. - _cd_id = id(self) # FIXME: Improve, used to allow build with anonymous modules. + _cd_id = self.duid # Use duid for a deterministic unique ID. _cd_rst = Signal() _cd_from = ClockDomain(f"from{_cd_id}") _cd_to = ClockDomain(f"to{_cd_id}") From 5b989bcb0e5498d16b13d4397ee284b3d5d1a98f Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 17:24:03 +0100 Subject: [PATCH 17/18] gen/fhdl/verilog: Switch Assign/Operator types to IntEnum. --- litex/gen/fhdl/verilog.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/litex/gen/fhdl/verilog.py b/litex/gen/fhdl/verilog.py index 7fe572100..12d8953f8 100644 --- a/litex/gen/fhdl/verilog.py +++ b/litex/gen/fhdl/verilog.py @@ -17,6 +17,7 @@ import time import datetime import collections +from enum import IntEnum from operator import itemgetter from migen.fhdl.structure import * @@ -181,19 +182,22 @@ def _generate_signal(ns, s): # Print Operator ----------------------------------------------------------------------------------- -(UNARY, BINARY, TERNARY) = (1, 2, 3) +class OperatorType(IntEnum): + UNARY = 1 + BINARY = 2 + TERNARY = 3 def _generate_operator(ns, node): operator = node.op operands = node.operands arity = len(operands) - assert arity in [UNARY, BINARY, TERNARY] + assert arity in [item.value for item in OperatorType] def to_signed(r): return f"$signed({{1'd0, {r}}})" # Unary Operator. - if arity == UNARY: + if arity == OperatorType.UNARY: r1, s1 = _generate_expression(ns, operands[0]) # Negation Operator. if operator == "-": @@ -206,7 +210,7 @@ def _generate_operator(ns, node): s = s1 # Binary Operator. - if arity == BINARY: + if arity == OperatorType.BINARY: r1, s1 = _generate_expression(ns, operands[0]) r2, s2 = _generate_expression(ns, operands[1]) # Convert all expressions to signed when at least one is signed. @@ -219,7 +223,7 @@ def _generate_operator(ns, node): s = s1 or s2 # Ternary Operator. - if arity == TERNARY: + if arity == OperatorType.TERNARY: assert operator == "m" r1, s1 = _generate_expression(ns, operands[0]) r2, s2 = _generate_expression(ns, operands[1]) @@ -292,17 +296,21 @@ def _generate_expression(ns, node): # NODES # # ------------------------------------------------------------------------------------------------ # -(_AT_BLOCKING, _AT_NONBLOCKING, _AT_SIGNAL) = range(3) +class AssignType(IntEnum): + BLOCKING = 0 + NON_BLOCKING = 1 + SIGNAL = 2 def _generate_node(ns, at, level, node, target_filter=None): + assert at in [item.value for item in AssignType] if target_filter is not None and target_filter not in list_targets(node): return "" # Assignment. elif isinstance(node, _Assign): - if at == _AT_BLOCKING: + if at == AssignType.BLOCKING: assignment = " = " - elif at == _AT_NONBLOCKING: + elif at == AssignType.NON_BLOCKING: assignment = " <= " elif is_variable(node.l): assignment = " = " @@ -478,11 +486,11 @@ def _generate_combinatorial_logic_sim(f, ns): for n, (t, stmts) in enumerate(target_stmt_map.items()): assert isinstance(t, Signal) if _use_wire(stmts): - r += "assign " + _generate_node(ns, _AT_BLOCKING, 0, stmts[0]) + r += "assign " + _generate_node(ns, AssignType.BLOCKING, 0, stmts[0]) else: r += "always @(*) begin\n" r += _tab + ns.get_name(t) + " <= " + _generate_expression(ns, t.reset)[0] + ";\n" - r += _generate_node(ns, _AT_NONBLOCKING, 1, stmts, t) + r += _generate_node(ns, AssignType.NON_BLOCKING, 1, stmts, t) r += "end\n" r += "\n" return r @@ -494,12 +502,12 @@ def _generate_combinatorial_logic_synth(f, ns): for n, g in enumerate(groups): if _use_wire(g[1]): - r += "assign " + _generate_node(ns, _AT_BLOCKING, 0, g[1][0]) + r += "assign " + _generate_node(ns, AssignType.BLOCKING, 0, g[1][0]) else: r += "always @(*) begin\n" for t in sorted(g[0], key=lambda x: ns.get_name(x)): r += _tab + ns.get_name(t) + " <= " + _generate_expression(ns, t.reset)[0] + ";\n" - r += _generate_node(ns, _AT_NONBLOCKING, 1, g[1]) + r += _generate_node(ns, AssignType.NON_BLOCKING, 1, g[1]) r += "end\n" r += "\n" return r @@ -512,7 +520,7 @@ def _generate_synchronous_logic(f, ns): r = "" for k, v in sorted(f.sync.items(), key=itemgetter(0)): r += "always @(posedge " + ns.get_name(f.clock_domains[k].clk) + ") begin\n" - r += _generate_node(ns, _AT_SIGNAL, 1, v) + r += _generate_node(ns, AssignType.SIGNAL, 1, v) r += "end\n\n" return r From 657252c5732ff82054cb3b396b65d0e753a85d5e Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 6 Nov 2023 17:55:54 +0100 Subject: [PATCH 18/18] gen/fhdl/namer: Update copyrights. --- litex/gen/fhdl/namer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litex/gen/fhdl/namer.py b/litex/gen/fhdl/namer.py index 678cd65b7..9b31d03d2 100644 --- a/litex/gen/fhdl/namer.py +++ b/litex/gen/fhdl/namer.py @@ -2,6 +2,7 @@ # This file is part of LiteX (Adapted from Migen for LiteX usage). # # This file is Copyright (c) 2013-2014 Sebastien Bourdeauducq +# This file is Copyright (c) 2023 Florent Kermarrec # SPDX-License-Identifier: BSD-2-Clause from itertools import combinations