DRC binding for property computation, filtering

This commit is contained in:
Matthias Koefferlein 2025-08-03 13:55:14 +02:00
parent 2193f28e2a
commit c8568e8aed
20 changed files with 362 additions and 82 deletions

View File

@ -743,9 +743,17 @@ AsIfFlatEdges::filtered_pair (const EdgeFilterBase &filter) const
for (EdgesIterator p (begin_merged ()); ! p.at_end (); ++p) { for (EdgesIterator p (begin_merged ()); ! p.at_end (); ++p) {
if (filter.selected (*p, p.prop_id ())) { if (filter.selected (*p, p.prop_id ())) {
new_region_true->insert (*p); if (p.prop_id () != 0) {
new_region_true->insert (db::EdgeWithProperties (*p, p.prop_id ()));
} else {
new_region_true->insert (*p);
}
} else { } else {
new_region_false->insert (*p); if (p.prop_id () != 0) {
new_region_false->insert (db::EdgeWithProperties (*p, p.prop_id ()));
} else {
new_region_false->insert (*p);
}
} }
} }

View File

@ -526,9 +526,17 @@ AsIfFlatRegion::filtered_pair (const PolygonFilterBase &filter) const
for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) { for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) {
if (filter.selected (*p, p.prop_id ())) { if (filter.selected (*p, p.prop_id ())) {
new_region_true->insert (*p); if (p.prop_id () != 0) {
new_region_true->insert (db::PolygonWithProperties (*p, p.prop_id ()));
} else {
new_region_true->insert (*p);
}
} else { } else {
new_region_false->insert (*p); if (p.prop_id () != 0) {
new_region_false->insert (db::PolygonWithProperties (*p, p.prop_id ()));
} else {
new_region_false->insert (*p);
}
} }
} }

View File

@ -179,9 +179,17 @@ AsIfFlatTexts::filtered_pair (const TextFilterBase &filter) const
for (TextsIterator p (begin ()); ! p.at_end (); ++p) { for (TextsIterator p (begin ()); ! p.at_end (); ++p) {
if (filter.selected (*p, p.prop_id ())) { if (filter.selected (*p, p.prop_id ())) {
new_texts_true->insert (*p); if (p.prop_id () != 0) {
new_texts_true->insert (db::TextWithProperties (*p, p.prop_id ()));
} else {
new_texts_true->insert (*p);
}
} else { } else {
new_texts_false->insert (*p); if (p.prop_id () != 0) {
new_texts_false->insert (db::TextWithProperties (*p, p.prop_id ()));
} else {
new_texts_false->insert (*p);
}
} }
} }

View File

@ -2105,12 +2105,18 @@ LayoutToNetlist::measure_net (const db::Region &primary, const std::map<std::str
continue; continue;
} }
eval.reset (*cid, *c); try {
compiled_expr.execute ();
if (! eval.skip ()) { eval.reset (*cid, *c);
db::Shapes &shapes = ly.cell (*cid).shapes (dl.layer ()); compiled_expr.execute ();
get_merged_shapes_of_net (*cid, *c, primary_layer, shapes, db::properties_id (eval.prop_set_out ()));
if (! eval.skip ()) {
db::Shapes &shapes = ly.cell (*cid).shapes (dl.layer ());
get_merged_shapes_of_net (*cid, *c, primary_layer, shapes, db::properties_id (eval.prop_set_out ()));
}
} catch (tl::Exception &ex) {
tl::warn << ex.msg ();
} }
} }

View File

