Ruby via PCell sample

This commit is contained in:
Matthias Koefferlein 2025-08-23 20:45:08 +02:00
parent e2e4c1a827
commit cad905eb63
1 changed files with 265 additions and 245 deletions

View File

@ -7,271 +7,291 @@
<show-in-menu>false</show-in-menu> <show-in-menu>false</show-in-menu>
<category>macros</category> <category>macros</category>
<interpreter>ruby</interpreter> <interpreter>ruby</interpreter>
<text>import pya <text># Sample via PCell
import math #
# 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.
""" module MyViaLib
Sample via PCell
This sample PCell implements a library called "MyViaLib" with a single PCell that include RBA
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):
""" # Extend the ViaType class with some custom attributes
Constructor: provides the PCell parameter definitions class ViaTypeEx &lt; RBA::ViaType
"""
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): def initialize(*args)
""" super(*args)
PCell interface implementation end
"""
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): attr_accessor :enc_bottom
""" attr_accessor :enc_top
A helper function to compute the minimum attr_accessor :vwidth
width or height less the enclosure attr_accessor :vspace
(a = w/h_bottom, b = w/h_top,
da/b = enclosure for a/b)
"""
if a &lt; 1e-10 and b &lt; 1e-10:
return 0.0
elif a &lt; 1e-10:
return b - 2.0 * max(da, db)
elif b &lt; 1e-10:
return a - 2.0 * max(da, db)
else:
return min(a - 2.0 * da, b - 2.0 * db)
def getnxy(self, vt): end
"""
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 class ViaPCell &lt; PCellDeclarationHelper
# Constructor: provides the PCell parameter definitions
def initialize
if mode == 1: # Important: initialize the super class
super
# 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.
param(:via, TypeString, "Via type", :hidden =&gt; false)
param(:w_bottom, TypeDouble, "Bottom width", :hidden =&gt; false, :default =&gt; 0.0)
param(:h_bottom, TypeDouble, "Bottom height", :hidden =&gt; false, :default =&gt; 0.0)
param(:w_top, TypeDouble, "Top width", :hidden =&gt; false, :default =&gt; 0.0)
param(:h_top, TypeDouble, "Top height", :hidden =&gt; false, :default =&gt; 0.0)
# Optional additional parameters: here we allow to override
# the computed array dimension by setting these parameters
# to non-zero.
param(:nx, TypeInt, "nx", :default =&gt; 0)
param(:ny, TypeInt, "ny", :default =&gt; 0)
# Build a list of supported vias
@via_type_list = []
# Via1
vt = ViaTypeEx::new("V1", "Via1 (0.2x0.2)")
vt.wbmin = 0.4
vt.hbmin = 0.4
vt.wtmin = 0.5
vt.htmin = 0.5
vt.bottom = RBA::LayerInfo::new(1, 0)
vt.cut = RBA::LayerInfo::new(2, 0)
vt.top = RBA::LayerInfo::new(3, 0)
vt.bottom_grid = 0.01
vt.top_grid = 0.01
# foreign attributes (not used by RBA, but handy for
# internal use):
vt.enc_bottom = 0.1
vt.enc_top = 0.15
vt.vwidth = 0.2
vt.vspace = 0.2
@via_type_list.append(vt)
# Via2
vt = ViaTypeEx::new("V2", "Via2 (0.3x0.3)")
vt.wbmin = 0.5
vt.hbmin = 0.5
vt.wtmin = 0.5
vt.htmin = 0.5
vt.bottom = RBA::LayerInfo::new(3, 0)
vt.cut = RBA::LayerInfo::new(4, 0)
vt.top = RBA::LayerInfo::new(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
@via_type_list.append(vt)
end
# Implements the "via_types" method from the PCellDeclaration
# interface: delivers the vias supported by this PCell.
# This implementation uses the maximum size requested def via_types
# by either top or bottom: return @via_type_list
end
# Returns the via with the name given by the "via"
# PCell argument
def via_type
@via_type_list.each do |vt|
if vt.name == self.via
return vt
end
end
return nil
end
# PCell interface implementation
def display_text_impl
return "Via(" + self.via + ")"
end
# PCell interface implementation
def coerce_parameters_impl
end
# PCell interface implementation
def can_create_from_shape_impl
return false
end
# PCell interface implementation
def parameters_from_shape_impl
end
# PCell interface implementation
def transformation_from_shape_impl
return RBA::Trans::new
end
# 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)
def min_dim(a, b, da, db)
if a &lt; 1e-10 &amp;&amp; b &lt; 1e-10
return 0.0
elsif a &lt; 1e-10
return b - 2.0 * [da, db].max
elsif b &lt; 1e-10
return a - 2.0 * [da, db].max
else
return [a - 2.0 * da, b - 2.0 * db].min
end
end
# 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.
def getnxy(vt)
mode = 2 # 1: maximum, 2: minimum
if mode == 1
# This implementation uses the maximum size requested
# by either top or bottom:
w = [[self.w_bottom, vt.wbmin].max - 2 * vt.enc_bottom,
[self.w_top, vt.wtmin].max - 2 * vt.enc_top].max
h = [[self.h_bottom, vt.hbmin].max - 2 * vt.enc_bottom,
[self.h_top, vt.htmin].max - 2 * vt.enc_top].max
elsif mode == 2
# This implementation delivers the minimum via, ignoring
# zero dimensions:
w = max(max(self.w_bottom, vt.wbmin) - 2 * vt.enc_bottom, w = self.min_dim(self.w_bottom, self.w_top, vt.enc_bottom, vt.enc_top)
max(self.w_top, vt.wtmin) - 2 * vt.enc_top) h = self.min_dim(self.h_bottom, self.h_top, vt.enc_bottom, 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: end
# This implementation delivers the minimum via, ignoring # parameter nx or ny override computed value if &gt; 0
# zero dimensions:
w = self.min_dim(self.w_bottom, self.w_top, vt.enc_bottom, vt.enc_top) if self.nx &gt; 0
h = self.min_dim(self.h_bottom, self.h_top, vt.enc_bottom, vt.enc_top) nx = self.nx
else
# parameter nx or ny override computed value if &gt; 0 nx = [1, ((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10).floor.to_i].max
end
if self.nx &gt; 0:
nx = self.nx
else:
nx = max(1, int(math.floor((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10)))
if self.ny &gt; 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): if self.ny &gt; 0
ny = self.ny
else
ny = [1, ((h + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10).floor.to_i].max
end
""" return [nx, ny]
Implementation of the PCell interface: generates the layouts
"""
vt = self.via_type()
if vt is None:
return
(nx, ny) = self.getnxy(vt) end
wcut = nx * vt.vwidth + (nx - 1) * vt.vspace # Implementation of the PCell interface: generates the layouts
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) def produce_impl
ltop = self.layout.layer(vt.top)
lcut = self.layout.layer(vt.cut)
self.cell.shapes(lbottom).insert(pya.DBox(wbottom, hbottom)) vt = self.via_type
self.cell.shapes(ltop).insert(pya.DBox(wtop, htop)) if !vt
return
end
(nx, ny) = self.getnxy(vt)
wcut = nx * vt.vwidth + (nx - 1) * vt.vspace
hcut = ny * vt.vwidth + (ny - 1) * vt.vspace
wbottom = [[self.w_bottom, vt.wbmin].max, vt.enc_bottom * 2.0 + wcut].max
hbottom = [[self.h_bottom, vt.hbmin].max, vt.enc_bottom * 2.0 + hcut].max
wtop = [[self.w_top, vt.wtmin].max, vt.enc_top * 2.0 + wcut].max
htop = [[self.h_top, vt.htmin].max, vt.enc_top * 2.0 + hcut].max
lbottom = self.layout.layer(vt.bottom)
ltop = self.layout.layer(vt.top)
lcut = self.layout.layer(vt.cut)
self.cell.shapes(lbottom).insert(RBA::DBox::new(wbottom, hbottom))
self.cell.shapes(ltop).insert(RBA::DBox::new(wtop, htop))
scut = self.cell.shapes(lcut)
nx.times do |ix|
x = (ix - 0.5 * (nx - 1)) * (vt.vwidth + vt.vspace)
ny.times do |iy|
y = (iy - 0.5 * (ny - 1)) * (vt.vwidth + vt.vspace)
scut.insert(RBA::DBox::new(vt.vwidth, vt.vwidth).moved(x, y))
end
end
end
scut = self.cell.shapes(lcut) end
# A declaration for a test library
class MyViaLib &lt; RBA::Library
def initialize
self.description = "Via Test Library"
self.layout().register_pcell("Via", ViaPCell::new)
self.register("MyViaLib")
end
for ix in range(0, nx): end
x = (ix - 0.5 * (nx - 1)) * (vt.vwidth + vt.vspace)
for iy in range(0, ny): # instantiates the test library
y = (iy - 0.5 * (ny - 1)) * (vt.vwidth + vt.vspace) MyViaLib::new
scut.insert(pya.DBox(vt.vwidth, vt.vwidth).moved(x, y))
end
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()
</text> </text>
</klayout-macro> </klayout-macro>