diff --git a/README.md b/README.md index e7e05e5f..07624326 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ The OpenRAM compiler has very few dependencies: * ngspice-26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later) * Python 3.5 and higher * Python numpy +* flask_table * a setup script for each technology * a technology directory for each technology with the base cells diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 0e115df1..46cbc4d2 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -25,6 +25,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. @@ -96,7 +98,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. """ @@ -516,3 +518,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 (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/datasheet/characterization_corners.py b/compiler/datasheet/characterization_corners.py new file mode 100644 index 00000000..54f75c3f --- /dev/null +++ b/compiler/datasheet/characterization_corners.py @@ -0,0 +1,17 @@ +from flask_table import * + +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 + diff --git a/compiler/datasheet/datasheet.py b/compiler/datasheet/datasheet.py new file mode 100644 index 00000000..396215a8 --- /dev/null +++ b/compiler/datasheet/datasheet.py @@ -0,0 +1,55 @@ +from flask_table import * +from operating_conditions import * +from characterization_corners import * +from deliverables import * +from timing_and_current_data import * + +class datasheet(): + + def __init__(self,identifier): + self.corners = [] + self.timing = [] + self.operating = [] + self.dlv = [] + self.name = identifier + self.html = "" + + def generate_html(self): + self.html = """""" + self.html +='

{0}

' + self.html +='

{0}

' + self.html +='

{0}

' + self.html +='

Operating Conditions

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

Timing and Current Data

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

Characterization Corners

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

Deliverables

' + self.html += deliverables(self.dlv,table_id='data').__html__().replace('<','<').replace('"','"').replace('>',">") + + diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py new file mode 100644 index 00000000..6bfb165f --- /dev/null +++ b/compiler/datasheet/datasheet_gen.py @@ -0,0 +1,133 @@ +#!/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 +from globals import OPTS +from deliverables import * +from operating_conditions import * +from timing_and_current_data import * +from characterization_corners import * +from datasheet import * + +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)': + try: + if float(math.floor(1000/float(MIN_PERIOD)) < float(item.max)): + item.max = str(math.floor(1000/float(MIN_PERIOD))) + except Exception: + pass + + + + 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')) + try: + new_sheet.operating.append(operating_conditions_item('Operating Frequency (F)','','',str(math.floor(1000/float(MIN_PERIOD))),'MHz')) + except Exception: + new_sheet.operating.append(operating_conditions_item('Operating Frequency (F)','','',"unknown",'MHz')) #analytical model fails to provide MIN_PERIOD + + + + + 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_gen(): + def datasheet_write(name): + + in_dir = OPTS.openram_temp + + 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: + with open(name, 'w+') as f: + sheets.generate_html() + f.write(sheets.html) diff --git a/compiler/datasheet/deliverables.py b/compiler/datasheet/deliverables.py new file mode 100644 index 00000000..d5287c3a --- /dev/null +++ b/compiler/datasheet/deliverables.py @@ -0,0 +1,13 @@ +from flask_table import * + +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 diff --git a/compiler/datasheet/operating_conditions.py b/compiler/datasheet/operating_conditions.py new file mode 100644 index 00000000..e08adc61 --- /dev/null +++ b/compiler/datasheet/operating_conditions.py @@ -0,0 +1,17 @@ +from flask_table import * + +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 + diff --git a/compiler/datasheet/timing_and_current_data.py b/compiler/datasheet/timing_and_current_data.py new file mode 100644 index 00000000..ebf489e8 --- /dev/null +++ b/compiler/datasheet/timing_and_current_data.py @@ -0,0 +1,16 @@ +from flask_table import * + +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 + + diff --git a/compiler/example_config_freepdk45.py b/compiler/example_config_freepdk45.py index 973d811c..e9e753b9 100644 --- a/compiler/example_config_freepdk45.py +++ b/compiler/example_config_freepdk45.py @@ -8,7 +8,7 @@ supply_voltages = [1.0] temperatures = [25] output_path = "temp" -output_name = "sram_{0}_{1}_{2}_{3}".format(word_size,num_words,num_banks,tech_name) +output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) #Setting for multiport # netlist_only = True diff --git a/compiler/example_config_scn4m_subm.py b/compiler/example_config_scn4m_subm.py index 92332fd5..9aa8e498 100644 --- a/compiler/example_config_scn4m_subm.py +++ b/compiler/example_config_scn4m_subm.py @@ -8,12 +8,4 @@ supply_voltages = [ 5.0 ] temperatures = [ 25 ] output_path = "temp" -output_name = "sram_{0}_{1}_{2}_{3}".format(word_size,num_words,num_banks,tech_name) - -#Setting for multiport -# netlist_only = True -# bitcell = "pbitcell" -# replica_bitcell="replica_pbitcell" -# num_rw_ports = 1 -# num_r_ports = 1 -# num_w_ports = 0 +output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) diff --git a/compiler/globals.py b/compiler/globals.py index af89eaa4..f19559e2 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -287,7 +287,7 @@ def setup_paths(): # Add all of the subdirs to the python path # These subdirs are modules and don't need to be added: characterizer, verify - for subdir in ["gdsMill", "tests", "modules", "base", "pgates"]: + for subdir in ["gdsMill", "tests", "modules", "base", "pgates", "datasheet"]: full_path = "{0}/{1}".format(OPENRAM_HOME,subdir) debug.check(os.path.isdir(full_path), "$OPENRAM_HOME/{0} does not exist: {1}".format(subdir,full_path)) diff --git a/compiler/openram.py b/compiler/openram.py index e4ab593e..6fc6ec71 100755 --- a/compiler/openram.py +++ b/compiler/openram.py @@ -27,7 +27,6 @@ if len(args) != 1: # These depend on arguments, so don't load them until now. import debug - init_openram(config_file=args[0], is_unit_test=False) # Only print banner here so it's not in unit tests @@ -40,8 +39,8 @@ report_status() import verify from sram import sram from sram_config import sram_config - -output_extensions = ["sp","v","lib"] +#from parser import * +output_extensions = ["sp","v","lib","html"] if not OPTS.netlist_only: output_extensions.extend(["gds","lef"]) output_files = ["{0}.{1}".format(OPTS.output_name,x) for x in output_extensions] diff --git a/compiler/sram.py b/compiler/sram.py index 0feea1b3..59b7d7e8 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -57,7 +57,7 @@ class sram(): def verilog_write(self,name): self.s.verilog_write(name) - + def save(self): """ Save all the output files while reporting time to do it as well. """ @@ -107,6 +107,14 @@ class sram(): print("LEF: Writing to {0}".format(lefname)) self.s.lef_write(lefname) print_time("LEF", datetime.datetime.now(), start_time) + + # Write the datasheet + start_time = datetime.datetime.now() + from datasheet_gen import datasheet_gen + dname = OPTS.output_path + self.s.name + ".html" + print("Datasheet: writing to {0}".format(dname)) + datasheet_gen.datasheet_write(dname) + print_time("Datasheet", datetime.datetime.now(), start_time) # Write a verilog model start_time = datetime.datetime.now() diff --git a/compiler/tests/30_openram_test.py b/compiler/tests/30_openram_test.py index 81864840..038a2e15 100755 --- a/compiler/tests/30_openram_test.py +++ b/compiler/tests/30_openram_test.py @@ -62,7 +62,12 @@ 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 + if os.path.exists(out_path): + datasheets = glob.glob('{0}/*html'.format(out_path)) + 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()