From b3d1161957aa0a13f0af5b2764f8c53070dab151 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 08:19:25 -0700 Subject: [PATCH 01/67] Add u+x permissions to new tests --- compiler/tests/10_write_driver_array_1rw_1r_test.py | 0 compiler/tests/10_write_mask_and_array_1rw_1r_test.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 compiler/tests/10_write_driver_array_1rw_1r_test.py mode change 100644 => 100755 compiler/tests/10_write_mask_and_array_1rw_1r_test.py diff --git a/compiler/tests/10_write_driver_array_1rw_1r_test.py b/compiler/tests/10_write_driver_array_1rw_1r_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/10_write_mask_and_array_1rw_1r_test.py b/compiler/tests/10_write_mask_and_array_1rw_1r_test.py old mode 100644 new mode 100755 From a32b5b13e8852a9f68fafbe8fc888aa18fca2160 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 08:26:15 -0700 Subject: [PATCH 02/67] Rename nwell yoffset for consistency --- compiler/pgates/pgate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 09074960..104d7a06 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -150,7 +150,7 @@ class pgate(design.design): """ Extend the n/p wells to cover whole cell """ # This should match the cells in the cell library - self.nwell_y_offset = 0.48 * self.height + self.nwell_yoffset = 0.48 * self.height full_height = self.height + 0.5 * self.m1_width # FIXME: float rounding problem @@ -158,8 +158,8 @@ class pgate(design.design): # Add a rail width to extend the well to the top of the rail nwell_max_offset = max(self.find_highest_layer_coords("nwell").y, full_height) - nwell_position = vector(0, self.nwell_y_offset) - vector(self.well_extend_active, 0) - nwell_height = nwell_max_offset - self.nwell_y_offset + nwell_position = vector(0, self.nwell_yoffset) - vector(self.well_extend_active, 0) + nwell_height = nwell_max_offset - self.nwell_yoffset self.add_rect(layer="nwell", offset=nwell_position, width=self.width + 2 * self.well_extend_active, @@ -175,7 +175,7 @@ class pgate(design.design): pwell_min_offset = min(self.find_lowest_layer_coords("pwell").y, -0.5 * self.m1_width) pwell_position = vector(-self.well_extend_active, pwell_min_offset) - pwell_height = self.nwell_y_offset - pwell_position.y + pwell_height = self.nwell_yoffset - pwell_position.y self.add_rect(layer="pwell", offset=pwell_position, width=self.width + 2 * self.well_extend_active, From cddb16dabce5c1d945c9bb80a21ea871ca564a5f Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 09:17:39 -0700 Subject: [PATCH 03/67] Separate active and poly contact to gate rule --- compiler/base/design.py | 3 ++- compiler/pgates/pnand2.py | 4 ++-- compiler/pgates/pnand3.py | 7 +++---- compiler/pgates/precharge.py | 2 +- compiler/pgates/ptx.py | 6 +++--- technology/freepdk45/tech/tech.py | 4 ++-- technology/scn4m_subm/tech/tech.py | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/compiler/base/design.py b/compiler/base/design.py index 1fded37e..2b2d7711 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -205,7 +205,8 @@ class design(hierarchy_design): print("poly_to_active", self.poly_to_active) print("poly_extend_active", self.poly_extend_active) print("poly_to_contact", self.poly_to_contact) - print("contact_to_gate", self.contact_to_gate) + print("active_contact_to_gate", self.active_contact_to_gate) + print("poly_contact_to_gate", self.poly_contact_to_gate) print("well_enclose_active", self.well_enclose_active) print("implant_enclose_active", self.implant_enclose_active) print("implant_space", self.implant_space) diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 12323885..9cc530c0 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -184,7 +184,7 @@ class pnand2(pgate.pgate): # doesn't use nmos uy because that is calculated using offset + poly height active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height - active_to_poly_contact2 = active_top + drc("contact_to_gate") + 0.5 * self.route_layer_width + active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width self.inputA_yoffset = max(active_contact_to_poly_contact, active_to_poly_contact, active_to_poly_contact2) @@ -200,7 +200,7 @@ class pnand2(pgate.pgate): # active_contact_to_poly_contact = self.output_yoffset - self.route_layer_space - 0.5 * contact.poly_contact.second_layer_height # active_bottom = self.pmos1_inst.by() # active_to_poly_contact = active_bottom - self.poly_to_active - 0.5 * contact.poly_contact.first_layer_height - # active_to_poly_contact2 = active_bottom - drc("contact_to_gate") - 0.5 * self.route_layer_width + # active_to_poly_contact2 = active_bottom - self.poly_contact_to_gate - 0.5 * self.route_layer_width # self.inputB_yoffset = min(active_contact_to_poly_contact, # active_to_poly_contact, # active_to_poly_contact2) diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index e227d7af..03652b9b 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -222,7 +222,7 @@ class pnand3(pgate.pgate): # doesn't use nmos uy because that is calculated using offset + poly height active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height - active_to_poly_contact2 = active_top + drc("contact_to_gate") + 0.5 * self.route_layer_width + active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width self.inputA_yoffset = max(active_contact_to_poly_contact, active_to_poly_contact, active_to_poly_contact2) @@ -233,15 +233,14 @@ class pnand3(pgate.pgate): "A", position="left") - # Put B right on the well line - self.inputB_yoffset = self.inputA_yoffset + non_contact_pitch + self.inputB_yoffset = self.inputA_yoffset + 1.2 * self.m3_pitch self.route_input_gate(self.pmos2_inst, self.nmos2_inst, self.inputB_yoffset, "B", position="center") - self.inputC_yoffset = self.inputB_yoffset + non_contact_pitch + self.inputC_yoffset = self.inputB_yoffset + 1.2 * self.m3_pitch self.route_input_gate(self.pmos3_inst, self.nmos3_inst, self.inputC_yoffset, diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index fdce1a35..dc016cab 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -196,7 +196,7 @@ class precharge(design.design): pin_offset = self.lower_pmos_inst.get_pin("G").lr() # This is an extra space down for some techs with contact to active spacing contact_space = max(self.poly_space, - self.contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height + self.poly_contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height offset = pin_offset - vector(0, contact_space) self.add_via_stack_center(from_layer="poly", to_layer=self.en_layer, diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index 9af08e61..8be003b2 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -196,7 +196,7 @@ class ptx(design.design): # This is the spacing between the poly gates self.min_poly_pitch = self.poly_space + self.poly_width self.contacted_poly_pitch = self.poly_space + contact.poly_contact.width - self.contact_pitch = 2 * self.contact_to_gate + self.poly_width + self.contact_width + self.contact_pitch = 2 * self.active_contact_to_gate + self.poly_width + self.contact_width self.poly_pitch = max(self.min_poly_pitch, self.contacted_poly_pitch, self.contact_pitch) @@ -206,7 +206,7 @@ class ptx(design.design): # Active width is determined by enclosure on both ends and contacted pitch, # at least one poly and n-1 poly pitches self.active_width = 2 * self.end_to_contact + self.active_contact.width \ - + 2 * self.contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch + + 2 * self.active_contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch # Active height is just the transistor width self.active_height = self.tx_width @@ -321,7 +321,7 @@ class ptx(design.design): """ # poly is one contacted spacing from the end and down an extension poly_offset = self.contact_offset \ - + vector(0.5 * self.active_contact.width + 0.5 * self.poly_width + self.contact_to_gate, 0) + + vector(0.5 * self.active_contact.width + 0.5 * self.poly_width + self.active_contact_to_gate, 0) # poly_positions are the bottom center of the poly gates self.poly_positions = [] diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index c0379e44..6de01254 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -234,9 +234,9 @@ drc.add_enclosure("active", enclosure = 0.005) # CONTACT.6 Minimum spacing of contact and gate -drc["contact_to_gate"] = 0.0375 #changed from 0.035 +drc["active_contact_to_gate"] = 0.0375 #changed from 0.035 # CONTACT.7 Minimum spacing of contact and poly -drc["contact_to_poly"] = 0.090 +drc["poly_contact_to_gate"] = 0.090 # CONTACT.1 Minimum width of contact # CONTACT.2 Minimum spacing of contact diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index 745b381e..55826ec5 100644 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -217,9 +217,9 @@ drc.add_enclosure("active", layer = "contact", enclosure = _lambda_) # Reserved for other technologies -drc["contact_to_gate"] = 2*_lambda_ +drc["active_contact_to_gate"] = 2*_lambda_ # 5.4 Minimum spacing to gate of transistor -drc["contact_to_poly"] = 2*_lambda_ +drc["poly_contact_to_gate"] = 2*_lambda_ # 6.1 Exact contact size # 5.3 Minimum contact spacing From 13409083301834f77115e4abd3a417dff5009e4f Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 09:24:26 -0700 Subject: [PATCH 04/67] Remove fudge factor for pin spacing --- compiler/pgates/pnand3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 03652b9b..1fd5146f 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -233,14 +233,14 @@ class pnand3(pgate.pgate): "A", position="left") - self.inputB_yoffset = self.inputA_yoffset + 1.2 * self.m3_pitch + self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch self.route_input_gate(self.pmos2_inst, self.nmos2_inst, self.inputB_yoffset, "B", position="center") - self.inputC_yoffset = self.inputB_yoffset + 1.2 * self.m3_pitch + self.inputC_yoffset = self.inputB_yoffset + self.m3_pitch self.route_input_gate(self.pmos3_inst, self.nmos3_inst, self.inputC_yoffset, From 98ec9442c61966cd602eebfddf68990dcddcdc30 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 10:00:00 -0700 Subject: [PATCH 05/67] Add npc enclosure for pnand2, pnand3, pnor2 --- compiler/pgates/pgate.py | 27 +++++++++++++++++++++++++++ compiler/pgates/pnand2.py | 4 ++++ compiler/pgates/pnand3.py | 3 +++ compiler/pgates/pnor2.py | 3 +++ 4 files changed, 37 insertions(+) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 104d7a06..bb105f36 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -43,6 +43,9 @@ class pgate(design.design): self.route_layer_space = getattr(self, "{}_space".format(self.route_layer)) self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer)) + # hack for enclosing input pin with npc + self.input_pin_vias = [] + # This is the space from a S/D contact to the supply rail contact_to_vdd_rail_space = 0.5 * self.route_layer_width + self.route_layer_space # This is a poly-to-poly of a flipped cell @@ -132,6 +135,8 @@ class pgate(design.design): offset=contact_offset, directions=directions) + self.input_pin_vias.append(via) + self.add_layout_pin_rect_center(text=name, layer=self.route_layer, offset=contact_offset, @@ -146,6 +151,28 @@ class pgate(design.design): height=contact.poly_contact.first_layer_width, width=left_gate_offset.x - contact_offset.x) + def enclose_npc(self): + """ Enclose the poly contacts with npc layer """ + ll = None + ur = None + for via in self.input_pin_vias: + # Find ll/ur + if not ll: + ll = via.ll() + else: + ll = ll.min(via.ll()) + if not ur: + ur = via.ur() + else: + ur = ur.max(via.ur()) + + npc_enclose_poly = drc("npc_enclose_poly") + npc_enclose_offset = vector(npc_enclose_poly, npc_enclose_poly) + self.add_rect(layer="npc", + offset=ll - npc_enclose_offset, + width=(ur.x - ll.x) + 2 * npc_enclose_poly, + height=(ur.y - ll.y) + 2 * npc_enclose_poly) + def extend_wells(self): """ Extend the n/p wells to cover whole cell """ diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 9cc530c0..6a65f721 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -212,6 +212,10 @@ class pnand2(pgate.pgate): "B", position="center") + if OPTS.tech_name == "sky130": + self.enclose_npc() + + def route_output(self): """ Route the Z output """ diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 1fd5146f..0c2bc081 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -246,6 +246,9 @@ class pnand3(pgate.pgate): self.inputC_yoffset, "C", position="right") + + if OPTS.tech_name == "sky130": + self.enclose_npc() def route_output(self): """ Route the Z output """ diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index 3cceb7f4..7f75ddd1 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -211,6 +211,9 @@ class pnor2(pgate.pgate): self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch + if OPTS.tech_name == "sky130": + self.enclose_npc() + def route_output(self): """ Route the Z output """ # PMOS2 (right) drain From 93d65e84e1ae95a4c754520a3fe42caccdc1894e Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 10:26:49 -0700 Subject: [PATCH 06/67] Fix power pin layer problems in delay line --- compiler/modules/delay_chain.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index ad9bc1cc..c07395f5 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -178,10 +178,14 @@ class delay_chain(design.design): load_list = self.load_inst_map[inst] for pin_name in ["vdd", "gnd"]: pin = load_list[0].get_pin(pin_name) - self.add_power_pin(pin_name, pin.rc() - vector(self.m1_pitch, 0)) + self.add_power_pin(pin_name, + pin.rc() - vector(self.m1_pitch, 0), + start_layer=pin.layer) - pin = load_list[-1].get_pin(pin_name) - self.add_power_pin(pin_name, pin.rc() - vector(0.5 * self.m1_pitch, 0)) + pin = load_list[-2].get_pin(pin_name) + self.add_power_pin(pin_name, + pin.rc() - vector(self.m1_pitch, 0), + start_layer=pin.layer) def add_layout_pins(self): From 4bc3df8931602269c242457ceed2f95c4da8e4eb Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 11:54:36 -0700 Subject: [PATCH 07/67] Add get_tx_insts and expand add_enclosure --- compiler/base/hierarchy_layout.py | 75 +++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 9331f31d..f90b3129 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -269,6 +269,23 @@ class layout(): width, end.y - start.y) + def get_tx_insts(self, tx_type=None): + """ + Return a list of the instances of given tx type. + """ + tx_list = [] + for i in self.insts: + try: + if tx_type and i.mod.tx_type == tx_type: + tx_list.append(i) + elif not tx_type: + if i.mod.tx_type == "nmos" or i.mod.tx_type == "pmos": + tx_list.append(i) + except AttributeError: + pass + + return tx_list + def get_pin(self, text): """ Return the pin or list of pins @@ -1301,26 +1318,48 @@ class layout(): height=ur.y - ll.y, width=ur.x - ll.x) - def add_enclosure(self, insts, layer="nwell"): - """ Add a layer that surrounds the given instances. Useful + def add_enclosure(self, insts, layer="nwell", extend=0, leftx=None, rightx=None, topy=None, boty=None): + """ + Add a layer that surrounds the given instances. Useful for creating wells, for example. Doesn't check for minimum widths or - spacings.""" + spacings. Extra arg can force a dimension to one side left/right top/bot. + """ - xmin = insts[0].lx() - ymin = insts[0].by() - xmax = insts[0].rx() - ymax = insts[0].uy() - for inst in insts: - xmin = min(xmin, inst.lx()) - ymin = min(ymin, inst.by()) - xmax = max(xmax, inst.rx()) - ymax = max(ymax, inst.uy()) - - self.add_rect(layer=layer, - offset=vector(xmin, ymin), - width=xmax - xmin, - height=ymax - ymin) + if leftx != None: + xmin = leftx + else: + xmin = insts[0].lx() + for inst in insts: + xmin = min(xmin, inst.lx()) + xmin = xmin - extend + if boty != None: + ymin = boty + else: + ymin = insts[0].by() + for inst in insts: + ymin = min(ymin, inst.by()) + ymin = ymin - extend + if rightx != None: + xmax = rightx + else: + xmax = insts[0].rx() + for inst in insts: + xmax = max(xmax, inst.rx()) + xmax = xmax + extend + if topy != None: + ymax = topy + else: + ymax = insts[0].uy() + for inst in insts: + ymax = max(ymax, inst.uy()) + ymax = ymax + extend + rect = self.add_rect(layer=layer, + offset=vector(xmin, ymin), + width=xmax - xmin, + height=ymax - ymin) + return rect + def copy_power_pins(self, inst, name): """ This will copy a power pin if it is on the lowest power_grid layer. @@ -1337,7 +1376,7 @@ class layout(): else: self.add_power_pin(name, pin.center(), start_layer=pin.layer) - + def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"): """ Add a single power pin from the lowest power_grid layer down to M1 (or li) at From e694622f2809bc588d94b0a28040534cd93fd293 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 11:54:59 -0700 Subject: [PATCH 08/67] use add_enclosure to extend implants --- compiler/pgates/pgate.py | 59 ++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index bb105f36..9fa8c916 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -135,8 +135,6 @@ class pgate(design.design): offset=contact_offset, directions=directions) - self.input_pin_vias.append(via) - self.add_layout_pin_rect_center(text=name, layer=self.route_layer, offset=contact_offset, @@ -151,27 +149,7 @@ class pgate(design.design): height=contact.poly_contact.first_layer_width, width=left_gate_offset.x - contact_offset.x) - def enclose_npc(self): - """ Enclose the poly contacts with npc layer """ - ll = None - ur = None - for via in self.input_pin_vias: - # Find ll/ur - if not ll: - ll = via.ll() - else: - ll = ll.min(via.ll()) - if not ur: - ur = via.ur() - else: - ur = ur.max(via.ur()) - - npc_enclose_poly = drc("npc_enclose_poly") - npc_enclose_offset = vector(npc_enclose_poly, npc_enclose_poly) - self.add_rect(layer="npc", - offset=ll - npc_enclose_offset, - width=(ur.x - ll.x) + 2 * npc_enclose_poly, - height=(ur.y - ll.y) + 2 * npc_enclose_poly) + return via def extend_wells(self): """ Extend the n/p wells to cover whole cell """ @@ -179,6 +157,7 @@ class pgate(design.design): # This should match the cells in the cell library self.nwell_yoffset = 0.48 * self.height full_height = self.height + 0.5 * self.m1_width + # FIXME: float rounding problem if "nwell" in layer: @@ -212,6 +191,8 @@ class pgate(design.design): offset=pwell_position, width=self.width + 2 * self.well_extend_active, height=pwell_height) + + self.extend_implants() def add_nwell_contact(self, pmos, pmos_pos): """ Add an nwell contact next to the given pmos device. """ @@ -267,6 +248,36 @@ class pgate(design.design): # Return the top of the well + def extend_implants(self): + """ + Add top-to-bottom implants for adjacency issues in s8. + """ + nmos_insts = self.get_tx_insts("nmos") + pmos_insts = self.get_tx_insts("pmos") + ntap_insts = [self.nwell_contact] + ptap_insts = [self.pwell_contact] + + self.add_enclosure(nmos_insts, + layer="nimplant", + extend=self.implant_enclose_active, + leftx=0, + boty=0) + self.add_enclosure(pmos_insts, + layer="pimplant", + extend=self.implant_enclose_active, + leftx=0, + topy=self.height) + self.add_enclosure(ntap_insts, + layer="nimplant", + extend=self.implant_enclose_active, + rightx=self.width, + topy=self.height) + self.add_enclosure(ptap_insts, + layer="pimplant", + extend=self.implant_enclose_active, + rightx=self.width, + boty=0) + def add_pwell_contact(self, nmos, nmos_pos): """ Add an pwell contact next to the given nmos device. """ @@ -295,7 +306,7 @@ class pgate(design.design): offset=contact_offset.scale(1, 0.5), width=self.pwell_contact.mod.second_layer_width, height=contact_offset.y) - + # Now add the full active and implant for the NMOS # active_offset = nmos_pos + vector(nmos.active_width,0) # This might be needed if the spacing between the actives From 6c523a75561cb3e4ff9e65b1dd0b953c4b028a37 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 11:55:44 -0700 Subject: [PATCH 09/67] use add_enclosure for npc contacts --- compiler/pgates/pnand2.py | 22 +++++++++++----------- compiler/pgates/pnand3.py | 32 ++++++++++++++++---------------- compiler/pgates/pnor2.py | 24 ++++++++++++------------ 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 6a65f721..3974d95b 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -189,11 +189,11 @@ class pnand2(pgate.pgate): active_to_poly_contact, active_to_poly_contact2) - self.route_input_gate(self.pmos1_inst, - self.nmos1_inst, - self.inputA_yoffset, - "A", - position="center") + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + position="center") self.inputB_yoffset = self.inputA_yoffset + 2 * self.m3_pitch # # active contact metal to poly contact metal spacing @@ -206,14 +206,14 @@ class pnand2(pgate.pgate): # active_to_poly_contact2) # This will help with the wells and the input/output placement - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - self.inputB_yoffset, - "B", - position="center") + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="center") if OPTS.tech_name == "sky130": - self.enclose_npc() + self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly")) def route_output(self): diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 0c2bc081..ff988d88 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -227,28 +227,28 @@ class pnand3(pgate.pgate): active_to_poly_contact, active_to_poly_contact2) - self.route_input_gate(self.pmos1_inst, - self.nmos1_inst, - self.inputA_yoffset, - "A", - position="left") + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + position="left") self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - self.inputB_yoffset, - "B", - position="center") + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="center") self.inputC_yoffset = self.inputB_yoffset + self.m3_pitch - self.route_input_gate(self.pmos3_inst, - self.nmos3_inst, - self.inputC_yoffset, - "C", - position="right") + cpin = self.route_input_gate(self.pmos3_inst, + self.nmos3_inst, + self.inputC_yoffset, + "C", + position="right") if OPTS.tech_name == "sky130": - self.enclose_npc() + self.add_enclosure([apin, bpin, cpin], "npc", drc("npc_enclose_poly")) def route_output(self): """ Route the Z output """ diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index 7f75ddd1..908bba82 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -195,24 +195,24 @@ class pnor2(pgate.pgate): self.inputB_yoffset = bottom_pin_offset + self.m1_nonpref_pitch self.inputA_yoffset = self.inputB_yoffset + self.m1_nonpref_pitch - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - self.inputB_yoffset, - "B", - position="right", - directions=("V", "V")) + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="right", + directions=("V", "V")) # This will help with the wells and the input/output placement - self.route_input_gate(self.pmos1_inst, - self.nmos1_inst, - self.inputA_yoffset, - "A", - directions=("V", "V")) + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + directions=("V", "V")) self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch if OPTS.tech_name == "sky130": - self.enclose_npc() + self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly")) def route_output(self): """ Route the Z output """ From ba92467fec2967a7f652c540c10ff80535aa3788 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 12:07:47 -0700 Subject: [PATCH 10/67] Add no well enclosure for techs without wells --- compiler/pgates/pgate.py | 62 +++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 9fa8c916..02a04b97 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -252,31 +252,47 @@ class pgate(design.design): """ Add top-to-bottom implants for adjacency issues in s8. """ + if self.add_wells: + rightx = None + else: + rightx = self.width + nmos_insts = self.get_tx_insts("nmos") + if len(nmos_insts) > 0: + self.add_enclosure(nmos_insts, + layer="nimplant", + extend=self.implant_enclose_active, + leftx=0, + rightx=rightx, + boty=0) + pmos_insts = self.get_tx_insts("pmos") - ntap_insts = [self.nwell_contact] - ptap_insts = [self.pwell_contact] - - self.add_enclosure(nmos_insts, - layer="nimplant", - extend=self.implant_enclose_active, - leftx=0, - boty=0) - self.add_enclosure(pmos_insts, - layer="pimplant", - extend=self.implant_enclose_active, - leftx=0, - topy=self.height) - self.add_enclosure(ntap_insts, - layer="nimplant", - extend=self.implant_enclose_active, - rightx=self.width, - topy=self.height) - self.add_enclosure(ptap_insts, - layer="pimplant", - extend=self.implant_enclose_active, - rightx=self.width, - boty=0) + if len(pmos_insts) > 0: + self.add_enclosure(pmos_insts, + layer="pimplant", + extend=self.implant_enclose_active, + leftx=0, + rightx=rightx, + topy=self.height) + + try: + ntap_insts = [self.nwell_contact] + self.add_enclosure(ntap_insts, + layer="nimplant", + extend=self.implant_enclose_active, + rightx=self.width, + topy=self.height) + except AttributeError: + pass + try: + ptap_insts = [self.pwell_contact] + self.add_enclosure(ptap_insts, + layer="pimplant", + extend=self.implant_enclose_active, + rightx=self.width, + boty=0) + except AttributeError: + pass def add_pwell_contact(self, nmos, nmos_pos): """ Add an pwell contact next to the given nmos device. """ From da900b89ba6759778942114c81068cb23d0b1c41 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 24 Jun 2020 13:48:30 -0700 Subject: [PATCH 11/67] Only expand implants in sky130 --- compiler/pgates/pgate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 02a04b97..809ffbd9 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -191,8 +191,9 @@ class pgate(design.design): offset=pwell_position, width=self.width + 2 * self.well_extend_active, height=pwell_height) - - self.extend_implants() + + if OPTS.tech_name == "sky130": + self.extend_implants() def add_nwell_contact(self, pmos, pmos_pos): """ Add an nwell contact next to the given pmos device. """ From 57b6d49edb4f39d4c74c0f5587eb078f101c921b Mon Sep 17 00:00:00 2001 From: jcirimel Date: Thu, 25 Jun 2020 06:32:07 -0700 Subject: [PATCH 12/67] fix pinv size bining --- compiler/pgates/pgate.py | 2 +- compiler/pgates/pinv.py | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 09074960..ef1445c0 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -379,4 +379,4 @@ class pgate(design.design): return(scaled_bins) def bin_accuracy(self, ideal_width, width): - return abs(1-(ideal_width - width)/ideal_width) + return 1-abs((ideal_width - width)/ideal_width) diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index de0ba8e4..1429e0c8 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -31,6 +31,9 @@ class pinv(pgate.pgate): height is usually the same as the 6t library cell and is measured from center of rail to rail. """ + # binning %error tracker + bin_count = 0 + bin_error = 0 def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True): @@ -44,7 +47,7 @@ class pinv(pgate.pgate): self.nmos_size = size self.pmos_size = beta * size self.beta = beta - + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): @@ -166,30 +169,37 @@ class pinv(pgate.pgate): valid_pmos = [] for bin in pmos_bins: - if self.bin_accuracy(self.pmos_width, bin[0]) > accuracy_requirement: + if abs(self.bin_accuracy(self.pmos_width, bin[0])) > accuracy_requirement and abs(self.bin_accuracy(self.pmos_width, bin[0])) <= 1: valid_pmos.append(bin) valid_pmos.sort(key = operator.itemgetter(1)) valid_nmos = [] for bin in nmos_bins: - if self.bin_accuracy(self.nmos_width, bin[0]) > accuracy_requirement: + if abs(self.bin_accuracy(self.nmos_width, bin[0])) > accuracy_requirement and abs(self.bin_accuracy(self.nmos_width, bin[0])) <= 1: valid_nmos.append(bin) valid_nmos.sort(key = operator.itemgetter(1)) for bin in valid_pmos: if bin[0]/bin[1] < pmos_height_available: self.pmos_width = bin[0]/bin[1] - pmos_mults = valid_pmos[0][1] + pmos_mults = bin[1] break for bin in valid_nmos: if bin[0]/bin[1] < nmos_height_available: self.nmos_width = bin[0]/bin[1] - nmos_mults = valid_pmos[0][1] + nmos_mults = bin[1] break self.tx_mults = max(pmos_mults, nmos_mults) - + debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("pmos", self.pmos_width, pmos_mults, self.pmos_width * pmos_mults, self.pmos_size * drc("minwidth_tx"))) + debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("nmos", self.nmos_width, nmos_mults, self.nmos_width * nmos_mults, self.nmos_size * drc("minwidth_tx"))) + pinv.bin_count += 1 + pinv.bin_error += abs((self.pmos_width * pmos_mults) - (self.pmos_size * drc("minwidth_tx"))/(self.pmos_size * drc("minwidth_tx"))) + pinv.bin_count += 1 + pinv.bin_error += abs((self.nmos_width * nmos_mults) - (self.nmos_size * drc("minwidth_tx"))/(self.nmos_size * drc("minwidth_tx"))) + debug.info(2, "pinv bin count: {0} pinv bin error: {1} percent error {2}".format(pinv.bin_count, pinv.bin_error, pinv.bin_error/pinv.bin_count)) + def add_ptx(self): """ Create the PMOS and NMOS transistors. """ self.nmos = factory.create(module_type="ptx", From 59562f2b92563ab718228a629854ede31523e0fe Mon Sep 17 00:00:00 2001 From: jcirimel Date: Thu, 25 Jun 2020 06:44:07 -0700 Subject: [PATCH 13/67] move accuracy_requirement from techfile to config --- compiler/options.py | 2 +- compiler/pgates/pgate.py | 4 ++-- compiler/pgates/pinv.py | 6 +++--- compiler/pgates/pinv_dec.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/options.py b/compiler/options.py index 976595ad..d229b96e 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -58,7 +58,7 @@ class options(optparse.Values): delay_chain_stages = 9 delay_chain_fanout_per_stage = 4 - + accuracy_requirement = 0.75 ################### # Debug options. diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 80a568fc..7f834fc4 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -15,7 +15,7 @@ from vector import vector from globals import OPTS if(OPTS.tech_name == "sky130"): - from tech import nmos_bins, pmos_bins, accuracy_requirement + from tech import nmos_bins, pmos_bins class pgate(design.design): @@ -372,7 +372,7 @@ class pgate(design.design): select = -1 for i in reversed(range(0, len(scaled_bins))): - if abs(target_width - scaled_bins[i])/target_width <= 1-accuracy_requirement: + if abs(target_width - scaled_bins[i])/target_width <= 1-OPTS.accuracy_requirement: select = i break if select == -1: diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 1429e0c8..8b512e72 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -20,7 +20,7 @@ from sram_factory import factory from errors import drc_error if(OPTS.tech_name == "sky130"): - from tech import nmos_bins, pmos_bins, accuracy_requirement + from tech import nmos_bins, pmos_bins class pinv(pgate.pgate): @@ -169,13 +169,13 @@ class pinv(pgate.pgate): valid_pmos = [] for bin in pmos_bins: - if abs(self.bin_accuracy(self.pmos_width, bin[0])) > accuracy_requirement and abs(self.bin_accuracy(self.pmos_width, bin[0])) <= 1: + if abs(self.bin_accuracy(self.pmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.pmos_width, bin[0])) <= 1: valid_pmos.append(bin) valid_pmos.sort(key = operator.itemgetter(1)) valid_nmos = [] for bin in nmos_bins: - if abs(self.bin_accuracy(self.nmos_width, bin[0])) > accuracy_requirement and abs(self.bin_accuracy(self.nmos_width, bin[0])) <= 1: + if abs(self.bin_accuracy(self.nmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.nmos_width, bin[0])) <= 1: valid_nmos.append(bin) valid_nmos.sort(key = operator.itemgetter(1)) diff --git a/compiler/pgates/pinv_dec.py b/compiler/pgates/pinv_dec.py index 3960f1bf..3ce6ad80 100644 --- a/compiler/pgates/pinv_dec.py +++ b/compiler/pgates/pinv_dec.py @@ -14,7 +14,7 @@ from globals import OPTS from sram_factory import factory if(OPTS.tech_name == "sky130"): - from tech import nmos_bins, pmos_bins, accuracy_requirement + from tech import nmos_bins, pmos_bins class pinv_dec(pinv.pinv): From 5941e01b515b11a8b43168ea84989b4f40a7126a Mon Sep 17 00:00:00 2001 From: jcirimel Date: Thu, 25 Jun 2020 08:02:08 -0700 Subject: [PATCH 14/67] add missing parens --- compiler/pgates/pinv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 8b512e72..aa0f7d9a 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -195,9 +195,9 @@ class pinv(pgate.pgate): debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("pmos", self.pmos_width, pmos_mults, self.pmos_width * pmos_mults, self.pmos_size * drc("minwidth_tx"))) debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("nmos", self.nmos_width, nmos_mults, self.nmos_width * nmos_mults, self.nmos_size * drc("minwidth_tx"))) pinv.bin_count += 1 - pinv.bin_error += abs((self.pmos_width * pmos_mults) - (self.pmos_size * drc("minwidth_tx"))/(self.pmos_size * drc("minwidth_tx"))) + pinv.bin_error += abs(((self.pmos_width * pmos_mults) - (self.pmos_size * drc("minwidth_tx")))/(self.pmos_size * drc("minwidth_tx"))) pinv.bin_count += 1 - pinv.bin_error += abs((self.nmos_width * nmos_mults) - (self.nmos_size * drc("minwidth_tx"))/(self.nmos_size * drc("minwidth_tx"))) + pinv.bin_error += abs(((self.nmos_width * nmos_mults) - (self.nmos_size * drc("minwidth_tx")))/(self.nmos_size * drc("minwidth_tx"))) debug.info(2, "pinv bin count: {0} pinv bin error: {1} percent error {2}".format(pinv.bin_count, pinv.bin_error, pinv.bin_error/pinv.bin_count)) def add_ptx(self): From f84ee04fa9e000da364024f557a757a9e947e8d6 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 25 Jun 2020 14:03:59 -0700 Subject: [PATCH 15/67] Single bank passing. Parameterized gate column mux of dff height. End-cap only supply option instead of no vdd in bitcell. --- compiler/modules/bank.py | 14 ++++--- compiler/modules/bitcell_base_array.py | 16 ++++---- compiler/modules/hierarchical_decoder.py | 6 +-- compiler/modules/hierarchical_predecode.py | 44 +++++++++++----------- compiler/modules/replica_bitcell_array.py | 19 ++++++---- compiler/modules/replica_column.py | 13 +++++-- 6 files changed, 61 insertions(+), 51 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 12fcb6dd..6134b143 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -497,18 +497,20 @@ class bank(design.design): Create a 2:4 or 3:8 column address decoder. """ - # Height is a multiple of DFF so that it can be staggered - # and rows do not align with the control logic module - self.dff = factory.create(module_type="dff") + self.dff =factory.create(module_type="dff") if self.col_addr_size == 0: return elif self.col_addr_size == 1: - self.column_decoder = factory.create(module_type="pinvbuf", height=self.dff.height) + self.column_decoder = factory.create(module_type="pinvbuf", + height=self.dff.height) elif self.col_addr_size == 2: - self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", height=self.dff.height) + self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", + height=self.dff.height) + elif self.col_addr_size == 3: - self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=self.dff.height) + self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", + height=self.dff.height) else: # No error checking before? debug.error("Invalid column decoder?", -1) diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py index e601208b..6698df33 100644 --- a/compiler/modules/bitcell_base_array.py +++ b/compiler/modules/bitcell_base_array.py @@ -110,17 +110,17 @@ class bitcell_base_array(design.design): # For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps. try: - bitcell_no_vdd_pin = cell_properties.bitcell.no_vdd_via + end_caps_enabled = cell_properties.bitcell.end_caps except AttributeError: - bitcell_no_vdd_pin = False + end_caps_enabled = False # Add vdd/gnd via stacks - for row in range(self.row_size): - for col in range(self.column_size): - inst = self.cell_inst[row,col] - for pin_name in ["vdd", "gnd"]: - for pin in inst.get_pins(pin_name): - if not (pin_name == "vdd" and bitcell_no_vdd_pin): + if not end_caps_enabled: + for row in range(self.row_size): + for col in range(self.column_size): + inst = self.cell_inst[row, col] + for pin_name in ["vdd", "gnd"]: + for pin in inst.get_pins(pin_name): self.add_power_pin(name=pin_name, loc=pin.center(), directions=bitcell_power_pin_directions, diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 3233bdc8..141387b4 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -78,12 +78,10 @@ class hierarchical_decoder(design.design): def add_decoders(self): """ Create the decoders based on the number of pre-decodes """ - self.pre2_4 = factory.create(module_type="hierarchical_predecode2x4", - height=self.cell_height) + self.pre2_4 = factory.create(module_type="hierarchical_predecode2x4") self.add_mod(self.pre2_4) - self.pre3_8 = factory.create(module_type="hierarchical_predecode3x8", - height=self.cell_height) + self.pre3_8 = factory.create(module_type="hierarchical_predecode3x8") self.add_mod(self.pre3_8) def determine_predecodes(self, num_inputs): diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index f2274c6e..d4cb51ce 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -20,12 +20,17 @@ class hierarchical_predecode(design.design): def __init__(self, name, input_number, height=None): self.number_of_inputs = input_number + b = factory.create(module_type="bitcell") if not height: - b = factory.create(module_type="bitcell") self.cell_height = b.height - else: + self.column_decoder = False + elif height != b.height: self.cell_height = height - + self.column_decoder = True + else: + self.cell_height = b.height + self.column_decoder = False + self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) design.design.__init__(self, name) @@ -40,21 +45,21 @@ class hierarchical_predecode(design.design): def add_modules(self): """ Add the INV and AND gate modules """ - if self.number_of_inputs == 2: - self.and_mod = factory.create(module_type="and2_dec", - height=self.cell_height) - elif self.number_of_inputs == 3: - self.and_mod = factory.create(module_type="and3_dec", - height=self.cell_height) - elif self.number_of_inputs == 4: - self.and_mod = factory.create(module_type="and4_dec", - height=self.cell_height) + debug.check(self.number_of_inputs < 4, + "Invalid number of predecode inputs: {}".format(self.number_of_inputs)) + + if self.column_decoder: + and_type = "pand{}".format(self.number_of_inputs) + inv_type = "pinv" else: - debug.error("Invalid number of predecode inputs: {}".format(self.number_of_inputs), -1) + and_type = "and{}_dec".format(self.number_of_inputs) + inv_type = "inv_dec" + self.and_mod = factory.create(module_type=and_type, + height=self.cell_height) self.add_mod(self.and_mod) # This uses the pinv_dec parameterized cell - self.inv = factory.create(module_type="inv_dec", + self.inv = factory.create(module_type=inv_type, height=self.cell_height, size=1) self.add_mod(self.inv) @@ -80,7 +85,7 @@ class hierarchical_predecode(design.design): # Outputs from cells are on output layer if OPTS.tech_name == "sky130": self.bus_layer = "m1" - self.bus_directions = None + self.bus_directions = "nonpref" self.bus_pitch = self.m1_pitch self.bus_space = 1.5 * self.m1_space self.input_layer = "m2" @@ -88,7 +93,7 @@ class hierarchical_predecode(design.design): self.output_layer_pitch = self.li_pitch else: self.bus_layer = "m2" - self.bus_directions = None + self.bus_directions = "pref" self.bus_pitch = self.m2_pitch self.bus_space = self.m2_space # This requires a special jog to ensure to conflicts with the output layers @@ -238,10 +243,7 @@ class hierarchical_predecode(design.design): # add output so that it is just below the vdd or gnd rail # since this is where the p/n devices are and there are no # pins in the and gates. - if OPTS.tech_name == "sky130": - inv_out_pos = inv_out_pin.lr() - else: - inv_out_pos = inv_out_pin.rc() + inv_out_pos = inv_out_pin.rc() y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0) rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset) @@ -309,7 +311,7 @@ class hierarchical_predecode(design.design): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ # In sky130, we use hand-made decoder cells with vertical power - if OPTS.tech_name == "sky130": + if not self.column_decoder: for n in ["vdd", "gnd"]: # This makes a wire from top to bottom for both inv and and gates for i in [self.inv_inst, self.and_inst]: diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 05cb1ac6..57bb3cf9 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -380,19 +380,22 @@ class replica_bitcell_array(design.design): # For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps. try: - bitcell_no_vdd_pin = cell_properties.bitcell.no_vdd_via + if cell_properties.bitcell.end_caps_enabled: + supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst, + self.dummy_row_top_inst, self.dummy_row_bot_inst] + list(self.replica_col_inst.values()) + else: + supply_insts = self.insts except AttributeError: - bitcell_no_vdd_pin = False + supply_insts = self.insts for pin_name in ["vdd", "gnd"]: - for inst in self.insts: + for inst in supply_insts: pin_list = inst.get_pins(pin_name) for pin in pin_list: - if not (pin_name == "vdd" and bitcell_no_vdd_pin): - self.add_power_pin(name=pin_name, - loc=pin.center(), - directions=("V", "V"), - start_layer=pin.layer) + self.add_power_pin(name=pin_name, + loc=pin.center(), + directions=("V", "V"), + start_layer=pin.layer) def get_rbl_wl_name(self, port): """ Return the WL for the given RBL port """ diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index 3a58668c..58d35e7f 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -183,11 +183,16 @@ class replica_column(design.design): width=self.width, height=wl_pin.height()) - # For every second row and column, add a via for gnd and vdd - for row in range(row_range_min, row_range_max): - inst = self.cell_inst[row] + # For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps. + if end_caps_enabled: + supply_insts = [self.cell_inst[0], self.cell_inst[self.total_size - 1]] + else: + supply_insts = self.cell_inst.values() + + for inst in supply_insts: for pin_name in ["vdd", "gnd"]: - self.copy_layout_pin(inst, pin_name) + if pin_name in inst.mod.pins: + self.copy_layout_pin(inst, pin_name) def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, From 66df659ad464352d1305ce7014aa66ce38c514c9 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 25 Jun 2020 14:25:48 -0700 Subject: [PATCH 16/67] Col decoders are anything not bitcell pitch. --- compiler/modules/hierarchical_decoder.py | 6 ++++-- compiler/modules/hierarchical_predecode.py | 11 +++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 141387b4..3233bdc8 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -78,10 +78,12 @@ class hierarchical_decoder(design.design): def add_decoders(self): """ Create the decoders based on the number of pre-decodes """ - self.pre2_4 = factory.create(module_type="hierarchical_predecode2x4") + self.pre2_4 = factory.create(module_type="hierarchical_predecode2x4", + height=self.cell_height) self.add_mod(self.pre2_4) - self.pre3_8 = factory.create(module_type="hierarchical_predecode3x8") + self.pre3_8 = factory.create(module_type="hierarchical_predecode3x8", + height=self.cell_height) self.add_mod(self.pre3_8) def determine_predecodes(self, num_inputs): diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index d4cb51ce..a89f5fa6 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -24,12 +24,11 @@ class hierarchical_predecode(design.design): if not height: self.cell_height = b.height self.column_decoder = False - elif height != b.height: - self.cell_height = height - self.column_decoder = True else: - self.cell_height = b.height - self.column_decoder = False + self.cell_height = height + # If we are pitch matched to the bitcell, it's a predecoder + # otherwise it's a column decoder (out of pgates) + self.column_decoder = (height != b.height) self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) design.design.__init__(self, name) @@ -311,7 +310,7 @@ class hierarchical_predecode(design.design): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ # In sky130, we use hand-made decoder cells with vertical power - if not self.column_decoder: + if OPTS.tech_name == "sky130" and not self.column_decoder: for n in ["vdd", "gnd"]: # This makes a wire from top to bottom for both inv and and gates for i in [self.inv_inst, self.and_inst]: From 7220b23402c396e6a6cca74757ec056349c359fd Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 25 Jun 2020 15:34:18 -0700 Subject: [PATCH 17/67] Add riscv unit tests --- compiler/sram/sram_1bank.py | 11 +++-- compiler/tests/50_riscv_func_test.py | 65 ++++++++++++++++++++++++++++ compiler/tests/50_riscv_phys_test.py | 59 +++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 4 deletions(-) create mode 100755 compiler/tests/50_riscv_func_test.py create mode 100755 compiler/tests/50_riscv_phys_test.py diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index bb8c0a7d..7d2ccfa8 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -165,7 +165,7 @@ class sram_1bank(sram_base): # The row address bits are placed above the control logic aligned on the right. x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width # It is above the control logic but below the top of the bitcell array - y_offset = max(self.control_logic_insts[port].uy(), self.bank_inst.uy() - self.row_addr_dff_insts[port].height) + y_offset = max(self.control_logic_insts[port].uy(), self.control_logic_insts[port].uy() + self.dff.height) row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port]) @@ -238,7 +238,7 @@ class sram_1bank(sram_base): # The row address bits are placed above the control logic aligned on the left. x_offset = control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width # It is below the control logic but below the bottom of the bitcell array - y_offset = min(self.control_logic_insts[port].by(), self.bank_inst.by() + self.row_addr_dff_insts[port].height) + y_offset = min(self.control_logic_insts[port].by(), self.control_logic_insts[port].by() - self.dff.height) row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY") @@ -485,11 +485,14 @@ class sram_1bank(sram_base): flop_pos = flop_pin.center() bank_pos = bank_pin.center() mid_pos = vector(bank_pos.x, flop_pos.y) - self.add_wire(self.m2_stack[::-1], - [flop_pos, mid_pos, bank_pos]) self.add_via_stack_center(from_layer=flop_pin.layer, to_layer="m3", offset=flop_pos) + self.add_path("m3", [flop_pos, mid_pos]) + self.add_via_stack_center(from_layer=bank_pin.layer, + to_layer="m3", + offset=mid_pos) + self.add_path(bank_pin.layer, [mid_pos, bank_pos]) def route_col_addr_dff(self): """ Connect the output of the col flops to the bank pins """ diff --git a/compiler/tests/50_riscv_func_test.py b/compiler/tests/50_riscv_func_test.py new file mode 100755 index 00000000..38756496 --- /dev/null +++ b/compiler/tests/50_riscv_func_test.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 22_sram_1rw_1r_1bank_nomux_func_test") +class psram_1bank_nomux_func_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.analytical_delay = False + OPTS.netlist_only = True + OPTS.trim_netlist = False + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 1 + globals.setup_bitcell() + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional, delay + from sram_config import sram_config + c = sram_config(word_size=32, + write_size=8, + num_words=256, + num_banks=1) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Functional test RISC-V memory" + "{} bit words, {} words, {} words per row, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = OPTS.openram_temp + "sram.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (fail, error) = f.run() + self.assertTrue(fail,error) + + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/50_riscv_phys_test.py b/compiler/tests/50_riscv_phys_test.py new file mode 100755 index 00000000..2d759c35 --- /dev/null +++ b/compiler/tests/50_riscv_phys_test.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 22_sram_1rw_1r_1bank_nomux_func_test") +class psram_1bank_nomux_func_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + OPTS.route_supplies=False + OPTS.perimeter_pins=False + + c = sram_config(word_size=32, + write_size=8, + num_words=256, + num_banks=1) + + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) From f11afaa63d057f43a2e24d9cc944c3a954a34ca8 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 25 Jun 2020 17:30:03 -0700 Subject: [PATCH 18/67] Refactor channel route to be a design. --- compiler/base/channel_route.py | 306 ++++++++++++++++++++++++++++++ compiler/base/hierarchy_layout.py | 277 +-------------------------- 2 files changed, 315 insertions(+), 268 deletions(-) create mode 100644 compiler/base/channel_route.py diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py new file mode 100644 index 00000000..7fe1a005 --- /dev/null +++ b/compiler/base/channel_route.py @@ -0,0 +1,306 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import collections +import debug +from tech import drc +from vector import vector +import design + + +class channel_route(design.design): + + unique_id = 0 + + def __init__(self, + netlist, + offset, + layer_stack, + directions=None, + vertical=False, + add_routes=True): + """ + The net list is a list of the nets with each net being a list of pins + to be connected. The offset is the lower-left of where the + routing channel will start. This does NOT try to minimize the + number of tracks -- instead, it picks an order to avoid the + vertical conflicts between pins. The track size must be the number of + nets times the *nonpreferred* routing of the non-track layer pitch. + + """ + name = "cr_{0}".format(channel_route.unique_id) + channel_route.unique_id += 1 + design.design.__init__(self, name) + + self.netlist = netlist + self.offset = offset + self.layer_stack = layer_stack + self.directions = directions + self.vertical = vertical + self.add_routes = add_routes + + if not directions or directions == "pref": + # Use the preferred layer directions + if self.get_preferred_direction(layer_stack[0]) == "V": + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + self.vertical_layer = layer_stack[2] + self.horizontal_layer = layer_stack[0] + elif directions == "nonpref": + # Use the preferred layer directions + if self.get_preferred_direction(layer_stack[0]) == "V": + self.vertical_layer = layer_stack[2] + self.horizontal_layer = layer_stack[0] + else: + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + # Use the layer directions specified to the router rather than + # the preferred directions + debug.check(directions[0] != directions[1], "Must have unique layer directions.") + if directions[0] == "V": + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + self.horizontal_layer = layer_stack[0] + self.vertical_layer = layer_stack[2] + + layer_stuff = self.get_layer_pitch(self.vertical_layer) + (self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff + + layer_stuff = self.get_layer_pitch(self.horizontal_layer) + (self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff + + self.route() + + def remove_net_from_graph(self, pin, g): + """ + Remove the pin from the graph and all conflicts + """ + g.pop(pin, None) + + # Remove the pin from all conflicts + # FIXME: This is O(n^2), so maybe optimize it. + for other_pin, conflicts in g.items(): + if pin in conflicts: + conflicts.remove(pin) + g[other_pin]=conflicts + return g + + def vcg_nets_overlap(self, net1, net2): + """ + Check all the pin pairs on two nets and return a pin + overlap if any pin overlaps. + """ + + if self.vertical: + pitch = self.horizontal_nonpref_pitch + else: + pitch = self.vertical_nonpref_pitch + + for pin1 in net1: + for pin2 in net2: + if self.vcg_pin_overlap(pin1, pin2, pitch): + return True + + return False + + def route(self): + # FIXME: Must extend this to a horizontal conflict graph + # too if we want to minimize the + # number of tracks! + # hcg = {} + + # Initialize the vertical conflict graph (vcg) + # and make a list of all pins + vcg = collections.OrderedDict() + + # Create names for the nets for the graphs + nets = collections.OrderedDict() + index = 0 + # print(netlist) + for pin_list in self.netlist: + net_name = "n{}".format(index) + index += 1 + nets[net_name] = pin_list + + # print("Nets:") + # for net_name in nets: + # print(net_name, [x.name for x in nets[net_name]]) + + # Find the vertical pin conflicts + # FIXME: O(n^2) but who cares for now + for net_name1 in nets: + if net_name1 not in vcg.keys(): + vcg[net_name1] = [] + for net_name2 in nets: + if net_name2 not in vcg.keys(): + vcg[net_name2] = [] + # Skip yourself + if net_name1 == net_name2: + continue + if self.vcg_nets_overlap(nets[net_name1], + nets[net_name2]): + vcg[net_name2].append(net_name1) + + current_offset = self.offset + + # list of routes to do + while vcg: + # from pprint import pformat + # print("VCG:\n", pformat(vcg)) + # get a route from conflict graph with empty fanout set + net_name = None + for net_name, conflicts in vcg.items(): + if len(conflicts) == 0: + vcg = self.remove_net_from_graph(net_name, vcg) + break + else: + # FIXME: We don't support cyclic VCGs right now. + debug.error("Cyclic VCG in channel router.", -1) + + # These are the pins we'll have to connect + pin_list = nets[net_name] + # print("Routing:", net_name, [x.name for x in pin_list]) + + # Remove the net from other constriants in the VCG + vcg = self.remove_net_from_graph(net_name, vcg) + + # Add the trunk routes from the bottom up for + # horizontal or the left to right for vertical + if self.vertical: + if self.add_routes: + self.add_vertical_trunk_route(pin_list, + current_offset, + self.vertical_nonpref_pitch) + # This accounts for the via-to-via spacings + current_offset += vector(self.horizontal_nonpref_pitch, 0) + else: + if self.add_routes: + self.add_horizontal_trunk_route(pin_list, + current_offset, + self.horizontal_nonpref_pitch) + # This accounts for the via-to-via spacings + current_offset += vector(0, self.vertical_nonpref_pitch) + + # Return the size of the channel + if self.vertical: + self.width = 0 + self.height = current_offset.y + return current_offset.y + self.vertical_nonpref_pitch - self.offset.y + else: + self.width = current_offset.x + self.height = 0 + return current_offset.x + self.horizontal_nonpref_pitch - self.offset.x + + def get_layer_pitch(self, layer): + """ Return the track pitch on a given layer """ + try: + # FIXME: Using non-pref pitch here due to overlap bug in VCG constraints. + # It should just result in inefficient channel width but will work. + pitch = getattr(self, "{}_pitch".format(layer)) + nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer)) + space = getattr(self, "{}_space".format(layer)) + except AttributeError: + debug.error("Cannot find layer pitch.", -1) + return (nonpref_pitch, pitch, pitch - space, space) + + def add_horizontal_trunk_route(self, + pins, + trunk_offset, + pitch): + """ + Create a trunk route for all pins with + the trunk located at the given y offset. + """ + max_x = max([pin.center().x for pin in pins]) + min_x = min([pin.center().x for pin in pins]) + + # if we are less than a pitch, just create a non-preferred layer jog + if max_x - min_x <= pitch: + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] + + # Add the horizontal trunk on the vertical layer! + self.add_path(self.vertical_layer, + [vector(min_x - half_layer_width, trunk_offset.y), + vector(max_x + half_layer_width, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + # No bend needed here + mid = vector(pin.center().x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin.center(), mid]) + else: + # Add the horizontal trunk + self.add_path(self.horizontal_layer, + [vector(min_x, trunk_offset.y), + vector(max_x, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + mid = vector(pin.center().x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin.center(), mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + + def add_vertical_trunk_route(self, + pins, + trunk_offset, + pitch): + """ + Create a trunk route for all pins with the + trunk located at the given x offset. + """ + max_y = max([pin.center().y for pin in pins]) + min_y = min([pin.center().y for pin in pins]) + + # if we are less than a pitch, just create a non-preferred layer jog + if max_y - min_y <= pitch: + + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] + + # Add the vertical trunk on the horizontal layer! + self.add_path(self.horizontal_layer, + [vector(trunk_offset.x, min_y - half_layer_width), + vector(trunk_offset.x, max_y + half_layer_width)]) + + # Route each pin to the trunk + for pin in pins: + # No bend needed here + mid = vector(trunk_offset.x, pin.center().y) + self.add_path(self.horizontal_layer, [pin.center(), mid]) + else: + # Add the vertical trunk + self.add_path(self.vertical_layer, + [vector(trunk_offset.x, min_y), + vector(trunk_offset.x, max_y)]) + + # Route each pin to the trunk + for pin in pins: + mid = vector(trunk_offset.x, pin.center().y) + self.add_path(self.horizontal_layer, [pin.center(), mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + + def vcg_pin_overlap(self, pin1, pin2, pitch): + """ Check for vertical or horizontal overlap of the two pins """ + + # FIXME: If the pins are not in a row, this may break. + # However, a top pin shouldn't overlap another top pin, + # for example, so the extra comparison *shouldn't* matter. + + # Pin 1 must be in the "BOTTOM" set + x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch + + # Pin 1 must be in the "LEFT" set + y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch + overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap) + return overlaps + diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index f90b3129..8720470b 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -5,7 +5,6 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import collections import geometry import gdsMill import debug @@ -1019,282 +1018,24 @@ class layout(): to_layer=dest_pin.layer, offset=out_pos) - def get_layer_pitch(self, layer): - """ Return the track pitch on a given layer """ - try: - # FIXME: Using non-pref pitch here due to overlap bug in VCG constraints. - # It should just result in inefficient channel width but will work. - pitch = getattr(self, "{}_pitch".format(layer)) - nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer)) - space = getattr(self, "{}_space".format(layer)) - except AttributeError: - debug.error("Cannot find layer pitch.", -1) - return (nonpref_pitch, pitch, pitch - space, space) - - def add_horizontal_trunk_route(self, - pins, - trunk_offset, - layer_stack, - pitch): - """ - Create a trunk route for all pins with - the trunk located at the given y offset. - """ - max_x = max([pin.center().x for pin in pins]) - min_x = min([pin.center().x for pin in pins]) - - # if we are less than a pitch, just create a non-preferred layer jog - if max_x - min_x <= pitch: - half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] - - # Add the horizontal trunk on the vertical layer! - self.add_path(self.vertical_layer, - [vector(min_x - half_layer_width, trunk_offset.y), - vector(max_x + half_layer_width, trunk_offset.y)]) - - # Route each pin to the trunk - for pin in pins: - # No bend needed here - mid = vector(pin.center().x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin.center(), mid]) - else: - # Add the horizontal trunk - self.add_path(self.horizontal_layer, - [vector(min_x, trunk_offset.y), - vector(max_x, trunk_offset.y)]) - - # Route each pin to the trunk - for pin in pins: - mid = vector(pin.center().x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin.center(), mid]) - self.add_via_center(layers=layer_stack, - offset=mid, - directions=self.directions) - - def add_vertical_trunk_route(self, - pins, - trunk_offset, - layer_stack, - pitch): - """ - Create a trunk route for all pins with the - trunk located at the given x offset. - """ - max_y = max([pin.center().y for pin in pins]) - min_y = min([pin.center().y for pin in pins]) - - # if we are less than a pitch, just create a non-preferred layer jog - if max_y - min_y <= pitch: - - half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] - - # Add the vertical trunk on the horizontal layer! - self.add_path(self.horizontal_layer, - [vector(trunk_offset.x, min_y - half_layer_width), - vector(trunk_offset.x, max_y + half_layer_width)]) - - # Route each pin to the trunk - for pin in pins: - # No bend needed here - mid = vector(trunk_offset.x, pin.center().y) - self.add_path(self.horizontal_layer, [pin.center(), mid]) - else: - # Add the vertical trunk - self.add_path(self.vertical_layer, - [vector(trunk_offset.x, min_y), - vector(trunk_offset.x, max_y)]) - - # Route each pin to the trunk - for pin in pins: - mid = vector(trunk_offset.x, pin.center().y) - self.add_path(self.horizontal_layer, [pin.center(), mid]) - self.add_via_center(layers=layer_stack, - offset=mid, - directions=self.directions) - - def create_channel_route(self, netlist, - offset, - layer_stack, - directions=None, - vertical=False): - """ - The net list is a list of the nets with each net being a list of pins - to be connected. The offset is the lower-left of where the - routing channel will start. This does NOT try to minimize the - number of tracks -- instead, it picks an order to avoid the - vertical conflicts between pins. The track size must be the number of - nets times the *nonpreferred* routing of the non-track layer pitch. - - """ - def remove_net_from_graph(pin, g): - """ - Remove the pin from the graph and all conflicts - """ - g.pop(pin, None) - - # Remove the pin from all conflicts - # FIXME: This is O(n^2), so maybe optimize it. - for other_pin, conflicts in g.items(): - if pin in conflicts: - conflicts.remove(pin) - g[other_pin]=conflicts - return g - - def vcg_nets_overlap(net1, net2, vertical): - """ - Check all the pin pairs on two nets and return a pin - overlap if any pin overlaps. - """ - - if vertical: - pitch = self.horizontal_nonpref_pitch - else: - pitch = self.vertical_nonpref_pitch - - for pin1 in net1: - for pin2 in net2: - if vcg_pin_overlap(pin1, pin2, vertical, pitch): - return True - - return False - - def vcg_pin_overlap(pin1, pin2, vertical, pitch): - """ Check for vertical or horizontal overlap of the two pins """ - - # FIXME: If the pins are not in a row, this may break. - # However, a top pin shouldn't overlap another top pin, - # for example, so the extra comparison *shouldn't* matter. - - # Pin 1 must be in the "BOTTOM" set - x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch - - # Pin 1 must be in the "LEFT" set - y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch - overlaps = (not vertical and x_overlap) or (vertical and y_overlap) - return overlaps - - self.directions = directions - if not directions or directions == "pref": - # Use the preferred layer directions - if self.get_preferred_direction(layer_stack[0]) == "V": - self.vertical_layer = layer_stack[0] - self.horizontal_layer = layer_stack[2] - else: - self.vertical_layer = layer_stack[2] - self.horizontal_layer = layer_stack[0] - elif directions == "nonpref": - # Use the preferred layer directions - if self.get_preferred_direction(layer_stack[0]) == "V": - self.vertical_layer = layer_stack[2] - self.horizontal_layer = layer_stack[0] - else: - self.vertical_layer = layer_stack[0] - self.horizontal_layer = layer_stack[2] - else: - # Use the layer directions specified to the router rather than - # the preferred directions - debug.check(directions[0] != directions[1], "Must have unique layer directions.") - if directions[0] == "V": - self.vertical_layer = layer_stack[0] - self.horizontal_layer = layer_stack[2] - else: - self.horizontal_layer = layer_stack[0] - self.vertical_layer = layer_stack[2] - - layer_stuff = self.get_layer_pitch(self.vertical_layer) - (self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff - - layer_stuff = self.get_layer_pitch(self.horizontal_layer) - (self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff - - # FIXME: Must extend this to a horizontal conflict graph - # too if we want to minimize the - # number of tracks! - # hcg = {} - - # Initialize the vertical conflict graph (vcg) - # and make a list of all pins - vcg = collections.OrderedDict() - - # Create names for the nets for the graphs - nets = collections.OrderedDict() - index = 0 - # print(netlist) - for pin_list in netlist: - net_name = "n{}".format(index) - index += 1 - nets[net_name] = pin_list - - # print("Nets:") - # for net_name in nets: - # print(net_name, [x.name for x in nets[net_name]]) - - # Find the vertical pin conflicts - # FIXME: O(n^2) but who cares for now - for net_name1 in nets: - if net_name1 not in vcg.keys(): - vcg[net_name1] = [] - for net_name2 in nets: - if net_name2 not in vcg.keys(): - vcg[net_name2] = [] - # Skip yourself - if net_name1 == net_name2: - continue - if vcg_nets_overlap(nets[net_name1], - nets[net_name2], - vertical): - vcg[net_name2].append(net_name1) - - # list of routes to do - while vcg: - # from pprint import pformat - # print("VCG:\n", pformat(vcg)) - # get a route from conflict graph with empty fanout set - net_name = None - for net_name, conflicts in vcg.items(): - if len(conflicts) == 0: - vcg = remove_net_from_graph(net_name, vcg) - break - else: - # FIXME: We don't support cyclic VCGs right now. - debug.error("Cyclic VCG in channel router.", -1) - - # These are the pins we'll have to connect - pin_list = nets[net_name] - # print("Routing:", net_name, [x.name for x in pin_list]) - - # Remove the net from other constriants in the VCG - vcg = remove_net_from_graph(net_name, vcg) - - # Add the trunk routes from the bottom up for - # horizontal or the left to right for vertical - if vertical: - self.add_vertical_trunk_route(pin_list, - offset, - layer_stack, - self.vertical_nonpref_pitch) - # This accounts for the via-to-via spacings - offset += vector(self.horizontal_nonpref_pitch, 0) - else: - self.add_horizontal_trunk_route(pin_list, - offset, - layer_stack, - self.horizontal_nonpref_pitch) - # This accounts for the via-to-via spacings - offset += vector(0, self.vertical_nonpref_pitch) - def create_vertical_channel_route(self, netlist, offset, layer_stack, directions=None): """ Wrapper to create a vertical channel route """ - self.create_channel_route(netlist, offset, layer_stack, directions, vertical=True) + import channel_route + cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=True) + self.add_inst("vc", cr) + self.connect_inst([]) def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None): """ Wrapper to create a horizontal channel route """ - self.create_channel_route(netlist, offset, layer_stack, directions, vertical=False) - + import channel_route + cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False) + self.add_inst("hc", cr) + self.connect_inst([]) + def add_boundary(self, ll=vector(0, 0), ur=None): """ Add boundary for debugging dimensions """ if OPTS.netlist_only: From 9eb1b500eaf79ec905b3ffef4f8e0038ab17347a Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 25 Jun 2020 17:31:23 -0700 Subject: [PATCH 19/67] Skip phys riscv test --- compiler/tests/50_riscv_phys_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/tests/50_riscv_phys_test.py b/compiler/tests/50_riscv_phys_test.py index 2d759c35..0ae11025 100755 --- a/compiler/tests/50_riscv_phys_test.py +++ b/compiler/tests/50_riscv_phys_test.py @@ -15,8 +15,8 @@ from globals import OPTS from sram_factory import factory import debug -#@unittest.skip("SKIPPING 22_sram_1rw_1r_1bank_nomux_func_test") -class psram_1bank_nomux_func_test(openram_test): +@unittest.skip("SKIPPING 50_riscv_phys_test") +class riscv_phys_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) From 76e5389c3384a8221475b5fd5e04136b991d12a2 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 25 Jun 2020 17:43:17 -0700 Subject: [PATCH 20/67] Change riscv func test name --- compiler/tests/50_riscv_func_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/tests/50_riscv_func_test.py b/compiler/tests/50_riscv_func_test.py index 38756496..bb4cd6b0 100755 --- a/compiler/tests/50_riscv_func_test.py +++ b/compiler/tests/50_riscv_func_test.py @@ -15,8 +15,7 @@ from globals import OPTS from sram_factory import factory import debug -#@unittest.skip("SKIPPING 22_sram_1rw_1r_1bank_nomux_func_test") -class psram_1bank_nomux_func_test(openram_test): +class riscv_func_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) From d53abba47946890d92a78c5b8f9762ffb7c5080e Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 25 Jun 2020 17:43:44 -0700 Subject: [PATCH 21/67] Always route the channel route since it is it's own design. --- compiler/base/channel_route.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index 7fe1a005..d6048376 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -21,8 +21,7 @@ class channel_route(design.design): offset, layer_stack, directions=None, - vertical=False, - add_routes=True): + vertical=False): """ The net list is a list of the nets with each net being a list of pins to be connected. The offset is the lower-left of where the @@ -41,7 +40,6 @@ class channel_route(design.design): self.layer_stack = layer_stack self.directions = directions self.vertical = vertical - self.add_routes = add_routes if not directions or directions == "pref": # Use the preferred layer directions @@ -174,17 +172,15 @@ class channel_route(design.design): # Add the trunk routes from the bottom up for # horizontal or the left to right for vertical if self.vertical: - if self.add_routes: - self.add_vertical_trunk_route(pin_list, - current_offset, - self.vertical_nonpref_pitch) + self.add_vertical_trunk_route(pin_list, + current_offset, + self.vertical_nonpref_pitch) # This accounts for the via-to-via spacings current_offset += vector(self.horizontal_nonpref_pitch, 0) else: - if self.add_routes: - self.add_horizontal_trunk_route(pin_list, - current_offset, - self.horizontal_nonpref_pitch) + self.add_horizontal_trunk_route(pin_list, + current_offset, + self.horizontal_nonpref_pitch) # This accounts for the via-to-via spacings current_offset += vector(0, self.vertical_nonpref_pitch) From af4ed3dd6edcf25db3a624da5a5d503bf3619f5e Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 26 Jun 2020 06:50:45 -0700 Subject: [PATCH 22/67] Skip riscv func test for time sake --- compiler/tests/50_riscv_func_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/tests/50_riscv_func_test.py b/compiler/tests/50_riscv_func_test.py index bb4cd6b0..1d5720f7 100755 --- a/compiler/tests/50_riscv_func_test.py +++ b/compiler/tests/50_riscv_func_test.py @@ -15,6 +15,7 @@ from globals import OPTS from sram_factory import factory import debug +@unittest.skip("SKIPPING 50_riscv_func_test") class riscv_func_test(openram_test): def runTest(self): From 567675ab31ea4cbd54a0380cd72ba37b84b144a5 Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 26 Jun 2020 11:47:12 -0700 Subject: [PATCH 23/67] PEP8 cleanup --- compiler/base/hierarchy_design.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index ac3fb30b..1321f06a 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -31,6 +31,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): except AttributeError: lvs_subdir = "lvs_lib" lvs_dir = OPTS.openram_tech + lvs_subdir + "/" + if os.path.exists(lvs_dir): self.lvs_file = lvs_dir + name + ".sp" else: From e23d41c1d428179c21ff7eafd11c6feea4808f7f Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 26 Jun 2020 11:47:35 -0700 Subject: [PATCH 24/67] PEP8 cleanup --- compiler/modules/sense_amp.py | 39 ++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index ff5638ba..35fbdf42 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -8,11 +8,12 @@ import design import debug import utils -from tech import GDS,layer, parameter,drc +from tech import GDS, layer, parameter, drc from tech import cell_properties as props from globals import OPTS import logical_effort + class sense_amp(design.design): """ This module implements the single sense amp cell used in the design. It @@ -28,10 +29,10 @@ class sense_amp(design.design): props.sense_amp.pin.gnd] type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] if not OPTS.netlist_only: - (width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) + (width, height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"]) else: - (width, height) = (0,0) + (width, height) = (0, 0) pin_map = [] def get_bl_names(self): @@ -61,41 +62,41 @@ class sense_amp(design.design): # FIXME: This input load will be applied to both the s_en timing and bitline timing. - #Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. - from tech import spice, parameter + # Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. + from tech import spice # Default is 8x. Per Samira and Hodges-Jackson book: # "Column-mux transistors driven by the decoder must be sized for optimal speed" - bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file. - return spice["min_tx_drain_c"]*(bitline_pmos_size)#ff + bitline_pmos_size = 8 # FIXME: This should be set somewhere and referenced. Probably in tech file. + return spice["min_tx_drain_c"] * bitline_pmos_size # ff def get_stage_effort(self, load): - #Delay of the sense amp will depend on the size of the amp and the output load. + # Delay of the sense amp will depend on the size of the amp and the output load. parasitic_delay = 1 - cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx") - sa_size = parameter["sa_inv_nmos_size"]/drc("minwidth_tx") + cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"]) / drc("minwidth_tx") + sa_size = parameter["sa_inv_nmos_size"] / drc("minwidth_tx") cc_inv_cin = cin - return logical_effort.logical_effort('column_mux', sa_size, cin, load+cc_inv_cin, parasitic_delay, False) + return logical_effort.logical_effort('column_mux', sa_size, cin, load + cc_inv_cin, parasitic_delay, False) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" - #Power in this module currently not defined. Returns 0 nW (leakage and dynamic). + # Power in this module currently not defined. Returns 0 nW (leakage and dynamic). total_power = self.return_power() return total_power def get_en_cin(self): """Get the relative capacitance of sense amp enable gate cin""" - pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx") - nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx") - #sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. - return 2*pmos_cin + nmos_cin + pmos_cin = parameter["sa_en_pmos_size"] / drc("minwidth_tx") + nmos_cin = parameter["sa_en_nmos_size"] / drc("minwidth_tx") + # sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. + return 2 * pmos_cin + nmos_cin def get_enable_name(self): """Returns name used for enable net""" - #FIXME: A better programmatic solution to designate pins + # FIXME: A better programmatic solution to designate pins enable_name = self.en_name debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name)) return enable_name - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) + self.add_graph_edges(graph, port_nets) From f57eeb88eb4af97bfb4a4f8aee46aba8f9fa533b Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 26 Jun 2020 11:47:55 -0700 Subject: [PATCH 25/67] PEP8 cleanup, multiple vdd/gnd support --- compiler/modules/sense_amp_array.py | 38 +++++++++++++---------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index 46c30c9d..5d41b2a0 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -28,7 +28,7 @@ class sense_amp_array(design.design): self.add_comment("words_per_row: {0}".format(words_per_row)) self.word_size = word_size - self.words_per_row = words_per_row + self.words_per_row = words_per_row if not num_spare_cols: self.num_spare_cols = 0 else: @@ -77,7 +77,7 @@ class sense_amp_array(design.design): self.DRC_LVS() def add_pins(self): - for i in range(0,self.word_size + self.num_spare_cols): + for i in range(0, self.word_size + self.num_spare_cols): self.add_pin(self.data_name + "_{0}".format(i), "OUTPUT") self.add_pin(self.get_bl_name() + "_{0}".format(i), "INPUT") self.add_pin(self.get_br_name() + "_{0}".format(i), "INPUT") @@ -96,7 +96,7 @@ class sense_amp_array(design.design): def create_sense_amp_array(self): self.local_insts = [] - for i in range(0,self.word_size + self.num_spare_cols): + for i in range(0, self.word_size + self.num_spare_cols): name = "sa_d{0}".format(i) self.local_insts.append(self.add_inst(name=name, mod=self.amp)) @@ -107,14 +107,10 @@ class sense_amp_array(design.design): def place_sense_amp_array(self): from tech import cell_properties - if self.bitcell.width > self.amp.width: - amp_spacing = self.bitcell.width - else: - amp_spacing = self.amp.width for i in range(0, self.row_size, self.words_per_row): index = int(i / self.words_per_row) - xoffset = i * amp_spacing + xoffset = i * self.bitcell.width if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: mirror = "MY" @@ -126,9 +122,9 @@ class sense_amp_array(design.design): self.local_insts[index].place(offset=amp_position, mirror=mirror) # place spare sense amps (will share the same enable as regular sense amps) - for i in range(0,self.num_spare_cols): + for i in range(0, self.num_spare_cols): index = self.word_size + i - xoffset = ((self.word_size * self.words_per_row) + i) * amp_spacing + xoffset = ((self.word_size * self.words_per_row) + i) * self.bitcell.width if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: mirror = "MY" @@ -143,17 +139,17 @@ class sense_amp_array(design.design): for i in range(len(self.local_insts)): inst = self.local_insts[i] - gnd_pin = inst.get_pin("gnd") - self.add_power_pin(name="gnd", - loc=gnd_pin.center(), - start_layer=gnd_pin.layer, - directions=("V", "V")) - - vdd_pin = inst.get_pin("vdd") - self.add_power_pin(name="vdd", - loc=vdd_pin.center(), - start_layer=vdd_pin.layer, - directions=("V", "V")) + for gnd_pin in inst.get_pins("gnd"): + self.add_power_pin(name="gnd", + loc=gnd_pin.center(), + start_layer=gnd_pin.layer, + directions=("V", "V")) + + for vdd_pin in inst.get_pins("vdd"): + self.add_power_pin(name="vdd", + loc=vdd_pin.center(), + start_layer=vdd_pin.layer, + directions=("V", "V")) bl_pin = inst.get_pin(inst.mod.get_bl_names()) br_pin = inst.get_pin(inst.mod.get_br_names()) From 94d700071751973e2c9bb93d3ce266dc658e300f Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 26 Jun 2020 12:16:54 -0700 Subject: [PATCH 26/67] Reduce output clutter from gds write --- compiler/base/hierarchy_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 8720470b..2a29be6b 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -739,7 +739,7 @@ class layout(): width=width, height=height, center=False) - debug.info(2, "Adding {0} boundary {1}".format(self.name, boundary)) + debug.info(4, "Adding {0} boundary {1}".format(self.name, boundary)) self.visited.append(self.name) From c07e20cbe4d6f2cca60b7fc836fd493e0ce433fc Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 26 Jun 2020 14:27:16 -0700 Subject: [PATCH 27/67] Move mux select from li to m2 in sky130 --- compiler/modules/single_level_column_mux_array.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index cc38cb45..37fd4dc1 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -33,8 +33,8 @@ class single_level_column_mux_array(design.design): self.column_offset = column_offset if "li" in layer: - self.col_mux_stack = self.li_stack - self.col_mux_stack_pitch = self.m1_pitch + self.col_mux_stack = self.m1_stack[::-1] + self.col_mux_stack_pitch = self.m2_pitch else: self.col_mux_stack = self.m1_stack self.col_mux_stack_pitch = self.m1_pitch @@ -155,7 +155,7 @@ class single_level_column_mux_array(design.design): self.route_bitlines() def add_horizontal_input_rail(self): - """ Create address input rails on M1 below the mux transistors """ + """ Create address input rails below the mux transistors """ for j in range(self.words_per_row): offset = vector(0, self.route_height + (j - self.words_per_row) * self.col_mux_stack_pitch) self.add_layout_pin(text="sel_{}".format(j), @@ -177,10 +177,10 @@ class single_level_column_mux_array(design.design): # use the y offset from the sel pin and the x offset from the gate offset = vector(gate_offset.x, self.get_pin("sel_{}".format(sel_index)).cy()) - # Add the poly contact with a shift to account for the rotation - self.add_via_center(layers=self.poly_stack, - offset=offset, - directions=self.via_directions) + self.add_via_stack_center(from_layer="poly", + to_layer=self.col_mux_stack[0], + offset=offset, + directions=self.via_directions) self.add_path("poly", [offset, gate_offset]) def route_bitlines(self): From a7ee17eb2d007ba7dc4e0e84be4d81679bf61d5f Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 26 Jun 2020 15:29:27 -0700 Subject: [PATCH 28/67] Move output of sense amp to side like other techs --- technology/freepdk45/gds_lib/sense_amp.gds | Bin 16384 -> 14702 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/technology/freepdk45/gds_lib/sense_amp.gds b/technology/freepdk45/gds_lib/sense_amp.gds index fecbfcb8cdc6af32968849ba447d240a27c92d8a..53c499f95f892bd63084ab55e93de706bd6a492d 100644 GIT binary patch delta 402 zcmZo@V0>2;$H2zG#1z3G!^p(`1jv?R;AfCwU_fRw@G!BZq^2d7=9Dlnh%mF-DIA%- z^l|V3OQjg`3%+aKG03pu)Vne6iJTGxD+2=)F9QRJW|juh?FbqozBxty6w~A!r7LP+ zwfzXSEN>ucK{OLooT-2E3kA8&Ez0@KlTDSR#Lt{LbGm?mM}QfqgoS}Yq97JYrx=d!UvAX$Y7+Pg8DkjpkWNB>fDoKsh}I4oS9@{L+%i&(zcQ-Ol1rVOuRr0q?x6G z^bZ6L5#O95e~M}HIyFV{vkV}>Bftz)#=^j$@{fT{k}WlFvWC(vC8k24U_TH8X^=TA zZxA#@e6x!3MCQrHno^NS=CIZR&0%5SkYr2GO9AQ<1Y!;b1~y(M23`g>J|>`jI$--i z#)F&*HJ<4&)R{mUCeFmW`I%Z2BiJb*hhkF;5udE2sm%&@#pFwxmQa>}mc-;7jfYU4 zpO(~Q9Zf|^B)2kc1M1cTxiuxfv}ALVRunHJ_=16Ufr9ZW5Cek`PJ_ia-!q@USP!yy h0KusJ0~q)~t4G6sH2eoL1V+=}K!!d8LmX|b2LO#IQC0u| From 2bd498c39cf1a412845bd8f93d9989770e3d62e8 Mon Sep 17 00:00:00 2001 From: mrg Date: Sat, 27 Jun 2020 08:21:30 -0700 Subject: [PATCH 29/67] Change precharge layer to m3 --- compiler/modules/precharge_array.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index 2cf4718a..3a3b8c8b 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -75,11 +75,18 @@ class precharge_array(design.design): def add_layout_pins(self): en_bar_pin = self.pc_cell.get_pin("en_bar") - self.add_layout_pin(text="en_bar", - layer=en_bar_pin.layer, - offset=en_bar_pin.ll(), - width=self.width, - height=en_bar_pin.height()) + if en_bar_pin.layer =="li": + en_bar_layer = "m3" + else: + en_bar_layer = en_bar_pin.layer + r = self.add_layout_pin(text="en_bar", + layer=en_bar_layer, + offset=en_bar_pin.ll(), + width=self.width, + height=en_bar_pin.height()) + self.add_via_stack_center(from_layer=en_bar_pin.layer, + to_layer=en_bar_layer, + offset = r.center()) for inst in self.local_insts: self.copy_layout_pin(inst, "vdd") From 609aa98c8be449ba86308eca1536c4c0d5178f91 Mon Sep 17 00:00:00 2001 From: mrg Date: Sat, 27 Jun 2020 08:21:53 -0700 Subject: [PATCH 30/67] Move write mask pin to left of cell to avoid sense amp --- compiler/modules/write_driver_array.py | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index 22a75218..a6eb1384 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -154,7 +154,6 @@ class write_driver_array(design.design): self.get_br_name() + "_{0}".format(index), self.en_name + "_{0}".format(i + offset), "vdd", "gnd"]) - def place_write_array(self): from tech import cell_properties if self.bitcell.width > self.driver.width: From c10a6a29c00fcf4ab47c2f3f757982b60eadbf7a Mon Sep 17 00:00:00 2001 From: mrg Date: Sat, 27 Jun 2020 08:22:16 -0700 Subject: [PATCH 31/67] Simplify precharge pin layer --- compiler/pgates/precharge.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index dc016cab..b3d25865 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -38,16 +38,10 @@ class precharge(design.design): if self.bitcell_bl_pin.layer == "m1": self.bitline_layer = "m1" - if "li" in layer: - self.en_layer = "li" - else: - self.en_layer = "m2" + self.en_layer = "m2" else: self.bitline_layer = "m2" - if "li" in layer: - self.en_layer = "li" - else: - self.en_layer = "m1" + self.en_layer = "m1" # Creates the netlist and layout # Since it has variable height, it is not a pgate. From 66ea559209c476804433bc12cf5e0c17cd994f88 Mon Sep 17 00:00:00 2001 From: mrg Date: Sat, 27 Jun 2020 08:23:12 -0700 Subject: [PATCH 32/67] Use channel for dffs all at once --- compiler/modules/write_mask_and_array.py | 17 +- compiler/sram/sram_1bank.py | 314 +++++++++++------------ 2 files changed, 163 insertions(+), 168 deletions(-) diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index f337ab7a..c87d3a90 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -108,8 +108,21 @@ class write_mask_and_array(design.design): end=vector(self.width, en_pin.cy())) for i in range(self.num_wmasks): + # Route the A pin over to the left so that it doesn't conflict with the sense + # amp output which is usually in the center + a_pin = self.and2_insts[i].get_pin("A") + a_pos = a_pin.center() + in_pos = vector(self.and2_insts[i].lx(), + a_pos.y) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=in_pos) + self.add_layout_pin_rect_center(text="wmask_in_{0}".format(i), + layer="m2", + offset=in_pos) + self.add_path(a_pin.layer, [in_pos, a_pos]) + # Copy remaining layout pins - self.copy_layout_pin(self.and2_insts[i], "A", "wmask_in_{0}".format(i)) self.copy_layout_pin(self.and2_insts[i], "Z", "wmask_out_{0}".format(i)) # Add via connections to metal3 for AND array's B pin @@ -122,7 +135,7 @@ class write_mask_and_array(design.design): for supply in ["gnd", "vdd"]: supply_pin=self.and2_insts[i].get_pin(supply) if "li" in layer: - self.add_power_pin(supply, supply_pin.center(), start_layer="li", directions = ("H", "H")) + self.add_power_pin(supply, supply_pin.center(), start_layer="li", directions=("H", "H")) else: self.add_power_pin(supply, supply_pin.center()) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 7d2ccfa8..4e2be40e 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -70,93 +70,12 @@ class sram_1bank(sram_base): # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. # So, m3 non-pref pitch means that this is routed on the m2 layer. - if self.write_size: - self.data_bus_gap = self.m4_nonpref_pitch * 2 - self.data_bus_size = self.m4_nonpref_pitch * (self.word_size + self.num_spare_cols) + self.data_bus_gap - self.wmask_bus_gap = self.m2_nonpref_pitch * 2 - self.wmask_bus_size = self.m2_nonpref_pitch * (max(self.num_wmasks + 1, self.col_addr_size + 1)) + self.wmask_bus_gap - if self.num_spare_cols: - self.spare_wen_bus_gap = self.m2_nonpref_pitch * 2 - self.spare_wen_bus_size = self.m2_nonpref_pitch * (max(self.num_spare_cols + 1, self.col_addr_size + 1)) + self.spare_wen_bus_gap - else: - self.spare_wen_bus_size = 0 - - elif self.num_spare_cols and not self.write_size: - self.data_bus_gap = self.m4_nonpref_pitch * 2 - self.data_bus_size = self.m4_nonpref_pitch * (self.word_size + self.num_spare_cols) + self.data_bus_gap - self.spare_wen_bus_gap = self.m2_nonpref_pitch * 2 - self.spare_wen_bus_size = self.m2_nonpref_pitch * (max(self.num_spare_cols + 1, self.col_addr_size + 1)) + self.spare_wen_bus_gap - - else: - self.data_bus_gap = self.m3_nonpref_pitch * 2 - self.data_bus_size = self.m3_nonpref_pitch * (max(self.word_size + 1, self.col_addr_size + 1)) + self.data_bus_gap - - self.col_addr_bus_gap = self.m2_nonpref_pitch * 2 - self.col_addr_bus_size = self.m2_nonpref_pitch * (self.col_addr_size) + self.col_addr_bus_gap + self.data_bus_gap = self.m4_nonpref_pitch * 2 + self.data_bus_size = self.m4_nonpref_pitch * (self.word_size + self.num_spare_cols + self.num_wmasks + self.col_addr_size + self.num_spare_cols) + self.data_bus_gap # Port 0 port = 0 - if port in self.write_ports: - if self.write_size: - bus_size = max(self.wmask_bus_size, self.spare_wen_bus_size) - # Add the write mask flops below the write mask AND array. - wmask_pos[port] = vector(self.bank.bank_array_ll.x, - - bus_size - self.dff.height) - self.wmask_dff_insts[port].place(wmask_pos[port]) - - # Add the data flops below the write mask flops. - data_pos[port] = vector(self.bank.bank_array_ll.x, - - self.data_bus_size - bus_size - 2 * self.dff.height) - self.data_dff_insts[port].place(data_pos[port]) - - #Add spare write enable flops to the right of write mask flops - if self.num_spare_cols: - spare_wen_pos[port] = vector(self.bank.bank_array_ll.x + self.wmask_dff_insts[port].width + self.bank.m2_gap, - - bus_size - self.dff.height) - self.spare_wen_dff_insts[port].place(spare_wen_pos[port]) - - elif self.num_spare_cols and not self.write_size: - # Add spare write enable flops below bank (lower right) - spare_wen_pos[port] = vector(self.bank.bank_array_ll.x, - - self.spare_wen_bus_size - self.dff.height) - self.spare_wen_dff_insts[port].place(spare_wen_pos[port]) - - # Add the data flops below the spare write enable flops. - data_pos[port] = vector(self.bank.bank_array_ll.x, - - self.data_bus_size - self.spare_wen_bus_size - 2 * self.dff.height) - self.data_dff_insts[port].place(data_pos[port]) - - else: - # Add the data flops below the bank to the right of the lower-left of bank array - # This relies on the lower-left of the array of the bank - # decoder in upper left, bank in upper right, sensing in lower right. - # These flops go below the sensing and leave a gap to channel route to the - # sense amps. - data_pos[port] = vector(self.bank.bank_array_ll.x, - -self.data_bus_size - self.dff.height) - self.data_dff_insts[port].place(data_pos[port]) - - else: - wmask_pos[port] = vector(self.bank.bank_array_ll.x, 0) - data_pos[port] = vector(self.bank.bank_array_ll.x, 0) - spare_wen_pos[port] = vector(self.bank.bank_array_ll.x, 0) - - # Add the col address flops below the bank to the left of the lower-left of bank array - if self.col_addr_dff: - if self.write_size: - col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, - -bus_size - self.col_addr_dff_insts[port].height) - elif self.num_spare_cols and not self.write_size: - col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, - -self.spare_wen_bus_size - self.col_addr_dff_insts[port].height) - else: - col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, - -self.data_bus_size - self.col_addr_dff_insts[port].height) - self.col_addr_dff_insts[port].place(col_addr_pos[port]) - else: - col_addr_pos[port] = vector(self.bank.bank_array_ll.x, 0) - # This includes 2 M2 pitches for the row addr clock line. control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y - 2 * self.bank.m2_gap) @@ -169,64 +88,45 @@ class sram_1bank(sram_base): row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port]) + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].rx() + self.dff.width + y_offset = - self.data_bus_size - self.dff.height + if self.col_addr_dff: + col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(col_addr_pos[port]) + x_offset = self.col_addr_dff_insts[port].rx() + else: + col_addr_pos[port] = vector(x_offset, 0) + + if port in self.write_ports: + if self.write_size: + # Add the write mask flops below the write mask AND array. + wmask_pos[port] = vector(x_offset, + y_offset) + self.wmask_dff_insts[port].place(wmask_pos[port]) + x_offset = self.wmask_dff_insts[port].rx() + + # Add spare write enable flops to the right of write mask flops + if self.num_spare_cols: + spare_wen_pos[port] = vector(x_offset, + y_offset) + self.spare_wen_dff_insts[port].place(spare_wen_pos[port]) + x_offset = self.spare_wen_dff_insts[port].rx() + + # Add the data flops below the write mask flops. + data_pos[port] = vector(x_offset, + y_offset) + self.data_dff_insts[port].place(data_pos[port]) + + else: + wmask_pos[port] = vector(x_offset, y_offset) + data_pos[port] = vector(x_offset, y_offset) + spare_wen_pos[port] = vector(x_offset, y_offset) + if len(self.all_ports)>1: # Port 1 port = 1 - - if port in self.write_ports: - if self.write_size: - bus_size = max(self.wmask_bus_size, self.spare_wen_bus_size) - # Add the write mask flops above the write mask AND array. - wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width, - self.bank.height + bus_size + self.dff.height) - self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") - - # Add the data flops above the write mask flops - data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + bus_size + self.data_bus_size + 2 * self.dff.height) - self.data_dff_insts[port].place(data_pos[port], mirror="MX") - - if self.num_spare_cols: - spare_wen_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width - - self.spare_wen_dff_insts[port].width - self.bank.m2_gap, - self.bank.height + bus_size + self.dff.height) - self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX") - - # Place dffs when spare cols is enabled - elif self.num_spare_cols and not self.write_size: - # Spare wen flops on the upper right, below data flops - spare_wen_pos[port] = vector(self.bank.bank_array_ur.x - self.spare_wen_dff_insts[port].width, - self.bank.height + self.spare_wen_bus_size + self.dff.height) - self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX") - # Add the data flops above the spare write enable flops - data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + self.spare_wen_bus_size + self.data_bus_size + 2 * self.dff.height) - self.data_dff_insts[port].place(data_pos[port], mirror="MX") - - else: - # Add the data flops above the bank to the left of the upper-right of bank array - # This relies on the upper-right of the array of the bank - # decoder in upper left, bank in upper right, sensing in lower right. - # These flops go below the sensing and leave a gap to channel route to the - # sense amps. - data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + self.data_bus_size + self.dff.height) - self.data_dff_insts[port].place(data_pos[port], mirror="MX") - - # Add the col address flops above the bank to the right of the upper-right of bank array - if self.col_addr_dff: - if self.write_size: - col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap, - self.bank.height + bus_size + self.dff.height) - elif self.num_spare_cols and not self.write_size: - col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap, - self.bank.height + self.spare_wen_bus_size + self.dff.height) - else: - col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap, - self.bank.height + self.data_bus_size + self.dff.height) - self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") - else: - col_addr_pos[port] = self.bank_inst.ur() # This includes 2 M2 pitches for the row addr clock line control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, @@ -242,6 +142,42 @@ class sram_1bank(sram_base): row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY") + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width + y_offset = self.bank.height + self.data_bus_size + self.dff.height + if self.col_addr_dff: + col_addr_pos[port] = vector(x_offset - self.col_addr_dff_insts[port].width, + y_offset) + self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") + x_offset = self.col_addr_dff_insts[port].lx() + else: + col_addr_pos[port] = vector(x_offset, y_offset) + + if port in self.write_ports: + if self.write_size: + # Add the write mask flops below the write mask AND array. + wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width, + y_offset) + self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") + x_offset = self.wmask_dff_insts[port].lx() + + # Add spare write enable flops to the right of write mask flops + if self.num_spare_cols: + spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, + y_offset) + self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX") + x_offset = self.spare_wen_dff_insts[port].lx() + + # Add the data flops below the write mask flops. + data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, + y_offset) + self.data_dff_insts[port].place(data_pos[port], mirror="MX") + + else: + wmask_pos[port] = vector(x_offset, y_offset) + data_pos[port] = vector(x_offset, y_offset) + spare_wen_pos[port] = vector(x_offset, y_offset) + def add_layout_pins(self): """ Add the top-level pins for a single bank SRAM with control. @@ -250,7 +186,6 @@ class sram_1bank(sram_base): lowest_coord = self.find_lowest_coords() bbox = [lowest_coord, highest_coord] - for port in self.all_ports: # Depending on the port, use the bottom/top or left/right sides # Port 0 is left/bottom @@ -370,17 +305,83 @@ class sram_1bank(sram_base): self.route_row_addr_dff() - if self.col_addr_dff: - self.route_col_addr_dff() + # if self.col_addr_dff: + # self.route_col_addr_dff() - self.route_data_dff() + # self.route_data_dff() - if self.write_size: - self.route_wmask_dff() + # if self.write_size: + # self.route_wmask_dff() - if self.num_spare_cols: - self.route_spare_wen_dff() + # if self.num_spare_cols: + # self.route_spare_wen_dff() + for port in self.all_ports: + self.route_dff(port) + def route_dff(self, port): + + route_map = [] + + # column mux dff + if self.col_addr_size > 0: + dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] + dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + # spare wen dff + if self.num_spare_cols > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] + dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + # wmask dff + if self.num_wmasks > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] + dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.write_ports: + # data dff + dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] + dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if self.num_wmasks > 0 and port in self.write_ports: + vertical_layer = "m4" + layer_stack = self.m3_stack + else: + vertical_layer = "m2" + layer_stack = self.m1_stack + for (pin1, pin2) in route_map: + if pin1.layer != vertical_layer: + self.add_via_stack_center(from_layer=pin1.layer, + to_layer=vertical_layer, + offset=pin1.center()) + if pin2.layer != vertical_layer: + self.add_via_stack_center(from_layer=pin2.layer, + to_layer=vertical_layer, + offset=pin2.center()) + + if port == 0: + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + - self.data_bus_size + 2 * self.m1_pitch) + else: + offset = vector(0, + self.bank.height + 2 * self.m1_space) + + if len(route_map) > 0: + self.create_horizontal_channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack) + def route_clk(self): """ Route the clock network """ @@ -423,8 +424,7 @@ class sram_1bank(sram_base): mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) self.add_wire(self.m2_stack[::-1], [dff_clk_pos, mid_pos, clk_steiner_pos]) - - if port in self.write_ports: + elif port in self.write_ports: data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") data_dff_clk_pos = data_dff_clk_pin.center() mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) @@ -436,24 +436,6 @@ class sram_1bank(sram_base): self.add_wire(self.m2_stack[::-1], [data_dff_clk_pos, mid_pos, clk_steiner_pos]) - if self.write_size: - wmask_dff_clk_pin = self.wmask_dff_insts[port].get_pin("clk") - wmask_dff_clk_pos = wmask_dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, wmask_dff_clk_pos.y) - # In some designs, the steiner via will be too close to the mid_pos via - # so make the wire as wide as the contacts - self.add_path("m2", [mid_pos, clk_steiner_pos], width=max(m2_via.width, m2_via.height)) - self.add_wire(self.m2_stack[::-1], [wmask_dff_clk_pos, mid_pos, clk_steiner_pos]) - - if self.num_spare_cols: - spare_wen_dff_clk_pin = self.spare_wen_dff_insts[port].get_pin("clk") - spare_wen_dff_clk_pos = spare_wen_dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, spare_wen_dff_clk_pos.y) - # In some designs, the steiner via will be too close to the mid_pos via - # so make the wire as wide as the contacts - self.add_path("m2", [mid_pos, clk_steiner_pos], width=max(m2_via.width, m2_via.height)) - self.add_wire(self.m2_stack[::-1], [spare_wen_dff_clk_pos, mid_pos, clk_steiner_pos]) - def route_control_logic(self): """ Route the control logic pins that are not inputs """ From 0c9f52e22f3b8a9d1107cf1ebce746ac2f17b28a Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 07:15:06 -0700 Subject: [PATCH 33/67] Realign col decoder and control by 1/4 so metal can pass over --- compiler/modules/bank.py | 11 ++++++++--- compiler/sram/sram_1bank.py | 13 +++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 6134b143..7bbed020 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -217,11 +217,12 @@ class bank(design.design): # Place the col decoder left aligned with wordline driver # This is also placed so that it's supply rails do not align with the SRAM-level # control logic to allow control signals to easily pass over in M3 - # by placing 1/2 a cell pitch down + # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs + # may be routed in M3 or M4 x_offset = self.central_bus_width[port] + self.port_address.wordline_driver.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = 0.5 * self.dff.height + self.column_decoder.height + y_offset = 1.25 * self.dff.height + self.column_decoder.height else: y_offset = 0 self.column_decoder_offsets[port] = vector(-x_offset, -y_offset) @@ -258,10 +259,14 @@ class bank(design.design): # UPPER RIGHT QUADRANT # Place the col decoder right aligned with wordline driver # Above the bitcell array with a well spacing + # This is also placed so that it's supply rails do not align with the SRAM-level + # control logic to allow control signals to easily pass over in M3 + # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs + # may be routed in M3 or M4 x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address.wordline_driver.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = self.bitcell_array_top + 0.5 * self.dff.height + self.column_decoder.height + y_offset = self.bitcell_array_top + 1.25 * self.dff.height + self.column_decoder.height else: y_offset = self.bitcell_array_top self.column_decoder_offsets[port] = vector(x_offset, y_offset) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 4e2be40e..b8efd195 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -77,8 +77,10 @@ class sram_1bank(sram_base): port = 0 # This includes 2 M2 pitches for the row addr clock line. + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, - self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y - 2 * self.bank.m2_gap) + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) self.control_logic_insts[port].place(control_pos[port]) # The row address bits are placed above the control logic aligned on the right. @@ -129,10 +131,13 @@ class sram_1bank(sram_base): port = 1 # This includes 2 M2 pitches for the row addr clock line + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, - self.bank.bank_array_ur.y + self.control_logic_insts[port].height - \ - (self.control_logic_insts[port].height - self.control_logic_insts[port].mod.control_logic_center.y) - + 2 * self.bank.m2_gap) + self.bank.bank_array_ur.y + + self.control_logic_insts[port].height + - self.control_logic_insts[port].height + + self.control_logic_insts[port].mod.control_logic_center.y) self.control_logic_insts[port].place(control_pos[port], mirror="XY") # The row address bits are placed above the control logic aligned on the left. From e774314add76384d4fdaa4a31346fc3da26027e7 Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 14:14:48 -0700 Subject: [PATCH 34/67] Separate write driver pins by M3 pitch --- technology/freepdk45/gds_lib/write_driver.gds | Bin 20480 -> 20332 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/technology/freepdk45/gds_lib/write_driver.gds b/technology/freepdk45/gds_lib/write_driver.gds index 86015e7a7991ff9a3775c8e11fa1fee5ce5113a1..44a67dd0e23566a60892773f14112232386bca06 100644 GIT binary patch delta 281 zcmZoz!1!h!V;ln;0~1pOgA5}R`x78rhJlZPg+U6L&A`LNmXey5SejG9z#ziRYNv2y z_R`0}2P~Ch#4q@+dB-5bic{~#I3bD}GiLuwe{5GOSKp zAO=bdDVceb|C?(-ZGB+w4rS?Bs7z+FP=xZB99$u+%{~qa`~W7% BN$UUr delta 440 zcmaDekFj9^V;ln;0}~L-FfuXl16cwL+zh4+S`5Mr91NTcTnyR_#tb}6Y$>T}iKRIu z3=AU7tab`VW-oml{LNA+M*M>Bns*E`tV|4iKz))x9U2TK$a*)%3F%BOFjr(f%K!wE zr}xLt70I!8hq89qtC(>xFtG75G4L|5@i76_>j*G2Ffdhvv_fb`2?hpc zb_NC*4H4h`!Ca4#3oJerBtKch^3r5Cdqq{Sr3^eWtWI0NMldk&fK5CN Date: Sun, 28 Jun 2020 14:28:18 -0700 Subject: [PATCH 35/67] Pick correct side of pin in channel route. --- compiler/base/channel_route.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index d6048376..8a35891c 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -240,10 +240,18 @@ class channel_route(design.design): # Route each pin to the trunk for pin in pins: mid = vector(pin.center().x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin.center(), mid]) + # Find the correct side of the pin + if pin.cy() < trunk_offset.y: + pin_pos = pin.uc() + else: + pin_pos = pin.bc() + self.add_path(self.vertical_layer, [pin_pos, mid]) self.add_via_center(layers=self.layer_stack, offset=mid, directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) def add_vertical_trunk_route(self, pins, @@ -280,10 +288,18 @@ class channel_route(design.design): # Route each pin to the trunk for pin in pins: mid = vector(trunk_offset.x, pin.center().y) - self.add_path(self.horizontal_layer, [pin.center(), mid]) + # Find the correct side of the pin + if pin.cx() < trunk_offset.x: + pin_pos = pin.rc() + else: + pin_pos = pin.lc() + self.add_path(self.horizontal_layer, [pin_pos, mid]) self.add_via_center(layers=self.layer_stack, offset=mid, directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.horizontal_layer, + offset=pin_pos) def vcg_pin_overlap(self, pin1, pin2, pitch): """ Check for vertical or horizontal overlap of the two pins """ From 4df02dad679b92537571af123752effbb78ab4d5 Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 14:28:43 -0700 Subject: [PATCH 36/67] Move spare wen_dff to the right by spare columns --- compiler/sram/sram_1bank.py | 42 ++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index b8efd195..61a0e5ef 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -109,18 +109,20 @@ class sram_1bank(sram_base): self.wmask_dff_insts[port].place(wmask_pos[port]) x_offset = self.wmask_dff_insts[port].rx() - # Add spare write enable flops to the right of write mask flops + # Add the data flops below the write mask flops. + data_pos[port] = vector(x_offset, + y_offset) + self.data_dff_insts[port].place(data_pos[port]) + x_offset = self.data_dff_insts[port].rx() + + # Add spare write enable flops to the right of data flops since the spare columns + # will be on the right if self.num_spare_cols: spare_wen_pos[port] = vector(x_offset, y_offset) self.spare_wen_dff_insts[port].place(spare_wen_pos[port]) x_offset = self.spare_wen_dff_insts[port].rx() - # Add the data flops below the write mask flops. - data_pos[port] = vector(x_offset, - y_offset) - self.data_dff_insts[port].place(data_pos[port]) - else: wmask_pos[port] = vector(x_offset, y_offset) data_pos[port] = vector(x_offset, y_offset) @@ -159,6 +161,14 @@ class sram_1bank(sram_base): col_addr_pos[port] = vector(x_offset, y_offset) if port in self.write_ports: + # Add spare write enable flops to the right of the data flops since the spare + # columns will be on the left + if self.num_spare_cols: + spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, + y_offset) + self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX") + x_offset = self.spare_wen_dff_insts[port].lx() + if self.write_size: # Add the write mask flops below the write mask AND array. wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width, @@ -166,18 +176,12 @@ class sram_1bank(sram_base): self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") x_offset = self.wmask_dff_insts[port].lx() - # Add spare write enable flops to the right of write mask flops - if self.num_spare_cols: - spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, - y_offset) - self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX") - x_offset = self.spare_wen_dff_insts[port].lx() - # Add the data flops below the write mask flops. data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, y_offset) self.data_dff_insts[port].place(data_pos[port], mirror="MX") + else: wmask_pos[port] = vector(x_offset, y_offset) data_pos[port] = vector(x_offset, y_offset) @@ -360,20 +364,9 @@ class sram_1bank(sram_base): route_map.extend(list(zip(bank_pins, dff_pins))) if self.num_wmasks > 0 and port in self.write_ports: - vertical_layer = "m4" layer_stack = self.m3_stack else: - vertical_layer = "m2" layer_stack = self.m1_stack - for (pin1, pin2) in route_map: - if pin1.layer != vertical_layer: - self.add_via_stack_center(from_layer=pin1.layer, - to_layer=vertical_layer, - offset=pin1.center()) - if pin2.layer != vertical_layer: - self.add_via_stack_center(from_layer=pin2.layer, - to_layer=vertical_layer, - offset=pin2.center()) if port == 0: offset = vector(self.control_logic_insts[port].rx() + self.dff.width, @@ -580,6 +573,7 @@ class sram_1bank(sram_base): self.create_horizontal_channel_route(netlist=route_map, offset=offset, layer_stack=self.m1_stack) + def route_spare_wen_dff(self): """ Connect the output of the spare write enable flops to the spare write drivers """ # This is where the channel will start (y-dimension at least) From 225fc69420164c6754c1b5aa1eaea204efe62388 Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 14:29:12 -0700 Subject: [PATCH 37/67] Use preferred routing direction --- compiler/modules/bank.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 7bbed020..ed835d43 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -888,7 +888,7 @@ class bank(design.design): route_map = list(zip(decode_pins, column_mux_pins)) if "li" in layer: stack = self.li_stack - directions = "nonpref" + directions = "pref" else: stack = self.m1_stack directions = "pref" From 709535f90fee00f0a7258aa7a7c354c81174b086 Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 14:47:17 -0700 Subject: [PATCH 38/67] Fix right perimeter pin coordinate bug --- compiler/base/hierarchy_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 2a29be6b..50b9f78a 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1165,7 +1165,7 @@ class layout(): layer = "m3" elif side == "right": layer = "m3" - peri_pin_loc = vector(right, pin_loc.x) + peri_pin_loc = vector(right, pin_loc.y) elif side == "top": layer = "m4" peri_pin_loc = vector(pin_loc.x, top) From 051c8d8697c35caddea0e4dba2cb6c0015e63052 Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 14:47:54 -0700 Subject: [PATCH 39/67] Only add bitcells to dummy and replica rows and columns (the perimeter) --- compiler/modules/bitcell_base_array.py | 56 +++++++++----------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py index 6698df33..e162c225 100644 --- a/compiler/modules/bitcell_base_array.py +++ b/compiler/modules/bitcell_base_array.py @@ -9,6 +9,7 @@ import debug import design from tech import cell_properties + class bitcell_base_array(design.design): """ Abstract base class for bitcell-arrays -- bitcell, dummy @@ -68,10 +69,10 @@ class bitcell_base_array(design.design): pin_names = self.cell.get_all_bitline_names() for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(col)) + bitcell_pins.append(pin + "_{0}".format(col)) pin_names = self.cell.get_all_wl_names() for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(row)) + bitcell_pins.append(pin + "_{0}".format(row)) bitcell_pins.append("vdd") bitcell_pins.append("gnd") @@ -85,46 +86,27 @@ class bitcell_base_array(design.design): for col in range(self.column_size): for cell_column in column_list: - bl_pin = self.cell_inst[0,col].get_pin(cell_column) - self.add_layout_pin(text=cell_column+"_{0}".format(col), + bl_pin = self.cell_inst[0, col].get_pin(cell_column) + self.add_layout_pin(text=cell_column + "_{0}".format(col), layer=bl_pin.layer, - offset=bl_pin.ll().scale(1,0), + offset=bl_pin.ll().scale(1, 0), width=bl_pin.width(), height=self.height) for row in range(self.row_size): for cell_row in row_list: - wl_pin = self.cell_inst[row,0].get_pin(cell_row) - self.add_layout_pin(text=cell_row+"_{0}".format(row), + wl_pin = self.cell_inst[row, 0].get_pin(cell_row) + self.add_layout_pin(text=cell_row + "_{0}".format(row), layer=wl_pin.layer, - offset=wl_pin.ll().scale(0,1), + offset=wl_pin.ll().scale(0, 1), width=self.width, height=wl_pin.height()) - # For non-square via stacks, vertical/horizontal direction refers to the stack orientation in 2d space - # Default uses prefered directions for each layer; this cell property is only currently used by sky130 tech (03/20) - try: - bitcell_power_pin_directions = cell_properties.bitcell_power_pin_directions - except AttributeError: - bitcell_power_pin_directions = None - - # For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps. - try: - end_caps_enabled = cell_properties.bitcell.end_caps - except AttributeError: - end_caps_enabled = False - - # Add vdd/gnd via stacks - if not end_caps_enabled: - for row in range(self.row_size): - for col in range(self.column_size): - inst = self.cell_inst[row, col] - for pin_name in ["vdd", "gnd"]: - for pin in inst.get_pins(pin_name): - self.add_power_pin(name=pin_name, - loc=pin.center(), - directions=bitcell_power_pin_directions, - start_layer=pin.layer) + # Copy a vdd/gnd layout pin from every column in the first row + for col in range(self.column_size): + inst = self.cell_inst[0, col] + for pin_name in ["vdd", "gnd"]: + self.copy_layout_pin(inst, pin_name) def _adjust_x_offset(self, xoffset, col, col_offset): tempx = xoffset @@ -144,11 +126,10 @@ class bitcell_base_array(design.design): dir_x = True return (tempy, dir_x) - def place_array(self, name_template, row_offset=0): # We increase it by a well enclosure so the precharges don't overlap our wells - self.height = self.row_size*self.cell.height - self.width = self.column_size*self.cell.width + self.height = self.row_size * self.cell.height + self.width = self.column_size * self.cell.width xoffset = 0.0 for col in range(self.column_size): @@ -156,7 +137,6 @@ class bitcell_base_array(design.design): tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset) for row in range(self.row_size): - name = name_template.format(row, col) tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset) if dir_x and dir_y: @@ -168,7 +148,7 @@ class bitcell_base_array(design.design): else: dir_key = "" - self.cell_inst[row,col].place(offset=[tempx, tempy], - mirror=dir_key) + self.cell_inst[row, col].place(offset=[tempx, tempy], + mirror=dir_key) yoffset += self.cell.height xoffset += self.cell.width From 20324ab3c4ceca04f379e66d27178e92bc9b9474 Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 14:55:58 -0700 Subject: [PATCH 40/67] Revert write driver pin spacing --- technology/freepdk45/gds_lib/write_driver.gds | Bin 20332 -> 20480 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/technology/freepdk45/gds_lib/write_driver.gds b/technology/freepdk45/gds_lib/write_driver.gds index 44a67dd0e23566a60892773f14112232386bca06..86015e7a7991ff9a3775c8e11fa1fee5ce5113a1 100644 GIT binary patch delta 440 zcmaDekFj9^V;ln;0}~L-FfuXl16cwL+zh4+S`5Mr91NTcTnyR_#tb}6Y$>T}iKRIu z3=AU7tab`VW-oml{LNA+M*M>Bns*E`tV|4iKz))x9U2TK$a*)%3F%BOFjr(f%K!wE zr}xLt70I!8hq89qtC(>xFtG75G4L|5@i76_>j*G2Ffdhvv_fb`2?hpc zb_NC*4H4h`!Ca4#3oJerBtKch^3r5Cdqq{Sr3^eWtWI0NMldk&fK5CND}GiLuwe{5GOSKp zAO=bdDVceb|C?(-ZGB+w4rS?Bs7z+FP=xZB99$u+%{~qa`~W7% BN$UUr From 751eab202bd308b715541424fe0ca9b736ad347d Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 15:06:29 -0700 Subject: [PATCH 41/67] Move row addr flops away from predecode. Route spare wen separately on lower layer. --- compiler/sram/sram_1bank.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 61a0e5ef..73df61bb 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -86,7 +86,7 @@ class sram_1bank(sram_base): # The row address bits are placed above the control logic aligned on the right. x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width # It is above the control logic but below the top of the bitcell array - y_offset = max(self.control_logic_insts[port].uy(), self.control_logic_insts[port].uy() + self.dff.height) + y_offset = max(self.control_logic_insts[port].uy(), self.bank_inst.uy() - self.row_addr_dff_insts[port].height) row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port]) @@ -145,7 +145,7 @@ class sram_1bank(sram_base): # The row address bits are placed above the control logic aligned on the left. x_offset = control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width # It is below the control logic but below the bottom of the bitcell array - y_offset = min(self.control_logic_insts[port].by(), self.control_logic_insts[port].by() - self.dff.height) + y_offset = min(self.control_logic_insts[port].by(), self.bank_inst.by() + self.row_addr_dff_insts[port].height) row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY") @@ -180,8 +180,6 @@ class sram_1bank(sram_base): data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, y_offset) self.data_dff_insts[port].place(data_pos[port], mirror="MX") - - else: wmask_pos[port] = vector(x_offset, y_offset) data_pos[port] = vector(x_offset, y_offset) @@ -339,14 +337,6 @@ class sram_1bank(sram_base): bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] route_map.extend(list(zip(bank_pins, dff_pins))) - # spare wen dff - if self.num_spare_cols > 0 and port in self.write_ports: - dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] - dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - # wmask dff if self.num_wmasks > 0 and port in self.write_ports: dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] @@ -379,6 +369,18 @@ class sram_1bank(sram_base): self.create_horizontal_channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack) + + # Route these separately because sometimes the pin pitch on the write driver is too narrow for M3 (FreePDK45) + # spare wen dff + if self.num_spare_cols > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] + dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map = zip(bank_pins, dff_pins) + self.create_horizontal_channel_route(netlist=route_map, + offset=offset, + layer_stack=self.m1_stack) def route_clk(self): """ Route the clock network """ From 5285468380cfd20e8dc61f58a6503a883a9c131b Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 15:09:47 -0700 Subject: [PATCH 42/67] All bitcells need a vdd/gnd pin --- compiler/modules/bitcell_base_array.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py index e162c225..7d241b4d 100644 --- a/compiler/modules/bitcell_base_array.py +++ b/compiler/modules/bitcell_base_array.py @@ -102,11 +102,12 @@ class bitcell_base_array(design.design): width=self.width, height=wl_pin.height()) - # Copy a vdd/gnd layout pin from every column in the first row - for col in range(self.column_size): - inst = self.cell_inst[0, col] - for pin_name in ["vdd", "gnd"]: - self.copy_layout_pin(inst, pin_name) + # Copy a vdd/gnd layout pin from every cell + for row in range(self.row_size): + for col in range(self.column_size): + inst = self.cell_inst[row, col] + for pin_name in ["vdd", "gnd"]: + self.copy_layout_pin(inst, pin_name) def _adjust_x_offset(self, xoffset, col, col_offset): tempx = xoffset From 47f541df0e07bbd66a13db7ae203e508c8ac836a Mon Sep 17 00:00:00 2001 From: mrg Date: Sun, 28 Jun 2020 16:58:28 -0700 Subject: [PATCH 43/67] Fix bugs in channel route. --- compiler/base/channel_route.py | 77 +++++++++++++++------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index 8a35891c..5e9b9531 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -216,42 +216,38 @@ class channel_route(design.design): """ max_x = max([pin.center().x for pin in pins]) min_x = min([pin.center().x for pin in pins]) - + # if we are less than a pitch, just create a non-preferred layer jog - if max_x - min_x <= pitch: + non_preferred_route = max_x - min_x <= pitch + + if non_preferred_route: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] - # Add the horizontal trunk on the vertical layer! self.add_path(self.vertical_layer, [vector(min_x - half_layer_width, trunk_offset.y), vector(max_x + half_layer_width, trunk_offset.y)]) - - # Route each pin to the trunk - for pin in pins: - # No bend needed here - mid = vector(pin.center().x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin.center(), mid]) else: # Add the horizontal trunk self.add_path(self.horizontal_layer, [vector(min_x, trunk_offset.y), vector(max_x, trunk_offset.y)]) - # Route each pin to the trunk - for pin in pins: - mid = vector(pin.center().x, trunk_offset.y) - # Find the correct side of the pin - if pin.cy() < trunk_offset.y: - pin_pos = pin.uc() - else: - pin_pos = pin.bc() - self.add_path(self.vertical_layer, [pin_pos, mid]) + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cy() < trunk_offset.y: + pin_pos = pin.uc() + else: + pin_pos = pin.bc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + if not non_preferred_route: self.add_via_center(layers=self.layer_stack, offset=mid, directions=self.directions) - self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.vertical_layer, - offset=pin_pos) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) def add_vertical_trunk_route(self, pins, @@ -263,43 +259,38 @@ class channel_route(design.design): """ max_y = max([pin.center().y for pin in pins]) min_y = min([pin.center().y for pin in pins]) - + # if we are less than a pitch, just create a non-preferred layer jog - if max_y - min_y <= pitch: - + non_preferred_route = max_y - min_y <= pitch + + if non_preferred_route: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] - # Add the vertical trunk on the horizontal layer! self.add_path(self.horizontal_layer, [vector(trunk_offset.x, min_y - half_layer_width), vector(trunk_offset.x, max_y + half_layer_width)]) - - # Route each pin to the trunk - for pin in pins: - # No bend needed here - mid = vector(trunk_offset.x, pin.center().y) - self.add_path(self.horizontal_layer, [pin.center(), mid]) else: # Add the vertical trunk self.add_path(self.vertical_layer, [vector(trunk_offset.x, min_y), vector(trunk_offset.x, max_y)]) - # Route each pin to the trunk - for pin in pins: - mid = vector(trunk_offset.x, pin.center().y) - # Find the correct side of the pin - if pin.cx() < trunk_offset.x: - pin_pos = pin.rc() - else: - pin_pos = pin.lc() - self.add_path(self.horizontal_layer, [pin_pos, mid]) + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cx() < trunk_offset.x: + pin_pos = pin.rc() + else: + pin_pos = pin.lc() + mid = vector(trunk_offset.x, pin_pos.y) + self.add_path(self.horizontal_layer, [pin_pos, mid]) + if not non_preferred_route: self.add_via_center(layers=self.layer_stack, offset=mid, directions=self.directions) - self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.horizontal_layer, - offset=pin_pos) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.horizontal_layer, + offset=pin_pos) def vcg_pin_overlap(self, pin1, pin2, pitch): """ Check for vertical or horizontal overlap of the two pins """ From 5f3a45b91b41d98d99560c9a3b6f2e55fb4c98a5 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 29 Jun 2020 05:54:30 -0700 Subject: [PATCH 44/67] Compute bus size separately for ports --- compiler/sram/sram_1bank.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 73df61bb..ac86eb94 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -71,7 +71,19 @@ class sram_1bank(sram_base): # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. # So, m3 non-pref pitch means that this is routed on the m2 layer. self.data_bus_gap = self.m4_nonpref_pitch * 2 - self.data_bus_size = self.m4_nonpref_pitch * (self.word_size + self.num_spare_cols + self.num_wmasks + self.col_addr_size + self.num_spare_cols) + self.data_bus_gap + + # Spare wen are on a separate layer so not included + self.data_bus_size = [None] * len(self.all_ports) + for port in self.all_ports: + # All ports need the col addr flops + self.data_bus_size[port] = self.col_addr_size + # Write ports need the data input flops and write mask flops + if port in self.write_ports: + self.data_bus_size[port] += self.num_wmasks + self.word_size + # Convert to length + self.data_bus_size[port] *= self.m4_nonpref_pitch + # Add the gap in unit length + self.data_bus_size[port] += self.data_bus_gap # Port 0 port = 0 @@ -92,7 +104,7 @@ class sram_1bank(sram_base): # Add the col address flops below the bank to the right of the control logic x_offset = self.control_logic_insts[port].rx() + self.dff.width - y_offset = - self.data_bus_size - self.dff.height + y_offset = - self.data_bus_size[port] - self.dff.height if self.col_addr_dff: col_addr_pos[port] = vector(x_offset, y_offset) @@ -151,7 +163,7 @@ class sram_1bank(sram_base): # Add the col address flops below the bank to the right of the control logic x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width - y_offset = self.bank.height + self.data_bus_size + self.dff.height + y_offset = self.bank.height + self.data_bus_size[port] + self.dff.height if self.col_addr_dff: col_addr_pos[port] = vector(x_offset - self.col_addr_dff_insts[port].width, y_offset) @@ -360,7 +372,7 @@ class sram_1bank(sram_base): if port == 0: offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - - self.data_bus_size + 2 * self.m1_pitch) + - self.data_bus_size[port] + 2 * self.m1_pitch) else: offset = vector(0, self.bank.height + 2 * self.m1_space) @@ -507,7 +519,7 @@ class sram_1bank(sram_base): # This is where the channel will start (y-dimension at least) for port in self.write_ports: if port % 2: - offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size) + offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size[port]) else: offset = self.data_dff_insts[port].ul() + vector(0, self.data_bus_gap) From 1bc0775810685ba142e89a917fb7ae7455303ece Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 29 Jun 2020 10:03:24 -0700 Subject: [PATCH 45/67] Only add pins to periphery --- compiler/modules/replica_bitcell_array.py | 14 ++++---------- compiler/modules/replica_column.py | 9 ++------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 57bb3cf9..09a014ce 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -378,16 +378,10 @@ class replica_bitcell_array(design.design): width=pin.width(), height=self.height) - # For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps. - try: - if cell_properties.bitcell.end_caps_enabled: - supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst, - self.dummy_row_top_inst, self.dummy_row_bot_inst] + list(self.replica_col_inst.values()) - else: - supply_insts = self.insts - except AttributeError: - supply_insts = self.insts - + # vdd/gnd are only connected in the perimeter cells + # replica column should only have a vdd/gnd in the dummy cell on top/bottom + supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst, + self.dummy_row_top_inst, self.dummy_row_bot_inst] + list(self.replica_col_inst.values()) for pin_name in ["vdd", "gnd"]: for inst in supply_insts: pin_list = inst.get_pins(pin_name) diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index 58d35e7f..9cd65e57 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -183,13 +183,8 @@ class replica_column(design.design): width=self.width, height=wl_pin.height()) - # For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps. - if end_caps_enabled: - supply_insts = [self.cell_inst[0], self.cell_inst[self.total_size - 1]] - else: - supply_insts = self.cell_inst.values() - - for inst in supply_insts: + # Supplies are only connected in the ends + for inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]: for pin_name in ["vdd", "gnd"]: if pin_name in inst.mod.pins: self.copy_layout_pin(inst, pin_name) From 07d0f3af8e031f60cf0783deb7348764fbd35667 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 29 Jun 2020 11:46:59 -0700 Subject: [PATCH 46/67] Only copy end-cap pins to the bank level --- compiler/base/hierarchy_layout.py | 4 ++-- compiler/modules/bank.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 50b9f78a..378a4657 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1101,7 +1101,7 @@ class layout(): height=ymax - ymin) return rect - def copy_power_pins(self, inst, name): + def copy_power_pins(self, inst, name, add_vias=True): """ This will copy a power pin if it is on the lowest power_grid layer. If it is on M1, it will add a power via too. @@ -1115,7 +1115,7 @@ class layout(): pin.width(), pin.height()) - else: + elif add_vias: self.add_power_pin(name, pin.center(), start_layer=pin.layer) def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"): diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index ed835d43..e18660e3 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -132,14 +132,17 @@ class bank(design.design): # Connect the rbl to the port data pin bl_pin = self.port_data_inst[port].get_pin("rbl_bl") if port % 2: - pin_offset = bl_pin.uc() + pin_pos = bl_pin.uc() + pin_offset = pin_pos + vector(0, self.m3_pitch) left_right_offset = vector(self.max_x_offset, pin_offset.y) else: - pin_offset = bl_pin.bc() + pin_pos = bl_pin.bc() + pin_offset = pin_pos - vector(0, self.m3_pitch) left_right_offset = vector(self.min_x_offset, pin_offset.y) self.add_via_stack_center(from_layer=bl_pin.layer, to_layer="m3", offset=pin_offset) + self.add_path(bl_pin.layer, [pin_offset, pin_pos]) self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port), layer="m3", start=left_right_offset, @@ -583,9 +586,11 @@ class bank(design.design): def route_supplies(self): """ Propagate all vdd/gnd pins up to this level for all modules """ + # Copy only the power pins already on the power layer + # (this won't add vias to internal bitcell pins, for example) for inst in self.insts: - self.copy_power_pins(inst, "vdd") - self.copy_power_pins(inst, "gnd") + self.copy_power_pins(inst, "vdd", add_vias=False) + self.copy_power_pins(inst, "gnd", add_vias=False) def route_bank_select(self, port): """ Route the bank select logic. """ From e97644c424d562d3dee9a1a8ca1fdb51937abe54 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Mon, 29 Jun 2020 14:42:24 -0700 Subject: [PATCH 47/67] Only do reverse lookup on valid interconnect layers since layer numbers can be shared. --- compiler/base/pin_layout.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index f758a903..dac4e525 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -8,7 +8,7 @@ import debug from tech import GDS, drc from vector import vector -from tech import layer +from tech import layer, layer_indices import math @@ -31,12 +31,15 @@ class pin_layout: debug.check(self.width() > 0, "Zero width pin.") debug.check(self.height() > 0, "Zero height pin.") + # These are the valid pin layers + valid_layers = { x: layer[x] for x in layer_indices.keys()} + # if it's a string, use the name if type(layer_name_pp) == str: self._layer = layer_name_pp # else it is required to be a lpp else: - for (layer_name, lpp) in layer.items(): + for (layer_name, lpp) in valid_layers.items(): if not lpp: continue if self.same_lpp(layer_name_pp, lpp): From 4e7e0c5954316b3d3d0ef1f0a31a6f20a94c4905 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 29 Jun 2020 15:28:16 -0700 Subject: [PATCH 48/67] Skip test in sky130 --- compiler/tests/14_replica_bitcell_array_1rw_1r_test.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py b/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py index a36fc80f..626725c4 100755 --- a/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py +++ b/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py @@ -28,9 +28,11 @@ class replica_bitcell_array_1rw_1r_test(openram_test): a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=1, bitcell_ports=[0, 1]) self.local_check(a) - debug.info(2, "Testing 4x4 array for cell_1rw_1r") - a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0, 1]) - self.local_check(a) + # Sky 130 has restrictions on the symmetries + if OPTS.tech_name != "sky130": + debug.info(2, "Testing 4x4 array for cell_1rw_1r") + a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0, 1]) + self.local_check(a) globals.end_openram() From bec948dcc3d08caab36e9937f73ebb55d511c0e5 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 29 Jun 2020 15:28:55 -0700 Subject: [PATCH 49/67] Fix error in when to add vias for array power --- compiler/modules/replica_bitcell_array.py | 6 +++++- compiler/modules/replica_column.py | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 09a014ce..0c7e412e 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -381,7 +381,7 @@ class replica_bitcell_array(design.design): # vdd/gnd are only connected in the perimeter cells # replica column should only have a vdd/gnd in the dummy cell on top/bottom supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst, - self.dummy_row_top_inst, self.dummy_row_bot_inst] + list(self.replica_col_inst.values()) + self.dummy_row_top_inst, self.dummy_row_bot_inst] for pin_name in ["vdd", "gnd"]: for inst in supply_insts: pin_list = inst.get_pins(pin_name) @@ -390,6 +390,10 @@ class replica_bitcell_array(design.design): loc=pin.center(), directions=("V", "V"), start_layer=pin.layer) + + for inst in list(self.replica_col_inst.values()): + self.copy_layout_pin(inst, pin_name) + self.copy_layout_pin(inst, pin_name) def get_rbl_wl_name(self, port): """ Return the WL for the given RBL port """ diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index 9cd65e57..9613e6fa 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -184,9 +184,11 @@ class replica_column(design.design): height=wl_pin.height()) # Supplies are only connected in the ends - for inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]: + for (index, inst) in self.cell_inst.items(): for pin_name in ["vdd", "gnd"]: - if pin_name in inst.mod.pins: + if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]: + self.copy_power_pins(inst, pin_name) + else: self.copy_layout_pin(inst, pin_name) def get_bitcell_pins(self, col, row): From 459e3789b815ddf7c3332d3a555dc0c912bdf6f4 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 29 Jun 2020 16:23:25 -0700 Subject: [PATCH 50/67] Change control layers in sky130. --- compiler/modules/bank.py | 32 +++++++---- compiler/modules/precharge_array.py | 28 +++++----- compiler/modules/sense_amp_array.py | 25 ++++++--- .../modules/single_level_column_mux_array.py | 56 ++++++++++--------- 4 files changed, 84 insertions(+), 57 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index e18660e3..5716986f 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -592,6 +592,18 @@ class bank(design.design): self.copy_power_pins(inst, "vdd", add_vias=False) self.copy_power_pins(inst, "gnd", add_vias=False) + # If we use the pinvbuf as the decoder, we need to add power pins. + # Other decoders already have them. + if self.col_addr_size == 1: + for port in self.all_ports: + inst = self.column_decoder_inst[port] + for pin_name in ["vdd", "gnd"]: + pin_list = inst.get_pins(pin_name) + for pin in pin_list: + self.add_power_pin(pin_name, + pin.center(), + start_layer=pin.layer) + def route_bank_select(self, port): """ Route the bank select logic. """ @@ -862,6 +874,13 @@ class bank(design.design): if not self.col_addr_size>0: return + if OPTS.tech_name == "sky130": + stack = self.m2_stack + pitch = self.m3_pitch + else: + stack = self.m1_stack + pitch = self.m2_pitch + if self.col_addr_size == 1: # Connect to sel[0] and sel[1] @@ -881,9 +900,9 @@ class bank(design.design): self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name) if port % 2: - offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * self.m2_nonpref_pitch, 0) + offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * pitch, 0) else: - offset = self.column_decoder_inst[port].lr() + vector(self.m2_nonpref_pitch, 0) + offset = self.column_decoder_inst[port].lr() + vector(pitch, 0) decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names] @@ -891,16 +910,9 @@ class bank(design.design): column_mux_pins = [self.port_data_inst[port].get_pin(x) for x in sel_names] route_map = list(zip(decode_pins, column_mux_pins)) - if "li" in layer: - stack = self.li_stack - directions = "pref" - else: - stack = self.m1_stack - directions = "pref" self.create_vertical_channel_route(route_map, offset, - stack, - directions=directions) + stack) def add_lvs_correspondence_points(self): """ diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index 3a3b8c8b..d37de64f 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -30,6 +30,11 @@ class precharge_array(design.design): self.bitcell_br = bitcell_br self.column_offset = column_offset + if OPTS.tech_name == "sky130": + self.en_bar_layer = "m3" + else: + self.en_bar_layer = "m1" + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -74,21 +79,18 @@ class precharge_array(design.design): def add_layout_pins(self): - en_bar_pin = self.pc_cell.get_pin("en_bar") - if en_bar_pin.layer =="li": - en_bar_layer = "m3" - else: - en_bar_layer = en_bar_pin.layer - r = self.add_layout_pin(text="en_bar", - layer=en_bar_layer, - offset=en_bar_pin.ll(), - width=self.width, - height=en_bar_pin.height()) - self.add_via_stack_center(from_layer=en_bar_pin.layer, - to_layer=en_bar_layer, - offset = r.center()) + en_pin = self.pc_cell.get_pin("en_bar") + start_offset = en_pin.lc().scale(0, 1) + end_offset = start_offset + vector(self.width, 0) + self.add_layout_pin_segment_center(text="en_bar", + layer=self.en_bar_layer, + start=start_offset, + end=end_offset) for inst in self.local_insts: + self.add_via_stack_center(from_layer=en_pin.layer, + to_layer=self.en_bar_layer, + offset=inst.get_pin("en_bar").center()) self.copy_layout_pin(inst, "vdd") for i in range(len(self.local_insts)): diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index 5d41b2a0..98cbee66 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -37,6 +37,11 @@ class sense_amp_array(design.design): self.column_offset = column_offset self.row_size = self.word_size * self.words_per_row + if OPTS.tech_name == "sky130": + self.en_layer = "m3" + else: + self.en_layer = "m1" + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -173,14 +178,18 @@ class sense_amp_array(design.design): height=dout_pin.height()) def route_rails(self): - # add sclk rail across entire array - sclk = self.amp.get_pin(self.amp.en_name) - sclk_offset = self.amp.get_pin(self.amp.en_name).ll().scale(0, 1) - self.add_layout_pin(text=self.en_name, - layer=sclk.layer, - offset=sclk_offset, - width=self.width, - height=drc("minwidth_" + sclk.layer)) + # Add enable across the array + en_pin = self.amp.get_pin(self.amp.en_name) + start_offset = en_pin.lc().scale(0, 1) + end_offset = start_offset + vector(self.width, 0) + self.add_layout_pin_segment_center(text=self.en_name, + layer=self.en_layer, + start=start_offset, + end=end_offset) + for inst in self.local_insts: + self.add_via_stack_center(from_layer=en_pin.layer, + to_layer=self.en_layer, + offset=inst.get_pin(self.amp.en_name).center()) def input_load(self): return self.amp.input_load() diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index 37fd4dc1..8b01d111 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -32,14 +32,16 @@ class single_level_column_mux_array(design.design): self.bitcell_br = bitcell_br self.column_offset = column_offset - if "li" in layer: - self.col_mux_stack = self.m1_stack[::-1] - self.col_mux_stack_pitch = self.m2_pitch + if OPTS.tech_name == "sky130": + self.sel_layer = "m3" + self.sel_pitch = self.m3_pitch + self.bitline_layer = "m1" else: - self.col_mux_stack = self.m1_stack - self.col_mux_stack_pitch = self.m1_pitch + self.sel_layer = "m1" + self.sel_pitch = self.m2_pitch + self.bitline_layer = "m2" - if preferred_directions[self.col_mux_stack[0]] == "V": + if preferred_directions[self.sel_layer] == "V": self.via_directions = ("H", "H") else: self.via_directions = "pref" @@ -96,7 +98,7 @@ class single_level_column_mux_array(design.design): self.width = self.columns * self.mux.width # one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br # one extra route pitch is to space from the sense amp - self.route_height = (self.words_per_row + 3) * self.col_mux_stack_pitch + self.route_height = (self.words_per_row + 3) * self.sel_pitch def create_array(self): self.mux_inst = [] @@ -157,9 +159,9 @@ class single_level_column_mux_array(design.design): def add_horizontal_input_rail(self): """ Create address input rails below the mux transistors """ for j in range(self.words_per_row): - offset = vector(0, self.route_height + (j - self.words_per_row) * self.col_mux_stack_pitch) + offset = vector(0, self.route_height + (j - self.words_per_row) * self.sel_pitch) self.add_layout_pin(text="sel_{}".format(j), - layer=self.col_mux_stack[0], + layer=self.sel_layer, offset=offset, width=self.mux.width * self.columns) @@ -178,7 +180,7 @@ class single_level_column_mux_array(design.design): offset = vector(gate_offset.x, self.get_pin("sel_{}".format(sel_index)).cy()) self.add_via_stack_center(from_layer="poly", - to_layer=self.col_mux_stack[0], + to_layer=self.sel_layer, offset=offset, directions=self.via_directions) self.add_path("poly", [offset, gate_offset]) @@ -190,42 +192,44 @@ class single_level_column_mux_array(design.design): bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc() br_offset_begin = self.mux_inst[j].get_pin("br_out").bc() - bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch) - br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch) + bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.sel_pitch) + br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.sel_pitch) # Add the horizontal wires for the first bit if j % self.words_per_row == 0: bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc() br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc() - bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch) - br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch) + bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.sel_pitch) + br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.sel_pitch) - self.add_path(self.col_mux_stack[0], [bl_out_offset_begin, bl_out_offset_end]) - self.add_path(self.col_mux_stack[0], [br_out_offset_begin, br_out_offset_end]) + self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end]) + self.add_path(self.sel_layer, [br_out_offset_begin, br_out_offset_end]) # Extend the bitline output rails and gnd downward on the first bit of each n-way mux self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)), - layer=self.col_mux_stack[2], + layer=self.bitline_layer, start=bl_offset_begin, end=bl_out_offset_begin) self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)), - layer=self.col_mux_stack[2], + layer=self.bitline_layer, start=br_offset_begin, end=br_out_offset_begin) else: - self.add_path(self.col_mux_stack[2], [bl_out_offset_begin, bl_offset_begin]) - self.add_path(self.col_mux_stack[2], [br_out_offset_begin, br_offset_begin]) + self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin]) + self.add_path(self.bitline_layer, [br_out_offset_begin, br_offset_begin]) # This via is on the right of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=bl_out_offset_begin, - directions=self.via_directions) + self.add_via_stack_center(from_layer=self.bitline_layer, + to_layer=self.sel_layer, + offset=bl_out_offset_begin, + directions=self.via_directions) # This via is on the left of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=br_out_offset_begin, - directions=self.via_directions) + self.add_via_stack_center(from_layer=self.bitline_layer, + to_layer=self.sel_layer, + offset=br_out_offset_begin, + directions=self.via_directions) def get_drain_cin(self): """Get the relative capacitance of the drain of the NMOS pass TX""" From 372a8a728efb4f0a5cf4af636446626305193a16 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 29 Jun 2020 16:47:34 -0700 Subject: [PATCH 51/67] Off by one error in channel spacing --- compiler/modules/bank.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 5716986f..390d17a3 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -900,7 +900,7 @@ class bank(design.design): self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name) if port % 2: - offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * pitch, 0) + offset = self.column_decoder_inst[port].ll() - vector((self.num_col_addr_lines + 1) * pitch, 0) else: offset = self.column_decoder_inst[port].lr() + vector(pitch, 0) From 9b939c9a1a3c9890f299d038da47e7e420a97604 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Tue, 30 Jun 2020 07:16:05 -0700 Subject: [PATCH 52/67] DRC/LVS and errors fixes. Only enact pdb if assert fails in debug.error. Only run drc/lvs one time in parse_info by saving result. Cleanup drc/lvs output. --- compiler/base/hierarchy_design.py | 6 +++++- compiler/characterizer/lib.py | 8 ++++++-- compiler/debug.py | 2 +- compiler/verify/calibre.py | 25 +++++++++++-------------- compiler/verify/magic.py | 5 +++-- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 1321f06a..b0370179 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -7,7 +7,6 @@ # import hierarchy_layout import hierarchy_spice -import verify import debug import os from globals import OPTS @@ -55,6 +54,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): def DRC_LVS(self, final_verification=False, force_check=False): """Checks both DRC and LVS for a module""" + import verify # No layout to check if OPTS.netlist_only: @@ -94,6 +94,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): def DRC(self, final_verification=False): """Checks DRC for a module""" + import verify + # Unit tests will check themselves. # Do not run if disabled in options. @@ -117,6 +119,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): def LVS(self, final_verification=False): """Checks LVS for a module""" + import verify + # Unit tests will check themselves. # Do not run if disabled in options. diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index b0df4394..92882db7 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -657,8 +657,12 @@ class lib: )) # information of checks - (drc_errors, lvs_errors) = self.sram.DRC_LVS(final_verification=True) - datasheet.write("{0},{1},".format(drc_errors, lvs_errors)) + # run it only the first time + try: + datasheet.write("{0},{1},".format(self.drc_errors, self.lvs_errors)) + except AttributeError: + (self.drc_errors, self.lvs_errors) = self.sram.DRC_LVS(final_verification=True) + datasheet.write("{0},{1},".format(self.drc_errors, self.lvs_errors)) # write area datasheet.write(str(self.sram.width * self.sram.height) + ',') diff --git a/compiler/debug.py b/compiler/debug.py index a902bca0..f07471cc 100644 --- a/compiler/debug.py +++ b/compiler/debug.py @@ -40,7 +40,7 @@ def error(str, return_value=0): log("ERROR: file {0}: line {1}: {2}\n".format( os.path.basename(filename), line_number, str)) - if globals.OPTS.debug_level > 0: + if globals.OPTS.debug_level > 0 and return_value != 0: import pdb pdb.set_trace() assert return_value == 0 diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py index 0a975599..443c91ca 100644 --- a/compiler/verify/calibre.py +++ b/compiler/verify/calibre.py @@ -219,16 +219,14 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False): errors = int(re.split(r'\W+', results[2])[5]) # always display this summary - if errors > 0: - debug.error("{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, + result_str = "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, geometries, rulechecks, - errors)) + errors) + if errors > 0: + debug.warning(result_str) else: - debug.info(1, "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, - geometries, - rulechecks, - errors)) + debug.info(1, result_str) return errors @@ -307,16 +305,15 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): out_errors = len(stdouterrors) total_errors = summary_errors + out_errors + ext_errors - if total_errors > 0: - debug.error("{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, + # always display this summary + result_str = "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, summary_errors, out_errors, - ext_errors)) + ext_errors) + if total_errors > 0: + debug.warning(result_str) else: - debug.info(1, "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, - summary_errors, - out_errors, - ext_errors)) + debug.info(1, result_str) return total_errors diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index e20c0499..5df6e698 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -200,13 +200,14 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): # always display this summary + result_str = "DRC Errors {0}\t{1}".format(cell_name, errors) if errors > 0: for line in results: if "error tiles" in line: debug.info(1,line.rstrip("\n")) - debug.error("DRC Errors {0}\t{1}".format(cell_name, errors)) + debug.warning(result_str) else: - debug.info(1, "DRC Errors {0}\t{1}".format(cell_name, errors)) + debug.info(1, result_str) return errors From 8cedeeb3d945c77157c9977668a33751564983e9 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 30 Jun 2020 10:57:41 -0700 Subject: [PATCH 53/67] Widen pitch of control bus in bank. --- compiler/base/hierarchy_layout.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 378a4657..c57de915 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1135,6 +1135,7 @@ class layout(): size=size, offset=loc, directions=directions) + # Hack for min area if OPTS.tech_name == "sky130": width = round_to_grid(sqrt(drc["minarea_m3"])) From eb11ac22f3c3d5a2198817d25e1be500138cf7ec Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 30 Jun 2020 10:58:09 -0700 Subject: [PATCH 54/67] Widen pitch of control bus in bank. --- compiler/modules/bank.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 390d17a3..158ac37b 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -316,7 +316,7 @@ class bank(design.design): self.input_control_signals = [] port_num = 0 for port in range(OPTS.num_rw_ports): - self.input_control_signals.append(["w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) + self.input_control_signals.append(["s_en{}".format(port_num), "w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) port_num += 1 for port in range(OPTS.num_w_ports): self.input_control_signals.append(["w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) @@ -329,7 +329,7 @@ class bank(design.design): self.num_control_lines = [len(x) for x in self.input_control_signals] # The width of this bus is needed to place other modules (e.g. decoder) for each port - self.central_bus_width = [self.m2_pitch * x + self.m2_width for x in self.num_control_lines] + self.central_bus_width = [self.m3_pitch * x + self.m3_width for x in self.num_control_lines] # These will be outputs of the gaters if this is multibank, if not, normal signals. self.control_signals = [] @@ -338,6 +338,7 @@ class bank(design.design): self.control_signals.append(["gated_" + str for str in self.input_control_signals[port]]) else: self.control_signals.append(self.input_control_signals[port]) + # The central bus is the column address (one hot) and row address (binary) if self.col_addr_size>0: @@ -672,7 +673,8 @@ class bank(design.design): names=self.control_signals[0], length=control_bus_length, vertical=True, - make_pins=(self.num_banks==1)) + make_pins=(self.num_banks==1), + pitch=self.m3_pitch) # Port 1 if len(self.all_ports)==2: @@ -686,7 +688,8 @@ class bank(design.design): names=list(reversed(self.control_signals[1])), length=control_bus_length, vertical=True, - make_pins=(self.num_banks==1)) + make_pins=(self.num_banks==1), + pitch=self.m3_pitch) def route_port_data_to_bitcell_array(self, port): """ Routing of BL and BR between port data and bitcell array """ From 5626fd182e2c993ed107154c809802db28c3f68c Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 30 Jun 2020 10:58:24 -0700 Subject: [PATCH 55/67] Extra track in data bus. Remove old code. --- compiler/sram/sram_1bank.py | 146 +----------------------------------- 1 file changed, 3 insertions(+), 143 deletions(-) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index ac86eb94..cdc5ab92 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -73,10 +73,11 @@ class sram_1bank(sram_base): self.data_bus_gap = self.m4_nonpref_pitch * 2 # Spare wen are on a separate layer so not included - self.data_bus_size = [None] * len(self.all_ports) + # Start with 1 track minimum + self.data_bus_size = [1] * len(self.all_ports) for port in self.all_ports: # All ports need the col addr flops - self.data_bus_size[port] = self.col_addr_size + self.data_bus_size[port] += self.col_addr_size # Write ports need the data input flops and write mask flops if port in self.write_ports: self.data_bus_size[port] += self.num_wmasks + self.word_size @@ -324,16 +325,6 @@ class sram_1bank(sram_base): self.route_row_addr_dff() - # if self.col_addr_dff: - # self.route_col_addr_dff() - - # self.route_data_dff() - - # if self.write_size: - # self.route_wmask_dff() - - # if self.num_spare_cols: - # self.route_spare_wen_dff() for port in self.all_ports: self.route_dff(port) @@ -488,137 +479,6 @@ class sram_1bank(sram_base): offset=mid_pos) self.add_path(bank_pin.layer, [mid_pos, bank_pos]) - def route_col_addr_dff(self): - """ Connect the output of the col flops to the bank pins """ - for port in self.all_ports: - if port % 2: - offset = self.col_addr_dff_insts[port].ll() - vector(0, self.col_addr_bus_size) - else: - offset = self.col_addr_dff_insts[port].ul() + vector(0, self.col_addr_bus_gap) - - bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)] - col_addr_bus_offsets = self.create_horizontal_bus(layer="m1", - offset=offset, - names=bus_names, - length=self.col_addr_dff_insts[port].width) - - dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] - data_dff_map = zip(dff_names, bus_names) - self.connect_horizontal_bus(data_dff_map, - self.col_addr_dff_insts[port], - col_addr_bus_offsets) - - bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] - data_bank_map = zip(bank_names, bus_names) - self.connect_horizontal_bus(data_bank_map, - self.bank_inst, - col_addr_bus_offsets) - - def route_data_dff(self): - """ Connect the output of the data flops to the write driver """ - # This is where the channel will start (y-dimension at least) - for port in self.write_ports: - if port % 2: - offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size[port]) - else: - offset = self.data_dff_insts[port].ul() + vector(0, self.data_bus_gap) - - dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] - dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] - if self.write_size or self.num_spare_cols: - for x in dff_names: - pin = self.data_dff_insts[port].get_pin(x) - pin_offset = pin.center() - self.add_via_center(layers=self.m1_stack, - offset=pin_offset, - directions=("V", "V")) - self.add_via_stack_center(from_layer="m2", - to_layer="m4", - offset=pin_offset) - - bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - if self.write_size or self.num_spare_cols: - for x in bank_names: - pin = self.bank_inst.get_pin(x) - if port % 2: - pin_offset = pin.uc() - else: - pin_offset = pin.bc() - self.add_via_stack_center(from_layer=pin.layer, - to_layer="m4", - offset=pin_offset) - - route_map = list(zip(bank_pins, dff_pins)) - if self.write_size or self.num_spare_cols: - layer_stack = self.m3_stack - else: - layer_stack = self.m1_stack - - self.create_horizontal_channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack) - - def route_wmask_dff(self): - """ Connect the output of the wmask flops to the write mask AND array """ - # This is where the channel will start (y-dimension at least) - for port in self.write_ports: - if port % 2: - offset = self.wmask_dff_insts[port].ll() - vector(0, self.wmask_bus_size) - else: - offset = self.wmask_dff_insts[port].ul() + vector(0, self.wmask_bus_gap) - - dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] - dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] - for x in dff_names: - offset_pin = self.wmask_dff_insts[port].get_pin(x).center() - self.add_via_center(layers=self.m1_stack, - offset=offset_pin, - directions=("V", "V")) - - bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - for x in bank_names: - offset_pin = self.bank_inst.get_pin(x).center() - self.add_via_center(layers=self.m1_stack, - offset=offset_pin) - - route_map = list(zip(bank_pins, dff_pins)) - self.create_horizontal_channel_route(netlist=route_map, - offset=offset, - layer_stack=self.m1_stack) - - def route_spare_wen_dff(self): - """ Connect the output of the spare write enable flops to the spare write drivers """ - # This is where the channel will start (y-dimension at least) - for port in self.write_ports: - if port % 2: - # for port 0 - offset = self.spare_wen_dff_insts[port].ll() - vector(0, self.spare_wen_bus_size) - else: - offset = self.spare_wen_dff_insts[port].ul() + vector(0, self.spare_wen_bus_gap) - - dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] - dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] - for x in dff_names: - offset_pin = self.spare_wen_dff_insts[port].get_pin(x).center() - self.add_via_center(layers=self.m1_stack, - offset=offset_pin, - directions=("V", "V")) - - bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - for x in bank_names: - offset_pin = self.bank_inst.get_pin(x).center() - self.add_via_center(layers=self.m1_stack, - offset=offset_pin) - - route_map = list(zip(bank_pins, dff_pins)) - self.create_horizontal_channel_route(netlist=route_map, - offset=offset, - layer_stack=self.m1_stack) - - def add_lvs_correspondence_points(self): """ This adds some points for easier debugging if LVS goes wrong. From a48ea522539c8d8a7b1834bd8bfceaabe0a86450 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 30 Jun 2020 13:26:38 -0700 Subject: [PATCH 56/67] Add missing contact to vdd pins. --- compiler/modules/dff_array.py | 87 +++++++++++++++---------------- compiler/modules/dff_buf_array.py | 4 +- 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/compiler/modules/dff_array.py b/compiler/modules/dff_array.py index 62464834..d3f9b68e 100644 --- a/compiler/modules/dff_array.py +++ b/compiler/modules/dff_array.py @@ -7,12 +7,11 @@ # import debug import design -from tech import drc -from math import log from vector import vector from sram_factory import factory from globals import OPTS + class dff_array(design.design): """ This is a simple row (or multiple rows) of flops. @@ -52,42 +51,41 @@ class dff_array(design.design): self.add_mod(self.dff) def add_pins(self): - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_din_name(row,col), "INPUT") - for row in range(self.rows): + self.add_pin(self.get_din_name(row, col), "INPUT") + for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_dout_name(row,col), "OUTPUT") + self.add_pin(self.get_dout_name(row, col), "OUTPUT") self.add_pin("clk", "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") def create_dff_array(self): self.dff_insts={} - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - name = "dff_r{0}_c{1}".format(row,col) - self.dff_insts[row,col]=self.add_inst(name=name, - mod=self.dff) - instance_ports = [self.get_din_name(row,col), - self.get_dout_name(row,col)] + name = "dff_r{0}_c{1}".format(row, col) + self.dff_insts[row, col]=self.add_inst(name=name, + mod=self.dff) + instance_ports = [self.get_din_name(row, col), + self.get_dout_name(row, col)] for port in self.dff.pin_names: if port != 'D' and port != 'Q': instance_ports.append(port) self.connect_inst(instance_ports) def place_dff_array(self): - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - name = "dff_r{0}_c{1}".format(row,col) if (row % 2 == 0): - base = vector(col*self.dff.width,row*self.dff.height) + base = vector(col * self.dff.width, row * self.dff.height) mirror = "R0" else: - base = vector(col*self.dff.width,(row+1)*self.dff.height) + base = vector(col * self.dff.width, (row + 1) * self.dff.height) mirror = "MX" - self.dff_insts[row,col].place(offset=base, - mirror=mirror) + self.dff_insts[row, col].place(offset=base, + mirror=mirror) def get_din_name(self, row, col): if self.columns == 1: @@ -95,7 +93,7 @@ class dff_array(design.design): elif self.rows == 1: din_name = "din_{0}".format(col) else: - din_name = "din_{0}_{1}".format(row,col) + din_name = "din_{0}_{1}".format(row, col) return din_name @@ -105,61 +103,58 @@ class dff_array(design.design): elif self.rows == 1: dout_name = "dout_{0}".format(col) else: - dout_name = "dout_{0}_{1}".format(row,col) + dout_name = "dout_{0}_{1}".format(row, col) return dout_name - def add_layout_pins(self): for row in range(self.rows): - for col in range(self.columns): + for col in range(self.columns): # Continous vdd rail along with label. - vdd_pin=self.dff_insts[row,col].get_pin("vdd") - self.add_power_pin("vdd", vdd_pin.center()) + vdd_pin=self.dff_insts[row, col].get_pin("vdd") + self.add_power_pin("vdd", vdd_pin.center(), start_layer=vdd_pin.layer) # Continous gnd rail along with label. - gnd_pin=self.dff_insts[row,col].get_pin("gnd") - self.add_power_pin("gnd", gnd_pin.center()) + gnd_pin=self.dff_insts[row, col].get_pin("gnd") + self.add_power_pin("gnd", gnd_pin.center(), start_layer=gnd_pin.layer) - - for row in range(self.rows): - for col in range(self.columns): - din_pin = self.dff_insts[row,col].get_pin("D") - debug.check(din_pin.layer=="m2","DFF D pin not on metal2") - self.add_layout_pin(text=self.get_din_name(row,col), + for row in range(self.rows): + for col in range(self.columns): + din_pin = self.dff_insts[row, col].get_pin("D") + debug.check(din_pin.layer == "m2", "DFF D pin not on metal2") + self.add_layout_pin(text=self.get_din_name(row, col), layer=din_pin.layer, offset=din_pin.ll(), width=din_pin.width(), height=din_pin.height()) - dout_pin = self.dff_insts[row,col].get_pin("Q") - debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2") - self.add_layout_pin(text=self.get_dout_name(row,col), + dout_pin = self.dff_insts[row, col].get_pin("Q") + debug.check(dout_pin.layer == "m2", "DFF Q pin not on metal2") + self.add_layout_pin(text=self.get_dout_name(row, col), layer=dout_pin.layer, offset=dout_pin.ll(), width=dout_pin.width(), height=dout_pin.height()) - - # Create vertical spines to a single horizontal rail - clk_pin = self.dff_insts[0,0].get_pin(self.dff.clk_pin) - clk_ypos = 2*self.m3_pitch+self.m3_width - debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2") + clk_pin = self.dff_insts[0, 0].get_pin(self.dff.clk_pin) + clk_ypos = 2 * self.m3_pitch + self.m3_width + debug.check(clk_pin.layer == "m2", "DFF clk pin not on metal2") self.add_layout_pin_segment_center(text="clk", layer="m3", - start=vector(0,clk_ypos), - end=vector(self.width,clk_ypos)) + start=vector(0, clk_ypos), + end=vector(self.width, clk_ypos)) for col in range(self.columns): - clk_pin = self.dff_insts[0,col].get_pin(self.dff.clk_pin) + clk_pin = self.dff_insts[0, col].get_pin(self.dff.clk_pin) # Make a vertical strip for each column self.add_rect(layer="m2", - offset=clk_pin.ll().scale(1,0), + offset=clk_pin.ll().scale(1, 0), width=self.m2_width, height=self.height) # Drop a via to the M3 pin - self.add_via_center(layers=self.m2_stack, - offset=vector(clk_pin.cx(),clk_ypos)) + self.add_via_stack_center(from_layer=clk_pin.layer, + to_layer="m3", + offset=vector(clk_pin.cx(), clk_ypos)) def get_clk_cin(self): """Return the total capacitance (in relative units) that the clock is loaded by in the dff array""" diff --git a/compiler/modules/dff_buf_array.py b/compiler/modules/dff_buf_array.py index b608cdeb..1cbd9284 100644 --- a/compiler/modules/dff_buf_array.py +++ b/compiler/modules/dff_buf_array.py @@ -167,11 +167,11 @@ class dff_buf_array(design.design): for col in range(self.columns): # Continous vdd rail along with label. vdd_pin=self.dff_insts[row, col].get_pin("vdd") - self.add_power_pin("vdd", vdd_pin.lc()) + self.add_power_pin("vdd", vdd_pin.lc(), start_layer=vdd_pin.layer) # Continous gnd rail along with label. gnd_pin=self.dff_insts[row, col].get_pin("gnd") - self.add_power_pin("gnd", gnd_pin.lc()) + self.add_power_pin("gnd", gnd_pin.lc(), start_layer=gnd_pin.layer) def add_layout_pins(self): From 011ac2fc05c89e678103b1e08bf74fa6a3934da9 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 30 Jun 2020 13:57:45 -0700 Subject: [PATCH 57/67] Don't route to clk to perimeter on m2 --- compiler/modules/control_logic.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index d857f24d..74fad6a5 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -523,12 +523,12 @@ class control_logic(design.design): def route_clk_buf(self): clk_pin = self.clk_buf_inst.get_pin("A") clk_pos = clk_pin.center() - self.add_layout_pin_segment_center(text="clk", - layer="m2", - start=clk_pos, - end=clk_pos.scale(1, 0)) - self.add_via_center(layers=self.m1_stack, - offset=clk_pos) + self.add_layout_pin_rect_center(text="clk", + layer="m2", + offset=clk_pos) + self.add_via_stack_center(from_layer=clk_pin.layer, + to_layer="m2", + offset=clk_pos) self.route_output_to_bus_jogged(self.clk_buf_inst, "clk_buf") From c1fedda575517f5fbc35ec36c0665811e2680a8f Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 30 Jun 2020 15:07:34 -0700 Subject: [PATCH 58/67] Modifications for min area metal. Made add_via_stack_center iterative instead of recursive. Removed add_via_stack (non-center) since it isn't used. Add min area metal during iterative via insertion. --- compiler/base/hierarchy_layout.py | 126 ++++++++++++++---------------- compiler/pgates/pwrite_driver.py | 24 +++--- 2 files changed, 69 insertions(+), 81 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index c57de915..a5272ba5 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -13,6 +13,7 @@ from tech import drc, GDS from tech import layer as techlayer from tech import layer_indices from tech import layer_stacks +from tech import preferred_directions import os from globals import OPTS from vector import vector @@ -521,7 +522,6 @@ class layout(): def get_preferred_direction(self, layer): """ Return the preferred routing directions """ - from tech import preferred_directions return preferred_directions[layer] def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None): @@ -567,24 +567,6 @@ class layout(): self.connect_inst([]) return inst - def add_via_stack(self, offset, from_layer, to_layer, - directions=None, - size=[1, 1], - implant_type=None, - well_type=None): - """ - Punch a stack of vias from a start layer to a target layer. - """ - return self.__add_via_stack_internal(offset=offset, - directions=directions, - from_layer=from_layer, - to_layer=to_layer, - via_func=self.add_via, - last_via=None, - size=size, - implant_type=implant_type, - well_type=well_type) - def add_via_stack_center(self, offset, from_layer, @@ -594,24 +576,7 @@ class layout(): implant_type=None, well_type=None): """ - Punch a stack of vias from a start layer to a target layer by the center - coordinate accounting for mirroring and rotation. - """ - return self.__add_via_stack_internal(offset=offset, - directions=directions, - from_layer=from_layer, - to_layer=to_layer, - via_func=self.add_via_center, - last_via=None, - size=size, - implant_type=implant_type, - well_type=well_type) - - def __add_via_stack_internal(self, offset, directions, from_layer, to_layer, - via_func, last_via, size, implant_type=None, well_type=None): - """ - Punch a stack of vias from a start layer to a target layer. Here we - figure out whether to punch it up or down the stack. + Punch a stack of vias from a start layer to a target layer by the center. """ if from_layer == to_layer: @@ -619,38 +584,65 @@ class layout(): # a metal enclosure. This helps with center-line path routing. self.add_rect_center(layer=from_layer, offset=offset) - return last_via + return None - from_id = layer_indices[from_layer] - to_id = layer_indices[to_layer] + via = None + cur_layer = from_layer + while cur_layer != to_layer: + from_id = layer_indices[cur_layer] + to_id = layer_indices[to_layer] - if from_id < to_id: # grow the stack up - search_id = 0 - next_id = 2 - else: # grow the stack down - search_id = 2 - next_id = 0 + if from_id < to_id: # grow the stack up + search_id = 0 + next_id = 2 + else: # grow the stack down + search_id = 2 + next_id = 0 - curr_stack = next(filter(lambda stack: stack[search_id] == from_layer, layer_stacks), None) - if curr_stack is None: - raise ValueError("Cannot create via from '{0}' to '{1}'." - "Layer '{0}' not defined".format(from_layer, to_layer)) + curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None) + + via = self.add_via_center(layers=curr_stack, + size=size, + offset=offset, + directions=directions, + implant_type=implant_type, + well_type=well_type) + + if cur_layer != from_layer: + self.add_min_area_rect_center(cur_layer, + offset, + via.mod.first_layer_width, + via.mod.first_layer_height) + + cur_layer = curr_stack[next_id] - via = via_func(layers=curr_stack, - size=size, - offset=offset, - directions=directions, - implant_type=implant_type, - well_type=well_type) - - via = self.__add_via_stack_internal(offset=offset, - directions=directions, - from_layer=curr_stack[next_id], - to_layer=to_layer, - via_func=via_func, - last_via=via, - size=size) return via + + def add_min_area_rect_center(self, + layer, + offset, + width=None, + height=None): + """ + Add a minimum area retcangle at the given point. + Either width or height should be fixed. + """ + + min_area = drc("minarea_{}".format(layer)) + if min_area == 0: + return + + min_width = drc("minwidth_{}".format(layer)) + + if preferred_directions[layer] == "V": + height = max(min_area / width, min_width) + else: + width = max(min_area / height, min_width) + + self.add_rect_center(layer=layer, + offset=offset, + width=width, + height=height) def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"): """Adds a ptx module to the design.""" @@ -1181,12 +1173,8 @@ class layout(): self.add_path(layer, [pin_loc, peri_pin_loc]) - self.add_via_stack_center(from_layer=layer, - to_layer="m4", - offset=peri_pin_loc) - self.add_layout_pin_rect_center(text=name, - layer="m4", + layer=layer, offset=peri_pin_loc) def add_power_ring(self, bbox): diff --git a/compiler/pgates/pwrite_driver.py b/compiler/pgates/pwrite_driver.py index da5eacdc..87db7b20 100644 --- a/compiler/pgates/pwrite_driver.py +++ b/compiler/pgates/pwrite_driver.py @@ -160,11 +160,11 @@ class pwrite_driver(design.design): track_xoff = self.get_m2_track(1) din_loc = self.din_inst.get_pin("A").center() - self.add_via_stack("m1", "m2", din_loc) + self.add_via_stack_center("m1", "m2", din_loc) din_track = vector(track_xoff,din_loc.y) br_in = self.br_inst.get_pin("in").center() - self.add_via_stack("m1", "m2", br_in) + self.add_via_stack_center("m1", "m2", br_in) br_track = vector(track_xoff,br_in.y) din_in = vector(track_xoff,0) @@ -181,11 +181,11 @@ class pwrite_driver(design.design): track_xoff = self.get_m4_track(self.din_bar_track) din_bar_in = self.din_inst.get_pin("Z").center() - self.add_via_stack("m1", "m3", din_bar_in) + self.add_via_stack_center("m1", "m3", din_bar_in) din_bar_track = vector(track_xoff,din_bar_in.y) bl_in = self.bl_inst.get_pin("in").center() - self.add_via_stack("m1", "m3", bl_in) + self.add_via_stack_center("m1", "m3", bl_in) bl_track = vector(track_xoff,bl_in.y) din_in = vector(track_xoff,0) @@ -204,15 +204,15 @@ class pwrite_driver(design.design): # This M2 pitch is a hack since the A and Z pins align horizontally en_bar_loc = self.en_inst.get_pin("Z").uc() en_bar_track = vector(track_xoff, en_bar_loc.y) - self.add_via_stack("m1", "m3", en_bar_loc) + self.add_via_stack_center("m1", "m3", en_bar_loc) # This is a U route to the right down then left bl_en_loc = self.bl_inst.get_pin("en_bar").center() bl_en_track = vector(track_xoff, bl_en_loc.y) - self.add_via_stack("m1", "m3", bl_en_loc) + self.add_via_stack_center("m1", "m3", bl_en_loc) br_en_loc = self.br_inst.get_pin("en_bar").center() br_en_track = vector(track_xoff, bl_en_loc.y) - self.add_via_stack("m1", "m3", br_en_loc) + self.add_via_stack_center("m1", "m3", br_en_loc) # L shape @@ -237,21 +237,21 @@ class pwrite_driver(design.design): en_loc = self.en_inst.get_pin("A").center() en_rail = vector(en_loc.x, vdd_yloc) - self.add_via_stack("m1", "m2", en_loc) + self.add_via_stack_center("m1", "m2", en_loc) self.add_path("m2", [en_loc, en_rail]) - self.add_via_stack("m2", "m3", en_rail) + self.add_via_stack_center("m2", "m3", en_rail) # Start point in the track on the pin rail en_track = vector(track_xoff, vdd_yloc) - self.add_via_stack("m3", "m4", en_track) + self.add_via_stack_center("m3", "m4", en_track) # This is a U route to the right down then left bl_en_loc = self.bl_inst.get_pin("en").center() bl_en_track = vector(track_xoff, bl_en_loc.y) - self.add_via_stack("m1", "m3", bl_en_loc) + self.add_via_stack_center("m1", "m3", bl_en_loc) br_en_loc = self.br_inst.get_pin("en").center() br_en_track = vector(track_xoff, bl_en_loc.y) - self.add_via_stack("m1", "m3", br_en_loc) + self.add_via_stack_center("m1", "m3", br_en_loc) # U shape self.add_wire(self.m3_stack, From 0a87691176ddd61679535dac5d7a7c324192f86c Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 30 Jun 2020 15:27:10 -0700 Subject: [PATCH 59/67] Run Calibre LVS even if DRC fails. --- compiler/tests/testutils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index eb75fc44..4a58cfe2 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -48,12 +48,11 @@ class openram_test(unittest.TestCase): # if we ignore things like minimum metal area of pins drc_result=verify.run_drc(a.name, tempgds, extract=True, final_verification=final_verification) - # Always run LVS if we are using magic - if "magic" in OPTS.drc_exe or drc_result == 0: - lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) + # We can still run LVS even if DRC fails in Magic OR Calibre + lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) # Only allow DRC to fail and LVS to pass if we are using magic - if "magic" in OPTS.drc_exe and lvs_result == 0 and drc_result != 0: + if lvs_result == 0 and drc_result != 0: # import shutil # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) # debug.info(0, "Archiving failed files to {}.zip".format(zip_file)) From 3379f46da1d336a379697aa9cd8c0571eda3c3a1 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 30 Jun 2020 16:22:44 -0700 Subject: [PATCH 60/67] Fail unit test, but mention if LVS passes and DRC fails. --- compiler/tests/testutils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index 4a58cfe2..a8da9fb4 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -57,8 +57,7 @@ class openram_test(unittest.TestCase): # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) # debug.info(0, "Archiving failed files to {}.zip".format(zip_file)) # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) - debug.warning("DRC failed but LVS passed: {}".format(a.name)) - # self.fail("DRC failed but LVS passed: {}".format(a.name)) + self.fail("DRC failed but LVS passed: {}".format(a.name)) elif drc_result != 0: # import shutil # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) From b07f30cb9edaed782cd746e906a81ee0d08330db Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 30 Jun 2020 16:23:07 -0700 Subject: [PATCH 61/67] Missing output via in control logic --- compiler/modules/control_logic.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 74fad6a5..78215d49 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -797,10 +797,14 @@ class control_logic(design.design): out_pin = inst.get_pin(pin_name) right_pos = out_pin.center() + vector(self.width - out_pin.cx(), 0) - self.add_layout_pin_segment_center(text=out_name, - layer="m1", - start=out_pin.center(), - end=right_pos) + + self.add_path(out_pin.layer, [out_pin.center(), right_pos]) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m1", + offset=right_pos) + self.add_layout_pin_rect_center(text=out_name, + layer="m1", + offset=right_pos) def route_supply(self): """ Add vdd and gnd to the instance cells """ From 3d0f29ff3a079c33b556a5219e2c9fdb1b815bcd Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 1 Jul 2020 09:22:59 -0700 Subject: [PATCH 62/67] Fix missing via LVS issues. LVS passing for some 20 tests. --- compiler/base/hierarchy_layout.py | 13 ++++++----- compiler/modules/control_logic.py | 37 ++++++++++++++++++++----------- compiler/modules/delay_chain.py | 29 ++++++++++++------------ compiler/sram/sram_1bank.py | 7 +++++- 4 files changed, 53 insertions(+), 33 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index a5272ba5..275b69e9 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -917,8 +917,10 @@ class layout(): (horizontal_layer, via_layer, vertical_layer) = layer_stack if horizontal: route_layer = vertical_layer + bys_layer = horizontal_layer else: route_layer = horizontal_layer + bus_layer = vertical_layer for (pin_name, bus_name) in mapping: pin = inst.get_pin(pin_name) @@ -940,17 +942,18 @@ class layout(): # Connect to the pin on the instances with a via if it is # not on the right layer if pin.layer != route_layer: - self.add_via_center(layers=layer_stack, - offset=pin_pos) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=route_layer, + offset=pin_pos) # FIXME: output pins tend to not be rotate, # but supply pins are. Make consistent? # We only need a via if they happened to align perfectly # so the add_wire didn't add a via if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x): - self.add_via_center(layers=layer_stack, - offset=bus_pos, - rotate=90) + self.add_via_stack_center(from_layer=route_layer, + to_layer=bus_layer, + offset=bus_pos) def connect_vbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"): """ diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 78215d49..020b90ec 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -555,9 +555,15 @@ class control_logic(design.design): clkbuf_map = zip(["A"], ["clk_buf"]) self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus) - out_pos = self.clk_bar_inst.get_pin("Z").center() - in_pos = self.gated_clk_bar_inst.get_pin("A").center() - self.add_zjog("m1", out_pos, in_pos) + out_pin = self.clk_bar_inst.get_pin("Z") + out_pos = out_pin.center() + in_pin = self.gated_clk_bar_inst.get_pin("A") + in_pos = in_pin.center() + self.add_zjog(out_pin.layer, out_pos, in_pos) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer=in_pin.layer, + offset=in_pos) + # This is the second gate over, so it needs to be on M3 clkbuf_map = zip(["B"], ["cs"]) @@ -809,23 +815,27 @@ class control_logic(design.design): def route_supply(self): """ Add vdd and gnd to the instance cells """ + if OPTS.tech_name == "sky130": + supply_layer = "li" + else: + supply_layer = "m1" max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) for inst in self.row_end_inst: pins = inst.get_pins("vdd") for pin in pins: - if pin.layer == "m1": + if pin.layer == supply_layer: row_loc = pin.rc() pin_loc = vector(max_row_x_loc, pin.rc().y) - self.add_power_pin("vdd", pin_loc) - self.add_path("m1", [row_loc, pin_loc]) + self.add_power_pin("vdd", pin_loc, start_layer=pin.layer) + self.add_path(supply_layer, [row_loc, pin_loc]) pins = inst.get_pins("gnd") for pin in pins: - if pin.layer == "m1": + if pin.layer == supply_layer: row_loc = pin.rc() pin_loc = vector(max_row_x_loc, pin.rc().y) - self.add_power_pin("gnd", pin_loc) - self.add_path("m1", [row_loc, pin_loc]) + self.add_power_pin("gnd", pin_loc, start_layer=pin.layer) + self.add_path(supply_layer, [row_loc, pin_loc]) self.copy_layout_pin(self.delay_inst, "gnd") self.copy_layout_pin(self.delay_inst, "vdd") @@ -1008,12 +1018,13 @@ class control_logic(design.design): def route_output_to_bus_jogged(self, inst, name): # Connect this at the bottom of the buffer - out_pos = inst.get_pin("Z").center() + out_pin = inst.get_pin("Z") + out_pos = out_pin.center() mid1 = vector(out_pos.x, out_pos.y - 0.4 * inst.mod.height) mid2 = vector(self.input_bus[name].cx(), mid1.y) bus_pos = self.input_bus[name].center() self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) - # The pin is on M1, so we need another via as well - self.add_via_center(layers=self.m1_stack, - offset=out_pos) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m2", + offset=out_pos) diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index c07395f5..c261138a 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -140,21 +140,20 @@ class delay_chain(design.design): for load in self.load_inst_map[inv]: # Drop a via on each A pin a_pin = load.get_pin("A") - self.add_via_center(layers=self.m1_stack, - offset=a_pin.center()) - self.add_via_center(layers=self.m2_stack, - offset=a_pin.center()) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m3", + offset=a_pin.center()) # Route an M3 horizontal wire to the furthest z_pin = inv.get_pin("Z") a_pin = inv.get_pin("A") a_max = self.load_inst_map[inv][-1].get_pin("A") - self.add_via_center(layers=self.m1_stack, - offset=a_pin.center()) - self.add_via_center(layers=self.m1_stack, - offset=z_pin.center()) - self.add_via_center(layers=self.m2_stack, - offset=z_pin.center()) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=a_pin.center()) + self.add_via_stack_center(from_layer=z_pin.layer, + to_layer="m3", + offset=z_pin.center()) self.add_path("m3", [z_pin.center(), a_max.center()]) # Route Z to the A of the next stage @@ -191,8 +190,9 @@ class delay_chain(design.design): # input is A pin of first inverter a_pin = self.driver_inst_list[0].get_pin("A") - self.add_via_center(layers=self.m1_stack, - offset=a_pin.center()) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=a_pin.center()) self.add_layout_pin(text="in", layer="m2", offset=a_pin.ll().scale(1, 0), @@ -201,8 +201,9 @@ class delay_chain(design.design): # output is A pin of last load inverter last_driver_inst = self.driver_inst_list[-1] a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A") - self.add_via_center(layers=self.m1_stack, - offset=a_pin.center()) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=a_pin.center()) mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy()) self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)]) self.add_layout_pin_segment_center(text="out", diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index cdc5ab92..52cbb2a1 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -457,7 +457,12 @@ class sram_1bank(sram_base): dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port)) self.add_wire(self.m2_stack[::-1], [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) - # self.connect_hbus(src_pin, dest_pin) + self.add_via_stack_center(from_layer=src_pin.layer, + to_layer="m2", + offset=src_pin.center()) + self.add_via_stack_center(from_layer=dest_pin.layer, + to_layer="m2", + offset=dest_pin.center()) def route_row_addr_dff(self): """ Connect the output of the row flops to the bank pins """ From bb18d05f7535c7239ce860e883d9ee9f47d58eee Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 1 Jul 2020 11:33:25 -0700 Subject: [PATCH 63/67] Move control output via inside module instead of perimeter --- compiler/modules/control_logic.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 020b90ec..a4e26a6f 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -802,15 +802,16 @@ class control_logic(design.design): """ Create an output pin on the right side from the pin of a given instance. """ out_pin = inst.get_pin(pin_name) - right_pos = out_pin.center() + vector(self.width - out_pin.cx(), 0) + out_pos = out_pin.center() + right_pos = out_pos + vector(self.width - out_pin.cx(), 0) - self.add_path(out_pin.layer, [out_pin.center(), right_pos]) self.add_via_stack_center(from_layer=out_pin.layer, - to_layer="m1", - offset=right_pos) - self.add_layout_pin_rect_center(text=out_name, - layer="m1", - offset=right_pos) + to_layer="m2", + offset=out_pos) + self.add_layout_pin_segment_center(text=out_name, + layer="m2", + start=out_pos, + end=right_pos) def route_supply(self): """ Add vdd and gnd to the instance cells """ From c340870ba0ff6c6e86da99ac1e667c27a69ae481 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 1 Jul 2020 14:44:01 -0700 Subject: [PATCH 64/67] Channel route dout wires as well in read write ports --- compiler/base/channel_route.py | 2 +- compiler/base/hierarchy_layout.py | 6 +-- compiler/sram/sram_1bank.py | 64 ++++++++++++++++++++++--------- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index 5e9b9531..cd4bbdc1 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -161,7 +161,7 @@ class channel_route(design.design): else: # FIXME: We don't support cyclic VCGs right now. debug.error("Cyclic VCG in channel router.", -1) - + # These are the pins we'll have to connect pin_list = nets[net_name] # print("Routing:", net_name, [x.name for x in pin_list]) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 275b69e9..8995488a 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1176,9 +1176,9 @@ class layout(): self.add_path(layer, [pin_loc, peri_pin_loc]) - self.add_layout_pin_rect_center(text=name, - layer=layer, - offset=peri_pin_loc) + return self.add_layout_pin_rect_center(text=name, + layer=layer, + offset=peri_pin_loc) def add_power_ring(self, bbox): """ diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 52cbb2a1..d2c58e49 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -81,6 +81,10 @@ class sram_1bank(sram_base): # Write ports need the data input flops and write mask flops if port in self.write_ports: self.data_bus_size[port] += self.num_wmasks + self.word_size + # This is for the din pins that get routed in the same channel + # when we have dout and din together + if port in self.readwrite_ports: + self.data_bus_size[port] += self.word_size # Convert to length self.data_bus_size[port] *= self.m4_nonpref_pitch # Add the gap in unit length @@ -237,18 +241,45 @@ class sram_1bank(sram_base): "clk", "clk{}".format(port)) - # Data output pins go to BOTTOM/TOP - if port in self.read_ports: + # Data input pins go to BOTTOM/TOP + din_ports = [] + if port in self.write_ports: for bit in range(self.word_size + self.num_spare_cols): if OPTS.perimeter_pins: + p = self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit), + pin=self.data_dff_insts[port].get_pin("din_{0}".format(bit)), + side=bottom_or_top, + bbox=bbox) + din_ports.append(p) + else: + self.copy_layout_pin(self.bank_inst, + "din{0}_{1}".format(port, bit), + "din{0}[{1}]".format(port, bit)) + + # Data output pins go to BOTTOM/TOP + if port in self.readwrite_ports and OPTS.perimeter_pins: + for bit in range(self.word_size + self.num_spare_cols): + # This should be routed next to the din pin + p = din_ports[bit] + self.add_layout_pin_rect_center(text="dout{0}[{1}]".format(port, bit), + layer=p.layer, + offset=p.center() + vector(self.m3_pitch, 0), + width=p.width(), + height=p.height()) + elif port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + if OPTS.perimeter_pins: + # This should have a clear route to the perimeter if there are no din routes self.add_perimeter_pin(name="dout{0}[{1}]".format(port, bit), pin=self.bank_inst.get_pin("dout{0}_{1}".format(port, bit)), side=bottom_or_top, bbox=bbox) else: - self.copy_layout_pin(self.bank_inst, - "dout{0}_{1}".format(port, bit), + self.copy_layout_pin(self.data_dff_insts[port], + "dout_{}".format(bit), "dout{0}[{1}]".format(port, bit)) + + # Lower address bits go to BOTTOM/TOP for bit in range(self.col_addr_size): @@ -274,19 +305,6 @@ class sram_1bank(sram_base): "din_{}".format(bit), "addr{0}[{1}]".format(port, bit + self.col_addr_size)) - # Data input pins go to BOTTOM/TOP - if port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - if OPTS.perimeter_pins: - self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit), - pin=self.data_dff_insts[port].get_pin("din_{}".format(bit)), - side=bottom_or_top, - bbox=bbox) - else: - self.copy_layout_pin(self.data_dff_insts[port], - "din_{}".format(bit), - "din{0}[{1}]".format(port, bit)) - # Write mask pins go to BOTTOM/TOP if port in self.write_ports: if self.write_size: @@ -349,12 +367,22 @@ class sram_1bank(sram_base): route_map.extend(list(zip(bank_pins, dff_pins))) if port in self.write_ports: - # data dff + # synchronized inputs from data dff dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.readwrite_ports and OPTS.perimeter_pins: + # outputs from sense amp + # These are the output pins which had their pin placed on the perimeter, so route from the + # sense amp which should not align with write driver input + sram_names = ["dout{0}[{1}]".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + sram_pins = [self.get_pin(x) for x in sram_names] + bank_names = ["dout{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, sram_pins))) if self.num_wmasks > 0 and port in self.write_ports: layer_stack = self.m3_stack From 8cd1cba8184106944d7cc6d97131afa2dac1ce96 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 1 Jul 2020 14:44:18 -0700 Subject: [PATCH 65/67] Fix missing via in wmask driver --- compiler/modules/port_data.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index e370b891..bd6b39e2 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -377,11 +377,11 @@ class port_data(design.design): temp.append("{0}_{1}".format(br_name, bit)) else: temp.append("{0}_out_{1}".format(bl_name, bit)) - temp.append("{0}_out_{1}".format(br_name, bit)) + temp.append("{0}_out_{1}".format(br_name, bit)) - for bit in range(self.num_spare_cols): + for bit in range(self.num_spare_cols): temp.append("spare{0}_{1}".format(bl_name, bit)) - temp.append("spare{0}_{1}".format(br_name, bit)) + temp.append("spare{0}_{1}".format(br_name, bit)) if self.write_size is not None: for i in range(self.num_wmasks): @@ -522,13 +522,14 @@ class port_data(design.design): wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0) mid_pos = vector(wdriver_pos.x, wmask_pos.y) - # Add driver on mask output - self.add_via_center(layers=self.m1_stack, - offset=wmask_pos) + self.add_via_stack_center(from_layer=wmask_out_pin.layer, + to_layer="m1", + offset=wmask_pos) # Add via for the write driver array's enable input - self.add_via_center(layers=self.m1_stack, - offset=wdriver_pos) + self.add_via_stack_center(from_layer=wdriver_en_pin.layer, + to_layer="m2", + offset=wdriver_pos) # Route between write mask AND array and write driver array self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos]) From bed2e36550d15f34a6ec9a8815a50e79c21d0f14 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 1 Jul 2020 14:44:48 -0700 Subject: [PATCH 66/67] Simplify write mask supply via logic --- compiler/modules/write_mask_and_array.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index c87d3a90..d48aefef 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -134,10 +134,7 @@ class write_mask_and_array(design.design): for supply in ["gnd", "vdd"]: supply_pin=self.and2_insts[i].get_pin(supply) - if "li" in layer: - self.add_power_pin(supply, supply_pin.center(), start_layer="li", directions=("H", "H")) - else: - self.add_power_pin(supply, supply_pin.center()) + self.add_power_pin(supply, supply_pin.center(), start_layer=supply_pin.layer) for supply in ["gnd", "vdd"]: supply_pin_left = self.and2_insts[0].get_pin(supply) From d48f48324855c441f83583fd760c98d9e09f3287 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 1 Jul 2020 15:10:20 -0700 Subject: [PATCH 67/67] Fix swapped instance bug in perimeter pins. --- compiler/sram/sram_1bank.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index d2c58e49..a9731ed5 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -252,8 +252,8 @@ class sram_1bank(sram_base): bbox=bbox) din_ports.append(p) else: - self.copy_layout_pin(self.bank_inst, - "din{0}_{1}".format(port, bit), + self.copy_layout_pin(self.data_dff_insts[port], + "din_{}".format(bit), "din{0}[{1}]".format(port, bit)) # Data output pins go to BOTTOM/TOP @@ -275,8 +275,8 @@ class sram_1bank(sram_base): side=bottom_or_top, bbox=bbox) else: - self.copy_layout_pin(self.data_dff_insts[port], - "dout_{}".format(bit), + self.copy_layout_pin(self.bank_inst, + "dout{0}_{1}".format(port, bit), "dout{0}[{1}]".format(port, bit))