#!/usr/bin/env python3

import argparse
import os
import glob
import csv
import sys
import re
from shutil import which
import datetime

def is_tool(name):
    return which(name) is not None

def check_path(path):
    paths = glob.glob(path)
    if len(paths) == 0:
        exit("file not found: %s" % path)
    if len(paths) > 1:
        print("warning: glob pattern found too many files, using first one: %s" % paths[0])
    
    return paths[0]

def openlane_date_sort(e):
    datestamp = os.path.basename(e)
    if re.match(r'^\d+\-\d+\_\d+\-\d+$',datestamp):
        timestamp = datetime.datetime.strptime(datestamp, '%d-%m_%H-%M')
        return timestamp.timestamp()
    return datestamp

def summary_report(summary_file):
    # print short summary of the csv file
    status = None
    with open(summary_file) as fh:
        summary = csv.DictReader(fh)
        for row in summary:
            for key, value in row.items():
                if "violation" in key or "error" in key:
                    print("%30s : %20s" % (key, value))
                if "AREA" in key:
                    area = float(value)
                if "flow_status" in key:
                    status = value           

    print("area %d um^2" % (1e6 * area))
    if status is not None: # newer OpenLANE has status, older ones don't
        print("flow status: %s" % status)

def full_summary_report(summary_file):
    # print short summary of the csv file
    with open(summary_file) as fh:
        summary = csv.DictReader(fh)
        for row in summary:
            for key, value in row.items():
                print("%30s : %20s" % (key, value))
                    
def drc_report(drc_file):
    last_drc = None
    drc_count = 0
    with open(drc_file) as drc:
        for line in drc.readlines():
            drc_count += 1
            if '(' in line:
                if last_drc is not None:
                    print("* %s (%d)" % (last_drc, drc_count/4))
                last_drc = line.strip()
                drc_count = 0

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="OpenLANE summary tool")
    group = parser.add_mutually_exclusive_group(required=True)

    # either choose the design and interation
    group.add_argument('--design', help="only run checks on specific design", action='store')
    # or show standard cells
    group.add_argument('--show-sky130', help='show all standard cells', action='store_const', const=True)

    # optionally choose different name for top module and which run to use (default latest)
    parser.add_argument('--top', help="name of top module if not same as design", action='store')
    parser.add_argument('--run', help="choose a specific run. If not given use latest. If not arg, show a menu", action='store', default=-1, nargs='?', type=int)

    # what to show
    parser.add_argument('--drc', help='show DRC report', action='store_const', const=True)
    parser.add_argument('--summary', help='show violations, area & status from summary report', action='store_const', const=True)
    parser.add_argument('--full-summary', help='show the full summary report csv file', action='store_const', const=True)
    parser.add_argument('--synth', help='show post techmap synth', action='store_const', const=True)
    parser.add_argument('--yosys-report', help='show cell usage after yosys synth', action='store_const', const=True)

    # klayout for intermediate files
    parser.add_argument('--floorplan', help='show floorplan', action='store_const', const=True)
    parser.add_argument('--pdn', help='show PDN', action='store_const', const=True)
    parser.add_argument('--global-placement', help='show global placement PDN', action='store_const', const=True)
    parser.add_argument('--detailed-placement', help='show detailed placement', action='store_const', const=True)
    parser.add_argument('--gds', help='show final GDS', action='store_const', const=True)

    # GDS3D for 3d view
    parser.add_argument('--gds-3d', help='show final GDS in 3D', action='store_const', const=True)
 
    parser.add_argument('--caravel', help='use caravel directory structure instead of standard openlane', action='store_const', const=True)
   
    args = parser.parse_args()

    if not args.top:
        args.top = args.design 

    if not 'OPENLANE_ROOT' in os.environ:
        exit("pls set OPENLANE_ROOT to where your OpenLANE is installed")

    klayout_def = os.path.join(os.path.dirname(sys.argv[0]), 'klayout_def.xml')
    klayout_gds = os.path.join(os.path.dirname(sys.argv[0]), 'klayout_gds.xml')
    gds3d_tech  = os.path.join(os.path.dirname(sys.argv[0]), 'sky130.txt')

    # if showing off the sky130 cells
    if args.show_sky130:
        if not os.environ['PDK_ROOT']:
            exit("pls set PDK_ROOT to where your PDK is installed")
        path = check_path(os.path.join(os.environ['PDK_ROOT'], "sky130A", "libs.ref", "sky130_fd_sc_hd", "gds", "sky130_fd_sc_hd.gds"))
        os.system("klayout -l %s %s" % (klayout_gds, path))
        exit()

    # otherwise need to know where openlane and the designs are
    openlane_designs = ''
    if args.caravel:
        if os.path.exists('openlane'):
            openlane_designs = 'openlane'
        else:
            openlane_designs = '.'
        run_dir = os.path.join(openlane_designs, args.design, 'runs/*')
    else:
        openlane_designs = os.path.join(os.environ['OPENLANE_ROOT'], 'designs')
        run_dir = os.path.join(openlane_designs, args.design, 'runs/*-*')

    list_of_files = glob.glob(run_dir)
    if len(list_of_files) == 0:
        exit("couldn't find that design")
    list_of_files.sort(key=openlane_date_sort)

    # what run to show?
    if args.run == -1:
        # default is to use the latest
        print("using latest run:")
        run_path = max(list_of_files, key=os.path.getctime)

    elif args.run is None:
        # UI for asking for which run to use
        for run_index, run in enumerate(list_of_files):
            print("\n%2d: %s" % (run_index, os.path.basename(run)), end='')
        print(" <default>\n")
        
        n = input("which run? <enter for default>: ") or run_index
        run_path = list_of_files[int(n)]

    else:
        # use the given run
        print("using run %d:" % args.run)
        run_path = list_of_files[args.run]

    print(run_path)

    if args.summary:
        path = check_path(os.path.join(run_path, 'reports', 'final_summary_report.csv'))
        summary_report(path)

    if args.full_summary:
        path = check_path(os.path.join(run_path, 'reports', 'final_summary_report.csv'))
        full_summary_report(path)

    if args.drc:
        path = os.path.join(run_path, 'logs', 'magic', 'magic.drc') # don't check path because if DRC is clean, don't get the file
        if os.path.exists(path):
            drc_report(path)
        else:
            print("no DRC file, DRC clean?")

    if args.synth:
        path = check_path(os.path.join(run_path, "tmp", "synthesis", "post_techmap.dot")) # post_techmap is created by https://github.com/efabless/openlane/pull/282
        os.system("xdot %s" % path)

    if args.yosys_report:
        filename = "*yosys_*.stat.rpt"
        path = check_path(os.path.join(run_path, "reports", "synthesis", filename))
        os.system("cat %s" % path)

    if args.floorplan:
        path = check_path(os.path.join(run_path, "results", "floorplan", args.top + ".floorplan.def"))
        os.system("klayout -l %s %s" % (klayout_def, path))

    if args.pdn:
        filename = "*pdn.def"
        path = check_path(os.path.join(run_path, "tmp", "floorplan", filename))
        os.system("klayout -l %s %s" % (klayout_def, path))

    if args.global_placement:
        filename = "*replace.def"
        path = check_path(os.path.join(run_path, "tmp", "placement", filename))
        os.system("klayout -l %s %s" % (klayout_def, path))

    if args.detailed_placement:
        path = check_path(os.path.join(run_path, "results", "placement", args.top + ".placement.def"))
        os.system("klayout -l %s %s" % (klayout_def, path))

    if args.gds:
        path = check_path(os.path.join(run_path, "results", "magic", args.top + ".gds"))
        os.system("klayout -l %s %s" % (klayout_gds, path))

    if args.gds_3d:
        if not is_tool('GDS3D'):
            exit("pls install GDS3D from https://github.com/trilomix/GDS3D")
        path = check_path(os.path.join(run_path, "results", "magic", args.top + ".gds"))
        os.system("GDS3D -p %s -i %s" % (gds3d_tech, path))
        

