| #! /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() |