Handling Module Graphs¶
Modules can be interpreted as graphs. In this case the nodes are instances and module ports and the edges represent connections between them (i.e. wires). Since instances can have multiple connections between each other and each wire has a direction (from driver to load), the module graph is a directed, multi-edged graph. This is implemented using MultiDiGraphs from NetworkX. This notebook shows how to handle module graphs.
Building and Retrieving the Graph¶
- The module graph can be build using
Module.graph(), which depending on the size of the module may take a couple of seconds. - Every subsequent execution will however return the previously built graph, as long as the module did not change structurally -- in this case, the graph is rebuilt, which again take some seconds.
- Execute the cell below to build the graph for inspection.
Info: Graph rebuilding can be forced via Module._build_graph(), but this should only be used for debugging or testing purposes.
In [ ]:
Copied!
import netlist_carpentry
circuit = netlist_carpentry.read("files/decentral_mux.v", top="decentral_mux")
module = circuit.top
graph = module.graph()
import netlist_carpentry
circuit = netlist_carpentry.read("files/decentral_mux.v", top="decentral_mux")
module = circuit.top
graph = module.graph()
Accessing Nodes from the Graph¶
- All nodes from the graph are stored in
Graph.nodes. - Execute the cell below to retrieve the names of all nodes in the module graph.
In [ ]:
Copied!
print(f"The graph of module {module.name} contains these nodes:")
for node in graph.nodes:
print(f"\t{node}")
print(f"The graph of module {module.name} consists of a total of {len(graph.nodes)} nodes (including all ports and instances).")
print(f"The graph of module {module.name} contains these nodes:")
for node in graph.nodes:
print(f"\t{node}")
print(f"The graph of module {module.name} consists of a total of {len(graph.nodes)} nodes (including all ports and instances).")
Accessing Node Data¶
- To access data of a certain node from the graph,
Graph.nodes[node_name]can be used, which returns a dictionary of additional data for the given node. - Currently, nodes contain the type of the node (port or instance) associated with key
ntype, along with a more specific description of the type (e.g. the instance type or the direction if it is a port instead) with keynsubtype, as well as the object itself in keyndata. - Execute the cell below to retrieve all data from the node representing the input port
DATA_Iof the module.
In [ ]:
Copied!
print("The node representing the port DATA_I has the following additional data:")
for key, value in graph.nodes["DATA_I"].items():
print(f"\t{key}: {value}")
print("The node representing the port DATA_I has the following additional data:")
for key, value in graph.nodes["DATA_I"].items():
print(f"\t{key}: {value}")
Accessing Edges of the Graph¶
- All edges from the graph are stored in
Graph.edges. - Since the module graph is a directed multi-edge graph, each edge consists of a source node and target node, as well as a key to uniquely identify it, if there are multiple connections between the same nodes.
- Execute the cell below to retrieve all edges in the module graph as tuples
(source_node, target_node, key), where the key is a string of the formatsource_port§target_port.
In [ ]:
Copied!
print(f"The graph of module {module.name} contains these edges:")
for source, target, key in graph.edges:
source_port, target_port = key.split("§")
print(f"\tFrom {source}:{source_port} to {target}:{target_port}")
print(f"The graph of module {module.name} contains these edges:")
for source, target, key in graph.edges:
source_port, target_port = key.split("§")
print(f"\tFrom {source}:{source_port} to {target}:{target_port}")
Retrieving Node Degrees¶
- The degree of a node is defined by the number of connections to it.
- In particular,
Graph.out_degree(node_name)andGraph.in_degree(node_name)return the number of outgoing and incoming connections, respectively. - Without specified parameters,
Graph.out_degreeandGraph.in_degreereturns an iterator over all nodes with their respective degrees. - Execute the cell below to retrieve all nodes with their respective numbers of incoming and outgoing edges.
In [ ]:
Copied!
for node in graph.nodes:
if graph.out_degree(node) > 0 or graph.in_degree(node) > 0:
print(f"Node {node} has {graph.in_degree(node)} incoming edges and {graph.out_degree(node)} outgoing edges.")
else:
print(f"Node {node} does not have any incoming or outgoing edges.")
for node in graph.nodes:
if graph.out_degree(node) > 0 or graph.in_degree(node) > 0:
print(f"Node {node} has {graph.in_degree(node)} incoming edges and {graph.out_degree(node)} outgoing edges.")
else:
print(f"Node {node} does not have any incoming or outgoing edges.")
Retrieving Predecessors and Successors of a Node¶
- The
Graph.predecessors(node_name)method returns an iterator over all predecessors of the given nodenode_name. - A predecessor of node
nis any nodemso that there exists a directed edge(m → n)in the graph. - Analogously, the
Graph.successors(node_name)method returns an iterator over all successors of the given nodenode_name. - A successor of node
nis any nodemso that there exists a directed edge(n → m)in the graph.
In [ ]:
Copied!
for node in graph.nodes:
predecessors = list(graph.predecessors(node))
successors = list(graph.successors(node))
if predecessors or successors:
print(f"Node {node} has these predecessors: {predecessors} and these successors: {successors}.")
for node in graph.nodes:
predecessors = list(graph.predecessors(node))
successors = list(graph.successors(node))
if predecessors or successors:
print(f"Node {node} has these predecessors: {predecessors} and these successors: {successors}.")