| # Copyright (c) 2012-2013 ARM Limited | 
 | # All rights reserved. | 
 | # | 
 | # The license below extends only to copyright in the software and shall | 
 | # not be construed as granting a license to any other intellectual | 
 | # property including but not limited to intellectual property relating | 
 | # to a hardware implementation of the functionality of the software | 
 | # licensed hereunder.  You may use the software subject to the license | 
 | # terms below provided that you ensure that this notice is replicated | 
 | # unmodified and in its entirety in all distributions of the software, | 
 | # modified or unmodified, in source code or in binary form. | 
 | # | 
 | # Redistribution and use in source and binary forms, with or without | 
 | # modification, are permitted provided that the following conditions are | 
 | # met: redistributions of source code must retain the above copyright | 
 | # notice, this list of conditions and the following disclaimer; | 
 | # redistributions in binary form must reproduce the above copyright | 
 | # notice, this list of conditions and the following disclaimer in the | 
 | # documentation and/or other materials provided with the distribution; | 
 | # neither the name of the copyright holders nor the names of its | 
 | # contributors may be used to endorse or promote products derived from | 
 | # this software without specific prior written permission. | 
 | # | 
 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 | # | 
 | # Authors: Andreas Hansson | 
 | #          Uri Wiener | 
 | #          Sascha Bischoff | 
 |  | 
 | ##################################################################### | 
 | # | 
 | # System visualization using DOT | 
 | # | 
 | # While config.ini and config.json provide an almost complete listing | 
 | # of a system's components and connectivity, they lack a birds-eye | 
 | # view. The output generated by do_dot() is a DOT-based figure (as a | 
 | # pdf and an editable svg file) and its source dot code. Nodes are | 
 | # components, and edges represent the memory hierarchy: the edges are | 
 | # directed, from a master to slave. Initially all nodes are | 
 | # generated, and then all edges are added. do_dot should be called | 
 | # with the top-most SimObject (namely root but not necessarily), the | 
 | # output folder and the output dot source filename. From the given | 
 | # node, both processes (node and edge creation) is performed | 
 | # recursivly, traversing all children of the given root. | 
 | # | 
 | # pydot is required. When missing, no output will be generated. | 
 | # | 
 | ##################################################################### | 
 |  | 
 | import m5, os, re | 
 | from m5.SimObject import isRoot, isSimObjectVector | 
 | from m5.params import PortRef, isNullPointer | 
 | from m5.util import warn | 
 | try: | 
 |     import pydot | 
 | except: | 
 |     pydot = False | 
 |  | 
 | def simnode_children(simNode): | 
 |     for child in simNode._children.itervalues(): | 
 |         if isNullPointer(child): | 
 |             continue | 
 |         if isSimObjectVector(child): | 
 |             for obj in child: | 
 |                 if not isNullPointer(obj): | 
 |                     yield obj | 
 |         else: | 
 |             yield child | 
 |  | 
 | # need to create all nodes (components) before creating edges (memory channels) | 
 | def dot_create_nodes(simNode, callgraph): | 
 |     if isRoot(simNode): | 
 |         label = "root" | 
 |     else: | 
 |         label = simNode._name | 
 |     full_path = re.sub('\.', '_', simNode.path()) | 
 |     # add class name under the label | 
 |     label = "\"" + label + " \\n: " + simNode.__class__.__name__ + "\"" | 
 |  | 
 |     # each component is a sub-graph (cluster) | 
 |     cluster = dot_create_cluster(simNode, full_path, label) | 
 |  | 
 |     # create nodes per port | 
 |     for port_name in simNode._ports.keys(): | 
 |         port = simNode._port_refs.get(port_name, None) | 
 |         if port != None: | 
 |             full_port_name = full_path + "_" + port_name | 
 |             port_node = dot_create_node(simNode, full_port_name, port_name) | 
 |             cluster.add_node(port_node) | 
 |  | 
 |     # recurse to children | 
 |     for child in simnode_children(simNode): | 
 |         dot_create_nodes(child, cluster) | 
 |  | 
 |     callgraph.add_subgraph(cluster) | 
 |  | 
 | # create all edges according to memory hierarchy | 
 | def dot_create_edges(simNode, callgraph): | 
 |     for port_name in simNode._ports.keys(): | 
 |         port = simNode._port_refs.get(port_name, None) | 
 |         if port != None: | 
 |             full_path = re.sub('\.', '_', simNode.path()) | 
 |             full_port_name = full_path + "_" + port_name | 
 |             port_node = dot_create_node(simNode, full_port_name, port_name) | 
 |             # create edges | 
 |             if isinstance(port, PortRef): | 
 |                 dot_add_edge(simNode, callgraph, full_port_name, port) | 
 |             else: | 
 |                 for p in port.elements: | 
 |                     dot_add_edge(simNode, callgraph, full_port_name, p) | 
 |  | 
 |     # recurse to children | 
 |     for child in simnode_children(simNode): | 
 |         dot_create_edges(child, callgraph) | 
 |  | 
 | def dot_add_edge(simNode, callgraph, full_port_name, peerPort): | 
 |     if peerPort.role == "MASTER": | 
 |         peer_port_name = re.sub('\.', '_', peerPort.peer.simobj.path() \ | 
 |                 + "." + peerPort.peer.name) | 
 |         callgraph.add_edge(pydot.Edge(full_port_name, peer_port_name)) | 
 |  | 
 | def dot_create_cluster(simNode, full_path, label): | 
 |     # get the parameter values of the node and use them as a tooltip | 
 |     ini_strings = [] | 
 |     for param in sorted(simNode._params.keys()): | 
 |         value = simNode._values.get(param) | 
 |         if value != None: | 
 |             # parameter name = value in HTML friendly format | 
 |             ini_strings.append(str(param) + "=" + | 
 |                                simNode._values[param].ini_str()) | 
 |     # join all the parameters with an HTML newline | 
 |     tooltip = "
