From 9c607d7663c7c136c64c1a4c2326ee6b685f8b50 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 30 Dec 2018 22:37:31 +0100 Subject: [PATCH] Added a first version of the layout to netlist extraction feature The main entry point is RBA::LayoutToNetlist which is the GSI binding for the layout to netlist extractor. For a first impression about the abilities of this extractor see the Ruby tests in testdata/ruby/dbLayoutToNetlist.rb. The framework itself consists of many classes, specifically - RBA::Netlist for the netlist representation - RBA::DeviceClass and superclasses (e.g. RBA::DeviceClassResistor and RBA::DeviceClassMOS3Transistor) for the description of devices. - RBA::DeviceExtractor and superclasses (i.e. RBA::DeviceExtractorMOS3Transistor or the generic RBA::GenericDeviceExtractor) for the implementation of the device extraction. - RBA::Connectivity for the description of inter- and intra-layer connections. --- src/db/db/db.pro | 3 +- src/db/db/dbLayoutToNetlist.cc | 103 ++++++- src/db/db/dbLayoutToNetlist.h | 17 +- src/db/db/dbNetlist.cc | 88 +++--- src/db/db/dbNetlist.h | 13 + src/db/db/gsiDeclDbLayoutToNetlist.cc | 212 ++++++++++++++ src/db/db/gsiDeclDbNetlist.cc | 5 + src/db/db/gsiDeclDbNetlistDeviceExtractor.cc | 48 ++-- src/db/unit_tests/dbLayoutToNetlistTests.cc | 33 +++ src/rba/unit_tests/rba.cc | 1 + testdata/ruby/dbLayoutToNetlist.rb | 287 +++++++++++++++++++ testdata/ruby/dbNetlistDeviceClasses.rb | 2 +- 12 files changed, 733 insertions(+), 79 deletions(-) create mode 100644 src/db/db/gsiDeclDbLayoutToNetlist.cc create mode 100644 testdata/ruby/dbLayoutToNetlist.rb diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 13ac344f1..9d78787ca 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -152,7 +152,8 @@ SOURCES = \ gsiDeclDbNetlistDeviceExtractor.cc \ gsiDeclDbHierNetworkProcessor.cc \ dbNetlistDeviceExtractorClasses.cc \ - dbLayoutToNetlist.cc + dbLayoutToNetlist.cc \ + gsiDeclDbLayoutToNetlist.cc HEADERS = \ dbArray.h \ diff --git a/src/db/db/dbLayoutToNetlist.cc b/src/db/db/dbLayoutToNetlist.cc index f9b0f8c72..4ac11e41c 100644 --- a/src/db/db/dbLayoutToNetlist.cc +++ b/src/db/db/dbLayoutToNetlist.cc @@ -53,16 +53,31 @@ void LayoutToNetlist::set_threads (int n) m_dss.set_threads (n); } +int LayoutToNetlist::threads () const +{ + return m_dss.threads (); +} + void LayoutToNetlist::set_area_ratio (double ar) { m_dss.set_max_area_ratio (ar); } +double LayoutToNetlist::area_ratio () const +{ + return m_dss.max_area_ratio (); +} + void LayoutToNetlist::set_max_vertex_count (size_t n) { m_dss.set_max_vertex_count (n); } +size_t LayoutToNetlist::max_vertex_count () const +{ + return m_dss.max_vertex_count (); +} + db::Region *LayoutToNetlist::make_layer (unsigned int layer_index) { db::RecursiveShapeIterator si (m_iter); @@ -233,25 +248,32 @@ db::Net *LayoutToNetlist::probe_net (const db::Region &of_region, const db::DPoi return probe_net (of_region, db::CplxTrans (internal_layout ()->dbu ()).inverted () * point); } -size_t LayoutToNetlist::search_net (const db::ICplxTrans &trans, const db::Cell *cell, const db::local_cluster &test_cluster, db::cell_index_type &cell_index_found) +size_t LayoutToNetlist::search_net (const db::ICplxTrans &trans, const db::Cell *cell, const db::local_cluster &test_cluster, std::vector &rev_inst_path) { - db::Box local_box = trans.inverted () * test_cluster.bbox (); + db::Box local_box = trans * test_cluster.bbox (); const db::local_clusters &lcc = net_clusters ().clusters_per_cell (cell->cell_index ()); for (db::local_clusters::touching_iterator i = lcc.begin_touching (local_box); ! i.at_end (); ++i) { const db::local_cluster &lc = *i; if (lc.interacts (test_cluster, trans, m_conn)) { - cell_index_found = cell->cell_index (); return lc.id (); } } for (db::Cell::touching_iterator i = cell->begin_touching (local_box); ! i.at_end (); ++i) { - db::ICplxTrans t = trans * i->complex_trans (); - size_t cluster_id = search_net (t, &internal_layout ()->cell (i->cell_index ()), test_cluster, cell_index_found); - if (cluster_id > 0) { - return cluster_id; + + for (db::CellInstArray::iterator ia = i->begin_touching (local_box, internal_layout ()); ! ia.at_end (); ++ia) { + + db::ICplxTrans trans_inst = i->complex_trans (*ia); + db::ICplxTrans t = trans_inst.inverted () * trans; + size_t cluster_id = search_net (t, &internal_layout ()->cell (i->cell_index ()), test_cluster, rev_inst_path); + if (cluster_id > 0) { + rev_inst_path.push_back (db::InstElement (*i, ia)); + return cluster_id; + } + } + } return 0; @@ -264,6 +286,9 @@ db::Net *LayoutToNetlist::probe_net (const db::Region &of_region, const db::Poin } tl_assert (mp_netlist.get ()); + db::CplxTrans dbu_trans (internal_layout ()->dbu ()); + db::VCplxTrans dbu_trans_inv = dbu_trans.inverted (); + unsigned int layer = layer_of (of_region); // Prepare a test cluster @@ -272,15 +297,69 @@ db::Net *LayoutToNetlist::probe_net (const db::Region &of_region, const db::Poin db::local_cluster test_cluster; test_cluster.add (db::PolygonRef (db::Polygon (box), sr), layer); - db::cell_index_type ci = 0; - size_t cluster_id = search_net (db::ICplxTrans (), internal_top_cell (), test_cluster, ci); + std::vector inst_path; + + size_t cluster_id = search_net (db::ICplxTrans (), internal_top_cell (), test_cluster, inst_path); if (cluster_id > 0) { - db::Circuit *circuit = mp_netlist->circuit_by_cell_index (ci); - tl_assert (circuit != 0); + // search_net delivers the path in reverse order + std::reverse (inst_path.begin (), inst_path.end ()); + + std::vector cell_indexes; + cell_indexes.reserve (inst_path.size () + 1); + cell_indexes.push_back (internal_top_cell ()->cell_index ()); + for (std::vector::const_iterator i = inst_path.begin (); i != inst_path.end (); ++i) { + cell_indexes.push_back (i->inst_ptr.cell_index ()); + } + + db::Circuit *circuit = mp_netlist->circuit_by_cell_index (cell_indexes.back ()); + if (! circuit) { + // the circuit has probably been optimized away + return 0; + } db::Net *net = circuit->net_by_cluster_id (cluster_id); - tl_assert (net != 0); + if (! net) { + // the net has probably been optimized away + return 0; + } + + // follow the path up in the net hierarchy using the transformation and the upper cell index as the + // guide line + while (! inst_path.empty () && circuit->is_external_net (net)) { + + cell_indexes.pop_back (); + + db::Pin *pin = 0; + for (db::Circuit::pin_iterator p = circuit->begin_pins (); p != circuit->end_pins () && ! pin; ++p) { + if (circuit->net_for_pin (p->id ()) == net) { + pin = p.operator-> (); + } + } + tl_assert (pin != 0); + + db::DCplxTrans dtrans = dbu_trans * inst_path.back ().complex_trans () * dbu_trans_inv; + + // try to find a parent circuit which connects to this net + db::Circuit *upper_circuit = 0; + db::Net *upper_net = 0; + for (db::Circuit::refs_iterator r = circuit->begin_refs (); r != circuit->end_refs () && ! upper_net; ++r) { + if (r->trans ().equal (dtrans) && r->circuit () && r->circuit ()->cell_index () == cell_indexes.back ()) { + upper_net = r->net_for_pin (pin->id ()); + upper_circuit = r->circuit (); + } + } + + if (upper_net) { + circuit = upper_circuit; + net = upper_net; + inst_path.pop_back (); + } else { + break; + } + + } + return net; } else { diff --git a/src/db/db/dbLayoutToNetlist.h b/src/db/db/dbLayoutToNetlist.h index d1678e433..4963439f2 100644 --- a/src/db/db/dbLayoutToNetlist.h +++ b/src/db/db/dbLayoutToNetlist.h @@ -85,6 +85,11 @@ public: */ void set_threads (int n); + /** + * @brief Gets the number of threads to use + */ + int threads () const; + /** * @brief Sets the area_ratio parameter for the hierarchical network processor * This parameter controls splitting of large polygons in order to reduce the @@ -92,6 +97,11 @@ public: */ void set_area_ratio (double ar); + /** + * @brief Gets the area ratio + */ + double area_ratio () const; + /** * @brief Sets the max_vertex_count parameter for the hierarchical network processor * This parameter controls splitting of large polygons in order to enhance performance @@ -99,6 +109,11 @@ public: */ void set_max_vertex_count (size_t n); + /** + * @brief Gets the max vertex count + */ + size_t max_vertex_count () const; + /** * @brief Creates a new region representing an original layer * "layer_index" is the layer index of the desired layer in the original layout. @@ -240,7 +255,7 @@ private: std::set m_dlrefs; bool m_netlist_extracted; - size_t search_net (const db::ICplxTrans &trans, const db::Cell *cell, const db::local_cluster &test_cluster, db::cell_index_type &cell_index_found); + size_t search_net (const db::ICplxTrans &trans, const db::Cell *cell, const db::local_cluster &test_cluster, std::vector &rev_inst_path); }; } diff --git a/src/db/db/dbNetlist.cc b/src/db/db/dbNetlist.cc index 392814066..909cf7953 100644 --- a/src/db/db/dbNetlist.cc +++ b/src/db/db/dbNetlist.cc @@ -446,6 +446,15 @@ void Net::set_name (const std::string &name) } } +std::string Net::qname () const +{ + if (circuit ()) { + return circuit ()->name () + ":" + expanded_name (); + } else { + return expanded_name (); + } +} + std::string Net::expanded_name () const { if (name ().empty ()) { @@ -531,7 +540,9 @@ Circuit::Circuit () m_net_by_name (this, &Circuit::begin_nets, &Circuit::end_nets), m_index (0) { - // .. nothing yet .. + m_devices.changed ().add (this, &Circuit::devices_changed); + m_nets.changed ().add (this, &Circuit::nets_changed); + m_subcircuits.changed ().add (this, &Circuit::subcircuits_changed); } Circuit::Circuit (const Circuit &other) @@ -544,6 +555,10 @@ Circuit::Circuit (const Circuit &other) m_net_by_name (this, &Circuit::begin_nets, &Circuit::end_nets), m_index (0) { + m_devices.changed ().add (this, &Circuit::devices_changed); + m_nets.changed ().add (this, &Circuit::nets_changed); + m_subcircuits.changed ().add (this, &Circuit::subcircuits_changed); + operator= (other); } @@ -551,14 +566,9 @@ Circuit &Circuit::operator= (const Circuit &other) { if (this != &other) { - m_name = other.m_name; - m_device_by_id.invalidate (); - m_subcircuit_by_id.invalidate (); - m_net_by_cluster_id.invalidate (); - m_device_by_name.invalidate (); - m_subcircuit_by_name.invalidate (); - m_net_by_name.invalidate (); + clear (); + m_name = other.m_name; m_pins = other.m_pins; std::map device_table; @@ -630,6 +640,28 @@ const Pin *Circuit::pin_by_name (const std::string &name) const return 0; } +void Circuit::devices_changed () +{ + m_device_by_id.invalidate (); + m_device_by_name.invalidate (); +} + +void Circuit::subcircuits_changed () +{ + m_subcircuit_by_id.invalidate (); + m_subcircuit_by_name.invalidate (); + + if (mp_netlist) { + mp_netlist->invalidate_topology (); + } +} + +void Circuit::nets_changed () +{ + m_net_by_cluster_id.invalidate (); + m_net_by_name.invalidate (); +} + void Circuit::clear () { m_name.clear (); @@ -637,12 +669,6 @@ void Circuit::clear () m_devices.clear (); m_nets.clear (); m_subcircuits.clear (); - m_device_by_id.invalidate (); - m_subcircuit_by_id.invalidate (); - m_net_by_cluster_id.invalidate (); - m_device_by_name.invalidate (); - m_subcircuit_by_name.invalidate (); - m_net_by_name.invalidate (); } void Circuit::set_name (const std::string &name) @@ -720,15 +746,11 @@ void Circuit::add_net (Net *net) { m_nets.push_back (net); net->set_circuit (this); - m_net_by_cluster_id.invalidate (); - m_net_by_name.invalidate (); } void Circuit::remove_net (Net *net) { m_nets.erase (net); - m_net_by_cluster_id.invalidate (); - m_net_by_name.invalidate (); } void Circuit::add_device (Device *device) @@ -743,15 +765,11 @@ void Circuit::add_device (Device *device) device->set_id (id + 1); m_devices.push_back (device); - m_device_by_id.invalidate (); - m_device_by_name.invalidate (); } void Circuit::remove_device (Device *device) { m_devices.erase (device); - m_device_by_id.invalidate (); - m_device_by_name.invalidate (); } void Circuit::add_subcircuit (SubCircuit *subcircuit) @@ -766,23 +784,11 @@ void Circuit::add_subcircuit (SubCircuit *subcircuit) subcircuit->set_id (id + 1); m_subcircuits.push_back (subcircuit); - m_subcircuit_by_id.invalidate (); - m_subcircuit_by_name.invalidate (); - - if (mp_netlist) { - mp_netlist->invalidate_topology (); - } } void Circuit::remove_subcircuit (SubCircuit *subcircuit) { m_subcircuits.erase (subcircuit); - m_subcircuit_by_id.invalidate (); - m_subcircuit_by_name.invalidate (); - - if (mp_netlist) { - mp_netlist->invalidate_topology (); - } } void Circuit::register_ref (SubCircuit *r) @@ -1182,6 +1188,7 @@ Netlist::Netlist () m_circuit_by_cell_index (this, &Netlist::begin_circuits, &Netlist::end_circuits) { m_circuits.changed ().add (this, &Netlist::invalidate_topology); + m_circuits.changed ().add (this, &Netlist::circuits_changed); } Netlist::Netlist (const Netlist &other) @@ -1191,6 +1198,7 @@ Netlist::Netlist (const Netlist &other) { operator= (other); m_circuits.changed ().add (this, &Netlist::invalidate_topology); + m_circuits.changed ().add (this, &Netlist::circuits_changed); } Netlist &Netlist::operator= (const Netlist &other) @@ -1222,6 +1230,12 @@ Netlist &Netlist::operator= (const Netlist &other) return *this; } +void Netlist::circuits_changed () +{ + m_circuit_by_cell_index.invalidate (); + m_circuit_by_name.invalidate (); +} + void Netlist::invalidate_topology () { if (m_valid_topology) { @@ -1465,24 +1479,18 @@ void Netlist::clear () { m_device_classes.clear (); m_circuits.clear (); - m_circuit_by_name.invalidate (); - m_circuit_by_cell_index.invalidate (); } void Netlist::add_circuit (Circuit *circuit) { m_circuits.push_back (circuit); circuit->set_netlist (this); - m_circuit_by_name.invalidate (); - m_circuit_by_cell_index.invalidate (); } void Netlist::remove_circuit (Circuit *circuit) { circuit->set_netlist (0); m_circuits.erase (circuit); - m_circuit_by_name.invalidate (); - m_circuit_by_cell_index.invalidate (); } void Netlist::add_device_class (DeviceClass *device_class) diff --git a/src/db/db/dbNetlist.h b/src/db/db/dbNetlist.h index 54b6a5ab1..9c4909200 100644 --- a/src/db/db/dbNetlist.h +++ b/src/db/db/dbNetlist.h @@ -459,6 +459,14 @@ public: */ std::string expanded_name () const; + /** + * @brief Gets the qualified name + * + * The qualified name is like the expanded name, but preceeded with the + * Circuit name if known (e.g. "CIRCUIT:NET") + */ + std::string qname () const; + /** * @brief Sets the cluster ID of this net * @@ -1578,6 +1586,10 @@ private: void set_netlist (Netlist *netlist); bool combine_parallel_devices (const db::DeviceClass &cls); bool combine_serial_devices (const db::DeviceClass &cls); + + void devices_changed (); + void subcircuits_changed (); + void nets_changed (); }; /** @@ -2269,6 +2281,7 @@ private: void invalidate_topology (); void validate_topology (); + void circuits_changed (); const tl::vector &child_circuits (Circuit *circuit); const tl::vector &parent_circuits (Circuit *circuit); diff --git a/src/db/db/gsiDeclDbLayoutToNetlist.cc b/src/db/db/gsiDeclDbLayoutToNetlist.cc new file mode 100644 index 000000000..900c906f5 --- /dev/null +++ b/src/db/db/gsiDeclDbLayoutToNetlist.cc @@ -0,0 +1,212 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "gsiDecl.h" +#include "dbLayoutToNetlist.h" + +namespace gsi +{ + +static db::LayoutToNetlist *make_l2n (const db::RecursiveShapeIterator &iter) +{ + return new db::LayoutToNetlist (iter); +} + +static db::Layout *l2n_internal_layout (db::LayoutToNetlist *l2n) +{ + // although this isn't very clean, we dare to do so as const references are pretty useless in script languages. + return const_cast (l2n->internal_layout ()); +} + +static db::Cell *l2n_internal_top_cell (db::LayoutToNetlist *l2n) +{ + // although this isn't very clean, we dare to do so as const references are pretty useless in script languages. + return const_cast (l2n->internal_top_cell ()); +} + +Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", + gsi::constructor ("new", &make_l2n, gsi::arg ("iter"), + "@brief The constructor\n" + "See the class description for details.\n" + ) + + gsi::method ("threads=", &db::LayoutToNetlist::set_threads, gsi::arg ("n"), + "@brief Sets the number of threads to use for operations which support multiple threads\n" + ) + + gsi::method ("threads", &db::LayoutToNetlist::threads, + "@brief Gets the number of threads to use for operations which support multiple threads\n" + ) + + gsi::method ("area_ratio=", &db::LayoutToNetlist::set_area_ratio, gsi::arg ("r"), + "@brief Sets the area_ratio parameter for the hierarchical network processor\n" + "This parameter controls splitting of large polygons in order to reduce the\n" + "error made by the bounding box approximation.\n" + ) + + gsi::method ("area_ratio", &db::LayoutToNetlist::area_ratio, + "@brief Gets the area_ratio parameter for the hierarchical network processor\n" + "See \\area_ratio= for details about this attribute." + ) + + gsi::method ("max_vertex_count=", &db::LayoutToNetlist::set_max_vertex_count, gsi::arg ("n"), + "@brief Sets the max_vertex_count parameter for the hierarchical network processor\n" + "This parameter controls splitting of large polygons in order to enhance performance\n" + "for very big polygons.\n" + ) + + gsi::method ("max_vertex_count", &db::LayoutToNetlist::max_vertex_count, + "See \\max_vertex_count= for details about this attribute." + ) + + gsi::method ("make_layer", &db::LayoutToNetlist::make_layer, gsi::arg ("layer_index"), + "@brief Creates a new region representing an original layer\n" + "'layer_index'' is the layer index of the desired layer in the original layout.\n" + "The Region object returned is a new object and must be deleted by the caller.\n" + "This variant produces polygons and takes texts for net name annotation.\n" + "A variant not taking texts is \\make_polygon_layer. A Variant only taking\n" + "texts is \\make_text_layer.\n""" + ) + + gsi::method ("make_text_layer", &db::LayoutToNetlist::make_text_layer, gsi::arg ("layer_index"), + "@brief Creates a new region representing an original layer taking texts only\n" + "See \\make_layer for details.\n" + ) + + gsi::method ("make_polygon_layer", &db::LayoutToNetlist::make_polygon_layer, gsi::arg ("layer_index"), + "@brief Creates a new region representing an original layer taking polygons and texts\n" + "See \\make_layer for details.\n" + ) + + gsi::method ("extract_devices", &db::LayoutToNetlist::extract_devices, gsi::arg ("extractor"), gsi::arg ("layers"), + "@brief Extracts devices\n" + "See the class description for more details.\n" + "This method will run device extraction for the given extractor. The layer map is specific\n" + "for the extractor and uses the region objects derived with \\make_layer and it's variants.\n" + "\n" + "In addition, derived regions can be passed too. Certain limitations apply. It's safe to use\n" + "boolean operations for deriving layers. Other operations are applicable as long as they are\n" + "capable of delivering hierarchical layers.\n" + "\n" + "If errors occur, the device extractor will contain theses errors.\n" + ) + + gsi::method ("connect", (void (db::LayoutToNetlist::*) (const db::Region &)) &db::LayoutToNetlist::connect, gsi::arg ("l"), + "@brief Defines an intra-layer connection for the given layer.\n" + "The layer is either an original layer created with \\make_layer and it's variants or\n" + "a derived layer. Certain limitations apply. It's safe to use\n" + "boolean operations for deriving layers. Other operations are applicable as long as they are\n" + "capable of delivering hierarchical layers.\n" + ) + + gsi::method ("connect", (void (db::LayoutToNetlist::*) (const db::Region &, const db::Region &)) &db::LayoutToNetlist::connect, gsi::arg ("a"), gsi::arg ("b"), + "@brief Defines an inter-layer connection for the given layers.\n" + "The conditions mentioned with intra-layer \\connect apply for this method too.\n" + ) + + gsi::method ("extract_netlist", &db::LayoutToNetlist::extract_netlist, + "@brief Runs the netlist extraction\n" + "See the class description for more details.\n" + ) + + gsi::method_ext ("internal_layout", &l2n_internal_layout, + "@brief Gets the internal layout\n" + "Usually it should not be required to obtain the internal layout. If you need to do so, make sure not to modify the layout as\n" + "the functionality of the netlist extractor depends on it." + ) + + gsi::method_ext ("internal_top_cell", &l2n_internal_top_cell, + "@brief Gets the internal top cell\n" + "Usually it should not be required to obtain the internal cell. If you need to do so, make sure not to modify the cell as\n" + "the functionality of the netlist extractor depends on it." + ) + + gsi::method ("layer_of", &db::LayoutToNetlist::layer_of, gsi::arg ("l"), + "@brief Gets the internal layer for a given extraction layer\n" + "This method is required to derive the internal layer index - for example for\n" + "investigating the cluster tree.\n" + ) + + gsi::method ("cell_mapping_into", &db::LayoutToNetlist::cell_mapping_into, gsi::arg ("layout"), gsi::arg ("cell"), + "@brief Creates a cell mapping for copying shapes from the internal layout to the given target layout.\n" + "CAUTION: may create new cells in 'layout'.\n" + ) + + gsi::method ("const_cell_mapping_into", &db::LayoutToNetlist::const_cell_mapping_into, gsi::arg ("layout"), gsi::arg ("cell"), + "@brief Creates a cell mapping for copying shapes from the internal layout to the given target layout.\n" + "This version will not create new cells in the target layout.\n" + "If the required cells do not exist there yet, flatting will happen.\n" + ) + + gsi::method ("netlist", &db::LayoutToNetlist::netlist, + "@brief gets the netlist extracted (0 if no extraction happened yet)\n" + ) + + gsi::method ("shapes_of_net", &db::LayoutToNetlist::shapes_of_net, gsi::arg ("net"), gsi::arg ("of_layer"), gsi::arg ("recursive"), + "@brief Returns all shapes of a specific net and layer.\n" + "If 'recursive'' is true, the returned region will contain the shapes of\n" + "all subcircuits too.\n" + ) + + gsi::method ("probe_net", (db::Net *(db::LayoutToNetlist::*) (const db::Region &, const db::DPoint &)) &db::LayoutToNetlist::probe_net, gsi::arg ("of_layer"), gsi::arg ("point"), + "@brief Finds the net by probing a specific location on the given layer\n" + "\n" + "This method will find a net looking at the given layer at the specific position.\n" + "It will traverse the hierarchy below if no shape in the requested layer is found\n" + "in the specified location. The function will report the topmost net from far above the\n" + "hierarchy of circuits as possible.\n" + "\n" + "If no net is found at all, 0 is returned.\n" + "\n" + "It is recommended to use \\probe on the netlist right after extraction.\n" + "Optimization functions such as \\Netlist#purge will remove parts of the net which means\n" + "shape to net probing may no longer work for these nets.\n" + "\n" + "This variant accepts a micrometer-unit location. The location is given in the\n" + "coordinate space of the initial cell.\n" + ) + + gsi::method ("probe_net", (db::Net *(db::LayoutToNetlist::*) (const db::Region &, const db::Point &)) &db::LayoutToNetlist::probe_net, gsi::arg ("of_layer"), gsi::arg ("point"), + "@brief Finds the net by probing a specific location on the given layer\n" + "See the description of the other \\probe_net variant.\n" + "This variant accepts a database-unit location. The location is given in the\n" + "coordinate space of the initial cell.\n" + ), + "@brief A generic framework for extracting netlists from layouts\n" + "\n" + "This class wraps various concepts from db::NetlistExtractor and db::NetlistDeviceExtractor\n" + "and more. It is supposed to provide a framework for extracting a netlist from a layout.\n" + "\n" + "The use model of this class consists of five steps which need to be executed in this order.\n" + "\n" + "@ul\n" + "@li Configuration: in this step, the LayoutToNetlist object is created and\n" + " if required, configured. Methods to be used in this step are \\threads=,\n" + " \\area_ratio= or \\max_vertex_count=. The constructor for the LayoutToNetlist\n" + " object receives a \\RecursiveShapeIterator object which basically supplies the\n" + " hierarchy and the layout taken as input.\n" + "@/li\n" + "@li Preparation\n" + " In this step, the device recognitions and extraction layers are drawn from\n" + " the framework. Derived can now be computed using boolean operations.\n" + " Methods to use in this step are \\make_layer and it's variants.\n" + " Layer preparation is not necessarily required to happen before all\n" + " other steps. Layers can be computed shortly before they are required.\n" + "@/li\n" + "@li Following the preparation, the devices can be extracted using \\extract_devices.\n" + " This method needs to be called for each device extractor required. Each time,\n" + " a device extractor needs to be given plus a map of device layers. The device\n" + " layers are device extractor specific. Either original or derived layers\n" + " may be specified here. Layer preparation may happen between calls to \\extract_devices.\n" + "@/li\n" + "@li Once the devices are derived, the netlist connectivity can be defined and the\n" + " netlist extracted. The connectivity is defined with \\connect and it's\n" + " flavours. The actual netlist extraction happens with \\extract_netlist.\n" + "@/li\n" + "@li After netlist extraction, the information is ready to be retrieved.\n" + " The produced netlist is available with \\netlist. The Shapes of a\n" + " specific net are available with \\shapes_of_net. \\probe_net allows\n" + " finding a net by probing a specific location.\n" + "@li\n" + "\n" + "This class has been introduced in version 0.26." +); + +} diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index f644ca07f..10957428a 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -269,6 +269,11 @@ Class decl_dbNet ("db", "Net", "@brief Gets the name of the net.\n" "See \\name= for details about the name." ) + + gsi::method ("qname|to_s", &db::Net::qname, + "@brief Gets the qualified name.\n" + "The qualified name is like the expanded name, but the circuit's name is preceeded\n" + "(i.e. 'CIRCUIT:NET') if available.\n" + ) + gsi::method ("expanded_name", &db::Net::expanded_name, "@brief Gets the expanded name of the net.\n" "The expanded name takes the name of the net. If the name is empty, the cluster ID will be used to build a name. " diff --git a/src/db/db/gsiDeclDbNetlistDeviceExtractor.cc b/src/db/db/gsiDeclDbNetlistDeviceExtractor.cc index f56ffcb94..6c1400a32 100644 --- a/src/db/db/gsiDeclDbNetlistDeviceExtractor.cc +++ b/src/db/db/gsiDeclDbNetlistDeviceExtractor.cc @@ -29,11 +29,11 @@ namespace { /** * @brief A NetlistDeviceExtractor implementation that allows reimplementation of the virtual methods */ -class NetlistDeviceExtractorImpl +class GenericDeviceExtractor : public db::NetlistDeviceExtractor { public: - NetlistDeviceExtractorImpl () + GenericDeviceExtractor () : db::NetlistDeviceExtractor (std::string ()) { // .. nothing yet .. @@ -66,7 +66,7 @@ public: virtual void setup () { if (cb_setup.can_issue ()) { - cb_setup.issue (&NetlistDeviceExtractorImpl::setup_fb); + cb_setup.issue (&GenericDeviceExtractor::setup_fb); } else { db::NetlistDeviceExtractor::setup (); } @@ -80,7 +80,7 @@ public: virtual db::Connectivity get_connectivity (const db::Layout &layout, const std::vector &layers) const { if (cb_get_connectivity.can_issue ()) { - return cb_get_connectivity.issue &> (&NetlistDeviceExtractorImpl::get_connectivity_fb, layout, layers); + return cb_get_connectivity.issue &> (&GenericDeviceExtractor::get_connectivity_fb, layout, layers); } else { return db::NetlistDeviceExtractor::get_connectivity (layout, layers); } @@ -94,7 +94,7 @@ public: virtual void extract_devices (const std::vector &layer_geometry) { if (cb_extract_devices.can_issue ()) { - cb_extract_devices.issue &> (&NetlistDeviceExtractorImpl::extract_devices_fb, layer_geometry); + cb_extract_devices.issue &> (&GenericDeviceExtractor::extract_devices_fb, layer_geometry); } else { db::NetlistDeviceExtractor::extract_devices (layer_geometry); } @@ -110,7 +110,7 @@ public: namespace tl { -template<> struct tl::type_traits : public tl::type_traits +template<> struct tl::type_traits : public tl::type_traits { // mark "NetlistDeviceExtractor" as not having a default ctor and no copy ctor typedef tl::false_tag has_copy_constructor; @@ -235,11 +235,11 @@ Class decl_dbNetlistDeviceExtractor ("db", "DeviceEx "This class has been introduced in version 0.26." ); -Class decl_NetlistDeviceExtractorImpl (decl_dbNetlistDeviceExtractor, "db", "DeviceExtractor", - gsi::method ("name=", &NetlistDeviceExtractorImpl::set_name, +Class decl_GenericDeviceExtractor (decl_dbNetlistDeviceExtractor, "db", "GenericDeviceExtractor", + gsi::method ("name=", &GenericDeviceExtractor::set_name, "@brief Sets the name of the device extractor and the device class." ) + - gsi::callback ("setup", &NetlistDeviceExtractorImpl::setup, &NetlistDeviceExtractorImpl::cb_setup, + gsi::callback ("setup", &GenericDeviceExtractor::setup, &GenericDeviceExtractor::cb_setup, "@brief Sets up the extractor.\n" "This method is supposed to set up the device extractor. This involves three basic steps:\n" "defining the name, the device classe and setting up the device layers.\n" @@ -248,7 +248,7 @@ Class decl_NetlistDeviceExtractorImpl (decl_dbNetlis "Use \\register_device_class to register the device class you need.\n" "Defined the layers by calling \\define_layer once or several times.\n" ) + - gsi::callback ("get_connectivity", &NetlistDeviceExtractorImpl::get_connectivity, &NetlistDeviceExtractorImpl::cb_get_connectivity, + gsi::callback ("get_connectivity", &GenericDeviceExtractor::get_connectivity, &GenericDeviceExtractor::cb_get_connectivity, gsi::arg ("layout"), gsi::arg ("layers"), "@brief Gets the connectivity object used to extract the device geometry.\n" "This method shall raise an error, if the input layer are not properly defined (e.g.\n" @@ -258,7 +258,7 @@ Class decl_NetlistDeviceExtractorImpl (decl_dbNetlis "The list of layers corresponds to the number of layers defined. Use the layer indexes from this list " "to build the connectivity with \\Connectivity#connect." ) + - gsi::callback ("extract_devices", &NetlistDeviceExtractorImpl::extract_devices, &NetlistDeviceExtractorImpl::cb_extract_devices, + gsi::callback ("extract_devices", &GenericDeviceExtractor::extract_devices, &GenericDeviceExtractor::cb_extract_devices, gsi::arg ("layer_geometry"), "@brief Extracts the devices from the given shape cluster.\n" "\n" @@ -271,7 +271,7 @@ Class decl_NetlistDeviceExtractorImpl (decl_dbNetlis "terminals by which the nets extracted in the network extraction step connect\n" "to the new devices.\n" ) + - gsi::method ("register_device_class", &NetlistDeviceExtractorImpl::register_device_class, gsi::arg ("device_class"), + gsi::method ("register_device_class", &GenericDeviceExtractor::register_device_class, gsi::arg ("device_class"), "@brief Registers a device class.\n" "The device class object will become owned by the netlist and must not be deleted by\n" "the caller. The name of the device class will be changed to the name given to\n" @@ -279,19 +279,19 @@ Class decl_NetlistDeviceExtractorImpl (decl_dbNetlis "This method shall be used inside the implementation of \\setup to register\n" "the device classes.\n" ) + - gsi::method ("define_layer", &NetlistDeviceExtractorImpl::define_layer, gsi::arg ("name"), gsi::arg ("description"), + gsi::method ("define_layer", &GenericDeviceExtractor::define_layer, gsi::arg ("name"), gsi::arg ("description"), "@brief Defines a layer.\n" "Each call will define one more layer for the device extraction.\n" "This method shall be used inside the implementation of \\setup to define\n" "the device layers. The actual geometries are later available to \\extract_devices\n" "in the order the layers are defined.\n" ) + - gsi::method ("create_device", &NetlistDeviceExtractorImpl::create_device, + gsi::method ("create_device", &GenericDeviceExtractor::create_device, "@brief Creates a device.\n" "The device object returned can be configured by the caller, e.g. set parameters.\n" "It will be owned by the netlist and must not be deleted by the caller.\n" ) + - gsi::method ("define_terminal", (void (NetlistDeviceExtractorImpl::*) (db::Device *, size_t, size_t, const db::Polygon &)) &NetlistDeviceExtractorImpl::define_terminal, + gsi::method ("define_terminal", (void (GenericDeviceExtractor::*) (db::Device *, size_t, size_t, const db::Polygon &)) &GenericDeviceExtractor::define_terminal, gsi::arg ("device"), gsi::arg ("terminal_id"), gsi::arg ("layer_index"), gsi::arg ("shape"), "@brief Defines a device terminal.\n" "This method will define a terminal to the given device and the given terminal ID. \n" @@ -301,7 +301,7 @@ Class decl_NetlistDeviceExtractorImpl (decl_dbNetlis "This version produces a terminal with a shape given by the polygon. Note that the polygon is\n" "specified in database units.\n" ) + - gsi::method ("define_terminal", (void (NetlistDeviceExtractorImpl::*) (db::Device *, size_t, size_t, const db::Box &)) &NetlistDeviceExtractorImpl::define_terminal, + gsi::method ("define_terminal", (void (GenericDeviceExtractor::*) (db::Device *, size_t, size_t, const db::Box &)) &GenericDeviceExtractor::define_terminal, gsi::arg ("device"), gsi::arg ("terminal_id"), gsi::arg ("layer_index"), gsi::arg ("shape"), "@brief Defines a device terminal.\n" "This method will define a terminal to the given device and the given terminal ID. \n" @@ -311,7 +311,7 @@ Class decl_NetlistDeviceExtractorImpl (decl_dbNetlis "This version produces a terminal with a shape given by the box. Note that the box is\n" "specified in database units.\n" ) + - gsi::method ("define_terminal", (void (NetlistDeviceExtractorImpl::*) (db::Device *, size_t, size_t, const db::Point &)) &NetlistDeviceExtractorImpl::define_terminal, + gsi::method ("define_terminal", (void (GenericDeviceExtractor::*) (db::Device *, size_t, size_t, const db::Point &)) &GenericDeviceExtractor::define_terminal, gsi::arg ("device"), gsi::arg ("terminal_id"), gsi::arg ("layer_index"), gsi::arg ("point"), "@brief Defines a device terminal.\n" "This method will define a terminal to the given device and the given terminal ID. \n" @@ -321,30 +321,30 @@ Class decl_NetlistDeviceExtractorImpl (decl_dbNetlis "This version produces a point-like terminal. Note that the point is\n" "specified in database units.\n" ) + - gsi::method ("dbu", &NetlistDeviceExtractorImpl::dbu, + gsi::method ("dbu", &GenericDeviceExtractor::dbu, "@brief Gets the database unit\n" ) + - gsi::method ("error", (void (NetlistDeviceExtractorImpl::*) (const std::string &)) &NetlistDeviceExtractorImpl::error, + gsi::method ("error", (void (GenericDeviceExtractor::*) (const std::string &)) &GenericDeviceExtractor::error, gsi::arg ("message"), "@brief Issues an error with the given message\n" ) + - gsi::method ("error", (void (NetlistDeviceExtractorImpl::*) (const std::string &, const db::DPolygon &)) &NetlistDeviceExtractorImpl::error, + gsi::method ("error", (void (GenericDeviceExtractor::*) (const std::string &, const db::DPolygon &)) &GenericDeviceExtractor::error, gsi::arg ("message"), gsi::arg ("geometry"), "@brief Issues an error with the given message and micrometer-units polygon geometry\n" ) + - gsi::method ("error", (void (NetlistDeviceExtractorImpl::*) (const std::string &, const db::Polygon &)) &NetlistDeviceExtractorImpl::error, + gsi::method ("error", (void (GenericDeviceExtractor::*) (const std::string &, const db::Polygon &)) &GenericDeviceExtractor::error, gsi::arg ("message"), gsi::arg ("geometry"), "@brief Issues an error with the given message and databse-unit polygon geometry\n" ) + - gsi::method ("error", (void (NetlistDeviceExtractorImpl::*) (const std::string &, const std::string &, const std::string &)) &NetlistDeviceExtractorImpl::error, + gsi::method ("error", (void (GenericDeviceExtractor::*) (const std::string &, const std::string &, const std::string &)) &GenericDeviceExtractor::error, gsi::arg ("category_name"), gsi::arg ("category_description"), gsi::arg ("message"), "@brief Issues an error with the given category name and description, message\n" ) + - gsi::method ("error", (void (NetlistDeviceExtractorImpl::*) (const std::string &, const std::string &, const std::string &, const db::DPolygon &)) &NetlistDeviceExtractorImpl::error, + gsi::method ("error", (void (GenericDeviceExtractor::*) (const std::string &, const std::string &, const std::string &, const db::DPolygon &)) &GenericDeviceExtractor::error, gsi::arg ("category_name"), gsi::arg ("category_description"), gsi::arg ("message"), gsi::arg ("geometry"), "@brief Issues an error with the given category name and description, message and micrometer-units polygon geometry\n" ) + - gsi::method ("error", (void (NetlistDeviceExtractorImpl::*) (const std::string &, const std::string &, const std::string &, const db::Polygon &)) &NetlistDeviceExtractorImpl::error, + gsi::method ("error", (void (GenericDeviceExtractor::*) (const std::string &, const std::string &, const std::string &, const db::Polygon &)) &GenericDeviceExtractor::error, gsi::arg ("category_name"), gsi::arg ("category_description"), gsi::arg ("message"), gsi::arg ("geometry"), "@brief Issues an error with the given category name and description, message and databse-unit polygon geometry\n" ), diff --git a/src/db/unit_tests/dbLayoutToNetlistTests.cc b/src/db/unit_tests/dbLayoutToNetlistTests.cc index f450c71a9..2254c9b23 100644 --- a/src/db/unit_tests/dbLayoutToNetlistTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistTests.cc @@ -172,6 +172,12 @@ static void dump_recursive_nets_to_layout (const db::LayoutToNetlist &l2n, db::L } } +// TODO: may be useful elsewhere? +static std::string qnet_name (const db::Net *net) +{ + return net ? net->qname () : "(null)"; +} + static unsigned int define_layer (db::Layout &ly, db::LayerMap &lmap, int gds_layer, int gds_datatype = 0) { unsigned int lid = ly.insert_layer (db::LayerProperties (gds_layer, gds_datatype)); @@ -361,6 +367,19 @@ TEST(1_Basic) "Circuit TRANS ($1=$1,$2=$2,$3=$3):\n" ); + // do some probing before purging + + // top level + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal2, db::DPoint (0.0, 1.8))), "RINGO:FB"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal2, db::Point (0, 1800))), "RINGO:FB"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal2, db::DPoint (-2.0, 1.8))), "(null)"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (-1.5, 1.8))), "RINGO:FB"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (24.5, 1.8))), "RINGO:OSC"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (5.3, 0.0))), "RINGO:VSS"); + + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (2.6, 1.0))), "RINGO:$I39"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (6.4, 1.0))), "RINGO:$I2"); + // doesn't do anything here, but we test that this does not destroy anything: l2n.netlist ()->combine_devices (); @@ -396,4 +415,18 @@ TEST(1_Basic) au = tl::combine_path (au, "device_extract_au1_with_rec_nets.gds"); db::compare_layouts (_this, ly, au); + + // do some probing after purging + + // top level + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal2, db::DPoint (0.0, 1.8))), "RINGO:FB"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal2, db::Point (0, 1800))), "RINGO:FB"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal2, db::DPoint (-2.0, 1.8))), "(null)"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (-1.5, 1.8))), "RINGO:FB"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (24.5, 1.8))), "RINGO:OSC"); + // the transistor which supplies this probe target has been optimized away by "purge". + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (5.3, 0.0))), "(null)"); + + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (2.6, 1.0))), "INV2:$2"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (6.4, 1.0))), "RINGO:$I2"); } diff --git a/src/rba/unit_tests/rba.cc b/src/rba/unit_tests/rba.cc index 2df8e38bb..883df8009 100644 --- a/src/rba/unit_tests/rba.cc +++ b/src/rba/unit_tests/rba.cc @@ -108,6 +108,7 @@ RUBYTEST (dbLayout, "dbLayout.rb") RUBYTEST (dbLayoutTest, "dbLayoutTest.rb") RUBYTEST (dbLayoutDiff, "dbLayoutDiff.rb") RUBYTEST (dbLayoutQuery, "dbLayoutQuery.rb") +RUBYTEST (dbLayoutToNetlist, "dbLayoutToNetlist.rb") RUBYTEST (dbMatrix, "dbMatrix.rb") RUBYTEST (dbNetlist, "dbNetlist.rb") RUBYTEST (dbNetlistDeviceClasses, "dbNetlistDeviceClasses.rb") diff --git a/testdata/ruby/dbLayoutToNetlist.rb b/testdata/ruby/dbLayoutToNetlist.rb new file mode 100644 index 000000000..59d821ab8 --- /dev/null +++ b/testdata/ruby/dbLayoutToNetlist.rb @@ -0,0 +1,287 @@ +# encoding: UTF-8 + +# KLayout Layout Viewer +# Copyright (C) 2006-2018 Matthias Koefferlein +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +if !$:.member?(File::dirname($0)) + $:.push(File::dirname($0)) +end + +load("test_prologue.rb") + +class DBLayoutToNetlist_TestClass < TestBase + + def test_1_Basic + + ly = RBA::Layout::new + ly.read(File.join($ut_testsrc, "testdata", "algo", "device_extract_l1.gds")) + + l2n = RBA::LayoutToNetlist::new(RBA::RecursiveShapeIterator::new(ly, ly.top_cell, [])) + + l2n.threads = 17 + l2n.max_vertex_count = 42 + l2n.area_ratio = 7.5 + assert_equal(l2n.threads, 17) + assert_equal(l2n.max_vertex_count, 42) + assert_equal(l2n.area_ratio, 7.5) + + r = l2n.make_layer(ly.layer(6, 0)) + + assert_not_equal(l2n.internal_layout.object_id, ly.object_id) + assert_equal(l2n.internal_layout.top_cell.name, ly.top_cell.name) + assert_equal(l2n.internal_top_cell.name, ly.top_cell.name) + + assert_not_equal(l2n.layer_of(r), ly.layer(6, 0)) # would be a strange coincidence ... + + cm = l2n.const_cell_mapping_into(ly, ly.top_cell) + (0 .. l2n.internal_layout.cells - 1).each do |ci| + assert_equal(l2n.internal_layout.cell(ci).name, ly.cell(cm.cell_mapping(ci)).name) + end + + ly2 = RBA::Layout::new + ly2.create_cell(ly.top_cell.name) + + cm = l2n.cell_mapping_into(ly2, ly2.top_cell) + assert_equal(ly2.cells, ly.cells) + (0 .. l2n.internal_layout.cells - 1).each do |ci| + assert_equal(l2n.internal_layout.cell(ci).name, ly2.cell(cm.cell_mapping(ci)).name) + end + + end + + def test_2_ShapesFromNet + + ly = RBA::Layout::new + ly.read(File.join($ut_testsrc, "testdata", "algo", "device_extract_l1.gds")) + + l2n = RBA::LayoutToNetlist::new(RBA::RecursiveShapeIterator::new(ly, ly.top_cell, [])) + + # only plain backend connectivity + + rmetal1 = l2n.make_polygon_layer( ly.layer(6, 0) ) + rmetal1_lbl = l2n.make_text_layer( ly.layer(6, 1) ) + rvia1 = l2n.make_polygon_layer( ly.layer(7, 0) ) + rmetal2 = l2n.make_polygon_layer( ly.layer(8, 0) ) + rmetal2_lbl = l2n.make_text_layer( ly.layer(8, 1) ) + + # Intra-layer + l2n.connect(rmetal1) + l2n.connect(rvia1) + l2n.connect(rmetal2) + + # Inter-layer + l2n.connect(rmetal1, rvia1) + l2n.connect(rvia1, rmetal2) + l2n.connect(rmetal1, rmetal1_lbl) # attaches labels + l2n.connect(rmetal2, rmetal2_lbl) # attaches labels + + # Perform netlist extraction + l2n.extract_netlist + + assert_equal(l2n.netlist.to_s, < rpsd, "G" => rpgate, "P" => rpoly }) + + # NMOS transistor device extraction + nmos_ex = RBA::DeviceExtractorMOS3Transistor::new("NMOS") + l2n.extract_devices(nmos_ex, { "SD" => rnsd, "G" => rngate, "P" => rpoly }) + + # Define connectivity for netlist extraction + + # Intra-layer + l2n.connect(rpsd) + l2n.connect(rnsd) + l2n.connect(rpoly) + l2n.connect(rdiff_cont) + l2n.connect(rpoly_cont) + l2n.connect(rmetal1) + l2n.connect(rvia1) + l2n.connect(rmetal2) + + # Inter-layer + l2n.connect(rpsd, rdiff_cont) + l2n.connect(rnsd, rdiff_cont) + l2n.connect(rpoly, rpoly_cont) + l2n.connect(rpoly_cont, rmetal1) + l2n.connect(rdiff_cont, rmetal1) + l2n.connect(rmetal1, rvia1) + l2n.connect(rvia1, rmetal2) + l2n.connect(rpoly, rpoly_lbl) # attaches labels + l2n.connect(rmetal1, rmetal1_lbl) # attaches labels + l2n.connect(rmetal2, rmetal2_lbl) # attaches labels + + # Perform netlist extraction + l2n.extract_netlist + + assert_equal(l2n.netlist.to_s, <