WIP: added full LVS test.

This commit is contained in:
Matthias Koefferlein 2019-07-08 21:43:06 +02:00
parent b48453633f
commit 9625caea65
13 changed files with 442 additions and 85 deletions

View File

@ -56,9 +56,9 @@ static const std::string indent1 (" ");
static const std::string indent2 (" ");
template <class Keys>
std_writer_impl<Keys>::std_writer_impl (tl::OutputStream &stream, double dbu)
std_writer_impl<Keys>::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);

View File

@ -48,7 +48,7 @@ template <class Keys>
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);

View File

@ -59,7 +59,7 @@ class std_writer_impl
: public l2n_std_format::std_writer_impl<typename Keys::l2n_keys>
{
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 <class Keys>
std_writer_impl<Keys>::std_writer_impl (tl::OutputStream &stream, double dbu)
: l2n_std_format::std_writer_impl<typename Keys::l2n_keys> (stream, dbu)
std_writer_impl<Keys>::std_writer_impl (tl::OutputStream &stream, double dbu, const std::string &progress_description)
: l2n_std_format::std_writer_impl<typename Keys::l2n_keys> (stream, dbu, progress_description.empty () ? tl::to_string (tr ("Writing LVS database")) : progress_description)
{
// .. nothing yet ..
}

View File

@ -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 Obj>
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 db::DeviceClass *, size_t>::const_iterator cpa = m_cat_by_ptr.find (ca);
std::map<const db::DeviceClass *, size_t>::const_iterator cpb = m_cat_by_ptr.find (cb);
typename std::map<const Obj *, size_t>::const_iterator cpa = m_cat_by_ptr.find (ca);
typename std::map<const Obj *, size_t>::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<const db::DeviceClass *, size_t>::iterator cp = m_cat_by_ptr.begin (); cp != m_cat_by_ptr.end (); ++cp) {
for (typename std::map<const Obj *, size_t>::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 db::DeviceClass *, size_t>::const_iterator cp = m_cat_by_ptr.find (cls);
typename std::map<const Obj *, size_t>::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<const db::DeviceClass *, size_t> m_cat_by_ptr;
std::map<const Obj *, size_t> m_cat_by_ptr;
std::map<std::string, size_t> 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<db::DeviceClass>
{
public:
DeviceCategorizer ()
: generic_categorizer<db::DeviceClass> ()
{
// .. nothing yet ..
}
void same_class (const db::DeviceClass *ca, const db::DeviceClass *cb)
{
generic_categorizer<db::DeviceClass>::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<db::DeviceClass>::has_cat_for (cls);
}
size_t cat_for_device_class (const db::DeviceClass *cls)
{
return generic_categorizer<db::DeviceClass>::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<db::Circuit>
{
public:
CircuitCategorizer ()
: m_next_cat (0)
: generic_categorizer<db::Circuit> ()
{
// .. 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<db::Circuit>::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 db::Circuit *, size_t>::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<std::string, size_t>::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<db::Circuit>::cat_for (cr);
}
public:
std::map<const db::Circuit *, size_t> m_cat_by_ptr;
std::map<std::string, size_t> 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 db::Circuit *, CircuitMapper>::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 db::Net *, size_t>::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<db::DeviceTerminalDefinition> &td = d->device_class ()->terminal_definitions ();
@ -1757,16 +1795,12 @@ NetlistComparer::equivalent_pins (const db::Circuit *cb, const std::vector<size_
void
NetlistComparer::same_device_classes (const db::DeviceClass *ca, const db::DeviceClass *cb)
{
tl_assert (ca != 0);
tl_assert (cb != 0);
mp_device_categorizer->same_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<size_t, std::pair<const db::Circuit *, const db::Circuit *> > cat2circuits;
std::set<const db::Circuit *> 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<size_t, std::pair<const db::DeviceClass *, const db::DeviceClass *> >::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<const db::Circuit *> verified_circuits_a, verified_circuits_b;
std::map<const db::Circuit *, CircuitMapper> 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<size_t, std::pair<const db::Circuit *, const db::Circuit *> >::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<std::pair<size_t, size_t> > 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<std::pair<size_t, size_t> > 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<std::pair<size_t, size_t> > 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<std::pair<size_t, size_t> > 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) {

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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");
}

View File

@ -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

BIN
testdata/lvs/vexriscv.cir.gz vendored Normal file

Binary file not shown.

111
testdata/lvs/vexriscv.lvs vendored Normal file
View File

@ -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

BIN
testdata/lvs/vexriscv.oas.gz vendored Normal file

Binary file not shown.

BIN
testdata/lvs/vexriscv_schematic.cir.gz vendored Normal file

Binary file not shown.