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 # Extend the ViaType class with some custom attributes
2/0 Via1 class ViaTypeEx &lt; RBA::ViaType
3/0 Metal2
4/0 Via2
5/0 Metal3
The sample demonstrates how to equip a PCell with the necessary declarations, def initialize(*args)
so it can supply vias for the wire (path) tool. super(*args)
end
It implements simple rectangular via arrays made from squares with a fixed size, attr_accessor :enc_bottom
pitch and enclosure. attr_accessor :enc_top
attr_accessor :vwidth
attr_accessor :vspace
NOTE: after changing the code, the macro needs to be rerun to install the new end
implementation. The macro is also set to "auto run" to install the PCell
when KLayout is run.
"""
class ViaPCell(pya.PCellDeclarationHelper): class ViaPCell &lt; PCellDeclarationHelper
def __init__(self): # Constructor: provides the PCell parameter definitions
""" def initialize
Constructor: provides the PCell parameter definitions
"""
super(ViaPCell, self).__init__() # Important: initialize the super class
super
# Every via PCell should declare these parameters: # 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.
def via_types
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
# #
# via: the name of the via as declared in the via types # w/h_top/bottom can be zero, indicating no specific
# w_bottom: the width of the bottom box in micrometers # dimension is requested. The implementation can choose
# h_bottom: the height of the bottom box in micrometers # what to do in this case. It can also decide to provide
# w_top: the width of the top box in micrometers # largher vias than given by w_ or h_ or favor square vias
# h_top: the width of the top box in micrometers # over rectangular ones.
#
# 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) def getnxy(vt)
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 mode = 2 # 1: maximum, 2: minimum
# the computed array dimension by setting these parameters
# to non-zero.
self.param("nx", self.TypeInt, "nx", default = 0) if mode == 1
self.param("ny", self.TypeInt, "ny", default = 0)
# Build a list of supported vias # This implementation uses the maximum size requested
# by either top or bottom:
self.via_type_list = [] w = [[self.w_bottom, vt.wbmin].max - 2 * vt.enc_bottom,
[self.w_top, vt.wtmin].max - 2 * vt.enc_top].max
# Via1 h = [[self.h_bottom, vt.hbmin].max - 2 * vt.enc_bottom,
vt = pya.ViaType("V1", "Via1 (0.2x0.2)") [self.h_top, vt.htmin].max - 2 * vt.enc_top].max
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 elsif mode == 2
# 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) # This implementation delivers the minimum via, ignoring
# zero dimensions:
# Via2 w = self.min_dim(self.w_bottom, self.w_top, vt.enc_bottom, vt.enc_top)
vt = pya.ViaType("V2", "Via2 (0.3x0.3)") h = self.min_dim(self.h_bottom, self.h_top, vt.enc_bottom, vt.enc_top)
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 end
# 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) # parameter nx or ny override computed value if &gt; 0
def via_types(self): if self.nx &gt; 0
""" nx = self.nx
Implements the "via_types" method from the PCellDeclaration else
interface: delivers the vias supported by this PCell. nx = [1, ((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10).floor.to_i].max
""" end
return self.via_type_list
def via_type(self): if self.ny &gt; 0
""" ny = self.ny
Returns the via with the name given by the "via" else
PCell argument ny = [1, ((h + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10).floor.to_i].max
""" end
for vt in self.via_type_list:
if vt.name == self.via:
return vt
return None
def display_text_impl(self): return [nx, ny]
"""
PCell interface implementation
"""
return "Via(" + self.via + ")"
def coerce_parameters_impl(self): end
"""
PCell interface implementation
"""
pass
def can_create_from_shape_impl(self): # Implementation of the PCell interface: generates the layouts
"""
PCell interface implementation
"""
return false
def parameters_from_shape_impl(self): def produce_impl
"""
PCell interface implementation
"""
pass
def transformation_from_shape_impl(self): vt = self.via_type
""" if !vt
PCell interface implementation return
""" end
return pya.Trans()
def min_dim(self, a, b, da, db): (nx, ny) = self.getnxy(vt)
"""
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 &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): 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)
Computes the nx and ny as mandated by the widths and ltop = self.layout.layer(vt.top)
heights lcut = self.layout.layer(vt.cut)
w/h_top/bottom can be zero, indicating no specific self.cell.shapes(lbottom).insert(RBA::DBox::new(wbottom, hbottom))
dimension is requested. The implementation can choose self.cell.shapes(ltop).insert(RBA::DBox::new(wtop, htop))
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 scut = self.cell.shapes(lcut)
if mode == 1: 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
# This implementation uses the maximum size requested end
# by either top or bottom:
w = max(max(self.w_bottom, vt.wbmin) - 2 * vt.enc_bottom, end
max(self.w_top, vt.wtmin) - 2 * vt.enc_top)
h = max(max(self.h_bottom, vt.hbmin) - 2 * vt.enc_bottom, # A declaration for a test library
max(self.h_top, vt.htmin) - 2 * vt.enc_top)
elif mode == 2: class MyViaLib &lt; RBA::Library
# This implementation delivers the minimum via, ignoring def initialize
# zero dimensions: self.description = "Via Test Library"
self.layout().register_pcell("Via", ViaPCell::new)
self.register("MyViaLib")
end
w = self.min_dim(self.w_bottom, self.w_top, vt.enc_bottom, vt.enc_top) end
h = self.min_dim(self.h_bottom, self.h_top, vt.enc_bottom, vt.enc_top)
# parameter nx or ny override computed value if &gt; 0 # instantiates the test library
MyViaLib::new
if self.nx &gt; 0: end
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):
"""
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()
</text> </text>
</klayout-macro> </klayout-macro>