From 0d623bc57af37c349a9667fae9188d856c886321 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 13 Jun 2019 13:33:28 +0200 Subject: [PATCH] Avoid netlist extraction issues with duplicate instances So far, duplicate instances have lead to net propagation into parent cells and floating nets. This is fixed by ignoring duplicate instances where possible. --- src/db/db/dbHierNetworkProcessor.cc | 45 ++-- src/db/unit_tests/dbHierProcessorTests.cc | 24 +++ src/db/unit_tests/dbLayoutToNetlistTests.cc | 202 ++++++++++++++++++ ...ice_extract_au1_dup_inst_with_rec_nets.gds | Bin 0 -> 55954 bytes testdata/algo/device_extract_l1_dup_inst.gds | Bin 0 -> 2914 bytes testdata/algo/hlp15.oas | Bin 0 -> 528 bytes testdata/algo/hlp16.gds | Bin 0 -> 1056 bytes 7 files changed, 256 insertions(+), 15 deletions(-) create mode 100644 testdata/algo/device_extract_au1_dup_inst_with_rec_nets.gds create mode 100644 testdata/algo/device_extract_l1_dup_inst.gds create mode 100644 testdata/algo/hlp15.oas create mode 100644 testdata/algo/hlp16.gds diff --git a/src/db/db/dbHierNetworkProcessor.cc b/src/db/db/dbHierNetworkProcessor.cc index 8e7e19eca..2bfa0fd70 100644 --- a/src/db/db/dbHierNetworkProcessor.cc +++ b/src/db/db/dbHierNetworkProcessor.cc @@ -1270,6 +1270,11 @@ private: for (db::CellInstArray::iterator ii2 = i2.begin_touching (ib1.transformed (t2i), mp_layout); ! ii2.at_end (); ++ii2) { db::ICplxTrans tt2 = t2 * i2.complex_trans (*ii2); + if (i1.cell_index () == i2.cell_index () && tt1 == tt2) { + // skip interactions between identical instances (duplicate instance removal) + continue; + } + box_type ib2 = bb2.transformed (tt2); box_type common12 = ib1 & ib2 & common; @@ -1653,6 +1658,8 @@ hier_clusters::make_path (const db::Layout &layout, const db::Cell &cell, siz connected_clusters &child_cc = clusters_per_cell (p->inst_cell_index ()); if (child_cc.is_root (id)) { + std::set seen; // to avoid duplicate connections + const db::Cell &child_cell = layout.cell (p->inst_cell_index ()); for (db::Cell::parent_inst_iterator pi = child_cell.begin_parent_insts (); ! pi.at_end (); ++pi) { @@ -1662,7 +1669,7 @@ hier_clusters::make_path (const db::Layout &layout, const db::Cell &cell, siz for (db::CellInstArray::iterator pii = child_inst.begin (); ! pii.at_end (); ++pii) { ClusterInstance ci2 (id, child_inst.cell_index (), child_inst.complex_trans (*pii), child_inst.prop_id ()); - if (cell.cell_index () != pi->parent_cell_index () || ci != ci2) { + if ((cell.cell_index () != pi->parent_cell_index () || ci != ci2) && seen.find (ci2) == seen.end ()) { size_t id_dummy; @@ -1676,6 +1683,7 @@ hier_clusters::make_path (const db::Layout &layout, const db::Cell &cell, siz } parent_cc.add_connection (id_dummy, ci2); + seen.insert (ci2); } @@ -1709,6 +1717,8 @@ hier_clusters::make_path (const db::Layout &layout, const db::Cell &cell, siz connected_clusters &child_cc = clusters_per_cell (p->inst_cell_index ()); if (child_cc.is_root (id)) { + std::set seen; // to avoid duplicate connections + const db::Cell &child_cell = layout.cell (p->inst_cell_index ()); for (db::Cell::parent_inst_iterator pi = child_cell.begin_parent_insts (); ! pi.at_end (); ++pi) { @@ -1717,22 +1727,27 @@ hier_clusters::make_path (const db::Layout &layout, const db::Cell &cell, siz connected_clusters &parent_cc = clusters_per_cell (pi->parent_cell_index ()); for (db::CellInstArray::iterator pii = child_inst.begin (); ! pii.at_end (); ++pii) { - size_t id_dummy; - - const typename db::local_cluster::global_nets &gn = child_cc.cluster_by_id (id).get_global_nets (); - if (gn.empty ()) { - id_dummy = parent_cc.insert_dummy (); - } else { - local_cluster *lc = parent_cc.insert (); - lc->set_global_nets (gn); - id_dummy = lc->id (); - } - ClusterInstance ci2 (id, child_inst.cell_index (), child_inst.complex_trans (*pii), child_inst.prop_id ()); - parent_cc.add_connection (id_dummy, ci2); + if (seen.find (ci2) == seen.end ()) { + + size_t id_dummy; + + const typename db::local_cluster::global_nets &gn = child_cc.cluster_by_id (id).get_global_nets (); + if (gn.empty ()) { + id_dummy = parent_cc.insert_dummy (); + } else { + local_cluster *lc = parent_cc.insert (); + lc->set_global_nets (gn); + id_dummy = lc->id (); + } + + parent_cc.add_connection (id_dummy, ci2); + seen.insert (ci2); + + if (pci == pi->parent_cell_index () && ci == ci2) { + id_new = id_dummy; + } - if (pci == pi->parent_cell_index () && ci == ci2) { - id_new = id_dummy; } } diff --git a/src/db/unit_tests/dbHierProcessorTests.cc b/src/db/unit_tests/dbHierProcessorTests.cc index e5b2724c8..01591c691 100644 --- a/src/db/unit_tests/dbHierProcessorTests.cc +++ b/src/db/unit_tests/dbHierProcessorTests.cc @@ -1095,3 +1095,27 @@ TEST(BasicHierarchyVariantsNot2) run_test_bool (_this, "hlp14.oas", TMNot, 101); } +TEST(RedundantHierarchyAnd1) +{ + // Redundant hierarchy, NOT + run_test_bool2 (_this, "hlp15.oas", TMAnd, 100); +} + +TEST(RedundantHierarchyNot1) +{ + // Redundant hierarchy, NOT + run_test_bool2 (_this, "hlp15.oas", TMNot, 101); +} + +TEST(RedundantHierarchyAnd2) +{ + // Redundant hierarchy, NOT + run_test_bool2 (_this, "hlp16.gds", TMAnd, 100); +} + +TEST(RedundantHierarchyNot2) +{ + // Redundant hierarchy, NOT + run_test_bool2 (_this, "hlp16.gds", TMNot, 101); +} + diff --git a/src/db/unit_tests/dbLayoutToNetlistTests.cc b/src/db/unit_tests/dbLayoutToNetlistTests.cc index 9cf89998a..3fed04252 100644 --- a/src/db/unit_tests/dbLayoutToNetlistTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistTests.cc @@ -2553,3 +2553,205 @@ TEST(10_Antenna) db::compare_layouts (_this, ly2, au); } +TEST(11_DuplicateInstances) +{ + db::Layout ly; + db::LayerMap lmap; + + unsigned int nwell = define_layer (ly, lmap, 1); + unsigned int active = define_layer (ly, lmap, 2); + unsigned int poly = define_layer (ly, lmap, 3); + unsigned int poly_lbl = define_layer (ly, lmap, 3, 1); + unsigned int diff_cont = define_layer (ly, lmap, 4); + unsigned int poly_cont = define_layer (ly, lmap, 5); + unsigned int metal1 = define_layer (ly, lmap, 6); + unsigned int metal1_lbl = define_layer (ly, lmap, 6, 1); + unsigned int via1 = define_layer (ly, lmap, 7); + unsigned int metal2 = define_layer (ly, lmap, 8); + unsigned int metal2_lbl = define_layer (ly, lmap, 8, 1); + + { + db::LoadLayoutOptions options; + options.get_options ().layer_map = lmap; + options.get_options ().create_other_layers = false; + + std::string fn (tl::testsrc ()); + fn = tl::combine_path (fn, "testdata"); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "device_extract_l1_dup_inst.gds"); + + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly, options); + } + + db::Cell &tc = ly.cell (*ly.begin_top_down ()); + db::LayoutToNetlist l2n (db::RecursiveShapeIterator (ly, tc, std::set ())); + + std::auto_ptr rnwell (l2n.make_layer (nwell, "nwell")); + std::auto_ptr ractive (l2n.make_layer (active, "active")); + std::auto_ptr rpoly (l2n.make_polygon_layer (poly, "poly")); + std::auto_ptr rpoly_lbl (l2n.make_text_layer (poly_lbl, "poly_lbl")); + std::auto_ptr rdiff_cont (l2n.make_polygon_layer (diff_cont, "diff_cont")); + std::auto_ptr rpoly_cont (l2n.make_polygon_layer (poly_cont, "poly_cont")); + std::auto_ptr rmetal1 (l2n.make_polygon_layer (metal1, "metal1")); + std::auto_ptr rmetal1_lbl (l2n.make_text_layer (metal1_lbl, "metal1_lbl")); + std::auto_ptr rvia1 (l2n.make_polygon_layer (via1, "via1")); + std::auto_ptr rmetal2 (l2n.make_polygon_layer (metal2, "metal2")); + std::auto_ptr rmetal2_lbl (l2n.make_text_layer (metal2_lbl, "metal2_lbl")); + + // derived regions + + db::Region rpactive = *ractive & *rnwell; + db::Region rpgate = rpactive & *rpoly; + db::Region rpsd = rpactive - rpgate; + + db::Region rnactive = *ractive - *rnwell; + db::Region rngate = rnactive & *rpoly; + db::Region rnsd = rnactive - rngate; + + db::NetlistDeviceExtractorMOS3Transistor pmos_ex ("PMOS"); + db::NetlistDeviceExtractorMOS3Transistor nmos_ex ("NMOS"); + + // device extraction + + db::NetlistDeviceExtractor::input_layers dl; + + dl["SD"] = &rpsd; + dl["G"] = &rpgate; + dl["P"] = rpoly.get (); // not needed for extraction but to return terminal shapes + l2n.extract_devices (pmos_ex, dl); + + dl["SD"] = &rnsd; + dl["G"] = &rngate; + dl["P"] = rpoly.get (); // not needed for extraction but to return terminal shapes + l2n.extract_devices (nmos_ex, dl); + + // return the computed layers into the original layout and write it for debugging purposes + // NOTE: this will include the device layers too + + unsigned int lgate = ly.insert_layer (db::LayerProperties (10, 0)); // 10/0 -> Gate + unsigned int lsd = ly.insert_layer (db::LayerProperties (11, 0)); // 11/0 -> Source/Drain + unsigned int lpdiff = ly.insert_layer (db::LayerProperties (12, 0)); // 12/0 -> P Diffusion + unsigned int lndiff = ly.insert_layer (db::LayerProperties (13, 0)); // 13/0 -> N Diffusion + unsigned int lpoly = ly.insert_layer (db::LayerProperties (14, 0)); // 14/0 -> Poly with gate terminal + + rpgate.insert_into (&ly, tc.cell_index (), lgate); + rngate.insert_into (&ly, tc.cell_index (), lgate); + rpsd.insert_into (&ly, tc.cell_index (), lsd); + rnsd.insert_into (&ly, tc.cell_index (), lsd); + rpsd.insert_into (&ly, tc.cell_index (), lpdiff); + rnsd.insert_into (&ly, tc.cell_index (), lndiff); + rpoly->insert_into (&ly, tc.cell_index (), lpoly); + + // net extraction + + l2n.register_layer (rpsd, "psd"); + l2n.register_layer (rnsd, "nsd"); + + // 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 + + // create some mess - we have to keep references to the layers to make them not disappear + rmetal1_lbl.reset (0); + rmetal2_lbl.reset (0); + rpoly_lbl.reset (0); + + l2n.extract_netlist (); + + // debug layers produced for nets + // 202/0 -> Active + // 203/0 -> Poly + // 204/0 -> Diffusion contacts + // 205/0 -> Poly contacts + // 206/0 -> Metal1 + // 207/0 -> Via1 + // 208/0 -> Metal2 + // 210/0 -> N source/drain + // 211/0 -> P source/drain + 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, true /*with device cells*/); + 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 the collected test data + + 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_dup_inst_with_rec_nets.gds"); + + db::compare_layouts (_this, ly, au); + + // compare netlist as string + CHECKPOINT (); + db::compare_netlist (_this, *l2n.netlist (), + "circuit RINGO ();\n" + " subcircuit INV2 $1 (IN=$I12,$2=FB,OUT=OSC,$4=VSS,$5=VDD);\n" + " subcircuit INV2 $2 (IN=FB,$2=$I25,OUT=$I1,$4=VSS,$5=VDD);\n" + " subcircuit INV2 $3 (IN=$I1,$2=$I26,OUT=$I2,$4=VSS,$5=VDD);\n" + " subcircuit INV2 $4 (IN=$I2,$2=$I27,OUT=$I3,$4=VSS,$5=VDD);\n" + " subcircuit INV2 $5 (IN=$I3,$2=$I28,OUT=$I4,$4=VSS,$5=VDD);\n" + " subcircuit BLOCK $6 ($1=$I29,$2=VDD,$3=VSS,$4=$I11,$5=$I5,$6=$I4,$7=$I12);\n" + " subcircuit INV2 $7 (IN=$I11,$2=$I29,OUT=$I5,$4=VSS,$5=VDD);\n" + "end;\n" + "circuit INV2 (IN=IN,$2=$2,OUT=OUT,$4=$4,$5=$5);\n" + " device PMOS $1 (S=$2,G=IN,D=$5) (L=0.25,W=0.95,AS=0.49875,AD=0.26125,PS=2.95,PD=1.5);\n" + " device PMOS $2 (S=$5,G=$2,D=OUT) (L=0.25,W=0.95,AS=0.26125,AD=0.49875,PS=1.5,PD=2.95);\n" + " device NMOS $3 (S=$2,G=IN,D=$4) (L=0.25,W=0.95,AS=0.49875,AD=0.26125,PS=2.95,PD=1.5);\n" + " device NMOS $4 (S=$4,G=$2,D=OUT) (L=0.25,W=0.95,AS=0.26125,AD=0.49875,PS=1.5,PD=2.95);\n" + " subcircuit TRANS $1 ($1=$2,$2=$4,$3=IN);\n" + " subcircuit TRANS $2 ($1=$2,$2=$5,$3=IN);\n" + " subcircuit TRANS $3 ($1=$5,$2=OUT,$3=$2);\n" + " subcircuit TRANS $4 ($1=$4,$2=OUT,$3=$2);\n" + "end;\n" + "circuit TRANS ($1=$1,$2=$2,$3=$3);\n" + "end;\n" + "circuit BLOCK ($1=$I21,$2=$I14,$3=$I10,$4=$I9,$5=$I6,$6=$I5,$7=$I3);\n" + " subcircuit INV2 $1 (IN=$I6,$2=$I22,OUT=$I1,$4=$I10,$5=$I14);\n" + " subcircuit INV2 $2 (IN=$I9,$2=$I21,OUT=$I6,$4=$I10,$5=$I14);\n" + " subcircuit INV2 $3 (IN=$I2,$2=$I20,OUT=$I3,$4=$I10,$5=$I14);\n" + " subcircuit INV2 $4 (IN=$I1,$2=$I19,OUT=$I2,$4=$I10,$5=$I14);\n" + " subcircuit INV2 $5 (IN=$I5,$2=$I18,OUT=$I9,$4=$I10,$5=$I14);\n" + "end;\n" + ); +} diff --git a/testdata/algo/device_extract_au1_dup_inst_with_rec_nets.gds b/testdata/algo/device_extract_au1_dup_inst_with_rec_nets.gds new file mode 100644 index 0000000000000000000000000000000000000000..639924e76841d6230304f2cda52d171ef7ec1686 GIT binary patch literal 55954 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 WIr$D9l;oE8^F=?t{?@P0^Y9C^Z=X~E literal 0 HcmV?d00001