From b4902c46e88f2c613e9f852872622be636b47d75 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 21 Apr 2021 13:01:38 -0700 Subject: [PATCH 01/28] v1.1.16 --- compiler/globals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/globals.py b/compiler/globals.py index 68d055ff..b0586b38 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -22,7 +22,7 @@ import getpass import subprocess -VERSION = "1.1.15" +VERSION = "1.1.16" NAME = "OpenRAM v{}".format(VERSION) USAGE = "openram.py [options] \nUse -h for help.\n" From 54c6043cb81c51f5f4a2f77e91145545ce0ed6d6 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 21 Apr 2021 13:46:31 -0700 Subject: [PATCH 02/28] v1.1.17 --- compiler/globals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/globals.py b/compiler/globals.py index b0586b38..4c5f8a00 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -22,7 +22,7 @@ import getpass import subprocess -VERSION = "1.1.16" +VERSION = "1.1.17" NAME = "OpenRAM v{}".format(VERSION) USAGE = "openram.py [options] \nUse -h for help.\n" From 9694237dba921bc394366dd49b7753e4d94eb4cf Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 28 Jul 2021 08:12:33 -0700 Subject: [PATCH 03/28] Flip MSB and LSB in lib file due to bug report --- compiler/characterizer/lib.py | 112 ++++++++++++++++------------------ 1 file changed, 54 insertions(+), 58 deletions(-) diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 434b2bf0..6138c479 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") @@ -650,21 +650,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 @@ -687,23 +687,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: @@ -720,41 +720,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' @@ -774,7 +774,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, @@ -802,8 +802,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), @@ -820,8 +820,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 = '' @@ -860,34 +860,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)) - - - - From e3911865814d10a30cb3634b4a594bb6e773edf3 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 28 Jul 2021 11:42:56 -0700 Subject: [PATCH 04/28] Update klayout tech files --- technology/freepdk45/tf/FreePDK45.lyp | 892 +++++++++++++++++++++++--- technology/freepdk45/tf/FreePDK45.lyt | 172 +++++ 2 files changed, 959 insertions(+), 105 deletions(-) create mode 100644 technology/freepdk45/tf/FreePDK45.lyt diff --git a/technology/freepdk45/tf/FreePDK45.lyp b/technology/freepdk45/tf/FreePDK45.lyp index 29e8e32f..4823c15a 100644 --- a/technology/freepdk45/tf/FreePDK45.lyp +++ b/technology/freepdk45/tf/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/tf/FreePDK45.lyt b/technology/freepdk45/tf/FreePDK45.lyt new file mode 100644 index 00000000..98e934a3 --- /dev/null +++ b/technology/freepdk45/tf/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' + + From 90a4ad4d75b5e30b8548f14673208fa955eb0ba9 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 28 Jul 2021 12:05:31 -0700 Subject: [PATCH 05/28] Update size of 30 config tests to 2 bits. --- compiler/tests/configs/config_back_end.py | 2 +- compiler/tests/configs/config_front_end.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 From e88f927e0111455bff19f52c80e80e027eeaa5ee Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 29 Jul 2021 11:41:41 -0700 Subject: [PATCH 06/28] v1.1.17 --- compiler/globals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/globals.py b/compiler/globals.py index f6929c4d..adc4cddf 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -22,7 +22,7 @@ import getpass import subprocess -VERSION = "1.1.15" +VERSION = "1.1.17" NAME = "OpenRAM v{}".format(VERSION) USAGE = "openram.py [options] \nUse -h for help.\n" From 85955ce29824d555bb7096f36d13bdbc0396c532 Mon Sep 17 00:00:00 2001 From: biarmic Date: Fri, 30 Jul 2021 12:22:55 +0300 Subject: [PATCH 07/28] Fix addr flop in Verilog --- compiler/base/verilog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index 205baeb7..efd19e9f 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) From c117238fa75d05fef3b9f03ccaf60ecb329e3bd4 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 3 Aug 2021 14:41:09 -0700 Subject: [PATCH 08/28] Initial klayout DRC/LVS options --- compiler/verify/__init__.py | 13 +- technology/freepdk45/tech/freepdk45.lydrc | 430 ++++++++++++++++++ technology/freepdk45/tech/freepdk45.lylvs | 270 +++++++++++ .../{tf/FreePDK45.lyp => tech/freepdk45.lyp} | 0 .../{tf/FreePDK45.lyt => tech/freepdk45.lyt} | 0 technology/freepdk45/tech/tech.py | 6 +- technology/freepdk45/tf/glade_freepdk45.py | 7 - 7 files changed, 712 insertions(+), 14 deletions(-) create mode 100644 technology/freepdk45/tech/freepdk45.lydrc create mode 100644 technology/freepdk45/tech/freepdk45.lylvs rename technology/freepdk45/{tf/FreePDK45.lyp => tech/freepdk45.lyp} (100%) rename technology/freepdk45/{tf/FreePDK45.lyt => tech/freepdk45.lyt} (100%) delete mode 100644 technology/freepdk45/tf/glade_freepdk45.py diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index 326771f8..e9769975 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", "calibre", "assura", "magic"], drc_name) + OPTS.lvs_exe = get_tool("LVS", ["klayout", "calibre", "assura", "netgen"], lvs_name) + OPTS.pex_exe = get_tool("PEX", ["klayout", "calibre", "magic"], 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/technology/freepdk45/tech/freepdk45.lydrc b/technology/freepdk45/tech/freepdk45.lydrc new file mode 100644 index 00000000..1d719e33 --- /dev/null +++ b/technology/freepdk45/tech/freepdk45.lydrc @@ -0,0 +1,430 @@ + + + + + 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 +via2.edges.without_length(70.nm).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 70nm") +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..c9107e45 --- /dev/null +++ b/technology/freepdk45/tech/freepdk45.lylvs @@ -0,0 +1,270 @@ + + + + + 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("sram_8_256_freepdk45.sp") + 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 = false + +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 100% rename from technology/freepdk45/tf/FreePDK45.lyp rename to technology/freepdk45/tech/freepdk45.lyp diff --git a/technology/freepdk45/tf/FreePDK45.lyt b/technology/freepdk45/tech/freepdk45.lyt similarity index 100% rename from technology/freepdk45/tf/FreePDK45.lyt rename to technology/freepdk45/tech/freepdk45.lyt diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index f5decd3c..de3629e3 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -460,8 +460,8 @@ parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of d # Technology Tool Preferences ################################################### -drc_name = "calibre" -lvs_name = "calibre" -pex_name = "calibre" +drc_name = "klayout" +lvs_name = "klayout" +pex_name = "klayout" blackbox_bitcell = False 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") - - - - From 6f4d9f17afe51b9e7878d75ca428e3018d9c07e3 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 18 Aug 2021 11:30:00 -0700 Subject: [PATCH 09/28] v1.1.18 --- compiler/globals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/globals.py b/compiler/globals.py index adc4cddf..50960fdc 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -22,7 +22,7 @@ import getpass import subprocess -VERSION = "1.1.17" +VERSION = "1.1.18" NAME = "OpenRAM v{}".format(VERSION) USAGE = "openram.py [options] \nUse -h for help.\n" From e9b370bf21c2fe1bb6ed974f41a2a65a2ca5e8e8 Mon Sep 17 00:00:00 2001 From: erendo Date: Sun, 29 Aug 2021 00:31:32 +0300 Subject: [PATCH 10/28] Fix write masks in Verilog --- compiler/base/verilog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index efd19e9f..9cfd7d7c 100644 --- a/compiler/base/verilog.py +++ b/compiler/base/verilog.py @@ -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)) From bcacf13f6117d632193d3797787837fed89288ee Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 30 Aug 2021 09:42:55 -0700 Subject: [PATCH 11/28] Don't run workflow on public repo --- .github/workflows/ci.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef2ef6a6..ea6e0b5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,8 @@ name: ci -on: [push] +on: + push: + branches-ignore: + - "VLSIDA/OpenRAM" jobs: scn4me_subm: runs-on: self-hosted @@ -8,7 +11,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 +30,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 +57,3 @@ jobs: # with: # name: code-coverage-report # path: ${{ github.workspace }}/coverage_html/ - From f42c52509ebdc17e9b173e3bf8e8191f51a77790 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 30 Aug 2021 09:49:22 -0700 Subject: [PATCH 12/28] Change double quote to single --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea6e0b5e..e741bbca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: ci on: push: branches-ignore: - - "VLSIDA/OpenRAM" + - 'VLSIDA/OpenRAM' jobs: scn4me_subm: runs-on: self-hosted From 3e9cce04002744bbe0118f3f499d9d1b88ef7383 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 30 Aug 2021 09:59:41 -0700 Subject: [PATCH 13/28] Revert github action and disable in repo settings --- .github/workflows/ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e741bbca..ed86160b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,5 @@ name: ci -on: - push: - branches-ignore: - - 'VLSIDA/OpenRAM' +on: [push] jobs: scn4me_subm: runs-on: self-hosted From 3f031a90db49513e6bca534425266777f97d3d8c Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 3 Sep 2021 12:52:17 -0700 Subject: [PATCH 14/28] Specify two stage wl_en driver to prevent race condition --- compiler/modules/control_logic.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 From b2389fe00feec7dd9cbf349e85f6153241310c23 Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 3 Sep 2021 14:04:39 -0700 Subject: [PATCH 15/28] Change tolerance to 30% --- compiler/tests/21_model_delay_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 83f2d146461956627bf7dc6569251f9182f68e5b Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 7 Sep 2021 14:07:22 -0700 Subject: [PATCH 16/28] Fix unit test errors. Skip test 50s for now. Change golden power values in xyce delay test. --- compiler/tests/21_xyce_delay_test.py | 26 ++++++++++++------------ compiler/tests/50_riscv_1rw_func_test.py | 2 ++ compiler/tests/50_riscv_1rw_phys_test.py | 4 ++-- 3 files changed, 17 insertions(+), 15 deletions(-) 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..6f0b668d 100755 --- a/compiler/tests/50_riscv_1rw_func_test.py +++ b/compiler/tests/50_riscv_1rw_func_test.py @@ -26,6 +26,7 @@ class riscv_func_test(openram_test): OPTS.netlist_only = True OPTS.trim_netlist = False + if OPTS.tech_name == "sky130": num_spare_rows = 1 num_spare_cols = 1 @@ -36,6 +37,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..6d9ad843 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): @@ -25,7 +25,7 @@ class riscv_phys_test(openram_test): from sram_config import sram_config 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() From 178f1197ca72dcf428f80cd95ff00cc158696d45 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 7 Sep 2021 16:49:11 -0700 Subject: [PATCH 17/28] Use spare rows only for sky130 --- compiler/tests/50_riscv_1rw_func_test.py | 1 - compiler/tests/50_riscv_1rw_phys_test.py | 15 +++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/tests/50_riscv_1rw_func_test.py b/compiler/tests/50_riscv_1rw_func_test.py index 6f0b668d..0e7e79d7 100755 --- a/compiler/tests/50_riscv_1rw_func_test.py +++ b/compiler/tests/50_riscv_1rw_func_test.py @@ -26,7 +26,6 @@ class riscv_func_test(openram_test): OPTS.netlist_only = True OPTS.trim_netlist = False - if OPTS.tech_name == "sky130": num_spare_rows = 1 num_spare_cols = 1 diff --git a/compiler/tests/50_riscv_1rw_phys_test.py b/compiler/tests/50_riscv_1rw_phys_test.py index 6d9ad843..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,6 +24,13 @@ 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 = 0 OPTS.num_w_ports = 0 @@ -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 " From 03f87cd6817860538eaa61ae4b77bfdfa3e0596f Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 7 Sep 2021 16:49:31 -0700 Subject: [PATCH 18/28] Add str function for sram_config --- compiler/sram/sram_config.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 """ From 8d9a4cc27b2503f012c7e7f178aeb638717abaf4 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 7 Sep 2021 16:49:44 -0700 Subject: [PATCH 19/28] PEP8 cleanup --- compiler/base/vector.py | 3 ++- compiler/modules/bank.py | 4 +--- compiler/modules/local_bitcell_array.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) 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/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/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() - From 554b3f4ca7729e12d2eedf7432597068e24c994d Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 3 Aug 2021 14:41:09 -0700 Subject: [PATCH 20/28] Initial klayout DRC/LVS options --- compiler/verify/__init__.py | 13 +- technology/freepdk45/tech/freepdk45.lydrc | 430 ++++++++++++++++++ technology/freepdk45/tech/freepdk45.lylvs | 270 +++++++++++ .../{tf/FreePDK45.lyp => tech/freepdk45.lyp} | 0 .../{tf/FreePDK45.lyt => tech/freepdk45.lyt} | 0 technology/freepdk45/tech/tech.py | 6 +- technology/freepdk45/tf/glade_freepdk45.py | 7 - 7 files changed, 712 insertions(+), 14 deletions(-) create mode 100644 technology/freepdk45/tech/freepdk45.lydrc create mode 100644 technology/freepdk45/tech/freepdk45.lylvs rename technology/freepdk45/{tf/FreePDK45.lyp => tech/freepdk45.lyp} (100%) rename technology/freepdk45/{tf/FreePDK45.lyt => tech/freepdk45.lyt} (100%) delete mode 100644 technology/freepdk45/tf/glade_freepdk45.py diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index 326771f8..e9769975 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", "calibre", "assura", "magic"], drc_name) + OPTS.lvs_exe = get_tool("LVS", ["klayout", "calibre", "assura", "netgen"], lvs_name) + OPTS.pex_exe = get_tool("PEX", ["klayout", "calibre", "magic"], 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/technology/freepdk45/tech/freepdk45.lydrc b/technology/freepdk45/tech/freepdk45.lydrc new file mode 100644 index 00000000..1d719e33 --- /dev/null +++ b/technology/freepdk45/tech/freepdk45.lydrc @@ -0,0 +1,430 @@ + + + + + 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 +via2.edges.without_length(70.nm).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 70nm") +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..c9107e45 --- /dev/null +++ b/technology/freepdk45/tech/freepdk45.lylvs @@ -0,0 +1,270 @@ + + + + + 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("sram_8_256_freepdk45.sp") + 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 = false + +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 100% rename from technology/freepdk45/tf/FreePDK45.lyp rename to technology/freepdk45/tech/freepdk45.lyp diff --git a/technology/freepdk45/tf/FreePDK45.lyt b/technology/freepdk45/tech/freepdk45.lyt similarity index 100% rename from technology/freepdk45/tf/FreePDK45.lyt rename to technology/freepdk45/tech/freepdk45.lyt diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index f5decd3c..de3629e3 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -460,8 +460,8 @@ parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of d # Technology Tool Preferences ################################################### -drc_name = "calibre" -lvs_name = "calibre" -pex_name = "calibre" +drc_name = "klayout" +lvs_name = "klayout" +pex_name = "klayout" blackbox_bitcell = False 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") - - - - From f3d1c6edc33757ab1cc1f2b3cf6c63a9a55433be Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 15 Sep 2021 11:33:39 -0700 Subject: [PATCH 21/28] klayout DRC/LVS working --- compiler/verify/klayout.py | 263 ++++++++++++++ technology/freepdk45/tech/freepdk45.lydrc | 161 +++------ technology/freepdk45/tech/freepdk45.lylvs | 406 +++++++++++++++++++++- 3 files changed, 707 insertions(+), 123 deletions(-) create mode 100644 compiler/verify/klayout.py diff --git a/compiler/verify/klayout.py b/compiler/verify/klayout.py new file mode 100644 index 00000000..808c66a7 --- /dev/null +++ b/compiler/verify/klayout.py @@ -0,0 +1,263 @@ +# 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 + drc_file = OPTS.openram_tech + "tech/{}.lydrc".format(OPTS.tech_name) + if os.path.exists(drc_file): + shutil.copy(drc_file, output_path) + else: + debug.warning("Could not locate file: {}".format(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) + breakpoint() + 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 + lvs_file = OPTS.openram_tech + "tech/{}.lylvs".format(OPTS.tech_name) + if os.path.exists(lvs_file): + shutil.copy(lvs_file, output_path) + else: + debug.warning("Could not locate file: {}".format(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/technology/freepdk45/tech/freepdk45.lydrc b/technology/freepdk45/tech/freepdk45.lydrc index 1d719e33..87025bc7 100644 --- a/technology/freepdk45/tech/freepdk45.lydrc +++ b/technology/freepdk45/tech/freepdk45.lydrc @@ -14,12 +14,11 @@ tools_menu.drc.end dsl drc-dsl-xml - -# -# DRC for FreePDK45 according to : + # +# DRC for FreePDK45 according to : # https://www.eda.ncsu.edu/wiki/FreePDK45:RuleDevel # https://www.eda.ncsu.edu/wiki/FreePDK45:Contents -# +# ########################################################################################## tstart = Time.now @@ -42,15 +41,14 @@ end ############### OFFGRID = true ANTENNA = true -DRC = true # KLAYOUT setup ######################## # Use a tile size of 1mm -# tiles(100.um) +tiles(1000.um) # Use a tile border of 10 micron: -# tile_borders(1.um) -# no_borders +tile_borders(1.um) +#no_borders # Hierachical deep @@ -66,37 +64,30 @@ 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) +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 +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) # DRC section ######################## @@ -108,6 +99,7 @@ def classify_by_width(layer, *dimensions) end # Wells +well = nwell + pwell 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") @@ -115,14 +107,13 @@ pwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of pwe 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 +gate = poly & active 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") +if poly.separation(active, 140.nm, projection).polygons? + poly.separation(active, 140.nm, projection).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") @@ -135,17 +126,17 @@ well.enclosing(active, 55.nm, euclidian).output("ACTIVE.3", "ACTIVE.3 : Minimum active.not(well).output("ACTIVE.4", "ACTIVE.4 : active must be inside nwell or pwell") # Implant +implant = nplus + pplus 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") +cont.not(active + poly + 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") @@ -155,21 +146,16 @@ metal1.width(65.nm, euclidian).output("METAL1.1", "METAL1.1 : Minimum width of m 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") @@ -182,21 +168,16 @@ metal2.width(70.nm, euclidian).output("METAL2.1", "METAL2.1 : Minimum width of 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 via2.edges.without_length(70.nm).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 70nm") @@ -209,21 +190,16 @@ metal3.width(70.nm, euclidian).output("METAL3.1", "METAL3.1 : Minimum width of 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") @@ -238,7 +214,6 @@ metal4_gt270, metal4_gt500, metal4_gt900 = classify_by_width(metal4, 270.nm, 500 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") @@ -253,7 +228,6 @@ metal5_gt270, metal5_gt500, metal5_gt900 = classify_by_width(metal5, 270.nm, 500 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") @@ -268,7 +242,6 @@ metal6_gt270, metal6_gt500, metal6_gt900 = classify_by_width(metal6, 270.nm, 500 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") @@ -283,7 +256,6 @@ metal7_gt500, metal7_gt900, metal7_gt1500 = classify_by_width(metal7, 500.nm, 90 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") @@ -298,7 +270,6 @@ metal8_gt500, metal8_gt900, metal8_gt1500 = classify_by_width(metal8, 500.nm, 90 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") @@ -313,7 +284,6 @@ metal9_gt500, metal9_gt900, metal9_gt1500 = classify_by_width(metal9, 500.nm, 90 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") @@ -328,9 +298,7 @@ metal10_gt500, metal10_gt900, metal10_gt1500 = classify_by_width(metal10, 500.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 : @@ -354,70 +322,46 @@ end if ANTENNA info("ANTENNA section") -diode = nplus & active - nwell # diode recognition layer - -# build connction of poly+gate to metal1 +# build connections of poly+gate to metals 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) +diode = nplus & active - nwell # diode recognition layer +connect(diode, cont) + +# runs an antenna checks for each metal with a ratio of 300 +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") +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") +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") +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") +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") +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") +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") +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") +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") 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") +# this will remove all connections made +clear_connections end # time spent for the DRC @@ -425,6 +369,5 @@ 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" - +$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 index c9107e45..c8dfccef 100644 --- a/technology/freepdk45/tech/freepdk45.lylvs +++ b/technology/freepdk45/tech/freepdk45.lylvs @@ -26,7 +26,7 @@ if $input end if $report - report_lvs($report) + report($report) else report_lvs("lvs_report.lvsdb") end @@ -34,7 +34,7 @@ end if $schematic #reference netlist schematic($schematic) -else +else # schematic("sram_8_256_freepdk45.sp") schematic(RBA::CellView::active.filename.sub(/\.(oas|gds|oas.gz|gds.gz)$/, ".sp")) end @@ -50,7 +50,7 @@ spice_with_comments = false 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("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 @@ -136,7 +136,7 @@ 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") { +cheat("cell_6t", "dummy_cell_6t", "cell_1rw", "dummy_cell_1rw", "cell_2rw", "dummy_cell_2rw", "dff","wordline_driver_0", "replica_cell_1rw", "replica_bitcell_array") { # PMOS transistor device extraction extract_devices(mos4("PMOS_VTL"), { "SD" => psd, "G" => lv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell }) @@ -210,16 +210,286 @@ 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(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 -#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 +# NangateOpenCellLibrary Digital gates VDD VSS implicit connection due to lack of taps insides the cells +connect_implicit("AND2_X1" , "VDD") +connect_implicit("AND2_X1" , "VSS") +connect_implicit("AND2_X2" , "VDD") +connect_implicit("AND2_X2" , "VSS") +connect_implicit("AND2_X4" , "VDD") +connect_implicit("AND2_X4" , "VSS") +connect_implicit("AND3_X1" , "VDD") +connect_implicit("AND3_X1" , "VSS") +connect_implicit("AND3_X2" , "VDD") +connect_implicit("AND3_X2" , "VSS") +connect_implicit("AND3_X4" , "VDD") +connect_implicit("AND3_X4" , "VSS") +connect_implicit("AND4_X1" , "VDD") +connect_implicit("AND4_X1" , "VSS") +connect_implicit("AND4_X2" , "VDD") +connect_implicit("AND4_X2" , "VSS") +connect_implicit("AND4_X4" , "VDD") +connect_implicit("AND4_X4" , "VSS") +connect_implicit("ANTENNA_X1" , "VDD") +connect_implicit("ANTENNA_X1" , "VSS") +connect_implicit("AOI211_X1" , "VDD") +connect_implicit("AOI211_X1" , "VSS") +connect_implicit("AOI211_X2" , "VDD") +connect_implicit("AOI211_X2" , "VSS") +connect_implicit("AOI211_X4" , "VDD") +connect_implicit("AOI211_X4" , "VSS") +connect_implicit("AOI21_X1" , "VDD") +connect_implicit("AOI21_X1" , "VSS") +connect_implicit("AOI21_X2" , "VDD") +connect_implicit("AOI21_X2" , "VSS") +connect_implicit("AOI21_X4" , "VDD") +connect_implicit("AOI21_X4" , "VSS") +connect_implicit("AOI221_X1" , "VDD") +connect_implicit("AOI221_X1" , "VSS") +connect_implicit("AOI221_X2" , "VDD") +connect_implicit("AOI221_X2" , "VSS") +connect_implicit("AOI221_X4" , "VDD") +connect_implicit("AOI221_X4" , "VSS") +connect_implicit("AOI222_X1" , "VDD") +connect_implicit("AOI222_X1" , "VSS") +connect_implicit("AOI222_X2" , "VDD") +connect_implicit("AOI222_X2" , "VSS") +connect_implicit("AOI222_X4" , "VDD") +connect_implicit("AOI222_X4" , "VSS") +connect_implicit("AOI22_X1" , "VDD") +connect_implicit("AOI22_X1" , "VSS") +connect_implicit("AOI22_X2" , "VDD") +connect_implicit("AOI22_X2" , "VSS") +connect_implicit("AOI22_X4" , "VDD") +connect_implicit("AOI22_X4" , "VSS") +connect_implicit("BUF_X1" , "VDD") +connect_implicit("BUF_X1" , "VSS") +connect_implicit("BUF_X16" , "VDD") +connect_implicit("BUF_X16" , "VSS") +connect_implicit("BUF_X2" , "VDD") +connect_implicit("BUF_X2" , "VSS") +connect_implicit("BUF_X32" , "VDD") +connect_implicit("BUF_X32" , "VSS") +connect_implicit("BUF_X4" , "VDD") +connect_implicit("BUF_X4" , "VSS") +connect_implicit("BUF_X8" , "VDD") +connect_implicit("BUF_X8" , "VSS") +connect_implicit("CLKBUF_X1" , "VDD") +connect_implicit("CLKBUF_X1" , "VSS") +connect_implicit("CLKBUF_X2" , "VDD") +connect_implicit("CLKBUF_X2" , "VSS") +connect_implicit("CLKBUF_X3" , "VDD") +connect_implicit("CLKBUF_X3" , "VSS") +connect_implicit("CLKGATETST_X1" , "VDD") +connect_implicit("CLKGATETST_X1" , "VSS") +connect_implicit("CLKGATETST_X2" , "VDD") +connect_implicit("CLKGATETST_X2" , "VSS") +connect_implicit("CLKGATETST_X4" , "VDD") +connect_implicit("CLKGATETST_X4" , "VSS") +connect_implicit("CLKGATETST_X8" , "VDD") +connect_implicit("CLKGATETST_X8" , "VSS") +connect_implicit("CLKGATE_X1" , "VDD") +connect_implicit("CLKGATE_X1" , "VSS") +connect_implicit("CLKGATE_X2" , "VDD") +connect_implicit("CLKGATE_X2" , "VSS") +connect_implicit("CLKGATE_X4" , "VDD") +connect_implicit("CLKGATE_X4" , "VSS") +connect_implicit("CLKGATE_X8" , "VDD") +connect_implicit("CLKGATE_X8" , "VSS") +connect_implicit("DFFRS_X1" , "VDD") +connect_implicit("DFFRS_X1" , "VSS") +connect_implicit("DFFRS_X2" , "VDD") +connect_implicit("DFFRS_X2" , "VSS") +connect_implicit("DFFR_X1" , "VDD") +connect_implicit("DFFR_X1" , "VSS") +connect_implicit("DFFR_X2" , "VDD") +connect_implicit("DFFR_X2" , "VSS") +connect_implicit("DFFS_X1" , "VDD") +connect_implicit("DFFS_X1" , "VSS") +connect_implicit("DFFS_X2" , "VDD") +connect_implicit("DFFS_X2" , "VSS") +connect_implicit("DFF_X1" , "VDD") +connect_implicit("DFF_X1" , "VSS") +connect_implicit("DFF_X2" , "VDD") +connect_implicit("DFF_X2" , "VSS") +connect_implicit("DLH_X1" , "VDD") +connect_implicit("DLH_X1" , "VSS") +connect_implicit("DLH_X2" , "VDD") +connect_implicit("DLH_X2" , "VSS") +connect_implicit("DLL_X1" , "VDD") +connect_implicit("DLL_X1" , "VSS") +connect_implicit("DLL_X2" , "VDD") +connect_implicit("DLL_X2" , "VSS") +connect_implicit("FA_X1" , "VDD") +connect_implicit("FA_X1" , "VSS") +connect_implicit("FILLCELL_X1" , "VDD") +connect_implicit("FILLCELL_X1" , "VSS") +connect_implicit("FILLCELL_X16" , "VDD") +connect_implicit("FILLCELL_X16" , "VSS") +connect_implicit("FILLCELL_X2" , "VDD") +connect_implicit("FILLCELL_X2" , "VSS") +connect_implicit("FILLCELL_X32" , "VDD") +connect_implicit("FILLCELL_X32" , "VSS") +connect_implicit("FILLCELL_X4" , "VDD") +connect_implicit("FILLCELL_X4" , "VSS") +connect_implicit("FILLCELL_X8" , "VDD") +connect_implicit("FILLCELL_X8" , "VSS") +connect_implicit("HA_X1" , "VDD") +connect_implicit("HA_X1" , "VSS") +connect_implicit("INV_X1" , "VDD") +connect_implicit("INV_X1" , "VSS") +connect_implicit("INV_X16" , "VDD") +connect_implicit("INV_X16" , "VSS") +connect_implicit("INV_X2" , "VDD") +connect_implicit("INV_X2" , "VSS") +connect_implicit("INV_X32" , "VDD") +connect_implicit("INV_X32" , "VSS") +connect_implicit("INV_X4" , "VDD") +connect_implicit("INV_X4" , "VSS") +connect_implicit("INV_X8" , "VDD") +connect_implicit("INV_X8" , "VSS") +connect_implicit("LOGIC0_X1" , "VDD") +connect_implicit("LOGIC0_X1" , "VSS") +connect_implicit("LOGIC1_X1" , "VDD") +connect_implicit("LOGIC1_X1" , "VSS") +connect_implicit("MUX2_X1" , "VDD") +connect_implicit("MUX2_X1" , "VSS") +connect_implicit("MUX2_X2" , "VDD") +connect_implicit("MUX2_X2" , "VSS") +connect_implicit("NAND2_X1" , "VDD") +connect_implicit("NAND2_X1" , "VSS") +connect_implicit("NAND2_X2" , "VDD") +connect_implicit("NAND2_X2" , "VSS") +connect_implicit("NAND2_X4" , "VDD") +connect_implicit("NAND2_X4" , "VSS") +connect_implicit("NAND3_X1" , "VDD") +connect_implicit("NAND3_X1" , "VSS") +connect_implicit("NAND3_X2" , "VDD") +connect_implicit("NAND3_X2" , "VSS") +connect_implicit("NAND3_X4" , "VDD") +connect_implicit("NAND3_X4" , "VSS") +connect_implicit("NAND4_X1" , "VDD") +connect_implicit("NAND4_X1" , "VSS") +connect_implicit("NAND4_X2" , "VDD") +connect_implicit("NAND4_X2" , "VSS") +connect_implicit("NAND4_X4" , "VDD") +connect_implicit("NAND4_X4" , "VSS") +connect_implicit("NOR2_X1" , "VDD") +connect_implicit("NOR2_X1" , "VSS") +connect_implicit("NOR2_X2" , "VDD") +connect_implicit("NOR2_X2" , "VSS") +connect_implicit("NOR2_X4" , "VDD") +connect_implicit("NOR2_X4" , "VSS") +connect_implicit("NOR3_X1" , "VDD") +connect_implicit("NOR3_X1" , "VSS") +connect_implicit("NOR3_X2" , "VDD") +connect_implicit("NOR3_X2" , "VSS") +connect_implicit("NOR3_X4" , "VDD") +connect_implicit("NOR3_X4" , "VSS") +connect_implicit("NOR4_X1" , "VDD") +connect_implicit("NOR4_X1" , "VSS") +connect_implicit("NOR4_X2" , "VDD") +connect_implicit("NOR4_X2" , "VSS") +connect_implicit("NOR4_X4" , "VDD") +connect_implicit("NOR4_X4" , "VSS") +connect_implicit("OAI211_X1" , "VDD") +connect_implicit("OAI211_X1" , "VSS") +connect_implicit("OAI211_X2" , "VDD") +connect_implicit("OAI211_X2" , "VSS") +connect_implicit("OAI211_X4" , "VDD") +connect_implicit("OAI211_X4" , "VSS") +connect_implicit("OAI21_X1" , "VDD") +connect_implicit("OAI21_X1" , "VSS") +connect_implicit("OAI21_X2" , "VDD") +connect_implicit("OAI21_X2" , "VSS") +connect_implicit("OAI21_X4" , "VDD") +connect_implicit("OAI21_X4" , "VSS") +connect_implicit("OAI221_X1" , "VDD") +connect_implicit("OAI221_X1" , "VSS") +connect_implicit("OAI221_X2" , "VDD") +connect_implicit("OAI221_X2" , "VSS") +connect_implicit("OAI221_X4" , "VDD") +connect_implicit("OAI221_X4" , "VSS") +connect_implicit("OAI222_X1" , "VDD") +connect_implicit("OAI222_X1" , "VSS") +connect_implicit("OAI222_X2" , "VDD") +connect_implicit("OAI222_X2" , "VSS") +connect_implicit("OAI222_X4" , "VDD") +connect_implicit("OAI222_X4" , "VSS") +connect_implicit("OAI22_X1" , "VDD") +connect_implicit("OAI22_X1" , "VSS") +connect_implicit("OAI22_X2" , "VDD") +connect_implicit("OAI22_X2" , "VSS") +connect_implicit("OAI22_X4" , "VDD") +connect_implicit("OAI22_X4" , "VSS") +connect_implicit("OAI33_X1" , "VDD") +connect_implicit("OAI33_X1" , "VSS") +connect_implicit("OR2_X1" , "VDD") +connect_implicit("OR2_X1" , "VSS") +connect_implicit("OR2_X2" , "VDD") +connect_implicit("OR2_X2" , "VSS") +connect_implicit("OR2_X4" , "VDD") +connect_implicit("OR2_X4" , "VSS") +connect_implicit("OR3_X1" , "VDD") +connect_implicit("OR3_X1" , "VSS") +connect_implicit("OR3_X2" , "VDD") +connect_implicit("OR3_X2" , "VSS") +connect_implicit("OR3_X4" , "VDD") +connect_implicit("OR3_X4" , "VSS") +connect_implicit("OR4_X1" , "VDD") +connect_implicit("OR4_X1" , "VSS") +connect_implicit("OR4_X2" , "VDD") +connect_implicit("OR4_X2" , "VSS") +connect_implicit("OR4_X4" , "VDD") +connect_implicit("OR4_X4" , "VSS") +connect_implicit("SDFFRS_X1" , "VDD") +connect_implicit("SDFFRS_X1" , "VSS") +connect_implicit("SDFFRS_X2" , "VDD") +connect_implicit("SDFFRS_X2" , "VSS") +connect_implicit("SDFFR_X1" , "VDD") +connect_implicit("SDFFR_X1" , "VSS") +connect_implicit("SDFFR_X2" , "VDD") +connect_implicit("SDFFR_X2" , "VSS") +connect_implicit("SDFFS_X1" , "VDD") +connect_implicit("SDFFS_X1" , "VSS") +connect_implicit("SDFFS_X2" , "VDD") +connect_implicit("SDFFS_X2" , "VSS") +connect_implicit("SDFF_X1" , "VDD") +connect_implicit("SDFF_X1" , "VSS") +connect_implicit("SDFF_X2" , "VDD") +connect_implicit("SDFF_X2" , "VSS") +connect_implicit("TBUF_X1" , "VDD") +connect_implicit("TBUF_X1" , "VSS") +connect_implicit("TBUF_X16" , "VDD") +connect_implicit("TBUF_X16" , "VSS") +connect_implicit("TBUF_X2" , "VDD") +connect_implicit("TBUF_X2" , "VSS") +connect_implicit("TBUF_X4" , "VDD") +connect_implicit("TBUF_X4" , "VSS") +connect_implicit("TBUF_X8" , "VDD") +connect_implicit("TBUF_X8" , "VSS") +connect_implicit("TINV_X1" , "VDD") +connect_implicit("TINV_X1" , "VSS") +connect_implicit("TLAT_X1" , "VDD") +connect_implicit("TLAT_X1" , "VSS") +connect_implicit("XNOR2_X1" , "VDD") +connect_implicit("XNOR2_X1" , "VSS") +connect_implicit("XNOR2_X2" , "VDD") +connect_implicit("XNOR2_X2" , "VSS") +connect_implicit("XOR2_X1" , "VDD") +connect_implicit("XOR2_X1" , "VSS") +connect_implicit("XOR2_X2" , "VDD") +connect_implicit("XOR2_X2" , "VSS") # Actually performs the extraction netlist # ... not really required @@ -248,13 +518,121 @@ 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) +# NangateOpenCellLibrary Digital gates input equivalence : +equivalent_pins("AND2_X1", "A1", "A2") +equivalent_pins("AND2_X2", "A1", "A2") +equivalent_pins("AND2_X4", "A1", "A2") +equivalent_pins("AND3_X1", "A1", "A2", "A3") +equivalent_pins("AND3_X2", "A1", "A2", "A3") +equivalent_pins("AND3_X4", "A1", "A2", "A3") +equivalent_pins("AND4_X1", "A1", "A2", "A3", "A4") +equivalent_pins("AND4_X2", "A1", "A2", "A3", "A4") +equivalent_pins("AND4_X4", "A1", "A2", "A3", "A4") +equivalent_pins("NAND2_X1", "A1", "A2") +equivalent_pins("NAND2_X2", "A1", "A2") +equivalent_pins("NAND2_X4", "A1", "A2") +equivalent_pins("NAND3_X1", "A1", "A2", "A3") +equivalent_pins("NAND3_X2", "A1", "A2", "A3") +equivalent_pins("NAND3_X4", "A1", "A2", "A3") +equivalent_pins("NAND4_X1", "A1", "A2", "A3", "A4") +equivalent_pins("NAND4_X2", "A1", "A2", "A3", "A4") +equivalent_pins("NAND4_X4", "A1", "A2", "A3", "A4") +equivalent_pins("OR2_X1", "A1", "A2") +equivalent_pins("OR2_X2", "A1", "A2") +equivalent_pins("OR2_X4", "A1", "A2") +equivalent_pins("OR3_X1", "A1", "A2", "A3") +equivalent_pins("OR3_X2", "A1", "A2", "A3") +equivalent_pins("OR3_X4", "A1", "A2", "A3") +equivalent_pins("OR4_X1", "A1", "A2", "A3", "A4") +equivalent_pins("OR4_X2", "A1", "A2", "A3", "A4") +equivalent_pins("OR4_X4", "A1", "A2", "A3", "A4") +equivalent_pins("NOR2_X1", "A1", "A2") +equivalent_pins("NOR2_X2", "A1", "A2") +equivalent_pins("NOR2_X4", "A1", "A2") +equivalent_pins("NOR3_X1", "A1", "A2", "A3") +equivalent_pins("NOR3_X2", "A1", "A2", "A3") +equivalent_pins("NOR3_X4", "A1", "A2", "A3") +equivalent_pins("NOR4_X1", "A1", "A2", "A3", "A4") +equivalent_pins("NOR4_X2", "A1", "A2", "A3", "A4") +equivalent_pins("NOR4_X4", "A1", "A2", "A3", "A4") +equivalent_pins("XOR2_X1", "A", "B") +equivalent_pins("XOR2_X2", "A", "B") +equivalent_pins("XNOR2_X1", "A", "B") +equivalent_pins("XNOR2_X2", "A", "B") + +equivalent_pins("AOI211_X1","A","B") +equivalent_pins("AOI211_X1","C1","C2") +equivalent_pins("AOI211_X2","A","B") +equivalent_pins("AOI211_X2","C1","C2") +equivalent_pins("AOI211_X4","A","B") +equivalent_pins("AOI211_X4","C1","C2") +equivalent_pins("AOI21_X1","B1","B2") +equivalent_pins("AOI21_X2","B1","B2") +equivalent_pins("AOI21_X4","B1","B2") +equivalent_pins("AOI221_X1","B1","B2") +equivalent_pins("AOI221_X1","C1","C2") +equivalent_pins("AOI221_X2","B1","B2") +equivalent_pins("AOI221_X2","C1","C2") +equivalent_pins("AOI221_X4","B1","B2") +equivalent_pins("AOI221_X4","C1","C2") +equivalent_pins("AOI222_X1","A1","A2") +equivalent_pins("AOI222_X1","B1","B2") +equivalent_pins("AOI222_X1","C1","C2") +equivalent_pins("AOI222_X2","A1","A2") +equivalent_pins("AOI222_X2","B1","B2") +equivalent_pins("AOI222_X2","C1","C2") +equivalent_pins("AOI222_X4","A1","A2") +equivalent_pins("AOI222_X4","B1","B2") +equivalent_pins("AOI222_X4","C1","C2") +equivalent_pins("AOI22_X1","A1","A2") +equivalent_pins("AOI22_X1","B1","B2") +equivalent_pins("AOI22_X2","A1","A2") +equivalent_pins("AOI22_X2","B1","B2") +equivalent_pins("AOI22_X4","A1","A2") +equivalent_pins("AOI22_X4","B1","B2") + +equivalent_pins("OAI211_X1","A","B") +equivalent_pins("OAI211_X1","C1","C2") +equivalent_pins("OAI211_X2","A","B") +equivalent_pins("OAI211_X2","C1","C2") +equivalent_pins("OAI211_X4","A","B") +equivalent_pins("OAI211_X4","C1","C2") +equivalent_pins("OAI21_X1","B1","B2") +equivalent_pins("OAI21_X2","B1","B2") +equivalent_pins("OAI21_X4","B1","B2") +equivalent_pins("OAI221_X1","B1","B2") +equivalent_pins("OAI221_X1","C1","C2") +equivalent_pins("OAI221_X2","B1","B2") +equivalent_pins("OAI221_X2","C1","C2") +equivalent_pins("OAI221_X4","B1","B2") +equivalent_pins("OAI221_X4","C1","C2") +equivalent_pins("OAI222_X1","A1","A2") +equivalent_pins("OAI222_X1","B1","B2") +equivalent_pins("OAI222_X1","C1","C2") +equivalent_pins("OAI222_X2","A1","A2") +equivalent_pins("OAI222_X2","B1","B2") +equivalent_pins("OAI222_X2","C1","C2") +equivalent_pins("OAI222_X4","A1","A2") +equivalent_pins("OAI222_X4","B1","B2") +equivalent_pins("OAI222_X4","C1","C2") +equivalent_pins("OAI22_X1","A1","A2") +equivalent_pins("OAI22_X1","B1","B2") +equivalent_pins("OAI22_X2","A1","A2") +equivalent_pins("OAI22_X2","B1","B2") +equivalent_pins("OAI22_X4","A1","A2") +equivalent_pins("OAI22_X4","B1","B2") +equivalent_pins("OAI33_X1","A1","A2","A3") +equivalent_pins("OAI33_X1","B1","B2","B3") + +equivalent_pins("HA_X1","A","B") + #max_res(1000000) #min_caps(1e-15) max_branch_complexity(65536) max_depth(16) -if ! compare +if ! compare #raise "ERROR : Netlists don't match" puts "ERROR : Netlists don't match" else From 11c5a644eb2559d1c7f53544786fc267f449da29 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 15 Sep 2021 11:43:40 -0700 Subject: [PATCH 22/28] Remove previous breakpoint --- compiler/verify/klayout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/verify/klayout.py b/compiler/verify/klayout.py index 808c66a7..4926c4c8 100644 --- a/compiler/verify/klayout.py +++ b/compiler/verify/klayout.py @@ -75,7 +75,7 @@ def run_drc(cell_name, gds_name, sp_name=None, extract=True, final_verification= f = open(resultsfile, "r") except FileNotFoundError: debug.error("Unable to load DRC results file from {}. Is klayout set up?".format(resultsfile), 1) - breakpoint() + results = f.readlines() f.close() errors=len([x for x in results if "" in x]) From c5f372c2648ba420ae15fa46c9b30ecb3e30b4e5 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 15 Sep 2021 11:58:31 -0700 Subject: [PATCH 23/28] Fix via2 to match incorrect FreePDK45 rules --- technology/freepdk45/tech/freepdk45.lydrc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/technology/freepdk45/tech/freepdk45.lydrc b/technology/freepdk45/tech/freepdk45.lydrc index 87025bc7..965e0488 100644 --- a/technology/freepdk45/tech/freepdk45.lydrc +++ b/technology/freepdk45/tech/freepdk45.lydrc @@ -15,10 +15,10 @@ dsl drc-dsl-xml # -# DRC for FreePDK45 according to : +# DRC for FreePDK45 according to : # https://www.eda.ncsu.edu/wiki/FreePDK45:RuleDevel # https://www.eda.ncsu.edu/wiki/FreePDK45:Contents -# +# ########################################################################################## tstart = Time.now @@ -107,7 +107,7 @@ pwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of pwe 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 gate = poly & active poly.width(50.nm, euclidian).output("POLY.1", "POLY.1 : Minimum width of poly : 50nm") @@ -180,8 +180,11 @@ metal2_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL 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") # via2 -via2.edges.without_length(70.nm).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 70nm") -via2.space(85.nm, euclidian).output("VIA2.2", "VIA2.2 : Minimum spacing of via2 : 85nm") +# The FreePDK45 design rules say this is 70nm, but their Calibre deck uses 65nm, so we are using that for compatibility. +#via2.edges.without_length(70.nm).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 70nm") +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.space(75.nm, euclidian).output("VIA2.2", "VIA2.2 : Minimum spacing of via2 : 75nm") 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") From 8081bea708ab96626ee7dc6049c7fa1b4ef45a43 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 16 Sep 2021 15:28:39 -0700 Subject: [PATCH 24/28] Shrink 70nm contacts to 65nm --- technology/freepdk45/gds_lib/cell_1rw.gds | Bin 20480 -> 19192 bytes technology/freepdk45/gds_lib/dff.gds | Bin 22182 -> 22182 bytes .../freepdk45/gds_lib/dummy_cell_1rw.gds | Bin 18994 -> 18930 bytes .../freepdk45/gds_lib/replica_cell_1rw.gds | Bin 19124 -> 19060 bytes technology/freepdk45/gds_lib/sense_amp.gds | Bin 14702 -> 14318 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/technology/freepdk45/gds_lib/cell_1rw.gds b/technology/freepdk45/gds_lib/cell_1rw.gds index 1a138c4abdc970d88930c04d79afd23c1a36f64b..3a67e40b4ceeba52e0663ae9a61c1e918a35b8f5 100644 GIT binary patch literal 19192 zcmeI3U#MM26~<@pefGZR-rSl9Vu+2Q4JftdL4DBLmc~?Rtx3_eq_#yVNmKd~Yt2JS zK?o?~i-HIi!9XckL8xGSkW#3BBT8B&2pS6k{}cr^rQ(Av6p7!r)_iCG$(eo6%|4e} za#Ak8bj|+GTC--&%$_+np$d6^ci5igOMeZ^VN2M||C)aft9kX=haY(8=)uo~@Rp*y z>8exTdh*3PU;W_DPi;MO`}eL5+shtpRo(xham<5yL&s;=>{2XAncA z?Abl(`12o74;@Hd`B+Wa(|Y*bhv#2#GJpAdJb$F@#S3Zw#T|SOKcMCR{WWFp=!^FF zg)lx2{}{hbAA=U-C(1rP-tynjbNoVBe#S{1$4`_UY>xjN^`1A26M0U75XZRkDks?&D<&=%{uQ@;(p`&XVc?xO7J@pJzgXN|ikdq-_fHF-*J7o*Y!@>jm`NpXZ^7F8Ef`Stl41xJMdpMWe1b-57lSS zyML1Y^FuZ1Kei|R57l?$@A{AJN&l9&-P7}z%f36EKkGN1o7R8oV|&tnah2zfWd4dD z)@1%-`-bU9Uw8i`{g<3Gk@O$ilm3V5yZ!I_pQZoI^R)a;>^t*QDMs>j&vqJJm-W)cVLnEi|vW=pS0U^(uuR^i=ViElDg4dHK~v7 zN&oZKm%nxY{>Lx*_%A;JqV7f6!KD9Tdav8@ zOHTUb{1;_U`=5gz^UnN$>bEF++P~*bd0PHr%wMGJX+7d^`iIW?A32MEjywI3jGOf@ z%AWS`Iz1Nx*J**8+(?LX(8`3LDgwkPYirAOW^f0483Tm6U5{6{AG zKN|S{Jy-oDy#KZQ^SwduS$gjgWe1!2Z|HsgYM_h z!^is0FAcrNr*(>T*Iqx>p7!6WzbW%)O8qW3;}2i)b34iN9?vf#&!e%u+5e06-|**_ z-i>@>&WKgCgGqgcXO@17euLvrV*dQ6rtDx+&-X*dNiuHJN6HQ+^+WqV5C7rUp1OvYazXUsFHyo<8;<$tKYJAOH8fa6HcGJZq#`JKoe@<%dn`QK~G-j_eVfAIc? zO(c|1ole(pb_>}%;6=QO?S zeepN_Gj95yFvivApYxCRT$6c-^~cs7Q1;IKS1bS2alM|VmmN&zzv-XrJ=Z^R7XO_8 zd~UK1VEwXn2b8@pe(D(KG`;M7@i+Z5ZmxggEdJSlTkk>M`?3F`>|k&HCeLB*xBWxp ze&V0stLMJQ=j2zE9Zc%kfBGk>WB*0j`{Hlv`MzcQhZsM1Ej{C$=3n+s{F_?)?_t!* zFVKIGL=;qOM| z|D=;TzQ2pIuca?BZv33;?XDMP2h;KIL(FFpKgjit-(QHbhqFC;+#i*jz5n#f{)@7w z{rk8v&lR={e~{+57axX|F$$<26DaMb=+z@6G?p9?$>E z&prPW%DyL+|DpYd-@B0eb58rd^2Z#1{68prIG6UHp|5*P{*~xVOht|(L^jashZiupjiTXpm{(#fhP_c-(GJ?`S8qU`tj@M|G|F4d24HLr0nVW zyU;Ul%G3JuhqeA$uV0e&Yu`CWvVLRxV*SMyIYx=%_t#qIhvG^YA4l%MrK|DKyK2e~ zCi6E`&%Ju-Ip4F#w`2Ss#QSHY>|oOWQ2ph;|K?k zAKMf0qpzp^Ig);P{uil_?MXf3<~hyI&)CcD{2VEJ`uyg3vwJ4wX&E}^Pn12a_xR|C zaZ<{ow6GfdbB;m zcG7jM1nR>rvXg25*)D6hZ(^upB|2sA*B7V#IaU&N{3gRGd%wP9oWDW85jRf(tR$!G z#zg-yzWgnCR3LBd!w29WmF$$=nAB%C*yo4r2G?cZ6=gRj^k_S!AJ+xU2>K5k*SamC z=eXlJ&LHS`186tFM%j%CJ=*vtz#8C940Fhv7^CcDdi-srJLmrG1_kve+D!`L>YT(n z^=s`GzjWe4?|JRZpS=lU?%GWZ#;xK_jM2u;-xPbs@AcLfH^nyz(B34l4v6+9;q8g~ zo#f{-tsfs_JISL4IB#t1BV|w5&q8nCB=9EblJe*KoxL$*-(%h8AE_xjxTs$&QSl~Z zvHnl~B_r2utK>VN7ie^|fbqHEQY!sCR_%w(7x$4x>b|tm)h|Q|$Sl@Gklo^~VlNALsr` z^X$W0!0{{C!@*^|fAU*AQFgFd`z<}s41CT`q0rkYG*b4o9{t37^Y|4f{qo!(%AVF^ zjCcx%e>;UkZ>R7`+57a2yFB3WmuEfx31vT!?!Vol;yEMq%pdhe*?aVioBoZ^F@EZe zvM2SPxAH(Fbaw6%Wl!qS-xJs`Z;QljwB7!Qvfq~Ozuhw7IU{t%Q}{el_8z_6g5fzM zbodwTrZ>voqqkc)JZFTSw{Y}tl)XpKf5mCHy&!La^FP*koPs{icqc#99(?fum? z?H1qLlk;b&zBq^_3O-1Fi&4B-Q})ic7_In+>hoh>d-*ZsaS!tMswlfL*?;QzTMtIe z9qM_8Hp<@qEt=Kl`ZY|?*!1IZvk(3wJK3uLmVeIsYA5Cm`E3xfPbmA&g`WFY+<)er zKN}y*zpZ`9zf<;IyD8)whO{^4_wd07AYO|5=h s&(H85*~!KJyK(#Py7!Nlo9}NJt;wI2FW~(NKjNeT?+%QA2|uIoKkoC3y8r+H literal 20480 zcmeI3U#MM26~^c6efG{hH@7B&7-G{H1D0Czpn|csr6pFXjV(1TjaHPBw52bx);y#H zgcghVq9B4ruuuZ6Ak+takWi>m8!^->dC^!1Xe|iFO2r2a6p7!r)_iCG>7B_vci%LG zoIv_JS#!U$)~s1GduH}+s6!E|uw_()!(kM*gmKs&Hncu}G%OYMR~~ur;e-3W9KyTG z>ZWbSfAH*Ucc1iYf%4;;Af!v`M?6l z-%*4Rc5E!qojZ3s{$7WMogzNpNxNwF@jJtE`VdBMxUPIm#5bRyUA&NbeD~|{b#Dm8 z$%f@EZWqy>jKBD)>#8Rkmbc*hU>DPR_(7h^SMc*6YS_)6*^~auyIfcAY*^ok&#;U5 z{R)*mv;JqV-An zajPGSTg=4I7)QTB%>Qaw{2}%s679+Ohw4W!yWe7eL;9y(%=sUxSNwhdHg+rQe{r+t zZ^~Z&U{B`H`UyS#Q*Z3`Kc_FZdEUyOHDums7jyX=svm#X{Z^W#hr=k%=qg@5|Ei<2W> z7b5K0`jJkQPE6~)&Z*=47bi#DF1{Ul);;}`@++qOd!D2dCr`xuMW*$M{y!1>ADPyB zozpMJU1Z)_7vhD~vwrE9bt}>@?PAXVP(Ax8zoPmr=KM?F$sfke@)w!YFB?B}*8j+! zemrphy{7tA?EgCc%WbIjxbG3Q?-G0apO)V19P6{qJ5kSlvA2G=^gd41&9MGf-TWhl z+0HL5eKUV&SU+0&X8!i)PiFp3+VO{P`8^5GFT5`yc^;%)guT6fzg7P|#DO_nN3Q#z z>qXd;`Vlnieu?$xKMfZ?hw+46M0--t{UhV1US!;~i|36$_M7@y`@a_c!|yzPadf}u zPlP?${}Q=U-bLn}b}^U#p?Zy90iPU4QsXA(#&4*;xEr}b{))dhWZq~ObNS=`h4(+B z?=;8xN8ENv|D-G4=GeLpO2uJ_!xf8RkE z_%>;!-^OnU&*B>%2{pc9W$Ksh&&dG)*rPm!wdHE6A3e|+3G_s7!rPcH6q zyLdi*h03E&tgmvt2z$am;=CQPpF&yeY&g2t?IPOKdfbH3FZ(anS9$-%w14O@PRu`N zqNs5b)A{#)((mf}KU`nFwtDKU4Ncujx+N=<%`iS?n>oqq(L{AC;@>xOnQ>z{AZ(RX{3ZbTk9e>pFV z)}E;Uq4{4MJ?m_>>&N&{9CzqZgQD%|U>DQlkK7#g{Dp6dF>i;(^W=|f9sR%9us-AQ zh-gpef36>tzww(sZuLauxMA+RDdxxTV(W|RJ?`?;qQ_laFXrPPs^@8i&n>P!BHv2e zDJC-e{JN~`*T;?zb4XhZ-*2;m>Xn?MeSb z_1rhG?k?Bocd-d;7tT-R`x`Q+w2SHU6Q8qjty|Ue7uxy$>yoa&>G}6>zWxS~>#p5v zMCSGnqd&T?_ijg>xqLy#En-@4@q@2zye``xok}pEuOKiI~&#oJl>;{UUX=i#a{xbNxkkZNcv~|VopE3ze4`_J;3WeZx;1<<5YdnbGLFea(4vktzltv{m{d>p0eDpT#tG17u@AM zN&TS*q>pd?-YdF}wMTtyPwFei{d?A&h;ie7 z1b2~6vnTcCD`-cpj^iB=Yd?Gpe(`3-Y3)h<=mz&Y`jP0m(G8;66MD2A=U@CR$I(b# z{A{$(2|dRd?VM{IeW6))Z%c{4Tb>yvg?robT8p*fYoW9I%VM zy~DhIrq2(byPP-n?j_RB{Hz7up@8Z6H*KHmF=v!Vo>Q5xxCl^Ve2pE@BH~a-FI;QUIP1%#rFQ+&QAYD+&nu)PO$f~vr{DO?fq9r&*vs} zc6N)T-p+23X+7!(drs=@>=-#=O~Edv^;qY4c7@K)u95U_XV=J_o^e+9c>GhI^7t$9 zo^=12C+h6%E<$H^F{5Xk)Z5*G2%Xu*q~7yHy*+=$Q_(Ia_2~Cgcy8Q=7`C>EyIix2 zx25}UcQ??N&$-?Ro!P~V-tLZ|x4R<|I|w$2%+b!w8+(#f+Z+7bovdi$6EC zJ5$)j-rZ@Z{zp&8J8#^#+Z{M)ciymzy}R!<)tCFQ&w{V=wT5=*54+gA`|tQ4sxKZ# ztsR5^LPQ>SA)k+nW>2mk#Ua-fXT_*HB%V+2zq3-DZ4u|s0(zc5josf}S!%C;u|2F# z&)7U}`#lZhfqid2Putu5@A&7ti+VfeA7Y*I8T^}eaeHU}&GpmsN6gcp{Q3A*{5>a( zAIHJ&ZEPI*{btXMpLF8etG9RmSoiGRzle3u>|%HQo7c~@etgV0?>OdDJ`?R?a{kTr zoHNtBDUZsVn9LuZu`ghaM%~)Ge-U+Sc5z<6nf0gj+|W9x=Z2Wc|E2%@(YhX;z_ diff --git a/technology/freepdk45/gds_lib/dff.gds b/technology/freepdk45/gds_lib/dff.gds index 2a6004d145d7ef077006b8a6b4a6abce4d37eef4..b054d462ac8b1169ec6649f828f85de4e50b0531 100644 GIT binary patch delta 87 zcmZ3smT}oyMkNL|1}3Hm1{p>s_NNS-3<3FOB$n1$qs^U1LHpcyC;`Cu)5Q|`7 M5cb)e#Ox9b0B$4>WdHyG delta 87 zcmZ3smT}oyMkNL|1}3Hm1{p>s_9s9#Cj&2o1cNy;d!mx6I1Z_eale^3!x$LEA{ZEi L!!{=|y95IOZCVa} diff --git a/technology/freepdk45/gds_lib/dummy_cell_1rw.gds b/technology/freepdk45/gds_lib/dummy_cell_1rw.gds index 28dd8124a44d4eec113c66a142d330e2a175f8e2..cbfcb06421b33ab8ed54140940c31b6e25d1928a 100644 GIT binary patch delta 394 zcmdlqh4Is5MkNL|1}3Hm1{p>s_NNS-3<3=N3}Ord$n1$qs^U1LHpZ=FRCN0P|Gxzg zJ25b@`2(>7jE0GC)?ktcDq~>asD~)y_JSyb(Gc;?8O-|3oR=6FI2ST7uwL4{fNeP= z=LVpzw?JJRCJS(gGj7wdKjV#OoPQIC)v*jnZ@xP kVisp5#4H#M5udE&05*%`JH#xIEXWKP4H4hG$YBB}0Hrr>hX4Qo delta 332 zcmew~nQ_w;MkNL|1}3Hm1{p>s_9qP747?1S3UJOfEYvrRk7|vqQT;mmF(w(%;NYCF^jVjVit^sh);fG4>pVAJH#xIEXXVv4H4g* IB~0Hc*t(*OVf diff --git a/technology/freepdk45/gds_lib/replica_cell_1rw.gds b/technology/freepdk45/gds_lib/replica_cell_1rw.gds index 67c9bdf7f050b4179bb908ce413dd244f37f6bb4..cef0722d49c6456cabf4a9c5df43f82b86c40383 100644 GIT binary patch delta 414 zcmdlomGR3IMkNL|1}3Hm1{p>s_NNS-3<3=N3_=XX$n1$qs^Yk$;;R)-;u}}ZXH*2ra@0dqaeF~j!Dxv1<}9Z99Euwl7+Bna%78TMLk0%MLkJoo zzFCWVJtOBOh%oD=&5wANGlFz+yoKoETnNzxqaos(SMfn~frNp&k^~a96&-=Pz5}^H znzIr}A4bp+@ySoD;}!RVQ~}Kf(jZj~P}LwBB0f3AWeDf09c&5$H4uxC*h|zXW delta 320 zcmew|g>lPNMkNL|1}3Hm1{p>s_9qP747?1S42BG{$n1$qs^Yk$;kzZxG+2DHiY?eI w)=-F9AX$)Ea2hPWd5LX2lWGG{$$ka~rUsxAM+OFFCKwG92hp2f*!OS&01UQPLI3~& diff --git a/technology/freepdk45/gds_lib/sense_amp.gds b/technology/freepdk45/gds_lib/sense_amp.gds index 53c499f95f892bd63084ab55e93de706bd6a492d..edff18ab758dbd712a4b7cae6bdb3703ee1307e5 100644 GIT binary patch delta 80 zcmaD?^e$hCfsKKQDS|YGa(7wh~h{1B09+0|S^o z45a@8`7jzRK3Ps%UJ Date: Thu, 16 Sep 2021 15:42:02 -0700 Subject: [PATCH 25/28] Change via2 to 65nm to be compatible with Calibre FreePDK45 deck --- technology/freepdk45/tech/freepdk45.lydrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/technology/freepdk45/tech/freepdk45.lydrc b/technology/freepdk45/tech/freepdk45.lydrc index 1d719e33..91c7c30f 100644 --- a/technology/freepdk45/tech/freepdk45.lydrc +++ b/technology/freepdk45/tech/freepdk45.lydrc @@ -199,7 +199,8 @@ metal2_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL [ metal2_gt90, metal2_gt270, metal2_gt500, metal2_gt900, metal2_gt1500 ].each { |l| l.forget } # via2 -via2.edges.without_length(70.nm).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 70nm") +# 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") From be92282150b9886cb6db5ebbf67939565df69580 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 20 Sep 2021 11:02:40 -0700 Subject: [PATCH 26/28] Prefer open source over commercial --- compiler/verify/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index e9769975..b4a3f254 100644 --- a/compiler/verify/__init__.py +++ b/compiler/verify/__init__.py @@ -32,9 +32,9 @@ if not OPTS.check_lvsdrc: # OPTS.magic_exe = None else: debug.info(1, "Finding DRC/LVS/PEX tools.") - OPTS.drc_exe = get_tool("DRC", ["klayout", "calibre", "assura", "magic"], drc_name) - OPTS.lvs_exe = get_tool("LVS", ["klayout", "calibre", "assura", "netgen"], lvs_name) - OPTS.pex_exe = get_tool("PEX", ["klayout", "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"]) From f2882782e7ce64fc6c39630f210a2916afa23063 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 20 Sep 2021 11:05:49 -0700 Subject: [PATCH 27/28] Use calibre by default until klayout LVS is clean. --- technology/freepdk45/tech/tech.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index de3629e3..f5decd3c 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -460,8 +460,8 @@ parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of d # Technology Tool Preferences ################################################### -drc_name = "klayout" -lvs_name = "klayout" -pex_name = "klayout" +drc_name = "calibre" +lvs_name = "calibre" +pex_name = "calibre" blackbox_bitcell = False From fe077e79d5f6a54f70a85b65ae024210e1575fec Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 20 Sep 2021 11:06:27 -0700 Subject: [PATCH 28/28] Use local temp DRC/LVS rules file for running. --- compiler/verify/klayout.py | 19 ++++++++------- compiler/verify/magic.py | 47 +++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/compiler/verify/klayout.py b/compiler/verify/klayout.py index 4926c4c8..b5e1fb82 100644 --- a/compiler/verify/klayout.py +++ b/compiler/verify/klayout.py @@ -34,11 +34,12 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa # 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 - drc_file = OPTS.openram_tech + "tech/{}.lydrc".format(OPTS.tech_name) - if os.path.exists(drc_file): - shutil.copy(drc_file, output_path) + 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(drc_file)) + 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" @@ -102,11 +103,13 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out output_path = OPTS.openram_temp # Copy .lylvs file into the output directory - lvs_file = OPTS.openram_tech + "tech/{}.lylvs".format(OPTS.tech_name) - if os.path.exists(lvs_file): - shutil.copy(lvs_file, output_path) + 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(lvs_file)) + debug.warning("Could not locate file: {}".format(full_lvs_file)) run_file = output_path + "/run_lvs.sh" f = open(run_file, "w") 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: