From 72a140957de54d9cd9c8c1a962454398b02544f6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 30 Dec 2018 18:22:45 +0100 Subject: [PATCH] WIP: added test for recursive net shape retrieval --- src/db/db/dbLayoutToNetlist.cc | 4 +- src/db/unit_tests/dbLayoutToNetlistTests.cc | 102 +++++++++++++----- .../algo/device_extract_au1_with_rec_nets.gds | Bin 0 -> 50894 bytes 3 files changed, 79 insertions(+), 27 deletions(-) create mode 100644 testdata/algo/device_extract_au1_with_rec_nets.gds diff --git a/src/db/db/dbLayoutToNetlist.cc b/src/db/db/dbLayoutToNetlist.cc index d0437d16d..f9b0f8c72 100644 --- a/src/db/db/dbLayoutToNetlist.cc +++ b/src/db/db/dbLayoutToNetlist.cc @@ -209,7 +209,7 @@ db::Region LayoutToNetlist::shapes_of_net (const db::Net &net, const db::Region const db::local_cluster &lc = m_netex.clusters ().clusters_per_cell (ci).cluster_by_id (net.cluster_id ()); for (db::local_cluster::shape_iterator s = lc.begin (lid); !s.at_end (); ++s) { - res.insert (*s); + res.insert (s->obj ().transformed (s->trans ())); } } else { @@ -220,7 +220,7 @@ db::Region LayoutToNetlist::shapes_of_net (const db::Net &net, const db::Region db::cell_index_type ci = circuit->cell_index (); for (db::recursive_cluster_shape_iterator rci (m_netex.clusters (), lid, ci, net.cluster_id ()); !rci.at_end (); ++rci) { - res.insert (rci->obj ().transformed (db::ICplxTrans (rci->trans ()) * rci.trans ())); + res.insert (rci->obj ().transformed (rci.trans () * db::ICplxTrans (rci->trans ()))); } } diff --git a/src/db/unit_tests/dbLayoutToNetlistTests.cc b/src/db/unit_tests/dbLayoutToNetlistTests.cc index 287f874b5..bebbd6e0c 100644 --- a/src/db/unit_tests/dbLayoutToNetlistTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistTests.cc @@ -101,34 +101,74 @@ private: } -static void dump_nets_to_layout (const db::Netlist &nl, const db::hier_clusters &clusters, db::Layout &ly, const std::map &lmap, const db::CellMapping &cmap) +static void dump_nets_to_layout (const db::LayoutToNetlist &l2n, db::Layout &ly, const std::map &lmap, const db::CellMapping &cmap) { + const db::Netlist &nl = *l2n.netlist (); for (db::Netlist::const_circuit_iterator c = nl.begin_circuits (); c != nl.end_circuits (); ++c) { db::Cell &cell = ly.cell (cmap.cell_mapping (c->cell_index ())); for (db::Circuit::const_net_iterator n = c->begin_nets (); n != c->end_nets (); ++n) { - const db::local_cluster &lc = clusters.clusters_per_cell (c->cell_index ()).cluster_by_id (n->cluster_id ()); + db::cell_index_type nci = std::numeric_limits::max (); + + for (std::map::const_iterator m = lmap.begin (); m != lmap.end (); ++m) { + + db::Region shapes = l2n.shapes_of_net (*n, *m->first, false); + if (shapes.empty ()) { + continue; + } + + if (nci == std::numeric_limits::max ()) { + std::string nn = "NET_" + c->name () + "_" + n->expanded_name (); + nci = ly.add_cell (nn.c_str ()); + cell.insert (db::CellInstArray (db::CellInst (nci), db::Trans ())); + } + + shapes.insert_into (&ly, nci, m->second); - bool any_shapes = false; - for (std::map::const_iterator m = lmap.begin (); m != lmap.end () && !any_shapes; ++m) { - any_shapes = ! lc.begin (m->first).at_end (); } - if (any_shapes) { + } - std::string nn = "NET_" + c->name () + "_" + n->expanded_name (); - db::Cell &net_cell = ly.cell (ly.add_cell (nn.c_str ())); - cell.insert (db::CellInstArray (db::CellInst (net_cell.cell_index ()), db::Trans ())); + } +} - for (std::map::const_iterator m = lmap.begin (); m != lmap.end (); ++m) { - db::Shapes &target = net_cell.shapes (m->second); - for (db::local_cluster::shape_iterator s = lc.begin (m->first); !s.at_end (); ++s) { - target.insert (*s); - } +static void dump_recursive_nets_to_layout (const db::LayoutToNetlist &l2n, db::Layout &ly, const std::map &lmap, const db::CellMapping &cmap) +{ + const db::Netlist &nl = *l2n.netlist (); + for (db::Netlist::const_circuit_iterator c = nl.begin_circuits (); c != nl.end_circuits (); ++c) { + + db::Cell &cell = ly.cell (cmap.cell_mapping (c->cell_index ())); + + for (db::Circuit::const_net_iterator n = c->begin_nets (); n != c->end_nets (); ++n) { + + // only handle nets without outgoing pins - these are local + bool is_local = true; + for (db::Net::const_pin_iterator p = n->begin_pins (); p != n->end_pins () && is_local; ++p) { + is_local = (p->subcircuit () != 0); + } + if (! is_local) { + continue; + } + + db::cell_index_type nci = std::numeric_limits::max (); + + for (std::map::const_iterator m = lmap.begin (); m != lmap.end (); ++m) { + + db::Region shapes = l2n.shapes_of_net (*n, *m->first, true); + if (shapes.empty ()) { + continue; } + if (nci == std::numeric_limits::max ()) { + std::string nn = "RNET_" + c->name () + "_" + n->expanded_name (); + nci = ly.add_cell (nn.c_str ()); + cell.insert (db::CellInstArray (db::CellInst (nci), db::Trans ())); + } + + shapes.insert_into (&ly, nci, m->second); + } } @@ -274,19 +314,31 @@ TEST(1_Basic) // 208/0 -> Metal2 // 210/0 -> N source/drain // 211/0 -> P source/drain - std::map dump_map; - dump_map [l2n.layer_of (rpsd) ] = ly.insert_layer (db::LayerProperties (210, 0)); - dump_map [l2n.layer_of (rnsd) ] = ly.insert_layer (db::LayerProperties (211, 0)); - dump_map [l2n.layer_of (*rpoly) ] = ly.insert_layer (db::LayerProperties (203, 0)); - dump_map [l2n.layer_of (*rdiff_cont)] = ly.insert_layer (db::LayerProperties (204, 0)); - dump_map [l2n.layer_of (*rpoly_cont)] = ly.insert_layer (db::LayerProperties (205, 0)); - dump_map [l2n.layer_of (*rmetal1) ] = ly.insert_layer (db::LayerProperties (206, 0)); - dump_map [l2n.layer_of (*rvia1) ] = ly.insert_layer (db::LayerProperties (207, 0)); - dump_map [l2n.layer_of (*rmetal2) ] = ly.insert_layer (db::LayerProperties (208, 0)); + std::map dump_map; + dump_map [&rpsd ] = ly.insert_layer (db::LayerProperties (210, 0)); + dump_map [&rnsd ] = ly.insert_layer (db::LayerProperties (211, 0)); + dump_map [rpoly.get () ] = ly.insert_layer (db::LayerProperties (203, 0)); + dump_map [rdiff_cont.get ()] = ly.insert_layer (db::LayerProperties (204, 0)); + dump_map [rpoly_cont.get ()] = ly.insert_layer (db::LayerProperties (205, 0)); + dump_map [rmetal1.get () ] = ly.insert_layer (db::LayerProperties (206, 0)); + dump_map [rvia1.get () ] = ly.insert_layer (db::LayerProperties (207, 0)); + dump_map [rmetal2.get () ] = ly.insert_layer (db::LayerProperties (208, 0)); // write nets to layout db::CellMapping cm = l2n.cell_mapping_into (ly, tc); - dump_nets_to_layout (*l2n.netlist (), l2n.net_clusters (), ly, dump_map, cm); + dump_nets_to_layout (l2n, ly, dump_map, cm); + + dump_map.clear (); + dump_map [&rpsd ] = ly.insert_layer (db::LayerProperties (310, 0)); + dump_map [&rnsd ] = ly.insert_layer (db::LayerProperties (311, 0)); + dump_map [rpoly.get () ] = ly.insert_layer (db::LayerProperties (303, 0)); + dump_map [rdiff_cont.get ()] = ly.insert_layer (db::LayerProperties (304, 0)); + dump_map [rpoly_cont.get ()] = ly.insert_layer (db::LayerProperties (305, 0)); + dump_map [rmetal1.get () ] = ly.insert_layer (db::LayerProperties (306, 0)); + dump_map [rvia1.get () ] = ly.insert_layer (db::LayerProperties (307, 0)); + dump_map [rmetal2.get () ] = ly.insert_layer (db::LayerProperties (308, 0)); + + dump_recursive_nets_to_layout (l2n, ly, dump_map, cm); // compare netlist as string EXPECT_EQ (l2n.netlist ()->to_string (), @@ -345,7 +397,7 @@ TEST(1_Basic) 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.gds"); + au = tl::combine_path (au, "device_extract_au1_with_rec_nets.gds"); db::compare_layouts (_this, ly, au); } diff --git a/testdata/algo/device_extract_au1_with_rec_nets.gds b/testdata/algo/device_extract_au1_with_rec_nets.gds new file mode 100644 index 0000000000000000000000000000000000000000..c61fa0fb5d4a1254e2dc0b4a383984492ee51f7f GIT binary patch literal 50894 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