From e2e4c1a82716094ab56fba4e7c529c3b0fc7beee Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 23 Aug 2025 20:37:22 +0200 Subject: [PATCH] Via PCell samples --- src/lay/lay/layMacroTemplates.qrc | 1 + src/lay/lay/macro_templates/index.txt | 1 + .../lay/macro_templates/via_pcell_sample.lym | 277 ++++++++++++++++++ .../via_pcell_sample_python.lym | 277 ++++++++++++++++++ 4 files changed, 556 insertions(+) create mode 100644 src/lay/lay/macro_templates/via_pcell_sample.lym create mode 100644 src/lay/lay/macro_templates/via_pcell_sample_python.lym diff --git a/src/lay/lay/layMacroTemplates.qrc b/src/lay/lay/layMacroTemplates.qrc index 3976c564d..03f6d232b 100644 --- a/src/lay/lay/layMacroTemplates.qrc +++ b/src/lay/lay/layMacroTemplates.qrc @@ -19,5 +19,6 @@ macro_templates/pcell_python.lym macro_templates/pcell_sample_python.lym macro_templates/via_pcell_sample_python.lym + macro_templates/via_pcell_sample.lym diff --git a/src/lay/lay/macro_templates/index.txt b/src/lay/lay/macro_templates/index.txt index a6ced11fb..bd5dcebed 100644 --- a/src/lay/lay/macro_templates/index.txt +++ b/src/lay/lay/macro_templates/index.txt @@ -20,6 +20,7 @@ pcell.lym # Samples :Samples;; pcell_sample.lym +via_pcell_sample.lym qt_designer.lym qt_dialog.lym qt_server.lym diff --git a/src/lay/lay/macro_templates/via_pcell_sample.lym b/src/lay/lay/macro_templates/via_pcell_sample.lym new file mode 100644 index 000000000..ada1d58c7 --- /dev/null +++ b/src/lay/lay/macro_templates/via_pcell_sample.lym @@ -0,0 +1,277 @@ + + + Via PCell sample (Ruby)\nThis sample provides a via PCell with two via types + general + true + false + false + macros + ruby + import pya +import math + +""" +Sample via PCell + +This sample PCell implements a library called "MyViaLib" with a single PCell that +provides two vias for a hypothetical technology with these layers: + + 1/0 Metal1 + 2/0 Via1 + 3/0 Metal2 + 4/0 Via2 + 5/0 Metal3 + +The sample demonstrates how to equip a PCell with the necessary declarations, +so it can supply vias for the wire (path) tool. + +It implements simple rectangular via arrays made from squares with a fixed size, +pitch and enclosure. + +NOTE: after changing the code, the macro needs to be rerun to install the new +implementation. The macro is also set to "auto run" to install the PCell +when KLayout is run. +""" + +class ViaPCell(pya.PCellDeclarationHelper): + + def __init__(self): + + """ + Constructor: provides the PCell parameter definitions + """ + + super(ViaPCell, self).__init__() + + # Every via PCell should declare these parameters: + # + # via: the name of the via as declared in the via types + # w_bottom: the width of the bottom box in micrometers + # h_bottom: the height of the bottom box in micrometers + # w_top: the width of the top box in micrometers + # h_top: the width of the top box in micrometers + # + # w_bottom etc. can be zero, indicating that the via + # does not have a specific extension in this layer. + # The PCell may chose a dimension in that case. + + self.param("via", self.TypeString, "Via type", hidden = False) + self.param("w_bottom", self.TypeDouble, "Bottom width", hidden = False, default = 0.0) + self.param("h_bottom", self.TypeDouble, "Bottom height", hidden = False, default = 0.0) + self.param("w_top", self.TypeDouble, "Top width", hidden = False, default = 0.0) + self.param("h_top", self.TypeDouble, "Top height", hidden = False, default = 0.0) + + # Optional additional parameters: here we allow to override + # the computed array dimension by setting these parameters + # to non-zero. + + self.param("nx", self.TypeInt, "nx", default = 0) + self.param("ny", self.TypeInt, "ny", default = 0) + + # Build a list of supported vias + + self.via_type_list = [] + + # Via1 + vt = pya.ViaType("V1", "Via1 (0.2x0.2)") + vt.wbmin = 0.4 + vt.hbmin = 0.4 + vt.wtmin = 0.5 + vt.htmin = 0.5 + vt.bottom = pya.LayerInfo(1, 0) + vt.cut = pya.LayerInfo(2, 0) + vt.top = pya.LayerInfo(3, 0) + vt.bottom_grid = 0.01 + vt.top_grid = 0.01 + + # foreign attributes (not used by pya, but handy for + # internal use): + vt.enc_bottom = 0.1 + vt.enc_top = 0.15 + vt.vwidth = 0.2 + vt.vspace = 0.2 + + self.via_type_list.append(vt) + + # Via2 + vt = pya.ViaType("V2", "Via2 (0.3x0.3)") + vt.wbmin = 0.5 + vt.hbmin = 0.5 + vt.wtmin = 0.5 + vt.htmin = 0.5 + vt.bottom = pya.LayerInfo(3, 0) + vt.cut = pya.LayerInfo(4, 0) + vt.top = pya.LayerInfo(5, 0) + vt.bottom_grid = 0.01 + vt.top_grid = 0.01 + + # foreign attributes (not used by pya, but handy for + # internal use): + vt.enc_bottom = 0.1 + vt.enc_top = 0.1 + vt.vwidth = 0.3 + vt.vspace = 0.2 + + self.via_type_list.append(vt) + + def via_types(self): + """ + Implements the "via_types" method from the PCellDeclaration + interface: delivers the vias supported by this PCell. + """ + return self.via_type_list + + def via_type(self): + """ + Returns the via with the name given by the "via" + PCell argument + """ + for vt in self.via_type_list: + if vt.name == self.via: + return vt + return None + + def display_text_impl(self): + """ + PCell interface implementation + """ + return "Via(" + self.via + ")" + + def coerce_parameters_impl(self): + """ + PCell interface implementation + """ + pass + + def can_create_from_shape_impl(self): + """ + PCell interface implementation + """ + return false + + def parameters_from_shape_impl(self): + """ + PCell interface implementation + """ + pass + + def transformation_from_shape_impl(self): + """ + PCell interface implementation + """ + return pya.Trans() + + def min_dim(self, a, b, da, db): + """ + A helper function to compute the minimum + width or height less the enclosure + (a = w/h_bottom, b = w/h_top, + da/b = enclosure for a/b) + """ + if a < 1e-10 and b < 1e-10: + return 0.0 + elif a < 1e-10: + return b - 2.0 * max(da, db) + elif b < 1e-10: + return a - 2.0 * max(da, db) + else: + return min(a - 2.0 * da, b - 2.0 * db) + + def getnxy(self, vt): + + """ + Computes the nx and ny as mandated by the widths and + heights + + w/h_top/bottom can be zero, indicating no specific + dimension is requested. The implementation can choose + what to do in this case. It can also decide to provide + largher vias than given by w_ or h_ or favor square vias + over rectangular ones. + """ + + mode = 2 # 1: maximum, 2: minimum + + if mode == 1: + + # This implementation uses the maximum size requested + # by either top or bottom: + + w = max(max(self.w_bottom, vt.wbmin) - 2 * vt.enc_bottom, + max(self.w_top, vt.wtmin) - 2 * vt.enc_top) + + h = max(max(self.h_bottom, vt.hbmin) - 2 * vt.enc_bottom, + max(self.h_top, vt.htmin) - 2 * vt.enc_top) + + elif mode == 2: + + # This implementation delivers the minimum via, ignoring + # zero dimensions: + + w = self.min_dim(self.w_bottom, self.w_top, vt.enc_bottom, vt.enc_top) + h = self.min_dim(self.h_bottom, self.h_top, vt.enc_bottom, vt.enc_top) + + # parameter nx or ny override computed value if > 0 + + if self.nx > 0: + nx = self.nx + else: + nx = max(1, int(math.floor((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10))) + + if self.ny > 0: + ny = self.ny + else: + ny = max(1, int(math.floor((h + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10))) + + return (nx, ny) + + def produce_impl(self): + + """ + Implementation of the PCell interface: generates the layouts + """ + + vt = self.via_type() + if vt is None: + return + + (nx, ny) = self.getnxy(vt) + + wcut = nx * vt.vwidth + (nx - 1) * vt.vspace + hcut = ny * vt.vwidth + (ny - 1) * vt.vspace + wbottom = max(max(self.w_bottom, vt.wbmin), vt.enc_bottom * 2.0 + wcut) + hbottom = max(max(self.h_bottom, vt.hbmin), vt.enc_bottom * 2.0 + hcut) + wtop = max(max(self.w_top, vt.wtmin), vt.enc_top * 2.0 + wcut) + htop = max(max(self.h_top, vt.htmin), vt.enc_top * 2.0 + hcut) + + lbottom = self.layout.layer(vt.bottom) + ltop = self.layout.layer(vt.top) + lcut = self.layout.layer(vt.cut) + + self.cell.shapes(lbottom).insert(pya.DBox(wbottom, hbottom)) + self.cell.shapes(ltop).insert(pya.DBox(wtop, htop)) + + scut = self.cell.shapes(lcut) + + for ix in range(0, nx): + x = (ix - 0.5 * (nx - 1)) * (vt.vwidth + vt.vspace) + for iy in range(0, ny): + y = (iy - 0.5 * (ny - 1)) * (vt.vwidth + vt.vspace) + scut.insert(pya.DBox(vt.vwidth, vt.vwidth).moved(x, y)) + + +class MyViaLib(pya.Library): + + """ + A declaration for a test library + """ + + def __init__(self): + self.description = "Via Test Library" + self.layout().register_pcell("Via", ViaPCell()) + self.register("MyViaLib") + +# instantiates the test library +MyViaLib() + + diff --git a/src/lay/lay/macro_templates/via_pcell_sample_python.lym b/src/lay/lay/macro_templates/via_pcell_sample_python.lym new file mode 100644 index 000000000..64fb2adb8 --- /dev/null +++ b/src/lay/lay/macro_templates/via_pcell_sample_python.lym @@ -0,0 +1,277 @@ + + + Via PCell sample (Python)\nThis sample provides a via PCell with two via types + general + true + false + false + pymacros + python + import pya +import math + +""" +Sample via PCell + +This sample PCell implements a library called "MyViaLib" with a single PCell that +provides two vias for a hypothetical technology with these layers: + + 1/0 Metal1 + 2/0 Via1 + 3/0 Metal2 + 4/0 Via2 + 5/0 Metal3 + +The sample demonstrates how to equip a PCell with the necessary declarations, +so it can supply vias for the wire (path) tool. + +It implements simple rectangular via arrays made from squares with a fixed size, +pitch and enclosure. + +NOTE: after changing the code, the macro needs to be rerun to install the new +implementation. The macro is also set to "auto run" to install the PCell +when KLayout is run. +""" + +class ViaPCell(pya.PCellDeclarationHelper): + + def __init__(self): + + """ + Constructor: provides the PCell parameter definitions + """ + + super(ViaPCell, self).__init__() + + # Every via PCell should declare these parameters: + # + # via: the name of the via as declared in the via types + # w_bottom: the width of the bottom box in micrometers + # h_bottom: the height of the bottom box in micrometers + # w_top: the width of the top box in micrometers + # h_top: the width of the top box in micrometers + # + # w_bottom etc. can be zero, indicating that the via + # does not have a specific extension in this layer. + # The PCell may chose a dimension in that case. + + self.param("via", self.TypeString, "Via type", hidden = False) + self.param("w_bottom", self.TypeDouble, "Bottom width", hidden = False, default = 0.0) + self.param("h_bottom", self.TypeDouble, "Bottom height", hidden = False, default = 0.0) + self.param("w_top", self.TypeDouble, "Top width", hidden = False, default = 0.0) + self.param("h_top", self.TypeDouble, "Top height", hidden = False, default = 0.0) + + # Optional additional parameters: here we allow to override + # the computed array dimension by setting these parameters + # to non-zero. + + self.param("nx", self.TypeInt, "nx", default = 0) + self.param("ny", self.TypeInt, "ny", default = 0) + + # Build a list of supported vias + + self.via_type_list = [] + + # Via1 + vt = pya.ViaType("V1", "Via1 (0.2x0.2)") + vt.wbmin = 0.4 + vt.hbmin = 0.4 + vt.wtmin = 0.5 + vt.htmin = 0.5 + vt.bottom = pya.LayerInfo(1, 0) + vt.cut = pya.LayerInfo(2, 0) + vt.top = pya.LayerInfo(3, 0) + vt.bottom_grid = 0.01 + vt.top_grid = 0.01 + + # foreign attributes (not used by pya, but handy for + # internal use): + vt.enc_bottom = 0.1 + vt.enc_top = 0.15 + vt.vwidth = 0.2 + vt.vspace = 0.2 + + self.via_type_list.append(vt) + + # Via2 + vt = pya.ViaType("V2", "Via2 (0.3x0.3)") + vt.wbmin = 0.5 + vt.hbmin = 0.5 + vt.wtmin = 0.5 + vt.htmin = 0.5 + vt.bottom = pya.LayerInfo(3, 0) + vt.cut = pya.LayerInfo(4, 0) + vt.top = pya.LayerInfo(5, 0) + vt.bottom_grid = 0.01 + vt.top_grid = 0.01 + + # foreign attributes (not used by pya, but handy for + # internal use): + vt.enc_bottom = 0.1 + vt.enc_top = 0.1 + vt.vwidth = 0.3 + vt.vspace = 0.2 + + self.via_type_list.append(vt) + + def via_types(self): + """ + Implements the "via_types" method from the PCellDeclaration + interface: delivers the vias supported by this PCell. + """ + return self.via_type_list + + def via_type(self): + """ + Returns the via with the name given by the "via" + PCell argument + """ + for vt in self.via_type_list: + if vt.name == self.via: + return vt + return None + + def display_text_impl(self): + """ + PCell interface implementation + """ + return "Via(" + self.via + ")" + + def coerce_parameters_impl(self): + """ + PCell interface implementation + """ + pass + + def can_create_from_shape_impl(self): + """ + PCell interface implementation + """ + return False + + def parameters_from_shape_impl(self): + """ + PCell interface implementation + """ + pass + + def transformation_from_shape_impl(self): + """ + PCell interface implementation + """ + return pya.Trans() + + def min_dim(self, a, b, da, db): + """ + A helper function to compute the minimum + width or height less the enclosure + (a = w/h_bottom, b = w/h_top, + da/b = enclosure for a/b) + """ + if a < 1e-10 and b < 1e-10: + return 0.0 + elif a < 1e-10: + return b - 2.0 * max(da, db) + elif b < 1e-10: + return a - 2.0 * max(da, db) + else: + return min(a - 2.0 * da, b - 2.0 * db) + + def getnxy(self, vt): + + """ + Computes the nx and ny as mandated by the widths and + heights + + w/h_top/bottom can be zero, indicating no specific + dimension is requested. The implementation can choose + what to do in this case. It can also decide to provide + largher vias than given by w_ or h_ or favor square vias + over rectangular ones. + """ + + mode = 2 # 1: maximum, 2: minimum + + if mode == 1: + + # This implementation uses the maximum size requested + # by either top or bottom: + + w = max(max(self.w_bottom, vt.wbmin) - 2 * vt.enc_bottom, + max(self.w_top, vt.wtmin) - 2 * vt.enc_top) + + h = max(max(self.h_bottom, vt.hbmin) - 2 * vt.enc_bottom, + max(self.h_top, vt.htmin) - 2 * vt.enc_top) + + elif mode == 2: + + # This implementation delivers the minimum via, ignoring + # zero dimensions: + + w = self.min_dim(self.w_bottom, self.w_top, vt.enc_bottom, vt.enc_top) + h = self.min_dim(self.h_bottom, self.h_top, vt.enc_bottom, vt.enc_top) + + # parameter nx or ny override computed value if > 0 + + if self.nx > 0: + nx = self.nx + else: + nx = max(1, int(math.floor((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10))) + + if self.ny > 0: + ny = self.ny + else: + ny = max(1, int(math.floor((h + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10))) + + return (nx, ny) + + def produce_impl(self): + + """ + Implementation of the PCell interface: generates the layouts + """ + + vt = self.via_type() + if vt is None: + return + + (nx, ny) = self.getnxy(vt) + + wcut = nx * vt.vwidth + (nx - 1) * vt.vspace + hcut = ny * vt.vwidth + (ny - 1) * vt.vspace + wbottom = max(max(self.w_bottom, vt.wbmin), vt.enc_bottom * 2.0 + wcut) + hbottom = max(max(self.h_bottom, vt.hbmin), vt.enc_bottom * 2.0 + hcut) + wtop = max(max(self.w_top, vt.wtmin), vt.enc_top * 2.0 + wcut) + htop = max(max(self.h_top, vt.htmin), vt.enc_top * 2.0 + hcut) + + lbottom = self.layout.layer(vt.bottom) + ltop = self.layout.layer(vt.top) + lcut = self.layout.layer(vt.cut) + + self.cell.shapes(lbottom).insert(pya.DBox(wbottom, hbottom)) + self.cell.shapes(ltop).insert(pya.DBox(wtop, htop)) + + scut = self.cell.shapes(lcut) + + for ix in range(0, nx): + x = (ix - 0.5 * (nx - 1)) * (vt.vwidth + vt.vspace) + for iy in range(0, ny): + y = (iy - 0.5 * (ny - 1)) * (vt.vwidth + vt.vspace) + scut.insert(pya.DBox(vt.vwidth, vt.vwidth).moved(x, y)) + + +class MyViaLib(pya.Library): + + """ + A declaration for a test library + """ + + def __init__(self): + self.description = "Via Test Library" + self.layout().register_pcell("Via", ViaPCell()) + self.register("MyViaLib") + +# instantiates the test library +MyViaLib() + +