Merge pull request #1828 from enjoy-digital/verilog_improvements_2
Verilog improvements.
This commit is contained in:
commit
d0bb837b7c
|
@ -2,271 +2,477 @@
|
||||||
# This file is part of LiteX (Adapted from Migen for LiteX usage).
|
# This file is part of LiteX (Adapted from Migen for LiteX usage).
|
||||||
#
|
#
|
||||||
# This file is Copyright (c) 2013-2014 Sebastien Bourdeauducq <sb@m-labs.hk>
|
# This file is Copyright (c) 2013-2014 Sebastien Bourdeauducq <sb@m-labs.hk>
|
||||||
|
# This file is Copyright (c) 2023 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
|
|
||||||
from migen.fhdl.structure import *
|
from migen.fhdl.structure import *
|
||||||
|
|
||||||
|
# Hierarchy Node Class -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class _Node:
|
class _HierarchyNode:
|
||||||
|
"""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):
|
def __init__(self):
|
||||||
self.signal_count = 0
|
self.signal_count = 0
|
||||||
self.numbers = set()
|
self.numbers = set()
|
||||||
self.use_name = False
|
self.use_name = False
|
||||||
self.use_number = False
|
self.use_number = False
|
||||||
self.children = OrderedDict()
|
self.children = {}
|
||||||
|
self.all_numbers = []
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
def _display_tree(filename, tree):
|
Parameters:
|
||||||
from migen.util.treeviz import RenderNode
|
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.
|
||||||
|
|
||||||
def _to_render_node(name, node):
|
Returns:
|
||||||
children = [_to_render_node(k, v) for k, v in node.children.items()]
|
_HierarchyNode: The updated or created child node.
|
||||||
if node.use_name:
|
"""
|
||||||
if node.use_number:
|
# Create the appropriate key for the node.
|
||||||
color = (0.5, 0.9, 0.8)
|
key = (name, number) if use_number else name
|
||||||
else:
|
# Use setdefault to either get the existing child node or create a new one.
|
||||||
color = (0.8, 0.5, 0.9)
|
child = self.children.setdefault(key, _HierarchyNode())
|
||||||
else:
|
# Add the number to the set of numbers associated with this node.
|
||||||
if node.use_number:
|
child.numbers.add(number)
|
||||||
color = (0.9, 0.8, 0.5)
|
# Increment the count of signals that have traversed this node.
|
||||||
else:
|
child.signal_count += 1
|
||||||
color = (0.8, 0.8, 0.8)
|
# If numbering is used, sort and store all numbers associated with the base node.
|
||||||
label = "{0}\n{1} signals\n{2}".format(name, node.signal_count, node.numbers)
|
if use_number and current_base:
|
||||||
return RenderNode(label, children, color=color)
|
child.all_numbers = sorted(current_base.numbers)
|
||||||
|
return child
|
||||||
|
|
||||||
top = _to_render_node("top", tree)
|
# Build Hierarchy Tree Function --------------------------------------------------------------------
|
||||||
top.to_svg(filename)
|
|
||||||
|
|
||||||
|
def _build_hierarchy_tree(signals, base_tree=None):
|
||||||
|
"""
|
||||||
|
Constructs a hierarchical tree from signals, where each signal's backtrace contributes to the tree structure.
|
||||||
|
|
||||||
def _build_tree(signals, basic_tree=None):
|
Parameters:
|
||||||
root = _Node()
|
- 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.
|
||||||
|
"""
|
||||||
|
root = _HierarchyNode()
|
||||||
|
|
||||||
|
# Iterate over each signal to be included in the tree.
|
||||||
for signal in signals:
|
for signal in signals:
|
||||||
current_b = basic_tree
|
current = root
|
||||||
current = root
|
current_base = base_tree
|
||||||
current.signal_count += 1
|
|
||||||
|
# Traverse or build the hierarchy of nodes based on the signal's backtrace.
|
||||||
for name, number in signal.backtrace:
|
for name, number in signal.backtrace:
|
||||||
if basic_tree is None:
|
# Decide whether to use a numbered key based on the base tree.
|
||||||
use_number = False
|
use_number = False
|
||||||
else:
|
if current_base:
|
||||||
current_b = current_b.children[name]
|
current_base = current_base.children.get(name)
|
||||||
use_number = current_b.use_number
|
use_number = current_base.use_number if current_base else False
|
||||||
if use_number:
|
|
||||||
key = (name, number)
|
# Update the current node in the hierarchy.
|
||||||
else:
|
current = current.update(name, number, use_number, current_base)
|
||||||
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
|
return root
|
||||||
|
|
||||||
|
# Determine Name Usage Function --------------------------------------------------------------------
|
||||||
|
|
||||||
def _set_use_name(node, node_name=""):
|
def _determine_name_usage(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):
|
Recursively determines if node names should be used to ensure unique signal naming.
|
||||||
if not c1_names.isdisjoint(c2_names):
|
"""
|
||||||
node.children[c1_prefix].use_name = True
|
required_names = set() # This will accumulate all names that ensure unique identification of signals.
|
||||||
node.children[c2_prefix].use_name = True
|
|
||||||
r = set()
|
# Recursively collect names from children, identifying if any naming conflicts occur.
|
||||||
for c_prefix, c_names in cnames:
|
child_name_sets = {
|
||||||
if node.children[c_prefix].use_name:
|
child_name: _determine_name_usage(child_node, child_name)
|
||||||
for c_name in c_names:
|
for child_name, child_node in node.children.items()
|
||||||
r.add((c_prefix, ) + c_name)
|
}
|
||||||
|
|
||||||
|
# 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:
|
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
|
node.use_name = True
|
||||||
r.add((node_name, ))
|
required_names.add((node_name,)) # Add this node's name only if it has additional signals.
|
||||||
|
|
||||||
return r
|
return required_names
|
||||||
|
|
||||||
|
# Build Signal Name Dict From Tree Function --------------------------------------------------------
|
||||||
|
|
||||||
def _name_signal(tree, signal):
|
def _build_signal_name_dict_from_tree(tree, signals):
|
||||||
elements = []
|
"""
|
||||||
treepos = tree
|
Constructs a mapping of signals to their names derived from a tree structure.
|
||||||
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)
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
def _build_pnd_from_tree(tree, signals):
|
# Initialize a dictionary to hold the signal names.
|
||||||
return dict((signal, _name_signal(tree, signal)) for signal in signals)
|
name_dict = {}
|
||||||
|
|
||||||
|
# Process each signal to build its hierarchical name.
|
||||||
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:
|
for signal in signals:
|
||||||
current = tree
|
# 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:
|
for step_name, step_n in signal.backtrace:
|
||||||
current = current.children[step_name]
|
# Navigate the tree according to the signal's path.
|
||||||
current.use_number = current.signal_count > len(current.numbers) and len(current.numbers) > 1
|
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
|
||||||
|
|
||||||
_debug = 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.
|
||||||
|
name_dict[signal] = "_".join(elements)
|
||||||
|
|
||||||
def _build_pnd_for_group(group_n, signals):
|
# Return the completed name dictionary.
|
||||||
basic_tree = _build_tree(signals)
|
return name_dict
|
||||||
_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.
|
# Invert Signal Name Dict Function -----------------------------------------------------------------
|
||||||
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.
|
def _invert_signal_name_dict(name_dict):
|
||||||
inv_pnd = _invert_pnd(pnd)
|
"""
|
||||||
duid_suffixed = False
|
Inverts a signal-to-name dictionary to a name-to-signals dictionary.
|
||||||
for name, signals in inv_pnd.items():
|
|
||||||
|
Parameters:
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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 = inverted_dict.get(name, [])
|
||||||
|
# Add the current signal to the list.
|
||||||
|
signals_with_name.append(signal)
|
||||||
|
# Place the updated list back in the dictionary.
|
||||||
|
inverted_dict[name] = signals_with_name
|
||||||
|
return inverted_dict
|
||||||
|
|
||||||
|
# List Conflicting Signals Function ----------------------------------------------------------------
|
||||||
|
|
||||||
|
def _list_conflicting_signals(name_dict):
|
||||||
|
"""Lists signals that have conflicting names in the provided mapping.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
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.
|
||||||
|
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 inverted_dict.items():
|
||||||
|
# If there is more than one signal for this name, it means there is a conflict.
|
||||||
if len(signals) > 1:
|
if len(signals) > 1:
|
||||||
duid_suffixed = True
|
# Add all conflicting signals to our set.
|
||||||
for n, signal in enumerate(sorted(signals, key=lambda x: x.duid)):
|
conflicts.update(signals)
|
||||||
pnd[signal] += str(n)
|
|
||||||
if _debug and duid_suffixed:
|
|
||||||
print("namer: using DUID suffixes (group {0})".format(group_n))
|
|
||||||
|
|
||||||
return pnd
|
# Return the set of all signals that have name conflicts.
|
||||||
|
return conflicts
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None: Tree is modified in place.
|
||||||
|
"""
|
||||||
|
for signal in signals:
|
||||||
|
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.
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
group_number (int): The group number.
|
||||||
|
signals (iterable): The signals within the group.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
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.
|
||||||
|
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).
|
||||||
|
disambiguate_signals_with_duid()
|
||||||
|
|
||||||
|
return name_dict
|
||||||
|
|
||||||
|
# Build Signal Groups Function ---------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_signal_groups(signals):
|
def _build_signal_groups(signals):
|
||||||
r = []
|
"""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.
|
||||||
|
"""
|
||||||
|
grouped_signals = []
|
||||||
|
|
||||||
|
# Create groups of related signals.
|
||||||
for signal in signals:
|
for signal in signals:
|
||||||
# Build chain of related signals.
|
chain = []
|
||||||
related_list = []
|
# Trace back the chain of related signals.
|
||||||
cur_signal = signal
|
while signal is not None:
|
||||||
while cur_signal is not None:
|
chain.insert(0, signal)
|
||||||
related_list.insert(0, cur_signal)
|
signal = signal.related
|
||||||
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
|
|
||||||
|
|
||||||
|
# Ensure there's a set for each level of relation.
|
||||||
|
while len(grouped_signals) < len(chain):
|
||||||
|
grouped_signals.append(set())
|
||||||
|
|
||||||
def _build_pnd(signals):
|
# Assign signals to their respective group.
|
||||||
|
for group, sig in zip(grouped_signals, chain):
|
||||||
|
group.add(sig)
|
||||||
|
|
||||||
|
return grouped_signals
|
||||||
|
|
||||||
|
# Build Hierarchical Name Function -----------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
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
|
||||||
|
|
||||||
|
# Build Signal Name Dict Function ------------------------------------------------------------------
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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)
|
||||||
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
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
]
|
||||||
|
|
||||||
def build_namespace(signals, reserved_keywords=set()):
|
# Create the final signal-to-name mapping.
|
||||||
pnd = _build_pnd(signals)
|
name_dict = {}
|
||||||
ns = Namespace(pnd, reserved_keywords)
|
for group_number, group_name_dict in enumerate(group_name_dict_mappings):
|
||||||
# Register Signals with name_override.
|
_update_name_dict_with_group(name_dict, group_number, group_name_dict, group_name_dict_mappings)
|
||||||
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
|
|
||||||
|
|
||||||
|
return name_dict
|
||||||
|
|
||||||
class Namespace:
|
# Signal Namespace Class ---------------------------------------------------------------------------
|
||||||
def __init__(self, pnd, reserved_keywords=set()):
|
|
||||||
self.counts = {k: 1 for k in reserved_keywords}
|
class _SignalNamespace:
|
||||||
self.sigs = {}
|
"""
|
||||||
self.pnd = pnd
|
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
|
||||||
|
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.
|
||||||
|
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:
|
||||||
|
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, 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()
|
self.clock_domains = dict()
|
||||||
|
|
||||||
def get_name(self, sig):
|
def get_name(self, sig):
|
||||||
# Get name of a Clock Signal.
|
# Handle Clock and Reset Signals.
|
||||||
# ---------------------------
|
# -------------------------------
|
||||||
if isinstance(sig, ClockSignal):
|
if isinstance(sig, (ClockSignal, ResetSignal)):
|
||||||
sig = self.clock_domains[sig.cd].clk
|
# Retrieve the clock domain from the dictionary.
|
||||||
|
domain = self.clock_domains.get(sig.cd)
|
||||||
# Get name of a Reset Signal.
|
if domain is None:
|
||||||
# ---------------------------
|
raise ValueError(f"Clock Domain '{sig.cd}' not found.")
|
||||||
if isinstance(sig, ResetSignal):
|
# Assign the appropriate signal from the clock domain.
|
||||||
sig = self.clock_domains[sig.cd].rst
|
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:
|
if sig is None:
|
||||||
msg = f"Clock Domain {sig.cd} is reset-less, can't obtain name"
|
raise ValueError(f"Clock Domain '{sig.cd}' is reset-less, can't obtain name.")
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
# Get name of a Regular Signal.
|
# Get name of a Regular Signal.
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# Use Name's override when set...
|
# Use Name's override when set...
|
||||||
if sig.name_override is not None:
|
if sig.name_override is not None:
|
||||||
sig_name = sig.name_override
|
sig_name = sig.name_override
|
||||||
# ... else get Name from pnd.
|
# ... else get Name from name_dict.
|
||||||
else:
|
else:
|
||||||
sig_name = self.pnd[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.
|
|
||||||
# -----------------------------------------
|
# Check/Add numbering when required.
|
||||||
try:
|
# ----------------------------------
|
||||||
n = self.sigs[sig]
|
# Retrieve the current count for the signal name, defaulting to 0.
|
||||||
except KeyError:
|
n = self.sigs.get(sig)
|
||||||
try:
|
if n is None:
|
||||||
n = self.counts[sig_name]
|
n = self.counts.get(sig_name, 0)
|
||||||
except KeyError:
|
self.sigs[sig] = n
|
||||||
n = 0
|
|
||||||
self.sigs[sig] = n
|
|
||||||
self.counts[sig_name] = n + 1
|
self.counts[sig_name] = n + 1
|
||||||
suffix = "" if n == 0 else f"_{n}"
|
# If the count is greater than 0, append it to the signal name.
|
||||||
|
if n > 0:
|
||||||
|
sig_name += f"_{n}"
|
||||||
|
|
||||||
# Return Name.
|
# Return Name.
|
||||||
return sig_name + suffix
|
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:
|
||||||
|
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_signal_name_dict(signals)
|
||||||
|
|
||||||
|
# Initialize the namespace with reserved keywords and the primary mapping.
|
||||||
|
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
|
||||||
|
|
|
@ -17,6 +17,7 @@ import time
|
||||||
import datetime
|
import datetime
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
|
from enum import IntEnum
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from migen.fhdl.structure import *
|
from migen.fhdl.structure import *
|
||||||
|
@ -26,7 +27,7 @@ from migen.fhdl.conv_output import ConvOutput
|
||||||
from migen.fhdl.specials import Instance, Memory
|
from migen.fhdl.specials import Instance, Memory
|
||||||
|
|
||||||
from litex.gen import LiteXContext
|
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.gen.fhdl.hierarchy import LiteXHierarchyExplorer
|
||||||
|
|
||||||
from litex.build.tools import get_litex_git_revision
|
from litex.build.tools import get_litex_git_revision
|
||||||
|
@ -181,19 +182,22 @@ def _generate_signal(ns, s):
|
||||||
|
|
||||||
# Print Operator -----------------------------------------------------------------------------------
|
# Print Operator -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
(UNARY, BINARY, TERNARY) = (1, 2, 3)
|
class OperatorType(IntEnum):
|
||||||
|
UNARY = 1
|
||||||
|
BINARY = 2
|
||||||
|
TERNARY = 3
|
||||||
|
|
||||||
def _generate_operator(ns, node):
|
def _generate_operator(ns, node):
|
||||||
operator = node.op
|
operator = node.op
|
||||||
operands = node.operands
|
operands = node.operands
|
||||||
arity = len(operands)
|
arity = len(operands)
|
||||||
assert arity in [UNARY, BINARY, TERNARY]
|
assert arity in [item.value for item in OperatorType]
|
||||||
|
|
||||||
def to_signed(r):
|
def to_signed(r):
|
||||||
return f"$signed({{1'd0, {r}}})"
|
return f"$signed({{1'd0, {r}}})"
|
||||||
|
|
||||||
# Unary Operator.
|
# Unary Operator.
|
||||||
if arity == UNARY:
|
if arity == OperatorType.UNARY:
|
||||||
r1, s1 = _generate_expression(ns, operands[0])
|
r1, s1 = _generate_expression(ns, operands[0])
|
||||||
# Negation Operator.
|
# Negation Operator.
|
||||||
if operator == "-":
|
if operator == "-":
|
||||||
|
@ -206,7 +210,7 @@ def _generate_operator(ns, node):
|
||||||
s = s1
|
s = s1
|
||||||
|
|
||||||
# Binary Operator.
|
# Binary Operator.
|
||||||
if arity == BINARY:
|
if arity == OperatorType.BINARY:
|
||||||
r1, s1 = _generate_expression(ns, operands[0])
|
r1, s1 = _generate_expression(ns, operands[0])
|
||||||
r2, s2 = _generate_expression(ns, operands[1])
|
r2, s2 = _generate_expression(ns, operands[1])
|
||||||
# Convert all expressions to signed when at least one is signed.
|
# Convert all expressions to signed when at least one is signed.
|
||||||
|
@ -219,7 +223,7 @@ def _generate_operator(ns, node):
|
||||||
s = s1 or s2
|
s = s1 or s2
|
||||||
|
|
||||||
# Ternary Operator.
|
# Ternary Operator.
|
||||||
if arity == TERNARY:
|
if arity == OperatorType.TERNARY:
|
||||||
assert operator == "m"
|
assert operator == "m"
|
||||||
r1, s1 = _generate_expression(ns, operands[0])
|
r1, s1 = _generate_expression(ns, operands[0])
|
||||||
r2, s2 = _generate_expression(ns, operands[1])
|
r2, s2 = _generate_expression(ns, operands[1])
|
||||||
|
@ -292,17 +296,21 @@ def _generate_expression(ns, node):
|
||||||
# NODES #
|
# 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):
|
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):
|
if target_filter is not None and target_filter not in list_targets(node):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# Assignment.
|
# Assignment.
|
||||||
elif isinstance(node, _Assign):
|
elif isinstance(node, _Assign):
|
||||||
if at == _AT_BLOCKING:
|
if at == AssignType.BLOCKING:
|
||||||
assignment = " = "
|
assignment = " = "
|
||||||
elif at == _AT_NONBLOCKING:
|
elif at == AssignType.NON_BLOCKING:
|
||||||
assignment = " <= "
|
assignment = " <= "
|
||||||
elif is_variable(node.l):
|
elif is_variable(node.l):
|
||||||
assignment = " = "
|
assignment = " = "
|
||||||
|
@ -478,11 +486,11 @@ def _generate_combinatorial_logic_sim(f, ns):
|
||||||
for n, (t, stmts) in enumerate(target_stmt_map.items()):
|
for n, (t, stmts) in enumerate(target_stmt_map.items()):
|
||||||
assert isinstance(t, Signal)
|
assert isinstance(t, Signal)
|
||||||
if _use_wire(stmts):
|
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:
|
else:
|
||||||
r += "always @(*) begin\n"
|
r += "always @(*) begin\n"
|
||||||
r += _tab + ns.get_name(t) + " <= " + _generate_expression(ns, t.reset)[0] + ";\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 += "end\n"
|
||||||
r += "\n"
|
r += "\n"
|
||||||
return r
|
return r
|
||||||
|
@ -494,12 +502,12 @@ def _generate_combinatorial_logic_synth(f, ns):
|
||||||
|
|
||||||
for n, g in enumerate(groups):
|
for n, g in enumerate(groups):
|
||||||
if _use_wire(g[1]):
|
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:
|
else:
|
||||||
r += "always @(*) begin\n"
|
r += "always @(*) begin\n"
|
||||||
for t in sorted(g[0], key=lambda x: ns.get_name(x)):
|
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 += _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 += "end\n"
|
||||||
r += "\n"
|
r += "\n"
|
||||||
return r
|
return r
|
||||||
|
@ -512,7 +520,7 @@ def _generate_synchronous_logic(f, ns):
|
||||||
r = ""
|
r = ""
|
||||||
for k, v in sorted(f.sync.items(), key=itemgetter(0)):
|
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 += "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"
|
r += "end\n\n"
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
@ -611,9 +619,9 @@ def convert(f, ios=set(), name="top", platform=None,
|
||||||
if io_name:
|
if io_name:
|
||||||
io.name_override = io_name
|
io.name_override = io_name
|
||||||
|
|
||||||
# Build NameSpace.
|
# Build Signal Namespace.
|
||||||
# ----------------
|
# ----------------------
|
||||||
ns = build_namespace(
|
ns = build_signal_namespace(
|
||||||
signals = (
|
signals = (
|
||||||
list_signals(f) |
|
list_signals(f) |
|
||||||
list_special_ios(f, ins=True, outs=True, inouts=True) |
|
list_special_ios(f, ins=True, outs=True, inouts=True) |
|
||||||
|
|
|
@ -12,7 +12,7 @@ import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from litex.gen.fhdl.namer import build_namespace
|
from litex.gen.fhdl.namer import build_signal_namespace
|
||||||
|
|
||||||
def vcd_codes():
|
def vcd_codes():
|
||||||
codechars = [chr(i) for i in range(33, 127)]
|
codechars = [chr(i) for i in range(33, 127)]
|
||||||
|
@ -71,7 +71,7 @@ class VCDWriter:
|
||||||
|
|
||||||
# write vcd header
|
# write vcd header
|
||||||
header = ""
|
header = ""
|
||||||
ns = build_namespace(self.codes.keys())
|
ns = build_signal_namespace(self.codes.keys())
|
||||||
for signal, code in self.codes.items():
|
for signal, code in self.codes.items():
|
||||||
name = ns.get_name(signal)
|
name = ns.get_name(signal)
|
||||||
header += "$var wire {len} {code} {name} $end\n".format(name=name, code=code, len=len(signal))
|
header += "$var wire {len} {code} {name} $end\n".format(name=name, code=code, len=len(signal))
|
||||||
|
|
|
@ -244,8 +244,9 @@ class AsyncFIFO(_FIFOWrapper):
|
||||||
|
|
||||||
# ClockDomainCrossing ------------------------------------------------------------------------------
|
# 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):
|
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.sink = Endpoint(layout)
|
||||||
self.source = Endpoint(layout)
|
self.source = Endpoint(layout)
|
||||||
|
|
||||||
|
@ -259,7 +260,7 @@ class ClockDomainCrossing(LiteXModule):
|
||||||
else:
|
else:
|
||||||
if with_common_rst:
|
if with_common_rst:
|
||||||
# Create intermediate Clk Domains and generate a 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_rst = Signal()
|
||||||
_cd_from = ClockDomain(f"from{_cd_id}")
|
_cd_from = ClockDomain(f"from{_cd_id}")
|
||||||
_cd_to = ClockDomain(f"to{_cd_id}")
|
_cd_to = ClockDomain(f"to{_cd_id}")
|
||||||
|
|
Loading…
Reference in New Issue