Displaying Module Graphs¶
In the context of digital circuit design, a module graph is a directed acyclic graph that represents the structure of a module. It contains the ports of the module and all instances (primitive gates and submodules) as nodes, whereas wires are represented by edges. Module graphs can be visualized using graph visualization tools like Graphviz or Gephi and can be managed using NetworkX in Python.
Simple Visualization of Module Graphs¶
- NetworkX graphs can be visualized using
networkx.draw. - Execute the cell below to load the decentral multiplexer and display its module graph.
In [1]:
Copied!
import networkx as nx
import netlist_carpentry
circuit = netlist_carpentry.read("files/decentral_mux.v", top="decentral_mux")
original_module_graph = circuit.top.graph()
nx.draw(original_module_graph)
import networkx as nx
import netlist_carpentry
circuit = netlist_carpentry.read("files/decentral_mux.v", top="decentral_mux")
original_module_graph = circuit.top.graph()
nx.draw(original_module_graph)
- As can be seen in the graph representation, there are a couple of nodes without outgoing or incoming edges.
- In the decentral mux, this is due to a
genvarused to describe the multiplexer's behavior in aforloop. - Yosys includes this integer as a wire, and since it originally consisted of 32 bit, Yosys implemented a 32-bit constant wire that is driven by constant buffer elements (i.e. buffers that are tied to a certain value) in the netlist.
- To remove these useless constructs,
Module.optimizecan be used. - Execute the cell below to optimize the module and re-render the graph afterwards, without the useless nodes.
In [2]:
Copied!
circuit.top.optimize()
optimized_module_graph = circuit.top.graph()
nx.draw(optimized_module_graph)
circuit.top.optimize()
optimized_module_graph = circuit.top.graph()
nx.draw(optimized_module_graph)
Customizing the Graph Representation¶
- To customize the visualization, use a layout algorithm to position nodes in the graph.
- The Kamada-Kawai layout is used here, since it is a force-directed layout, where edges are treated as springs pulling connected nodes together, while non-connected nodes repel each other.
- Execute the cell below to render the module graph using this layout algorithm.
In [3]:
Copied!
pos = nx.kamada_kawai_layout(optimized_module_graph)
nx.draw(optimized_module_graph, pos)
pos = nx.kamada_kawai_layout(optimized_module_graph)
nx.draw(optimized_module_graph, pos)
- To label the nodes (i.e. make them display their instance names), the parameter
with_labelscan be used. - Also, using
matplotlib, the figure size can be increased to force the algorithm to place the nodes and labels in a more readable manner. - Execute the cell below to render the module graph in a more stretched version and with labelled nodes.
In [4]:
Copied!
import matplotlib.pyplot as plt
pos = nx.kamada_kawai_layout(optimized_module_graph)
plt.figure(figsize=(20, 12))
nx.draw(optimized_module_graph, pos, with_labels=True)
plt.show()
import matplotlib.pyplot as plt
pos = nx.kamada_kawai_layout(optimized_module_graph)
plt.figure(figsize=(20, 12))
nx.draw(optimized_module_graph, pos, with_labels=True)
plt.show()
- To further customize the presented graph, nodes can be colored and labelled differently, depending on additional data.
- For example, the nodes can be colored differently depending on their type (input, output or internal instance).
- This can be retrieved from the module graph using the
ntype(node type) andnsubtype(node type additional info) attributes. - The
ntypeattribute contains the superordinate type of the node (i.e. whether it is a port or an instance), whereasnsubtypedescribes additional information, such as the direction of a port (e.g.input,output) or the type of the instance. - This information can be used to color the nodes differently as well as relabel them for better readability.
- Execute the cell below to render a colored graph using the
node_colorandlabelsparameters, where inputs are green, outputs are red, and all other nodes are blue.
In [5]:
Copied!
input_nodes = []
output_nodes = []
other_nodes = []
labels = {}
for node in optimized_module_graph.nodes:
ntype = optimized_module_graph.nodes[node]["nsubtype"]
if ntype == "input":
input_nodes.append(node)
labels[node] = node
elif ntype == "output":
output_nodes.append(node)
labels[node] = node
else:
other_nodes.append(node)
labels[node] = ntype
pos = nx.kamada_kawai_layout(optimized_module_graph)
plt.figure(figsize=(20, 12))
nx.draw_networkx_nodes(optimized_module_graph, pos, nodelist=input_nodes, node_size=700, node_color="green")
nx.draw_networkx_nodes(optimized_module_graph, pos, nodelist=output_nodes, node_size=700, node_color="red")
nx.draw_networkx_nodes(optimized_module_graph, pos, nodelist=other_nodes, node_size=700, node_color="lightblue")
nx.draw_networkx_edges(optimized_module_graph, pos, node_size=700)
nx.draw_networkx_labels(optimized_module_graph, pos, labels)
plt.show()
input_nodes = []
output_nodes = []
other_nodes = []
labels = {}
for node in optimized_module_graph.nodes:
ntype = optimized_module_graph.nodes[node]["nsubtype"]
if ntype == "input":
input_nodes.append(node)
labels[node] = node
elif ntype == "output":
output_nodes.append(node)
labels[node] = node
else:
other_nodes.append(node)
labels[node] = ntype
pos = nx.kamada_kawai_layout(optimized_module_graph)
plt.figure(figsize=(20, 12))
nx.draw_networkx_nodes(optimized_module_graph, pos, nodelist=input_nodes, node_size=700, node_color="green")
nx.draw_networkx_nodes(optimized_module_graph, pos, nodelist=output_nodes, node_size=700, node_color="red")
nx.draw_networkx_nodes(optimized_module_graph, pos, nodelist=other_nodes, node_size=700, node_color="lightblue")
nx.draw_networkx_edges(optimized_module_graph, pos, node_size=700)
nx.draw_networkx_labels(optimized_module_graph, pos, labels)
plt.show()
- For more information and detailed explanations (especially regarding automated graph formatting/output), look into the Visualization section of the User Guide.