From 44edd8d26a576966016ab9cd0b1cedbc48223cd9 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 16 Mar 2024 00:02:44 +0100 Subject: [PATCH] Added a regression test for netlist extractor with soft connections --- src/db/unit_tests/dbNetlistExtractorTests.cc | 339 +++++++++++++++++++ testdata/algo/soft_connections.gds | Bin 0 -> 13710 bytes testdata/algo/soft_connections_au.gds | Bin 0 -> 26174 bytes 3 files changed, 339 insertions(+) create mode 100644 testdata/algo/soft_connections.gds create mode 100644 testdata/algo/soft_connections_au.gds diff --git a/src/db/unit_tests/dbNetlistExtractorTests.cc b/src/db/unit_tests/dbNetlistExtractorTests.cc index 916169102..494e8dc32 100644 --- a/src/db/unit_tests/dbNetlistExtractorTests.cc +++ b/src/db/unit_tests/dbNetlistExtractorTests.cc @@ -101,6 +101,11 @@ static void dump_nets_to_layout (const db::Netlist &nl, const db::hier_clusters< for (db::Net::const_terminal_iterator t = n->begin_terminals (); t != n->end_terminals (); ++t) { const db::NetTerminalRef &tref = *t; + + if (! tref.device ()->device_abstract ()) { + continue; + } + db::cell_index_type dci = tref.device ()->device_abstract ()->cell_index (); db::DCplxTrans dtr = tref.device ()->trans (); @@ -121,6 +126,10 @@ static void dump_nets_to_layout (const db::Netlist &nl, const db::hier_clusters< for (db::Circuit::const_device_iterator d = c->begin_devices (); d != c->end_devices (); ++d) { + if (! d->device_abstract ()) { + continue; + } + std::vector original_device_cells; db::cell_index_type dci = d->device_abstract ()->cell_index (); @@ -3176,6 +3185,336 @@ TEST(14_JoinNets) db::compare_layouts (_this, ly, au); } +static +void annotate_soft_connections (db::Netlist &netlist, const db::hier_clusters &net_clusters) +{ + db::DeviceClassDiode *soft_diode = new db::DeviceClassDiode (); + soft_diode->set_name ("SOFT"); + netlist.add_device_class (soft_diode); + + for (auto c = netlist.begin_bottom_up (); c != netlist.end_bottom_up (); ++c) { + + auto clusters = net_clusters.clusters_per_cell (c->cell_index ()); + + for (auto n = c->begin_nets (); n != c->end_nets (); ++n) { + + auto soft_connections = clusters.upward_soft_connections (n->cluster_id ()); + for (auto sc = soft_connections.begin (); sc != soft_connections.end (); ++sc) { + + db::Device *sc_device = new db::Device (soft_diode); + c->add_device (sc_device); + + auto nn = c->net_by_cluster_id (*sc); + if (nn) { + sc_device->connect_terminal (db::DeviceClassDiode::terminal_id_C, n.operator-> ()); + sc_device->connect_terminal (db::DeviceClassDiode::terminal_id_A, nn); + } + + } + + } + + } +} + +TEST(15_SoftConnections) +{ + db::Layout ly; + db::LayerMap lmap; + + unsigned int nwell = define_layer (ly, lmap, 1); + unsigned int diff = define_layer (ly, lmap, 2); + unsigned int pplus = define_layer (ly, lmap, 3); + unsigned int nplus = define_layer (ly, lmap, 4); + unsigned int poly = define_layer (ly, lmap, 5); + unsigned int contact = define_layer (ly, lmap, 8); + unsigned int metal1 = define_layer (ly, lmap, 9); + unsigned int via1 = define_layer (ly, lmap, 10); + unsigned int metal2 = define_layer (ly, lmap, 11); + + { + db::LoadLayoutOptions options; + options.get_options ().layer_map = lmap; + options.get_options ().create_other_layers = false; + + std::string fn (tl::testdata ()); + fn = tl::combine_path (fn, "algo"); + fn = tl::combine_path (fn, "soft_connections.gds"); + + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly, options); + } + + db::Cell &tc = ly.cell (*ly.begin_top_down ()); + + db::DeepShapeStore dss; + dss.set_text_enlargement (1); + dss.set_text_property_name (tl::Variant ("LABEL")); + + // original layers + db::Region rnwell (db::RecursiveShapeIterator (ly, tc, nwell), dss); + db::Region rdiff (db::RecursiveShapeIterator (ly, tc, diff), dss); + db::Region rpplus (db::RecursiveShapeIterator (ly, tc, pplus), dss); + db::Region rnplus (db::RecursiveShapeIterator (ly, tc, nplus), dss); + db::Region rpoly (db::RecursiveShapeIterator (ly, tc, poly), dss); + db::Region rcontact (db::RecursiveShapeIterator (ly, tc, contact), dss); + db::Region rmetal1 (db::RecursiveShapeIterator (ly, tc, metal1), dss); + db::Region rvia1 (db::RecursiveShapeIterator (ly, tc, via1), dss); + db::Region rmetal2 (db::RecursiveShapeIterator (ly, tc, metal2), dss); + + // derived regions + + db::Region rdiff_in_nwell = rdiff & rnwell; + db::Region rpdiff = rdiff_in_nwell - rnplus; + db::Region rntie = rdiff_in_nwell & rnplus; + db::Region rpgate = rpdiff & rpoly; + db::Region rpsd = rpdiff - rpgate; + + db::Region rdiff_outside_nwell = rdiff - rnwell; + db::Region rndiff = rdiff_outside_nwell - rpplus; + db::Region rptie = rdiff_outside_nwell & rpplus; + db::Region rngate = rndiff & rpoly; + db::Region rnsd = rndiff - rngate; + + // Global + + db::Region bulk (dss); + + // return the computed layers into the original layout and write it for debugging purposes + + unsigned int lpgate = ly.insert_layer (db::LayerProperties (10, 0)); // 10/0 -> Gate (p) + unsigned int lngate = ly.insert_layer (db::LayerProperties (11, 0)); // 11/0 -> Gate (n) + unsigned int lpsd = ly.insert_layer (db::LayerProperties (12, 0)); // 12/0 -> Source/Drain (p) + unsigned int lnsd = ly.insert_layer (db::LayerProperties (13, 0)); // 13/0 -> Source/Drain (n) + unsigned int lpdiff = ly.insert_layer (db::LayerProperties (14, 0)); // 14/0 -> P Diffusion + unsigned int lndiff = ly.insert_layer (db::LayerProperties (15, 0)); // 15/0 -> N Diffusion + unsigned int lptie = ly.insert_layer (db::LayerProperties (16, 0)); // 16/0 -> P Tie + unsigned int lntie = ly.insert_layer (db::LayerProperties (17, 0)); // 17/0 -> N Tie + + rpgate.insert_into (&ly, tc.cell_index (), lpgate); + rngate.insert_into (&ly, tc.cell_index (), lngate); + rpsd.insert_into (&ly, tc.cell_index (), lpsd); + rnsd.insert_into (&ly, tc.cell_index (), lnsd); + rpdiff.insert_into (&ly, tc.cell_index (), lpdiff); + rndiff.insert_into (&ly, tc.cell_index (), lndiff); + rptie.insert_into (&ly, tc.cell_index (), lptie); + rntie.insert_into (&ly, tc.cell_index (), lntie); + + // perform the extraction + + db::Netlist nl; + db::hier_clusters cl; + + db::NetlistDeviceExtractorMOS4Transistor pmos_ex ("PMOS"); + db::NetlistDeviceExtractorMOS4Transistor nmos_ex ("NMOS"); + + db::NetlistDeviceExtractor::input_layers dl; + + dl["SD"] = &rpsd; + dl["G"] = &rpgate; + dl["W"] = &rnwell; + dl["P"] = &rpoly; // not needed for extraction but to return terminal shapes + pmos_ex.extract (dss, 0, dl, nl, cl); + + dl["SD"] = &rnsd; + dl["G"] = &rngate; + dl["W"] = &bulk; + dl["P"] = &rpoly; // not needed for extraction but to return terminal shapes + nmos_ex.extract (dss, 0, dl, nl, cl); + + // perform the net extraction + + db::NetlistExtractor net_ex; + + db::Connectivity conn; + + // Global nets + conn.connect_global (bulk, "BULK"); + conn.soft_connect_global (rptie, "BULK"); + + // Intra-layer + conn.connect (rpsd); + conn.connect (rnsd); + conn.connect (rptie); + conn.connect (rntie); + conn.connect (rnwell); + conn.connect (rpoly); + conn.connect (rcontact); + conn.connect (rmetal1); + conn.connect (rvia1); + conn.connect (rmetal2); + // Inter-layer + conn.soft_connect (rcontact, rpsd); + conn.soft_connect (rcontact, rnsd); + conn.soft_connect (rntie, rnwell); + conn.soft_connect (rcontact, rptie); + conn.soft_connect (rcontact, rntie); + conn.soft_connect (rcontact, rpoly); + conn.connect (rcontact, rmetal1); + conn.connect (rvia1, rmetal1); + conn.connect (rvia1, rmetal2); + + // extract the nets +#if 0 // @@@ + std::list > jn; + + jn.push_back (std::set ()); + jn.back ().insert ("BULK"); + jn.back ().insert ("VSS"); + + jn.push_back (std::set ()); + jn.back ().insert ("NWELL"); + jn.back ().insert ("VDD"); + + net_ex.set_joined_nets ("INV2", jn); + + std::list gp; + gp.push_back (tl::GlobPattern ("NEXT")); + gp.push_back (tl::GlobPattern ("FB")); + net_ex.set_joined_net_names (gp); +#endif + + net_ex.extract_nets (dss, 0, conn, nl, cl); + + annotate_soft_connections (nl, cl); + + EXPECT_EQ (all_net_names_unique (nl), true); + + // debug layers produced for nets + std::map dump_map; + dump_map [layer_of (bulk) ] = ly.insert_layer (db::LayerProperties (200, 0)); + dump_map [layer_of (rnwell) ] = ly.insert_layer (db::LayerProperties (201, 0)); + dump_map [layer_of (rpsd) ] = ly.insert_layer (db::LayerProperties (202, 0)); + dump_map [layer_of (rnsd) ] = ly.insert_layer (db::LayerProperties (203, 0)); + dump_map [layer_of (rpoly) ] = ly.insert_layer (db::LayerProperties (204, 0)); + dump_map [layer_of (rcontact) ] = ly.insert_layer (db::LayerProperties (205, 0)); + dump_map [layer_of (rmetal1) ] = ly.insert_layer (db::LayerProperties (206, 0)); + dump_map [layer_of (rvia1) ] = ly.insert_layer (db::LayerProperties (207, 0)); + dump_map [layer_of (rmetal2) ] = ly.insert_layer (db::LayerProperties (208, 0)); + + // write nets to layout + db::CellMapping cm = dss.cell_mapping_to_original (0, &ly, tc.cell_index ()); + dump_nets_to_layout (nl, cl, ly, dump_map, cm, true); + + // compare netlist as string + CHECKPOINT (); + db::compare_netlist (_this, nl, + "circuit RINGO ();\n" + " subcircuit ND2X1 $1 (NWELL=$4,B=FB,A=ENABLE,VDD=VDD,OUT=$5,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $2 (NWELL=$4,IN=$14,VDD=VDD,OUT=FB,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $3 (NWELL=$4,IN=FB,VDD=$I17,OUT=OUT,VSS=$I27,BULK=BULK);\n" + " subcircuit TIE $4 (NWELL=$4,VSS=$I27,VDD=$I17,BULK=BULK);\n" + " subcircuit EMPTY $5 ($1=$4,$2=$I27,$3=$I17);\n" + " subcircuit TIE $6 (NWELL=$4,VSS=VSS,VDD=VDD,BULK=BULK);\n" + " subcircuit INVX1 $7 (NWELL=$4,IN=$5,VDD=VDD,OUT=$6,VSS=VSS,BULK=BULK);\n" + " subcircuit EMPTY $8 ($1=$4,$2=VSS,$3=VDD);\n" + " subcircuit INVX1 $9 (NWELL=$4,IN=$6,VDD=VDD,OUT=$7,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $10 (NWELL=$4,IN=$7,VDD=VDD,OUT=$8,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $11 (NWELL=$4,IN=$8,VDD=VDD,OUT=$9,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $12 (NWELL=$4,IN=$9,VDD=VDD,OUT=$10,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $13 (NWELL=$4,IN=$10,VDD=VDD,OUT=$11,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $14 (NWELL=$4,IN=$11,VDD=VDD,OUT=$12,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $15 (NWELL=$4,IN=$12,VDD=VDD,OUT=$15,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $16 (NWELL=$4,IN=$15,VDD=VDD,OUT=$14,VSS=VSS,BULK=BULK);\n" + "end;\n" + "circuit ND2X1 (NWELL=NWELL,B=B,A=A,VDD=VDD,OUT=OUT,VSS=VSS,BULK=BULK);\n" + " device PMOS $1 (S=$7,G=$4,D=$9,B=NWELL) (L=0.25,W=1.5,AS=0.6375,AD=0.3375,PS=3.85,PD=1.95);\n" + " device PMOS $2 (S=$9,G=$2,D=$6,B=NWELL) (L=0.25,W=1.5,AS=0.3375,AD=0.6375,PS=1.95,PD=3.85);\n" + " device NMOS $3 (S=$13,G=$4,D=$14,B=BULK) (L=0.25,W=0.95,AS=0.40375,AD=0.21375,PS=2.75,PD=1.4);\n" + " device NMOS $4 (S=$14,G=$2,D=$11,B=BULK) (L=0.25,W=0.95,AS=0.21375,AD=0.40375,PS=1.4,PD=2.75);\n" + " device SOFT $5 (A=B,C=$2) (A=0,P=0);\n" + " device SOFT $6 (A=A,C=$4) (A=0,P=0);\n" + " device SOFT $7 (A=OUT,C=$6) (A=0,P=0);\n" + " device SOFT $8 (A=OUT,C=$7) (A=0,P=0);\n" + " device SOFT $9 (A=VDD,C=$9) (A=0,P=0);\n" + " device SOFT $10 (A=OUT,C=$11) (A=0,P=0);\n" + " device SOFT $11 (A=VSS,C=$13) (A=0,P=0);\n" + "end;\n" + "circuit TIE (NWELL=NWELL,VSS=VSS,VDD=VDD,BULK=BULK);\n" + " device SOFT $1 (A=$2,C=NWELL) (A=0,P=0);\n" + " device SOFT $2 (A=VDD,C=$2) (A=0,P=0);\n" + " device SOFT $3 (A=VSS,C=$3) (A=0,P=0);\n" + " device SOFT $4 (A=$3,C=BULK) (A=0,P=0);\n" + "end;\n" + "circuit EMPTY ($1=$1,$2=$2,$3=$3);\n" + "end;\n" + "circuit INVX1 (NWELL=NWELL,IN=IN,VDD=VDD,OUT=OUT,VSS=VSS,BULK=BULK);\n" + " device PMOS $1 (S=$5,G=$2,D=$7,B=NWELL) (L=0.25,W=1.5,AS=0.6375,AD=0.6375,PS=3.85,PD=3.85);\n" + " device NMOS $2 (S=$10,G=$2,D=$8,B=BULK) (L=0.25,W=0.95,AS=0.40375,AD=0.40375,PS=2.75,PD=2.75);\n" + " device SOFT $3 (A=IN,C=$2) (A=0,P=0);\n" + " device SOFT $4 (A=VDD,C=$5) (A=0,P=0);\n" + " device SOFT $5 (A=OUT,C=$7) (A=0,P=0);\n" + " device SOFT $6 (A=OUT,C=$8) (A=0,P=0);\n" + " device SOFT $7 (A=VSS,C=$10) (A=0,P=0);\n" + "end;\n" + ); + + // doesn't do anything here, but we test that this does not destroy anything: + nl.combine_devices (); + + // make pins for named nets of top-level circuits - this way they are not purged + nl.make_top_level_pins (); + nl.purge (); + + // compare netlist as string + CHECKPOINT (); + db::compare_netlist (_this, nl, + "circuit RINGO (FB=FB,ENABLE=ENABLE,OUT=OUT,VDD=VDD,VSS=VSS,BULK=BULK);\n" + " subcircuit ND2X1 $1 (NWELL=$4,B=FB,A=ENABLE,VDD=VDD,OUT=$5,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $2 (NWELL=$4,IN=$14,VDD=VDD,OUT=FB,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $3 (NWELL=$4,IN=FB,VDD=$I17,OUT=OUT,VSS=$I27,BULK=BULK);\n" + " subcircuit TIE $4 (NWELL=$4,VSS=$I27,VDD=$I17,BULK=BULK);\n" + " subcircuit TIE $5 (NWELL=$4,VSS=VSS,VDD=VDD,BULK=BULK);\n" + " subcircuit INVX1 $6 (NWELL=$4,IN=$5,VDD=VDD,OUT=$6,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $7 (NWELL=$4,IN=$6,VDD=VDD,OUT=$7,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $8 (NWELL=$4,IN=$7,VDD=VDD,OUT=$8,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $9 (NWELL=$4,IN=$8,VDD=VDD,OUT=$9,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $10 (NWELL=$4,IN=$9,VDD=VDD,OUT=$10,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $11 (NWELL=$4,IN=$10,VDD=VDD,OUT=$11,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $12 (NWELL=$4,IN=$11,VDD=VDD,OUT=$12,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $13 (NWELL=$4,IN=$12,VDD=VDD,OUT=$15,VSS=VSS,BULK=BULK);\n" + " subcircuit INVX1 $14 (NWELL=$4,IN=$15,VDD=VDD,OUT=$14,VSS=VSS,BULK=BULK);\n" + "end;\n" + "circuit ND2X1 (NWELL=NWELL,B=B,A=A,VDD=VDD,OUT=OUT,VSS=VSS,BULK=BULK);\n" + " device PMOS $1 (S=$7,G=$4,D=$9,B=NWELL) (L=0.25,W=1.5,AS=0.6375,AD=0.3375,PS=3.85,PD=1.95);\n" + " device PMOS $2 (S=$9,G=$2,D=$6,B=NWELL) (L=0.25,W=1.5,AS=0.3375,AD=0.6375,PS=1.95,PD=3.85);\n" + " device NMOS $3 (S=$13,G=$4,D=$14,B=BULK) (L=0.25,W=0.95,AS=0.40375,AD=0.21375,PS=2.75,PD=1.4);\n" + " device NMOS $4 (S=$14,G=$2,D=$11,B=BULK) (L=0.25,W=0.95,AS=0.21375,AD=0.40375,PS=1.4,PD=2.75);\n" + " device SOFT $5 (A=B,C=$2) (A=0,P=0);\n" + " device SOFT $6 (A=A,C=$4) (A=0,P=0);\n" + " device SOFT $7 (A=OUT,C=$6) (A=0,P=0);\n" + " device SOFT $8 (A=OUT,C=$7) (A=0,P=0);\n" + " device SOFT $9 (A=VDD,C=$9) (A=0,P=0);\n" + " device SOFT $10 (A=OUT,C=$11) (A=0,P=0);\n" + " device SOFT $11 (A=VSS,C=$13) (A=0,P=0);\n" + "end;\n" + "circuit TIE (NWELL=NWELL,VSS=VSS,VDD=VDD,BULK=BULK);\n" + " device SOFT $1 (A=$2,C=NWELL) (A=0,P=0);\n" + " device SOFT $2 (A=VDD,C=$2) (A=0,P=0);\n" + " device SOFT $3 (A=VSS,C=$3) (A=0,P=0);\n" + " device SOFT $4 (A=$3,C=BULK) (A=0,P=0);\n" + "end;\n" + "circuit INVX1 (NWELL=NWELL,IN=IN,VDD=VDD,OUT=OUT,VSS=VSS,BULK=BULK);\n" + " device PMOS $1 (S=$5,G=$2,D=$7,B=NWELL) (L=0.25,W=1.5,AS=0.6375,AD=0.6375,PS=3.85,PD=3.85);\n" + " device NMOS $2 (S=$10,G=$2,D=$8,B=BULK) (L=0.25,W=0.95,AS=0.40375,AD=0.40375,PS=2.75,PD=2.75);\n" + " device SOFT $3 (A=IN,C=$2) (A=0,P=0);\n" + " device SOFT $4 (A=VDD,C=$5) (A=0,P=0);\n" + " device SOFT $5 (A=OUT,C=$7) (A=0,P=0);\n" + " device SOFT $6 (A=OUT,C=$8) (A=0,P=0);\n" + " device SOFT $7 (A=VSS,C=$10) (A=0,P=0);\n" + "end;\n" + ); + + // compare the collected test data + + std::string au = tl::testdata (); + au = tl::combine_path (au, "algo"); + au = tl::combine_path (au, "soft_connections_au.gds"); + + db::compare_layouts (_this, ly, au); +} + TEST(100_issue954) { diff --git a/testdata/algo/soft_connections.gds b/testdata/algo/soft_connections.gds new file mode 100644 index 0000000000000000000000000000000000000000..7a92fe3282b263ffbd0e1466d2fc40ec72c294a7 GIT binary patch literal 13710 zcmdU$Ux=MY8OG=QKYR9UcC%Ttf4ad`1w#pHW7JeiP1a;}<92H|+qLE*EX_8AQc95` zB1J?*Vil=UO3@M)ZxksRO3%zPuwb(+nU)5jjZ+n9RWnC4j3G)!|})fjVhqGn9v=f;d4 zbvpS9F8GNtQrV&P{RzJMcgIu7)&5#2dEcJqp8@|PTnFv`0sQqsWrrsD#xY~YJLo@j z;`8YLg+gVACVBR+@q=DJZU5PCjze~6DbI29K6B4HPAWU}1LHY=^h2dz&Yx8FGXL)f z&p2rv(Lduym0j^0fqAT2`;T9L%{#dY+v8Y;m1oTqS0g{|7mOL%uxg_=9Mzq76f}bx z-*OZ*gF5gDM?nWu^BUK|-#RK#OS@g!i;CTq6|CaUdBg=e_D^iR*9(;$n&j(Ee?4^n zBgV9zDb(K2wWs+xOw~2`g?2C}jm1J`hbDRUo7dTKfamV!eWcrt>eA z9a`$2*U>*Y`jyIF#y4*{|5S2qtW+s^-=6fp*Z%YVj1!e{qkpOFgW|{QhMzxE4W9}X z?%<~pKWGOhYvVhG$_@?2-_IZTqvL0vcf3?~=m*Bv-oX69Kb3yl#|xFc%>VnrGfun@ z<46Be+2i=(%HXM7`6bSZ3B-^3XRP0qKxLl@jrnIr{IZ>;w3jryM_v>&Z)HhbG6*{_{F2ImS;adl}DuD^7}=RQ9s?wI&@; zCD*!9D0$zW?7xkh;vT zwC<#`$Lr7Z@p`ymC6nc*I(cKYoBW0N@b7tZ|tAr=XF$ajK5U>!cc?epRGf7E~a&3pfe z`(MR*Gx{p}4efp#`|!Jk$_`D&uki$`3X9nH)OQh!j|D3GqT^kDYDWD24YS&#<~Xyz z*5kOtHK(j;wC{2?59%cTD*r1q&PsZBwOJv~BWB9>Q~y08w1E}kRd#4ND)p+zvo^rb zA*`Z0E*Q=&AWUVWjznofE7sEuY5Bx!l&NQbhtozMX@bB&& zjgz0XpW~*>^~vhO{9)6cZLXg_jsIk=FPE=5{@l4;@XU_Jx9I9K(b*q}nRb73n zP_B009`1^$0{vmCzz$c7WCgQ@MFh2@9*$xKlT>~MlN45PhA&nyNexxO>{Vu2xg-S* zW;3o_n&)vgp2v=W+DhYGp|Y1&E^&Qc38Ix0D4-cs2I@q5Xi$dG?;(1{$f6V{lq-|=gogVuXV0?O>j<)3MRyn=rjErx~5V4?`;g)7;Ty@N{sKFM#T*#dJ6MkPPTAeEnE_!+Q~8?J}UF-Ya-7^LDHW81gO*jv+ob9;mVac!KiQ$B{^u?`{tjfV$D!{zUMf4(@?Cez4pm3wvu@r! zBX(6sBwyS1jPdhdddf3?Jf*3@Cmet2Dc_iKJaza}o-;jjBEJ@Q=Kim*eG{D>?iA%Y z&u3@;Iq#piHVVV}8sBohpcxd;dHTvf=Sgil=c9Gbxzv8*-12&<>^b|4JogzjvCm4^ z89A;qYVb*{Gj|1Qy7nVZD!XdFgKM~}ODkV_00HQrz&5Gh^xFEpf4i~$Kx=!_y-CygrJ(fsZ9eZGZiJ+Xt$qV=Bh}Dstko zy~?b=eD*3aI6-wT>C;9GYutu@fsS_u$m!2$Cc4(3xxdQ)B zqdo%d{t3ql_kma0p-KMYhVxIQ|IMvJ>Hp$}ZBO#&@G`{fsN~m{3zc2{AMoCE$$l=o zZ}k|v`PHoNU;B9EF#P_u_x)?zy|)GLJ;t81eD3hP<#UMVyHs{Pk38QgzQaVUAIJFb zbvjYU{2j;my~+*^;}`M#1=uKjQ@h{C{Qb00*`Z1P;)~8dm3}uj3#EVGp5)Jc)$vsF zYZHZ%_wAN9}RnALw-4ZVsw%5T@Z2<5)lobm}rPAWSz$&YLx zYHNrZv?^iv>-}~k|`KyCX tW8=2dxBf%%og;|DJm0_1tejd{KI0;g(*s5mGl*~g7k##ex3GHM{2%vo?;QXD literal 0 HcmV?d00001 diff --git a/testdata/algo/soft_connections_au.gds b/testdata/algo/soft_connections_au.gds new file mode 100644 index 0000000000000000000000000000000000000000..8a34b60f76bd60c91667e4ae3e956fc4ae04a62e GIT binary patch literal 26174 zcmds9Ym8l06+Uxk=FXj&+jcrs`d}JBMnKXiQ(D>*BBi!yp$yOtSS)DGx16?n#?3gGLnqN|DE(Y-FVjZZ7yNIm>T){+ zf1bDCH)j3J>AdP^pvR*}y_{&Q;ukTWGmPap13Ry(9zO%UypHc@AnJEBka{@-JvyG7 zp*j798Q8S_O=h6$t84}?Z(m`Cxjw-REbS}zysEF?R(<7WAp0tvfy>)d%WYMdfzGNh z1HH8stn#h0s@ZOLanI8AdlwI0y>R`mEB9TJj7=p;e>Phg`!l0SGP*xWdRO_eA7H1Z z#`%S_u{I8>F|57O+6j-e2@CRF9;Y^=e}0!FLdk({?QPSdg9i>|tM#ulUja?`L*qX^ zP8@;8#Uv3*4vbg(0)GOt^{4+DP? zq2v#GeybU=bvO&8Po$+0X?gUCLdl1*z=OMYN^WO>8A1Be6aEE0kQP9nDwG^pX#w=58-h>W7<{Qv za$qH2dWkGg{X(`QJg?-_7taOXI~RO%ijoiEllNlQmX7zENizL2v83?&Cv zws!>F>Cb}??1Rf)jZku6CBJhO%AW)taU<}W2qizs^IOe0obcg1+`gvD=j(Zh2&8L* z_C2A$qMaDphZ#7H*+`sy!mkZN$$|0v8vYRdW%$%P{^_h8Tob96C^@iFzuHZtU6Lo{ zdxYo9!XGeRYvMh_;#GV0?SoI!m{g5f7!$j@e;`R#--k0E@Z_CXac;>_^40g5K8g6~ z1I(+nw+6f&hI`~lq<4CuqK z&k6dns8&Ir_JiCXQlC(A?Gsz+D?OL`_M^YukK;joLdi8A&Gaqp-4o6#&CA!Uaqx8; zZt_0wq&c(?eoH7hFrEW;jRb%0E1th`m*)#52iEe}fk&Ua?sd;kQSx1eUl>o>3GIht zDU@9H6XuKI(+92VdY`@Vlh6;JEtDJ>x8LwP_xLr+A?8ZwOZc}Cb16J;VZR*TnsXZZ ziG9UCj#d08IWTT-VZ6E*dww_6k)q@m*Yn5bJ%8-?UVn;`&l|q!FHQu_GmcgKC%NWd z)p&8t_&0GL^kck)k^|%SH~Kjq^l=WuIMK&(+){b7J;h4zvpHV$ZUbgxTpGSS{SV~gZQD}8@ zJB|hayt}e(q{Zu-U)X{cqQ+y;7y8%4VYnmEY1^H`(LX}T+r?qj-g5@Fx2!$O9~biN zsqr^BgYdMLN=M z_jx{%dtlZlWWC{eY)_^=j2o~#k#P*h6@;^>i}eXr4p>&-Cca+}&X|!dhYtg{`bd{U z$wQ75_NP#CU}gKoBT_H+WJN|%GJe;AzDzzh9 zQ{DHZbPZRecq)!6ME*<9LvPhblCh?`NHUOdJg&Lz;&@>VtBm6@f_j`ql8t>ag3ECv z@=U`>u26DdrF|s$=`6&v5!XJUgwyilg7jbE5&s>LTw?x`8*JGjN4ei;q=R$OFcz@%~JMi9&MiLpN9Q}BPlGjBk z=`9(JBr-ZV`e=rd*F`CLN@A4KA)=H#JuzD8Q1Vt$%HVCbH1IYx`}@#Wg$SV{k@TeW zEkeobB9ue)RYAWZk%~+TB|lCgl*Tr9RoLeEN3e3jJ30Ih=EOVkZ=vKDRePH8%_xsW zg*bCA_*hYek^?I})%c6xW5ss(Fz^rfw@~u1lDAq3^AYqC-+*Q}*~=UI7{{7Ndmz#- z=|hB)1LOC#{Pz82`9UxBb0wgDA=?q!Rjq#+_?!po2kPg06D8OD7%*NTg6@5ZVkQ`q zn$<-k=HlxmBN{wP6d(6-bwySf$K^RjD0yzL8S&wP{vP7%#M|-je?)j|`rP&SfHw{? zqWyapKX93i7@FO~^yg6P73fzY(m$rZ5TWE(cpGb_G2U+#G|q*Z=Yqx*B_BfL`~o)S zS`&@Ni~ff9!kR|pns)Kc8A`4-t<5|7xKgkKqw&&Ly+$H6Uh&ZksWCinJ&yAWn9`;1 zc)=gNMj|!7|3ev4V|YG<#)S%v+n@IuiPU)hwHZ=lcs_*2vnw<%JnJJr`$4ec^d~eaQML(|6kAULTSA&cH${q`vUHy*})u z%k<4*Z^wFx)N{&t8A=XpuMd0WGJWrO#Ootc-}=oNQeSx9ULSHs%Ji*z#_JiGdq*yhKDkg z9OyKL7C#r~)g68S{PV#F&WwSN_<&GyV9fuG;cripmH5tc7&vni_|If0Ik1|46#B9A zI!we)?1qsDC2zJP3oE>z(R&sz+_i5{I!dO`CNV10cVTS?_J4{k0NN~+92mFR^cmOm zX^b$j|0(dloT22vm~Z-Q5@R*JjtzxddgP;y`;f9eeA!%52FM&P^gZ=vL^PFTLr zF2Z-z9yV7iI*IA8KHM>FiAWn;aa)Fx=WNXI5A;2sNdD5k49O4Aw?9N?xa5<-}XM!}E#chv)A7 z2kf4Zd+zct;E|?K$*jw-4jCC?hYf?^&(&5av)O+K^hGMloKq}=j;B?R#GZ?F&?oap zUtO1Z5%`mNzP=DCLqsTf?n`xnujm@1ZO4$=((88dCD`}rtnbt4%dm^ey4TElb!Aox z_qzs_DCyB`WT?q~A1tdSjIb-i^T2>IF2}x5rZ{@Vb5`A#ZMdB<`X_eK58bsRNzb>0 z6gXPU`4>tKjD0|XPi?G+IalNhCC^#QsL{X4 za~a*7I0F7>Gi05V17rPTdH&jO`Enay_I#n_z?k1N{5R2Y@VCevNz!}}{C5ZbIF;MH zkOH4v{*e>^Ne-;luY4or9|O5b5LViOi zx%|iAHPqcm!kfYiukoJT-%$!)tl&X2Z|%!f`266leHmmym(>&27hlHpd08n_)BM)K zHMtYc;F`9T)?~G9FD+x+&9N@iW=gsHSp!RzuUY8v<;bUwb}Yp;uP%2t%mMJzITY4B zSbMVewnl3n))Z!paBXo}O?PG}xiU#g*G%yEg^PUm1s8mw&!rky1 z!1d1{V*K3E+eo8mwvBn{9oY89KkvI6dWcc*CJl_y` z?rCse!0@S;QF66|`q>_$V%FddgsAe0(oa5a?cpi_w;d9qPzIGpa52EC`_OHn6XFJvIx*OJE^)!6yC7;OmRzDCW z&%OW3Sbx6#`S%ac5TasP8{Y-df6^H?8-y-PI;8cl-UKd zX1-m^gF zG9G72%`YIabzoiY8~4Ccm8YO%$gjZI*W`2IX_I`K+7nb=c1G~XWhbat{*O;kCTcl~ zH+4Q3NZ*^!T@24-AGGXz`Y`ID52JpeL)7lAe4O23B2XaghKm|=LiJHjS&Rm#1TJ! zLdoCC2n6+}H_)E)`nhMI9i{Raa{?{&|FJ|MhREeN`rG7NPx|?*6MXkn%$KCM3j1-K zG`4VG&UiELZxMN}?wyq(Gpk1a?K7;1b#Vpm-Sy3ir_sqnK2AWCyv?^P@)#E((o6CO zjCUn^t4+GE#`s5*`%uBF89w6?ik~Q+B9y%D{fD@(WAwA#vrqW;Q(uGoV2023EB-Qt zzTzq^lpI*ue?zS5Mt_@Cy`NfT`W*u+T=}=av6qi6E<^t4DehZh`Yo`He(p|JJ!B&* zZTQd~;yO6Ny54WY9>@9!?T8BP3hZFJCaS}goI83Vcl2D*g_7r@B8IQ%36VTSXF@c_ zMLBYO6BVLf?%avo$y2{j^4yNz@D)8FlBehlk^JyHwp|kyqCUQ--aCJy3C}C}dQT$X zU;RjAz2SK!KOfamKN86=iRzfB65Gf3BE08leiKTr_79GVxx7itHh51&DqO4wQ`oJQ zP>!GLM?CG#`V1}ijPcv(8JHm_t}M!er|w&OsFxYV)!dtRnx{tVhyBXvS~Tt28|v+y z(1H6ON#@vY<^}S{OlAJUzx5s=7MJFWmbEy<2TdMsS-B94^5rU{Uq)kn^v~B+k!@h> zsg3@Hddj1Jwx04_16!sx_7`;V{|R*26bVvYEWuxgHn|-P&2~iF-c65 zLS|;0Ec(DV`-?VN|7H9(w-n0wZEjVR@lSi)@2iRY?q7Ecq>SI@)=C+_%`KZUew%wi zW&AcbnacP>)h0Oh0d#@q9$&_|@GuE8`E5Z*ST^#Qg@tKYR!~N%(%^%+5x4 zN({eEzW;z7?v3tullN5Y)4Bbva{UT?EPr6UGZbqR%MEeM!SE+xZPQEs6tCMbLdk)Z`gsG1 zw-H+0L^Ar@+;Rw33;y6!DZ=(ur@}`3sWZ{HbIjM%ou<{;SGH*Zdl4gtlOb>Tkd2+~ H*jVykD~3K! literal 0 HcmV?d00001