1# Copyright (c) 2014 Mark D. Hill and David A. Wood
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27
28from ConfigParser import ConfigParser
29import string, sys, subprocess, os
30
31# Compile DSENT to generate the Python module and then import it.
32# This script assumes it is executed from the gem5 root.
33print("Attempting compilation")
34from subprocess import call
35
36src_dir = 'ext/dsent'
37build_dir = 'build/ext/dsent'
38
39if not os.path.exists(build_dir):
40    os.makedirs(build_dir)
41os.chdir(build_dir)
42
43error = call(['cmake', '../../../%s' % src_dir])
44if error:
45    print("Failed to run cmake")
46    exit(-1)
47
48error = call(['make'])
49if error:
50    print("Failed to run make")
51    exit(-1)
52
53print("Compiled dsent")
54os.chdir("../../../")
55sys.path.append("build/ext/dsent")
56import dsent
57
58# Parse gem5 config.ini file for the configuration parameters related to
59# the on-chip network.
60def parseConfig(config_file):
61    config = ConfigParser()
62    if not config.read(config_file):
63        print("ERROR: config file '", config_file, "' not found")
64        sys.exit(1)
65
66    if not config.has_section("system.ruby.network"):
67        print("ERROR: Ruby network not found in '", config_file)
68        sys.exit(1)
69
70    if config.get("system.ruby.network", "type") != "GarnetNetwork_d" :
71        print("ERROR: Garnet network not used in '", config_file)
72        sys.exit(1)
73
74    number_of_virtual_networks = config.getint("system.ruby.network",
75                                               "number_of_virtual_networks")
76    vcs_per_vnet = config.getint("system.ruby.network", "vcs_per_vnet")
77
78    buffers_per_data_vc = config.getint("system.ruby.network",
79                                        "buffers_per_data_vc")
80    buffers_per_control_vc = config.getint("system.ruby.network",
81                                           "buffers_per_ctrl_vc")
82
83    ni_flit_size_bits = 8 * config.getint("system.ruby.network",
84                                          "ni_flit_size")
85
86    routers = config.get("system.ruby.network", "routers").split()
87    int_links = config.get("system.ruby.network", "int_links").split()
88    ext_links = config.get("system.ruby.network", "ext_links").split()
89
90    return (config, number_of_virtual_networks, vcs_per_vnet,
91            buffers_per_data_vc, buffers_per_control_vc, ni_flit_size_bits,
92            routers, int_links, ext_links)
93
94
95def getClock(obj, config):
96    if config.get(obj, "type") == "SrcClockDomain":
97        return config.getint(obj, "clock")
98
99    if config.get(obj, "type") == "DerivedClockDomain":
100        source = config.get(obj, "clk_domain")
101        divider = config.getint(obj, "clk_divider")
102        return getClock(source, config)  / divider
103
104    source = config.get(obj, "clk_domain")
105    return getClock(source, config)
106
107
108## Compute the power consumed by the given router
109def computeRouterPowerAndArea(router, stats_file, config, int_links, ext_links,
110                              number_of_virtual_networks, vcs_per_vnet,
111                              buffers_per_data_vc, buffers_per_control_vc,
112                              ni_flit_size_bits):
113    frequency = getClock(router, config)
114    num_ports = 0
115
116    for int_link in int_links:
117        if config.get(int_link, "node_a") == router or \
118           config.get(int_link, "node_b") == router:
119           num_ports += 1
120
121    for ext_link in ext_links:
122        if config.get(ext_link, "int_node") == router:
123           num_ports += 1
124
125    power = dsent.computeRouterPowerAndArea(frequency, num_ports, num_ports,
126                                            number_of_virtual_networks,
127                                            vcs_per_vnet, buffers_per_data_vc,
128                                            ni_flit_size_bits)
129
130    print("%s Power: " % router, power)
131
132
133## Compute the power consumed by the given link
134def computeLinkPower(link, stats_file, config, sim_seconds):
135    frequency = getClock(link + ".nls0", config)
136    power = dsent.computeLinkPower(frequency)
137    print("%s.nls0 Power: " % link, power)
138
139    frequency = getClock(link + ".nls1", config)
140    power = dsent.computeLinkPower(frequency)
141    print("%s.nls1 Power: " % link, power)
142
143
144def parseStats(stats_file, config, router_config_file, link_config_file,
145               routers, int_links, ext_links, number_of_virtual_networks,
146               vcs_per_vnet, buffers_per_data_vc, buffers_per_control_vc,
147               ni_flit_size_bits):
148
149    # Open the stats.txt file and parse it to for the required numbers
150    # and the number of routers.
151    try:
152        stats_handle = open(stats_file, 'r')
153        stats_handle.close()
154    except IOError:
155        print("Failed to open ", stats_file, " for reading")
156        exit(-1)
157
158    # Now parse the stats
159    pattern = "sim_seconds"
160    lines = string.split(subprocess.check_output(
161                ["grep", pattern, stats_file]), '\n', -1)
162    assert len(lines) >= 1
163
164    ## Assume that the first line is the one required
165    [l1,l2,l3] = lines[0].partition(" ")
166    l4 = l3.strip().partition(" ")
167    simulation_length_in_seconds = float(l4[0])
168
169    # Initialize DSENT with a configuration file
170    dsent.initialize(router_config_file)
171
172    # Compute the power consumed by the routers
173    for router in routers:
174        computeRouterPowerAndArea(router, stats_file, config, int_links,
175                                  ext_links, number_of_virtual_networks,
176                                  vcs_per_vnet, buffers_per_data_vc,
177                                  buffers_per_control_vc, ni_flit_size_bits)
178
179    # Finalize DSENT
180    dsent.finalize()
181
182    # Initialize DSENT with a configuration file
183    dsent.initialize(link_config_file)
184
185    # Compute the power consumed by the links
186    for link in int_links:
187        computeLinkPower(link, stats_file, config,
188                         simulation_length_in_seconds)
189    for link in ext_links:
190        computeLinkPower(link, stats_file, config,
191                         simulation_length_in_seconds)
192
193    # Finalize DSENT
194    dsent.finalize()
195
196# This script parses the config.ini and the stats.txt from a run and
197# generates the power and the area of the on-chip network using DSENT
198def main():
199    if len(sys.argv) != 5:
200        print("Usage: ", sys.argv[0], " <gem5 root directory> " \
201              "<simulation directory> <router config file> <link config file>")
202        exit(-1)
203
204    print("WARNING: configuration files for DSENT and McPAT are separate. " \
205          "Changes made to one are not reflected in the other.")
206
207    (config, number_of_virtual_networks, vcs_per_vnet, buffers_per_data_vc,
208     buffers_per_control_vc, ni_flit_size_bits, routers, int_links,
209     ext_links) = parseConfig("%s/%s/config.ini" % (sys.argv[1], sys.argv[2]))
210
211    parseStats("%s/%s/stats.txt" % (sys.argv[1], sys.argv[2]), config,
212               sys.argv[3], sys.argv[4], routers, int_links, ext_links,
213               number_of_virtual_networks, vcs_per_vnet, buffers_per_data_vc,
214               buffers_per_control_vc, ni_flit_size_bits)
215
216if __name__ == "__main__":
217    main()
218