diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef2ef6a6..ed86160b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: uses: actions/checkout@v1 - name: SCMOS test run: | - . /home/github-runner/setup-paths.sh + . /home/github-runner/setup-paths.sh export OPENRAM_HOME="${{ github.workspace }}/compiler" export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech" export OPENRAM_TMP="${{ github.workspace }}/scn4me_subm_temp" @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v1 - name: FreePDK45 test run: | - . /home/github-runner/setup-paths.sh + . /home/github-runner/setup-paths.sh export OPENRAM_HOME="${{ github.workspace }}/compiler" export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech" export OPENRAM_TMP="${{ github.workspace }}/freepdk45_temp" @@ -54,4 +54,3 @@ jobs: # with: # name: code-coverage-report # path: ${{ github.workspace }}/coverage_html/ - diff --git a/compiler/base/vector.py b/compiler/base/vector.py index d217ec90..6c4fabe2 100644 --- a/compiler/base/vector.py +++ b/compiler/base/vector.py @@ -5,10 +5,11 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import debug + import math import tech + class vector(): """ This is the vector class to represent the coordinate diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index 205baeb7..9cfd7d7c 100644 --- a/compiler/base/verilog.py +++ b/compiler/base/verilog.py @@ -153,7 +153,7 @@ class verilog: self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port)) if self.num_spare_cols: self.vf.write(" spare_wen{0}_reg = spare_wen{0};\n".format(port)) - self.vf.write(" addr{0}_reg = addr{0};\n".format(port)) + self.vf.write(" addr{0}_reg = addr{0};\n".format(port)) if port in self.read_ports: self.add_write_read_checks(port) @@ -190,15 +190,15 @@ class verilog: self.vf.write(" input csb{0}; // active low chip select\n".format(port)) if port in self.readwrite_ports: self.vf.write(" input web{0}; // active low write control\n".format(port)) + + self.vf.write(" input [ADDR_WIDTH-1:0] addr{0};\n".format(port)) + if port in self.write_ports: if self.write_size: self.vf.write(" input [NUM_WMASKS-1:0] wmask{0}; // write mask\n".format(port)) if self.num_spare_cols == 1: self.vf.write(" input spare_wen{0}; // spare mask\n".format(port)) elif self.num_spare_cols > 1: self.vf.write(" input [{1}:0] spare_wen{0}; // spare mask\n".format(port, self.num_spare_cols-1)) - - self.vf.write(" input [ADDR_WIDTH-1:0] addr{0};\n".format(port)) - if port in self.write_ports: self.vf.write(" input [DATA_WIDTH-1:0] din{0};\n".format(port)) if port in self.read_ports: self.vf.write(" output [DATA_WIDTH-1:0] dout{0};\n".format(port)) diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 6f6f7bcd..68539aa5 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -31,7 +31,7 @@ class lib: self.gnd_name = spice["ground"] except KeyError: self.gnd_name = "gnd" - + self.out_dir = out_dir self.sram = sram self.sp_file = sp_file @@ -60,7 +60,7 @@ class lib: self.load_scales = np.array(OPTS.load_scales) self.load = tech.spice["dff_in_cap"] self.loads = self.load_scales * self.load - + self.slew_scales = np.array(OPTS.slew_scales) self.slew = tech.spice["rise_time"] @@ -79,7 +79,7 @@ class lib: self.slews.append(slew) self.loads = np.array(self.loads) self.slews = np.array(self.slews) - debug.info(1, "Slews: {0}".format(self.slews)) + debug.info(1, "Slews: {0}".format(self.slews)) debug.info(1, "Loads: {0}".format(self.loads)) debug.info(1, "self.load_slews : {0}".format(self.load_slews)) def create_corners(self): @@ -102,7 +102,7 @@ class lib: self.corners = [] self.lib_files = [] - + if OPTS.use_specified_corners == None: # Nominal corner corner_tuples = set() @@ -114,7 +114,7 @@ class lib: for v in self.supply_voltages: for t in self.temperatures: corner_tuples.add((p, v, t)) - else: + else: nom_corner = (nom_process, nom_supply, nom_temperature) corner_tuples.add(nom_corner) if not OPTS.nominal_corner_only: @@ -132,7 +132,7 @@ class lib: corner_tuples.remove(nom_corner) else: corner_tuples = OPTS.use_specified_corners - + for corner_tuple in corner_tuples: self.add_corner(*corner_tuple) @@ -366,16 +366,16 @@ class lib: self.lib.write(" base_type : array;\n") self.lib.write(" data_type : bit;\n") self.lib.write(" bit_width : {0};\n".format(self.sram.word_size)) - self.lib.write(" bit_from : 0;\n") - self.lib.write(" bit_to : {0};\n".format(self.sram.word_size - 1)) + self.lib.write(" bit_from : {0};\n".format(self.sram.word_size - 1)) + self.lib.write(" bit_to : 0;\n") self.lib.write(" }\n\n") self.lib.write(" type (addr){\n") self.lib.write(" base_type : array;\n") self.lib.write(" data_type : bit;\n") self.lib.write(" bit_width : {0};\n".format(self.sram.addr_size)) - self.lib.write(" bit_from : 0;\n") - self.lib.write(" bit_to : {0};\n".format(self.sram.addr_size - 1)) + self.lib.write(" bit_from : {0};\n".format(self.sram.addr_size - 1)) + self.lib.write(" bit_to : 0;\n") self.lib.write(" }\n\n") if self.sram.write_size: @@ -383,8 +383,8 @@ class lib: self.lib.write(" base_type : array;\n") self.lib.write(" data_type : bit;\n") self.lib.write(" bit_width : {0};\n".format(self.sram.num_wmasks)) - self.lib.write(" bit_from : 0;\n") - self.lib.write(" bit_to : {0};\n".format(self.sram.num_wmasks - 1)) + self.lib.write(" bit_from : {0};\n".format(self.sram.num_wmasks - 1)) + self.lib.write(" bit_to : 0;\n") self.lib.write(" }\n\n") @@ -652,21 +652,21 @@ class lib: probe_address = "0" + "1" * (self.sram.addr_size - 1) probe_data = self.sram.word_size - 1 char_results = self.d.analyze(probe_address, probe_data, self.load_slews) - - - + + + self.char_sram_results, self.char_port_results = char_results if 'sim_time' in self.char_sram_results: self.pred_time = self.char_sram_results['sim_time'] # Add to the OPTS to be written out as part of the extended OPTS file - # FIXME: Temporarily removed from characterization output + # FIXME: Temporarily removed from characterization output # if not self.use_model: # OPTS.sen_path_delays = self.char_sram_results["sen_path_measures"] # OPTS.sen_path_names = self.char_sram_results["sen_path_names"] # OPTS.bl_path_delays = self.char_sram_results["bl_path_measures"] # OPTS.bl_path_names = self.char_sram_results["bl_path_names"] - - + + def compute_setup_hold(self): """ Do the analysis if we haven't characterized a FF yet """ # Do the analysis if we haven't characterized a FF yet @@ -689,23 +689,23 @@ class lib: datasheet = open(datasheet_path +'/datasheet.info', 'w') else: datasheet = open(datasheet_path +'/datasheet.info', 'a+') - + self.write_inp_params_datasheet(datasheet, corner, lib_name) self.write_signal_from_ports(datasheet, - "din{1}[{0}:0]".format(self.sram.word_size - 1, '{}'), - self.write_ports, + "din{1}[{0}:0]".format(self.sram.word_size - 1, '{}'), + self.write_ports, "setup_times_LH", - "setup_times_HL", + "setup_times_HL", "hold_times_LH", "hold_times_HL") - + # self.write_signal_from_ports(datasheet, - # "dout{1}[{0}:0]".format(self.sram.word_size - 1, '{}'), - # self.read_ports, + # "dout{1}[{0}:0]".format(self.sram.word_size - 1, '{}'), + # self.read_ports, # "delay_lh", - # "delay_hl", + # "delay_hl", # "slew_lh", - # "slew_hl") + # "slew_hl") for port in self.all_ports: #dout timing if port in self.read_ports: @@ -722,41 +722,41 @@ class lib: min(list(map(round_time,self.char_port_results[port]["slew_hl"]))), max(list(map(round_time,self.char_port_results[port]["slew_hl"]))) - )) + )) self.write_signal_from_ports(datasheet, - "csb{}", - self.all_ports, + "csb{}", + self.all_ports, "setup_times_LH", - "setup_times_HL", + "setup_times_HL", "hold_times_LH", "hold_times_HL") - - self.write_signal_from_ports(datasheet, - "addr{1}[{0}:0]".format(self.sram.addr_size - 1, '{}'), - self.all_ports, - "setup_times_LH", - "setup_times_HL", - "hold_times_LH", - "hold_times_HL") self.write_signal_from_ports(datasheet, - "web{}", - self.readwrite_ports, + "addr{1}[{0}:0]".format(self.sram.addr_size - 1, '{}'), + self.all_ports, "setup_times_LH", - "setup_times_HL", + "setup_times_HL", "hold_times_LH", - "hold_times_HL") + "hold_times_HL") + + self.write_signal_from_ports(datasheet, + "web{}", + self.readwrite_ports, + "setup_times_LH", + "setup_times_HL", + "hold_times_LH", + "hold_times_HL") self.write_power_datasheet(datasheet) self.write_model_params(datasheet, time) - + datasheet.write("END\n") datasheet.close() def write_inp_params_datasheet(self, datasheet, corner, lib_name): - + if OPTS.is_unit_test: git_id = 'FFFFFFFFFFFFFFFFFFFF' @@ -776,7 +776,7 @@ class lib: debug.warning("Failed to retrieve git id") git_id = 'Failed to retrieve' current_time = datetime.date.today() - + # write static information to be parser later datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format( OPTS.output_name, @@ -804,8 +804,8 @@ class lib: # write area datasheet.write(str(self.sram.width * self.sram.height) + ',') - - def write_signal_from_ports(self, datasheet, signal, ports, time_pos_1, time_pos_2, time_pos_3, time_pos_4): + + def write_signal_from_ports(self, datasheet, signal, ports, time_pos_1, time_pos_2, time_pos_3, time_pos_4): for port in ports: datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format( signal.format(port), @@ -822,8 +822,8 @@ class lib: max(list(map(round_time,self.times[time_pos_4]))) )) - - def write_power_datasheet(self, datasheet): + + def write_power_datasheet(self, datasheet): # write power information for port in self.all_ports: name = '' @@ -862,34 +862,30 @@ class lib: control_str += ' & csb{0}'.format(i) datasheet.write("{0},{1},{2},".format('leak', control_str, self.char_sram_results["leakage_power"])) - + def write_model_params(self, datasheet, time): """Write values which will be used in the analytical model as inputs""" datasheet.write("{0},{1},".format('sim_time', time)) datasheet.write("{0},{1},".format('words_per_row', OPTS.words_per_row)) datasheet.write("{0},{1},".format('slews', list(self.slews))) datasheet.write("{0},{1},".format('loads', list(self.loads))) - + for port in self.read_ports: datasheet.write("{0},{1},".format('cell_rise_{}'.format(port), self.char_port_results[port]["delay_lh"])) datasheet.write("{0},{1},".format('cell_fall_{}'.format(port), self.char_port_results[port]["delay_hl"])) datasheet.write("{0},{1},".format('rise_transition_{}'.format(port), self.char_port_results[port]["slew_lh"])) datasheet.write("{0},{1},".format('fall_transition_{}'.format(port), self.char_port_results[port]["slew_hl"])) - + for port in self.write_ports: write1_power = np.mean(self.char_port_results[port]["write1_power"]) write0_power = np.mean(self.char_port_results[port]["write0_power"]) datasheet.write("{0},{1},".format('write_rise_power_{}'.format(port), write1_power)) #FIXME: should be write_fall_power datasheet.write("{0},{1},".format('write_fall_power_{}'.format(port), write0_power)) - + for port in self.read_ports: read1_power = np.mean(self.char_port_results[port]["read1_power"]) read0_power = np.mean(self.char_port_results[port]["read0_power"]) datasheet.write("{0},{1},".format('read_rise_power_{}'.format(port), read1_power)) #FIXME: should be read_fall_power datasheet.write("{0},{1},".format('read_fall_power_{}'.format(port), read0_power)) - - - - diff --git a/compiler/globals.py b/compiler/globals.py index f6929c4d..50960fdc 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -22,7 +22,7 @@ import getpass import subprocess -VERSION = "1.1.15" +VERSION = "1.1.18" NAME = "OpenRAM v{}".format(VERSION) USAGE = "openram.py [options] \nUse -h for help.\n" diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 666458ed..7a36abf6 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -369,11 +369,9 @@ class bank(design.design): 3 * self.m2_pitch, drc("nwell_to_nwell")) - def add_modules(self): """ Add all the modules using the class loader """ - local_array_size = OPTS.local_array_size if local_array_size > 0: @@ -705,7 +703,7 @@ class bank(design.design): pitch=self.m3_pitch) self.copy_layout_pin(self.port_address_inst[0], "wl_en", self.prefix + "wl_en0") - + # Port 1 if len(self.all_ports)==2: # The other control bus is routed up to two pitches above the bitcell array diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 52c1dd87..9565958a 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -121,8 +121,10 @@ class control_logic(design.design): # max_fanout = max(self.num_rows, self.num_cols) # wl_en drives every row in the bank + # MRG 9/3/2021: Ensure that this is two stages to prevent race conditions with the write driver + size_list = [max(int(self.num_rows / 9), 1), max(int(self.num_rows / 3), 1)] self.wl_en_driver = factory.create(module_type="pdriver", - fanout=self.num_rows, + size_list=size_list, height=dff_height) self.add_mod(self.wl_en_driver) @@ -348,7 +350,7 @@ class control_logic(design.design): row += 1 control_center_y = self.wl_en_inst.uy() + self.m3_pitch - + # Delay chain always gets placed at row 4 self.place_delay(4) height = self.delay_inst.uy() @@ -391,7 +393,7 @@ class control_logic(design.design): def place_delay(self, row): """ Place the replica bitline """ debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.") - + # It is flipped on X axis y_off = row * self.and2.height + self.delay_chain.height diff --git a/compiler/modules/local_bitcell_array.py b/compiler/modules/local_bitcell_array.py index e48cff5c..6049f3b1 100644 --- a/compiler/modules/local_bitcell_array.py +++ b/compiler/modules/local_bitcell_array.py @@ -302,4 +302,3 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array): Clears the bit exclusions """ self.bitcell_array.clear_exclude_bits() - diff --git a/compiler/sram/sram_config.py b/compiler/sram/sram_config.py index d28f28a8..4c1bb117 100644 --- a/compiler/sram/sram_config.py +++ b/compiler/sram/sram_config.py @@ -43,6 +43,21 @@ class sram_config: self.compute_sizes() + def __str__(self): + """ override print function output """ + config_items = ["num_banks", + "word_size", + "num_words", + "words_per_row", + "write_size", + "num_spare_rows", + "num_spare_cols"] + str = "" + for item in config_items: + val = getattr(self, item) + str += "{} : {}\n".format(item, val) + return str + def set_local_config(self, module): """ Copy all of the member variables to the given module for convenience """ diff --git a/compiler/tests/21_model_delay_test.py b/compiler/tests/21_model_delay_test.py index 28c3def1..cf320208 100755 --- a/compiler/tests/21_model_delay_test.py +++ b/compiler/tests/21_model_delay_test.py @@ -49,7 +49,7 @@ class model_delay_test(openram_test): debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data)) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - + d = delay(s.s, tempspice, corner) m = elmore(s.s, tempspice, corner) import tech @@ -77,9 +77,9 @@ class model_delay_test(openram_test): debug.info(1,"Model Delays={}".format(model_delays)) if OPTS.tech_name == "freepdk45": - error_tolerance = 0.25 + error_tolerance = 0.30 elif OPTS.tech_name == "scn4m_subm": - error_tolerance = 0.25 + error_tolerance = 0.30 else: self.assertTrue(False) # other techs fail diff --git a/compiler/tests/21_xyce_delay_test.py b/compiler/tests/21_xyce_delay_test.py index 63798931..16fdc2f6 100755 --- a/compiler/tests/21_xyce_delay_test.py +++ b/compiler/tests/21_xyce_delay_test.py @@ -60,20 +60,20 @@ class timing_sram_test(openram_test): data.update(port_data[0]) if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl': [0.24042560000000002], - 'delay_lh': [0.24042560000000002], - 'disabled_read0_power': [0.8981647999999998], - 'disabled_read1_power': [0.9101543999999998], - 'disabled_write0_power': [0.9270382999999998], - 'disabled_write1_power': [0.9482969999999998], - 'leakage_power': 2.9792199999999998, + golden_data = {'delay_hl': [0.2314011], + 'delay_lh': [0.2314011], + 'disabled_read0_power': [0.173459901], + 'disabled_read1_power': [0.185612201], + 'disabled_write0_power': [0.202493001], + 'disabled_write1_power': [0.224080601], + 'leakage_power': 0.0017065770000000001, 'min_period': 0.938, - 'read0_power': [1.1107930999999998], - 'read1_power': [1.1143252999999997], - 'slew_hl': [0.2800772], - 'slew_lh': [0.2800772], - 'write0_power': [1.1667769], - 'write1_power': [1.0986076999999999]} + 'read0_power': [0.372276201], + 'read1_power': [0.37621480100000004], + 'slew_hl': [0.27947489999999997], + 'slew_lh': [0.27947489999999997], + 'write0_power': [0.429895901], + 'write1_power': [0.383337501]} elif OPTS.tech_name == "scn4m_subm": golden_data = {'delay_hl': [1.884186], 'delay_lh': [1.884186], diff --git a/compiler/tests/50_riscv_1rw_func_test.py b/compiler/tests/50_riscv_1rw_func_test.py index c77f21d1..0e7e79d7 100755 --- a/compiler/tests/50_riscv_1rw_func_test.py +++ b/compiler/tests/50_riscv_1rw_func_test.py @@ -36,6 +36,7 @@ class riscv_func_test(openram_test): OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 0 + OPTS.local_array_size = 16 globals.setup_bitcell() # This is a hack to reload the characterizer __init__ with the spice version diff --git a/compiler/tests/50_riscv_1rw_phys_test.py b/compiler/tests/50_riscv_1rw_phys_test.py index a2822b09..4cf2def5 100755 --- a/compiler/tests/50_riscv_1rw_phys_test.py +++ b/compiler/tests/50_riscv_1rw_phys_test.py @@ -16,7 +16,7 @@ from sram_factory import factory import debug -# @unittest.skip("SKIPPING 50_riscv_phys_test") +#@unittest.skip("SKIPPING 50_riscv_phys_test") class riscv_phys_test(openram_test): def runTest(self): @@ -24,8 +24,15 @@ class riscv_phys_test(openram_test): globals.init_openram(config_file) from sram_config import sram_config + if OPTS.tech_name == "sky130": + num_spare_rows = 1 + num_spare_cols = 1 + else: + num_spare_rows = 0 + num_spare_cols = 0 + OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 1 + OPTS.num_r_ports = 0 OPTS.num_w_ports = 0 OPTS.local_array_size = 16 globals.setup_bitcell() @@ -36,9 +43,9 @@ class riscv_phys_test(openram_test): write_size=8, num_words=32, num_banks=1, - num_spare_rows=1, - num_spare_cols=1) - c.words_per_row=2 + num_spare_cols=num_spare_cols, + num_spare_rows=num_spare_rows) + c.words_per_row=1 c.recompute_sizes() debug.info(1, "Layout test for {}rw,{}r,{}w sram " "with {} bit words, {} words, {} words per " diff --git a/compiler/tests/configs/config_back_end.py b/compiler/tests/configs/config_back_end.py index 884b4926..3294e979 100644 --- a/compiler/tests/configs/config_back_end.py +++ b/compiler/tests/configs/config_back_end.py @@ -6,7 +6,7 @@ # All rights reserved. # from globals import OPTS -word_size = 1 +word_size = 2 num_words = 16 tech_name = OPTS.tech_name diff --git a/compiler/tests/configs/config_front_end.py b/compiler/tests/configs/config_front_end.py index e255d7a5..4486b077 100644 --- a/compiler/tests/configs/config_front_end.py +++ b/compiler/tests/configs/config_front_end.py @@ -6,7 +6,7 @@ # All rights reserved. # from globals import OPTS -word_size = 1 +word_size = 2 num_words = 16 tech_name = OPTS.tech_name diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index 326771f8..b4a3f254 100644 --- a/compiler/verify/__init__.py +++ b/compiler/verify/__init__.py @@ -32,14 +32,16 @@ if not OPTS.check_lvsdrc: # OPTS.magic_exe = None else: debug.info(1, "Finding DRC/LVS/PEX tools.") - OPTS.drc_exe = get_tool("DRC", ["calibre", "assura", "magic"], drc_name) - OPTS.lvs_exe = get_tool("LVS", ["calibre", "assura", "netgen"], lvs_name) - OPTS.pex_exe = get_tool("PEX", ["calibre", "magic"], pex_name) + OPTS.drc_exe = get_tool("DRC", ["klayout", "magic", "calibre", "assura"], drc_name) + OPTS.lvs_exe = get_tool("LVS", ["klayout", "netgen", "calibre", "assura"], lvs_name) + OPTS.pex_exe = get_tool("PEX", ["klayout", "magic", "calibre"], pex_name) # if OPTS.tech_name == "sky130": # OPTS.magic_exe = get_tool("GDS", ["magic"]) if not OPTS.drc_exe: from .none import run_drc, print_drc_stats, write_drc_script +elif "klayout"==OPTS.drc_exe[0]: + from .klayout import run_drc, print_drc_stats, write_drc_script elif "calibre"==OPTS.drc_exe[0]: from .calibre import run_drc, print_drc_stats, write_drc_script elif "assura"==OPTS.drc_exe[0]: @@ -52,6 +54,8 @@ else: if not OPTS.lvs_exe: from .none import run_lvs, print_lvs_stats, write_lvs_script +elif "klayout"==OPTS.lvs_exe[0]: + from .klayout import run_lvs, print_lvs_stats, write_lvs_script elif "calibre"==OPTS.lvs_exe[0]: from .calibre import run_lvs, print_lvs_stats, write_lvs_script elif "assura"==OPTS.lvs_exe[0]: @@ -65,6 +69,8 @@ else: if not OPTS.pex_exe: from .none import run_pex, print_pex_stats +elif "klayout"==OPTS.pex_exe[0]: + from .klayout import run_pex, print_pex_stats elif "calibre"==OPTS.pex_exe[0]: from .calibre import run_pex, print_pex_stats elif "magic"==OPTS.pex_exe[0]: @@ -78,4 +84,3 @@ else: # from .magic import filter_gds # else: # debug.warning("Did not find Magic.") - diff --git a/compiler/verify/klayout.py b/compiler/verify/klayout.py new file mode 100644 index 00000000..b5e1fb82 --- /dev/null +++ b/compiler/verify/klayout.py @@ -0,0 +1,266 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2021 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +""" +This is a DRC/LVS/PEX interface file for klayout. + +""" + + +import os +import re +import shutil +import debug +from globals import OPTS +from run_script import * + +# Keep track of statistics +num_drc_runs = 0 +num_lvs_runs = 0 +num_pex_runs = 0 + + +def write_drc_script(cell_name, gds_name, extract, final_verification, output_path, sp_name=None): + """ + Write a klayout script to perform DRC and optionally extraction. + """ + global OPTS + + # DRC: + # klayout -b -r drc_FreePDK45.lydrc -rd input=sram_8_256_freepdk45.gds -rd topcell=sram_8_256_freepdk45 -rd output=drc_FreePDK45.lyrdb + + # Copy .lydrc file into the output directory + full_drc_file = OPTS.openram_tech + "tech/{}.lydrc".format(OPTS.tech_name) + drc_file = os.path.basename(full_drc_file) + if os.path.exists(full_drc_file): + shutil.copy(full_drc_file, output_path) + else: + debug.warning("Could not locate file: {}".format(full_drc_file)) + + # Create an auxiliary script to run calibre with the runset + run_file = output_path + "run_drc.sh" + f = open(run_file, "w") + f.write("#!/bin/sh\n") + cmd = "{0} -b -r {1} -rd input={2} -rd topcell={3} -rd output={3}.drc.report".format(OPTS.drc_exe[1], + drc_file, + gds_name, + cell_name) + + f.write(cmd) + f.write("\n") + f.close() + os.system("chmod u+x {}".format(run_file)) + + +def run_drc(cell_name, gds_name, sp_name=None, extract=True, final_verification=False): + """Run DRC check on a cell which is implemented in gds_name.""" + + global num_drc_runs + num_drc_runs += 1 + + write_drc_script(cell_name, gds_name, extract, final_verification, OPTS.openram_temp, sp_name=sp_name) + + (outfile, errfile, resultsfile) = run_script(cell_name, "drc") + + # Check the result for these lines in the summary: + # Total DRC errors found: 0 + # The count is shown in this format: + # Cell replica_cell_6t has 3 error tiles. + # Cell tri_gate_array has 8 error tiles. + # etc. + try: + f = open(resultsfile, "r") + except FileNotFoundError: + debug.error("Unable to load DRC results file from {}. Is klayout set up?".format(resultsfile), 1) + + results = f.readlines() + f.close() + errors=len([x for x in results if "" in x]) + + # always display this summary + result_str = "DRC Errors {0}\t{1}".format(cell_name, errors) + if errors > 0: + debug.warning(result_str) + else: + debug.info(1, result_str) + + return errors + + +def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, output_path=None): + """ Write a klayout script to perform LVS. """ + + # LVS: + # klayout -b -rd input=sram_32_2048_freepdk45.gds -rd report=my_report.lyrdb -rd schematic=sram_32_2048_freepdk45.sp -rd target_netlist=sram_32_2048_freepdk45_extracted.cir -r lvs_freepdk45.lvs + + global OPTS + + if not output_path: + output_path = OPTS.openram_temp + + # Copy .lylvs file into the output directory + full_lvs_file = OPTS.openram_tech + "tech/{}.lylvs".format(OPTS.tech_name) + lvs_file = os.path.basename(full_lvs_file) + + if os.path.exists(full_lvs_file): + shutil.copy(full_lvs_file, output_path) + else: + debug.warning("Could not locate file: {}".format(full_lvs_file)) + + run_file = output_path + "/run_lvs.sh" + f = open(run_file, "w") + f.write("#!/bin/sh\n") + cmd = "{0} -b -r {1} -rd input={2} -rd report={4}.lvs.report -rd schematic={3} -rd target_netlist={4}.spice".format(OPTS.lvs_exe[1], + lvs_file, + gds_name, + sp_name, + cell_name) + f.write(cmd) + f.write("\n") + f.close() + os.system("chmod u+x {}".format(run_file)) + + +def run_lvs(cell_name, gds_name, sp_name, final_verification=False, output_path=None): + """Run LVS check on a given top-level name which is + implemented in gds_name and sp_name. Final verification will + ensure that there are no remaining virtual conections. """ + + global num_lvs_runs + num_lvs_runs += 1 + + if not output_path: + output_path = OPTS.openram_temp + + write_lvs_script(cell_name, gds_name, sp_name, final_verification) + + (outfile, errfile, resultsfile) = run_script(cell_name, "lvs") + + # check the result for these lines in the summary: + try: + f = open(outfile, "r") + except FileNotFoundError: + debug.error("Unable to load LVS results from {}".format(outfile), 1) + + results = f.readlines() + f.close() + # Look for CONGRATULATIONS or ERROR + congrats = len([x for x in results if "CONGRATULATIONS" in x]) + total_errors = len([x for x in results if "ERROR" in x]) + + if total_errors>0: + debug.error("{0}\tLVS mismatch (results in {1})".format(cell_name, resultsfile)) + elif congrats>0: + debug.info(1, "{0}\tLVS matches".format(cell_name)) + else: + debug.info(1, "{0}\tNo LVS result".format(cell_name)) + total_errors += 1 + + return total_errors + + +def run_pex(name, gds_name, sp_name, output=None, final_verification=False, output_path=None): + """Run pex on a given top-level name which is + implemented in gds_name and sp_name. """ + + debug.error("PEX not implemented", -1) + + global num_pex_runs + num_pex_runs += 1 + + if not output_path: + output_path = OPTS.openram_temp + + os.chdir(output_path) + + if not output_path: + output_path = OPTS.openram_temp + + if output == None: + output = name + ".pex.netlist" + + # check if lvs report has been done + # if not run drc and lvs + if not os.path.isfile(name + ".lvs.report"): + run_drc(name, gds_name) + run_lvs(name, gds_name, sp_name) + + # # pex_fix did run the pex using a script while dev orignial method + # # use batch mode. + # # the dev old code using batch mode does not run and is split into functions + # pex_runset = write_script_pex_rule(gds_name, name, sp_name, output) + + # errfile = "{0}{1}.pex.err".format(output_path, name) + # outfile = "{0}{1}.pex.out".format(output_path, name) + + # script_cmd = "{0} 2> {1} 1> {2}".format(pex_runset, + # errfile, + # outfile) + # cmd = script_cmd + # debug.info(2, cmd) + # os.system(cmd) + + # # rename technology models + # pex_nelist = open(output, 'r') + # s = pex_nelist.read() + # pex_nelist.close() + # s = s.replace('pfet', 'p') + # s = s.replace('nfet', 'n') + # f = open(output, 'w') + # f.write(s) + # f.close() + + # # also check the output file + # f = open(outfile, "r") + # results = f.readlines() + # f.close() + # out_errors = find_error(results) + # debug.check(os.path.isfile(output), "Couldn't find PEX extracted output.") + + # correct_port(name, output, sp_name) + return out_errors + + +def write_batch_pex_rule(gds_name, name, sp_name, output): + """ + """ + # write the runset file + file = OPTS.openram_temp + "pex_runset" + f = open(file, "w") + + f.close() + return file + + +def write_script_pex_rule(gds_name, cell_name, sp_name, output): + global OPTS + run_file = OPTS.openram_temp + "run_pex.sh" + f = open(run_file, "w") + f.write("#!/bin/sh\n") + f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH'])) + f.write('echo "$(date): Starting PEX using Klayout {}"\n'.format(OPTS.drc_exe[1])) + + f.write("retcode=$?\n") + f.write("mv {0}.spice {1}\n".format(cell_name, output)) + f.write('echo "$(date): Finished PEX using Klayout {}"\n'.format(OPTS.drc_exe[1])) + f.write("exit $retcode\n") + + f.close() + os.system("chmod u+x {}".format(run_file)) + return run_file + + +def print_drc_stats(): + debug.info(1, "DRC runs: {0}".format(num_drc_runs)) + + +def print_lvs_stats(): + debug.info(1, "LVS runs: {0}".format(num_lvs_runs)) + + +def print_pex_stats(): + debug.info(1, "PEX runs: {0}".format(num_pex_runs)) diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index 18ba53fc..8c07df8b 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -71,15 +71,15 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa global OPTS # Copy .magicrc file into the output directory - magic_file = os.environ.get('OPENRAM_MAGICRC', None) - if not magic_file: - magic_file = OPTS.openram_tech + "tech/.magicrc" + full_magic_file = os.environ.get('OPENRAM_MAGICRC', None) + if not full_magic_file: + full_magic_file = OPTS.openram_tech + "tech/.magicrc" - if os.path.exists(magic_file): - shutil.copy(magic_file, output_path + "/.magicrc") + if os.path.exists(full_magic_file): + shutil.copy(full_magic_file, output_path + "/.magicrc") else: - debug.warning("Could not locate .magicrc file: {}".format(magic_file)) - + debug.warning("Could not locate .magicrc file: {}".format(full_magic_file)) + run_file = output_path + "run_ext.sh" f = open(run_file, "w") f.write("#!/bin/sh\n") @@ -96,7 +96,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa f.write("gds warning default\n") # These two options are temporarily disabled until Tim fixes a bug in magic related # to flattening channel routes and vias (hierarchy with no devices in it). Otherwise, - # they appear to be disconnected. + # they appear to be disconnected. f.write("gds flatten true\n") f.write("gds ordering true\n") f.write("gds readonly true\n") @@ -106,7 +106,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa f.write('puts "Finished loading cell {}"\n'.format(cell_name)) f.write("cellname delete \\(UNNAMED\\)\n") f.write("writeall force\n") - + # Extract if not sp_name: f.write("port makeall\n") @@ -142,7 +142,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa f.write(pre + "ext2spice format ngspice\n") f.write(pre + "ext2spice {}\n".format(cell_name)) f.write('puts "Finished ext2spice"\n') - + f.write("quit -noprompt\n") f.write("EOF\n") f.write("magic_retcode=$?\n") @@ -165,7 +165,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa mag_file = OPTS.openram_tech + "maglef_lib/" + blackbox_cell_name + ".mag" debug.check(os.path.isfile(mag_file), "Could not find blackbox cell {}".format(mag_file)) f.write('cp {0} .\n'.format(mag_file)) - + f.write('echo "$(date): Starting DRC using Magic {}"\n'.format(OPTS.drc_exe[1])) f.write('\n') f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1])) @@ -193,7 +193,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa f.close() os.system("chmod u+x {}".format(run_file)) - + def run_drc(cell_name, gds_name, sp_name=None, extract=True, final_verification=False): """Run DRC check on a cell which is implemented in gds_name.""" @@ -202,9 +202,9 @@ def run_drc(cell_name, gds_name, sp_name=None, extract=True, final_verification= num_drc_runs += 1 write_drc_script(cell_name, gds_name, extract, final_verification, OPTS.openram_temp, sp_name=sp_name) - + (outfile, errfile, resultsfile) = run_script(cell_name, "ext") - + (outfile, errfile, resultsfile) = run_script(cell_name, "drc") # Check the result for these lines in the summary: @@ -252,13 +252,14 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out output_path = OPTS.openram_temp # Copy setup.tcl file into the output directory - setup_file = os.environ.get('OPENRAM_NETGENRC', None) - if not setup_file: - setup_file = OPTS.openram_tech + "tech/setup.tcl" - - if os.path.exists(setup_file): + full_setup_file = os.environ.get('OPENRAM_NETGENRC', None) + if not full_setup_file: + full_setup_file = OPTS.openram_tech + "tech/setup.tcl" + setup_file = os.path.basename(full_setup_file) + + if os.path.exists(full_setup_file): # Copy setup.tcl file into temp dir - shutil.copy(setup_file, output_path) + shutil.copy(full_setup_file, output_path) else: setup_file = 'nosetup' @@ -290,7 +291,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False, output_path= if not output_path: output_path = OPTS.openram_temp - + write_lvs_script(cell_name, gds_name, sp_name, final_verification) (outfile, errfile, resultsfile) = run_script(cell_name, "lvs") @@ -358,10 +359,10 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False, outp global num_pex_runs num_pex_runs += 1 - + if not output_path: output_path = OPTS.openram_temp - + os.chdir(output_path) if not output_path: diff --git a/technology/freepdk45/gds_lib/cell_1rw.gds b/technology/freepdk45/gds_lib/cell_1rw.gds index 1a138c4a..3a67e40b 100644 Binary files a/technology/freepdk45/gds_lib/cell_1rw.gds and b/technology/freepdk45/gds_lib/cell_1rw.gds differ diff --git a/technology/freepdk45/gds_lib/dff.gds b/technology/freepdk45/gds_lib/dff.gds index 2a6004d1..b054d462 100644 Binary files a/technology/freepdk45/gds_lib/dff.gds and b/technology/freepdk45/gds_lib/dff.gds differ diff --git a/technology/freepdk45/gds_lib/dummy_cell_1rw.gds b/technology/freepdk45/gds_lib/dummy_cell_1rw.gds index 28dd8124..cbfcb064 100644 Binary files a/technology/freepdk45/gds_lib/dummy_cell_1rw.gds and b/technology/freepdk45/gds_lib/dummy_cell_1rw.gds differ diff --git a/technology/freepdk45/gds_lib/replica_cell_1rw.gds b/technology/freepdk45/gds_lib/replica_cell_1rw.gds index 67c9bdf7..cef0722d 100644 Binary files a/technology/freepdk45/gds_lib/replica_cell_1rw.gds and b/technology/freepdk45/gds_lib/replica_cell_1rw.gds differ diff --git a/technology/freepdk45/gds_lib/sense_amp.gds b/technology/freepdk45/gds_lib/sense_amp.gds index 53c499f9..edff18ab 100644 Binary files a/technology/freepdk45/gds_lib/sense_amp.gds and b/technology/freepdk45/gds_lib/sense_amp.gds differ diff --git a/technology/freepdk45/tech/freepdk45.lydrc b/technology/freepdk45/tech/freepdk45.lydrc new file mode 100644 index 00000000..91c7c30f --- /dev/null +++ b/technology/freepdk45/tech/freepdk45.lydrc @@ -0,0 +1,431 @@ + + + + + drc + + + + false + false + + true + drc_scripts + tools_menu.drc.end + dsl + drc-dsl-xml + +# +# DRC for FreePDK45 according to : +# https://www.eda.ncsu.edu/wiki/FreePDK45:RuleDevel +# https://www.eda.ncsu.edu/wiki/FreePDK45:Contents +# +########################################################################################## +tstart = Time.now + +# optionnal for a batch launch : klayout -b r drc_FreePDK45.lydrc -rd input=my_layout.gds -rd topcell=your_topcell -rd output=drc_FreePDK45.lyrdb +if $input + if $topcell + source($input,$topcell) + else + source($input) + end +end + +if $output + report("FreePDK45 DRC runset", $output) +else + report("FreePDK45 DRC runset", "FreePDK45_DRC.lyrdb") +end + +# DRC test to run or not +############### +OFFGRID = true +ANTENNA = true +DRC = true + +# KLAYOUT setup +######################## +# Use a tile size of 1mm +# tiles(100.um) +# Use a tile border of 10 micron: +# tile_borders(1.um) +# no_borders + +# Hierachical +deep + +# Use 4 CPU cores +threads(4) +verbose(true) + +# layers definitions +######################## +active = polygons(1, 0) +pwell = polygons(2, 0) +nwell = polygons(3, 0) +nplus = polygons(4, 0) +pplus = polygons(5, 0) +vtg = polygons(6, 0) +vth = polygons(7, 0) +thkox = polygons(8, 0) +poly = polygons(9, 0) +cont = polygons(10, 0) +metal1 = polygons(11, 0) +via1 = polygons(12, 0) +metal2 = polygons(13, 0) +via2 = polygons(14, 0) +metal3 = polygons(15, 0) +via3 = polygons(16, 0) +metal4 = polygons(17, 0) +via4 = polygons(18, 0) +metal5 = polygons(19, 0) +via5 = polygons(20, 0) +metal6 = polygons(21, 0) +via6 = polygons(22, 0) +metal7 = polygons(23, 0) +via7 = polygons(24, 0) +metal8 = polygons(25, 0) +via8 = polygons(26, 0) +metal9 = polygons(27, 0) +via9 = polygons(28, 0) +metal10 = polygons(29, 0) + +# Computed layers +well = nwell.or(pwell) +gate = poly & active +implant = nplus.or(pplus) + +if DRC + +# DRC section +######################## +info("DRC section") + +# splits a layer classes with increasing min dimensions +def classify_by_width(layer, *dimensions) + dimensions.collect { |d| layer = layer.sized(-0.5 * (d - 1.dbu)).sized(0.5 * (d - 1.dbu)) } +end + +# Wells +nwell.and(pwell).output("WELL.1", "WELL.1 : nwell/pwell must not overlap") +# the rule "WELL.2 : Minimum spacing of well at different potential : 225nm" was not coded : see : https://www.klayout.de/forum/discussion/comment/6021 +nwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of nwell at same potential : 135nm") +pwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of pwell at same potential : 135nm") +well.separation(well, 200.nm, euclidian).output("WELL.4", "WELL.4 : Minimum width of nwell/pwell : 200nm") +vtg.not(well).output("VT.1","VT.1 : Vtg adjust layers must coincide with well") +vth.not(well).output("VT.1","VT.1 : Vth adjust layers must coincide with well") + + # Poly +poly.width(50.nm, euclidian).output("POLY.1", "POLY.1 : Minimum width of poly : 50nm") +poly_sep_active = poly.separation(active, 140.nm, projection) +if poly_sep_active.polygons? + poly_sep_active.polygons.without_area(0).output("POLY.2", "POLY.2 : Minimum spacing of poly AND active: 140nm") +end +poly_sep_active.forget +poly.enclosing(gate, 55.nm, projection).polygons.without_area(0).output("POLY.3", "POLY.3 : Minimum poly extension beyond active : 55nm") +active.enclosing(gate, 70.nm, projection).polygons.without_area(0).output("POLY.4", "POLY.4 : Minimum enclosure of active around gate : 70nm") +poly.not(active).separation(active, 50.nm, projection).polygons.without_area(0).output("POLY.5", "POLY.5 : Minimum spacing of field poly to active: 50nm") +poly.space(75.nm, euclidian).output("POLY.6", "POLY.6 : Minimum spacing of field poly: 75nm") + + # Active +active.width(90.nm, euclidian).output("ACTIVE.1", "ACTIVE.1 : Minimum width of active : 90nm") +active.space(80.nm, euclidian).output("ACTIVE.2", "ACTIVE.2 : Minimum spacing of active : 80nm") +well.enclosing(active, 55.nm, euclidian).output("ACTIVE.3", "ACTIVE.3 : Minimum enclosure/spacing of nwell/pwell to active: 55nm") +active.not(well).output("ACTIVE.4", "ACTIVE.4 : active must be inside nwell or pwell") + +# Implant +implant.separation(gate, 70.nm, projection).polygons.without_area(0).output("IMPLANT.1", "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to channel : 70nm") +implant.separation(cont, 25.nm, projection).polygons.without_area(0).output("IMPLANT.2", "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to contact : 25nm") +implant.width(45.nm, euclidian).output("IMPLANT.3", "IMPLANT.3 : Minimum width of nimplant/ pimplant : 45nm") +implant.space(45.nm, euclidian).output("IMPLANT.4", "IMPLANT.4 : Minimum spacing of nimplant/ pimplant : 45nm") +nplus.and(pplus).output("IMPLANT.5", "IMPLANT.5 : Nimplant and pimplant must not overlap") +implant.forget + +# Contact +cont.edges.without_length(65.nm).output("CONTACT.1", "CONTACT.1 : Minimum/Maximum width of contact : 65nm") +cont.space(75.nm, euclidian).output("CONTACT.2", "CONTACT.2 : Minimum spacing of contact : 75nm") +cont.not(active).not(poly).not(metal1).output("CONTACT.3", "CONTACT.3 : contact must be inside active or poly or metal1") +active.enclosing(cont, 5.nm, euclidian).output("CONTACT.4", "CONTACT.4 : Minimum enclosure of active around contact : 5nm") +poly.enclosing(cont, 5.nm, euclidian).output("CONTACT.5", "CONTACT.5 : Minimum enclosure of poly around contact : 5nm") +cont.separation(poly, 35.nm, euclidian).output("CONTACT.6", "CONTACT.6 : Minimum spacing of contact and poly : 35nm") + +# Metal1 +metal1.width(65.nm, euclidian).output("METAL1.1", "METAL1.1 : Minimum width of metal1 : 65nm") +metal1.space(65.nm, euclidian).output("METAL1.2", "METAL1.2 : Minimum spacing of metal1 : 65nm") +cont_edges_with_less_enclosure = metal1.enclosing(cont, 35.nm, projection).second_edges +error_corners = cont_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +cont_edges_with_less_enclosure.forget +cont.interacting(error_corners.polygons(1.dbu)).output("METAL1.3", "METAL1.3 : Minimum enclosure around contact on two opposite sides : 35nm") +error_corners.forget +via1_edges_with_less_enclosure = metal1.enclosing(via1, 35.nm, projection).second_edges +error_corners = via1_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via1_edges_with_less_enclosure.forget +via1.interacting(error_corners.polygons(1.dbu)).output("METAL1.4", "METAL1.4 : Minimum enclosure around via1 on two opposite sides : 35nm") +error_corners.forget +metal1_gt90, metal1_gt270, metal1_gt500, metal1_gt900, metal1_gt1500 = classify_by_width(metal1, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm) +metal1_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL1.5", "METAL1.5 : Minimum spacing of metal1 wider than 90 nm and longer than 300 nm : 90nm") +metal1_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL1.6", "METAL1.6 : Minimum spacing of metal1 wider than 270 nm and longer than 900 nm : 270nm") +metal1_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL1.7", "METAL1.7 : Minimum spacing of metal1 wider than 500 nm and longer than 1.8 um : 500nm") +metal1_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL1.8", "METAL1.8 : Minimum spacing of metal1 wider than 900 nm and longer than 2.7 um : 900nm") +metal1_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL1.9", "METAL1.9 : Minimum spacing of metal1 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal1_gt90, metal1_gt270, metal1_gt500, metal1_gt900, metal1_gt1500 ].each { |l| l.forget } + +# Via1 +via1.edges.without_length(65.nm).output("VIA1.1", "VIA1.1 : Minimum/Maximum width of via1 : 65nm") +via1.space(75.nm, euclidian).output("VIA1.2", "VIA1.2 : Minimum spacing of via1 : 75nm") +via1.not(metal1).output("VIA1.3", "VIA1.3 : via1 must be inside metal1") +via1.not(metal2).output("VIA1.4", "VIA1.4 : via1 must be inside metal2") + +# metal2 +metal2.width(70.nm, euclidian).output("METAL2.1", "METAL2.1 : Minimum width of intermediate metal2 : 70nm") +metal2.space(70.nm, euclidian).output("METAL2.2", "METAL2.2 : Minimum spacing of intermediate metal2 : 70nm") +via1_edges_with_less_enclosure = metal2.enclosing(via1, 35.nm, projection).second_edges +error_corners = via1_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via1_edges_with_less_enclosure.forget +via1.interacting(error_corners.polygons(1.dbu)).output("METAL2.3", "METAL2.3 : Minimum enclosure around via1 on two opposite sides : 35nm") +error_corners.forget +via2_edges_with_less_enclosure = metal2.enclosing(via2, 35.nm, projection).second_edges +error_corners = via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via2_edges_with_less_enclosure.forget +via2.interacting(error_corners.polygons(1.dbu)).output("METAL2.4", "METAL2.4 : Minimum enclosure around via2 on two opposite sides : 35nm") +error_corners.forget +metal2_gt90, metal2_gt270, metal2_gt500, metal2_gt900, metal2_gt1500 = classify_by_width(metal2, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm) +metal2_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL2.5", "METAL2.5 : Minimum spacing of intermediate metal2 wider than 90 nm and longer than 300 nm : 90nm") +metal2_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL2.6", "METAL2.6 : Minimum spacing of intermediate metal2 wider than 270 nm and longer than 900 nm : 270nm") +metal2_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL2.7", "METAL2.7 : Minimum spacing of intermediate metal2 wider than 500 nm and longer than 1.8 um : 500nm") +metal2_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL2.8", "METAL2.8 : Minimum spacing of intermediate metal2 wider than 900 nm and longer than 2.7 um : 900nm") +metal2_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL2.9", "METAL2.9 : Minimum spacing of intermediate metal2 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal2_gt90, metal2_gt270, metal2_gt500, metal2_gt900, metal2_gt1500 ].each { |l| l.forget } + +# via2 +# FreePDK Calibre deck incorrectly has this as 65nm so we are going to be compatible. +via2.edges.without_length(65.nm).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 65nm") +via2.space(85.nm, euclidian).output("VIA2.2", "VIA2.2 : Minimum spacing of via2 : 85nm") +via2.not(metal2).output("VIA2.3", "VIA2.3 : via2 must be inside metal2") +via2.not(metal3).output("VIA2.4", "VIA2.4 : via2 must be inside metal3") + +# metal3 +metal3.width(70.nm, euclidian).output("METAL3.1", "METAL3.1 : Minimum width of intermediate metal3 : 70nm") +metal3.space(70.nm, euclidian).output("METAL3.2", "METAL3.2 : Minimum spacing of intermediate metal3 : 70nm") +via2_edges_with_less_enclosure = metal3.enclosing(via2, 35.nm, projection).second_edges +error_corners = via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via2_edges_with_less_enclosure.forget +via2.interacting(error_corners.polygons(1.dbu)).output("METAL3.3", "METAL3.3 : Minimum enclosure around via2 on two opposite sides : 35nm") +error_corners.forget +via3_edges_with_less_enclosure = metal3.enclosing(via3, 35.nm, projection).second_edges +error_corners = via3_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via3_edges_with_less_enclosure.forget +via3.interacting(error_corners.polygons(1.dbu)).output("METAL3.4", "METAL3.4 : Minimum enclosure around via3 on two opposite sides : 35nm") +error_corners.forget +metal3_gt90, metal3_gt270, metal3_gt500, metal3_gt900, metal3_gt1500 = classify_by_width(metal3, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm) +metal3_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL3.5", "METAL3.5 : Minimum spacing of intermediate metal3 wider than 90 nm and longer than 300 nm : 90nm") +metal3_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL3.6", "METAL3.6 : Minimum spacing of intermediate metal3 wider than 270 nm and longer than 900 nm : 270nm") +metal3_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL3.7", "METAL3.7 : Minimum spacing of intermediate metal3 wider than 500 nm and longer than 1.8 um : 500nm") +metal3_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL3.8", "METAL3.8 : Minimum spacing of intermediate metal3 wider than 900 nm and longer than 2.7 um : 900nm") +metal3_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL3.9", "METAL3.9 : Minimum spacing of intermediate metal3 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal3_gt90, metal3_gt270, metal3_gt500, metal3_gt900, metal3_gt1500 ].each { |l| l.forget } + +# via3 +via3.edges.without_length(70.nm).output("VIA3.1", "VIA3.1 : Minimum/Maximum width of via3 : 70nm") +via3.space(85.nm, euclidian).output("VIA3.2", "VIA3.2 : Minimum spacing of via3 : 85nm") +via3.not(metal3).output("VIA3.3", "VIA3.3 : via3 must be inside metal3") +via3.not(metal4).output("VIA3.4", "VIA3.4 : via3 must be inside metal4") + +# metal4 +metal4.width(140.nm, euclidian).output("METAL4.1", "METAL4.1 : Minimum width of semi-global metal4 : 140nm") +metal4.space(140.nm, euclidian).output("METAL4.2", "METAL4.2 : Minimum spacing of semi-global metal4 : 140nm") +metal4_gt270, metal4_gt500, metal4_gt900 = classify_by_width(metal4, 270.nm, 500.nm, 900.nm) +metal4_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL4.6", "METAL4.6 : Minimum spacing of semi-global metal4 wider than 270 nm and longer than 900 nm : 270nm") +metal4_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL4.7", "METAL4.7 : Minimum spacing of semi-global metal4 wider than 500 nm and longer than 1.8 um : 500nm") +metal4_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL4.8", "METAL4.8 : Minimum spacing of semi-global meta4l wider than 900 nm and longer than 2.7 um : 900nm") +[ metal4_gt270, metal4_gt500, metal4_gt900 ].each { |l| l.forget } + +# via4 +via4.edges.without_length(140.nm).output("VIA4.1", "VIA4.1 : Minimum/Maximum width of via4 : 140nm") +via4.space(160.nm, euclidian).output("VIA4.2", "VIA4.2 : Minimum spacing of via4 : 160nm") +via4.not(metal4).output("VIA4.3", "VIA4.3 : via4 must be inside metal4") +via4.not(metal5).output("VIA4.4", "VIA4.4 : via4 must be inside metal5") + +# metal5 +metal5.width(140.nm, euclidian).output("METAL5.1", "METAL5.1 : Minimum width of semi-global metal5 : 140nm") +metal5.space(140.nm, euclidian).output("METAL5.2", "METAL5.2 : Minimum spacing of semi-global metal5 : 140nm") +metal5_gt270, metal5_gt500, metal5_gt900 = classify_by_width(metal5, 270.nm, 500.nm, 900.nm) +metal5_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL5.6", "METAL5.6 : Minimum spacing of semi-global metal5 wider than 270 nm and longer than 900 nm : 270nm") +metal5_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL5.7", "METAL5.7 : Minimum spacing of semi-global metal5 wider than 500 nm and longer than 1.8 um : 500nm") +metal5_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL5.8", "METAL5.8 : Minimum spacing of semi-global meta5l wider than 900 nm and longer than 2.7 um : 900nm") +[ metal5_gt270, metal5_gt500, metal5_gt900 ].each { |l| l.forget } + +# via5 +via5.edges.without_length(140.nm).output("VIA5.1", "VIA5.1 : Minimum/Maximum width of via5 : 140nm") +via5.space(160.nm, euclidian).output("VIA5.2", "VIA5.2 : Minimum spacing of via5 : 160nm") +via5.not(metal5).output("VIA5.3", "VIA5.3 : via5 must be inside metal5") +via5.not(metal6).output("VIA5.4", "VIA5.4 : via5 must be inside metal6") + +# metal6 +metal6.width(140.nm, euclidian).output("METAL6.1", "METAL6.1 : Minimum width of semi-global metal6 : 140nm") +metal6.space(140.nm, euclidian).output("METAL6.2", "METAL6.2 : Minimum spacing of semi-global metal6 : 140nm") +metal6_gt270, metal6_gt500, metal6_gt900 = classify_by_width(metal6, 270.nm, 500.nm, 900.nm) +metal6_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL6.6", "METAL6.6 : Minimum spacing of semi-global metal6 wider than 270 nm and longer than 900 nm : 270nm") +metal6_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL6.7", "METAL6.7 : Minimum spacing of semi-global metal6 wider than 500 nm and longer than 1.8 um : 500nm") +metal6_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL6.8", "METAL6.8 : Minimum spacing of semi-global metal6 wider than 900 nm and longer than 2.7 um : 900nm") +[ metal6_gt270, metal6_gt500, metal6_gt900 ].each { |l| l.forget } + +# via6 +via6.edges.without_length(140.nm).output("VIA6.1", "VIA6.1 : Minimum/Maximum width of via6 : 140nm") +via6.space(160.nm, euclidian).output("VIA6.2", "VIA6.2 : Minimum spacing of via6 : 160nm") +via6.not(metal6).output("VIA6.3", "VIA6.3 : via6 must be inside metal6") +via6.not(metal7).output("VIA6.4", "VIA6.4 : via6 must be inside metal7") + +# metal7 +metal7.width(400.nm, euclidian).output("METAL7.1", "METAL7.1 : Minimum width of thin global metal7 : 400nm") +metal7.space(400.nm, euclidian).output("METAL7.2", "METAL7.2 : Minimum spacing of thin global metal7 : 400nm") +metal7_gt500, metal7_gt900, metal7_gt1500 = classify_by_width(metal7, 500.nm, 900.nm, 1500.nm) +metal7_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL7.7", "METAL7.7 : Minimum spacing of thin global metal7 wider than 500 nm and longer than 1.8 um : 500nm") +metal7_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL7.8", "METAL7.8 : Minimum spacing of thin global metal7 wider than 900 nm and longer than 2.7 um : 900nm") +metal7_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL7.9", "METAL7.9 : Minimum spacing of thin global meta7l wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal7_gt500, metal7_gt900, metal7_gt1500 ].each { |l| l.forget } + +# via7 +via7.edges.without_length(400.nm).output("VIA6.1", "VIA6.1 : Minimum/Maximum width of via7 : 400nm") +via7.space(440.nm, euclidian).output("VIA6.2", "VIA6.2 : Minimum spacing of via7 : 440nm") +via7.not(metal7).output("VIA7.3", "VIA7.3 : via7 must be inside metal7") +via7.not(metal8).output("VIA7.4", "VIA7.4 : via7 must be inside metal8") + +# metal8 +metal8.width(400.nm, euclidian).output("METAL8.1", "METAL8.1 : Minimum width of thin global metal8 : 400nm") +metal8.space(400.nm, euclidian).output("METAL8.2", "METAL8.2 : Minimum spacing of thin global metal8 : 400nm") +metal8_gt500, metal8_gt900, metal8_gt1500 = classify_by_width(metal8, 500.nm, 900.nm, 1500.nm) +metal8_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL8.7", "METAL8.7 : Minimum spacing of thin global metal8 wider than 500 nm and longer than 1.8 um : 500nm") +metal8_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL8.8", "METAL8.8 : Minimum spacing of thin global metal8 wider than 900 nm and longer than 2.7 um : 900nm") +metal8_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL8.9", "METAL8.9 : Minimum spacing of thin global metal8 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal8_gt500, metal8_gt900, metal8_gt1500 ].each { |l| l.forget } + +# via8 +via8.edges.without_length(400.nm).output("VIA8.1", "VIA8.1 : Minimum/Maximum width of via8 : 400nm") +via8.space(440.nm, euclidian).output("VIA8.2", "VIA8.2 : Minimum spacing of via8 : 440nm") +via8.not(metal8).output("VIA8.3", "VIA8.3 : via8 must be inside metal8") +via8.not(metal9).output("VIA8.4", "VIA8.4 : via8 must be inside metal9") + +# metal9 +metal9.width(800.nm, euclidian).output("METAL9.1", "METAL9.1 : Minimum width of global metal9 : 800nm") +metal9.space(800.nm, euclidian).output("METAL9.2", "METAL9.2 : Minimum spacing of global metal9 : 800nm") +metal9_gt500, metal9_gt900, metal9_gt1500 = classify_by_width(metal9, 500.nm, 900.nm, 1500.nm) +metal9_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL9.7", "METAL9.7 : Minimum spacing of global metal9 wider than 500 nm and longer than 1.8 um : 500nm") +metal9_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL9.8", "METAL9.8 : Minimum spacing of global metal9 wider than 900 nm and longer than 2.7 um : 900nm") +metal9_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL9.9", "METAL9.9 : Minimum spacing of global metal9 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal9_gt500, metal9_gt900, metal9_gt1500 ].each { |l| l.forget } + +# via9 +via9.edges.without_length(800.nm).output("VIA9.1", "VIA9.1 : Minimum/Maximum width of via9 : 800nm") +via9.space(880.nm, euclidian).output("VIA9.2", "VIA9.2 : Minimum spacing of via9 : 880nm") +via9.not(metal9).output("VIA9.3", "VIA9.3 : via9 must be inside metal9") +via9.not(metal10).output("VIA9.4", "VIA9.4 : via9 must be inside metal10") + +# metal10 +metal10.width(800.nm, euclidian).output("METAL10.1", "METAL10.1 : Minimum width of global metal10 : 800nm") +metal10.space(800.nm, euclidian).output("METAL10.2", "METAL10.2 : Minimum spacing of global metal10 : 800nm") +metal10_gt500, metal10_gt900, metal10_gt1500 = classify_by_width(metal10, 500.nm, 900.nm, 1500.nm) +metal10_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL10.7", "METAL10.7 : Minimum spacing of global metal10 wider than 500 nm and longer than 1.8 um : 500nm") +metal10_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL10.8", "METAL10.8 : Minimum spacing of global metal10 wider than 900 nm and longer than 2.7 um : 900nm") +metal10_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL10.9", "METAL10.9 : Minimum spacing of global metal10 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal10_gt500, metal10_gt900, metal10_gt1500 ].each { |l| l.forget } + +end + + +# ONGRID also defined in : +# https://www.eda.ncsu.edu/wiki/FreePDK45:Manufacturing_Grid +########################################### +if OFFGRID +info("GRID section") + +grid = 2.5.nm +all_drawing = [ :well, :active, :vtg, :vth, :pplus, :nplus, :poly, :thkox, :cont, :metal1, :via1, :metal2, :via2, :metal3, :via3, :metal4, :via4, :metal5, :via5, :metal6, :via6, :metal7, :via7, :metal8, :via8, :metal9, :via9, :metal10 ] +all_drawing.each do |dwg| + # a Ruby idiom to get the value of a variable whose name is in "dwg" (as symbol) + layer = binding.local_variable_get(dwg) + r_grid = layer.ongrid(grid).polygons(10.nm) + r_grid.output("GRID: vertexes on layer #{dwg} not on grid of #{'%.12g' % grid}") +end +end + +# ANTENNA checks +################ +if ANTENNA +info("ANTENNA section") + +diode = nplus & active - nwell # diode recognition layer + +# build connction of poly+gate to metal1 +connect(gate, poly) +connect(poly, cont) +connect(diode, cont) +connect(cont, metal1) + +antenna_check(gate, metal1, 300.0, diode).output("METAL1_ANTENNA", "METAL1_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal2 +connect(metal1, via1) +connect(via1, metal2) + +antenna_check(gate, metal2, 300.0, diode).output("METAL2_ANTENNA", "METAL2_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal3 +connect(metal2, via2) +connect(via2, metal3) + +antenna_check(gate, metal3, 300.0, diode).output("METAL3_ANTENNA", "METAL3_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal4 +connect(metal3, via3) +connect(via3, metal4) + +antenna_check(gate, metal4, 300.0, diode).output("METAL4_ANTENNA", "METAL4_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal5 +connect(metal4, via4) +connect(via4, metal5) + +antenna_check(gate, metal5, 300.0, diode).output("METAL5_ANTENNA", "METAL5_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal6 +connect(metal5, via5) +connect(via5, metal6) + +antenna_check(gate, metal6, 300.0, diode).output("METAL6_ANTENNA", "METAL6_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal7 +connect(metal6, via6) +connect(via6, metal7) + +antenna_check(gate, metal7, 300.0, diode).output("METAL7_ANTENNA", "METAL7_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal8 +connect(metal7, via7) +connect(via7, metal8) + +antenna_check(gate, metal8, 300.0, diode).output("METAL8_ANTENNA", "METAL8_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal9 +connect(metal8, via8) +connect(via8, metal9) + +antenna_check(gate, metal9, 300.0, diode).output("METAL9_ANTENNA", "METAL9_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal10 +connect(metal9, via9) +connect(via9, metal10) + +antenna_check(gate, metal10, 300.0, diode).output("METAL10_ANTENNA", "METAL10_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +end + +# time spent for the DRC +time = Time.now +hours = ((time - tstart)/3600).to_i +minutes = ((time - tstart)/60 - hours * 60).to_i +seconds = ((time - tstart) - (minutes * 60 + hours * 3600)).to_i +$stdout.write "DRC finished at : #{time.hour}:#{time.min}:#{time.sec} - DRC duration = #{hours} hrs. #{minutes} min. #{seconds} sec.\n" + + diff --git a/technology/freepdk45/tech/freepdk45.lylvs b/technology/freepdk45/tech/freepdk45.lylvs new file mode 100644 index 00000000..5d8e50b3 --- /dev/null +++ b/technology/freepdk45/tech/freepdk45.lylvs @@ -0,0 +1,269 @@ + + + + + lvs + + + + false + false + + true + lvs_scripts + tools_menu.lvs.end + dsl + lvs-dsl-xml + # +# Extraction for freePDK45 +# +############################ +tstart = Time.now + +# optionnal for a batch launch : klayout -b -rd input=my_layout.gds -rd report=my_report.lyrdb -rd schematic=reference_netlist.cir -rd target_netlist=extracted_netlist.cir -r lvs_freepdk45.lvs +if $input + source($input) +end + +if $report + report_lvs($report) +else + report_lvs("lvs_report.lvsdb") +end + +if $schematic +#reference netlist + schematic($schematic) +else + schematic(RBA::CellView::active.filename.sub(/\.(oas|gds|oas.gz|gds.gz)$/, ".sp")) +end + +# true: use net names instead of numbers +# false: use numbers for nets +spice_with_net_names = true + +# true: put in comments with details +# false: no comments +spice_with_comments = true + +if $target_netlist + target_netlist($target_netlist) +else + # target_netlist("netlist.cir", write_spice(spice_with_net_names, spice_with_comments), "The netlist comment goes here.") + target_netlist(File.join(File.dirname(RBA::CellView::active.filename), source.cell_name+"_extracted.cir"), write_spice(spice_with_net_names, spice_with_comments), "Extracted by KLayout on : #{Time.now.strftime("%d/%m/%Y %H:%M")}") +end + +# Hierarchical mode +deep +# Use 4 CPU cores +threads(4) +# Print details +verbose(true) + + +# layers definitions +######################## +active = input(1, 0) +pwell = input(2, 0) +nwell = input(3, 0) +nplus = input(4, 0) +pplus = input(5, 0) +vtg = input(6, 0) +vth = input(7, 0) +thkox = input(8, 0) +poly = input(9, 0) +cont = input(10, 0) +metal1 = input(11, 0) +metal1_lbl = input(11, 1) +metal1_pin = input(11, 2) +via1 = input(12, 0) +metal2 = input(13, 0) +metal2_lbl = input(13, 1) +metal2_pin = input(13, 2) +via2 = input(14, 0) +metal3 = input(15, 0) +metal3_lbl = input(15, 1) +metal3_pin = input(15, 2) +via3 = input(16, 0) +metal4 = input(17, 0) +metal4_lbl = input(17, 1) +metal4_pin = input(17, 2) +via4 = input(18, 0) +metal5 = input(19, 0) +metal5_lbl = input(19, 1) +metal5_pin = input(19, 2) +via5 = input(20, 0) +metal6 = input(21, 0) +metal6_lbl = input(21, 1) +metal6_pin = input(21, 2) +via6 = input(22, 0) +metal7 = input(23, 0) +metal7_lbl = input(23, 1) +metal7_pin = input(23, 2) +via7 = input(24, 0) +metal8 = input(25, 0) +metal8_lbl = input(25, 1) +metal8_pin = input(25, 2) +via8 = input(26, 0) +metal9 = input(27, 0) +metal9_lbl = input(27, 1) +metal9_pin = input(27, 2) +via9 = input(28, 0) +metal10 = input(29, 0) +metal10_lbl = input(29, 1) +metal10_pin = input(29, 2) + +# Bulk layer for terminal provisioning +bulk = polygon_layer + +# Computed layers +active_in_nwell = active & nwell +pactive = active_in_nwell & pplus +ntie = active_in_nwell & nplus +pgate = pactive & poly +psd = pactive - pgate +lv_pgate = pgate - vtg - thkox +gv_pgate = pgate & vtg - vth - thkox +hv_pgate = pgate - vtg - vth & thkox + +active_in_pwell = active & pwell +nactive = active_in_pwell & nplus +ptie = active_in_pwell & pplus +ngate = nactive & poly +nsd = nactive - ngate +lv_ngate = ngate - vtg - thkox +gv_ngate = ngate & vtg - vth - thkox +hv_ngate = ngate - vtg - vth & thkox + +cheat("cell_6t", "dummy_cell_6t", "cell_1rw", "dummy_cell_1rw", "cell_2rw", "dummy_cell_2rw", "dff","wordline_driver_0") { + +# PMOS transistor device extraction +extract_devices(mos4("PMOS_VTL"), { "SD" => psd, "G" => lv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell }) +extract_devices(mos4("PMOS_VTG"), { "SD" => psd, "G" => gv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell }) +extract_devices(mos4("PMOS_VTH"), { "SD" => psd, "G" => hv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell }) + +# NMOS transistor device extraction +extract_devices(mos4("NMOS_VTL"), { "SD" => nsd, "G" => lv_ngate, "tS" => nsd, "tD" => nsd, "tG" => poly, "W" => pwell }) +extract_devices(mos4("NMOS_VTG"), { "SD" => nsd, "G" => gv_ngate, "tS" => nsd, "tD" => nsd, "tG" => poly, "W" => pwell }) +extract_devices(mos4("NMOS_VTH"), { "SD" => nsd, "G" => hv_ngate, "tS" => nsd, "tD" => nsd, "tG" => poly, "W" => pwell }) + +} + +# Define connectivity for netlist extraction + +# Inter-layer +connect(nwell, ntie) +connect(pwell, ptie) +connect(cont, ntie) +connect(cont, ptie) +connect(psd, cont) +connect(nsd, cont) +connect(poly, cont) +connect(cont, metal1) +connect(cont, metal1) +connect(metal1, via1) +connect(via1, metal2) +connect(metal2, via2) +connect(via2, metal3) +connect(metal3, via3) +connect(via3, metal4) +connect(metal4, via4) +connect(via4, metal5) +connect(metal5, via5) +connect(via5, metal6) +connect(metal6, via6) +connect(via6, metal7) +connect(metal7, via7) +connect(via7, metal8) +connect(metal8, via8) +connect(via8, metal9) +connect(metal9, via9) +connect(via9, metal10) +# attach labels : +connect(metal1, metal1_lbl) +connect(metal1, metal1_pin) +connect(metal2, metal2_lbl) +connect(metal2, metal2_pin) +connect(metal3, metal3_lbl) +connect(metal3, metal3_pin) +connect(metal4, metal4_lbl) +connect(metal4, metal4_pin) +connect(metal5, metal5_lbl) +connect(metal5, metal5_pin) +connect(metal6, metal6_lbl) +connect(metal6, metal6_pin) +connect(metal7, metal7_lbl) +connect(metal7, metal7_pin) +connect(metal8, metal8_lbl) +connect(metal8, metal8_pin) +connect(metal9, metal9_lbl) +connect(metal9, metal9_pin) +connect(metal10, metal10_lbl) +connect(metal10, metal10_pin) + + +# Global +schematic.simplify + +connect_global(pwell, "PWELL") +connect_global(nwell, "NWELL") +connect_global(bulk, "BULK") + +#for pat in %w(pnand*_0 and2_dec_0 port_address* replica_bitcell_array) +# connect_explicit(pat, [ "NWELL", "vdd" ]) +# connect_explicit(pat, [ "BULK", "PWELL", "gnd" ]) +#end + +#for pat in %w(XOR* XNOR* TLAT* TINV* TBUF* SDFF* OR* OAI* NOR* NAND* MUX* LOGIC* INV* HA* FILLCELL* +# FA* DLL* DLH* DFF* DFFS* DFFR* DFFRS* CLKGATE* CLKBUF* BUF* AOI* ANTENNA* AND*) +# connect_explicit(pat, [ "NWELL", "VDD" ]) +# connect_explicit(pat, [ "BULK", "VSS" ]) +#end + +# Actually performs the extraction +netlist # ... not really required + +# Flatten cells which are present in one netlist only +align +# SIMPLIFICATION of the netlist +#netlist.make_top_level_pins +#netlist.combine_devices +#netlist.purge +#netlist.purge_nets +netlist.simplify + +# Tolerances for the devices extracted parameters +# tolerance(device_class_name, parameter_name [, :absolute => absolute_tolerance] [, :relative => relative_tolerance]) +tolerance("PMOS_LVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_LVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_GVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_GVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_HVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_HVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_LVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_LVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_GVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_GVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_HVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_HVT", "L", :absolute => 1.nm, :relative => 0.001) + +#max_res(1000000) +#min_caps(1e-15) + +max_branch_complexity(65536) +max_depth(16) + +if ! compare + #raise "ERROR : Netlists don't match" + puts "ERROR : Netlists don't match" +else + puts "CONGRATULATIONS! Netlists match." +end + +# time spent for the LVS +time = Time.now +hours = ((time - tstart)/3600).to_i +minutes = ((time - tstart)/60 - hours * 60).to_i +seconds = ((time - tstart) - (minutes * 60 + hours * 3600)).to_i +$stdout.write "LVS finished at : #{time.hour}:#{time.min}:#{time.sec} - LVS duration = #{hours} hrs. #{minutes} min. #{seconds} sec.\n" + diff --git a/technology/freepdk45/tf/FreePDK45.lyp b/technology/freepdk45/tech/freepdk45.lyp similarity index 72% rename from technology/freepdk45/tf/FreePDK45.lyp rename to technology/freepdk45/tech/freepdk45.lyp index 29e8e32f..4823c15a 100644 --- a/technology/freepdk45/tf/FreePDK45.lyp +++ b/technology/freepdk45/tech/freepdk45.lyp @@ -5,7 +5,7 @@ #ff8000 0 0 - C36 + C35 C0 true true @@ -22,7 +22,7 @@ #00cc66 0 0 - C36 + C35 C0 true true @@ -39,7 +39,7 @@ #0000ff 0 0 - C41 + C40 C1 true true @@ -56,7 +56,7 @@ #0000ff 0 0 - C42 + C41 C1 true true @@ -73,7 +73,7 @@ #00cc66 0 0 - C34 + C33 C0 true true @@ -85,6 +85,23 @@ active.drawing - 1/0 1/0@1 + + #00cc66 + #00cc66 + 0 + 0 + C34 + C0 + false + true + false + 1 + false + false + 0 + active.blockage - 1/1 + 1/1@1 + #00cc66 #00cc66 @@ -124,7 +141,7 @@ #ff0000 0 0 - C37 + C36 C0 true true @@ -136,12 +153,29 @@ poly.drawing - 9/0 9/0@1 + + #ff0000 + #ff0000 + 0 + 0 + C37 + C0 + false + true + false + 1 + false + false + 0 + poly.blockage - 9/1 + 9/1@1 + #ffff00 #ffff00 0 0 - C43 + C42 C0 true true @@ -154,21 +188,38 @@ 8/0@1 - #00cc66 - #00cc66 + #00ff00 + #000000 0 0 - C1 + I1 C0 true true false + 2 + false + true + 0 + contact.drawing - 10/0 + 10/0@1 + + + #00ff00 + #000000 + 0 + 0 + I1 + C0 + false + true + false 1 false false 0 - contact.drawing - 10/0 - 10/0@1 + contact.blockage - 10/1 + 10/1@1 #0000ff @@ -188,22 +239,73 @@ 11/0@1 - #333399 - #ff00ff + #0000ff + #0000ff 0 0 - C39 + C12 C0 - true + false true false 1 false false 0 + metal1.blockage - 11/1 + 11/1@1 + + + #0000ff + #0000ff + 0 + 0 + C12 + C0 + true + true + false + 3 + false + false + 0 + metal1.pin - 11/2 + 11/2@1 + + + #333399 + #ff00ff + 0 + 0 + C38 + C0 + true + true + false + 1 + false + true + 0 via1.drawing - 12/0 12/0@1 + + #333399 + #ff00ff + 0 + 0 + C38 + C0 + false + true + false + 1 + false + false + 0 + via1.blockage - 12/1 + 12/1@1 + #ff00ff #ff00ff @@ -222,22 +324,73 @@ 13/0@1 - #39bfff - #39bfff + #ff00ff + #ff00ff 0 0 - C39 + C2 C0 - true + false true false 1 false false 0 + metal2.blockage - 13/1 + 13/1@1 + + + #ff00ff + #ff00ff + 0 + 0 + C2 + C0 + true + true + false + 3 + false + false + 0 + metal2.pin - 13/2 + 13/2@1 + + + #39bfff + #39bfff + 0 + 0 + C38 + C0 + true + true + false + 1 + false + true + 0 via2.drawing - 14/0 14/0@1 + + #39bfff + #39bfff + 0 + 0 + C38 + C0 + false + true + false + 1 + false + false + 0 + via2.blockage - 14/1 + 14/1@1 + #00ffff #00ffff @@ -256,28 +409,79 @@ 15/0@1 - #ffe6bf - #ffe6bf + #00ffff + #00ffff 0 0 - C39 + C11 C0 - true + false true false 1 false false 0 + metal3.blockage - 15/1 + 15/1@1 + + + #00ffff + #00ffff + 0 + 0 + C11 + C0 + true + true + false + 3 + false + false + 0 + metal3.pin - 15/2 + 15/2@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C38 + C0 + true + true + false + 1 + false + true + 0 via3.drawing - 16/0 16/0@1 + + #ffe6bf + #ffe6bf + 0 + 0 + C38 + C0 + false + true + false + 1 + false + false + 0 + via3.blockage - 16/1 + 16/1@1 + #ffffcc #ffffcc 0 0 - C25 + C24 C0 true true @@ -290,28 +494,79 @@ 17/0@1 - #0000ff - #0000ff + #ffffcc + #ffffcc 0 0 - C39 + C24 C0 - true + false true false 1 false false 0 + metal4.blockage - 17/1 + 17/1@1 + + + #ffffcc + #ffffcc + 0 + 0 + C24 + C0 + true + true + false + 3 + false + false + 0 + metal4.pin - 17/2 + 17/2@1 + + + #0000ff + #0000ff + 0 + 0 + C38 + C0 + true + true + false + 1 + false + true + 0 via4.drawing - 18/0 18/0@1 + + #0000ff + #0000ff + 0 + 0 + C38 + C0 + false + true + false + 1 + false + false + 0 + via4.blockage - 18/1 + 18/1@1 + #39bfff #39bfff 0 0 - C29 + C28 C0 true true @@ -324,22 +579,73 @@ 19/0@1 - #ffff00 - #ffff00 + #39bfff + #39bfff 0 0 - C39 + C28 C0 - true + false true false 1 false false 0 + metal5.blockage - 19/1 + 19/1@1 + + + #39bfff + #39bfff + 0 + 0 + C28 + C0 + true + true + false + 3 + false + false + 0 + metal5.pin - 19/2 + 19/2@1 + + + #ffff00 + #ffff00 + 0 + 0 + C38 + C0 + true + true + false + 1 + false + true + 0 via5.drawing - 20/0 20/0@1 + + #ffff00 + #ffff00 + 0 + 0 + C38 + C0 + false + true + false + 1 + false + false + 0 + via5.blockage - 20/1 + 20/1@1 + #d9cc00 #d9cc00 @@ -358,22 +664,73 @@ 21/0@1 - #ff00ff - #ff00ff + #d9cc00 + #d9cc00 0 0 - C39 + C8 C0 - true + false true false 1 false false 0 + metal6.blockage - 21/1 + 21/1@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C8 + C0 + true + true + false + 3 + false + false + 0 + metal6.pin - 21/2 + 21/2@1 + + + #ff00ff + #ff00ff + 0 + 0 + C38 + C0 + true + true + false + 1 + false + true + 0 via6.drawing - 22/0 22/0@1 + + #ff00ff + #ff00ff + 0 + 0 + C38 + C0 + false + true + false + 1 + false + false + 0 + via6.blockage - 22/1 + 22/1@1 + #00ff00 #00ff00 @@ -392,22 +749,73 @@ 23/0@1 - #39bfff - #39bfff + #00ff00 + #00ff00 0 0 - C39 + C11 C0 - true + false true false 1 false false 0 + metal7.blockage - 23/1 + 23/1@1 + + + #00ff00 + #00ff00 + 0 + 0 + C11 + C0 + true + true + false + 3 + false + false + 0 + metal7.pin - 23/2 + 23/2@1 + + + #39bfff + #39bfff + 0 + 0 + C38 + C0 + true + true + false + 1 + false + true + 0 via7.drawing - 24/0 24/0@1 + + #39bfff + #39bfff + 0 + 0 + C38 + C0 + false + true + false + 1 + false + false + 0 + via7.blockage - 24/1 + 24/1@1 + #ffffff #ffffff @@ -426,22 +834,73 @@ 25/0@1 - #ffffcc - #ffffcc + #ffffff + #ffffff 0 0 - C39 + C4 C0 - true + false true false 1 false false 0 + metal8.blockage - 25/1 + 25/1@1 + + + #ffffff + #ffffff + 0 + 0 + C4 + C0 + true + true + false + 3 + false + false + 0 + metal8.pin - 25/2 + 25/2@1 + + + #ffffcc + #ffffcc + 0 + 0 + C38 + C0 + true + true + false + 1 + false + true + 0 via8.drawing - 26/0 26/0@1 + + #ffffcc + #ffffcc + 0 + 0 + C38 + C0 + false + true + false + 1 + false + false + 0 + via8.blockage - 26/1 + 26/1@1 + #ffe6bf #ffe6bf @@ -460,28 +919,79 @@ 27/0@1 - #0000ff - #0000ff + #ffe6bf + #ffe6bf 0 0 - C39 + C6 C0 - true + false true false 1 false false 0 + metal9.blockage - 27/1 + 27/1@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C6 + C0 + true + true + false + 3 + false + false + 0 + metal9.pin - 27/2 + 27/2@1 + + + #0000ff + #0000ff + 0 + 0 + C38 + C0 + true + true + false + 1 + false + true + 0 via9.drawing - 28/0 28/0@1 + + #0000ff + #0000ff + 0 + 0 + C38 + C0 + false + true + false + 1 + false + false + 0 + via9.blockage - 28/1 + 28/1@1 + #ff8000 #ff8000 0 0 - C29 + C28 C0 true true @@ -493,6 +1003,57 @@ metal10.drawing - 29/0 29/0@1 + + #ff8000 + #ff8000 + 0 + 0 + C28 + C0 + false + true + false + 1 + false + false + 0 + metal10.blockage - 29/1 + 29/1@1 + + + #ff8000 + #ff8000 + 0 + 0 + C28 + C0 + true + true + false + 3 + false + false + 0 + metal10.pin - 29/2 + 29/2@1 + + + #ffff00 + #ffff00 + 0 + 0 + C0 + + true + true + false + 1 + false + false + 0 + text.drawing - 230/0 + 230/0@1 + #9900e6 #9900e6 @@ -510,6 +1071,142 @@ comment.drawing - 239/0 239/0@1 + + #01ff6b + #01ff6b + 0 + 0 + I3 + I6 + true + true + false + 1 + false + false + 0 + phot_silicon.drawing + 400/0@1 + + + #808080 + #808080 + 0 + 0 + I2 + I0 + true + true + false + 1 + false + false + 0 + phot_poly.drawing + 410/0@1 + + + #ff0000 + #ff0000 + 0 + 0 + I9 + + true + true + false + 1 + false + false + 0 + phot_pwell.drawing + 420/0@1 + + + #0000ff + #0000ff + 0 + 0 + I5 + + true + true + false + 1 + false + false + 0 + phot_nwell.drawing + 430/0@1 + + + #ff0000 + #ff0000 + 0 + 0 + I11 + + true + true + false + 1 + false + false + 0 + phot_pimplant.drawing + 440/0@1 + + + #0000ff + #0000ff + 0 + 0 + I7 + + true + true + false + 1 + false + false + 0 + phot_nimplant.drawing + 450/0@1 + + + #ddff00 + #ddff00 + 0 + 0 + I9 + + false + true + true + + false + false + 0 + IP + 63/63@1 + + + #ffffff + + 0 + 0 + I1 + I1 + true + true + false + + false + false + 0 + OUTLINE - 235/0 + 235/0@1 + @@ -973,28 +1670,6 @@ 21 triangle - - - *...*...*...*... - .*.*.*.*.*.*.*.* - ..*...*...*...*. - .*.*.*.*.*.*.*.* - *...*...*...*... - .*.*.*.*.*.*.*.* - ..*...*...*...*. - .*.*.*.*.*.*.*.* - *...*...*...*... - .*.*.*.*.*.*.*.* - ..*...*...*...*. - .*.*.*.*.*.*.*.* - *...*...*...*... - .*.*.*.*.*.*.*.* - ..*...*...*...*. - .*.*.*.*.*.*.*.* - - 22 - x - *............... @@ -1014,7 +1689,7 @@ ................ ................ - 23 + 22 dot1 @@ -1036,7 +1711,7 @@ ................ ................ - 24 + 23 dot2 @@ -1058,7 +1733,7 @@ ..*.....*.....*. ................ - 25 + 24 dot3 @@ -1080,7 +1755,7 @@ .*...*.....*.... ................ - 26 + 25 dot4 @@ -1102,7 +1777,7 @@ ........******** ........******** - 27 + 26 checker @@ -1124,7 +1799,7 @@ ....*...*...*... ...............* - 28 + 27 viap @@ -1146,7 +1821,7 @@ .............**. ..............** - 29 + 28 metal1S @@ -1168,7 +1843,7 @@ .....*.......... *..............* - 30 + 29 metal2S @@ -1190,7 +1865,7 @@ ....***.....***. ......*.......*. - 31 + 30 gnd2S @@ -1212,7 +1887,7 @@ .**......**..... ..*.......*..... - 32 + 31 vcc2S @@ -1234,7 +1909,7 @@ .**...*..**...*. ..*....*..*....* - 33 + 32 vcc1S @@ -1256,7 +1931,7 @@ ................ ................ - 34 + 33 poly2p @@ -1278,7 +1953,7 @@ ................ ................ - 35 + 34 contp @@ -1316,7 +1991,7 @@ ................................ ................................ - 36 + 35 pplusp @@ -1354,7 +2029,7 @@ ................................ ................................ - 37 + 36 wellp @@ -1376,7 +2051,7 @@ .*.*.*.*.*.*.*.* *.*.*.*.*.*.*.*. - 38 + 37 checker1 @@ -1398,7 +2073,7 @@ ..**..**..**..** ..**..**..**..** - 39 + 38 checker2 @@ -1420,7 +2095,7 @@ .***.***.***.*** *.*.*.*.*.*.*.*. - 40 + 39 invCross @@ -1458,7 +2133,7 @@ ................................ ................................ - 41 + 40 wellBp @@ -1496,7 +2171,7 @@ ...........*........*........... ...........**********........... - 42 + 41 wellvtg @@ -1534,7 +2209,7 @@ ...........*.......*............ ...........*.......*............ - 43 + 42 wellvth @@ -1572,7 +2247,7 @@ ...............*................ ...............*................ - 44 + 43 thickox @@ -1610,7 +2285,7 @@ ................................ ................................ - 45 + 44 cwellBp @@ -1632,7 +2307,7 @@ ................ ................ - 46 + 45 capID @@ -1654,7 +2329,7 @@ ................ ................ - 47 + 46 resID @@ -1676,7 +2351,7 @@ ................ ................ - 48 + 47 diodeID @@ -1698,9 +2373,16 @@ **.*.*.***.*.*.* **************** - 49 + 48 sgrid + + + * + + 49 + + *** 1 @@ -1737,13 +2419,13 @@ hidden - *** + 8 - thickLine + - *** - 9 - mLine + + 0 + diff --git a/technology/freepdk45/tech/freepdk45.lyt b/technology/freepdk45/tech/freepdk45.lyt new file mode 100644 index 00000000..98e934a3 --- /dev/null +++ b/technology/freepdk45/tech/freepdk45.lyt @@ -0,0 +1,172 @@ + + + FreePDK45 + Free PDK 45nm + + 0.001 + $(appdata_path)/tech/FreePDK45 + .klayout/tech/FreePDK45 + FreePDK45.lyp + true + + + 1 + true + true + + + true + layer_map() + true + true + + + true + layer_map() + 0.001 + true + #1 + true + #1 + false + #1 + true + OUTLINE + true + PLACEMENT_BLK + true + REGIONS + true + + 0 + true + .PIN + 2 + true + .OBS + 3 + true + .BLK + 4 + true + .LABEL + 1 + true + + 0 + + + false + true + true + 64 + 0 + 1 + 0 + DATA + 0 + 0 + BORDER + layer_map() + true + + + 0.001 + 1 + 100 + 100 + 0 + 0 + 0 + false + false + false + true + layer_map() + + + 0 + 0.001 + layer_map() + true + false + + + 1 + 0.001 + layer_map() + true + false + true + + + + + + + true + false + false + false + false + 8000 + 32000 + LIB + + + 2 + false + false + 1 + * + false + + + 0 + + + false + false + + + 0 + + true + + + + DrainSource,contact,metal1 + poly,contact,metal1 + metal1,via1,metal2 + metal2,via2,metal3 + metal3,via3,metal4 + metal4,via4,metal5 + metal5,via5,metal6 + metal6,via6,metal7 + metal7,via7,metal8 + metal8,via8,metal9 + metal9,via9,metal10 + DrainSource='1/0 - 9/0' + poly='9/0' + contact='10/0' + metal1='11/0 + 11/1 + 11/2' + via1='12/0' + metal2='13/0 + 13/1 + 13/2' + via2='14/0' + metal3='15/0 + 15/1 + 15/2' + via3='16/0' + metal4='17/0 + 17/1 + 17/2' + via4='18/0' + metal5='19/0 + 19/1 + 19/2' + via5='20/0' + metal6='21/0 + 21/1 + 21/2' + via6='22/0' + metal7='23/0 + 23/1 + 23/2' + via7='24/0' + metal8='25/0 + 25/1 + 25/2' + via8='26/0' + metal9='27/0 + 27/1 + 27/2' + via9='28/0' + metal10='29/0 + 29/1 + 29/2' + + diff --git a/technology/freepdk45/tf/glade_freepdk45.py b/technology/freepdk45/tf/glade_freepdk45.py deleted file mode 100644 index 09ad83ea..00000000 --- a/technology/freepdk45/tf/glade_freepdk45.py +++ /dev/null @@ -1,7 +0,0 @@ -import os -CWD = os.environ.get("OPENRAM_TECH") + "/freepdk45/tf" -ui().importCds("default", CWD+"/display.drf", CWD+"/FreePDK45.tf", 1000, 1, CWD+"/layers.map") - - - -