Skip to content

Commit

Permalink
Merge pull request #1803 from KLayout/feature/issue-1790
Browse files Browse the repository at this point in the history
Implemented a solution for issue #1790 (Support for recursive PCell i…
  • Loading branch information
klayoutmatthias authored Jul 30, 2024
2 parents cfa0dac + c3fdc6e commit f4e4ce9
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 30 deletions.
90 changes: 66 additions & 24 deletions src/db/db/built-in-macros/pcell_declaration_helper.lym
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ module RBA
# of a PCell
class PCellDeclarationHelper < PCellDeclaration

# makes PCellDeclaration's "layout" method available
if ! self.method_defined?(:_layout_base)
alias_method :_layout_base, :layout
end

# import the Type... constants from PCellParameterDeclaration
PCellParameterDeclaration.constants.each do |c|
if !const_defined?(c)
Expand All @@ -290,18 +295,58 @@ module RBA
@cell = nil
@layer_param_index = []
@layers = nil
@state_stack = []
end

# provide accessors for the current layout and cell (for prod)
attr_reader :layout, :cell, :shape, :layer
attr_reader :cell, :shape, :layer

def layout
@layout || _layout_base()
end

# provide fallback accessors in case of a name clash with a
# parameter
def _layer; @layer; end
def _layout; @layout; end
def _layout; @layout || _layout_base(); end
def _cell; @cell; end
def _shape; @shape; end

# Starts an operation - pushes the state on the state stack

def start
@state_stack << [ @param_values, @param_states, @layers, @cell, @layout, @layer, @shape ]
self._reset_state
end

# Finishes an operation - pops the state from the state stack

def finish
if ! @state_stack.empty?
@param_values, @param_states, @layers, @cell, @layout, @layer, @shape = @state_stack.pop
else
self._reset_state
end
end

# Resets the state to default values

def _reset_state

@param_values = nil
@param_states = nil
@layers = nil
@layout = nil

# This should be here:
# @cell = nil
# @layer = nil
# @shape = nil
# but this would break backward compatibility of "display_text" (actually
# exploiting this bug) - fix this in the next major release.

end

# A helper method to access the nth parameter

def _get_param(nth, name)
Expand Down Expand Up @@ -410,11 +455,13 @@ module RBA

# implementation of display_text
def display_text(parameters)
self.start
@param_values = parameters
text = ""
begin
text = display_text_impl
ensure
@param_values = nil
self.finish
end
text
end
Expand All @@ -431,94 +478,89 @@ module RBA

# coerce parameters (make consistent)
def coerce_parameters(layout, parameters)
self.start
@param_values = parameters
@layout = layout
ret = parameters
begin
coerce_parameters_impl
ensure
@layout = nil
ret = @param_values
@param_values = nil
self.finish
end
ret
end

# parameter change callback
def callback(layout, name, states)
@param_values = nil
self.start
@param_states = states
@layout = layout
begin
callback_impl(name)
ensure
@param_states = nil
@layout = nil
self.finish
end
end

# produce the layout
def produce(layout, layers, parameters, cell)
self.start
@layers = layers
@cell = cell
@param_values = parameters
@layout = layout
begin
produce_impl
ensure
@layers = nil
@cell = nil
@param_values = nil
@layout = nil
self.finish
end
end

# produce a helper for can_create_from_shape
def can_create_from_shape(layout, shape, layer)
self.start
ret = false
@layout = layout
@shape = shape
@layer = layer
begin
ret = can_create_from_shape_impl
ensure
@layout = nil
@shape = nil
@layer = nil
self.finish
end
ret
end

# produce a helper for parameters_from_shape
# produce a helper for transformation_from_shape
def transformation_from_shape(layout, shape, layer)
self.start
@layout = layout
@shape = shape
@layer = layer
t = nil
begin
t = transformation_from_shape_impl
ensure
@layout = nil
@shape = nil
@layer = nil
self.finish
end
t
end

# produce a helper for parameters_from_shape
# with this helper, the implementation can use the parameter setters
def parameters_from_shape(layout, shape, layer)
self.start
@param_values = @param_decls.map { |pd| pd.default }
@layout = layout
@shape = shape
@layer = layer
begin
parameters_from_shape_impl
ensure
@layout = nil
@shape = nil
@layer = nil
ret = @param_values
self.finish
end
@param_values
ret
end

# default implementation
Expand Down
35 changes: 31 additions & 4 deletions src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def __init__(self, *args, **kwargs):
self._param_states = None
self._layer_param_index = []
self._layers = []
self._state_stack = []
# public attributes
self.layout = None
self.shape = None
Expand Down Expand Up @@ -129,6 +130,7 @@ def display_text(self, parameters):
This function delegates the implementation to self.display_text_impl
after configuring the PCellDeclaration object.
"""
self.start()
self._param_values = parameters
try:
text = self.display_text_impl()
Expand Down Expand Up @@ -165,6 +167,7 @@ def init_values(self, values = None, layers = None, states = None):
"layers" are the layer indexes corresponding to the layer
parameters.
"""
self.start()
self._param_values = None
self._param_states = None
if states:
Expand All @@ -177,17 +180,39 @@ def init_values(self, values = None, layers = None, states = None):
self._param_values = values
self._layers = layers