[((16, 12, 16, 27), 'glob.glob', 'glob.glob', ({(16, 22, 16, 26): 'path'}, {}), '(path)', False, 'import glob\n'), ((25, 16, 25, 35), 'os.path.basename', 'os.path.basename', ({(25, 33, 25, 34): 'e'}, {}), '(e)', False, 'import os\n'), ((26, 7, 26, 50), 're.match', 're.match', ({(26, 16, 26, 39): '"""^\\\\d+\\\\-\\\\d+\\\\_\\\\d+\\\\-\\\\d+$"""', (26, 40, 26, 49): 'datestamp'}, {}), "('^\\\\d+\\\\-\\\\d+\\\\_\\\\d+\\\\-\\\\d+$', datestamp)", False, 'import re\n'), ((70, 13, 70, 73), 'argparse.ArgumentParser', 'argparse.ArgumentParser', (), '', False, 'import argparse\n'), ((133, 20, 133, 38), 'glob.glob', 'glob.glob', ({(133, 30, 133, 37): 'run_dir'}, {}), '(run_dir)', False, 'import glob\n'), ((13, 11, 13, 22), 'shutil.which', 'which', ({(13, 17, 13, 21): 'name'}, {}), '(name)', False, 'from shutil import which\n'), ((27, 20, 27, 72), 'datetime.datetime.strptime', 'datetime.datetime.strptime', ({(27, 47, 27, 56): 'datestamp', (27, 58, 27, 71): '"""%d-%m_%H-%M"""'}, {}), "(datestamp, '%d-%m_%H-%M')", False, 'import datetime\n'), ((35, 18, 35, 36), 'csv.DictReader', 'csv.DictReader', ({(35, 33, 35, 35): 'fh'}, {}), '(fh)', False, 'import csv\n'), ((52, 18, 52, 36), 'csv.DictReader', 'csv.DictReader', ({(52, 33, 52, 35): 'fh'}, {}), '(fh)', False, 'import csv\n'), ((109, 31, 109, 59), 'os.path.dirname', 'os.path.dirname', ({(109, 47, 109, 58): 'sys.argv[0]'}, {}), '(sys.argv[0])', False, 'import os\n'), ((110, 31, 110, 59), 'os.path.dirname', 'os.path.dirname', ({(110, 47, 110, 58): 'sys.argv[0]'}, {}), '(sys.argv[0])', False, 'import os\n'), ((111, 31, 111, 59), 'os.path.dirname', 'os.path.dirname', ({(111, 47, 111, 58): 'sys.argv[0]'}, {}), '(sys.argv[0])', False, 'import os\n'), ((118, 8, 118, 59), 'os.system', 'os.system', ({(118, 18, 118, 58): "('klayout -l %s %s' % (klayout_gds, path))"}, {}), "('klayout -l %s %s' % (klayout_gds, path))", False, 'import os\n'), ((124, 11, 124, 37), 'os.path.exists', 'os.path.exists', ({(124, 26, 124, 36): '"""openlane"""'}, {}), "('openlane')", False, 'import os\n'), ((128, 18, 128, 71), 'os.path.join', 'os.path.join', ({(128, 31, 128, 47): 'openlane_designs', (128, 49, 128, 60): 'args.design', (128, 62, 128, 70): '"""runs/*"""'}, {}), "(openlane_designs, args.design, 'runs/*')", False, 'import os\n'), ((130, 27, 130, 79), 'os.path.join', 'os.path.join', ({(130, 40, 130, 67): "os.environ['OPENLANE_ROOT']", (130, 69, 130, 78): '"""designs"""'}, {}), "(os.environ['OPENLANE_ROOT'], 'designs')", False, 'import os\n'), ((131, 18, 131, 73), 'os.path.join', 'os.path.join', ({(131, 31, 131, 47): 'openlane_designs', (131, 49, 131, 60): 'args.design', (131, 62, 131, 72): '"""runs/*-*"""'}, {}), "(openlane_designs, args.design, 'runs/*-*')", False, 'import os\n'), ((169, 15, 169, 67), 'os.path.join', 'os.path.join', ({(169, 28, 169, 36): 'run_path', (169, 38, 169, 44): '"""logs"""', (169, 46, 169, 53): '"""magic"""', (169, 55, 169, 66): '"""magic.drc"""'}, {}), "(run_path, 'logs', 'magic', 'magic.drc')", False, 'import os\n'), ((170, 11, 170, 31), 'os.path.exists', 'os.path.exists', ({(170, 26, 170, 30): 'path'}, {}), '(path)', False, 'import os\n'), ((177, 8, 177, 35), 'os.system', 'os.system', ({(177, 18, 177, 34): "('xdot %s' % path)"}, {}), "('xdot %s' % path)", False, 'import os\n'), ((182, 8, 182, 34), 'os.system', 'os.system', ({(182, 18, 182, 33): "('cat %s' % path)"}, {}), "('cat %s' % path)", False, 'import os\n'), ((186, 8, 186, 59), 'os.system', 'os.system', ({(186, 18, 186, 58): "('klayout -l %s %s' % (klayout_def, path))"}, {}), "('klayout -l %s %s' % (klayout_def, path))", False, 'import os\n'), ((191, 8, 191, 59), 'os.system', 'os.system', ({(191, 18, 191, 58): "('klayout -l %s %s' % (klayout_def, path))"}, {}), "('klayout -l %s %s' % (klayout_def, path))", False, 'import os\n'), ((196, 8, 196, 59), 'os.system', 'os.system', ({(196, 18, 196, 58): "('klayout -l %s %s' % (klayout_def, path))"}, {}), "('klayout -l %s %s' % (klayout_def, path))", False, 'import os\n'), ((200, 8, 200, 59), 'os.system', 'os.system', ({(200, 18, 200, 58): "('klayout -l %s %s' % (klayout_def, path))"}, {}), "('klayout -l %s %s' % (klayout_def, path))", False, 'import os\n'), ((204, 8, 204, 59), 'os.system', 'os.system', ({(204, 18, 204, 58): "('klayout -l %s %s' % (klayout_gds, path))"}, {}), "('klayout -l %s %s' % (klayout_gds, path))", False, 'import os\n'), ((210, 8, 210, 59), 'os.system', 'os.system', ({(210, 18, 210, 58): "('GDS3D -p %s -i %s' % (gds3d_tech, path))"}, {}), "('GDS3D -p %s -i %s' % (gds3d_tech, path))", False, 'import os\n'), ((117, 26, 117, 134), 'os.path.join', 'os.path.join', ({(117, 39, 117, 61): "os.environ['PDK_ROOT']", (117, 63, 117, 72): '"""sky130A"""', (117, 74, 117, 84): '"""libs.ref"""', (117, 86, 117, 103): '"""sky130_fd_sc_hd"""', (117, 105, 117, 110): '"""gds"""', (117, 112, 117, 133): '"""sky130_fd_sc_hd.gds"""'}, {}), "(os.environ['PDK_ROOT'], 'sky130A', 'libs.ref',\n    'sky130_fd_sc_hd', 'gds', 'sky130_fd_sc_hd.gds')", False, 'import os\n'), ((161, 26, 161, 87), 'os.path.join', 'os.path.join', ({(161, 39, 161, 47): 'run_path', (161, 49, 161, 58): '"""reports"""', (161, 60, 161, 86): '"""final_summary_report.csv"""'}, {}), "(run_path, 'reports', 'final_summary_report.csv')", False, 'import os\n'), ((165, 26, 165, 87), 'os.path.join', 'os.path.join', ({(165, 39, 165, 47): 'run_path', (165, 49, 165, 58): '"""reports"""', (165, 60, 165, 86): '"""final_summary_report.csv"""'}, {}), "(run_path, 'reports', 'final_summary_report.csv')", False, 'import os\n'), ((176, 26, 176, 88), 'os.path.join', 'os.path.join', ({(176, 39, 176, 47): 'run_path', (176, 49, 176, 54): '"""tmp"""', (176, 56, 176, 67): '"""synthesis"""', (176, 69, 176, 87): '"""post_techmap.dot"""'}, {}), "(run_path, 'tmp', 'synthesis', 'post_techmap.dot')", False, 'import os\n'), ((181, 26, 181, 82), 'os.path.join', 'os.path.join', ({(181, 39, 181, 47): 'run_path', (181, 49, 181, 58): '"""reports"""', (181, 60, 181, 71): '"""synthesis"""', (181, 73, 181, 81): 'filename'}, {}), "(run_path, 'reports', 'synthesis', filename)", False, 'import os\n'), ((185, 26, 185, 101), 'os.path.join', 'os.path.join', ({(185, 39, 185, 47): 'run_path', (185, 49, 185, 58): '"""results"""', (185, 60, 185, 71): '"""floorplan"""', (185, 73, 185, 100): "args.top + '.floorplan.def'"}, {}), "(run_path, 'results', 'floorplan', args.top + '.floorplan.def')", False, 'import os\n'), ((190, 26, 190, 78), 'os.path.join', 'os.path.join', ({(190, 39, 190, 47): 'run_path', (190, 49, 190, 54): '"""tmp"""', (190, 56, 190, 67): '"""floorplan"""', (190, 69, 190, 77): 'filename'}, {}), "(run_path, 'tmp', 'floorplan', filename)", False, 'import os\n'), ((195, 26, 195, 78), 'os.path.join', 'os.path.join', ({(195, 39, 195, 47): 'run_path', (195, 49, 195, 54): '"""tmp"""', (195, 56, 195, 67): '"""placement"""', (195, 69, 195, 77): 'filename'}, {}), "(run_path, 'tmp', 'placement', filename)", False, 'import os\n'), ((199, 26, 199, 101), 'os.path.join', 'os.path.join', ({(199, 39, 199, 47): 'run_path', (199, 49, 199, 58): '"""results"""', (199, 60, 199, 71): '"""placement"""', (199, 73, 199, 100): "args.top + '.placement.def'"}, {}), "(run_path, 'results', 'placement', args.top + '.placement.def')", False, 'import os\n'), ((203, 26, 203, 87), 'os.path.join', 'os.path.join', ({(203, 39, 203, 47): 'run_path', (203, 49, 203, 58): '"""results"""', (203, 60, 203, 67): '"""magic"""', (203, 69, 203, 86): "args.top + '.gds'"}, {}), "(run_path, 'results', 'magic', args.top + '.gds')", False, 'import os\n'), ((209, 26, 209, 87), 'os.path.join', 'os.path.join', ({(209, 39, 209, 47): 'run_path', (209, 49, 209, 58): '"""results"""', (209, 60, 209, 67): '"""magic"""', (209, 69, 209, 86): "args.top + '.gds'"}, {}), "(run_path, 'results', 'magic', args.top + '.gds')", False, 'import os\n'), ((147, 44, 147, 65), 'os.path.basename', 'os.path.basename', ({(147, 61, 147, 64): 'run'}, {}), '(run)', False, 'import os\n')]