diff --git a/src/db/db/dbShapeCollection.cc b/src/db/db/dbShapeCollection.cc index ea79c2b4b..4d5a3a0fc 100644 --- a/src/db/db/dbShapeCollection.cc +++ b/src/db/db/dbShapeCollection.cc @@ -57,9 +57,10 @@ DeepShapeCollectionDelegateBase::apply_property_translator (const db::Properties db::Shapes &shapes = c->shapes (m_deep_layer.layer ()); db::Shapes new_shapes (shapes.is_editable ()); - new_shapes.assign (shapes, pt); shapes.swap (new_shapes); + shapes.assign (new_shapes, pt); + } } diff --git a/src/db/db/gsiDeclDbRecursiveShapeIterator.cc b/src/db/db/gsiDeclDbRecursiveShapeIterator.cc index 092ec6502..e97f17bfe 100644 --- a/src/db/db/gsiDeclDbRecursiveShapeIterator.cc +++ b/src/db/db/gsiDeclDbRecursiveShapeIterator.cc @@ -172,6 +172,32 @@ static db::Region complex_region (const db::RecursiveShapeIterator *iter) } } +static void enable_properties (db::RecursiveShapeIterator *c) +{ + c->apply_property_translator (db::PropertiesTranslator::make_pass_all ()); +} + +static void remove_properties (db::RecursiveShapeIterator *c) +{ + c->apply_property_translator (db::PropertiesTranslator::make_remove_all ()); +} + +static void filter_properties (db::RecursiveShapeIterator *c, const std::vector &keys) +{ + if (c->layout ()) { + std::set kf; + kf.insert (keys.begin (), keys.end ()); + c->apply_property_translator (db::PropertiesTranslator::make_filter (const_cast (c->layout ())->properties_repository (), kf)); + } +} + +static void map_properties (db::RecursiveShapeIterator *c, const std::map &map) +{ + if (c->layout ()) { + c->apply_property_translator (db::PropertiesTranslator::make_key_mapper (const_cast (c->layout ())->properties_repository (), map)); + } +} + Class decl_RecursiveShapeIterator ("db", "RecursiveShapeIterator", gsi::constructor ("new", &new_si1, gsi::arg ("layout"), gsi::arg ("cell"), gsi::arg ("layer"), "@brief Creates a recursive, single-layer shape iterator.\n" @@ -547,6 +573,20 @@ Class decl_RecursiveShapeIterator ("db", "RecursiveS "\n" "This method has been introduced in version 0.25.3." ) + + gsi::method ("prop_id", &db::RecursiveShapeIterator::prop_id, + "@brief Gets the effective properties ID\n" + "The shape iterator supports property filtering and translation. This method will deliver " + "the effective property ID after translation. The original property ID can be obtained from " + "'shape.prop_id' and is not changed by installing filters or mappers.\n" + "\n" + "\\prop_id is evaluated by \\Region objects for example, when they are created " + "from a shape iterator.\n" + "\n" + "See \\enable_properties, \\filter_properties, \\remove_properties and \\map_properties for " + "details on this feature.\n" + "\n" + "This attribute has been introduced in version 0.28.4." + ) + gsi::method ("shape", &db::RecursiveShapeIterator::shape, "@brief Gets the current shape\n" "\n" @@ -592,6 +632,51 @@ Class decl_RecursiveShapeIterator ("db", "RecursiveS "@brief Comparison of iterators - inequality\n" "\n" "Two iterators are not equal if they do not point to the same shape.\n" + ) + + gsi::method_ext ("enable_properties", &enable_properties, + "@brief Enables properties for the given iterator.\n" + "Afer enabling properties, \\prop_id will deliver the effective properties ID for the current shape. " + "By default, properties are not enabled and \\prop_id will always return 0 (no properties attached). " + "Alternatively you can apply \\filter_properties " + "or \\map_properties to enable properties with a specific name key.\n" + "\n" + "Note that property filters/mappers are additive and act in addition (after) the currently installed filter.\n" + "\n" + "This feature has been introduced in version 0.28.4." + ) + + gsi::method_ext ("remove_properties", &remove_properties, + "@brief Removes properties for the given container.\n" + "This will remove all properties and \\prop_id will deliver 0 always (no properties attached).\n" + "Alternatively you can apply \\filter_properties " + "or \\map_properties to enable properties with a specific name key.\n" + "\n" + "Note that property filters/mappers are additive and act in addition (after) the currently installed filter.\n" + "So effectively after 'remove_properties' you cannot get them back.\n" + "\n" + "This feature has been introduced in version 0.28.4." + ) + + gsi::method_ext ("filter_properties", &filter_properties, gsi::arg ("keys"), + "@brief Filters properties by certain keys.\n" + "Calling this method will reduce the properties to values with name keys from the 'keys' list.\n" + "As a side effect, this method enables properties.\n" + "As with \\enable_properties or \\remove_properties, this filter has an effect on the value returned " + "by \\prop_id, not on the properties ID attached to the shape directly.\n" + "\n" + "Note that property filters/mappers are additive and act in addition (after) the currently installed filter.\n" + "\n" + "This feature has been introduced in version 0.28.4." + ) + + gsi::method_ext ("map_properties", &map_properties, gsi::arg ("key_map"), + "@brief Maps properties by name key.\n" + "Calling this method will reduce the properties to values with name keys from the 'keys' hash and " + "renames the properties. Property values with keys not listed in the key map will be removed.\n" + "As a side effect, this method enables properties.\n" + "As with \\enable_properties or \\remove_properties, this filter has an effect on the value returned " + "by \\prop_id, not on the properties ID attached to the shape directly.\n" + "\n" + "Note that property filters/mappers are additive and act in addition (after) the currently installed filter.\n" + "\n" + "This feature has been introduced in version 0.28.4." ), "@brief An iterator delivering shapes recursively\n" "\n" diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index 7e5ebecbd..2c166ee4f 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -323,6 +323,30 @@ module DRC def negative DRCNegative::new end + + def enable_props + DRCPropertySelector::new(:enable_properties) + end + + def remove_props + DRCPropertySelector::new(:remove_properties) + end + + def select_props(*keys) + self._context("select_props") do + keys.each do |k| + k.is_a?(String) || k.is_a?(1.class) || raise("Key values need to be integers or strings (got '#{k.inspect}')") + end + DRCPropertySelector::new(:filter_properties, keys) + end + end + + def map_props(hash) + self._context("map_props") do + hash.is_a?(Hash) || raise("Argument needs to be a hash (got '#{hash.inspect}')") + DRCPropertySelector::new(:map_properties, hash) + end + end def pattern(p) self._context("pattern") do @@ -2806,7 +2830,7 @@ CODE end end - def _input(layout, cell_index, layers, sel, box, clip, overlapping, shape_flags, global_trans, cls) + def _input(layout, cell_index, layers, sel, box, clip, overlapping, shape_flags, global_trans, prop_sel, cls) if layers.empty? && ! @deep @@ -2819,6 +2843,7 @@ CODE else iter = RBA::RecursiveShapeIterator::new(layout, layout.cell(cell_index), layers) end + prop_sel.each { |p| p.apply_to(iter) } iter.shape_flags = shape_flags iter.global_dtrans = global_trans diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index 70890edcb..2ff441a03 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -4783,37 +4783,35 @@ CODE # %DRC% # @name select_props - # @brief Enables or selects properties from original or property-annotated layers - # @synopsis layer.select_props + # @brief Enables or selects properties from a property-annotated layer # @synopsis layer.select_props(keys) # - # This method will enable user properties or select specific property keys - # from layers. It returns a new layer with properties enabled. The + # This method will select specific property keys + # from layers. It returns a new layer with the new properties. The # original layer is not modified. # - # When used on original layers, this method will enable properties on input. - # By default, properties are not read: - # - # @code - # layer1 = input(1, 0) - # layer1_with_props = input(1, 0).select_props - # @/code - # # You can specify the user property keys (names) to use. As user properties - # in general are a set of key/value pairs and may carry multiple information + # in general are a set of key/value pairs and may carry multiple values # under different keys, this feature can be handy to filter out a specific # aspect. To get only the values from key 1 (integer), use: # # @code - # layer1_with_props = input(1, 0).select_props(1) + # layer1 = input(1, 0, enable_properties) + # layer1_filtered = layer1.select_props(1) # @/code # # To get the combined key 1 and 2 properties, use: # # @code - # layer1_with_props = input(1, 0).select_props(1, 2) + # layer1 = input(1, 0, enable_properties) + # layer1_filtered = layer1.select_props(1, 2) # @/code # + # Without any arguments, this method will remove all properties. + # Note that you can directly filter or map properties on input + # which is more efficient than first loading all and then selecting some + # properties. See \DRCSource#input for details. + # # \map_props is a way to change property keys and \remove_props # will entirely remove all user properties. @@ -4822,7 +4820,7 @@ CODE # @brief Selects properties with certain keys and allows key mapping # @synopsis layer.map_props({ key => key_new, .. }) # - # Similar to \select_props, this method will enable user properties + # Similar to \select_props, this method will map or filter properties and # and take the values from certain keys. In addition, this method allows # mapping keys to new keys. Specify a hash argument with old to new keys. # @@ -4835,7 +4833,8 @@ CODE # use: # # @code - # layer1_with_props = input(1, 0).map_props({ 2 => 1 }) + # layer1 = input(1, 0, enable_properties) + # layer1_mapped = layer1.map_props({ 2 => 1 }) # @/code # # See also \select_props and \remove_props. diff --git a/src/drc/drc/built-in-macros/_drc_source.rb b/src/drc/drc/built-in-macros/_drc_source.rb index 2e969e463..6cade1e21 100644 --- a/src/drc/drc/built-in-macros/_drc_source.rb +++ b/src/drc/drc/built-in-macros/_drc_source.rb @@ -374,6 +374,7 @@ CODE # @synopsis source.input(layer, datatype) # @synopsis source.input(layer_into) # @synopsis source.input(filter, ...) + # @synopsis source.input(props_spec, ...) # Creates a layer with the shapes from the given layer of the source. # The layer can be specified by layer and optionally datatype, by a RBA::LayerInfo # object or by a sequence of filters. @@ -410,12 +411,27 @@ CODE # # "input" without any arguments will create a new, empty original layer. # + # If you want to use user properties - for example with properties constraints in DRC checks - + # you need to enable properties on input: + # + # @code + # input1_with_props = input(1, 0, enable_props) + # @/code + # + # You can also filter or map property keys, similar to the functions available on + # layers (\DRCLayer#map_props, \DRCLayer#select_props). For example to select + # property values with key 17 (numerical) only, use: + # + # @code + # input1_with_props = input(1, 0, select_props(17)) + # @/code + # # Use the global version of "input" without a source object to address the default source. def input(*args) @engine._context("input") do - layers = parse_input_layers(*args) - DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll, @global_trans, RBA::Region)) + layers, prop_selectors = parse_input_layers(*args) + DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll, @global_trans, prop_selectors, RBA::Region)) end end @@ -441,8 +457,8 @@ CODE def labels(*args) @engine._context("labels") do - layers = parse_input_layers(*args) - DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::STexts, @global_trans, RBA::Texts)) + layers, prop_selectors = parse_input_layers(*args) + DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::STexts, @global_trans, prop_selectors, RBA::Texts)) end end @@ -467,8 +483,8 @@ CODE def polygons(*args) @engine._context("polygons") do - layers = parse_input_layers(*args) - DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs, @global_trans, RBA::Region)) + layers, prop_selectors = parse_input_layers(*args) + DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs, @global_trans, prop_selectors, RBA::Region)) end end @@ -496,8 +512,8 @@ CODE def edges(*args) @engine._context("edges") do - layers = parse_input_layers(*args) - DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs | RBA::Shapes::SEdges, @global_trans, RBA::Edges)) + layers, prop_selectors = parse_input_layers(*args) + DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs | RBA::Shapes::SEdges, @global_trans, prop_selectors, RBA::Edges)) end end @@ -525,8 +541,8 @@ CODE def edge_pairs(*args) @engine._context("edge_pairs") do - layers = parse_input_layers(*args) - DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SEdgePairs, @global_trans, RBA::EdgePairs)) + layers, prop_selectors = parse_input_layers(*args) + DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SEdgePairs, @global_trans, prop_selectors, RBA::EdgePairs)) end end @@ -581,7 +597,10 @@ CODE def parse_input_layers(*args) layers = [] - + prop_selectors = args.select { |a| a.is_a?(DRCPropertySelector) } + + args = args.select { |a| !a.is_a?(DRCPropertySelector) } + if args.size == 0 li = @layout.insert_layer(RBA::LayerInfo::new) @@ -615,7 +634,7 @@ CODE end - layers + [ layers, prop_selectors ] end diff --git a/src/drc/drc/built-in-macros/_drc_tags.rb b/src/drc/drc/built-in-macros/_drc_tags.rb index 317f882b4..aabb84c52 100644 --- a/src/drc/drc/built-in-macros/_drc_tags.rb +++ b/src/drc/drc/built-in-macros/_drc_tags.rb @@ -158,6 +158,19 @@ module DRC def initialize end end + + # Property selector for "input" + class DRCPropertySelector + attr_accessor :method + attr_accessor :args + def apply_to(iter) + iter.send(self.method, *self.args) + end + def initialize(method, *args) + self.method = method + self.args = args + end + end # A wrapper for a pair of limit values # This class is used to identify projection limits for DRC diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc index 7622b3217..107aa58de 100644 --- a/src/drc/unit_tests/drcSimpleTests.cc +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -1401,3 +1401,8 @@ TEST(70_props) { run_test (_this, "70", false); } + +TEST(70d_props) +{ + run_test (_this, "70", true); +} diff --git a/testdata/drc/drcSimpleTests_70.drc b/testdata/drc/drcSimpleTests_70.drc index 57071a3e3..28c67a740 100644 --- a/testdata/drc/drcSimpleTests_70.drc +++ b/testdata/drc/drcSimpleTests_70.drc @@ -8,28 +8,37 @@ if $drc_test_deep deep end -l1 = input(1, 0) -l2 = input(2, 0) -l3 = input(3, 0) -l4 = input(4, 0) +# properties on input +l1 = input(1, 0) +l2 = input(2, 0) +l3_wp = input(3, 0, enable_props) +l3_wp1_input = input(3, 0, select_props(1)) +l3_wp2as1_input = input(3, 0, map_props({ 2 => 1 })) +l3 = input(3, 0) +l4_wp = input(4, 0, enable_props) +l4 = input(4, 0) + +# derived properties +l3_wp1 = l3_wp.select_props(1) +l3_wp2as1 = l3_wp.map_props({ 2 => 1 }) +l3_nowp = l3_wp.remove_props + +# dump to output l1.output(1, 0) l2.output(2, 0) l3.output(3, 0) l4.output(4, 0) -l3_wp = l3.select_props -l3_wp1 = l3.select_props(1) -l3_wp2as1 = l3.map_props({ 2 => 1 }) -l3_nowp = l3_wp.remove_props - -l4_wp = l4.select_props - l3_wp.output(10, 0) l3_wp1.output(11, 0) l3_wp2as1.output(12, 0) l3_nowp.output(13, 0) -l4_wp.output(14, 0) +l3_wp1_input.output(14, 0) +l3_wp2as1_input.output(15, 0) +l4_wp.output(16, 0) + +# booleans with properties constraints l3_wp.and(l4_wp, props_eq).output(20, 0) l3_wp1.and(l4_wp, props_eq).output(21, 0) diff --git a/testdata/drc/drcSimpleTests_au70.gds b/testdata/drc/drcSimpleTests_au70.gds index 8efd555a0..bd9736eb5 100644 Binary files a/testdata/drc/drcSimpleTests_au70.gds and b/testdata/drc/drcSimpleTests_au70.gds differ diff --git a/testdata/drc/drcSimpleTests_au70d.gds b/testdata/drc/drcSimpleTests_au70d.gds new file mode 100644 index 000000000..99d838d0d Binary files /dev/null and b/testdata/drc/drcSimpleTests_au70d.gds differ