From 407bbbcf62d43cb506f8e7388ce7ee6116824be8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 3 Jan 2026 01:21:30 +0100 Subject: [PATCH] More options on 'evaluate_nets' expressions, i.e. 'copy'. See documentation for details. --- src/db/db/dbLayoutToNetlist.cc | 108 ++++++++++++++----- src/db/db/dbLayoutToNetlist.h | 9 +- src/db/db/dbMeasureEval.cc | 120 +++++++++++++++++++-- src/db/db/dbMeasureEval.h | 15 +-- src/db/db/gsiDeclDbLayoutToNetlist.cc | 49 ++++++++- src/doc/doc/about/drc_ref_netter.xml | 56 ++++++++-- src/drc/drc/built-in-macros/_drc_netter.rb | 58 ++++++++-- src/tl/tl/tlExpression.cc | 17 ++- testdata/drc/drcSimpleTests_142.drc | 37 +++++-- testdata/drc/drcSimpleTests_au142.gds | Bin 3320 -> 5416 bytes testdata/drc/drcSimpleTests_au142d.gds | Bin 3050 -> 5146 bytes 11 files changed, 397 insertions(+), 72 deletions(-) diff --git a/src/db/db/dbLayoutToNetlist.cc b/src/db/db/dbLayoutToNetlist.cc index 043f1629c..a83234e2b 100644 --- a/src/db/db/dbLayoutToNetlist.cc +++ b/src/db/db/dbLayoutToNetlist.cc @@ -1800,40 +1800,87 @@ LayoutToNetlist::compute_area_and_perimeter_of_net_shapes (db::cell_index_type c } db::Point -LayoutToNetlist::get_merged_shapes_of_net (db::cell_index_type ci, size_t cid, unsigned int layer_id, db::Shapes &shapes, db::properties_id_type prop_id) const +LayoutToNetlist::get_shapes_of_net (db::cell_index_type ci, size_t cid, const std::vector &layer_ids, bool merge, size_t max_polygons, db::Shapes &shapes, db::properties_id_type prop_id) const { const db::Layout *layout = &dss ().const_layout (m_layout_index); - db::Point ref; + // count vertices and polygons and determine label reference point + + size_t n = 0, npoly = 0; bool any_ref = false; - db::EdgeProcessor ep; + db::Point ref; - // count vertices and reserve space - size_t n = 0; - for (db::recursive_cluster_shape_iterator rci (m_net_clusters, layer_id, ci, cid); !rci.at_end (); ++rci) { - n += rci->polygon_ref ().vertices (); - } - ep.reserve (n); + for (auto l = layer_ids.begin (); l != layer_ids.end (); ++l) { + for (db::recursive_cluster_shape_iterator rci (m_net_clusters, *l, ci, cid); !rci.at_end (); ++rci) { - size_t p = 0; - for (db::recursive_cluster_shape_iterator rci (m_net_clusters, layer_id, ci, cid); !rci.at_end (); ++rci) { - db::PolygonRef pr = rci->polygon_ref (); - db::PolygonRef::polygon_edge_iterator e = pr.begin_edge (); - if (! e.at_end ()) { - // pick one reference point for the label - auto p1 = (rci.trans () * *e).p1 (); - if (! any_ref || p1 < ref) { - ref = p1; - any_ref = true; + db::PolygonRef pr = rci->polygon_ref (); + + n += pr.vertices (); + ++npoly; + + db::PolygonRef::polygon_edge_iterator e = pr.begin_edge (); + if (! e.at_end ()) { + // pick one reference point for the label + auto p1 = (rci.trans () * *e).p1 (); + if (! any_ref || p1 < ref) { + ref = p1; + any_ref = true; + } } - ep.insert_with_trans (pr, rci.trans (), ++p); + } } - db::PolygonRefToShapesGenerator sg (const_cast (layout), &shapes, prop_id); - db::PolygonGenerator pg (sg, false); - db::SimpleMerge op; - ep.process (pg, op); + if (npoly >= max_polygons) { + + db::Box bbox; + + for (auto l = layer_ids.begin (); l != layer_ids.end (); ++l) { + for (db::recursive_cluster_shape_iterator rci (m_net_clusters, *l, ci, cid); !rci.at_end (); ++rci) { + db::PolygonRef pr = rci->polygon_ref (); + bbox += rci.trans () * pr.box (); + } + } + + if (prop_id != 0) { + shapes.insert (db::BoxWithProperties (bbox, prop_id)); + } else { + shapes.insert (bbox); + } + + } else if (merge) { + + db::EdgeProcessor ep; + ep.reserve (n); + + size_t p = 0; + for (auto l = layer_ids.begin (); l != layer_ids.end (); ++l) { + for (db::recursive_cluster_shape_iterator rci (m_net_clusters, *l, ci, cid); !rci.at_end (); ++rci) { + db::PolygonRef pr = rci->polygon_ref (); + db::PolygonRef::polygon_edge_iterator e = pr.begin_edge (); + if (! e.at_end ()) { + ep.insert_with_trans (pr, rci.trans (), ++p); + } + } + } + + db::PolygonRefToShapesGenerator sg (const_cast (layout), &shapes, prop_id); + db::PolygonGenerator pg (sg, false); + db::SimpleMerge op; + ep.process (pg, op); + + } else { + + db::PolygonRefToShapesGenerator sg (const_cast (layout), &shapes, prop_id); + + for (auto l = layer_ids.begin (); l != layer_ids.end (); ++l) { + for (db::recursive_cluster_shape_iterator rci (m_net_clusters, *l, ci, cid); !rci.at_end (); ++rci) { + db::PolygonRef pr = rci->polygon_ref (); + sg.put (pr.instantiate ().transformed (rci.trans ())); + } + } + + } return ref; } @@ -2017,7 +2064,9 @@ db::Region LayoutToNetlist::antenna_check (const db::Region &gate, double gate_a prop_id = db::properties_id (ps); } - db::Point ref = get_merged_shapes_of_net (*cid, *c, layer_of (metal), shapes, prop_id); + std::vector layers; + layers.push_back (layer_of (metal)); + db::Point ref = get_shapes_of_net (*cid, *c, layers, true, std::numeric_limits::max (), shapes, prop_id); if (values) { @@ -2074,7 +2123,9 @@ LayoutToNetlist::measure_net (const db::Region &primary, const std::mapfirst, v->second); } - eval.set_primary_layer (layer_of (primary)); + unsigned int primary_layer = layer_of (primary); + eval.set_primary_layer (primary_layer); + for (auto s = secondary.begin (); s != secondary.end (); ++s) { if (s->second) { eval.set_secondary_layer (s->first, layer_of (*s->second)); @@ -2088,7 +2139,6 @@ LayoutToNetlist::measure_net (const db::Region &primary, const std::map::max, + * the polygons will be replaced by a bounding box if the number of polygons exceeds the number given by the limit */ - db::Point get_merged_shapes_of_net (db::cell_index_type ci, size_t cid, unsigned int layer_id, db::Shapes &shapes, db::properties_id_type prop_id) const; + db::Point get_shapes_of_net (db::cell_index_type ci, size_t cid, const std::vector &layer_ids, bool merged, size_t max_polygons, db::Shapes &shapes, db::properties_id_type prop_id) const; private: // no copying diff --git a/src/db/db/dbMeasureEval.cc b/src/db/db/dbMeasureEval.cc index b2a29c10b..25426f5d6 100644 --- a/src/db/db/dbMeasureEval.cc +++ b/src/db/db/dbMeasureEval.cc @@ -385,10 +385,107 @@ public: virtual void execute (const tl::ExpressionParserContext &context, tl::Variant & /*out*/, const std::vector &args, const std::map * /*kwargs*/) const { - if (args.size () != 1) { - throw tl::EvalError (tl::to_string (tr ("'skip' function takes one argument (flag)")), context); + bool flag = true; + if (args.size () > 1) { + throw tl::EvalError (tl::to_string (tr ("'skip' function takes one optional argument (flag)")), context); + } else if (args.size () == 1) { + flag = args [0].to_bool (); } - mp_eval->skip_func (args [0].to_bool ()); + + std::vector layers; + if (! flag && ! mp_eval->layer_indexes ().empty ()) { + layers.push_back (0); + } + mp_eval->copy_func (layers, true, std::numeric_limits::max ()); + } + +private: + MeasureNetEval *mp_eval; +}; + +/** + * @brief A function to specify the copy behaviour + * + * With the copy behavior, the polygons emitted by the "evaluate_nets" method + * are specified. The function accepts up to one positional argument and + * three optional keyword arguments (limit, layers and merged). + * Together with "skip", it maps to the following behavior: + * + * skip() -> copy(layers=[]) + * skip(true) -> copy(layers=[]) + * skip(false) -> copy(layers=[primary], merged=true, limit=unlimited) + * copy() -> copy(layers=[all], merged=true, limit=unlimited) + * copy(false) -> copy(layers=[]) + * copy(true) -> copy(layers=[all], merged=true, limit=unlimited) + * copy(true, merged=m) -> copy(layers=[all], merged=m, limit=unlimited) + * copy(layers=l) -> copy(layers=[l], merged=true, limit=unlimited) (l is a layer symbol) + * copy(layers=[l]) -> copy(layers=[l], merged=true, limit=unlimited) ([l] is an array of layer symbols) + * copy(layers=.., merged=m) -> copy(layers=.., merged=m, limit=unlimited) + * copy(layers=.., merged=.., limit=n) -> copy(layers=.., merged=.., limit=n) + * + * The primary layer is "0", so "skip(false)" is identical to "copy(layer=0)" + */ + +class NetCopyFunction + : public tl::EvalFunction +{ +public: + NetCopyFunction (MeasureNetEval *eval) + : mp_eval (eval) + { + // .. nothing yet .. + } + + virtual bool supports_keyword_parameters () const { return true; } + + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant & /*out*/, const std::vector &args, const std::map *kwargs) const + { + bool flag = true; + size_t limit = std::numeric_limits::max (); + std::vector layers; + bool merged = true; + + if (args.size () > 1) { + throw tl::EvalError (tl::to_string (tr ("'copy' function takes one optional argument (flag) and the following keyword arguments: 'limit', 'layers', 'layer' or 'merged'")), context); + } else if (args.size () == 1) { + flag = args [0].to_bool (); + } + + // default for "layers" is 'all' + for (unsigned int i = 0; i < (unsigned int) mp_eval->layer_indexes ().size (); ++i) { + layers.push_back (i); + } + + if (kwargs) { + for (auto k = kwargs->begin (); k != kwargs->end (); ++k) { + if (k->first == "limit") { + limit = k->second.to (); + } else if (k->first == "merged") { + merged = k->second.to_bool (); + } else if (k->first == "layers") { + const tl::Variant &v = k->second; + if (! v.is_list ()) { + throw tl::EvalError (tl::to_string (tr ("'copy' function's 'layers' keyword argument expects an array of layer symbols")), context); + } + layers.clear (); + for (auto l = v.begin (); l != v.end (); ++l) { + layers.push_back (l->to_uint ()); + } + } else if (k->first == "layer") { + layers.clear (); + layers.push_back (k->second.to_uint ()); + } else { + throw tl::EvalError (tl::to_string (tr ("'copy' function takes one optional argument (flag) and the following keyword arguments: 'limit', 'layers', 'layer' or 'merged'")), context); + } + } + } + + if (! flag) { + // clear layers to indicate we don't want to copy + layers.clear (); + } + + mp_eval->copy_func (layers, merged, limit); } private: @@ -464,7 +561,8 @@ private: MeasureNetEval::MeasureNetEval (const db::LayoutToNetlist *l2n, double dbu) : tl::Eval (), mp_l2n (l2n), m_dbu (dbu) { - // .. nothing yet .. + m_copy_merge = false; + m_copy_max_polygons = std::numeric_limits::max (); } void @@ -486,6 +584,7 @@ MeasureNetEval::init () { define_function ("put", new NetPutFunction (this)); define_function ("skip", new NetSkipFunction (this)); + define_function ("copy", new NetCopyFunction (this)); define_function ("area", new NetAreaFunction (this)); define_function ("perimeter", new NetPerimeterFunction (this)); define_function ("net", new NetFunction (this)); @@ -494,7 +593,12 @@ MeasureNetEval::init () void MeasureNetEval::reset (db::cell_index_type cell_index, size_t cluster_id) const { - m_skip = false; + // default action: copy primary layer, merged, no limit + m_copy_layers.clear (); + m_copy_layers.push_back (0); + m_copy_merge = true; + m_copy_max_polygons = std::numeric_limits::max (); + m_cell_index = cell_index; m_cluster_id = cluster_id; m_area_and_perimeter_cache.clear (); @@ -553,9 +657,11 @@ MeasureNetEval::perimeter_func (int layer_index) const } void -MeasureNetEval::skip_func (bool f) const +MeasureNetEval::copy_func (const std::vector &layer_indexes, bool merge, size_t max_polygons) const { - m_skip = f; + m_copy_layers = layer_indexes; + m_copy_merge = merge; + m_copy_max_polygons = max_polygons; } tl::Variant diff --git a/src/db/db/dbMeasureEval.h b/src/db/db/dbMeasureEval.h index 0abbc0140..882d422fc 100644 --- a/src/db/db/dbMeasureEval.h +++ b/src/db/db/dbMeasureEval.h @@ -130,10 +130,9 @@ public: void reset (db::cell_index_type cell_index, size_t cluster_id) const; - bool skip () const - { - return m_skip; - } + const std::vector copy_layers () const { return m_copy_layers; } + size_t copy_max_polygons () const { return m_copy_max_polygons; } + bool copy_merge () const { return m_copy_merge; } db::PropertiesSet &prop_set_out () const { @@ -146,6 +145,7 @@ private: friend class NetPerimeterFunction; friend class NetFunction; friend class NetSkipFunction; + friend class NetCopyFunction; struct AreaAndPerimeter { @@ -156,7 +156,9 @@ private: const db::LayoutToNetlist *mp_l2n; double m_dbu; std::vector m_layers; - mutable bool m_skip; + mutable std::vector m_copy_layers; + mutable bool m_copy_merge; + mutable size_t m_copy_max_polygons; mutable db::PropertiesSet m_prop_set_out; mutable db::cell_index_type m_cell_index; mutable size_t m_cluster_id; @@ -164,11 +166,12 @@ private: mutable std::unique_ptr, const db::Net *> > m_nets_per_cell_and_cluster_id; AreaAndPerimeter compute_area_and_perimeter (int layer_index) const; + const std::vector &layer_indexes () const { return m_layers; } void put_func (const tl::Variant &name, const tl::Variant &value) const; tl::Variant area_func (int layer_index) const; tl::Variant perimeter_func (int layer_index) const; - void skip_func (bool f) const; + void copy_func (const std::vector &layer_indexes, bool merge, size_t max_polygons) const; tl::Variant net_func () const; }; diff --git a/src/db/db/gsiDeclDbLayoutToNetlist.cc b/src/db/db/gsiDeclDbLayoutToNetlist.cc index c5c6e38cb..34be0b7c8 100644 --- a/src/db/db/gsiDeclDbLayoutToNetlist.cc +++ b/src/db/db/gsiDeclDbLayoutToNetlist.cc @@ -1232,11 +1232,13 @@ Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", "arbitrary values without having to encode them into the expression string.\n" "\n" "It will look at nets connecting to shapes on the primary layer and execute the expression for each\n" - "of those nets. After that it will copy the primary shapes of the net to the output with the properties\n" + "of those nets. After that it will copy the merged primary shapes of the net to the output with the properties\n" "placed by 'put' attached to them.\n" "\n" "It is possible to skip primary shapes of a specific net by calling the 'skip' function with a 'true'\n" - "value.\n" + "value. It is also possible to configure the output in more detail, i.e. to skip other or all layers, to\n" + "replace the output by the net's bounding box above a certain complexity, or to select merged polygons or " + "unmerged ones, by using 'copy' instead of 'skip'. See below for more details.\n" "\n" "The expression may use the following functions:\n" "\n" @@ -1246,12 +1248,53 @@ Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", "@li 'perimeter': the perimeter of the primary-layer shapes on the net in um @/li\n" "@li 'perimeter(name)': the perimeter of the secondary-layer shapes. 'name' is a symbol with the name given in the secondary-layer map @/li\n" "@li 'put(name, value)': places the value as property 'name' on the output shapes @/li\n" - "@li 'skip(flag)': will skip the primary shapes of that net when called with a true value @/li\n" + "@li 'skip' or 'skip(flag)': will skip the primary shapes of that net when called with a true value or without one. See also 'copy'. @/li\n" + "@li 'copy(...)': see below for details @/li\n" "@li 'net': the \\Net object of the current net @/li\n" "@/ul\n" "\n" "If given, the 'dbu' argument gives the database unit to use for converting shape dimensions into micrometer units. " "If this value is 0, the area and perimeters are calculated in database units. If no DBU is specified, the value is determined automatically." + "\n" + "'copy' and 'skip' control the polygon output. Here are the options:\n" + "\n" + "@ul\n" + "@li 'skip' or 'skip(true)': skip output, identical to 'copy(layers=[])' @/li\n" + "@li 'skip(false)': copy the shapes from the primary layer, identical to 'copy(layer=0)' @/li\n" + "@li 'copy' or 'copy(true)': copy all shapes from the net, merged into a single polygon.\n" + " Note: this is not equivalent to 'skip(false)', as in the latter case, only the primary layer's\n" + " shapes are copied @/li\n" + "@li 'copy(false)': equivalent to 'skip(true)' @/li\n" + "@li 'copy(merged=false)': copies all shapes from all layers of the net, without merging.\n" + " 'merged' is a keyword argument that can be combined with other arguments. @/li\n" + "@li 'copy(limit=number)': if the net has less than 'number' polygons on the selected layers, \n" + " copy them to the output. For more polygons, emit the bounding box of the net for the \n" + " given layers.\n" + " 'limit' is a keyword argument that can be combined with other arguments. @/li\n" + "@li 'copy(layer=symbol)': copies all shapes from the layer denoted by the symbol.\n" + " The primary layer has value zero (0), so 'copy(layer=0)' copies the shapes from the primary layer.\n" + " 'layer' is a keyword argument that can be combined with other arguments, except 'layers'. @/li\n" + "@li 'copy(layers=[symbol, symbol, ...])': copies all shapes from the layers denoted by the symbols.\n" + " 'layers' is a keyword argument that can be combined with other arguments, except 'layer'. @/li\n" + "@/ul\n" + "\n" + "When mixing 'skip' and 'copy', the last active specification controls the output. The following\n" + "expressions are equivalent:\n" + "\n" + "@code\n" + "copy(net.name == 'VDD')\n" + "@/code\n" + "\n" + "and\n" + "\n" + "@code\n" + "skip ; net.name == 'VDD' && copy\n" + "@/code\n" + "\n" + "where the second expression establishes 'skip' as the default and conditionally executes 'copy',\n" + "overriding 'skip'.\n" + "\n" + "The 'copy' function was added and the 'skip' argument was made optional in version 0.30.6." ) + // test API gsi::method ("make_soft_connection_diodes=", &db::LayoutToNetlist::set_make_soft_connection_diodes, gsi::arg ("flag"), "@hide") + diff --git a/src/doc/doc/about/drc_ref_netter.xml b/src/doc/doc/about/drc_ref_netter.xml index 009b97ce4..5f557bd4a 100644 --- a/src/doc/doc/about/drc_ref_netter.xml +++ b/src/doc/doc/about/drc_ref_netter.xml @@ -355,8 +355,14 @@ It visits each net and evaluates the given expression on the net. The expression needs to be written in KLayout expression notations.

The default action is to copy the shapes of the primary layer to the -output. This action can be modified in some ways: skip shapes of -certain nets or attach properties to the shapes during the evaluation. +output. It is possible to customize the output further: you can +conditionally skip the output or copy all shapes of the net from +all layers the output. You can choose to emit individual polygons +or merge all polygons from a net (all layers or a subset) into +a single polygon. The latter is the default. +

+You can also choose to emit the bounding box of the net if the number of polygons +on the net exceeds a certain limit.

Using the "put" function inside the expression, properties can be attached to the output shapes. The properties can be computed using @@ -366,9 +372,6 @@ Also the Net object representing the net is av 'net' function. This allows implementing a more elaborate antenna check for example.

-Also, the expression can choose to drop shapes and not copy them to -the output by calling the "skip" function with a "true" argument. -

Arbitrary values can be passed as variables, which removes the need to encode variable values into the expression. For this, use the 'variables' argument and pass a hash with names and values. Each of @@ -379,7 +382,8 @@ The following functions are available inside the expressions:

  • "net" - the Net object of the current net
  • -
  • "skip(flag)" - if called with a 'true' argument, the primary layer's shapes are not copied for this net
  • +
  • "skip" or "skip(flag)" - if called with a 'true' argument (the default), the primary layer's shapes are not copied for this net
  • +
  • "copy(...)" - configures polygon output in a more elaborate way than "skip" (see below)
  • "put(name, value)" - places the value as a property with name 'name' (this must be a string) on the output shapes
  • "area" - the combined area of the primary layer's shapes on the net in square micrometer units
  • "area(symbol)" - the combined area of the secondary layer's shapes on the net in square micrometer units
  • @@ -390,6 +394,46 @@ The following functions are available inside the expressions: Here, 'symbol' is the name given to the secondary layer in the secondary layer dictionary.

    +"copy" and "skip" control the polygon output. Here are the options: +

    +

      +
    • "skip" or "skip(true): skip output, identical to "copy(layers=[])"
    • +
    • "skip(false)": copy the shapes from the primary layer, identical to "copy(layer=0)"
    • +
    • "copy" or "copy(true)": copy all shapes from the net, merged into a single polygon. +Note: this is not equivalent to "skip(false)", as in the latter case, only the primary layer's +shapes are copied
    • +
    • "copy(false)": equivalent to "skip(true)"
    • +
    • "copy(merged=false)": copies all shapes from all layers of the net, without merging. +"merged" is a keyword argument that can be combined with other arguments.
    • +
    • "copy(limit=number)": if the net has less than "number" polygons on the selected layers, +copy them to the output. For more polygons, emit the bounding box of the net for the +given layers. +"limit" is a keyword argument that can be combined with other arguments.
    • +
    • "copy(layer=symbol)": copies all shapes from the layer denoted by the symbol. +The primary layer has value zero (0), so "copy(layer=0)" copies the shapes from the primary layer. +"layer" is a keyword argument that can be combined with other arguments, except "layers".
    • +
    • "copy(layers=[symbol, symbol, ...])": copies all shapes from the layers denoted by the symbols. +"layers" is a keyword argument that can be combined with other arguments, except "layer".
    • +
    +

    +When mixing "skip" and "copy", the last active specification controls the output. The following +expressions are equivalent: +

    +

    +copy(net.name == "VDD")
    +
    +

    +and +

    +

    +skip ; net.name == "VDD" && copy
    +
    +

    +where the second expression establishes "skip" as the default and conditionally executes "copy", +overriding "skip". +

    +

    Antenna check example

    +

    The following example emulates an antenna check. It computes the area ratio of metal vs. gate area and attaches the value as a property with name 'AR' to the shapes, copied from the 'gate' layer:

    diff --git a/src/drc/drc/built-in-macros/_drc_netter.rb b/src/drc/drc/built-in-macros/_drc_netter.rb index eac15165d..efb9853ee 100644 --- a/src/drc/drc/built-in-macros/_drc_netter.rb +++ b/src/drc/drc/built-in-macros/_drc_netter.rb @@ -780,9 +780,15 @@ module DRC # The expression needs to be written in KLayout expression notations. # # The default action is to copy the shapes of the primary layer to the - # output. This action can be modified in some ways: skip shapes of - # certain nets or attach properties to the shapes during the evaluation. - # + # output. It is possible to customize the output further: you can + # conditionally skip the output or copy all shapes of the net from + # all layers the output. You can choose to emit individual polygons + # or merge all polygons from a net (all layers or a subset) into + # a single polygon. The latter is the default. + # + # You can also choose to emit the bounding box of the net if the number of polygons + # on the net exceeds a certain limit. + # # Using the "put" function inside the expression, properties can be # attached to the output shapes. The properties can be computed using # a number of net attributes - area and perimeter for example. @@ -791,9 +797,6 @@ module DRC # 'net' function. This allows implementing a more elaborate # antenna check for example. # - # Also, the expression can choose to drop shapes and not copy them to - # the output by calling the "skip" function with a "true" argument. - # # Arbitrary values can be passed as variables, which removes the need # to encode variable values into the expression. For this, use the # 'variables' argument and pass a hash with names and values. Each of @@ -804,7 +807,8 @@ module DRC # # @ul # @li "net" - the RBA::Net object of the current net @/li - # @li "skip(flag)" - if called with a 'true' argument, the primary layer's shapes are not copied for this net @/li + # @li "skip" or "skip(flag)" - if called with a 'true' argument (the default), the primary layer's shapes are not copied for this net @/li + # @li "copy(...)" - configures polygon output in a more elaborate way than "skip" (see below) @/li # @li "put(name, value)" - places the value as a property with name 'name' (this must be a string) on the output shapes @/li # @li "area" - the combined area of the primary layer's shapes on the net in square micrometer units @/li # @li "area(symbol)" - the combined area of the secondary layer's shapes on the net in square micrometer units @/li @@ -815,6 +819,46 @@ module DRC # Here, 'symbol' is the name given to the secondary layer in the secondary layer # dictionary. # + # "copy" and "skip" control the polygon output. Here are the options: + # + # @ul + # @li "skip" or "skip(true): skip output, identical to "copy(layers=[])" @/li + # @li "skip(false)": copy the shapes from the primary layer, identical to "copy(layer=0)" @/li + # @li "copy" or "copy(true)": copy all shapes from the net, merged into a single polygon. + # Note: this is not equivalent to "skip(false)", as in the latter case, only the primary layer's + # shapes are copied @/li + # @li "copy(false)": equivalent to "skip(true)" @/li + # @li "copy(merged=false)": copies all shapes from all layers of the net, without merging. + # "merged" is a keyword argument that can be combined with other arguments. @/li + # @li "copy(limit=number)": if the net has less than "number" polygons on the selected layers, + # copy them to the output. For more polygons, emit the bounding box of the net for the + # given layers. + # "limit" is a keyword argument that can be combined with other arguments. @/li + # @li "copy(layer=symbol)": copies all shapes from the layer denoted by the symbol. + # The primary layer has value zero (0), so "copy(layer=0)" copies the shapes from the primary layer. + # "layer" is a keyword argument that can be combined with other arguments, except "layers". @/li + # @li "copy(layers=[symbol, symbol, ...])": copies all shapes from the layers denoted by the symbols. + # "layers" is a keyword argument that can be combined with other arguments, except "layer". @/li + # @/ul + # + # When mixing "skip" and "copy", the last active specification controls the output. The following + # expressions are equivalent: + # + # @code + # copy(net.name == "VDD") + # @/code + # + # and + # + # @code + # skip ; net.name == "VDD" && copy + # @/code + # + # where the second expression establishes "skip" as the default and conditionally executes "copy", + # overriding "skip". + # + # @h4 Antenna check example @/h4 + # # The following example emulates an antenna check. It computes the area ratio of metal vs. gate area and # attaches the value as a property with name 'AR' to the shapes, copied from the 'gate' layer: # diff --git a/src/tl/tl/tlExpression.cc b/src/tl/tl/tlExpression.cc index bb42ad65b..cb64bfd3c 100644 --- a/src/tl/tl/tlExpression.cc +++ b/src/tl/tl/tlExpression.cc @@ -3993,9 +3993,20 @@ Eval::eval_atomic (ExpressionParserContext &ex, std::unique_ptr do { - std::unique_ptr v; - eval_top (ex, v); - n->add_child (v.release ()); + tl::Extractor exn = ex; + std::string name; + if (exn.try_read_word (name, "_") && exn.test ("=")) { + // keyword parameter -> read name again to skip it + ex.read_word (name, "_"); + ex.expect ("="); + } else { + name.clear (); + } + + std::unique_ptr a; + eval_assign (ex, a); + a->set_name (name); + n->add_child (a.release ()); if (ex.test (")")) { break; diff --git a/testdata/drc/drcSimpleTests_142.drc b/testdata/drc/drcSimpleTests_142.drc index a5be915aa..5ae12ea9f 100644 --- a/testdata/drc/drcSimpleTests_142.drc +++ b/testdata/drc/drcSimpleTests_142.drc @@ -17,19 +17,38 @@ connect(l2, l3) connect(l3, l4) connect(l4, l5) -l1_measured = evaluate_nets(l1, { "l2" => l2, "l3" => l3, "l4" => l4, "l5" => l5 }, "put(5, area(l5)); put(0, area); put(100, area(l5)/area)") -l2_measured = evaluate_nets(l1, {}, "put(0, area*factor)", { "factor" => 1000.0 }) -l3_measured = evaluate_nets(l1, {}, "put(0, net.name)") -l4_measured = evaluate_nets(l1, {}, "skip(net.name == n)", { "n" => "NET1" }) - l1.output(1, 0) l2.output(2, 0) l3.output(3, 0) l4.output(4, 0) l5.output(5, 0) -l1_measured.output(100, 0) -l2_measured.output(101, 0) -l3_measured.output(102, 0) -l4_measured.output(103, 0) +sec = { "l2" => l2, "l3" => l3, "l4" => l4, "l5" => l5 } +l100_measured = evaluate_nets(l1, sec, "put(5, area(l5)); put(0, area); put(100, area(l5)/area)") +l101_measured = evaluate_nets(l1, sec, "put(0, area*factor)", { "factor" => 1000.0 }) +l102_measured = evaluate_nets(l1, sec, "put(0, net.name)") +l103_measured = evaluate_nets(l1, sec, "skip(net.name == n)", { "n" => "NET1" }) +l104_measured = evaluate_nets(l1, sec, "copy(net.name == n)", { "n" => "NET1" }) +# default action is "skip", then copy primary if net name is "NET1" +l105_measured = evaluate_nets(l1, sec, "skip ; net.name == n && copy(layer = 0)", { "n" => "NET1" }) +l106_measured = evaluate_nets(l1, sec, "copy(net.name == n, merged = true)", { "n" => "NET1" }) +l107_measured = evaluate_nets(l1, sec, "copy(net.name == n, merged = false)", { "n" => "NET1" }) +l108_measured = evaluate_nets(l1, sec, "copy(net.name == n, merged = false, layers = [l4, l5])", { "n" => "NET1" }) +l109_measured = evaluate_nets(l1, sec, "copy(net.name == n, merged = false, layer = l5)", { "n" => "NET1" }) +l110_measured = evaluate_nets(l1, sec, "copy(net.name == n, merged = false, limit = 0)", { "n" => "NET1" }) +# default action is "skip", then copy all if net name is "NET1" +l111_measured = evaluate_nets(l1, sec, "skip ; net.name == n && copy", { "n" => "NET1" }) + +l100_measured.output(100, 0) +l101_measured.output(101, 0) +l102_measured.output(102, 0) +l103_measured.output(103, 0) +l104_measured.output(104, 0) +l105_measured.output(105, 0) +l106_measured.output(106, 0) +l107_measured.output(107, 0) +l108_measured.output(108, 0) +l109_measured.output(109, 0) +l110_measured.output(110, 0) +l111_measured.output(111, 0) diff --git a/testdata/drc/drcSimpleTests_au142.gds b/testdata/drc/drcSimpleTests_au142.gds index 3f17b1990a1a918c46436af436b77c48099caf41..62c69c6621d89cb9fba0d15d3e1f558322c834a4 100644 GIT binary patch delta 681 zcmew%xk4+7fsKKQDS|X%rVB=+C$Y5aOV`5-ns1acP|Np^(|Nl=0Os-=T)4K72 zfkE%V|Nk)hhXDhFc)|bw5SoF3{Q^iHL{HwwB&GpUHpc;?97LZCfSJa?BFMnN0<|r3 zat@@{HW z0=vv)4rXPL95YBXhXt$-OzNLw3{nIXK_~)pfI_)o siCkm}AcxgHI1d^cI*14X1tlc3=QzMfsKKQDS|{L4=vr&auACb78W*k6c#2@ z(8-@bX=R`w!NSCn!h#r!D=a8X)|odq?Cg8I6Ca--w#hHicNR2jLpc6sIF-D&8|#q*3Y;{e$_fH{)J zxy{iu&R%FEANbcb769~Z0~}j{Jrp})_PFDxH9xZh(9;V*cySk?WzC`35j8)f`RsXr z|7uM0vzkwSRfV2;V!Qr2obg=N#=@>&NzBT(95bzbrDJ@OuSddCVd4tK+eJ z(x38|^dtYvV}fFj#NYEzJkg(WpZJ1e7yaWZ%#A6(!tD3Ew_DbJ?XLc=j zb}*(?XLeDO%B(}N?<|42L^aM7O#g83o`ZbBl>1Q#8o?P0W&mrLx)+oe+A*s`J0#U& zPW>Uh(3zAPBsED;?1+c-qEBFr3cBZIs9aSHHPU+FdyHOO#k(*wshU&9S7sfGUGJnc z=%P{@C`(i@Mk9TpxXdSxeltbCSPZRO&DUU@>zJ>hh}JP*L$Sm8hRU_}+hCkLCI`--Z8Aoo#zWu+)lg(alN^=Iy$lY7tgm7cnf zoF23vYvmpYuSe{7Kj{4^e%(13%da!*H2?8`rsorO&Y^b z6i?58g8tj%C+NQw-`XF+`DFY(L1)ogL3-!$c>NOe@5+BPpLPGK&SOpe<=lnOli0Bj zjp0)*l0I4bW`e(I%)@O5=@mrqt6BPUAaJ>Hn{kQVJ*Ix&Hw$vfPnEqSwIe$eb z;z=LReY=c%>wl`xUjqFUKaqZl&-I(XQ@Bp+{9$|K`^Dq)KHa|S_7tC{L4=vr&auUcg#iHd78o}G