|  | #! /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=%s, r=%s' % (inst['fetch'], 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() |