fhdl/namer: Remove debug and add docstring comments.

This commit is contained in:
Florent Kermarrec 2023-11-06 09:38:17 +01:00
parent 6aa22271f9
commit 1e805a8789
1 changed files with 125 additions and 43 deletions

View File

@ -4,44 +4,37 @@
# This file is Copyright (c) 2013-2014 Sebastien Bourdeauducq <sb@m-labs.hk>
# 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 = {}