From a4f0fd665e648ac4f03f8074830eb5a904cd78e5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 6 Jan 2019 17:50:51 +0100 Subject: [PATCH] Provided a solution for connectivity through global nets. --- src/db/unit_tests/dbLayoutToNetlistTests.cc | 306 ++++++++++++++++-- .../algo/device_extract_au3_with_rec_nets.gds | Bin 0 -> 49126 bytes testdata/algo/device_extract_l3.gds | Bin 0 -> 4554 bytes 3 files changed, 288 insertions(+), 18 deletions(-) create mode 100644 testdata/algo/device_extract_au3_with_rec_nets.gds create mode 100644 testdata/algo/device_extract_l3.gds diff --git a/src/db/unit_tests/dbLayoutToNetlistTests.cc b/src/db/unit_tests/dbLayoutToNetlistTests.cc index f4ff086cc..e631f9b26 100644 --- a/src/db/unit_tests/dbLayoutToNetlistTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistTests.cc @@ -344,6 +344,15 @@ TEST(1_Basic) 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_with_rec_nets.gds"); + + db::compare_layouts (_this, ly, au); + // compare netlist as string EXPECT_EQ (l2n.netlist ()->to_string (), "Circuit RINGO ():\n" @@ -519,15 +528,6 @@ TEST(1_Basic) " DNMOS $4 (S=$4,G=$2,D=OUT) [L=0.25,W=0.95,AS=0.26125,AD=0.49875]\n" ); - // 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_with_rec_nets.gds"); - - db::compare_layouts (_this, ly, au); - // do some probing after purging // top level @@ -723,6 +723,15 @@ TEST(2_Probing) "Circuit TRANS ($1=$1,$2=$2,$3=$3):\n" ); + // 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_au2_with_rec_nets.gds"); + + db::compare_layouts (_this, ly, au); + // do some probing before purging // top level @@ -761,15 +770,6 @@ TEST(2_Probing) " DNMOS $4 (S=$4,G=$2,D=OUT) [L=0.25,W=0.95,AS=0.26125,AD=0.49875]\n" ); - // 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_au2_with_rec_nets.gds"); - - db::compare_layouts (_this, ly, au); - // do some probing after purging // top level @@ -784,3 +784,273 @@ TEST(2_Probing) EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (2.6, 1.0))), "INV2PAIR:$I7"); EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (6.4, 1.0))), "INV2PAIR:$I3"); } + +TEST(3_GlobalNetConnections) +{ + db::Layout ly; + db::LayerMap lmap; + + unsigned int nwell = define_layer (ly, lmap, 1); + unsigned int active = define_layer (ly, lmap, 2); + unsigned int pplus = define_layer (ly, lmap, 10); + unsigned int nplus = define_layer (ly, lmap, 11); + 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_l3.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)); + std::auto_ptr ractive (l2n.make_layer (active)); + std::auto_ptr rpplus (l2n.make_layer (pplus)); + std::auto_ptr rnplus (l2n.make_layer (nplus)); + std::auto_ptr rpoly (l2n.make_polygon_layer (poly)); + std::auto_ptr rpoly_lbl (l2n.make_text_layer (poly_lbl)); + std::auto_ptr rdiff_cont (l2n.make_polygon_layer (diff_cont)); + std::auto_ptr rpoly_cont (l2n.make_polygon_layer (poly_cont)); + std::auto_ptr rmetal1 (l2n.make_polygon_layer (metal1)); + std::auto_ptr rmetal1_lbl (l2n.make_text_layer (metal1_lbl)); + std::auto_ptr rvia1 (l2n.make_polygon_layer (via1)); + std::auto_ptr rmetal2 (l2n.make_polygon_layer (metal2)); + std::auto_ptr rmetal2_lbl (l2n.make_text_layer (metal2_lbl)); + + // derived regions + + db::Region ractive_in_nwell = *ractive & *rnwell; + db::Region rpactive = ractive_in_nwell & *rpplus; + db::Region rntie = ractive_in_nwell & *rnplus; + db::Region rpgate = rpactive & *rpoly; + db::Region rpsd = rpactive - rpgate; + + db::Region ractive_outside_nwell = *ractive - *rnwell; + db::Region rnactive = ractive_outside_nwell & *rnplus; + db::Region rptie = ractive_outside_nwell & *rpplus; + db::Region rngate = rnactive & *rpoly; + db::Region rnsd = rnactive - rngate; + + // return the computed layers into the original layout and write it for debugging purposes + + unsigned int lgate = ly.insert_layer (db::LayerProperties (20, 0)); // 20/0 -> Gate + unsigned int lsd = ly.insert_layer (db::LayerProperties (21, 0)); // 21/0 -> Source/Drain + unsigned int lpdiff = ly.insert_layer (db::LayerProperties (22, 0)); // 22/0 -> P Diffusion + unsigned int lndiff = ly.insert_layer (db::LayerProperties (23, 0)); // 23/0 -> N Diffusion + unsigned int lptie = ly.insert_layer (db::LayerProperties (24, 0)); // 24/0 -> P Tie + unsigned int lntie = ly.insert_layer (db::LayerProperties (25, 0)); // 25/0 -> N Tie + + 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); + rpsd.insert_into (&ly, tc.cell_index (), lptie); + rnsd.insert_into (&ly, tc.cell_index (), lntie); + + // NOTE: the device extractor will add more debug layers for the transistors: + // 20/0 -> Diffusion + // 21/0 -> Gate + MOSFETExtractor pmos_ex ("PMOS", &ly); + MOSFETExtractor nmos_ex ("NMOS", &ly); + + // 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); + + // net extraction + + // Intra-layer + l2n.connect (rpsd); + l2n.connect (rnsd); + l2n.connect (*rnwell); + l2n.connect (*rpoly); + l2n.connect (*rdiff_cont); + l2n.connect (*rpoly_cont); + l2n.connect (*rmetal1); + l2n.connect (*rvia1); + l2n.connect (*rmetal2); + l2n.connect (rptie); + l2n.connect (rntie); + // 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 (*rdiff_cont, rptie); + l2n.connect (*rdiff_cont, rntie); + l2n.connect (*rnwell, rntie); + 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 + // Global + l2n.connect_global (rptie, "BULK"); + + // 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 + // 201/0 -> Well + // 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 + // 212/0 -> N tie + // 213/0 -> P tie + 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 [&rptie ] = ly.insert_layer (db::LayerProperties (212, 0)); + dump_map [&rntie ] = ly.insert_layer (db::LayerProperties (213, 0)); + dump_map [rnwell.get () ] = ly.insert_layer (db::LayerProperties (201, 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, 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 [&rptie ] = ly.insert_layer (db::LayerProperties (312, 0)); + dump_map [&rntie ] = ly.insert_layer (db::LayerProperties (313, 0)); + dump_map [rnwell.get () ] = ly.insert_layer (db::LayerProperties (301, 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 (), + "Circuit RINGO ():\n" + " XINV2PAIR $1 ($1=VSS,$2=VSS,$3=FB,$4=VDD,$5=VSS,$6=$I7,$7=OSC,$8=VDD)\n" + " XINV2PAIR $2 ($1=VSS,$2=VSS,$3=$I22,$4=VDD,$5=VSS,$6=FB,$7=$I13,$8=VDD)\n" + " XINV2PAIR $3 ($1=VSS,$2=VSS,$3=$I23,$4=VDD,$5=VSS,$6=$I13,$7=$I5,$8=VDD)\n" + " XINV2PAIR $4 ($1=VSS,$2=VSS,$3=$I24,$4=VDD,$5=VSS,$6=$I5,$7=$I6,$8=VDD)\n" + " XINV2PAIR $5 ($1=VSS,$2=VSS,$3=$I25,$4=VDD,$5=VSS,$6=$I6,$7=$I7,$8=VDD)\n" + "Circuit INV2PAIR ($1=$I10,$2=$I9,$3=$I8,$4=$I6,$5=$I5,$6=$I3,$7=$I2,$8=$I1):\n" + " XINV2 $1 ($1=$I1,IN=$I4,$3=$I8,BULK=$I10,OUT=$I2,VSS=$I5,VDD=$I6)\n" + " XINV2 $2 ($1=$I1,IN=$I3,$3=$I7,BULK=$I9,OUT=$I4,VSS=$I5,VDD=$I6)\n" + "Circuit INV2 ($1=$1,IN=IN,$3=$3,BULK=BULK,OUT=OUT,VSS=VSS,VDD=VDD):\n" + " DPMOS $1 (S=$3,G=IN,D=VDD) [L=0.25,W=0.95,AS=0.49875,AD=0.26125]\n" + " DPMOS $2 (S=VDD,G=$3,D=OUT) [L=0.25,W=0.95,AS=0.26125,AD=0.49875]\n" + " DNMOS $3 (S=$3,G=IN,D=VSS) [L=0.25,W=0.95,AS=0.49875,AD=0.26125]\n" + " DNMOS $4 (S=VSS,G=$3,D=OUT) [L=0.25,W=0.95,AS=0.26125,AD=0.49875]\n" + " XTRANS $1 ($1=$3,$2=VSS,$3=IN)\n" + " XTRANS $2 ($1=$3,$2=VDD,$3=IN)\n" + " XTRANS $3 ($1=VDD,$2=OUT,$3=$3)\n" + " XTRANS $4 ($1=VSS,$2=OUT,$3=$3)\n" + "Circuit TRANS ($1=$1,$2=$2,$3=$3):\n" + ); + + // 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_au3_with_rec_nets.gds"); + + db::compare_layouts (_this, ly, au); + + // 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:$I22"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (6.4, 1.0))), "INV2PAIR:$I4"); + + // doesn't do anything here, but we test that this does not destroy anything: + l2n.netlist ()->combine_devices (); + + // make pins for named nets of top-level circuits - this way they are not purged + l2n.netlist ()->make_top_level_pins (); + l2n.netlist ()->purge (); + + // compare netlist as string + EXPECT_EQ (l2n.netlist ()->to_string (), + "Circuit RINGO (FB=FB,OSC=OSC,VDD=VDD,VSS=VSS):\n" + " XINV2PAIR $1 ($1=VSS,$2=VSS,$3=FB,$4=VDD,$5=VSS,$6=$I7,$7=OSC,$8=VDD)\n" + " XINV2PAIR $2 ($1=VSS,$2=VSS,$3=(null),$4=VDD,$5=VSS,$6=FB,$7=$I13,$8=VDD)\n" + " XINV2PAIR $3 ($1=VSS,$2=VSS,$3=(null),$4=VDD,$5=VSS,$6=$I13,$7=$I5,$8=VDD)\n" + " XINV2PAIR $4 ($1=VSS,$2=VSS,$3=(null),$4=VDD,$5=VSS,$6=$I5,$7=$I6,$8=VDD)\n" + " XINV2PAIR $5 ($1=VSS,$2=VSS,$3=(null),$4=VDD,$5=VSS,$6=$I6,$7=$I7,$8=VDD)\n" + "Circuit INV2PAIR ($1=$I10,$2=$I9,$3=$I8,$4=$I6,$5=$I5,$6=$I3,$7=$I2,$8=$I1):\n" + " XINV2 $1 ($1=$I1,IN=$I4,$3=$I8,BULK=$I10,OUT=$I2,VSS=$I5,VDD=$I6)\n" + " XINV2 $2 ($1=$I1,IN=$I3,$3=(null),BULK=$I9,OUT=$I4,VSS=$I5,VDD=$I6)\n" + "Circuit INV2 ($1=(null),IN=IN,$3=$3,BULK=(null),OUT=OUT,VSS=VSS,VDD=VDD):\n" + " DPMOS $1 (S=$3,G=IN,D=VDD) [L=0.25,W=0.95,AS=0.49875,AD=0.26125]\n" + " DPMOS $2 (S=VDD,G=$3,D=OUT) [L=0.25,W=0.95,AS=0.26125,AD=0.49875]\n" + " DNMOS $3 (S=$3,G=IN,D=VSS) [L=0.25,W=0.95,AS=0.49875,AD=0.26125]\n" + " DNMOS $4 (S=VSS,G=$3,D=OUT) [L=0.25,W=0.95,AS=0.26125,AD=0.49875]\n" + ); + + // 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))), "INV2PAIR:$I8"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (6.4, 1.0))), "INV2PAIR:$I4"); +} diff --git a/testdata/algo/device_extract_au3_with_rec_nets.gds b/testdata/algo/device_extract_au3_with_rec_nets.gds new file mode 100644 index 0000000000000000000000000000000000000000..9ce7a8522f323e714516ce1c2ea0cbb35cef025e GIT binary patch literal 49126 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 Yr?D4)@!qp|Zwd8VdOyPVw^)?_0C~PB;Q#;t literal 0 HcmV?d00001