From c3fdc6e1bcc42f3e1e6dfe55dc7c245c180ffce2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 27 Jul 2024 14:00:05 +0200 Subject: [PATCH] Implemented a solution for issue #1790 (Support for recursive PCell instances) This also fixes some other issues, like "display_text_impl" being called when a PCell is run with the debugger open. --- .../pcell_declaration_helper.lym | 90 ++++++++++++++----- .../klayout/db/pcell_declaration_helper.py | 35 +++++++- testdata/python/dbPCells.py | 79 +++++++++++++++- testdata/ruby/dbPCells.rb | 89 ++++++++++++++++++ 4 files changed, 263 insertions(+), 30 deletions(-) diff --git a/src/db/db/built-in-macros/pcell_declaration_helper.lym b/src/db/db/built-in-macros/pcell_declaration_helper.lym index a380b77f7b..3561cae826 100644 --- a/src/db/db/built-in-macros/pcell_declaration_helper.lym +++ b/src/db/db/built-in-macros/pcell_declaration_helper.lym @@ -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) @@ -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) @@ -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 @@ -431,34 +478,33 @@ 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 @@ -466,15 +512,13 @@ module RBA 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 @@ -482,24 +526,22 @@ module RBA 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 @@ -507,6 +549,7 @@ module RBA # 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 @@ -514,11 +557,10 @@ module RBA begin parameters_from_shape_impl ensure - @layout = nil - @shape = nil - @layer = nil + ret = @param_values + self.finish end - @param_values + ret end # default implementation diff --git a/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py b/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py index ab432550d7..3667d3f63b 100644 --- a/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py +++ b/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py @@ -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 @@ -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() @@ -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: @@ -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): """ @@ -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 @@ -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 diff --git a/testdata/python/dbPCells.py b/testdata/python/dbPCells.py index a68880477d..fcfb1e4dc9 100644 --- a/testdata/python/dbPCells.py +++ b/testdata/python/dbPCells.py @@ -18,6 +18,7 @@ import pya import unittest +import math import sys class BoxPCell(pya.PCellDeclaration): @@ -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") @@ -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) + ">" @@ -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) diff --git a/testdata/ruby/dbPCells.rb b/testdata/ruby/dbPCells.rb index afbc7f2f2a..36fa4d5ce2 100644 --- a/testdata/ruby/dbPCells.rb +++ b/testdata/ruby/dbPCells.rb @@ -170,6 +170,71 @@ def initialize end + # A recursive PCell + + class RecursivePCell < RBA::PCellDeclarationHelper + + def initialize + + super() + + param("layer", RecursivePCell::TypeLayer, "Layer", :default => RBA::LayerInfo::new(0, 0)) + param("line", RecursivePCell::TypeShape, "Line", :default => RBA::Edge::new(0, 0, 10000, 0)) + param("level", RecursivePCell::TypeInt, "Level", :default => 1) + + end + + def display_text_impl + # provide a descriptive text for the cell + return "RecursivePCell(L=" + self.layer.to_s + ",E=" + (RBA::CplxTrans::new(self.layout.dbu) * self.line).to_s + ",LVL=" + self.level.to_s + end + + def produce_impl + + # fetch the parameters + l = self.layer_layer + e = self.line + + if self.level <= 0 + self.cell.shapes(l).insert(e) + return + end + + d3 = e.d * (1.0 / 3.0) + d3n = RBA::Vector::new(-d3.y, d3.x) + + e1 = RBA::Edge::new(e.p1, e.p1 + d3) + e2 = RBA::Edge::new(e1.p2, e1.p2 + d3 * 0.5 + d3n * Math::cos(Math::PI / 6)) + e3 = RBA::Edge::new(e2.p2, e.p1 + d3 * 2.0) + e4 = RBA::Edge::new(e3.p2, e.p2) + + [ e1, e2, e3, e4 ].each do |e| + t = RBA::Trans::new(e.p1 - RBA::Point::new) + cc = self.layout.create_cell("RecursivePCell", { "layer" => self.layer, "line" => t.inverted * e, "level" => self.level - 1 }) + self.cell.insert(RBA::CellInstArray::new(cc, t)) + end + + end + + end + + class PCellTestLib3 < RBA::Library + + def initialize + + # set the description + self.description = "PCell test lib3" + + # create the PCell declarations + self.layout().register_pcell("RecursivePCell", RecursivePCell::new) + + # register us with the name "PCellTestLib3" + self.register("PCellTestLib3") + + end + + end + end # A helper for testing: provide an inspect method @@ -809,6 +874,30 @@ def test_11 end + def test_12 + + if !RBA.constants.member?(:PCellDeclarationHelper) + return + end + + # instantiate and register the library + tl = PCellTestLib3::new + + ly = RBA::Layout::new + + li1 = ly.find_layer("1/0") + assert_equal(li1 == nil, true) + + c1 = ly.create_cell("c1") + + c2 = ly.create_cell("RecursivePCell", "PCellTestLib3", { "layer" => RBA::LayerInfo::new(1, 0), "level" => 4, "line" => RBA::Edge::new(0, 0, 20000, 0) }) + c1.insert(RBA::CellInstArray::new(c2.cell_index(), RBA::Trans::new)) + + assert_equal(c2.display_title, "PCellTestLib3.RecursivePCell(L=1/0,E=(0,0;20,0),LVL=4") + assert_equal(c1.dbbox.to_s, "(0,0;20,5.774)") + + end + end load("test_epilogue.rb")