From ec3a3b0f8c46727d58b5ee8205477e8bc62cc31a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 6 Jan 2019 01:32:20 +0100 Subject: [PATCH] WIP: added ability to export nets to layouts. --- src/db/db/dbLayoutToNetlist.cc | 113 ++++++++++++++++++ src/db/db/dbLayoutToNetlist.h | 47 ++++++++ src/db/db/dbNetlist.h | 2 +- src/db/unit_tests/dbLayoutToNetlistTests.cc | 110 +++++++++++++++++ .../algo/device_extract_au1_rebuild_ff.gds | Bin 0 -> 3592 bytes .../algo/device_extract_au1_rebuild_fr.gds | Bin 0 -> 34686 bytes .../algo/device_extract_au1_rebuild_nf.gds | Bin 0 -> 4324 bytes .../algo/device_extract_au1_rebuild_nr.gds | Bin 0 -> 36354 bytes 8 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 testdata/algo/device_extract_au1_rebuild_ff.gds create mode 100644 testdata/algo/device_extract_au1_rebuild_fr.gds create mode 100644 testdata/algo/device_extract_au1_rebuild_nf.gds create mode 100644 testdata/algo/device_extract_au1_rebuild_nr.gds diff --git a/src/db/db/dbLayoutToNetlist.cc b/src/db/db/dbLayoutToNetlist.cc index d9c3052b4..af2026460 100644 --- a/src/db/db/dbLayoutToNetlist.cc +++ b/src/db/db/dbLayoutToNetlist.cc @@ -287,6 +287,119 @@ db::Region *LayoutToNetlist::shapes_of_net (const db::Net &net, const db::Region return res.release (); } +void +LayoutToNetlist::build_net_rec (const db::Net &net, db::Layout &target, db::Cell &target_cell, const std::map &lmap, const char *cell_name_prefix, std::map, db::cell_index_type> &cmap) const +{ + for (std::map::const_iterator l = lmap.begin (); l != lmap.end (); ++l) { + shapes_of_net (net, *l->second, false, target_cell.shapes (l->first)); + } + + if (! cell_name_prefix) { + return; + } + + const db::Circuit *circuit = net.circuit (); + tl_assert (circuit != 0); + + const db::connected_clusters &clusters = m_netex.clusters ().clusters_per_cell (circuit->cell_index ()); + typedef db::connected_clusters::connections_type connections_type; + const connections_type &connections = clusters.connections_for_cluster (net.cluster_id ()); + for (connections_type::const_iterator c = connections.begin (); c != connections.end (); ++c) { + + db::cell_index_type ci = c->inst ().inst_ptr.cell_index (); + const db::Circuit *subcircuit = mp_netlist->circuit_by_cell_index (ci); + tl_assert (subcircuit != 0); + + const db::Net *subnet = subcircuit->net_by_cluster_id (c->id ()); + tl_assert (subnet != 0); + + std::map, db::cell_index_type>::const_iterator cm = cmap.find (std::make_pair (ci, c->id ())); + if (cm == cmap.end ()) { + + db::cell_index_type target_ci = target.add_cell ((std::string (cell_name_prefix) + subcircuit->name ()).c_str ()); + cm = cmap.insert (std::make_pair (std::make_pair (ci, c->id ()), target_ci)).first; + + build_net_rec (*subnet, target, target.cell (target_ci), lmap, cell_name_prefix, cmap); + + } + + target_cell.insert (db::CellInstArray (db::CellInst (cm->second), c->inst ().complex_trans ())); + + } +} + +void +LayoutToNetlist::build_net (const db::Net &net, db::Layout &target, db::Cell &target_cell, const std::map &lmap, const char *cell_name_prefix) const +{ + if (! m_netlist_extracted) { + throw tl::Exception (tl::to_string (tr ("The netlist has not been extracted yet"))); + } + + std::map, db::cell_index_type> cell_map; + cell_map.insert (std::make_pair (std::make_pair (net.circuit ()->cell_index (), net.cluster_id ()), target_cell.cell_index ())); + + build_net_rec (net, target, target_cell, lmap, cell_name_prefix, cell_map); +} + +void +LayoutToNetlist::build_all_nets (const db::CellMapping &cmap, db::Layout &target, const std::map &lmap, const char *net_cell_name_prefix, const char *circuit_cell_name_prefix) const +{ + if (! m_netlist_extracted) { + throw tl::Exception (tl::to_string (tr ("The netlist has not been extracted yet"))); + } + + const db::Netlist *netlist = mp_netlist.get (); + for (db::Netlist::const_circuit_iterator c = netlist->begin_circuits (); c != netlist->end_circuits (); ++c) { + + if (! cmap.has_mapping (c->cell_index ())) { + continue; + } + + std::set excluded_nets; + if (circuit_cell_name_prefix) { + for (db::Circuit::const_pin_iterator p = c->begin_pins (); p != c->end_pins (); ++p) { + excluded_nets.insert (c->net_for_pin (p->id ())); + } + } + + db::cell_index_type target_ci = cmap.cell_mapping (c->cell_index ()); + + for (db::Circuit::const_net_iterator n = c->begin_nets (); n != c->end_nets (); ++n) { + + if (excluded_nets.find (n.operator-> ()) == excluded_nets.end ()) { + + const db::connected_clusters &ccl = m_netex.clusters ().clusters_per_cell (c->cell_index ()); + const db::local_cluster &cl = ccl.cluster_by_id (n->cluster_id ()); + + bool any_connections = ! ccl.connections_for_cluster (n->cluster_id ()).empty (); + + bool any_shapes = false; + for (std::map::const_iterator m = lmap.begin (); m != lmap.end () && !any_shapes; ++m) { + any_shapes = ! cl.begin (layer_of (*m->second)).at_end (); + } + + if (any_shapes || (circuit_cell_name_prefix && any_connections)) { + + db::cell_index_type net_ci = target_ci; + + if (net_cell_name_prefix) { + + db::Cell &tc = target.cell (target_ci); + net_ci = target.add_cell ((std::string (net_cell_name_prefix) + n->expanded_name ()).c_str ()); + tc.insert (db::CellInstArray (db::CellInst (net_ci), db::Trans ())); + + } + + build_net (*n, target, target.cell (net_ci), lmap, circuit_cell_name_prefix); + + } + + } + } + + } +} + db::Net *LayoutToNetlist::probe_net (const db::Region &of_region, const db::DPoint &point) { return probe_net (of_region, db::CplxTrans (internal_layout ()->dbu ()).inverted () * point); diff --git a/src/db/db/dbLayoutToNetlist.h b/src/db/db/dbLayoutToNetlist.h index 4efaf4294..7e7f40689 100644 --- a/src/db/db/dbLayoutToNetlist.h +++ b/src/db/db/dbLayoutToNetlist.h @@ -235,6 +235,52 @@ public: */ void shapes_of_net (const db::Net &net, const db::Region &of_layer, bool recursive, db::Shapes &to) const; + /** + * @brief Builds a net representation in the given layout and cell + * + * This method has two modes: recursive and top-level mode. In recursive mode, + * it will create a proper hierarchy below the given target cell to hold all subcircuits the + * net connects to. It will copy the net's parts from this subcircuits into these cells. + * + * In top-level mode, only the shapes from the net inside it's circuit are copied to + * the given target cell. No other cells are created. + * + * Recursive mode is picked when a cell name prefix is given. The new cells will be + * named like cell_name_prefix + circuit name. + * + * @param target The target layout + * @param target_cell The target cell + * @param lmap Target layer indexes (keys) and net regions (values) + * @param cell_name_prefix Chooses recursive mode if non-null + */ + void build_net (const db::Net &net, db::Layout &target, db::Cell &target_cell, const std::map &lmap, const char *cell_name_prefix) const; + + /** + * @brief Builds a full hierarchical representation of the nets + * + * This method copies all nets into cells corresponding to the circuits. It uses the cmap + * object to determine the target cell (create them with "cell_mapping_into" or "const_cell_mapping_into"). + * If no mapping is requested, the specific circuit it skipped. + * + * The method has two net annotation modes: + * * No annotation (net_cell_name_prefix == 0): the shapes will be put into the target cell simply + * * Individual subcells per net (net_cell_name_prefix != 0): for each net, a subcell is created + * and the net shapes will be put there (name of the subcell = net_cell_name_prefix + net name). + * + * In addition, net hierarchy is covered in two ways: + * * No connection indicated (circuit_cell_name_prefix == 0: the net shapes are simply put into their + * respective circuits. The connections are not indicated. + * * Subnet hierarchy (circuit_cell_name_prefix != 0): for each root net, a full hierarchy is built + * to accomodate the subnets (see build_net in recursive mode). + * + * @param cmap The mapping of internal layout to target layout for the circuit mapping + * @param target The target layout + * @param lmap Target layer indexes (keys) and net regions (values) + * @param circuit_cell_name_prefix See method description + * @param net_cell_name_prefix See method description + */ + void build_all_nets (const db::CellMapping &cmap, db::Layout &target, const std::map &lmap, const char *net_cell_name_prefix, const char *circuit_cell_name_prefix) const; + /** * @brief Finds the net by probing a specific location on the given layer * @@ -271,6 +317,7 @@ private: bool m_netlist_extracted; size_t search_net (const db::ICplxTrans &trans, const db::Cell *cell, const db::local_cluster &test_cluster, std::vector &rev_inst_path); + void build_net_rec (const db::Net &net, db::Layout &target, db::Cell &target_cell, const std::map &lmap, const char *cell_name_prefix, std::map, db::cell_index_type> &cmap) const; }; } diff --git a/src/db/db/dbNetlist.h b/src/db/db/dbNetlist.h index 73ea82d42..4c689389d 100644 --- a/src/db/db/dbNetlist.h +++ b/src/db/db/dbNetlist.h @@ -1534,7 +1534,7 @@ public: /** * @brief Returns true, if the net is an external net * - * External nets are net which are connected to an outgoing pin. + * External nets are nets which are connected to an outgoing pin. */ bool is_external_net (const db::Net *net) const; diff --git a/src/db/unit_tests/dbLayoutToNetlistTests.cc b/src/db/unit_tests/dbLayoutToNetlistTests.cc index 10ecc840c..f63bdbf78 100644 --- a/src/db/unit_tests/dbLayoutToNetlistTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistTests.cc @@ -382,6 +382,116 @@ TEST(1_Basic) 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"); + // test build_all_nets + + { + db::Layout ly2; + ly2.dbu (ly.dbu ()); + db::Cell &top2 = ly2.cell (ly2.add_cell ("TOP")); + + db::CellMapping cm = l2n.cell_mapping_into (ly2, top2); + + std::map lmap; + lmap [ly2.insert_layer (db::LayerProperties (10, 0))] = &rpsd; + lmap [ly2.insert_layer (db::LayerProperties (11, 0))] = &rnsd; + lmap [ly2.insert_layer (db::LayerProperties (3, 0)) ] = rpoly.get (); + lmap [ly2.insert_layer (db::LayerProperties (4, 0)) ] = rdiff_cont.get (); + lmap [ly2.insert_layer (db::LayerProperties (5, 0)) ] = rpoly_cont.get (); + lmap [ly2.insert_layer (db::LayerProperties (6, 0)) ] = rmetal1.get (); + lmap [ly2.insert_layer (db::LayerProperties (7, 0)) ] = rvia1.get (); + lmap [ly2.insert_layer (db::LayerProperties (8, 0)) ] = rmetal2.get (); + + l2n.build_all_nets (cm, ly2, lmap, 0, 0); + + std::string au = tl::testsrc (); + au = tl::combine_path (au, "testdata"); + au = tl::combine_path (au, "algo"); + au = tl::combine_path (au, "device_extract_au1_rebuild_ff.gds"); + + db::compare_layouts (_this, ly2, au); + } + + { + db::Layout ly2; + ly2.dbu (ly.dbu ()); + db::Cell &top2 = ly2.cell (ly2.add_cell ("TOP")); + + db::CellMapping cm = l2n.cell_mapping_into (ly2, top2); + + std::map lmap; + lmap [ly2.insert_layer (db::LayerProperties (10, 0))] = &rpsd; + lmap [ly2.insert_layer (db::LayerProperties (11, 0))] = &rnsd; + lmap [ly2.insert_layer (db::LayerProperties (3, 0)) ] = rpoly.get (); + lmap [ly2.insert_layer (db::LayerProperties (4, 0)) ] = rdiff_cont.get (); + lmap [ly2.insert_layer (db::LayerProperties (5, 0)) ] = rpoly_cont.get (); + lmap [ly2.insert_layer (db::LayerProperties (6, 0)) ] = rmetal1.get (); + lmap [ly2.insert_layer (db::LayerProperties (7, 0)) ] = rvia1.get (); + lmap [ly2.insert_layer (db::LayerProperties (8, 0)) ] = rmetal2.get (); + + l2n.build_all_nets (cm, ly2, lmap, "NET_", 0); + + std::string au = tl::testsrc (); + au = tl::combine_path (au, "testdata"); + au = tl::combine_path (au, "algo"); + au = tl::combine_path (au, "device_extract_au1_rebuild_nf.gds"); + + db::compare_layouts (_this, ly2, au); + } + + { + db::Layout ly2; + ly2.dbu (ly.dbu ()); + db::Cell &top2 = ly2.cell (ly2.add_cell ("TOP")); + + db::CellMapping cm = l2n.cell_mapping_into (ly2, top2); + + std::map lmap; + lmap [ly2.insert_layer (db::LayerProperties (10, 0))] = &rpsd; + lmap [ly2.insert_layer (db::LayerProperties (11, 0))] = &rnsd; + lmap [ly2.insert_layer (db::LayerProperties (3, 0)) ] = rpoly.get (); + lmap [ly2.insert_layer (db::LayerProperties (4, 0)) ] = rdiff_cont.get (); + lmap [ly2.insert_layer (db::LayerProperties (5, 0)) ] = rpoly_cont.get (); + lmap [ly2.insert_layer (db::LayerProperties (6, 0)) ] = rmetal1.get (); + lmap [ly2.insert_layer (db::LayerProperties (7, 0)) ] = rvia1.get (); + lmap [ly2.insert_layer (db::LayerProperties (8, 0)) ] = rmetal2.get (); + + l2n.build_all_nets (cm, ly2, lmap, 0, "CIRCUIT_"); + + std::string au = tl::testsrc (); + au = tl::combine_path (au, "testdata"); + au = tl::combine_path (au, "algo"); + au = tl::combine_path (au, "device_extract_au1_rebuild_fr.gds"); + + db::compare_layouts (_this, ly2, au); + } + + { + db::Layout ly2; + ly2.dbu (ly.dbu ()); + db::Cell &top2 = ly2.cell (ly2.add_cell ("TOP")); + + db::CellMapping cm = l2n.cell_mapping_into (ly2, top2); + + std::map lmap; + lmap [ly2.insert_layer (db::LayerProperties (10, 0))] = &rpsd; + lmap [ly2.insert_layer (db::LayerProperties (11, 0))] = &rnsd; + lmap [ly2.insert_layer (db::LayerProperties (3, 0)) ] = rpoly.get (); + lmap [ly2.insert_layer (db::LayerProperties (4, 0)) ] = rdiff_cont.get (); + lmap [ly2.insert_layer (db::LayerProperties (5, 0)) ] = rpoly_cont.get (); + lmap [ly2.insert_layer (db::LayerProperties (6, 0)) ] = rmetal1.get (); + lmap [ly2.insert_layer (db::LayerProperties (7, 0)) ] = rvia1.get (); + lmap [ly2.insert_layer (db::LayerProperties (8, 0)) ] = rmetal2.get (); + + l2n.build_all_nets (cm, ly2, lmap, "NET_", "CIRCUIT_"); + + std::string au = tl::testsrc (); + au = tl::combine_path (au, "testdata"); + au = tl::combine_path (au, "algo"); + au = tl::combine_path (au, "device_extract_au1_rebuild_nr.gds"); + + db::compare_layouts (_this, ly2, au); + } + // doesn't do anything here, but we test that this does not destroy anything: l2n.netlist ()->combine_devices (); diff --git a/testdata/algo/device_extract_au1_rebuild_ff.gds b/testdata/algo/device_extract_au1_rebuild_ff.gds new file mode 100644 index 0000000000000000000000000000000000000000..afbbb50923d70b2e717885039342897f2cef4e28 GIT binary patch literal 3592 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 literal 0 HcmV?d00001