From 9fa561803413d91c5a97887ed624203478fe847e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 8 Jan 2019 23:49:12 +0100 Subject: [PATCH] Added test for device combination. --- src/db/unit_tests/dbLayoutToNetlistTests.cc | 279 +++++++++++++++++- .../algo/device_extract_au5_with_rec_nets.gds | Bin 0 -> 73106 bytes testdata/algo/device_extract_l5.gds | Bin 0 -> 4934 bytes testdata/ruby/dbLayoutToNetlist.rb | 1 - 4 files changed, 273 insertions(+), 7 deletions(-) create mode 100644 testdata/algo/device_extract_au5_with_rec_nets.gds create mode 100644 testdata/algo/device_extract_l5.gds diff --git a/src/db/unit_tests/dbLayoutToNetlistTests.cc b/src/db/unit_tests/dbLayoutToNetlistTests.cc index 91ccde1fe..250b91ac0 100644 --- a/src/db/unit_tests/dbLayoutToNetlistTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistTests.cc @@ -40,6 +40,11 @@ namespace { +static std::string qnet_name (const db::Net *net) +{ + return net ? net->qname () : "(null)"; +} + static std::string device_name (const db::Device &device) { if (device.name ().empty ()) { @@ -202,12 +207,6 @@ static void dump_recursive_nets_to_layout (const db::LayoutToNetlist &l2n, db::L } } -// TODO: may be useful elsewhere? -static std::string qnet_name (const db::Net *net) -{ - return net ? net->qname () : "(null)"; -} - static unsigned int define_layer (db::Layout &ly, db::LayerMap &lmap, int gds_layer, int gds_datatype = 0) { unsigned int lid = ly.insert_layer (db::LayerProperties (gds_layer, gds_datatype)); @@ -1358,3 +1357,271 @@ TEST(4_GlobalNetDeviceExtraction) 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"); } + +TEST(5_DeviceExtractionWithDeviceCombination) +{ + 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_l5.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 rbulk (l2n.make_layer (ly.insert_layer ())); + 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 + MOSFET4Extractor pmos_ex ("PMOS", &ly); + MOSFET4Extractor 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 + dl["W"] = rnwell.get (); + 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 + dl["W"] = rbulk.get (); + 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"); + l2n.connect_global (*rbulk, "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 [rbulk.get () ] = ly.insert_layer (db::LayerProperties (214, 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 [rbulk.get () ] = ly.insert_layer (db::LayerProperties (314, 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 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=FB,$5=$I7,$6=OSC,$7=VDD)\n" + " XINV2PAIR $2 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=$I22,$5=FB,$6=$I13,$7=VDD)\n" + " XINV2PAIR $3 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=$I23,$5=$I13,$6=$I5,$7=VDD)\n" + " XINV2PAIR $4 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=$I24,$5=$I5,$6=$I6,$7=VDD)\n" + " XINV2PAIR $5 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=$I25,$5=$I6,$6=$I7,$7=VDD)\n" + "Circuit INV2PAIR (BULK=BULK,$2=$I6,$3=$I5,$4=$I4,$5=$I3,$6=$I2,$7=$I1):\n" + " XINV2 $1 ($1=$I1,IN=$I3,OUT=$I4,VSS=$I5,VDD=$I6,BULK=BULK)\n" + " XINV2 $2 ($1=$I1,IN=$I4,OUT=$I2,VSS=$I5,VDD=$I6,BULK=BULK)\n" + "Circuit INV2 ($1=$1,IN=IN,OUT=OUT,VSS=VSS,VDD=VDD,BULK=BULK):\n" + " DPMOS $1 (S=OUT,G=IN,D=VDD,B=$1) [L=0.25,W=1.75,AS=0.91875,AD=0.48125]\n" + " DPMOS $2 (S=VDD,G=IN,D=OUT,B=$1) [L=0.25,W=1.75,AS=0.48125,AD=0.91875]\n" + " DNMOS $3 (S=OUT,G=IN,D=VSS,B=BULK) [L=0.25,W=1.75,AS=0.91875,AD=0.48125]\n" + " DNMOS $4 (S=VSS,G=IN,D=OUT,B=BULK) [L=0.25,W=1.75,AS=0.48125,AD=0.91875]\n" + " XTRANS $1 ($1=OUT,$2=VSS,$3=IN)\n" + " XTRANS $2 ($1=OUT,$2=VDD,$3=IN)\n" + " XTRANS $3 ($1=OUT,$2=VSS,$3=IN)\n" + " XTRANS $4 ($1=OUT,$2=VDD,$3=IN)\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_au5_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:BULK,VSS"); + + // 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,'BULK,VSS'='BULK,VSS'):\n" + " XINV2PAIR $1 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=FB,$5=$I7,$6=OSC,$7=VDD)\n" + " XINV2PAIR $2 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=(null),$5=FB,$6=$I13,$7=VDD)\n" + " XINV2PAIR $3 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=(null),$5=$I13,$6=$I5,$7=VDD)\n" + " XINV2PAIR $4 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=(null),$5=$I5,$6=$I6,$7=VDD)\n" + " XINV2PAIR $5 (BULK='BULK,VSS',$2=VDD,$3='BULK,VSS',$4=(null),$5=$I6,$6=$I7,$7=VDD)\n" + "Circuit INV2PAIR (BULK=BULK,$2=$I6,$3=$I5,$4=$I4,$5=$I3,$6=$I2,$7=$I1):\n" + " XINV2 $1 ($1=$I1,IN=$I3,OUT=$I4,VSS=$I5,VDD=$I6,BULK=BULK)\n" + " XINV2 $2 ($1=$I1,IN=$I4,OUT=$I2,VSS=$I5,VDD=$I6,BULK=BULK)\n" + "Circuit INV2 ($1=$1,IN=IN,OUT=OUT,VSS=VSS,VDD=VDD,BULK=BULK):\n" + " DPMOS $1 (S=OUT,G=IN,D=VDD,B=$1) [L=0.25,W=3.5,AS=1.4,AD=1.4]\n" + " DNMOS $3 (S=OUT,G=IN,D=VSS,B=BULK) [L=0.25,W=3.5,AS=1.4,AD=1.4]\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)"); +} diff --git a/testdata/algo/device_extract_au5_with_rec_nets.gds b/testdata/algo/device_extract_au5_with_rec_nets.gds new file mode 100644 index 0000000000000000000000000000000000000000..8cdea5c7454c884b42d682408306e032a8c3457c GIT binary patch literal 73106 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�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