OpenRAM/compiler/datasheet/datasheet_gen.py

673 lines
29 KiB
Python
Raw Normal View History

2018-10-12 01:03:05 +02:00
#!/usr/bin/env python3
"""
This is a script to load data from the characterization and layout processes into
a web friendly html datasheet. This script requres the python-flask and flask-table
packages to be installed.
2018-10-12 01:03:05 +02:00
"""
#TODO:
#locate all port elements in .lib
#Locate all timing elements in .lib
#Diagram generation
#Improve css
2018-12-06 19:13:28 +01:00
import debug
2018-10-12 01:03:05 +02:00
from globals import OPTS
if OPTS.datasheet_gen:
import flask_table
import os, math
import optparse
import csv
from table_gen import *
from deliverables import *
from operating_conditions import *
from timing_and_current_data import *
from characterization_corners import *
from datasheet import *
from in_out import *
else:
debug.warning("Python library flask_table not found. Skipping html datasheet generation. This can be installed with pip install flask-table.")
#make sure appropriate python libraries are installed
2018-10-12 01:03:05 +02:00
def process_name(corner):
"""
Expands the names of the characterization corner types into something human friendly
"""
2018-10-12 01:03:05 +02:00
if corner == "TT":
return "Typical - Typical"
if corner == "SS":
return "Slow - Slow"
if corner == "FF":
return "Fast - Fast"
else:
return "custom"
2018-11-09 06:30:17 +01:00
def parse_characterizer_csv(sram,f,pages):
"""
Parses output data of the Liberty file generator in order to construct the timing and
current table
"""
2018-10-12 01:03:05 +02:00
with open(f) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
for row in csv_reader:
2018-10-12 01:03:05 +02:00
found = 0
col = 0
#defines layout of csv file
NAME = row[col]
col += 1
NUM_WORDS = row[col]
col += 1
NUM_BANKS = row[col]
col += 1
NUM_RW_PORTS = row[col]
col += 1
NUM_W_PORTS = row[col]
col += 1
NUM_R_PORTS = row[col]
col += 1
TECH_NAME = row[col]
col += 1
TEMP = row[col]
col += 1
VOLT = row[col]
col += 1
PROC = row[col]
col += 1
MIN_PERIOD = row[col]
col += 1
OUT_DIR = row[col]
col += 1
LIB_NAME = row[col]
col += 1
WORD_SIZE = row[col]
col += 1
2018-12-03 19:53:50 +01:00
ORIGIN_ID = row[col]
col += 1
2019-01-02 19:30:03 +01:00
DATETIME = row[col]
col+= 1
DRC = row[col]
col += 1
LVS = row[col]
col += 1
2018-10-12 01:03:05 +02:00
for sheet in pages:
if sheet.name == NAME:
2018-10-31 06:32:19 +01:00
2018-10-12 01:03:05 +02:00
found = 1
#if the .lib information is for an existing datasheet compare timing data
for item in sheet.operating:
2018-12-03 19:53:50 +01:00
#check if the new corner data is worse than the previous worse corner data
2018-10-12 01:03:05 +02:00
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
2018-10-12 01:03:05 +02:00
while(True):
if(row[col].startswith('DIN')):
start = col
for item in sheet.timing:
if item.parameter.startswith(row[col]):
if item.parameter.endswith('setup rising'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('setup falling'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('hold rising'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('hold falling'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
col += 1
elif(row[col].startswith('DOUT')):
start = col
for item in sheet.timing:
if item.parameter.startswith(row[col]):
if item.parameter.endswith('cell rise'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('cell fall'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('rise transition'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('fall transition'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
col += 1
elif(row[col].startswith('CSb')):
start = col
for item in sheet.timing:
if item.parameter.startswith(row[col]):
if item.parameter.endswith('setup rising'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('setup falling'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('hold rising'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('hold falling'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
col += 1
elif(row[col].startswith('WEb')):
start = col
for item in sheet.timing:
if item.parameter.startswith(row[col]):
if item.parameter.endswith('setup rising'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('setup falling'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('hold rising'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('hold falling'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
col += 1
elif(row[col].startswith('ADDR')):
start = col
for item in sheet.timing:
if item.parameter.startswith(row[col]):
if item.parameter.endswith('setup rising'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('setup falling'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('hold rising'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
elif item.parameter.endswith('hold falling'):
if float(row[col+1]) < float(item.min):
item.min = row[col+1]
if float(row[col+2]) > float(item.max):
item.max = row[col+2]
col += 2
col += 1
else:
break
2018-10-12 01:03:05 +02:00
#regardless of if there is already a corner for the current sram, append the new corner to the datasheet
2018-10-12 01:03:05 +02:00
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','<a href="file://{0}">{1}</a>'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,''))))
#
new_sheet.corners.add_row([PROC,process_name(PROC),VOLT,TEMP,LIB_NAME.replace(OUT_DIR,'').replace(NAME,'')])
new_sheet.dlv.add_row(['.lib','Synthesis models','<a href="file://{0}">{1}</a>'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,''))])
#
2018-10-12 01:03:05 +02:00
if found == 0:
#if this is the first corner for this sram, run first time configuration and set up tables
2018-10-12 01:03:05 +02:00
new_sheet = datasheet(NAME)
pages.append(new_sheet)
2019-01-02 19:30:03 +01:00
new_sheet.git_id = ORIGIN_ID
new_sheet.time = DATETIME
new_sheet.DRC = DRC
new_sheet.LVS = LVS
new_sheet.description = [NAME, NUM_WORDS, NUM_BANKS, NUM_RW_PORTS, NUM_W_PORTS, NUM_R_PORTS, TECH_NAME, WORD_SIZE, ORIGIN_ID, DATETIME]
2018-12-03 19:53:50 +01:00
2018-10-12 01:03:05 +02:00
new_sheet.corners.append(characterization_corners_item(PROC,process_name(PROC),VOLT,TEMP,LIB_NAME.replace(OUT_DIR,'').replace(NAME,'')))
#
new_sheet.corners_table = table_gen("corners")
new_sheet.corners_table.add_row([PROC,process_name(PROC),VOLT,TEMP,LIB_NAME.replace(OUT_DIR,'').replace(NAME,'')])
#
2018-10-12 01:03:05 +02:00
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_table = table_gen("operating_table")
new_sheet.operating_table.add_row(['Power supply (VDD) range',VOLT,VOLT,VOLT,'Volts'])
new_sheet.operating_table.add_row(['Operating Temperature',TEMP,TEMP,TEMP,'Celsius'])
#
try:
2018-12-06 19:17:12 +01:00
new_sheet.operating.append(operating_conditions_item('Operating Frequency (F)','','',str(math.floor(1000/float(MIN_PERIOD))),'MHz'))
#
new_sheet.operating_table.add_row(['Operating Frequency (F)','','',str(math.floor(1000/float(MIN_PERIOD))),'MHz'])
#
except Exception:
new_sheet.operating.append(operating_conditions_item('Operating Frequency (F)','','',"not available in netlist only",'MHz')) #failed to provide non-zero MIN_PERIOD
#
new_sheet.operating_table.add_row(['Operating Frequency (F)','','',"not available in netlist only",'MHz']) #failed to provide non-zero MIN_PERIOD
#
#
new_sheet.timing_table = table_gen("timing")
#
while(True):
#
if(row[col].startswith('DIN')):
start = col
new_sheet.timing_table.add_row(['{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
col +=1
#
if(row[col].startswith('DIN')):
start = col
new_sheet.timing.append(timing_and_current_data_item('{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
col +=1
#
elif(row[col].startswith('DOUT')):
start = col
new_sheet.timing.append(timing_and_current_data_item('{0} cell rise'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} cell fall'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} rise transition'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} fall transition'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
col +=1
#
elif(row[col].startswith('DOUT')):
start = col
new_sheet.timing_table.add_row(['{0} cell rise'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} cell fall'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} rise transition'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} fall transition'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
col +=1
#
elif(row[col].startswith('CSb')):
start = col
new_sheet.timing.append(timing_and_current_data_item('{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
col +=1
#
elif(row[col].startswith('CSb')):
start = col
new_sheet.timing_table.add_row(['{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
col +=1
#
elif(row[col].startswith('WEb')):
start = col
new_sheet.timing.append(timing_and_current_data_item('{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
col +=1
#
elif(row[col].startswith('WEb')):
start = col
new_sheet.timing_table.add_row(['{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
col +=1
#
elif(row[col].startswith('ADDR')):
start = col
new_sheet.timing.append(timing_and_current_data_item('{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
new_sheet.timing.append(timing_and_current_data_item('{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'))
col += 2
col +=1
#
elif(row[col].startswith('ADDR')):
start = col
new_sheet.timing_table.add_row(['{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
new_sheet.timing_table.add_row(['{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'])
col += 2
col +=1
#
else:
break
#
new_sheet.dlv_table = table_gen("dlv")
new_sheet.io_table = table_gen("io")
#
if not OPTS.netlist_only:
#physical layout files should not be generated in netlist only mode
2018-12-06 19:13:28 +01:00
new_sheet.dlv.append(deliverables_item('.gds','GDSII layout views','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'gds')))
new_sheet.dlv.append(deliverables_item('.lef','LEF files','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'lef')))
new_sheet.dlv.append(deliverables_item('.sp','SPICE netlists','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'sp')))
new_sheet.dlv.append(deliverables_item('.v','Verilog simulation models','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'v')))
new_sheet.dlv.append(deliverables_item('.html','This datasheet','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'html')))
new_sheet.dlv.append(deliverables_item('.lib','Synthesis models','<a href="{1}">{1}</a>'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,''))))
new_sheet.dlv.append(deliverables_item('.py','OpenRAM configuration file','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'py')))
#
new_sheet.dlv_table.add_row(['.sp','SPICE netlists','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'sp')])
new_sheet.dlv_table.add_row(['.v','Verilog simulation models','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'v')])
new_sheet.dlv_table.add_row(['.html','This datasheet','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'html')])
new_sheet.dlv_table.add_row(['.lib','Synthesis models','<a href="{1}">{1}</a>'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,''))])
new_sheet.dlv_table.add_row(['.py','OpenRAM configuration file','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'py')])
#
#debug table for multiport information
new_sheet.io.append(in_out_item('WORD_SIZE',WORD_SIZE))
new_sheet.io.append(in_out_item('NUM_WORDS',NUM_WORDS))
new_sheet.io.append(in_out_item('NUM_BANKS',NUM_BANKS))
new_sheet.io.append(in_out_item('NUM_RW_PORTS',NUM_RW_PORTS))
new_sheet.io.append(in_out_item('NUM_R_PORTS',NUM_R_PORTS))
new_sheet.io.append(in_out_item('NUM_W_PORTS',NUM_W_PORTS))
2018-11-09 06:30:17 +01:00
new_sheet.io.append(in_out_item('Area',sram.width * sram.height))
#
new_sheet.io_table.add_row(['WORD_SIZE',WORD_SIZE])
new_sheet.io_table.add_row(['NUM_WORDS',NUM_WORDS])
new_sheet.io_table.add_row(['NUM_BANKS',NUM_BANKS])
new_sheet.io_table.add_row(['NUM_RW_PORTS',NUM_RW_PORTS])
new_sheet.io_table.add_row(['NUM_R_PORTS',NUM_R_PORTS])
new_sheet.io_table.add_row(['NUM_W_PORTS',NUM_W_PORTS])
new_sheet.io_table.add_row(['Area',sram.width * sram.height])
#
2018-10-12 01:03:05 +02:00
class datasheet_gen():
2018-11-09 06:30:17 +01:00
def datasheet_write(sram,name):
if OPTS.datasheet_gen:
in_dir = OPTS.openram_temp
if not (os.path.isdir(in_dir)):
os.mkdir(in_dir)
2018-10-12 01:03:05 +02:00
datasheets = []
2018-11-09 06:30:17 +01:00
parse_characterizer_csv(sram, in_dir + "/datasheet.info", datasheets)
2018-10-12 01:03:05 +02:00
for sheets in datasheets:
with open(name, 'w+') as f:
sheets.generate_html()
f.write(sheets.html)