".join(ini_strings) | 
 |  | 
 |     return pydot.Cluster( \ | 
 |                          full_path, \ | 
 |                          shape = "Mrecord", \ | 
 |                          label = label, \ | 
 |                          tooltip = "\"" + tooltip + "\"", \ | 
 |                          style = "\"rounded, filled\"", \ | 
 |                          color = "#000000", \ | 
 |                          fillcolor = dot_gen_colour(simNode), \ | 
 |                          fontname = "Arial", \ | 
 |                          fontsize = "14", \ | 
 |                          fontcolor = "#000000" \ | 
 |                          ) | 
 |  | 
 | def dot_create_node(simNode, full_path, label): | 
 |     return pydot.Node( \ | 
 |                          full_path, \ | 
 |                          shape = "Mrecord", \ | 
 |                          label = label, \ | 
 |                          style = "\"rounded, filled\"", \ | 
 |                          color = "#000000", \ | 
 |                          fillcolor = dot_gen_colour(simNode, True), \ | 
 |                          fontname = "Arial", \ | 
 |                          fontsize = "14", \ | 
 |                          fontcolor = "#000000" \ | 
 |                          ) | 
 |  | 
 | # an enumerator for different kinds of node types, at the moment we | 
 | # discern the majority of node types, with the caches being the | 
 | # notable exception | 
 | class NodeType: | 
 |     SYS = 0 | 
 |     CPU = 1 | 
 |     XBAR = 2 | 
 |     MEM = 3 | 
 |     DEV = 4 | 
 |     OTHER = 5 | 
 |  | 
 | # based on the sim object, determine the node type | 
 | def get_node_type(simNode): | 
 |     if isinstance(simNode, m5.objects.System): | 
 |         return NodeType.SYS | 
 |     # NULL ISA has no BaseCPU or PioDevice, so check if these names | 
 |     # exists before using them | 
 |     elif 'BaseCPU' in dir(m5.objects) and \ | 
 |             isinstance(simNode, m5.objects.BaseCPU): | 
 |         return NodeType.CPU | 
 |     elif 'PioDevice' in dir(m5.objects) and \ | 
 |             isinstance(simNode, m5.objects.PioDevice): | 
 |         return NodeType.DEV | 
 |     elif isinstance(simNode, m5.objects.BaseXBar): | 
 |         return NodeType.XBAR | 
 |     elif isinstance(simNode, m5.objects.AbstractMemory): | 
 |         return NodeType.MEM | 
 |     else: | 
 |         return NodeType.OTHER | 
 |  | 
 | # based on the node type, determine the colour as an RGB tuple, the | 
 | # palette is rather arbitrary at this point (some coherent natural | 
 | # tones), and someone that feels artistic should probably have a look | 
 | def get_type_colour(nodeType): | 
 |     if nodeType == NodeType.SYS: | 
 |         return (228, 231, 235) | 
 |     elif nodeType == NodeType.CPU: | 
 |         return (187, 198, 217) | 
 |     elif nodeType == NodeType.XBAR: | 
 |         return (111, 121, 140) | 
 |     elif nodeType == NodeType.MEM: | 
 |         return (94, 89, 88) | 
 |     elif nodeType == NodeType.DEV: | 
 |         return (199, 167, 147) | 
 |     elif nodeType == NodeType.OTHER: | 
 |         # use a relatively gray shade | 
 |         return (186, 182, 174) | 
 |  | 
 | # generate colour for a node, either corresponding to a sim object or a | 
 | # port | 
 | def dot_gen_colour(simNode, isPort = False): | 
 |     # determine the type of the current node, and also its parent, if | 
 |     # the node is not the same type as the parent then we use the base | 
 |     # colour for its type | 
 |     node_type = get_node_type(simNode) | 
 |     if simNode._parent: | 
 |         parent_type = get_node_type(simNode._parent) | 
 |     else: | 
 |         parent_type = NodeType.OTHER | 
 |  | 
 |     # if this node is the same type as the parent, then scale the | 
 |     # colour based on the depth such that the deeper levels in the | 
 |     # hierarchy get darker colours | 
 |     if node_type == parent_type: | 
 |         # start out with a depth of zero | 
 |         depth = 0 | 
 |         parent = simNode._parent | 
 |         # find the closes parent that is not the same type | 
 |         while parent and get_node_type(parent) == parent_type: | 
 |             depth = depth + 1 | 
 |             parent = parent._parent | 
 |         node_colour = get_type_colour(parent_type) | 
 |         # slightly arbitrary, but assume that the depth is less than | 
 |         # five levels | 
 |         r, g, b = map(lambda x: x * max(1 - depth / 7.0, 0.3), node_colour) | 
 |     else: | 
 |         node_colour = get_type_colour(node_type) | 
 |         r, g, b = node_colour | 
 |  | 
 |     # if we are colouring a port, then make it a slightly darker shade | 
 |     # than the node that encapsulates it, once again use a magic constant | 
 |     if isPort: | 
 |         r, g, b = map(lambda x: 0.8 * x, (r, g, b)) | 
 |  | 
 |     return dot_rgb_to_html(r, g, b) | 
 |  | 
 | def dot_rgb_to_html(r, g, b): | 
 |     return "#%.2x%.2x%.2x" % (r, g, b) | 
 |  | 
 | # We need to create all of the clock domains. We abuse the alpha channel to get | 
 | # the correct domain colouring. | 
 | def dot_add_clk_domain(c_dom, v_dom): | 
 |     label = "\"" + str(c_dom) + "\ :\ " + str(v_dom) + "\"" | 
 |     label = re.sub('\.', '_', str(label)) | 
 |     full_path = re.sub('\.', '_', str(c_dom)) | 
 |     return pydot.Cluster( \ | 
 |                      full_path, \ | 
 |                      shape = "Mrecord", \ | 
 |                      label = label, \ | 
 |                      style = "\"rounded, filled, dashed\"", \ | 
 |                      color = "#000000", \ | 
 |                      fillcolor = "#AFC8AF8F", \ | 
 |                      fontname = "Arial", \ | 
 |                      fontsize = "14", \ | 
 |                      fontcolor = "#000000" \ | 
 |                      ) | 
 |  | 
 | def dot_create_dvfs_nodes(simNode, callgraph, domain=None): | 
 |     if isRoot(simNode): | 
 |         label = "root" | 
 |     else: | 
 |         label = simNode._name | 
 |     full_path = re.sub('\.', '_', simNode.path()) | 
 |     # add class name under the label | 
 |     label = "\"" + label + " \\n: " + simNode.__class__.__name__ + "\"" | 
 |  | 
 |     # each component is a sub-graph (cluster) | 
 |     cluster = dot_create_cluster(simNode, full_path, label) | 
 |  | 
 |     # create nodes per port | 
 |     for port_name in simNode._ports.keys(): | 
 |         port = simNode._port_refs.get(port_name, None) | 
 |         if port != None: | 
 |             full_port_name = full_path + "_" + port_name | 
 |             port_node = dot_create_node(simNode, full_port_name, port_name) | 
 |             cluster.add_node(port_node) | 
 |  | 
 |     # Dictionary of DVFS domains | 
 |     dvfs_domains = {} | 
 |  | 
 |     # recurse to children | 
 |     for child in simnode_children(simNode): | 
 |         try: | 
 |             c_dom = child.__getattr__('clk_domain') | 
 |             v_dom = c_dom.__getattr__('voltage_domain') | 
 |         except AttributeError: | 
 |             # Just re-use the domain from above | 
 |             c_dom = domain | 
 |             v_dom = c_dom.__getattr__('voltage_domain') | 
 |             pass | 
 |  | 
 |         if c_dom == domain or c_dom == None: | 
 |             dot_create_dvfs_nodes(child, cluster, domain) | 
 |         else: | 
 |             if c_dom not in dvfs_domains: | 
 |                 dvfs_cluster = dot_add_clk_domain(c_dom, v_dom) | 
 |                 dvfs_domains[c_dom] = dvfs_cluster | 
 |             else: | 
 |                 dvfs_cluster = dvfs_domains[c_dom] | 
 |             dot_create_dvfs_nodes(child, dvfs_cluster, c_dom) | 
 |  | 
 |     for key in dvfs_domains: | 
 |         cluster.add_subgraph(dvfs_domains[key]) | 
 |  | 
 |     callgraph.add_subgraph(cluster) | 
 |  | 
 | def do_dot(root, outdir, dotFilename): | 
 |     if not pydot: | 
 |         return | 
 |     # * use ranksep > 1.0 for for vertical separation between nodes | 
 |     # especially useful if you need to annotate edges using e.g. visio | 
 |     # which accepts svg format | 
 |     # * no need for hoizontal separation as nothing moves horizonally | 
 |     callgraph = pydot.Dot(graph_type='digraph', ranksep='1.3') | 
 |     dot_create_nodes(root, callgraph) | 
 |     dot_create_edges(root, callgraph) | 
 |     dot_filename = os.path.join(outdir, dotFilename) | 
 |     callgraph.write(dot_filename) | 
 |     try: | 
 |         # dot crashes if the figure is extremely wide. | 
 |         # So avoid terminating simulation unnecessarily | 
 |         callgraph.write_svg(dot_filename + ".svg") | 
 |         callgraph.write_pdf(dot_filename + ".pdf") | 
 |     except: | 
 |         warn("failed to generate dot output from %s", dot_filename) | 
 |  | 
 | def do_dvfs_dot(root, outdir, dotFilename): | 
 |     if not pydot: | 
 |         return | 
 |  | 
 |     # There is a chance that we are unable to resolve the clock or | 
 |     # voltage domains. If so, we fail silently. | 
 |     try: | 
 |         dvfsgraph = pydot.Dot(graph_type='digraph', ranksep='1.3') | 
 |         dot_create_dvfs_nodes(root, dvfsgraph) | 
 |         dot_create_edges(root, dvfsgraph) | 
 |         dot_filename = os.path.join(outdir, dotFilename) | 
 |         dvfsgraph.write(dot_filename) | 
 |     except: | 
 |         warn("Failed to generate dot graph for DVFS domains") | 
 |         return | 
 |  | 
 |     try: | 
 |         # dot crashes if the figure is extremely wide. | 
 |         # So avoid terminating simulation unnecessarily | 
 |         dvfsgraph.write_svg(dot_filename + ".svg") | 
 |         dvfsgraph.write_pdf(dot_filename + ".pdf") | 
 |     except: | 
 |         warn("failed to generate dot output from %s", dot_filename) |