From 6052a0442995680d28a528cefdee927483e92f89 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 17 Feb 2018 01:26:25 +0100 Subject: [PATCH] A few enhancements and unit tests for PCells This commit adds unit tests for implementation helper-based PCells in Python and Ruby. --- .../pcell_declaration_helper.lym | 20 +++ .../pcell_declaration_helper.lym | 5 +- testdata/python/dbPCells.py | 138 ++++++++++++++- testdata/ruby/dbPCells.rb | 159 +++++++++++++++++- 4 files changed, 311 insertions(+), 11 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 0c8497f0b..b0674e5b2 100644 --- a/src/db/db/built-in-macros/pcell_declaration_helper.lym +++ b/src/db/db/built-in-macros/pcell_declaration_helper.lym @@ -183,6 +183,13 @@ to create the \\LayerInfo object, i.e. like this: The default implementation does nothing. All parameters not set in this method will receive their default value. +If you use a parameter called "layer" for example, the parameter getter will hide the +"layer" argument. Use "_layer" for the argument in this case (same for "layout", "shape" or "cell): + +@code + set_layer layout.get_info(_layer) +@/code + @method transformation_from_shape_impl @brief Gets the initial PCell instance transformation when creating from a shape @@ -230,6 +237,13 @@ module RBA # provide accessors for the current layout and cell (for prod) attr_reader :layout, :cell, :shape, :layer + + # provide fallback accessors in case of a name clash with a + # parameter + def _layer; @layer; end + def _layout; @layout; end + def _cell; @cell; end + def _shape; @shape; end # define a parameter # name -> the short name of the parameter @@ -246,6 +260,12 @@ module RBA # set_{name} -> write accessor ({name}= does not work because the # Ruby confuses that method with variables) # {name}_layer -> read accessor for the layer index for TypeLayer parameters + # in addition, fallback accessors are defined which can be used if + # the parameter's name clashes with another name: + # param_{name} + # set_param_{name} + # param_{name}_layer + def param(name, type, description, args = {}) # create accessor methods for the parameters diff --git a/src/db/db/built-in-pymacros/pcell_declaration_helper.lym b/src/db/db/built-in-pymacros/pcell_declaration_helper.lym index f0b6c0003..f867865dc 100644 --- a/src/db/db/built-in-pymacros/pcell_declaration_helper.lym +++ b/src/db/db/built-in-pymacros/pcell_declaration_helper.lym @@ -26,7 +26,7 @@ class MyPCell(pya.PCellDeclarationHelper): def __init__(self): # Important: initialize the super class - super(Circle, self).__init__() + super(MyPCell, self).__init__() # your initialization: add parameters with name, type, description and # optional other values @@ -431,10 +431,11 @@ class _PCellDeclarationHelper(pya.PCellDeclaration): self.shape = shape self.layer = layer self.parameters_from_shape_impl() + param = self.get_values() self.layout = None self.shape = None self.layer = None - return self.get_values() + return param def display_text_impl(self): """ diff --git a/testdata/python/dbPCells.py b/testdata/python/dbPCells.py index 686df2e88..27a80057b 100644 --- a/testdata/python/dbPCells.py +++ b/testdata/python/dbPCells.py @@ -52,20 +52,25 @@ class BoxPCell(pya.PCellDeclaration): # create the shape cell.shapes(layers[0]).insert(pya.Box(-w / 2, -h / 2, w / 2, h / 2)) + + def can_create_from_shape(self, layout, shape, layer): + return shape.is_box() + + def transformation_from_shape(self, layout, shape, layer): + return pya.Trans(shape.box.center() - pya.Point()) + + def parameters_from_shape(self, layout, shape, layer): + return [ layout.get_info(layer), shape.box.width() * layout.dbu, shape.box.height() * layout.dbu ] - class PCellTestLib(pya.Library): - boxpcell = None - def __init__(self): # set the description self.description = "PCell test lib" # create the PCell declarations - boxpcell = BoxPCell() - self.layout().register_pcell("Box", boxpcell) + self.layout().register_pcell("Box", BoxPCell()) sb_index = self.layout().add_cell("StaticBox") l10 = self.layout().insert_layer(pya.LayerInfo(10, 0)) @@ -75,6 +80,60 @@ class PCellTestLib(pya.Library): # register us with the name "MyLib" self.register("PCellTestLib") + +# A PCell based on the declaration helper + +class BoxPCell2(pya.PCellDeclarationHelper): + + def __init__(self): + + super(BoxPCell2, self).__init__() + + self.param("layer", self.TypeLayer, "Layer", default = pya.LayerInfo(0, 0)) + self.param("width", self.TypeDouble, "Width", default = 1.0) + self.param("height", self.TypeDouble, "Height", default = 1.0) + + def display_text_impl(self): + # provide a descriptive text for the cell + return "Box2(L=" + str(self.layer) + ",W=" + ('%.3f' % self.width) + ",H=" + ('%.3f' % self.height) + ")" + + def produce_impl(self): + + dbu = self.layout.dbu + + # fetch the parameters + l = self.layer_layer + w = self.width / self.layout.dbu + h = self.height / self.layout.dbu + + # create the shape + self.cell.shapes(l).insert(pya.Box(-w / 2, -h / 2, w / 2, h / 2)) + + def can_create_from_shape_impl(self): + return self.shape.is_box() + + def transformation_from_shape_impl(self): + return pya.Trans(self.shape.box.center() - pya.Point()) + + def parameters_from_shape_impl(self): + self.layer = self.layout.get_info(self.layer) + self.width = self.shape.box.width() * self.layout.dbu + self.height = self.shape.box.height() * self.layout.dbu + +class PCellTestLib2(pya.Library): + + def __init__(self): + + # set the description + self.description = "PCell test lib2" + + # create the PCell declarations + self.layout().register_pcell("Box2", BoxPCell2()) + + # register us with the name "MyLib" + self.register("PCellTestLib2") + + def inspect_LayerInfo(self): return "<" + str(self) + ">" @@ -233,6 +292,75 @@ class DBPCellTests(unittest.TestCase): pcell_inst.cell_index = new_id self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-500,-100;500,100)") + l10 = ly.layer(10, 0) + c1.shapes(l10).insert(pya.Box(0, 10, 100, 210)) + l11 = ly.layer(11, 0) + c1.shapes(l11).insert(pya.Text("hello", pya.Trans())) + self.assertEqual(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l11).shape(), l10), False) + self.assertEqual(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10), True) + self.assertEqual(repr(pcell_decl.parameters_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10)), "[<10/0>, 1.0, 2.0]") + self.assertEqual(str(pcell_decl.transformation_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10)), "r0 50,110") + + + def test_1a(self): + + # instantiate and register the library + tl = PCellTestLib2() + + ly = pya.Layout(True) + ly.dbu = 0.01 + + li1 = find_layer(ly, "1/0") + self.assertEqual(li1 == None, True) + + ci1 = ly.add_cell("c1") + c1 = ly.cell(ci1) + + lib = pya.Library.library_by_name("PCellTestLib2") + self.assertEqual(lib != None, True) + pcell_decl = lib.layout().pcell_declaration("Box2") + + param = [ pya.LayerInfo(1, 0) ] # rest is filled with defaults + pcell_var_id = ly.add_pcell_variant(lib, pcell_decl.id(), param) + pcell_var = ly.cell(pcell_var_id) + pcell_inst = c1.insert(pya.CellInstArray(pcell_var_id, pya.Trans())) + self.assertEqual(pcell_var.basic_name(), "Box2") + self.assertEqual(pcell_var.pcell_parameters().__repr__(), "[<1/0>, 1.0, 1.0]") + self.assertEqual(pcell_var.display_title(), "PCellTestLib2.Box2(L=1/0,W=1.000,H=1.000)") + self.assertEqual(nh(pcell_var.pcell_parameters_by_name()), "{'height': 1.0, 'layer': <1/0>, 'width': 1.0}") + self.assertEqual(pcell_var.pcell_parameter("height").__repr__(), "1.0") + self.assertEqual(c1.pcell_parameters(pcell_inst).__repr__(), "[<1/0>, 1.0, 1.0]") + self.assertEqual(nh(c1.pcell_parameters_by_name(pcell_inst)), "{'height': 1.0, 'layer': <1/0>, 'width': 1.0}") + self.assertEqual(c1.pcell_parameter(pcell_inst, "height").__repr__(), "1.0") + self.assertEqual(nh(pcell_inst.pcell_parameters_by_name()), "{'height': 1.0, 'layer': <1/0>, 'width': 1.0}") + self.assertEqual(pcell_inst["height"].__repr__(), "1.0") + self.assertEqual(pcell_inst.pcell_parameter("height").__repr__(), "1.0") + self.assertEqual(pcell_var.pcell_declaration().__repr__(), pcell_decl.__repr__()) + self.assertEqual(c1.pcell_declaration(pcell_inst).__repr__(), pcell_decl.__repr__()) + self.assertEqual(pcell_inst.pcell_declaration().__repr__(), pcell_decl.__repr__()) + + li1 = find_layer(ly, "1/0") + self.assertEqual(li1 == None, False) + pcell_inst.change_pcell_parameter("height", 2.0) + self.assertEqual(nh(pcell_inst.pcell_parameters_by_name()), "{'height': 2.0, 'layer': <1/0>, 'width': 1.0}") + + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-50,-100;50,100)") + + param = { "layer": pya.LayerInfo(2, 0), "width": 2, "height": 1 } + li2 = ly.layer(2, 0) + c1.change_pcell_parameters(pcell_inst, param) + self.assertEqual(ly.begin_shapes(c1.cell_index(), li2).shape().__str__(), "box (-100,-50;100,50)") + + l10 = ly.layer(10, 0) + c1.shapes(l10).insert(pya.Box(0, 10, 100, 210)) + l11 = ly.layer(11, 0) + c1.shapes(l11).insert(pya.Text("hello", pya.Trans())) + self.assertEqual(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l11).shape(), l10), False) + self.assertEqual(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10), True) + self.assertEqual(repr(pcell_decl.parameters_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10)), "[<10/0>, 1.0, 2.0]") + self.assertEqual(str(pcell_decl.transformation_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10)), "r0 50,110") + + def test_2(self): # instantiate and register the library diff --git a/testdata/ruby/dbPCells.rb b/testdata/ruby/dbPCells.rb index 938c0d1f6..1f3e30950 100644 --- a/testdata/ruby/dbPCells.rb +++ b/testdata/ruby/dbPCells.rb @@ -61,6 +61,18 @@ class BoxPCell < RBA::PCellDeclaration end + def can_create_from_shape(layout, shape, layer) + return shape.is_box? + end + + def transformation_from_shape(layout, shape, layer) + return RBA::Trans::new(shape.box.center - RBA::Point::new) + end + + def parameters_from_shape(layout, shape, layer) + return [ layout.get_info(layer), shape.box.width * layout.dbu, shape.box.height * layout.dbu ] + end + end class PCellTestLib < RBA::Library @@ -85,6 +97,72 @@ class PCellTestLib < RBA::Library end +# A PCell based on the declaration helper + +class BoxPCell2 < RBA::PCellDeclarationHelper + + def initialize + + super() + + param("layer", BoxPCell2::TypeLayer, "Layer", :default => RBA::LayerInfo::new(0, 0)) + param("width", BoxPCell2::TypeDouble, "Width", :default => 1.0) + param("height", BoxPCell2::TypeDouble, "Height", :default => 1.0) + + end + + def display_text_impl + # provide a descriptive text for the cell + return "Box2(L=" + layer.to_s + ",W=" + ('%.3f' % width) + ",H=" + ('%.3f' % height) + ")" + end + + def produce_impl + + # fetch the parameters + l = layer_layer + w = width / layout.dbu + h = height / layout.dbu + + # create the shape + cell.shapes(l).insert(RBA::Box::new(-w / 2, -h / 2, w / 2, h / 2)) + + end + + def can_create_from_shape_impl + return self.shape.is_box? + end + + def transformation_from_shape_impl + return RBA::Trans::new(shape.box.center - RBA::Point::new) + end + + def parameters_from_shape_impl + # NOTE: because there is one parameter called "layer" already, we need to use + # the "_layer" fallback to access the argument to this method + set_layer(_layout.get_info(_layer)) + set_width(shape.box.width * _layout.dbu) + set_height(shape.box.height * _layout.dbu) + end + +end + +class PCellTestLib2 < RBA::Library + + def initialize + + # set the description + description = "PCell test lib2" + + # create the PCell declarations + layout.register_pcell("Box2", BoxPCell2::new) + + # register us with the name "MyLib" + self.register("PCellTestLib2") + + end + +end + # A helper for testing: provide an inspect method class RBA::LayerInfo def inspect @@ -115,8 +193,7 @@ class DBPCell_TestClass < TestBase ly = RBA::Layout::new(true) ly.dbu = 0.01 - li1 = ly.layer_indices.find { |li| ly.get_info(li).to_s == "1/0" } - assert_equal(li1 == nil, true) + li1 = ly.layer(1, 0) ci1 = ly.add_cell("c1") c1 = ly.cell(ci1) @@ -241,10 +318,84 @@ class DBPCell_TestClass < TestBase pcell_inst.cell_index = new_id assert_equal(ly.begin_shapes(c1.cell_index, li1).shape.to_s, "box (-500,-100;500,100)") -#ly.destroy + l10 = ly.layer(10, 0) + c1.shapes(l10).insert(RBA::Box::new(0, 10, 100, 210)) + l11 = ly.layer(11, 0) + c1.shapes(l11).insert(RBA::Text::new("hello", RBA::Trans::new)) + assert_equal(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l11).shape(), l10), false) + assert_equal(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10), true) + assert_equal(pcell_decl.parameters_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10).inspect, "[<10/0>, 1.0, 2.0]") + assert_equal(pcell_decl.transformation_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10).to_s, "r0 50,110") + + ly.destroy ensure -#tl.delete + tl.delete + end + + end + + def test_1a + + # instantiate and register the library + tl = PCellTestLib2::new + + begin + + ly = RBA::Layout::new(true) + ly.dbu = 0.01 + + ci1 = ly.add_cell("c1") + c1 = ly.cell(ci1) + + lib = RBA::Library.library_by_name("PCellTestLib2") + assert_equal(lib != nil, true) + pcell_decl = lib.layout().pcell_declaration("Box2") + + param = [ RBA::LayerInfo::new(1, 0) ] # rest is filled with defaults + pcell_var_id = ly.add_pcell_variant(lib, pcell_decl.id(), param) + pcell_var = ly.cell(pcell_var_id) + pcell_inst = c1.insert(RBA::CellInstArray::new(pcell_var_id, RBA::Trans::new)) + assert_equal(pcell_var.basic_name, "Box2") + assert_equal(pcell_var.pcell_parameters().inspect, "[<1/0>, 1.0, 1.0]") + assert_equal(pcell_var.display_title(), "PCellTestLib2.Box2(L=1/0,W=1.000,H=1.000)") + assert_equal(norm_hash(pcell_var.pcell_parameters_by_name()), "{\"height\"=>1.0, \"layer\"=><1/0>, \"width\"=>1.0}") + assert_equal(pcell_var.pcell_parameter("height").inspect(), "1.0") + assert_equal(c1.pcell_parameters(pcell_inst).inspect(), "[<1/0>, 1.0, 1.0]") + assert_equal(norm_hash(c1.pcell_parameters_by_name(pcell_inst)), "{\"height\"=>1.0, \"layer\"=><1/0>, \"width\"=>1.0}") + assert_equal(c1.pcell_parameter(pcell_inst, "height").inspect(), "1.0") + assert_equal(norm_hash(pcell_inst.pcell_parameters_by_name()), "{\"height\"=>1.0, \"layer\"=><1/0>, \"width\"=>1.0}") + assert_equal(pcell_inst["height"].inspect(), "1.0") + assert_equal(pcell_inst.pcell_parameter("height").inspect(), "1.0") + assert_equal(pcell_var.pcell_declaration().inspect(), pcell_decl.inspect) + assert_equal(c1.pcell_declaration(pcell_inst).inspect(), pcell_decl.inspect) + assert_equal(pcell_inst.pcell_declaration().inspect(), pcell_decl.inspect) + + li1 = ly.layer(1, 0) + assert_equal(li1 == nil, false) + pcell_inst.change_pcell_parameter("height", 2.0) + assert_equal(norm_hash(pcell_inst.pcell_parameters_by_name()), "{\"height\"=>2.0, \"layer\"=><1/0>, \"width\"=>1.0}") + + assert_equal(ly.begin_shapes(c1.cell_index(), li1).shape().to_s, "box (-50,-100;50,100)") + + param = { "layer" => RBA::LayerInfo::new(2, 0), "width" => 2, "height" => 1 } + li2 = ly.layer(2, 0) + c1.change_pcell_parameters(pcell_inst, param) + assert_equal(ly.begin_shapes(c1.cell_index(), li2).shape().to_s, "box (-100,-50;100,50)") + + l10 = ly.layer(10, 0) + c1.shapes(l10).insert(RBA::Box::new(0, 10, 100, 210)) + l11 = ly.layer(11, 0) + c1.shapes(l11).insert(RBA::Text::new("hello", RBA::Trans::new)) + assert_equal(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l11).shape(), l10), false) + assert_equal(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10), true) + assert_equal(pcell_decl.parameters_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10).inspect, "[<10/0>, 1.0, 2.0]") + assert_equal(pcell_decl.transformation_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10).to_s, "r0 50,110") + + ly.destroy + + ensure + tl.delete end end