blob: fe49706dad870dec036f0a7c2dd5c2177e45b732 [file] [log] [blame] [edit]
#! /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 os
import sys
import copy
# 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, "r") 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()