From fa979e2d344181d791ad0241383ec0e61dd81ab5 Mon Sep 17 00:00:00 2001 From: Jesse Cirimelli-Low Date: Sat, 6 Oct 2018 21:15:54 -0700 Subject: [PATCH] initial stages of html documentation generation --- compiler/characterizer/lib.py | 37 ++++- compiler/openram.py | 7 +- compiler/parser.py | 236 ++++++++++++++++++++++++++++++ compiler/tests/30_openram_test.py | 6 +- 4 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 compiler/parser.py diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 9f04fd7f..54558b01 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -29,6 +29,8 @@ class lib: self.characterize_corners() + + def gen_port_names(self): """Generates the port names to be written to the lib file""" #This is basically a copy and paste of whats in delay.py as well. Something more efficient should be done here. @@ -100,7 +102,7 @@ class lib: debug.info(1,"Writing to {0}".format(lib_name)) self.characterize() self.lib.close() - + self.parse_info() def characterize(self): """ Characterize the current corner. """ @@ -519,3 +521,36 @@ class lib: else: self.times = self.sh.analyze(self.slews,self.slews) + + def parse_info(self): + if OPTS.is_unit_test: + return + datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+') + + for (self.corner,lib_name) in zip(self.corners,self.lib_files): + + ports = "" + if OPTS.num_rw_ports>0: + ports += "{}_".format(OPTS.num_rw_ports) + if OPTS.num_w_ports>0: + ports += "{}_".format(OPTS.num_w_ports) + if OPTS.num_r_ports>0: + ports += "{}_".format(OPTS.num_r_ports) + + datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12}".format("sram_{0}_{1}_{2}{3}".format(OPTS.word_size, OPTS.num_words, ports, OPTS.tech_name), + OPTS.num_words, + OPTS.num_banks, + OPTS.num_rw_ports, + OPTS.num_w_ports, + OPTS.num_r_ports, + OPTS.tech_name, + self.corner[1], + self.corner[2], + self.corner[0], + round_time(self.char_results["min_period"]), + self.out_dir, + lib_name)) + + + datasheet.close() + diff --git a/compiler/openram.py b/compiler/openram.py index e4ab593e..b751b6df 100755 --- a/compiler/openram.py +++ b/compiler/openram.py @@ -40,7 +40,7 @@ report_status() import verify from sram import sram from sram_config import sram_config - +import parser output_extensions = ["sp","v","lib"] if not OPTS.netlist_only: output_extensions.extend(["gds","lef"]) @@ -63,8 +63,11 @@ s = sram(sram_config=c, # Output the files for the resulting SRAM s.save() +# generate datasheet from characterization of created SRAM +p = parser.parse(OPTS.openram_temp,os.environ.get('OPENRAM_HOME')+"/datasheets") + # Delete temp files etc. -end_openram() +#end_openram() print_time("End",datetime.datetime.now(), start_time) diff --git a/compiler/parser.py b/compiler/parser.py new file mode 100644 index 00000000..a9792b67 --- /dev/null +++ b/compiler/parser.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +""" +Datasheet Generator + +TODO: +locate all port elements in .lib +Locate all timing elements in .lib +Diagram generation +Improve css +""" + +import os, math +import optparse +from flask_table import * +import csv +import contextlib +from globals import OPTS + +class deliverables(Table): + typ = Col('Type') + description = Col('Description') + link = Col('Link') + + + +class deliverables_item(object): + def __init__(self, typ, description,link): + self.typ = typ + self.description = description + self.link = link + +class operating_conditions(Table): + parameter = Col('Parameter') + min = Col('Min') + typ = Col('Typ') + max = Col('Max') + units = Col('Units') + +class operating_conditions_item(object): + def __init__(self, parameter, min, typ, max, units): + self.parameter = parameter + self.min = min + self.typ = typ + self.max = max + self.units = units + +class timing_and_current_data(Table): + parameter = Col('Parameter') + min = Col('Min') + max = Col('Max') + units = Col('Units') + +class timing_and_current_data_item(object): + def __init__(self, parameter, min, max, units): + self.parameter = parameter + self.min = min + self.max = max + self.units = units + +class characterization_corners(Table): + corner_name = Col('Corner Name') + process = Col('Process') + power_supply = Col('Power Supply') + temperature = Col('Temperature') + library_name_suffix = Col('Library Name Suffix') + +class characterization_corners_item(object): + def __init__(self, corner_name, process, power_supply, temperature, library_name_suffix): + self.corner_name = corner_name + self.process = process + self.power_supply = power_supply + self.temperature = temperature + self.library_name_suffix = library_name_suffix + +def process_name(corner): + if corner == "TT": + return "Typical - Typical" + if corner == "SS": + return "Slow - Slow" + if corner == "FF": + return "Fast - Fast" + else: + return "custom" + +def parse_file(f,pages): + with open(f) as csv_file: + csv_reader = csv.reader(csv_file, delimiter=',') + line_count = 0 + for row in csv_reader: + found = 0 + NAME = row[0] + NUM_WORDS = row[1] + NUM_BANKS = row[2] + NUM_RW_PORTS = row[3] + NUM_W_PORTS = row[4] + NUM_R_PORTS = row[5] + TECH_NAME = row[6] + TEMP = row[7] + VOLT = row[8] + PROC = row[9] + MIN_PERIOD = row[10] + OUT_DIR = row[11] + LIB_NAME = row[12] + for sheet in pages: + + + if sheet.name == row[0]: + found = 1 + #if the .lib information is for an existing datasheet compare timing data + + for item in sheet.operating: + + if item.parameter == 'Operating Temperature': + if float(TEMP) > float(item.max): + item.typ = item.max + item.max = TEMP + if float(TEMP) < float(item.min): + item.typ = item.min + item.min = TEMP + + if item.parameter == 'Power supply (VDD) range': + if float(VOLT) > float(item.max): + item.typ = item.max + item.max = VOLT + if float(VOLT) < float(item.min): + item.typ = item.min + item.min = VOLT + + if item.parameter == 'Operating Frequncy (F)': + if float(math.floor(1000/float(MIN_PERIOD)) < float(item.max)): + item.max = str(math.floor(1000/float(MIN_PERIOD))) + + + + new_sheet.corners.append(characterization_corners_item(PROC,process_name(PROC),VOLT,TEMP,LIB_NAME.replace(OUT_DIR,'').replace(NAME,''))) + new_sheet.dlv.append(deliverables_item('.lib','Synthesis models','{1}'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,'')))) + + if found == 0: + new_sheet = datasheet(NAME) + pages.append(new_sheet) + + new_sheet.corners.append(characterization_corners_item(PROC,process_name(PROC),VOLT,TEMP,LIB_NAME.replace(OUT_DIR,'').replace(NAME,''))) + + new_sheet.operating.append(operating_conditions_item('Power supply (VDD) range',VOLT,VOLT,VOLT,'Volts')) + new_sheet.operating.append(operating_conditions_item('Operating Temperature',TEMP,TEMP,TEMP,'Celsius')) + new_sheet.operating.append(operating_conditions_item('Operating Frequency (F)','','',str(math.floor(1000/float(MIN_PERIOD))),'MHz')) + + new_sheet.timing.append(timing_and_current_data_item('1','2','3','4')) + + new_sheet.dlv.append(deliverables_item('.sp','SPICE netlists','{1}.{2}'.format(OUT_DIR,NAME,'sp'))) + new_sheet.dlv.append(deliverables_item('.v','Verilog simulation models','{1}.{2}'.format(OUT_DIR,NAME,'v'))) + new_sheet.dlv.append(deliverables_item('.gds','GDSII layout views','{1}.{2}'.format(OUT_DIR,NAME,'gds'))) + new_sheet.dlv.append(deliverables_item('.lef','LEF files','{1}.{2}'.format(OUT_DIR,NAME,'lef'))) + new_sheet.dlv.append(deliverables_item('.lib','Synthesis models','{1}'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,'')))) + + + +class datasheet(): + + def __init__(self,identifier): + self.corners = [] + self.timing = [] + self.operating = [] + self.dlv = [] + self.name = identifier + + def print(self): + print("""""") + print('

