diff --git a/README.md b/README.md index 07624326..0996bbb4 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ https://github.com/mguthaus/OpenRAM/blob/master/OpenRAM_ICCAD_2016_presentation. 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 +* Python numpy (pip3 install numpy) +* flask_table (pip3 install flask) * a setup script for each technology * a technology directory for each technology with the base cells @@ -49,7 +49,7 @@ We do not distribute the PDK, but you may get it from: If you are using SCMOS, you should install Magic and netgen from: http://opencircuitdesign.com/magic/ http://opencircuitdesign.com/netgen/ -We have included the SCN3ME design rules from QFlow: +We have included the SCN4M design rules from QFlow: http://opencircuitdesign.com/qflow/ # DIRECTORY STRUCTURE @@ -65,7 +65,7 @@ We have included the SCN3ME design rules from QFlow: * compiler/tests - unit tests * technology - openram technology directory (pointed to by OPENRAM_TECH) * technology/freepdk45 - example configuration library for freepdk45 technology node - * technology/scn3me_subm - example configuration library SCMOS technology node + * technology/scn4m_subm - example configuration library SCMOS technology node * technology/setup_scripts - setup scripts to customize your PDKs and OpenRAM technologies * docs - LaTeX manual (likely outdated) * lib - IP library of pregenerated memories @@ -90,8 +90,8 @@ To increase the verbosity of the test, add one (or more) -v options: python tests/00_code_format_check_test.py -v -t freepdk45 ``` To specify a particular technology use "-t " such as -"-t scn3me_subm". The default for a unit test is freepdk45 whereas -the default for openram.py is specified in the configuration file. +"-t freepdk45" or "-t scn4m_subm". The default for a unit test is scn4m_subm. +The default for openram.py is specified in the configuration file. # CREATING CUSTOM TECHNOLOGIES diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index b02ceee1..5816e350 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -222,9 +222,9 @@ class pbitcell(design.design): self.rowline_spacing = self.m1_space + contact.m1m2.width # spacing for vdd - vdd_offset_well_constraint = self.well_enclose_active + 0.5*contact.well.width - vdd_offset_metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space + 0.5*contact.well.width - self.vdd_offset = max(vdd_offset_well_constraint, vdd_offset_metal1_constraint) + implant_constraint = max(inverter_pmos_contact_extension, 0) + 2*self.implant_enclose_active + 0.5*(contact.well.width - self.m1_width) + metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space + self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width # read port dimensions width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx() @@ -334,7 +334,7 @@ class pbitcell(design.design): layer="metal1", offset=self.gnd_position, width=self.width, - height=contact.well.second_layer_width) + height=self.m1_width) vdd_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset self.vdd_position = vector(0, vdd_ypos) @@ -342,7 +342,7 @@ class pbitcell(design.design): layer="metal1", offset=self.vdd_position, width=self.width, - height=contact.well.second_layer_width) + height=self.m1_width) def create_readwrite_ports(self): """ @@ -933,8 +933,9 @@ class pbitcell(design.design): def route_rbc_short(self): """ route the short from Q_bar to gnd necessary for the replica bitcell """ - Q_bar_pos = self.inverter_pmos_right.get_pin("S").uc() - vdd_pos = vector(Q_bar_pos.x, self.vdd_position.y) + Q_bar_pos = self.inverter_pmos_right.get_pin("S").center() + vdd_pos = self.inverter_pmos_right.get_pin("D").center() + #vdd_pos = vector(Q_bar_pos.x, self.vdd_position.y) self.add_path("metal1", [Q_bar_pos, vdd_pos]) \ No newline at end of file diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 03d9961e..436ee301 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -526,15 +526,15 @@ class lib: 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) +# 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), + datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13}".format("sram_{0}_{1}_{2}".format(OPTS.word_size, OPTS.num_words, OPTS.tech_name), OPTS.num_words, OPTS.num_banks, OPTS.num_rw_ports, @@ -546,7 +546,8 @@ class lib: self.corner[0], round_time(self.char_sram_results["min_period"]), self.out_dir, - lib_name)) + lib_name, + OPTS.word_size)) datasheet.close() diff --git a/compiler/datasheet/assets/vlsi_logo.png b/compiler/datasheet/assets/vlsi_logo.png new file mode 100644 index 00000000..3f02a45e Binary files /dev/null and b/compiler/datasheet/assets/vlsi_logo.png differ diff --git a/compiler/datasheet/datasheet.py b/compiler/datasheet/datasheet.py index 396215a8..543c75fa 100644 --- a/compiler/datasheet/datasheet.py +++ b/compiler/datasheet/datasheet.py @@ -3,16 +3,21 @@ from operating_conditions import * from characterization_corners import * from deliverables import * from timing_and_current_data import * +from in_out import * +import os +from globals import OPTS class datasheet(): def __init__(self,identifier): + self.io = [] 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 +='