@ -51,6 +51,28 @@ private:
MeasureEval *mp_eval; MeasureEval *mp_eval;
}; };
class SkipFunction
: public tl::EvalFunction
{
public:
SkipFunction (MeasureEval *eval)
: mp_eval (eval)
{
// .. nothing yet ..
}
virtual void execute (const tl::ExpressionParserContext &context, tl::Variant & /*out*/, const std::vector<tl::Variant> &args, const std::map<std::string, tl::Variant> * /*kwargs*/) const
{
if (args.size () != 1) {
throw tl::EvalError (tl::to_string (tr ("'skip' function takes one argument (flag)")), context);
}
mp_eval->skip_func (args [0].to_bool ());
}
private:
MeasureEval *mp_eval;
};
class ValueFunction class ValueFunction
: public tl::EvalFunction : public tl::EvalFunction
{ {
@ -144,7 +166,7 @@ private:
// MeasureEval implementation // MeasureEval implementation
MeasureEval::MeasureEval (double dbu, bool with_put) MeasureEval::MeasureEval (double dbu, bool with_put)
: m_shape_type (None), m_prop_id (0), m_dbu (dbu), m_with_put (with_put) : m_shape_type (None), m_prop_id (0), m_skip (false), m_dbu (dbu), m_with_put (with_put)
{ {
mp_shape.any = 0; mp_shape.any = 0;
} }
@ -154,6 +176,7 @@ MeasureEval::init ()
{ {
if (m_with_put) { if (m_with_put) {
define_function ("put", new PutFunction (this)); define_function ("put", new PutFunction (this));
define_function ("skip", new SkipFunction (this));
} }
define_function ("shape", new ShapeFunction (this)); define_function ("shape", new ShapeFunction (this));
@ -161,14 +184,6 @@ MeasureEval::init ()
define_function ("values", new ValuesFunction (this)); define_function ("values", new ValuesFunction (this));
} }
void
MeasureEval::reset_shape () const
{
m_shape_type = None;
mp_shape.any = 0;
m_prop_id = 0;
}
void void
MeasureEval::set_shape (const db::Polygon *poly) const MeasureEval::set_shape (const db::Polygon *poly) const
{ {
@ -205,9 +220,10 @@ MeasureEval::set_shape (const db::Text *text) const
} }
void void
MeasureEval::set_prop_id (db::properties_id_type prop_id) const MeasureEval::reset (db::properties_id_type prop_id) const
{ {
m_prop_id = prop_id; m_prop_id = prop_id;
m_skip = false;
} }
void void
@ -223,6 +239,12 @@ MeasureEval::resolve_name (const std::string &name, const tl::EvalFunction *&fun
} }
} }
void
MeasureEval::skip_func (bool f) const
{
m_skip = f;
}
tl::Variant tl::Variant
MeasureEval::shape_func () const MeasureEval::shape_func () const
{ {

View File

@ -49,13 +49,17 @@ public:
void init (); void init ();
void reset_shape () const; void reset (db::properties_id_type prop_id) const;
void set_shape (const db::Polygon *poly) const; void set_shape (const db::Polygon *poly) const;
void set_shape (const db::PolygonRef *poly) const; void set_shape (const db::PolygonRef *poly) const;
void set_shape (const db::Edge *edge) const; void set_shape (const db::Edge *edge) const;
void set_shape (const db::EdgePair *edge_pair) const; void set_shape (const db::EdgePair *edge_pair) const;
void set_shape (const db::Text *text) const; void set_shape (const db::Text *text) const;
void set_prop_id (db::properties_id_type prop_id) const;
bool skip () const
{
return m_skip;
}
db::PropertiesSet &prop_set_out () const db::PropertiesSet &prop_set_out () const
{ {
@ -67,6 +71,7 @@ protected:
private: private:
friend class ShapeFunction; friend class ShapeFunction;
friend class SkipFunction;
friend class ValueFunction; friend class ValueFunction;
friend class ValuesFunction; friend class ValuesFunction;
friend class PropertyFunction; friend class PropertyFunction;
@ -95,6 +100,7 @@ private:
mutable ShapeType m_shape_type; mutable ShapeType m_shape_type;
mutable ShapeRef mp_shape; mutable ShapeRef mp_shape;
mutable db::properties_id_type m_prop_id; mutable db::properties_id_type m_prop_id;
mutable bool m_skip;
mutable db::PropertiesSet m_prop_set_out; mutable db::PropertiesSet m_prop_set_out;
double m_dbu; double m_dbu;
bool m_with_put; bool m_with_put;
@ -104,6 +110,7 @@ private:
tl::Variant value_func (const tl::Variant &name) const; tl::Variant value_func (const tl::Variant &name) const;
tl::Variant values_func (const tl::Variant &name) const; tl::Variant values_func (const tl::Variant &name) const;
void put_func (const tl::Variant &name, const tl::Variant &value) const; void put_func (const tl::Variant &name, const tl::Variant &value) const;
void skip_func (bool f) const;
}; };
/** /**

View File

@ -300,13 +300,14 @@ Class<property_computation_processor<db::EdgePairProcessorBase, db::EdgePairs> >
"is attached to the output edge pairs as user properties with the given names.\n" "is attached to the output edge pairs as user properties with the given names.\n"
"\n" "\n"
"Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties " "Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties "
"to the output shape.\n" "to the output shape. You can also use 'skip' to drop shapes in that case.\n"
"\n" "\n"
"The expression may use the following variables and functions:\n" "The expression may use the following variables and functions:\n"
"\n" "\n"
"@ul\n" "@ul\n"
"@li @b shape @/b: The current shape (i.e. 'EdgePair' without DBU specified or 'DEdgePair' otherwise) @/li\n" "@li @b shape @/b: The current shape (i.e. 'EdgePair' without DBU specified or 'DEdgePair' otherwise) @/li\n"
"@li @b put(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n" "@li @b put(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n"
"@li @b skip(<flag>) @/b: If called with a 'true' value, the shape is dropped from the output @/li\n"
"@li @b value(<name>) @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n" "@li @b value(<name>) @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n"
"@li @b values(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n" "@li @b values(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n"
"@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n" "@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n"

View File

@ -305,13 +305,14 @@ Class<property_computation_processor<db::EdgeProcessorBase, db::Edges> > decl_Ed
"is attached to the output edge pairs as user properties with the given names.\n" "is attached to the output edge pairs as user properties with the given names.\n"
"\n" "\n"
"Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties " "Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties "
"to the output shape.\n" "to the output shape. You can also use 'skip' to drop shapes in that case.\n"
"\n" "\n"
"The expression may use the following variables and functions:\n" "The expression may use the following variables and functions:\n"
"\n" "\n"
"@ul\n" "@ul\n"
"@li @b shape @/b: The current shape (i.e. 'Edge' without DBU specified or 'DEdge' otherwise) @/li\n" "@li @b shape @/b: The current shape (i.e. 'Edge' without DBU specified or 'DEdge' otherwise) @/li\n"
"@li @b put(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n" "@li @b put(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n"
"@li @b skip(<flag>) @/b: If called with a 'true' value, the shape is dropped from the output @/li\n"
"@li @b value(<name>) @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n" "@li @b value(<name>) @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n"
"@li @b values(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n" "@li @b values(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n"
"@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n" "@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n"

View File

@ -121,36 +121,44 @@ public:
virtual void process (const db::object_with_properties<shape_type> &shape, std::vector<db::object_with_properties<shape_type> > &res) const virtual void process (const db::object_with_properties<shape_type> &shape, std::vector<db::object_with_properties<shape_type> > &res) const
{ {
res.push_back (shape); try {
m_eval.set_prop_id (shape.properties_id ()); m_eval.reset (shape.properties_id ());
m_eval.set_shape (&shape); m_eval.set_shape (&shape);
db::PropertiesSet &ps_out = m_eval.prop_set_out ();
if (m_copy_properties) {
ps_out = db::properties (shape.properties_id ());
for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) {
if (e->first != db::property_names_id_type (0)) {
ps_out.erase (e->first);
}
}
} else {
ps_out.clear ();
}
db::PropertiesSet &ps_out = m_eval.prop_set_out ();
if (m_copy_properties) {
ps_out = db::properties (shape.properties_id ());
for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) { for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) {
if (e->first != db::property_names_id_type (0)) { if (e->first != db::property_names_id_type (0)) {
ps_out.erase (e->first); ps_out.insert (e->first, e->second.execute ());
} }
} }
} else {
ps_out.clear ();
}
for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) { for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) {
if (e->first != db::property_names_id_type (0)) { if (e->first == db::property_names_id_type (0)) {
ps_out.insert (e->first, e->second.execute ()); e->second.execute ();
}
} }
}
for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) { if (! m_eval.skip ()) {
if (e->first == db::property_names_id_type (0)) { res.push_back (shape);
e->second.execute (); res.back ().properties_id (db::properties_id (ps_out));
} }
}
res.back ().properties_id (db::properties_id (ps_out)); } catch (tl::Exception &ex) {
tl::warn << ex.msg ();
res.clear ();
}
} }
public: public:
@ -190,21 +198,35 @@ public:
virtual bool selected (const shape_type &shape, db::properties_id_type prop_id) const virtual bool selected (const shape_type &shape, db::properties_id_type prop_id) const
{ {
m_eval.set_prop_id (prop_id); try {
m_eval.set_shape (&shape);
bool res = m_expression.execute ().to_bool (); m_eval.reset (prop_id);
return m_inverse ? !res : res; m_eval.set_shape (&shape);
bool res = m_expression.execute ().to_bool ();
return m_inverse ? !res : res;
} catch (tl::Exception &ex) {
tl::warn << ex.msg ();
return false;
}
} }
// only needed for PolygonFilterBase // only needed for PolygonFilterBase
virtual bool selected (const db::PolygonRef &shape, db::properties_id_type prop_id) const virtual bool selected (const db::PolygonRef &shape, db::properties_id_type prop_id) const
{ {
m_eval.set_prop_id (prop_id); try {
m_eval.set_shape (&shape);
bool res = m_expression.execute ().to_bool (); m_eval.reset (prop_id);
return m_inverse ? !res : res; m_eval.set_shape (&shape);
bool res = m_expression.execute ().to_bool ();
return m_inverse ? !res : res;
} catch (tl::Exception &ex) {
tl::warn << ex.msg ();
return false;
}
} }
public: public:

View File

@ -320,13 +320,14 @@ Class<property_computation_processor<db::PolygonProcessorBase, db::Region> > dec
"is attached to the output edge pairs as user properties with the given names.\n" "is attached to the output edge pairs as user properties with the given names.\n"
"\n" "\n"
"Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties " "Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties "
"to the output shape.\n" "to the output shape. You can also use 'skip' to drop shapes in that case.\n"
"\n" "\n"
"The expression may use the following variables and functions:\n" "The expression may use the following variables and functions:\n"
"\n" "\n"
"@ul\n" "@ul\n"
"@li @b shape @/b: The current shape (i.e. 'Polygon' without DBU specified or 'DPolygon' otherwise) @/li\n" "@li @b shape @/b: The current shape (i.e. 'Polygon' without DBU specified or 'DPolygon' otherwise) @/li\n"
"@li @b put(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n" "@li @b put(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n"
"@li @b skip(<flag>) @/b: If called with a 'true' value, the shape is dropped from the output @/li\n"
"@li @b value(<name>) @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n" "@li @b value(<name>) @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n"
"@li @b values(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n" "@li @b values(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n"
"@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n" "@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n"

View File

@ -299,13 +299,14 @@ Class<property_computation_processor<db::TextProcessorBase, db::Texts> > decl_Te
"is attached to the output edge pairs as user properties with the given names.\n" "is attached to the output edge pairs as user properties with the given names.\n"
"\n" "\n"
"Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties " "Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties "
"to the output shape.\n" "to the output shape. You can also use 'skip' to drop shapes in that case.\n"
"\n" "\n"
"The expression may use the following variables and functions:\n" "The expression may use the following variables and functions:\n"
"\n" "\n"
"@ul\n" "@ul\n"
"@li @b shape @/b: The current shape (i.e. 'Text' without DBU specified or 'DText' otherwise) @/li\n" "@li @b shape @/b: The current shape (i.e. 'Text' without DBU specified or 'DText' otherwise) @/li\n"
"@li @b put(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n" "@li @b put(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n"
"@li @b skip(<flag>) @/b: If called with a 'true' value, the shape is dropped from the output @/li\n"
"@li @b value(<name>) @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n" "@li @b value(<name>) @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n"
"@li @b values(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n" "@li @b values(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n"
"@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n" "@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n"

View File

@ -453,6 +453,19 @@ module DRC
DRCPropertyName::new(k) DRCPropertyName::new(k)
end end
end end
def aniso(expression)
DRCTransformationVariantHint::new(expression, :is_scale_invariant)
end
def scales(expression)
DRCTransformationVariantHint::new(expression, :is_isotropic)
end
def aniso_and_scales(expression)
DRCTransformationVariantHint::new(expression, nil)
end
# %DRC% # %DRC%
# @brief Specifies "same properties" for operations supporting user properties constraints # @brief Specifies "same properties" for operations supporting user properties constraints

View File

@ -5176,7 +5176,7 @@ CODE
# %DRC% # %DRC%
# @name evaluate # @name evaluate
# @brief Evaluate expressions on the shapes of the layer # @brief Evaluate expressions on the shapes of the layer
# @synopsis layer.evaluate(expression [, variables]) # @synopsis layer.evaluate(expression [, variables [, keep_properties ]])
# #
# Evaluates the given expression on the shapes of the layer. # Evaluates the given expression on the shapes of the layer.
# The expression needs to be written in the KLayout expressions # The expression needs to be written in the KLayout expressions
@ -5185,12 +5185,14 @@ CODE
# The expressions can place properties on the shapes using the # The expressions can place properties on the shapes using the
# "put" function. As input, the expressions will receive the # "put" function. As input, the expressions will receive the
# (merged) shapes in micrometer units (e.g. RBA::DPolygon type) # (merged) shapes in micrometer units (e.g. RBA::DPolygon type)
# by calling the "shape" function. # by calling the "shape" function. You can also call 'skip' with
# a 'true' value to drop the shape from the output entirely.
# #
# Available functions are: # Available functions are:
# #
# @ul # @ul
# @li "put(name, value)": creates a property with name 'name' and 'value' value @/li # @li "put(name, value)": creates a property with the given name and value @/li
# @li "skip(flag)": if called with a 'true' value, the shape will be dropped from the output @/li
# @li "shape": the current shape in micrometer units @/li # @li "shape": the current shape in micrometer units @/li
# @li "value(name)": the value of a property with name 'name' or nil if the current shape does not have a property with this name @/li # @li "value(name)": the value of a property with name 'name' or nil if the current shape does not have a property with this name @/li
# @ul # @ul
@ -5202,50 +5204,106 @@ CODE
# becomes available as variables in the expression. This eliminates the need # becomes available as variables in the expression. This eliminates the need
# to build expression strings to pass variable values. # to build expression strings to pass variable values.
# #
# The following example computes the area of the shapes and puts them # If 'keep_properties' is true, the existing properties of the shape will be kept.
# into a property 'AREA': # Otherwise (the default), existing properties will be removed before adding new
# ones with 'put'.
#
# The expressions require a hint
# whether the they make use of anisotropic or scale-dependent properties.
# For example, the height of a box is an anisotropic property. If a check is made
# inside a rotated cell, the height transforms to a width and the check renders
# different results for the same cell if the cell is placed rotated and non-rotated.
# The solution is cell variant formation.
#
# Similarly, if a check is made against physical dimensions, the check will have
# different results for cells placed with different magnifications. Such a check
# is not scale-invariant.
#
# By default it is assumed that the expressions are isotropic and scale invariant.
# You can mark an expression as anisotropic and/or scale dependent using the following
# expression modifiers:
# #
# @code # @code
# layer.evalute("put('AREA', shape.area)") # # isotropic and scale invariant
# layer.evaluate("put('holes', shape.holes)")
#
# # anisotropic, but scale invariant
# layer.evaluate(aniso("put('aspect_ratio', shape.bbox.height/shape.bbox.width)"))
#
# # isotropic, but not scale invariant
# layer.evaluate(scales("put('area', shape.area)"))
#
# # anisotropic and not scale invariant
# layer.evaluate(aniso_and_scales("put('width', shape.bbox.width)"))
# @/code # @/code
# #
# If you forget to specify this hint, the expression will use the local
# shape properties and fail to correctly produce the results in the presence
# of deep mode and rotated or magnified cell instances.
#
# The following example computes the area of the shapes and puts them
# into a property 'area':
#
# @code
# layer.evaluate(scales("put('area', shape.area)"))
# @/code
#
# NOTE: GDS does not support properties with string names, so
# either save to OASIS or use integer numbers for the property names.
#
# This version modifies the input layer. A version that returns # This version modifies the input layer. A version that returns
# a new layer is \evaluated. # a new layer is \evaluated.
# %DRC% # %DRC%
# @name evaluated # @name evaluated
# @brief Evaluate expressions on the shapes of the layer and returns a new layer # @brief Evaluate expressions on the shapes of the layer and returns a new layer
# @synopsis layer.evaluated(expression [, variables]) # @synopsis layer.evaluated(expression [, variables [, keep_properties]])
# #
# This method is the out-of-place version of \evaluate. # This method is the out-of-place version of \evaluate.
def _make_proc(expression, variables) def _make_proc(expression, variables, keep_properties)
expression.is_a?(String) || raise("'expression' must be a string") if expression.is_a?(String)
expression = DRCTransformationVariantHint::new(expression)
elsif expression.is_a?(DRCTransformationVariantHint)
expression.expression.is_a?(String) || raise("'expression' must be a string")
else
raise("'expression' must be a string or a string decorated with a transformation variant hint")
end
variables.is_a?(Hash) || raise("'variables' must be a hash") variables.is_a?(Hash) || raise("'variables' must be a hash")
if data.is_a?(RBA::Region) if data.is_a?(RBA::Region)
RBA::PolygonPropertiesExpressions::new(data, expression, dbu: @engine.dbu, variables: variables) pr = RBA::PolygonPropertiesExpressions::new(data, expression.expression, copy_properties: keep_properties, dbu: @engine.dbu, variables: variables)
elsif data.is_a?(RBA::Edges) elsif data.is_a?(RBA::Edges)
RBA::EdgePropertiesExpressions::new(data, expression, dbu: @engine.dbu, variables: variables) pr = RBA::EdgePropertiesExpressions::new(data, expression.expression, copy_properties: keep_properties, dbu: @engine.dbu, variables: variables)
elsif data.is_a?(RBA::EdgePairs) elsif data.is_a?(RBA::EdgePairs)
RBA::EdgePairPropertiesExpressions::new(data, expression, dbu: @engine.dbu, variables: variables) pr = RBA::EdgePairPropertiesExpressions::new(data, expression.expression, copy_properties: keep_properties, dbu: @engine.dbu, variables: variables)
elsif data.is_a?(RBA::Texts) elsif data.is_a?(RBA::Texts)
RBA::TextPropertiesExpressions::new(data, expression, dbu: @engine.dbu, variables: variables) pr = RBA::TextPropertiesExpressions::new(data, expression.expression, copy_properties: keep_properties, dbu: @engine.dbu, variables: variables)
else else
nil pr = nil
end end
pr && expression.apply(pr)
pr
end end
def evaluate(expression, variables = {}) def evaluate(expression, variables = {}, keep_properties = false)
@engine._context("evaluate") do @engine._context("evaluate") do
pr = _make_proc(expression, variables) pr = _make_proc(expression, variables, keep_properties)
@engine._tcmd(self.data, 0, self.data.class, :process, pr) @engine._tcmd(self.data, 0, self.data.class, :process, pr)
self self
end end
end end
def evaluated(expression, variables = {}, keep_properties = false)
@engine._context("evaluated") do
pr = _make_proc(expression, variables, keep_properties)
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, self.data.class, :processed, pr))
end
end
# %DRC% # %DRC%
# @name select_if # @name select_if
# @brief Selects shapes of a layer based on the evaluation of an expression # @brief Selects shapes of a layer based on the evaluation of an expression
@ -5255,6 +5313,8 @@ CODE
# If the evaluation gives a 'true' value, the shape is selected. Otherwise # If the evaluation gives a 'true' value, the shape is selected. Otherwise
# it is discarded. # it is discarded.
# #
# The expression is written in KLayout expression notation.
#
# As input, the expressions will receive the # As input, the expressions will receive the
# (merged) shapes in micrometer units (e.g. RBA::DPolygon type) # (merged) shapes in micrometer units (e.g. RBA::DPolygon type)
# by calling the "shape" function. # by calling the "shape" function.
@ -5269,6 +5329,39 @@ CODE
# Properties with well-formed names (e.g. "VALUE") are available as # Properties with well-formed names (e.g. "VALUE") are available as
# variables in the expressions as a shortcut. # variables in the expressions as a shortcut.
# #
# The expressions require a hint
# whether the they make use of anisotropic or scale-dependent properties.
# For example, the height of a box is an anisotropic property. If a check is made
# inside a rotated cell, the height transforms to a width and the check renders
# different results for the same cell if the cell is placed rotated and non-rotated.
# The solution is cell variant formation.
#
# Similarly, if a check is made against physical dimensions, the check will have
# different results for cells placed with different magnifications. Such a check
# is not scale-invariant.
#
# By default it is assumed that the expressions are isotropic and scale invariant.
# You can mark an expression as anisotropic and/or scale dependent using the following
# expression modifiers:
#
# @code
# # isotropic and scale invariant
# layer.select_if("shape.holes > 0")
#
# # anisotropic, but scale invariant
# layer.select_if(aniso("shape.bbox.height/shape.bbox.width > 2"))
#
# # isotropic, but not scale invariant
# layer.select_if(scales("shape.area > 10.0"))
#
# # anisotropic and not scale invariant
# layer.select_if(aniso_and_scales("shape.bbox.width > 10.0"))
# @/code
#
# If you forget to specify this hint, the expression will use the local
# shape properties and fail to correctly produce the results in the presence
# of deep mode and rotated or magnified cell instances.
#
# 'variables' can be a hash of arbitrary names and values. Each of these values # 'variables' can be a hash of arbitrary names and values. Each of these values
# becomes available as variables in the expression. This eliminates the need # becomes available as variables in the expression. This eliminates the need
# to build expression strings to pass variable values. # to build expression strings to pass variable values.
@ -5277,7 +5370,7 @@ CODE
# less than 10 square micrometers: # less than 10 square micrometers:
# #
# @code # @code
# layer.select("shape.area < 10.0") # layer.select(scales("shape.area < 10.0"))
# @/code # @/code
# #
# This version modifies the input layer. A version that returns # This version modifies the input layer. A version that returns
@ -5302,30 +5395,39 @@ CODE
# less than 10 square micrometers and one with the shapes with a bigger area: # less than 10 square micrometers and one with the shapes with a bigger area:
# #
# @code # @code
# (smaller, bigger) = layer.split_if("shape.area < 10.0") # (smaller, bigger) = layer.split_if(scales("shape.area < 10.0"))
# @/code # @/code
def _make_filter(expression, variables) def _make_filter(expression, variables)
expression.is_a?(String) || raise("'expression' must be a string") if expression.is_a?(String)
expression = DRCTransformationVariantHint::new(expression)
elsif expression.is_a?(DRCTransformationVariantHint)
expression.expression.is_a?(String) || raise("'expression' must be a string")
else
raise("'expression' must be a string or a string decorated with a transformation variant hint")
end
variables.is_a?(Hash) || raise("'variables' must be a hash") variables.is_a?(Hash) || raise("'variables' must be a hash")
if data.is_a?(RBA::Region) if data.is_a?(RBA::Region)
RBA::PolygonFilterBase::expression_filter(expression, variables: variables) f = RBA::PolygonFilterBase::expression_filter(expression.expression, dbu: @engine.dbu, variables: variables)
elsif data.is_a?(RBA::Edges) elsif data.is_a?(RBA::Edges)
RBA::EdgeFilterBase::expression_filter(expression, variables: variables) f = RBA::EdgeFilterBase::expression_filter(expression.expression, dbu: @engine.dbu, variables: variables)
elsif data.is_a?(RBA::EdgePairs) elsif data.is_a?(RBA::EdgePairs)
RBA::EdgeFilterBase::expression_filter(expression, variables: variables) f = RBA::EdgeFilterBase::expression_filter(expression.expression, dbu: @engine.dbu, variables: variables)
elsif data.is_a?(RBA::Texts) elsif data.is_a?(RBA::Texts)
RBA::TextFilterBase::expression_filter(expression, variables: variables) f = RBA::TextFilterBase::expression_filter(expression.expression, dbu: @engine.dbu, variables: variables)
else else
nil f = nil
end end
f && expression.apply(f)
f
end end
def select_if(expression, variables = {}) def select_if(expression, variables = {})
@engine._context("evaluate") do @engine._context("select_if") do
f = _make_filter(expression, variables) f = _make_filter(expression, variables)
@engine._tcmd(self.data, 0, self.data.class, :filter, f) @engine._tcmd(self.data, 0, self.data.class, :filter, f)
self self
@ -5333,16 +5435,17 @@ CODE
end end
def selected_if(expression, variables = {}) def selected_if(expression, variables = {})
@engine._context("evaluated") do @engine._context("selected_if") do
f = _make_filter(expression, variables) f = _make_filter(expression, variables)
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, self.data.class, :filtered, f)) DRCLayer::new(@engine, @engine._tcmd(self.data, 0, self.data.class, :filtered, f))
end end
end end
def split_if(expression, variables = {}) def split_if(expression, variables = {})
@engine._context("evaluated") do @engine._context("split_if") do
f = _make_filter(expression, variables) f = _make_filter(expression, variables)
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, self.data.class, :split_filter, f)) res = @engine._tcmd(self.data, 0, self.data.class, :split_filter, f)
[ DRCLayer::new(@engine, res[0]), DRCLayer::new(@engine, res[1]) ]
end end
end end

View File

@ -833,6 +833,9 @@ module DRC
# #
# antenna_errors = evaluate_nets(gate, { "MET" => metal }, expression, variables) # antenna_errors = evaluate_nets(gate, { "MET" => metal }, expression, variables)
# @/code # @/code
#
# NOTE: GDS does not support properties with string names, so
# either save to OASIS or use integer numbers for the property names.
def evaluate_nets(primary, secondary, expression, variables = {}) def evaluate_nets(primary, secondary, expression, variables = {})

View File

@ -575,5 +575,20 @@ module DRC
end end
end end
class DRCTransformationVariantHint
def initialize(expression, mode = :is_isotropic_and_scale_invariant)
@expression = expression
@mode = mode
end
def expression
@expression
end
def apply(pr)
if @mode
pr.send(@mode)
end
end
end
end end

View File

@ -2024,3 +2024,13 @@ TEST(142d_evaluate_nets)
run_test (_this, "142", true); run_test (_this, "142", true);
} }
TEST(143_evaluate_and_filter)
{
run_test (_this, "143", false);
}
TEST(143d_evaluate_and_filter)
{
run_test (_this, "143", true);
}

51
testdata/drc/drcSimpleTests_143.drc vendored Normal file
View File

@ -0,0 +1,51 @@
source $drc_test_source
target $drc_test_target
if $drc_test_deep
deep
end
l1 = input(1, 0, enable_props)
l2 = input(2, 0, enable_props)
l1.output(1, 0)
l2.output(2, 0)
l2.evaluated("put(2, to_f(value(17))*2.0)").output(10, 0)
l2.evaluated("put(2, to_f(value(17))*factor)", { "factor" => 3.0 }, true).output(11, 0)
l1.evaluated(aniso("put(2, shape.bbox.height/shape.bbox.width)")).output(12, 0)
l1.evaluated(scales("put(2, shape.area)")).output(13, 0)
l1.evaluated(aniso_and_scales("put(2, shape.bbox.width)")).output(14, 0)
d = l2.dup
d.evaluate("put(2, to_f(value(17))*2.0)").output(20, 0)
d = l2.dup
d.evaluate("put(2, to_f(value(17))*factor)", { "factor" => 3.0 }, true).output(21, 0)
d = l1.dup
d.evaluate(aniso("put(2, shape.bbox.height/shape.bbox.width)")).output(22, 0)
d = l1.dup
d.evaluate(scales("put(2, shape.area)")).output(23, 0)
d = l1.dup
d.evaluate(aniso_and_scales("put(2, shape.bbox.width)")).output(24, 0)
l2.selected_if("to_f(value(17))<3.0").output(30, 0)
l2.selected_if("to_f(value(17))<thr)", { "thr" => 3.0 }).output(31, 0)
l1.selected_if(aniso("shape.bbox.height/shape.bbox.width<1.0")).output(32, 0)
l1.selected_if(scales("shape.area<20.0")).output(33, 0)
l1.selected_if(aniso_and_scales("shape.bbox.width<=5")).output(34, 0)
d = l2.dup
d.select_if("to_f(value(17))<3.0").output(40, 0)
d = l2.dup
d.select_if("to_f(value(17))<thr)", { "thr" => 3.0 }).output(41, 0)
d = l1.dup
d.select_if(aniso("shape.bbox.height/shape.bbox.width<1.0")).output(42, 0)
d = l1.dup
d.select_if(scales("shape.area<20.0")).output(43, 0)
d = l1.dup
d.select_if(aniso_and_scales("shape.bbox.width<=5")).output(44, 0)
l2.split_if("to_f(value(17))<3.0")[0].output(50, 0)
l2.split_if("to_f(value(17))<3.0")[1].output(51, 0)

BIN
testdata/drc/drcSimpleTests_143.gds vendored Normal file

Binary file not shown.

BIN
testdata/drc/drcSimpleTests_au143.gds vendored Normal file

Binary file not shown.

BIN
testdata/drc/drcSimpleTests_au143d.gds vendored Normal file

Binary file not shown.