diff --git a/src/db/db/dbLayoutToNetlistWriter.cc b/src/db/db/dbLayoutToNetlistWriter.cc index 3b3909197..9357e0a34 100644 --- a/src/db/db/dbLayoutToNetlistWriter.cc +++ b/src/db/db/dbLayoutToNetlistWriter.cc @@ -56,9 +56,9 @@ static const std::string indent1 (" "); static const std::string indent2 (" "); template -std_writer_impl::std_writer_impl (tl::OutputStream &stream, double dbu) +std_writer_impl::std_writer_impl (tl::OutputStream &stream, double dbu, const std::string &progress_description) : mp_stream (&stream), m_dbu (dbu), - m_progress (tl::to_string (tr ("Writing L2N database")), 10000) + m_progress (progress_description.empty () ? tl::to_string (tr ("Writing L2N database")) : progress_description, 10000) { m_progress.set_format (tl::to_string (tr ("%.0f MB"))); m_progress.set_unit (1024 * 1024); diff --git a/src/db/db/dbLayoutToNetlistWriter.h b/src/db/db/dbLayoutToNetlistWriter.h index 25da0a2b5..4e259bc7d 100644 --- a/src/db/db/dbLayoutToNetlistWriter.h +++ b/src/db/db/dbLayoutToNetlistWriter.h @@ -48,7 +48,7 @@ template class std_writer_impl { public: - std_writer_impl (tl::OutputStream &stream, double dbu); + std_writer_impl (tl::OutputStream &stream, double dbu, const std::string &progress_description = std::string ()); void write (const db::LayoutToNetlist *l2n); diff --git a/src/db/db/dbLayoutVsSchematicWriter.cc b/src/db/db/dbLayoutVsSchematicWriter.cc index edb817a39..80417b292 100644 --- a/src/db/db/dbLayoutVsSchematicWriter.cc +++ b/src/db/db/dbLayoutVsSchematicWriter.cc @@ -59,7 +59,7 @@ class std_writer_impl : public l2n_std_format::std_writer_impl { public: - std_writer_impl (tl::OutputStream &stream, double dbu); + std_writer_impl (tl::OutputStream &stream, double dbu, const std::string &progress_description = std::string ()); void write (const db::LayoutVsSchematic *l2n); @@ -80,8 +80,8 @@ static const std::string indent1 (" "); static const std::string indent2 (" "); template -std_writer_impl::std_writer_impl (tl::OutputStream &stream, double dbu) - : l2n_std_format::std_writer_impl (stream, dbu) +std_writer_impl::std_writer_impl (tl::OutputStream &stream, double dbu, const std::string &progress_description) + : l2n_std_format::std_writer_impl (stream, dbu, progress_description.empty () ? tl::to_string (tr ("Writing LVS database")) : progress_description) { // .. nothing yet .. } diff --git a/src/db/db/dbNetlistCompare.cc b/src/db/db/dbNetlistCompare.cc index fe96c1d4a..276e3462d 100644 --- a/src/db/db/dbNetlistCompare.cc +++ b/src/db/db/dbNetlistCompare.cc @@ -264,35 +264,46 @@ private: }; // -------------------------------------------------------------------------------------------------------------------- -// DeviceCategorizer definition and implementation +// generic_categorizer definition and implementation /** - * @brief A device categorizer + * @brief A generic categorizer * - * The objective of this class is to supply a category ID for a given device class. - * The category ID also identities equivalent device classes from netlist A and B. + * The objective of this class is to supply a category ID for a given object. + * The category ID also identities equivalent objects from netlist A and B. */ -class DeviceCategorizer +template +class generic_categorizer { public: - DeviceCategorizer () + generic_categorizer () : m_next_cat (0) { // .. nothing yet .. } - void same_class (const db::DeviceClass *ca, const db::DeviceClass *cb) + void same (const Obj *ca, const Obj *cb) { + if (! ca && ! cb) { + return; + } else if (! ca) { + same (cb, ca); + } else if (! cb) { + // makeing a object same as null will make this device being ignored + m_cat_by_ptr [ca] = 0; + return; + } + // reuse existing category if one is assigned already -> this allows associating // multiple categories to other ones (A->C, B->C) - std::map::const_iterator cpa = m_cat_by_ptr.find (ca); - std::map::const_iterator cpb = m_cat_by_ptr.find (cb); + typename std::map::const_iterator cpa = m_cat_by_ptr.find (ca); + typename std::map::const_iterator cpb = m_cat_by_ptr.find (cb); if (cpa != m_cat_by_ptr.end () && cpb != m_cat_by_ptr.end ()) { if (cpa->second != cpb->second) { // join categories (cat(B)->cat(A)) - for (std::map::iterator cp = m_cat_by_ptr.begin (); cp != m_cat_by_ptr.end (); ++cp) { + for (typename std::map::iterator cp = m_cat_by_ptr.begin (); cp != m_cat_by_ptr.end (); ++cp) { if (cp->second == cpb->second) { cp->second = cpa->second; } @@ -319,24 +330,14 @@ public: } } - size_t cat_for_device (const db::Device *device) - { - const db::DeviceClass *cls = device->device_class (); - if (! cls) { - return 0; - } - - return cat_for_device_class (cls); - } - - bool has_cat_for_device_class (const db::DeviceClass *cls) + bool has_cat_for (const Obj *cls) { return m_cat_by_ptr.find (cls) != m_cat_by_ptr.end (); } - size_t cat_for_device_class (const db::DeviceClass *cls) + size_t cat_for (const Obj *cls) { - std::map::const_iterator cp = m_cat_by_ptr.find (cls); + typename std::map::const_iterator cp = m_cat_by_ptr.find (cls); if (cp != m_cat_by_ptr.end ()) { return cp->second; } @@ -359,11 +360,56 @@ public: } public: - std::map m_cat_by_ptr; + std::map m_cat_by_ptr; std::map m_cat_by_name; size_t m_next_cat; }; +// -------------------------------------------------------------------------------------------------------------------- +// DeviceCategorizer definition and implementation + +/** + * @brief A device categorizer + * + * The objective of this class is to supply a category ID for a given device class. + * The category ID also identities equivalent device classes from netlist A and B. + */ +class DeviceCategorizer + : private generic_categorizer +{ +public: + DeviceCategorizer () + : generic_categorizer () + { + // .. nothing yet .. + } + + void same_class (const db::DeviceClass *ca, const db::DeviceClass *cb) + { + generic_categorizer::same (ca, cb); + } + + size_t cat_for_device (const db::Device *device) + { + const db::DeviceClass *cls = device->device_class (); + if (! cls) { + return 0; + } + + return cat_for_device_class (cls); + } + + bool has_cat_for_device_class (const db::DeviceClass *cls) + { + return generic_categorizer::has_cat_for (cls); + } + + size_t cat_for_device_class (const db::DeviceClass *cls) + { + return generic_categorizer::cat_for (cls); + } +}; + // -------------------------------------------------------------------------------------------------------------------- // CircuitCategorizer definition and implementation @@ -374,19 +420,26 @@ public: * The category ID also identities equivalent circuit from netlist A and B. */ class CircuitCategorizer + : private generic_categorizer { public: CircuitCategorizer () - : m_next_cat (0) + : generic_categorizer () { // .. nothing yet .. } void same_circuit (const db::Circuit *ca, const db::Circuit *cb) { - ++m_next_cat; - m_cat_by_ptr.insert (std::make_pair (ca, m_next_cat)); - m_cat_by_ptr.insert (std::make_pair (cb, m_next_cat)); + // no arbitrary cross-pairing + if (ca && has_cat_for (ca)) { + throw tl::Exception (tl::to_string (tr ("Circuit is already paired with other circuit: ")) + ca->name ()); + } + if (cb && has_cat_for (cb)) { + throw tl::Exception (tl::to_string (tr ("Circuit is already paired with other circuit: ")) + cb->name ()); + } + + generic_categorizer::same (ca, cb); } size_t cat_for_subcircuit (const db::SubCircuit *subcircuit) @@ -401,32 +454,8 @@ public: size_t cat_for_circuit (const db::Circuit *cr) { - std::map::const_iterator cp = m_cat_by_ptr.find (cr); - if (cp != m_cat_by_ptr.end ()) { - return cp->second; - } - - std::string cr_name = cr->name (); -#if defined(COMPARE_CASE_INSENSITIVE) - cr_name = tl::to_upper_case (cr_name); -#endif - - std::map::const_iterator c = m_cat_by_name.find (cr_name); - if (c != m_cat_by_name.end ()) { - m_cat_by_ptr.insert (std::make_pair (cr, c->second)); - return c->second; - } else { - ++m_next_cat; - m_cat_by_name.insert (std::make_pair (cr_name, m_next_cat)); - m_cat_by_ptr.insert (std::make_pair (cr, m_next_cat)); - return m_next_cat; - } + return generic_categorizer::cat_for (cr); } - -public: - std::map m_cat_by_ptr; - std::map m_cat_by_name; - size_t m_next_cat; }; // -------------------------------------------------------------------------------------------------------------------- @@ -626,12 +655,16 @@ public: for (db::Net::const_subcircuit_pin_iterator i = net->begin_subcircuit_pins (); i != net->end_subcircuit_pins (); ++i) { const db::SubCircuit *sc = i->subcircuit (); + size_t circuit_cat = circuit_categorizer.cat_for_subcircuit (sc); + if (! circuit_cat) { + // circuit is ignored + continue; + } + size_t pin_id = i->pin ()->id (); const db::Circuit *cr = sc->circuit_ref (); const db::Net *net_at_pin = cr->net_for_pin (pin_id); - size_t this_pin_id = pin_id; - std::map::const_iterator icm = circuit_map->find (cr); if (icm == circuit_map->end ()) { // this can happen if the other circuit is not present - this is allowed for single-pin @@ -662,7 +695,7 @@ public: if (! net_at_pin || net_at_pin->is_floating ()) { - Transition ed (sc, circuit_categorizer.cat_for_subcircuit (sc), pin_id, pin_id); + Transition ed (sc, circuit_cat, pin_id, pin_id); std::map::const_iterator in = n2entry.find (0); if (in == n2entry.end ()) { @@ -714,7 +747,7 @@ public: // NOTE: if a pin mapping is given, EdgeDesc::pin1_id and EdgeDesc::pin2_id are given // as pin ID's of the other circuit. - Transition ed (sc, circuit_categorizer.cat_for_subcircuit (sc), pin_id, pin_map->normalize_pin_id (cr, pin2_id)); + Transition ed (sc, circuit_cat, pin_id, pin_map->normalize_pin_id (cr, pin2_id)); const db::Net *net2 = sc->net_for_pin (this_pin2_id); @@ -738,6 +771,11 @@ public: } size_t device_cat = device_categorizer.cat_for_device (d); + if (! device_cat) { + // device is ignored + continue; + } + size_t terminal1_id = translate_terminal_id (i->terminal_id (), d); const std::vector &td = d->device_class ()->terminal_definitions (); @@ -1757,16 +1795,12 @@ NetlistComparer::equivalent_pins (const db::Circuit *cb, const std::vectorsame_class (ca, cb); } void NetlistComparer::same_circuits (const db::Circuit *ca, const db::Circuit *cb) { - tl_assert (ca != 0); - tl_assert (cb != 0); mp_circuit_categorizer->same_circuit (ca, cb); } @@ -1799,15 +1833,26 @@ NetlistComparer::compare (const db::Netlist *a, const db::Netlist *b) const bool good = true; std::map > cat2circuits; + std::set verified_circuits_a, verified_circuits_b; for (db::Netlist::const_circuit_iterator i = a->begin_circuits (); i != a->end_circuits (); ++i) { size_t cat = circuit_categorizer.cat_for_circuit (i.operator-> ()); - cat2circuits[cat].first = i.operator-> (); + if (cat) { + cat2circuits[cat].first = i.operator-> (); + } else { + // skip circuit (but count it as verified) + verified_circuits_a.insert (i.operator-> ()); + } } for (db::Netlist::const_circuit_iterator i = b->begin_circuits (); i != b->end_circuits (); ++i) { size_t cat = circuit_categorizer.cat_for_circuit (i.operator-> ()); - cat2circuits[cat].second = i.operator-> (); + if (cat) { + cat2circuits[cat].second = i.operator-> (); + } else { + // skip circuit (but count it as verified) + verified_circuits_b.insert (i.operator-> ()); + } } if (mp_logger) { @@ -1820,17 +1865,23 @@ NetlistComparer::compare (const db::Netlist *a, const db::Netlist *b) const for (db::Netlist::const_device_class_iterator dc = a->begin_device_classes (); dc != a->end_device_classes (); ++dc) { size_t cat = device_categorizer.cat_for_device_class (dc.operator-> ()); - cat2dc.insert (std::make_pair (cat, std::make_pair ((const db::DeviceClass *) 0, (const db::DeviceClass *) 0))).first->second.first = dc.operator-> (); + if (cat) { + cat2dc.insert (std::make_pair (cat, std::make_pair ((const db::DeviceClass *) 0, (const db::DeviceClass *) 0))).first->second.first = dc.operator-> (); + } } for (db::Netlist::const_device_class_iterator dc = b->begin_device_classes (); dc != b->end_device_classes (); ++dc) { size_t cat = device_categorizer.cat_for_device_class (dc.operator-> ()); - cat2dc.insert (std::make_pair (cat, std::make_pair ((const db::DeviceClass *) 0, (const db::DeviceClass *) 0))).first->second.second = dc.operator-> (); + if (cat) { + cat2dc.insert (std::make_pair (cat, std::make_pair ((const db::DeviceClass *) 0, (const db::DeviceClass *) 0))).first->second.second = dc.operator-> (); + } } for (std::map >::const_iterator i = cat2dc.begin (); i != cat2dc.end (); ++i) { if (! i->second.first || ! i->second.second) { - good = false; + // NOTE: device class mismatch does not set good to false. + // Reasoning: a device class may not be present because there is no device of a certain kind (e.g. in SPICE). + // This isn't necessarily a failure. if (mp_logger) { mp_logger->device_class_mismatch (i->second.first, i->second.second); } @@ -1848,12 +1899,14 @@ NetlistComparer::compare (const db::Netlist *a, const db::Netlist *b) const } } - std::set verified_circuits_a, verified_circuits_b; std::map c12_pin_mapping, c22_pin_mapping; for (db::Netlist::const_bottom_up_circuit_iterator c = a->begin_bottom_up (); c != a->end_bottom_up (); ++c) { size_t ccat = circuit_categorizer.cat_for_circuit (c.operator-> ()); + if (! ccat) { + continue; + } std::map >::const_iterator i = cat2circuits.find (ccat); tl_assert (i != cat2circuits.end ()); @@ -2384,6 +2437,12 @@ NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2, continue; } + size_t device_cat = device_categorizer.cat_for_device (d.operator-> ()); + if (! device_cat) { + // device is ignored + continue; + } + std::vector > k = compute_device_key (*d, g1); bool mapped = true; @@ -2400,7 +2459,7 @@ NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2, good = false; } else { // TODO: report devices which cannot be distiguished topologically? - device_map.insert (std::make_pair (k, std::make_pair (d.operator-> (), device_categorizer.cat_for_device (d.operator-> ())))); + device_map.insert (std::make_pair (k, std::make_pair (d.operator-> (), device_cat))); } } @@ -2411,6 +2470,12 @@ NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2, continue; } + size_t device_cat = device_categorizer.cat_for_device (d.operator-> ()); + if (! device_cat) { + // device is ignored + continue; + } + std::vector > k = compute_device_key (*d, g2); bool mapped = true; @@ -2437,8 +2502,6 @@ NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2, db::DeviceCompare dc; - size_t device_cat = device_categorizer.cat_for_device (d.operator-> ()); - if (! dc.equals (dm->second, std::make_pair (d.operator-> (), device_cat))) { if (dm->second.second != device_cat) { if (mp_logger) { @@ -2477,6 +2540,12 @@ NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2, for (db::Circuit::const_subcircuit_iterator sc = c1->begin_subcircuits (); sc != c1->end_subcircuits (); ++sc) { + size_t sc_cat = circuit_categorizer.cat_for_subcircuit (sc.operator-> ()); + if (! sc_cat) { + // subcircuit is ignored + continue; + } + std::vector > k = compute_subcircuit_key (*sc, g1, &c12_circuit_and_pin_mapping, &circuit_pin_mapper); bool mapped = true; @@ -2493,7 +2562,7 @@ NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2, good = false; } else if (! k.empty ()) { // TODO: report devices which cannot be distiguished topologically? - subcircuit_map.insert (std::make_pair (k, std::make_pair (sc.operator-> (), circuit_categorizer.cat_for_subcircuit (sc.operator-> ())))); + subcircuit_map.insert (std::make_pair (k, std::make_pair (sc.operator-> (), sc_cat))); } } @@ -2503,6 +2572,12 @@ NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2, for (db::Circuit::const_subcircuit_iterator sc = c2->begin_subcircuits (); sc != c2->end_subcircuits (); ++sc) { + size_t sc_cat = circuit_categorizer.cat_for_subcircuit (sc.operator-> ()); + if (! sc_cat) { + // subcircuit is ignored + continue; + } + std::vector > k = compute_subcircuit_key (*sc, g2, &c22_circuit_and_pin_mapping, &circuit_pin_mapper); bool mapped = true; @@ -2528,7 +2603,6 @@ NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2, } else { db::SubCircuitCompare scc; - size_t sc_cat = circuit_categorizer.cat_for_subcircuit (sc.operator-> ()); if (! scc.equals (scm->second, std::make_pair (sc.operator-> (), sc_cat))) { if (mp_logger) { diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 055b7dcf9..ce72c7c9d 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -231,7 +231,7 @@ void NetlistSpiceReader::finish () // purge nets with single connections (this way unconnected pins can be realized) if (mp_netlist) { - mp_netlist->purge_nets (); + // @@@ mp_netlist->purge_nets (); } mp_stream.reset (0); @@ -611,7 +611,11 @@ std::string NetlistSpiceReader::read_name (tl::Extractor &ex) { // TODO: allow configuring Spice reader as case sensitive? // this is easy to do: just avoid to_upper here: +#if 0 // @@@ return tl::to_upper_case (read_name_with_case (ex)); +#else + return read_name_with_case (ex); +#endif } bool NetlistSpiceReader::read_element (tl::Extractor &ex, const std::string &element, const std::string &name) diff --git a/src/db/unit_tests/dbNetlistCompareTests.cc b/src/db/unit_tests/dbNetlistCompareTests.cc index 6c790a387..994bd052f 100644 --- a/src/db/unit_tests/dbNetlistCompareTests.cc +++ b/src/db/unit_tests/dbNetlistCompareTests.cc @@ -687,6 +687,48 @@ TEST(1_SimpleInverterSkippedDevices) " device $1:$4 [Match]\n" ); EXPECT_EQ (good, true); + + xref.clear (); + + comp_xref.exclude_caps (-1); + comp_xref.exclude_resistors (-1); + comp_xref.same_device_classes (0, nl2.device_class_by_name ("RES")); + comp_xref.same_device_classes (0, nl2.device_class_by_name ("CAP")); + comp_xref.same_device_classes (nl1.device_class_by_name ("RES"), 0); + comp_xref.same_device_classes (nl1.device_class_by_name ("CAP"), 0); + + good = comp_xref.compare (&nl1, &nl2); + + EXPECT_EQ (xref2s (xref), + "INV:INV [Match]:\n" + " pin $0:$1 [Match]\n" + " pin $1:$3 [Match]\n" + " pin $2:$0 [Match]\n" + " pin $3:$2 [Match]\n" + " net IN:IN [Match]\n" + " terminal (null):$2[B]\n" + " terminal (null):$3[B]\n" + " terminal $1[G]:$4[G]\n" + " terminal $2[B]:(null)\n" + " terminal $3[G]:$1[G]\n" + " pin $0:$1\n" + " net OUT:OUT [Match]\n" + " terminal (null):$2[A]\n" + " terminal (null):$3[A]\n" + " terminal $1[D]:$4[D]\n" + " terminal $2[A]:(null)\n" + " terminal $3[D]:$1[S]\n" + " pin $1:$3\n" + " net VDD:VDD [Match]\n" + " terminal $1[S]:$4[S]\n" + " pin $2:$0\n" + " net VSS:VSS [Match]\n" + " terminal $3[S]:$1[D]\n" + " pin $3:$2\n" + " device $3:$1 [Match]\n" + " device $1:$4 [Match]\n" + ); + EXPECT_EQ (good, true); } TEST(2_SimpleInverterWithForcedNetAssignment) @@ -1713,6 +1755,43 @@ TEST(11_MismatchingSubcircuits) " subcircuit $1:$2 [Match]\n" ); EXPECT_EQ (good, false); + + xref.clear (); + + // ignore the subcircuits to make them match + comp_xref.same_circuits (nl1.circuit_by_name ("INV"), 0); + comp_xref.same_circuits (0, nl2.circuit_by_name ("INV")); + good = comp_xref.compare (&nl1, &nl2); + + // nets are now ambiguous + EXPECT_EQ (xref2s (xref), + "TOP:TOP [Match]:\n" + " pin $0:$0 [Match]\n" + " pin $1:$1 [Match]\n" + " pin $2:$2 [Match]\n" + " pin $3:$3 [Match]\n" + " net IN:OUT [MatchWithWarning]\n" + " pin $0:$0\n" + " subcircuit_pin (null):$1[$3]\n" + " subcircuit_pin $1[$0]:(null)\n" + " net OUT:VDD [MatchWithWarning]\n" + " pin $1:$1\n" + " subcircuit_pin (null):$1[$0]\n" + " subcircuit_pin (null):$2[$0]\n" + " subcircuit_pin $2[$1]:(null)\n" + " net VDD:IN [MatchWithWarning]\n" + " pin $2:$2\n" + " subcircuit_pin (null):$2[$1]\n" + " subcircuit_pin $1[$2]:(null)\n" + " subcircuit_pin $2[$2]:(null)\n" + " net VSS:VSS [MatchWithWarning]\n" + " pin $3:$3\n" + " subcircuit_pin (null):$1[$2]\n" + " subcircuit_pin (null):$2[$2]\n" + " subcircuit_pin $1[$3]:(null)\n" + " subcircuit_pin $2[$3]:(null)\n" + ); + EXPECT_EQ (good, true); } TEST(12_MismatchingSubcircuitsDuplicates) diff --git a/src/lvs/lvs/built-in-macros/_lvs_netter.rb b/src/lvs/lvs/built-in-macros/_lvs_netter.rb index 25803ca5d..8a383f491 100644 --- a/src/lvs/lvs/built-in-macros/_lvs_netter.rb +++ b/src/lvs/lvs/built-in-macros/_lvs_netter.rb @@ -196,18 +196,21 @@ module LVS # This method will force an equivalence between the two circuits. # By default, circuits are identified by name. If names are different, this # method allows establishing an explicit correspondence. + # + # One of the circuits may be nil. In this case, the corresponding + # other circuit is mapped to "nothing", i.e. ignored. # # Before this method can be used, a schematic netlist needs to be loaded with # \schematic. def same_circuits(a, b) - a.is_a?(String) || b.is_a?(String) || raise("Both arguments of 'same_circuits' need to be strings") + a.is_a?(String) || a == nil || b.is_a?(String) || b == nil || raise("Both arguments of 'same_circuits' need to be strings or nil") ( nl_a, nl_b ) = _ensure_two_netlists - circuit_a = nl_a.circuit_by_name(a) || raise("Not a valid circuit name in extracted netlist: #{a}") - circuit_b = nl_b.circuit_by_name(b) || raise("Not a valid circuit name in reference netlist: #{b}") + circuit_a = a && (nl_a.circuit_by_name(a) || raise("Not a valid circuit name in extracted netlist: #{a}")) + circuit_b = b && (nl_b.circuit_by_name(b) || raise("Not a valid circuit name in reference netlist: #{b}")) @comparer.same_circuits(circuit_a, circuit_b) @@ -222,17 +225,20 @@ module LVS # By default, device classes are identified by name. If names are different, this # method allows establishing an explicit correspondence. # + # One of the device classes may be nil. In this case, the corresponding + # other device class is mapped to "nothing", i.e. ignored. + # # Before this method can be used, a schematic netlist needs to be loaded with # \schematic. def same_device_classes(a, b) - a.is_a?(String) || b.is_a?(String) || raise("Both arguments of 'same_device_classes' need to be strings") + a.is_a?(String) || a == nil || b.is_a?(String) || b == nil || raise("Both arguments of 'same_device_classes' need to be strings or nil") ( nl_a, nl_b ) = _ensure_two_netlists - dc_a = nl_a.device_class_by_name(a) || raise("Not a valid device class in extracted netlist: #{a}") - dc_b = nl_b.device_class_by_name(b) || raise("Not a valid device class in reference netlist: #{b}") + dc_a = a && (nl_a.device_class_by_name(a) || raise("Not a valid device class in extracted netlist: #{a}")) + dc_b = b && (nl_b.device_class_by_name(b) || raise("Not a valid device class in reference netlist: #{b}")) @comparer.same_device_classes(dc_a, dc_b) diff --git a/src/lvs/unit_tests/lvsTests.cc b/src/lvs/unit_tests/lvsTests.cc new file mode 100644 index 000000000..ec79a02e1 --- /dev/null +++ b/src/lvs/unit_tests/lvsTests.cc @@ -0,0 +1,82 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2019 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "tlUnitTest.h" +#include "dbReader.h" +#include "dbTestSupport.h" +#include "dbNetlist.h" +#include "dbNetlistSpiceReader.h" +#include "lymMacro.h" +#include "tlFileUtils.h" + +void run_test (tl::TestBase *_this, const std::string &suffix, const std::string &layout, bool with_l2n = false, bool with_lvsdb = false) +{ + std::string rs = tl::testsrc (); + rs += "/testdata/lvs/" + suffix + ".lvs"; + + std::string src = tl::testsrc (); + src += "/testdata/lvs/" + layout; + + std::string au_lvsdb = tl::testsrc (); + au_lvsdb += "/testdata/lvs/" + suffix + ".lvsdb.gz"; + + std::string au_cir = tl::testsrc (); + au_cir += "/testdata/lvs/" + suffix + ".cir.gz"; + + std::string au_l2n = tl::testsrc (); + au_l2n += "/testdata/lvs/" + suffix + ".l2n.gz"; + + std::string output_lvsdb = _this->tmp_file ("tmp.lvsdb"); + std::string output_cir = _this->tmp_file ("tmp.cir"); + std::string output_l2n = _this->tmp_file ("tmp.l2n"); + + { + // Set some variables + lym::Macro config; + config.set_text (tl::sprintf ( + "$lvs_test_source = '%s'\n" + "$lvs_test_target_lvsdb = '%s'\n" + "$lvs_test_target_cir = '%s'\n" + "$lvs_test_target_l2n = '%s'\n" + , src, output_lvsdb, output_cir, output_l2n) + ); + config.set_interpreter (lym::Macro::Ruby); + EXPECT_EQ (config.run (), 0); + } + + lym::Macro lvs; + lvs.load_from (rs); + EXPECT_EQ (lvs.run (), 0); + + _this->compare_text_files (output_cir, au_cir); + if (with_lvsdb) { + _this->compare_text_files (output_lvsdb, au_lvsdb); + } + if (with_l2n) { + _this->compare_text_files (output_l2n, au_l2n); + } +} + +TEST(1_full) +{ + run_test (_this, "vexriscv", "vexriscv.oas.gz"); +} diff --git a/src/lvs/unit_tests/unit_tests.pro b/src/lvs/unit_tests/unit_tests.pro index f9f5fc15f..054a56ed6 100644 --- a/src/lvs/unit_tests/unit_tests.pro +++ b/src/lvs/unit_tests/unit_tests.pro @@ -9,6 +9,7 @@ include($$PWD/../../lib_ut.pri) SOURCES = \ lvsBasicTests.cc \ lvsSimpleTests.cc \ + lvsTests.cc INCLUDEPATH += $$DRC_INC $$TL_INC $$RDB_INC $$DB_INC $$GSI_INC $$LYM_INC DEPENDPATH += $$DRC_INC $$TL_INC $$RDB_INC $$DB_INC $$GSI_INC $$LYM_INC diff --git a/testdata/lvs/vexriscv.cir.gz b/testdata/lvs/vexriscv.cir.gz new file mode 100644 index 000000000..c292e3709 Binary files /dev/null and b/testdata/lvs/vexriscv.cir.gz differ diff --git a/testdata/lvs/vexriscv.lvs b/testdata/lvs/vexriscv.lvs new file mode 100644 index 000000000..be68c6c96 --- /dev/null +++ b/testdata/lvs/vexriscv.lvs @@ -0,0 +1,111 @@ + +source($lvs_test_source) + +# will get pretty big: +# report_lvs($lvs_test_target_lvsdb) + +target_netlist($lvs_test_target_cir, write_spice(true), "Extracted by KLayout") + +schematic("vexriscv_schematic.cir.gz") + +deep + +# Drawing layers + +nwell = input(1, 0) +pactive = input(4, 0) +nactive = input(3, 0) +ntie = input(5, 0) +ptie = input(6, 0) + +poly = input(7, 0) +cont = input(10, 0) +metal1 = input(11, 0) +via1 = input(14, 0) +metal2 = input(16, 0) +via2 = input(18, 0) +metal3 = input(19, 0) +via3 = input(21, 0) +metal4 = input(22, 0) +via4 = input(25, 0) +metal5 = input(26, 0) + +# Bulk layer for terminal provisioning + +bulk = polygon_layer + +# Computed layers + +poly_cont = cont & poly +diff_cont = cont - poly + +pgate = pactive & poly +psd = pactive - pgate + +ngate = nactive & poly +nsd = nactive - ngate + +# Device extraction + +# PMOS transistor device extraction +extract_devices(mos4("PMOS"), { "SD" => psd, "G" => pgate, "W" => nwell, + "tS" => psd, "tD" => psd, "tG" => poly, "tW" => nwell }) + +# NMOS transistor device extraction +extract_devices(mos4("NMOS"), { "SD" => nsd, "G" => ngate, "W" => bulk, + "tS" => nsd, "tD" => nsd, "tG" => poly, "tW" => bulk }) + +# Define connectivity for netlist extraction + +# Inter-layer +connect(psd, diff_cont) +connect(nsd, diff_cont) +connect(poly, poly_cont) +connect(poly_cont, metal1) +connect(diff_cont, metal1) +connect(diff_cont, ntie) +connect(diff_cont, ptie) +connect(nwell, ntie) +connect(metal1, via1) +connect(via1, metal2) +connect(metal2, via2) +connect(via2, metal3) +connect(metal3, via3) +connect(via3, metal4) +connect(metal4, via4) +connect(via4, metal5) + +# Global +connect_global(ptie, "BULK") +connect_global(bulk, "BULK") + +# Implicit +connect_implicit("VDD") +connect_implicit("VSS") + +# Compare section + +netlist.flatten_circuit("*_{x0,x1,x2,x4}") + +same_device_classes("PMOS", "tp") +same_device_classes("NMOS", "tn") + +# Ignore all caps from the schematic +same_device_classes(nil, "CAP") + +# Increase the default complexity from 100 to 200 +# This is required because the clock tree is incorrect and exhibits manifold ambiguities +# (the netlists are just samples, not necessarily functional). +# The algorithm needs enough freedom to follow all these branches and different variants. +max_branch_complexity(200) + +schematic.combine_devices + +netlist.combine_devices + +if ! compare + raise "Netlists don't match" +else + puts "Congratulations! Netlists match." +end + diff --git a/testdata/lvs/vexriscv.oas.gz b/testdata/lvs/vexriscv.oas.gz new file mode 100644 index 000000000..c813bf03d Binary files /dev/null and b/testdata/lvs/vexriscv.oas.gz differ diff --git a/testdata/lvs/vexriscv_schematic.cir.gz b/testdata/lvs/vexriscv_schematic.cir.gz new file mode 100644 index 000000000..4f67d3e57 Binary files /dev/null and b/testdata/lvs/vexriscv_schematic.cir.gz differ