This ensures `isort` is applied to all files in the repo. Change-Id: Ib7ced1c924ef1639542bf0d1a01c5737f6ba43e9
537 lines
17 KiB
Python
Executable File
537 lines
17 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
|
|
# Copyright (c) 2011 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.
|
|
|
|
# Pipeline activity viewer for the O3 CPU model.
|
|
|
|
import argparse
|
|
import copy
|
|
import os
|
|
import sys
|
|
|
|
# Temporary storage for instructions. The queue is filled in out-of-order
|
|
# until it reaches 'max_threshold' number of instructions. It is then
|
|
# sorted out and instructions are printed out until their number drops to
|
|
# 'min_threshold'.
|
|
# It is assumed that the instructions are not out of order for more then
|
|
# 'min_threshold' places - otherwise they will appear out of order.
|
|
insts = {
|
|
"queue": [], # Instructions to print.
|
|
"max_threshold": 2000, # Instructions are sorted out and printed when
|
|
# their number reaches this threshold.
|
|
"min_threshold": 1000, # Printing stops when this number is reached.
|
|
"sn_start": 0, # The first instruction seq. number to be printed.
|
|
"sn_stop": 0, # The last instruction seq. number to be printed.
|
|
"tick_start": 0, # The first tick to be printed
|
|
"tick_stop": 0, # The last tick to be printed
|
|
"tick_drift": 2000, # Used to calculate the start and the end of main
|
|
# loop. We assume here that the instructions are not
|
|
# out of order for more then 2000 CPU ticks,
|
|
# otherwise the print may not start/stop
|
|
# at the time specified by tick_start/stop.
|
|
"only_committed": 0, # Set if only committed instructions are printed.
|
|
}
|
|
|
|
|
|
def process_trace(
|
|
trace,
|
|
outfile,
|
|
cycle_time,
|
|
width,
|
|
color,
|
|
timestamps,
|
|
committed_only,
|
|
store_completions,
|
|
start_tick,
|
|
stop_tick,
|
|
start_sn,
|
|
stop_sn,
|
|
):
|
|
global insts
|
|
|
|
insts["sn_start"] = start_sn
|
|
insts["sn_stop"] = stop_sn
|
|
insts["tick_start"] = start_tick
|
|
insts["tick_stop"] = stop_tick
|
|
insts["tick_drift"] = insts["tick_drift"] * cycle_time
|
|
insts["only_committed"] = committed_only
|
|
line = None
|
|
fields = None
|
|
|
|
# Skip lines up to the starting tick
|
|
if start_tick != 0:
|
|
while True:
|
|
line = trace.readline()
|
|
if not line:
|
|
return
|
|
fields = line.split(":")
|
|
if fields[0] != "O3PipeView":
|
|
continue
|
|
if int(fields[2]) >= start_tick:
|
|
break
|
|
elif start_sn != 0:
|
|
while True:
|
|
line = trace.readline()
|
|
if not line:
|
|
return
|
|
fields = line.split(":")
|
|
if fields[0] != "O3PipeView":
|
|
continue
|
|
if fields[1] == "fetch" and int(fields[5]) >= start_sn:
|
|
break
|
|
else:
|
|
line = trace.readline()
|
|
if not line:
|
|
return
|
|
fields = line.split(":")
|
|
|
|
# Skip lines up to next instruction fetch
|
|
while fields[0] != "O3PipeView" or fields[1] != "fetch":
|
|
line = trace.readline()
|
|
if not line:
|
|
return
|
|
fields = line.split(":")
|
|
|
|
# Print header
|
|
outfile.write(
|
|
"// f = fetch, d = decode, n = rename, p = dispatch, "
|
|
"i = issue, c = complete, r = retire"
|
|
)
|
|
|
|
if store_completions:
|
|
outfile.write(", s = store-complete")
|
|
outfile.write("\n\n")
|
|
|
|
outfile.write(
|
|
" "
|
|
+ "timeline".center(width)
|
|
+ " "
|
|
+ "tick".center(15)
|
|
+ " "
|
|
+ "pc.upc".center(12)
|
|
+ " "
|
|
+ "disasm".ljust(25)
|
|
+ " "
|
|
+ "seq_num".center(10)
|
|
)
|
|
if timestamps:
|
|
outfile.write("timestamps".center(25))
|
|
outfile.write("\n")
|
|
|
|
# Region of interest
|
|
curr_inst = {}
|
|
while True:
|
|
if fields[0] == "O3PipeView":
|
|
curr_inst[fields[1]] = int(fields[2])
|
|
if fields[1] == "fetch":
|
|
if (
|
|
stop_tick > 0
|
|
and int(fields[2]) > stop_tick + insts["tick_drift"]
|
|
) or (
|
|
stop_sn > 0
|
|
and int(fields[5]) > (stop_sn + insts["max_threshold"])
|
|
):
|
|
print_insts(
|
|
outfile,
|
|
cycle_time,
|
|
width,
|
|
color,
|
|
timestamps,
|
|
store_completions,
|
|
0,
|
|
)
|
|
return
|
|
(curr_inst["pc"], curr_inst["upc"]) = fields[3:5]
|
|
curr_inst["sn"] = int(fields[5])
|
|
curr_inst["disasm"] = " ".join(fields[6][:-1].split())
|
|
elif fields[1] == "retire":
|
|
if curr_inst["retire"] == 0:
|
|
curr_inst["disasm"] = "-----" + curr_inst["disasm"]
|
|
if store_completions:
|
|
curr_inst[fields[3]] = int(fields[4])
|
|
queue_inst(
|
|
outfile,
|
|
curr_inst,
|
|
cycle_time,
|
|
width,
|
|
color,
|
|
timestamps,
|
|
store_completions,
|
|
)
|
|
|
|
line = trace.readline()
|
|
if not line:
|
|
print_insts(
|
|
outfile,
|
|
cycle_time,
|
|
width,
|
|
color,
|
|
timestamps,
|
|
store_completions,
|
|
0,
|
|
)
|
|
return
|
|
fields = line.split(":")
|
|
|
|
|
|
# Puts new instruction into the print queue.
|
|
# Sorts out and prints instructions when their number reaches threshold value
|
|
def queue_inst(
|
|
outfile, inst, cycle_time, width, color, timestamps, store_completions
|
|
):
|
|
global insts
|
|
l_copy = copy.deepcopy(inst)
|
|
insts["queue"].append(l_copy)
|
|
if len(insts["queue"]) > insts["max_threshold"]:
|
|
print_insts(
|
|
outfile,
|
|
cycle_time,
|
|
width,
|
|
color,
|
|
timestamps,
|
|
store_completions,
|
|
insts["min_threshold"],
|
|
)
|
|
|
|
|
|
# Sorts out and prints instructions in print queue
|
|
def print_insts(
|
|
outfile,
|
|
cycle_time,
|
|
width,
|
|
color,
|
|
timestamps,
|
|
store_completions,
|
|
lower_threshold,
|
|
):
|
|
global insts
|
|
# sort the list of insts by sequence numbers
|
|
insts["queue"].sort(key=lambda inst: inst["sn"])
|
|
while len(insts["queue"]) > lower_threshold:
|
|
print_item = insts["queue"].pop(0)
|
|
# As the instructions are processed out of order the main loop starts
|
|
# earlier then specified by start_sn/tick and finishes later then what
|
|
# is defined in stop_sn/tick.
|
|
# Therefore, here we have to filter out instructions that reside out of
|
|
# the specified boundaries.
|
|
if insts["sn_start"] > 0 and print_item["sn"] < insts["sn_start"]:
|
|
continue
|
|
# earlier then the starting sequence number
|
|
if insts["sn_stop"] > 0 and print_item["sn"] > insts["sn_stop"]:
|
|
continue
|
|
# later then the ending sequence number
|
|
if (
|
|
insts["tick_start"] > 0
|
|
and print_item["fetch"] < insts["tick_start"]
|
|
):
|
|
continue
|
|
# earlier then the starting tick number
|
|
if insts["tick_stop"] > 0 and print_item["fetch"] > insts["tick_stop"]:
|
|
continue
|
|
# later then the ending tick number
|
|
|
|
if insts["only_committed"] != 0 and print_item["retire"] == 0:
|
|
continue
|
|
# retire is set to zero if it hasn't been completed
|
|
print_inst(
|
|
outfile,
|
|
print_item,
|
|
cycle_time,
|
|
width,
|
|
color,
|
|
timestamps,
|
|
store_completions,
|
|
)
|
|
|
|
|
|
# Prints a single instruction
|
|
def print_inst(
|
|
outfile, inst, cycle_time, width, color, timestamps, store_completions
|
|
):
|
|
if color:
|
|
from m5.util.terminal import termcap
|
|
else:
|
|
from m5.util.terminal import no_termcap as termcap
|
|
# Pipeline stages
|
|
stages = [
|
|
{
|
|
"name": "fetch",
|
|
"color": termcap.Blue + termcap.Reverse,
|
|
"shorthand": "f",
|
|
},
|
|
{
|
|
"name": "decode",
|
|
"color": termcap.Yellow + termcap.Reverse,
|
|
"shorthand": "d",
|
|
},
|
|
{
|
|
"name": "rename",
|
|
"color": termcap.Magenta + termcap.Reverse,
|
|
"shorthand": "n",
|
|
},
|
|
{
|
|
"name": "dispatch",
|
|
"color": termcap.Green + termcap.Reverse,
|
|
"shorthand": "p",
|
|
},
|
|
{
|
|
"name": "issue",
|
|
"color": termcap.Red + termcap.Reverse,
|
|
"shorthand": "i",
|
|
},
|
|
{
|
|
"name": "complete",
|
|
"color": termcap.Cyan + termcap.Reverse,
|
|
"shorthand": "c",
|
|
},
|
|
{
|
|
"name": "retire",
|
|
"color": termcap.Blue + termcap.Reverse,
|
|
"shorthand": "r",
|
|
},
|
|
]
|
|
if store_completions:
|
|
stages.append(
|
|
{
|
|
"name": "store",
|
|
"color": termcap.Yellow + termcap.Reverse,
|
|
"shorthand": "s",
|
|
}
|
|
)
|
|
|
|
# Print
|
|
|
|
time_width = width * cycle_time
|
|
base_tick = (inst["fetch"] // time_width) * time_width
|
|
|
|
# Find out the time of the last event - it may not
|
|
# be 'retire' if the instruction is not comlpeted.
|
|
last_event_time = max(
|
|
inst["fetch"],
|
|
inst["decode"],
|
|
inst["rename"],
|
|
inst["dispatch"],
|
|
inst["issue"],
|
|
inst["complete"],
|
|
inst["retire"],
|
|
)
|
|
if store_completions:
|
|
last_event_time = max(last_event_time, inst["store"])
|
|
|
|
# Timeline shorter then time_width is printed in compact form where
|
|
# the print continues at the start of the same line.
|
|
if (last_event_time - inst["fetch"]) < time_width:
|
|
num_lines = 1 # compact form
|
|
else:
|
|
num_lines = ((last_event_time - base_tick) // time_width) + 1
|
|
|
|
curr_color = termcap.Normal
|
|
|
|
# This will visually distinguish completed and abandoned intructions.
|
|
if inst["retire"] == 0:
|
|
dot = "=" # abandoned instruction
|
|
else:
|
|
dot = "." # completed instruction
|
|
|
|
for i in range(num_lines):
|
|
start_tick = base_tick + i * time_width
|
|
end_tick = start_tick + time_width
|
|
if num_lines == 1: # compact form
|
|
end_tick += inst["fetch"] - base_tick
|
|
events = []
|
|
for stage_idx in range(len(stages)):
|
|
tick = inst[stages[stage_idx]["name"]]
|
|
if tick != 0:
|
|
if tick >= start_tick and tick < end_tick:
|
|
events.append(
|
|
(
|
|
tick % time_width,
|
|
stages[stage_idx]["name"],
|
|
stage_idx,
|
|
tick,
|
|
)
|
|
)
|
|
events.sort()
|
|
outfile.write("[")
|
|
pos = 0
|
|
if num_lines == 1 and events[0][2] != 0: # event is not fetch
|
|
curr_color = stages[events[0][2] - 1]["color"]
|
|
for event in events:
|
|
if (
|
|
stages[event[2]]["name"] == "dispatch"
|
|
and inst["dispatch"] == inst["issue"]
|
|
):
|
|
continue
|
|
outfile.write(curr_color + dot * ((event[0] // cycle_time) - pos))
|
|
outfile.write(
|
|
stages[event[2]]["color"] + stages[event[2]]["shorthand"]
|
|
)
|
|
|
|
if event[3] != last_event_time: # event is not the last one
|
|
curr_color = stages[event[2]]["color"]
|
|
else:
|
|
curr_color = termcap.Normal
|
|
|
|
pos = (event[0] // cycle_time) + 1
|
|
outfile.write(
|
|
curr_color
|
|
+ dot * (width - pos)
|
|
+ termcap.Normal
|
|
+ "]-("
|
|
+ str(base_tick + i * time_width).rjust(15)
|
|
+ ") "
|
|
)
|
|
if i == 0:
|
|
outfile.write(
|
|
"%s.%s %s [%s]"
|
|
% (
|
|
inst["pc"].rjust(10),
|
|
inst["upc"],
|
|
inst["disasm"].ljust(25),
|
|
str(inst["sn"]).rjust(10),
|
|
)
|
|
)
|
|
if timestamps:
|
|
outfile.write(f" f={inst['fetch']}, r={inst['retire']}")
|
|
outfile.write("\n")
|
|
else:
|
|
outfile.write("...".center(12) + "\n")
|
|
|
|
|
|
def validate_range(my_range):
|
|
my_range = [int(i) for i in my_range.split(":")]
|
|
if (
|
|
len(my_range) != 2
|
|
or my_range[0] < 0
|
|
or my_range[1] > 0
|
|
and my_range[0] >= my_range[1]
|
|
):
|
|
return None
|
|
return my_range
|
|
|
|
|
|
def main():
|
|
# Parse args
|
|
usage = "%(prog)s [OPTION]... TRACE_FILE"
|
|
parser = argparse.ArgumentParser(
|
|
usage=usage, formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
|
)
|
|
parser.add_argument(
|
|
"-o",
|
|
dest="outfile",
|
|
default=os.path.join(os.getcwd(), "o3-pipeview.out"),
|
|
help="output file",
|
|
)
|
|
parser.add_argument(
|
|
"-t", dest="tick_range", default="0:-1", help="tick range (-1 == inf.)"
|
|
)
|
|
parser.add_argument(
|
|
"-i",
|
|
dest="inst_range",
|
|
default="0:-1",
|
|
help="instruction range (-1 == inf.)",
|
|
)
|
|
parser.add_argument(
|
|
"-w", dest="width", type=int, default=80, help="timeline width"
|
|
)
|
|
parser.add_argument(
|
|
"--color",
|
|
action="store_true",
|
|
default=False,
|
|
help="enable colored output",
|
|
)
|
|
parser.add_argument(
|
|
"-c",
|
|
"--cycle-time",
|
|
type=int,
|
|
default=1000,
|
|
help="CPU cycle time in ticks",
|
|
)
|
|
parser.add_argument(
|
|
"--timestamps",
|
|
action="store_true",
|
|
default=False,
|
|
help="print fetch and retire timestamps",
|
|
)
|
|
parser.add_argument(
|
|
"--only_committed",
|
|
action="store_true",
|
|
default=False,
|
|
help="display only committed (completed) instructions",
|
|
)
|
|
parser.add_argument(
|
|
"--store_completions",
|
|
action="store_true",
|
|
default=False,
|
|
help="additionally display store completion ticks",
|
|
)
|
|
parser.add_argument("tracefile")
|
|
|
|
args = parser.parse_args()
|
|
tick_range = validate_range(args.tick_range)
|
|
if not tick_range:
|
|
parser.error("invalid range")
|
|
sys.exit(1)
|
|
inst_range = validate_range(args.inst_range)
|
|
if not inst_range:
|
|
parser.error("invalid range")
|
|
sys.exit(1)
|
|
# Process trace
|
|
print("Processing trace... ", end=" ")
|
|
with open(args.tracefile) as trace:
|
|
with open(args.outfile, "w") as out:
|
|
process_trace(
|
|
trace,
|
|
out,
|
|
args.cycle_time,
|
|
args.width,
|
|
args.color,
|
|
args.timestamps,
|
|
args.only_committed,
|
|
args.store_completions,
|
|
*(tick_range + inst_range),
|
|
)
|
|
print("done!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.path.append(
|
|
os.path.join(
|
|
os.path.dirname(os.path.abspath(__file__)), "..", "src", "python"
|
|
)
|
|
)
|
|
main()
|