def start(self):
"""
Is called to prepare the environment for an operation
After the operation, "finish" must be called.
This method will push the state onto a stack, hence implementing
reentrant implementation methods.
"""
self._state_stack.append( (self._param_values, self._param_states, self._layers, self.cell, self.layout, self.layer, self.shape) )
self._reset_state()

def finish(self):
"""
Is called at the end of an implementation of a PCellDeclaration method
"""
if len(self._state_stack) > 0:
self._param_values, self._param_states, self._layers, self.cell, self.layout, self.layer, self.shape = self._state_stack.pop()
else:
self._reset_state()

def _reset_state(self):
"""
Resets the internal state
"""
self._param_values = None
self._param_states = None
self._layers = None
self._cell = None
self._layout = None
self._layer = None
self._shape = None
self.layout = super(_PCellDeclarationHelperMixin, self).layout()
# This should be here:
# self.cell = None
# self.layer = None
# self.shape = None
# but this would break backward compatibility of "display_text" (actually
# exploiting this bug) - fix this in the next major release.

def get_layers(self, parameters):
"""
Expand Down Expand Up @@ -255,6 +280,7 @@ def can_create_from_shape(self, layout, shape, layer):
The function delegates the implementation to can_create_from_shape_impl
after updating the state of this object with the current parameters.
"""
self.start()
self.layout = layout
self.shape = shape
self.layer = layer
Expand All @@ -271,6 +297,7 @@ def transformation_from_shape(self, layout, shape, layer):
The function delegates the implementation to transformation_from_shape_impl
after updating the state of this object with the current parameters.
"""
self.start()
self.layout = layout
self.shape = shape
self.layer = layer
Expand Down
79 changes: 77 additions & 2 deletions testdata/python/dbPCells.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import pya
import unittest
import math
import sys

class BoxPCell(pya.PCellDeclaration):
Expand Down Expand Up @@ -77,7 +78,7 @@ def __init__(self):
sb_cell = self.layout().cell(sb_index)
sb_cell.shapes(l10).insert(pya.Box(0, 0, 100, 200))

# register us with the name "MyLib"
# register us with the name "PCellTestLib"
self.register("PCellTestLib")


Expand Down Expand Up @@ -135,9 +136,62 @@ def __init__(self):
# create the PCell declarations
self.layout().register_pcell("Box2", BoxPCell2())

# register us with the name "MyLib"
# register us with the name "PCellTestLib2"
self.register("PCellTestLib2")

# A recursive PCell

class RecursivePCell(pya.PCellDeclarationHelper):

def __init__(self):

super(RecursivePCell, self).__init__()

self.param("layer", self.TypeLayer, "Layer", default = pya.LayerInfo(0, 0))
self.param("line", self.TypeShape, "Line", default = pya.Edge(0, 0, 10000, 0))
self.param("level", self.TypeInt, "Level", default = 1)

def display_text_impl(self):
# provide a descriptive text for the cell
return "RecursivePCell(L=" + str(self.layer) + ",E=" + str(pya.CplxTrans(self.layout.dbu) * self.line) + ",LVL=" + str(self.level)

def produce_impl(self):

# fetch the parameters
l = self.layer_layer
e = self.line

if self.level <= 0:
self.cell.shapes(l).insert(e)
return

d3 = e.d() * (1.0 / 3.0)
d3n = pya.Vector(-d3.y, d3.x)

e1 = pya.Edge(e.p1, e.p1 + d3)
e2 = pya.Edge(e1.p2, e1.p2 + d3 * 0.5 + d3n * math.cos(math.pi / 6))
e3 = pya.Edge(e2.p2, e.p1 + d3 * 2.0)
e4 = pya.Edge(e3.p2, e.p2)

for e in [ e1, e2, e3, e4 ]:
t = pya.Trans(e.p1 - pya.Point())
cc = self.layout.create_cell("RecursivePCell", { "layer": self.layer, "line": t.inverted() * e, "level": self.level - 1 })
self.cell.insert(pya.CellInstArray(cc, t))

class PCellTestLib3(pya.Library):

def __init__(self):

# set the description
self.description = "PCell test lib3"

# create the PCell declarations
self.layout().register_pcell("RecursivePCell", RecursivePCell())

# register us with the name "PCellTestLib3"
self.register("PCellTestLib3")



def inspect_LayerInfo(self):
return "<" + str(self) + ">"
Expand Down Expand Up @@ -501,6 +555,27 @@ def test_8(self):

self.assertEqual(cell.begin_shapes_rec(ly.layer(5, 0)).shape().__str__(), "box (-100,-300;100,300)")

def test_9(self):

if not "PCellDeclarationHelper" in pya.__dict__:
return

# instantiate and register the library
tl = PCellTestLib3()

ly = pya.Layout(True)

li1 = find_layer(ly, "1/0")
self.assertEqual(li1 == None, True)

c1 = ly.create_cell("c1")

c2 = ly.create_cell("RecursivePCell", "PCellTestLib3", { "layer": pya.LayerInfo(1, 0), "level": 4, "line": pya.Edge(0, 0, 20000, 0) })
c1.insert(pya.CellInstArray(c2.cell_index(), pya.Trans()))

self.assertEqual(c2.display_title(), "PCellTestLib3.RecursivePCell(L=1/0,E=(0,0;20,0),LVL=4")
self.assertEqual(str(c1.dbbox()), "(0,0;20,5.774)")

# run unit tests
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(DBPCellTests)
Expand Down
Loading

0 comments on commit f4e4ce9

Please sign in to comment.