#!/usr/bin/env python3 # pylint: disable=C0103,C0114,C0116,C0209,R0914,R0912,R0915,eval-used ###################################################################### import argparse import collections import re # from pprint import pprint ###################################################################### def profcfunc(filename): funcs = {} with open(filename, "r", encoding="utf8") as fh: for line in fh: # %time cumesec selfsec calls {stuff} name match = re.match( r'^\s*([0-9.]+)\s+[0-9.]+\s+([0-9.]+)\s+([0-9.]+)\s+[^a-zA-Z_]*([a-zA-Z_].*)$', line) if match: pct = float(match.group(1)) sec = float(match.group(2)) calls = float(match.group(3)) func = match.group(4) if func not in funcs: funcs[func] = {'pct': 0, 'sec': 0, 'calls': 0} funcs[func]['pct'] += pct funcs[func]['sec'] += sec funcs[func]['calls'] += calls continue # Older gprofs have no call column for single-call functions # %time cumesec selfsec {stuff} name match = re.match(r'^\s*([0-9.]+)\s+[0-9.]+\s+([0-9.]+)\s+[^a-zA-Z_]*([a-zA-Z_].*)$', line) if match: pct = float(match.group(1)) sec = float(match.group(2)) calls = 1 func = match.group(3) if func not in funcs: funcs[func] = {'pct': 0, 'sec': 0, 'calls': 0} funcs[func]['pct'] += pct funcs[func]['sec'] += sec funcs[func]['calls'] += calls continue # Find modules verilated_mods = {} for func in funcs: match = re.search(r'(.*)::eval(_step)?\(', func) if match: prefix = match.group(1) if Args.debug: print("-got _eval %s prefix=%s" % (func, prefix)) verilated_mods[prefix] = re.compile(r'^' + prefix) # pprint(verilated_mods) # Sort by Verilog name vfuncs = {} groups = {} groups['type'] = collections.defaultdict(lambda: 0) groups['design'] = collections.defaultdict(lambda: 0) groups['module'] = collections.defaultdict(lambda: 0) for func, func_item in funcs.items(): pct = func_item['pct'] vfunc = func funcarg = re.sub(r'^.*\(', '', func) design = None for vde, vde_item in verilated_mods.items(): if vde_item.match(func) or vde_item.match(funcarg): design = vde break vdesign = "-" prof_match = re.search(r'__PROF__([a-zA-Z_0-9]+)__l?([0-9]+)\(', vfunc) if design and prof_match: linefunc = prof_match.group(1) lineno = int(prof_match.group(2)) vfunc = "VBlock %s:%d" % (linefunc, lineno) vdesign = design groups['type']["Verilog Blocks under " + design] += pct groups['design'][design] += pct groups['module'][linefunc] += pct elif design: vfunc = "VCommon " + func vdesign = design groups['type']["Common code under " + design] += pct groups['design'][design] += pct groups['module'][design + " common code"] += pct elif re.match(r'(VL_[A-Z0-9_]+|_?vl_[a-zA-Z0-9_]+|Verilated)', vfunc): vfunc = "VLib " + func groups['type']['VLib'] += pct groups['design']['VLib'] += pct groups['module']['VLib'] += pct elif re.match(r'^_mcount_private', vfunc): vfunc = "Prof " + func groups['type']['Prof'] += pct groups['design']['Prof'] += pct groups['module']['Prof'] += pct else: vfunc = "C++ " + func groups['type']['C++'] += pct groups['design']['C++'] += pct groups['module']['C++'] += pct if vfunc not in vfuncs: vfuncs[vfunc] = func_item vfuncs[vfunc]['design'] = vdesign else: vfuncs[vfunc]['pct'] += func_item['pct'] vfuncs[vfunc]['calls'] += func_item['calls'] vfuncs[vfunc]['sec'] += func_item['sec'] for ftype in ['type', 'design', 'module']: missing = 100 for item in groups[ftype].keys(): missing -= groups[ftype][item] groups[ftype]["\377Unaccounted for/rounding error"] = missing print("Overall summary by %s:" % ftype) print(" %-6s %s" % ("% time", ftype)) for what in sorted(groups[ftype].keys()): # \377 used to establish sort order pwhat = re.sub(r'^\377', '', what) print(" %6.2f %s" % (groups[ftype][what], pwhat)) print() design_width = 1 for func, func_item in vfuncs.items(): design_width = max(design_width, len(func_item['design'])) print("Verilog code profile:") print(" These are split into three categories:") print(" C++: Time in non-Verilated C++ code") print(" Prof: Time in profile overhead") print(" VBlock: Time attributable to a block in a" + " Verilog file and line") print(" VCommon: Time in a Verilated module," + " due to all parts of the design") print(" VLib: Time in Verilated common libraries," + " called by the Verilated code") print() print(" % cumulative self ") print((" time seconds seconds calls %-" + str(design_width) + "s type filename and line number") % "design") cume = 0 for func in sorted(vfuncs.keys(), key=lambda f: vfuncs[f]['sec'], reverse=True): cume += vfuncs[func]['sec'] print(("%6.2f %9.2f %8.2f %10d %-" + str(design_width) + "s %s") % (vfuncs[func]['pct'], cume, vfuncs[func]['sec'], vfuncs[func]['calls'], vfuncs[func]['design'], func)) ###################################################################### ###################################################################### parser = argparse.ArgumentParser( allow_abbrev=False, formatter_class=argparse.RawDescriptionHelpFormatter, description="""Read gprof report created with --prof-cfuncs. Verilator_profcfunc reads a profile report created by gprof. The names of the functions are then transformed, assuming the user used Verilator's --prof-cfuncs, and a report printed showing the percentage of time, etc, in each Verilog block. For documentation see https://verilator.org/guide/latest/exe_verilator_profcfunc.html""", epilog="""Copyright 2002-2024 by Wilson Snyder. This program is free software; you can redistribute it and/or modify it under the terms of either the GNU Lesser General Public License Version 3 or the Perl Artistic License Version 2.0. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0""") parser.add_argument('--debug', action='store_const', const=9, help='enable debug') parser.add_argument('filename', help='input gprof output to process') Args = parser.parse_args() profcfunc(Args.filename) ###################################################################### # Local Variables: # compile-command: "./verilator_profcfunc ../test_regress/t/t_profcfunc.gprof" # End: