From bc4f9efa5d2a07affc5d0055e87c5bf5d655cc72 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 5 Jan 2019 23:21:37 +0100 Subject: [PATCH] One more test for probing with a slightly more complex hierarchy. --- src/db/unit_tests/dbLayoutToNetlistTests.cc | 242 ++++++++++++++++++ .../algo/device_extract_au2_with_rec_nets.gds | Bin 0 -> 40390 bytes testdata/algo/device_extract_l2.gds | Bin 0 -> 2634 bytes 3 files changed, 242 insertions(+) create mode 100644 testdata/algo/device_extract_au2_with_rec_nets.gds create mode 100644 testdata/algo/device_extract_l2.gds diff --git a/src/db/unit_tests/dbLayoutToNetlistTests.cc b/src/db/unit_tests/dbLayoutToNetlistTests.cc index b1ace3ea0..10ecc840c 100644 --- a/src/db/unit_tests/dbLayoutToNetlistTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistTests.cc @@ -432,3 +432,245 @@ TEST(1_Basic) 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"); } + +TEST(2_Probing) +{ + 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_l2.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 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 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; + + // return the computed layers into the original layout and write it for debugging purposes + + 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 + + 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); + + // 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 (*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); + 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 (), + "Circuit RINGO ():\n" + " XINV2PAIR $1 ($1=FB,$2=VDD,$3=VSS,$4=$I3,$5=OSC)\n" + " XINV2PAIR $2 ($1=$I18,$2=VDD,$3=VSS,$4=FB,$5=$I9)\n" + " XINV2PAIR $3 ($1=$I19,$2=VDD,$3=VSS,$4=$I9,$5=$I1)\n" + " XINV2PAIR $4 ($1=$I20,$2=VDD,$3=VSS,$4=$I1,$5=$I2)\n" + " XINV2PAIR $5 ($1=$I21,$2=VDD,$3=VSS,$4=$I2,$5=$I3)\n" + "Circuit INV2PAIR ($1=$I7,$2=$I5,$3=$I4,$4=$I2,$5=$I1):\n" + " XINV2 $1 (IN=$I3,$2=$I7,OUT=$I1,$4=$I4,$5=$I5)\n" + " XINV2 $2 (IN=$I2,$2=$I6,OUT=$I3,$4=$I4,$5=$I5)\n" + "Circuit INV2 (IN=IN,$2=$2,OUT=OUT,$4=$4,$5=$5):\n" + " DPMOS $1 (S=$2,G=IN,D=$5) [L=0.25,W=0.95,AS=0.49875,AD=0.26125]\n" + " DPMOS $2 (S=$5,G=$2,D=OUT) [L=0.25,W=0.95,AS=0.26125,AD=0.49875]\n" + " DNMOS $3 (S=$2,G=IN,D=$4) [L=0.25,W=0.95,AS=0.49875,AD=0.26125]\n" + " DNMOS $4 (S=$4,G=$2,D=OUT) [L=0.25,W=0.95,AS=0.26125,AD=0.49875]\n" + " XTRANS $1 ($1=$2,$2=$4,$3=IN)\n" + " XTRANS $2 ($1=$2,$2=$5,$3=IN)\n" + " XTRANS $3 ($1=$5,$2=OUT,$3=$2)\n" + " XTRANS $4 ($1=$4,$2=OUT,$3=$2)\n" + "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:$I18"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (6.4, 1.0))), "INV2PAIR:$I3"); + + // 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,VSS=VSS,VDD=VDD):\n" + " XINV2PAIR $1 ($1=FB,$2=VDD,$3=VSS,$4=$I3,$5=OSC)\n" + " XINV2PAIR $2 ($1=(null),$2=VDD,$3=VSS,$4=FB,$5=$I9)\n" + " XINV2PAIR $3 ($1=(null),$2=VDD,$3=VSS,$4=$I9,$5=$I1)\n" + " XINV2PAIR $4 ($1=(null),$2=VDD,$3=VSS,$4=$I1,$5=$I2)\n" + " XINV2PAIR $5 ($1=(null),$2=VDD,$3=VSS,$4=$I2,$5=$I3)\n" + "Circuit INV2PAIR ($1=$I7,$2=$I5,$3=$I4,$4=$I2,$5=$I1):\n" + " XINV2 $1 (IN=$I3,$2=$I7,OUT=$I1,$4=$I4,$5=$I5)\n" + " XINV2 $2 (IN=$I2,$2=(null),OUT=$I3,$4=$I4,$5=$I5)\n" + "Circuit INV2 (IN=IN,$2=$2,OUT=OUT,$4=$4,$5=$5):\n" + " DPMOS $1 (S=$2,G=IN,D=$5) [L=0.25,W=0.95,AS=0.49875,AD=0.26125]\n" + " DPMOS $2 (S=$5,G=$2,D=OUT) [L=0.25,W=0.95,AS=0.26125,AD=0.49875]\n" + " DNMOS $3 (S=$2,G=IN,D=$4) [L=0.25,W=0.95,AS=0.49875,AD=0.26125]\n" + " 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 + 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:$I7"); + EXPECT_EQ (qnet_name (l2n.probe_net (*rmetal1, db::DPoint (6.4, 1.0))), "INV2PAIR:$I3"); +} diff --git a/testdata/algo/device_extract_au2_with_rec_nets.gds b/testdata/algo/device_extract_au2_with_rec_nets.gds new file mode 100644 index 0000000000000000000000000000000000000000..ddd8db94cf6c971b9b32e2f9cee6394876bdeae6 GIT binary patch literal 40390 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 literal 0 HcmV?d00001 diff --git a/testdata/algo/device_extract_l2.gds b/testdata/algo/device_extract_l2.gds new file mode 100644 index 0000000000000000000000000000000000000000..9a9df7ea0453981cd8521178eaa4321ac4c0b154 GIT binary patch literal 2634 zcma)-Pe@cz6vofId2`?V(@dqXT$B{FFoQB91W~g;gQ4L}QX=TGO%NmzVG)#INf6Y^ zT11pB+y#N4tB43!ErTM2)`8&4A?Nj-ci!>6C(cu0{LOste)rsa&$;K3AKP~!*=ZtE zW06Is)*_?~2XUTY=KwNbg{P2}Z%`={Aei4l2f4kR8DVFv8vd6gNqjL~`g9qI{% zc9^)q_3G`4=o^Z095efLP}tZlDKit*&KsxGihYeTi82$&+e@YD%ozU)VztXD+pi^G zO4XC?5x?e3?%Nt_#31e(Qpfk^uTphpH9tl1e&aghO-gMnc)#KK` zK8hkNxh~V;+DDBjRX3%``qSb2Ql5sV%XQ1s*vn_4)Lo_O%s4;t)h7P@NAW9F&$LDS ziD-PT6YURGs?c{h4|3XWxrosd{EU-k<9f zfBv-im8$1D#h>5ocl#{76}_Is-3|FMP3T&YQRo>uYO@=po6O+V5)#C3uO0eeUi#pb zp285>Ib5oG>5J(Geq$!6nY)j9CvzV}m*$S+|K82~cn=R11}+X_C73-gvwP7vLP{d= zy^`KCKFQqt9j+(sj$S|6dLPSQ!8N>&jDcpx=0aZ6Mk@z7$(e+d87dlf||8e#URgd$J-4;KS zed9L+vfqF1oxfj?*nd!IW2+dSCa$#NIlt1j8;45w>-Iu#q;4a#@hJRZw&7xNhuQGX O