From 52f54ab39e8135488d86f1d57aea1de2d2594966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6fferlein?= Date: Sat, 10 Oct 2020 23:15:36 +0200 Subject: [PATCH] Fixed #648 (join_nets should work on global nets too) (#655) --- src/db/db/dbHierNetworkProcessor.cc | 74 ++++-- src/db/db/dbHierNetworkProcessor.h | 42 +++- src/db/db/dbNetlistExtractor.cc | 16 +- src/db/unit_tests/dbLayoutToNetlistTests.cc | 229 ++++++++++++++++++ .../algo/device_extract_au13_circuits.gds | Bin 0 -> 48482 bytes testdata/algo/device_extract_l13.gds | Bin 0 -> 3018 bytes 6 files changed, 329 insertions(+), 32 deletions(-) create mode 100644 testdata/algo/device_extract_au13_circuits.gds create mode 100644 testdata/algo/device_extract_l13.gds diff --git a/src/db/db/dbHierNetworkProcessor.cc b/src/db/db/dbHierNetworkProcessor.cc index 41f876b79..0f1e12271 100644 --- a/src/db/db/dbHierNetworkProcessor.cc +++ b/src/db/db/dbHierNetworkProcessor.cc @@ -799,10 +799,18 @@ struct cluster_building_receiver typedef std::set global_nets; typedef std::pair cluster_value; - cluster_building_receiver (const db::Connectivity &conn) - : mp_conn (&conn) + cluster_building_receiver (const db::Connectivity &conn, const tl::equivalence_clusters *attr_equivalence) + : mp_conn (&conn), mp_attr_equivalence (attr_equivalence) { - // .. nothing yet.. + if (mp_attr_equivalence) { + // cache the global nets per attribute equivalence cluster + for (size_t gid = 0; gid < conn.global_nets (); ++gid) { + tl::equivalence_clusters::cluster_id_type cl = attr_equivalence->cluster_id (global_net_id_to_attr (gid)); + if (cl > 0) { + m_global_nets_by_attribute_cluster [cl].insert (gid); + } + } + } } void generate_clusters (local_clusters &clusters) @@ -817,7 +825,32 @@ struct cluster_building_receiver cluster->add_attr (s->second.second); } - cluster->set_global_nets (c->second); + std::set global_nets = c->second; + + // Add the global nets we derive from the attribute equivalence (join_nets of labelled vs. + // global nets) + if (mp_attr_equivalence) { + + for (typename shape_vector::const_iterator s = c->first.begin (); s != c->first.end (); ++s) { + + size_t a = s->second.second; + if (a == 0) { + continue; + } + + tl::equivalence_clusters::cluster_id_type cl = mp_attr_equivalence->cluster_id (a); + if (cl > 0) { + std::map >::const_iterator gn = m_global_nets_by_attribute_cluster.find (cl); + if (gn != m_global_nets_by_attribute_cluster.end ()) { + global_nets.insert (gn->second.begin (), gn->second.end ()); + } + } + + } + + } + + cluster->set_global_nets (global_nets); } } @@ -913,6 +946,8 @@ private: std::map::iterator> m_shape_to_clusters; std::map::iterator> m_global_to_clusters; std::list m_clusters; + std::map > m_global_nets_by_attribute_cluster; + const tl::equivalence_clusters *mp_attr_equivalence; void join (typename std::list::iterator ic1, typename std::list::iterator ic2) { @@ -1035,7 +1070,7 @@ local_clusters::build_clusters (const db::Cell &cell, const db::Connectivity } } - cluster_building_receiver rec (conn); + cluster_building_receiver rec (conn, attr_equivalence); bs.process (rec, 1 /*==touching*/, bc); rec.generate_clusters (*this); @@ -1048,36 +1083,27 @@ template void local_clusters::apply_attr_equivalences (const tl::equivalence_clusters &attr_equivalence) { - tl::equivalence_clusters eq; - - // collect all local attributes (the ones which are present in attr_equivalence) into "eq" - // and form equivalences for multi-attribute clusters. - for (const_iterator c = begin (); c != end (); ++c) { - typename local_cluster::attr_iterator a0; - for (typename local_cluster::attr_iterator a = c->begin_attr (); a != c->end_attr (); ++a) { - if (attr_equivalence.has_attribute (*a)) { - if (a0 == typename local_cluster::attr_iterator ()) { - a0 = a; - } - eq.same (*a0, *a); - } - } - } - - // apply the equivalences implied by attr_equivalence - eq.apply_equivalences (attr_equivalence); - // identify the layout clusters joined into one attribute cluster and join them std::map::cluster_id_type, std::set > c2c; for (const_iterator c = begin (); c != end (); ++c) { + for (typename local_cluster::attr_iterator a = c->begin_attr (); a != c->end_attr (); ++a) { tl::equivalence_clusters::cluster_id_type cl = attr_equivalence.cluster_id (*a); if (cl > 0) { c2c [cl].insert (c->id ()); } } + + for (typename local_cluster::global_nets_iterator g = c->begin_global_nets (); g != c->end_global_nets (); ++g) { + size_t a = global_net_id_to_attr (*g); + tl::equivalence_clusters::cluster_id_type cl = attr_equivalence.cluster_id (a); + if (cl > 0) { + c2c [cl].insert (c->id ()); + } + } + } for (std::map::cluster_id_type, std::set >::const_iterator c = c2c.begin (); c != c2c.end (); ++c) { diff --git a/src/db/db/dbHierNetworkProcessor.h b/src/db/db/dbHierNetworkProcessor.h index be84db6da..1161ea84a 100644 --- a/src/db/db/dbHierNetworkProcessor.h +++ b/src/db/db/dbHierNetworkProcessor.h @@ -1396,11 +1396,11 @@ private: /** * @brief A helper function generating an attribute ID from a property ID - * This function is used to provide a generic attribute wrapping a property ID and a text ID. + * This function is used to provide a generic attribute wrapping a property ID, text ID and global net ID */ inline size_t prop_id_to_attr (db::properties_id_type id) { - return size_t (id) * 2; + return size_t (id) * 4; } /** @@ -1408,7 +1408,7 @@ inline size_t prop_id_to_attr (db::properties_id_type id) */ inline bool is_prop_id_attr (size_t attr) { - return (attr & 1) == 0; + return (attr & 3) == 0; } /** @@ -1416,7 +1416,32 @@ inline bool is_prop_id_attr (size_t attr) */ inline db::properties_id_type prop_id_from_attr (size_t attr) { - return attr / 2; + return attr / 4; +} + +/** + * @brief A helper function generating an attribute ID from a global net ID + * This function is used to provide a generic attribute wrapping a property ID, text ID and global net ID + */ +inline size_t global_net_id_to_attr (size_t id) +{ + return size_t (id) * 4 + 2; +} + +/** + * @brief Gets a value indicating whether the attribute is a global net ID + */ +inline bool is_global_net_id_attr (size_t attr) +{ + return (attr & 3) == 2; +} + +/** + * @brief Gets the global net ID from an attribute + */ +inline size_t global_net_id_from_attr (size_t attr) +{ + return attr / 4; } /** @@ -1424,9 +1449,18 @@ inline db::properties_id_type prop_id_from_attr (size_t attr) */ inline size_t text_ref_to_attr (const db::Text *tr) { + // NOTE: pointers are 32bit aligned, hence the lower two bits are 0 return size_t (tr) + 1; } +/** + * @brief Gets a value indicating whether the attribute is a StringRef + */ +inline bool is_text_ref_attr (size_t attr) +{ + return (attr & 3) == 1; +} + /** * @brief Gets the text value from an attribute ID */ diff --git a/src/db/db/dbNetlistExtractor.cc b/src/db/db/dbNetlistExtractor.cc index 7783ffbc6..0067f9517 100644 --- a/src/db/db/dbNetlistExtractor.cc +++ b/src/db/db/dbNetlistExtractor.cc @@ -51,7 +51,7 @@ void NetlistExtractor::set_include_floating_subcircuits (bool f) } static void -build_net_name_equivalence (const db::Layout *layout, db::property_names_id_type net_name_id, const std::string &joined_net_names, tl::equivalence_clusters &eq) +build_net_name_equivalence (const db::Layout *layout, const db::Connectivity &conn, db::property_names_id_type net_name_id, const std::string &joined_net_names, tl::equivalence_clusters &eq) { std::map > prop_by_name; tl::GlobPattern jn_pattern (joined_net_names); @@ -67,6 +67,14 @@ build_net_name_equivalence (const db::Layout *layout, db::property_names_id_type } } + // include pseudo-attributes for global nets to implement "join_with" for global nets + for (size_t gid = 0; gid < conn.global_nets (); ++gid) { + const std::string &gn = conn.global_net_name (gid); + if (jn_pattern.match (gn)) { + prop_by_name [gn].insert (db::global_net_id_to_attr (gid)); + } + } + const db::repository &text_repository = layout->shape_repository ().repository (db::object_tag ()); for (db::repository::iterator t = text_repository.begin (); t != text_repository.end (); ++t) { std::string nn = t->string (); @@ -107,12 +115,12 @@ NetlistExtractor::extract_nets (const db::DeepShapeStore &dss, unsigned int layo std::map > net_name_equivalence; if (m_text_annot_name_id.first) { if (! m_joined_net_names.empty ()) { - build_net_name_equivalence (mp_layout, m_text_annot_name_id.second, m_joined_net_names, net_name_equivalence [hier_clusters_type::top_cell_index]); + build_net_name_equivalence (mp_layout, conn, m_text_annot_name_id.second, m_joined_net_names, net_name_equivalence [hier_clusters_type::top_cell_index]); } for (std::list >::const_iterator m = m_joined_net_names_per_cell.begin (); m != m_joined_net_names_per_cell.end (); ++m) { std::pair cp = mp_layout->cell_by_name (m->first.c_str ()); if (cp.first) { - build_net_name_equivalence (mp_layout, m_text_annot_name_id.second, m->second, net_name_equivalence [cp.second]); + build_net_name_equivalence (mp_layout, conn, m_text_annot_name_id.second, m->second, net_name_equivalence [cp.second]); } } } @@ -313,7 +321,7 @@ void NetlistExtractor::collect_labels (const connected_clusters_type &clusters, } - } else { + } else if (db::is_text_ref_attr (*a)) { net_names.insert (db::text_from_attr (*a)); diff --git a/src/db/unit_tests/dbLayoutToNetlistTests.cc b/src/db/unit_tests/dbLayoutToNetlistTests.cc index 3e886b16a..b2feaff2a 100644 --- a/src/db/unit_tests/dbLayoutToNetlistTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistTests.cc @@ -3156,3 +3156,232 @@ TEST(12_FlattenCircuitDoesFlattenLayout) db::compare_layouts (_this, ly, au); } +TEST(13_JoinNets) +{ + 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_l13.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 ("bulk")); + std::auto_ptr rnwell (l2n.make_layer (nwell, "nwell")); + std::auto_ptr ractive (l2n.make_layer (active, "active")); + std::auto_ptr rpplus (l2n.make_layer (pplus, "pplus")); + std::auto_ptr rnplus (l2n.make_layer (nplus, "nplus")); + 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 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); + + db::NetlistDeviceExtractorMOS4Transistor pmos_ex ("PMOS"); + db::NetlistDeviceExtractorMOS4Transistor 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 + 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 + + l2n.register_layer (rpsd, "psd"); + l2n.register_layer (rnsd, "nsd"); + l2n.register_layer (rptie, "ptie"); + l2n.register_layer (rntie, "ntie"); + + // 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 (rntie, "VDD"); + l2n.connect_global (*rnwell, "VDD"); + l2n.connect_global (rptie, "VSS"); + l2n.connect_global (*rbulk, "VSS"); + + // Extract with joining VSS and VDD + l2n.extract_netlist ("{VSS,VDD}"); + + // 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 + CHECKPOINT (); + db::compare_netlist (_this, *l2n.netlist (), + "circuit RINGO ();\n" + " subcircuit INV2 $1 (IN=$I7,$2=FB,OUT=OSC,$4=VSS,$5=VDD,VDD=VDD,VSS=VSS);\n" + " subcircuit INV2 $2 (IN=FB,$2=$I34,OUT=$I17,$4=VSS,$5=VDD,VDD=VDD,VSS=VSS);\n" + " subcircuit INV2 $3 (IN=$3,$2=$I38,OUT=$I4,$4=VSS,$5=VDD,VDD=VDD,VSS=VSS);\n" + " subcircuit INV2 $4 (IN=$I2,$2=$I37,OUT=$3,$4=VSS,$5=VDD,VDD=VDD,VSS=VSS);\n" + " subcircuit INV2 $5 (IN=$I4,$2=$I39,OUT=$I5,$4=VSS,$5=VDD,VDD=VDD,VSS=VSS);\n" + " subcircuit INV2 $6 (IN=$I5,$2=$I40,OUT=$I6,$4=VSS,$5=VDD,VDD=VDD,VSS=VSS);\n" + " subcircuit INV2 $7 (IN=$I6,$2=$I41,OUT=$I7,$4=VSS,$5=VDD,VDD=VDD,VSS=VSS);\n" + " subcircuit INV2 $8 (IN=$I17,$2=$I35,OUT=$I1,$4=VSS,$5=VDD,VDD=VDD,VSS=VSS);\n" + " subcircuit INV2 $9 (IN=$I1,$2=$I36,OUT=$I2,$4=VSS,$5=VDD,VDD=VDD,VSS=VSS);\n" + "end;\n" + "circuit INV2 (IN=IN,$2=$2,OUT=OUT,$4=$4,$5=$5,VDD=VDD,VSS=VSS);\n" + " device PMOS $1 (S=$2,G=IN,D=$5,B=VDD) (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,B=VDD) (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,B=VSS) (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,B=VSS) (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" + ); + + // 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_au13_circuits.gds"); + + db::compare_layouts (_this, ly, au); +} + diff --git a/testdata/algo/device_extract_au13_circuits.gds b/testdata/algo/device_extract_au13_circuits.gds new file mode 100644 index 0000000000000000000000000000000000000000..131540011e1a5a74914c08d1bae38a23626f9c00 GIT binary patch literal 48482 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 literal 0 HcmV?d00001 diff --git a/testdata/algo/device_extract_l13.gds b/testdata/algo/device_extract_l13.gds new file mode 100644 index 0000000000000000000000000000000000000000..2fad35e6b6c9ea39db8224397050c43a78fd36e8 GIT binary patch literal 3018 zcmai$OGp)A6vxk2lnA_R0Jhh5(GsH zYa!8N(Jm;0Tt!5aw;4%0)rs~z%EI(E< z#D2ux#r~M8(@>fpb6en<6$X#ND#sV9o*til9AB8*#XkH+RJ|s?N$&sm4B}tI`OUz0 z5wX`RRcBi3Pxul0Q#cN?59i;dR6X_=<^J+fe6yBs(O;a${|YrNVNwwMnqv~>*GN>4 zGV{YXrn`4=8@VN-QlsWdJ?BRCO!%Pi?*BbnnwJ>Xs9BY}LWCz^C%00bP%}rL6YWq> zD6zxbDbCk$S48iSjH7sRL=TCL-IB66mUiAaolV%+Fq5b_hI_lCRGpdOpF^x2a?0*~ zxi6*a`5l&D?@Q|K2Xe$9?hjlY*UfjO>dZ7hPIOr~4zdsN8LTGlM|44w728~7_t-|;doDDmx8N}lG= zyQrQQLBMQ4Rk&j@Rrg<5{yN*wonQRUUifjFLe-h#|4$#r_jG@E@8Lc?1l`ncl&&0h zQJfP0Xdkx5O2^X2nx@moR?nx8ZMVC&6>r~My6?!=^s)Xc>0`t1<1w^yen-)7fUsxQ z_MFNa={d}lo|Etst()d~LT`qs6`z%=XZ5UwBk;GQW_4nVvm!^nybH8W;RWLxp(Y2rx}t2(nEenv;!8MtVBp`g!`c{` p$69=z*Eet=V;*Oi^o>KEa#A(1la