'+ self.name + '.html' + '

' +# self.html +='

{0}

' +# self.html +='

{0}

' + + self.html +='

Ports and Configuration (DEBUG)

' + self.html += in_out(self.io,table_id='data').__html__().replace('<','<').replace('"','"').replace('>',">") + 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('>',">") - + self.html +='

*Feature only supported with characterizer

' + + self.html +='VLSIDA' diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py index 6bfb165f..e68e94df 100644 --- a/compiler/datasheet/datasheet_gen.py +++ b/compiler/datasheet/datasheet_gen.py @@ -8,17 +8,24 @@ Locate all timing elements in .lib Diagram generation Improve css """ - -import os, math -import optparse -from flask_table import * -import csv +import debug from globals import OPTS -from deliverables import * -from operating_conditions import * -from timing_and_current_data import * -from characterization_corners import * -from datasheet import * + +if OPTS.datasheet_gen: + import flask_table + import os, math + import optparse + import csv + 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.") + + def process_name(corner): if corner == "TT": @@ -43,12 +50,13 @@ def parse_file(f,pages): NUM_W_PORTS = row[4] NUM_R_PORTS = row[5] TECH_NAME = row[6] - TEMP = row[7] - VOLT = row[8] + TEMP = row[8] + VOLT = row[7] PROC = row[9] MIN_PERIOD = row[10] OUT_DIR = row[11] LIB_NAME = row[12] + WORD_SIZE = row[13] for sheet in pages: @@ -95,39 +103,55 @@ def parse_file(f,pages): 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')) + 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.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('Cycle time','2','3','4')) + new_sheet.timing.append(timing_and_current_data_item('Access time','2','3','4')) + new_sheet.timing.append(timing_and_current_data_item('Positive clk setup','2','3','4')) + new_sheet.timing.append(timing_and_current_data_item('Positive clk hold','2','3','4')) + new_sheet.timing.append(timing_and_current_data_item('RW setup','2','3','4')) + new_sheet.timing.append(timing_and_current_data_item('RW hold','2','3','4')) + new_sheet.timing.append(timing_and_current_data_item('AC current','2','3','4')) + new_sheet.timing.append(timing_and_current_data_item('Standby current','2','3','4')) + new_sheet.timing.append(timing_and_current_data_item('Area','2','3','4')) - 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,'')))) + + 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)) + + class datasheet_gen(): def datasheet_write(name): - - in_dir = OPTS.openram_temp - if not (os.path.isdir(in_dir)): - os.mkdir(in_dir) + if OPTS.datasheet_gen: + 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) + #if not (os.path.isdir(out_dir)): + # os.mkdir(out_dir) - datasheets = [] - parse_file(in_dir + "/datasheet.info", datasheets) + 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) + for sheets in datasheets: + with open(name, 'w+') as f: + sheets.generate_html() + f.write(sheets.html) diff --git a/compiler/datasheet/in_out.py b/compiler/datasheet/in_out.py new file mode 100644 index 00000000..f656dba6 --- /dev/null +++ b/compiler/datasheet/in_out.py @@ -0,0 +1,11 @@ +from flask_table import * + +class in_out(Table): + typ = Col('Type') + description = Col('Description') + + +class in_out_item(object): + def __init__(self, typ, description): + self.typ = typ + self.description = description diff --git a/compiler/globals.py b/compiler/globals.py index e4cbfe2a..5b2ab2b3 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -100,9 +100,17 @@ def check_versions(): minor_required = 5 if not (major_python_version == major_required and minor_python_version >= minor_required): debug.error("Python {0}.{1} or greater is required.".format(major_required,minor_required),-1) - + # FIXME: Check versions of other tools here?? # or, this could be done in each module (e.g. verify, characterizer, etc.) + global OPTS + + try: + import flask_table + OPTS.datasheet_gen = 1 + except: + OPTS.datasheet_gen = 0 + def init_openram(config_file, is_unit_test=True): """Initialize the technology, paths, simulators, etc.""" diff --git a/compiler/modules/replica_bitline.py b/compiler/modules/replica_bitline.py index 6d96ef21..8095d049 100644 --- a/compiler/modules/replica_bitline.py +++ b/compiler/modules/replica_bitline.py @@ -76,7 +76,6 @@ class replica_bitline(design.design): self.access_tx_offset = vector(-gap_width-self.access_tx.width-self.inv.width, 0.5*self.inv.height) - def add_modules(self): """ Add the modules for later usage """ @@ -184,19 +183,14 @@ class replica_bitline(design.design): pin = self.rbl_inst.get_pin(wl) # Route the connection to the right so that it doesn't interfere with the cells - # Wordlines may be close to each other when tiled, so gnd connections are routed in opposite directions - if row % 2 == 0: - vertical_extension = vector(0, 1.5*drc("minwidth_metal1") + 0.5*contact.m1m2.height) - else: - vertical_extension = vector(0, -1.5*drc("minwidth_metal1") - 1.5*contact.m1m2.height) - + # Wordlines may be close to each other when tiled, so gnd connections are routed in opposite directions pin_right = pin.rc() - pin_extension1 = pin_right + vector(self.m3_pitch,0) - pin_extension2 = pin_extension1 + vertical_extension + pin_extension = pin_right + vector(self.m3_pitch,0) + if pin.layer != "metal1": continue - self.add_path("metal1", [pin_right, pin_extension1, pin_extension2]) - self.add_power_pin("gnd", pin_extension2) + self.add_path("metal1", [pin_right, pin_extension]) + self.add_power_pin("gnd", pin_extension) # for multiport, need to short wordlines to each other so they all connect to gnd wl_last = self.wl_list[self.total_ports-1]+"_{}".format(row) @@ -280,7 +274,7 @@ class replica_bitline(design.design): # DRAIN ROUTE # Route the drain to the vdd rail drain_offset = self.tx_inst.get_pin("D").center() - self.add_power_pin("vdd", drain_offset) + self.add_power_pin("vdd", drain_offset, rotate=0) # SOURCE ROUTE # Route the drain to the RBL inverter input diff --git a/compiler/openram.py b/compiler/openram.py index 3bad4e71..ee43749f 100755 --- a/compiler/openram.py +++ b/compiler/openram.py @@ -39,7 +39,9 @@ import verify from sram import sram from sram_config import sram_config #from parser import * -output_extensions = ["sp","v","lib","html"] +output_extensions = ["sp","v","lib"] +if OPTS.datasheet_gen: + output_extensions.append("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/verify/magic.py b/compiler/verify/magic.py index bb116da3..9e020705 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -99,6 +99,26 @@ def write_netgen_script(cell_name, sp_name): f.close() os.system("chmod u+x {}".format(run_file)) + setup_file = OPTS.openram_temp + "setup.tcl" + f = open(setup_file, "w") + f.write("ignore class c\n") + f.write("equate class {{nfet {0}.spice}} {{n {1}}}\n".format(cell_name, sp_name)) + f.write("equate class {{pfet {0}.spice}} {{p {1}}}\n".format(cell_name, sp_name)) + # This circuit has symmetries and needs to be flattened to resolve them or the banks won't pass + # Is there a more elegant way to add this when needed? + f.write("flatten class {{{0}.spice bitcell_array}}\n".format(cell_name)) + f.write("flatten class {{{0}.spice precharge_array_1}}\n".format(cell_name)) + f.write("flatten class {{{0}.spice precharge_array_2}}\n".format(cell_name)) + f.write("flatten class {{{0}.spice precharge_array_3}}\n".format(cell_name)) + f.write("flatten class {{{0}.spice precharge_array_4}}\n".format(cell_name)) + f.write("property {{nfet {0}.spice}} remove as ad ps pd\n".format(cell_name)) + f.write("property {{pfet {0}.spice}} remove as ad ps pd\n".format(cell_name)) + f.write("property {{n {0}}} remove as ad ps pd\n".format(sp_name)) + f.write("property {{p {0}}} remove as ad ps pd\n".format(sp_name)) + f.write("permute transistors\n") + f.write("permute pins n source drain\n") + f.write("permute pins p source drain\n") + f.close() def run_drc(cell_name, gds_name, extract=False, final_verification=False): """Run DRC check on a cell which is implemented in gds_name."""