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,41 +7,53 @@
<show-in-menu>false</show-in-menu>
<category>macros</category>
<interpreter>ruby</interpreter>
<text>import pya
import math
<text># 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.
"""
Sample via PCell
module MyViaLib
This sample PCell implements a library called "MyViaLib" with a single PCell that
provides two vias for a hypothetical technology with these layers:
include RBA
1/0 Metal1
2/0 Via1
3/0 Metal2
4/0 Via2
5/0 Metal3
# Extend the ViaType class with some custom attributes
class ViaTypeEx &lt; RBA::ViaType
The sample demonstrates how to equip a PCell with the necessary declarations,
so it can supply vias for the wire (path) tool.
def initialize(*args)
super(*args)
end
It implements simple rectangular via arrays made from squares with a fixed size,
pitch and enclosure.
attr_accessor :enc_bottom
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
implementation. The macro is also set to "auto run" to install the PCell
when KLayout is run.
"""
end
class ViaPCell(pya.PCellDeclarationHelper):
class ViaPCell &lt; PCellDeclarationHelper
def __init__(self):
# Constructor: provides the PCell parameter definitions
"""
Constructor: provides the PCell parameter definitions
"""
def initialize
super(ViaPCell, self).__init__()
# Important: initialize the super class
super
# Every via PCell should declare these parameters:
#
@ -55,53 +67,53 @@ class ViaPCell(pya.PCellDeclarationHelper):
# 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)
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.
self.param("nx", self.TypeInt, "nx", default = 0)
self.param("ny", self.TypeInt, "ny", default = 0)
param(:nx, TypeInt, "nx", :default =&gt; 0)
param(:ny, TypeInt, "ny", :default =&gt; 0)
# Build a list of supported vias
self.via_type_list = []
@via_type_list = []
# Via1
vt = pya.ViaType("V1", "Via1 (0.2x0.2)")
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 = pya.LayerInfo(1, 0)
vt.cut = pya.LayerInfo(2, 0)
vt.top = pya.LayerInfo(3, 0)
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 pya, but handy for
# 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
self.via_type_list.append(vt)
@via_type_list.append(vt)
# Via2
vt = pya.ViaType("V2", "Via2 (0.3x0.3)")
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 = pya.LayerInfo(3, 0)
vt.cut = pya.LayerInfo(4, 0)
vt.top = pya.LayerInfo(5, 0)
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
@ -112,98 +124,93 @@ class ViaPCell(pya.PCellDeclarationHelper):
vt.vwidth = 0.3
vt.vspace = 0.2
self.via_type_list.append(vt)
@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
end
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:
# 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
return None
end
end
return nil
end
def display_text_impl(self):
"""
PCell interface implementation
"""
# PCell interface implementation
def display_text_impl
return "Via(" + self.via + ")"
end
def coerce_parameters_impl(self):
"""
PCell interface implementation
"""
pass
# PCell interface implementation
def coerce_parameters_impl
end
def can_create_from_shape_impl(self):
"""
PCell interface implementation
"""
# PCell interface implementation
def can_create_from_shape_impl
return false
end
def parameters_from_shape_impl(self):
"""
PCell interface implementation
"""
pass
# PCell interface implementation
def parameters_from_shape_impl
end
def transformation_from_shape_impl(self):
"""
PCell interface implementation
"""
return pya.Trans()
# PCell interface implementation
def transformation_from_shape_impl
return RBA::Trans::new
end
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 &lt; 1e-10 and b &lt; 1e-10:
# 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
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)
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
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.
"""
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:
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)
w = [[self.w_bottom, vt.wbmin].max - 2 * vt.enc_bottom,
[self.w_top, vt.wtmin].max - 2 * vt.enc_top].max
h = max(max(self.h_bottom, vt.hbmin) - 2 * vt.enc_bottom,
max(self.h_top, vt.htmin) - 2 * vt.enc_top)
h = [[self.h_bottom, vt.hbmin].max - 2 * vt.enc_bottom,
[self.h_top, vt.htmin].max - 2 * vt.enc_top].max
elif mode == 2:
elsif mode == 2
# This implementation delivers the minimum via, ignoring
# zero dimensions:
@ -211,67 +218,80 @@ class ViaPCell(pya.PCellDeclarationHelper):
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)
end
# parameter nx or ny override computed value if &gt; 0
if self.nx &gt; 0:
if self.nx &gt; 0
nx = self.nx
else:
nx = max(1, int(math.floor((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10)))
else
nx = [1, ((w + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10).floor.to_i].max
end
if self.ny &gt; 0:
if self.ny &gt; 0
ny = self.ny
else:
ny = max(1, int(math.floor((h + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10)))
else
ny = [1, ((h + vt.vspace) / (vt.vwidth + vt.vspace) + 1e-10).floor.to_i].max
end
return (nx, ny)
return [nx, ny]
def produce_impl(self):
end
"""
Implementation of the PCell interface: generates the layouts
"""
# Implementation of the PCell interface: generates the layouts
vt = self.via_type()
if vt is None:
def produce_impl
vt = self.via_type
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 = 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)
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(pya.DBox(wbottom, hbottom))
self.cell.shapes(ltop).insert(pya.DBox(wtop, htop))
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)
for ix in range(0, nx):
nx.times do |ix|
x = (ix - 0.5 * (nx - 1)) * (vt.vwidth + vt.vspace)
for iy in range(0, ny):
ny.times do |iy|
y = (iy - 0.5 * (ny - 1)) * (vt.vwidth + vt.vspace)
scut.insert(pya.DBox(vt.vwidth, vt.vwidth).moved(x, y))
scut.insert(RBA::DBox::new(vt.vwidth, vt.vwidth).moved(x, y))
end
end
end
class MyViaLib(pya.Library):
end
"""
A declaration for a test library
"""
# A declaration for a test library
def __init__(self):
class MyViaLib &lt; RBA::Library
def initialize
self.description = "Via Test Library"
self.layout().register_pcell("Via", ViaPCell())
self.layout().register_pcell("Via", ViaPCell::new)
self.register("MyViaLib")
end
end
# instantiates the test library
MyViaLib()
MyViaLib::new
end
</text>
</klayout-macro>