{0}

') + print('

{0}

') + print('

{0}

') + print('

Operating Conditions

') + print(operating_conditions(self.operating,table_id='data').__html__()) + print('

Timing and Current Data

') + print(timing_and_current_data(self.timing,table_id='data').__html__()) + print('

Characterization Corners

') + print(characterization_corners(self.corners,table_id='data').__html__()) + print('

Deliverables

') + print(deliverables(self.dlv,table_id='data').__html__().replace('<','<').replace('"','"').replace('>',">")) + + +class parse(): + def __init__(self,in_dir,out_dir): + + if not (os.path.isdir(in_dir)): + os.mkdir(in_dir) + + if not (os.path.isdir(out_dir)): + os.mkdir(out_dir) + + datasheets = [] + parse_file(in_dir + "/datasheet.info", datasheets) + + + for sheets in datasheets: + print (out_dir + sheets.name + ".html") + with open(out_dir + "/" + sheets.name + ".html", 'w+') as f: + with contextlib.redirect_stdout(f): + sheets.print() + + + + + + + + + + + + + diff --git a/compiler/tests/30_openram_test.py b/compiler/tests/30_openram_test.py index 81864840..983d746c 100755 --- a/compiler/tests/30_openram_test.py +++ b/compiler/tests/30_openram_test.py @@ -62,7 +62,11 @@ class openram_test(openram_test): import glob files = glob.glob('{0}/*.lib'.format(out_path)) self.assertTrue(len(files)>0) - + + # Make sure there is any .html file + datasheets = glob.glob('{0}/{1}/*html'.format(OPENRAM_HOME,datasheets)) + self.assertTrue(len(datasheets)>0) + # grep any errors from the output output_log = open("{0}/output.log".format(out_path),"r") output = output_log.read()