mirror of https://github.com/KLayout/klayout.git
2082 lines
71 KiB
C++
2082 lines
71 KiB
C++
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2024 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 "dbNetlistCompare.h"
|
|
#include "dbNetlistCompareUtils.h"
|
|
#include "dbNetlistCompareGraph.h"
|
|
#include "dbNetlistCompareCore.h"
|
|
#include "dbNetlistDeviceClasses.h"
|
|
#include "tlProgress.h"
|
|
#include "tlTimer.h"
|
|
#include "tlEquivalenceClusters.h"
|
|
#include "tlLog.h"
|
|
#include "tlEnv.h"
|
|
#include "tlInternational.h"
|
|
|
|
#include <cstring>
|
|
|
|
namespace db
|
|
{
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
// NetlistComparer implementation
|
|
|
|
NetlistComparer::NetlistComparer (NetlistCompareLogger *logger)
|
|
: mp_logger (logger)
|
|
{
|
|
mp_device_categorizer.reset (new DeviceCategorizer ());
|
|
mp_circuit_categorizer.reset (new CircuitCategorizer ());
|
|
mp_circuit_pin_categorizer.reset (new CircuitPinCategorizer ());
|
|
|
|
m_cap_threshold = -1.0; // not set
|
|
m_res_threshold = -1.0; // not set
|
|
|
|
// NOTE: as the backtracking algorithm is recursive, we need to limit the number of steps to follow
|
|
// Long chains can happen in case of depth-first because the backtracking algorithm will follow
|
|
// each successful path further to the very end. Depending on the circuit's complexity a long chain of
|
|
// jumps is possible leading to a deep stack. A value of 500 is compatible with 4M stack depth on a
|
|
// 64bit machine which is considered acceptable for now.
|
|
m_max_depth = 500;
|
|
|
|
m_max_n_branch = std::numeric_limits<size_t>::max ();
|
|
m_depth_first = true;
|
|
|
|
m_dont_consider_net_names = false;
|
|
m_case_sensitive = false;
|
|
|
|
m_with_log = true;
|
|
}
|
|
|
|
NetlistComparer::~NetlistComparer ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
void
|
|
NetlistComparer::exclude_caps (double threshold)
|
|
{
|
|
m_cap_threshold = threshold;
|
|
}
|
|
|
|
void
|
|
NetlistComparer::exclude_resistors (double threshold)
|
|
{
|
|
m_res_threshold = threshold;
|
|
}
|
|
|
|
void
|
|
NetlistComparer::same_nets (const db::Net *na, const db::Net *nb, bool must_match)
|
|
{
|
|
if (na || nb) {
|
|
m_same_nets [std::make_pair (na->circuit (), nb->circuit ())].push_back (std::make_pair (std::make_pair (na, nb), must_match));
|
|
}
|
|
}
|
|
|
|
void
|
|
NetlistComparer::same_nets (const db::Circuit *ca, const db::Circuit *cb, const db::Net *na, const db::Net *nb, bool must_match)
|
|
{
|
|
if (na || nb) {
|
|
m_same_nets [std::make_pair (ca, cb)].push_back (std::make_pair (std::make_pair (na, nb), must_match));
|
|
}
|
|
}
|
|
|
|
void
|
|
NetlistComparer::equivalent_pins (const db::Circuit *cb, size_t pin1_id, size_t pin2_id)
|
|
{
|
|
mp_circuit_pin_categorizer->map_pins (cb, pin1_id, pin2_id);
|
|
}
|
|
|
|
void
|
|
NetlistComparer::equivalent_pins (const db::Circuit *cb, const std::vector<size_t> &pin_ids)
|
|
{
|
|
mp_circuit_pin_categorizer->map_pins (cb, pin_ids);
|
|
}
|
|
|
|
void
|
|
NetlistComparer::same_device_classes (const db::DeviceClass *ca, const db::DeviceClass *cb)
|
|
{
|
|
mp_device_categorizer->same_class (ca, cb);
|
|
}
|
|
|
|
void
|
|
NetlistComparer::same_circuits (const db::Circuit *ca, const db::Circuit *cb)
|
|
{
|
|
mp_circuit_categorizer->same_circuit (ca, cb);
|
|
}
|
|
|
|
bool
|
|
NetlistComparer::compare (const db::Netlist *a, const db::Netlist *b, NetlistCompareLogger *logger) const
|
|
{
|
|
db::NetlistCompareLogger *org_logger = mp_logger;
|
|
mp_logger = logger;
|
|
bool res = false;
|
|
|
|
try {
|
|
res = compare (a, b);
|
|
} catch (...) {
|
|
mp_logger = org_logger;
|
|
throw;
|
|
}
|
|
|
|
mp_logger = org_logger;
|
|
return res;
|
|
}
|
|
|
|
void
|
|
NetlistComparer::unmatched_circuits (db::Netlist *a, db::Netlist *b, std::vector<db::Circuit *> &in_a, std::vector<db::Circuit *> &in_b) const
|
|
{
|
|
// we need to create a copy because this method is supposed to be const.
|
|
db::CircuitCategorizer circuit_categorizer = *mp_circuit_categorizer;
|
|
circuit_categorizer.set_case_sensitive (m_case_sensitive);
|
|
|
|
std::map<size_t, std::pair<std::vector<db::Circuit *>, std::vector<db::Circuit *> > > cat2circuits;
|
|
|
|
for (db::Netlist::circuit_iterator i = a->begin_circuits (); i != a->end_circuits (); ++i) {
|
|
size_t cat = circuit_categorizer.cat_for_circuit (i.operator-> ());
|
|
if (cat) {
|
|
cat2circuits[cat].first.push_back (i.operator-> ());
|
|
}
|
|
}
|
|
|
|
for (db::Netlist::circuit_iterator i = b->begin_circuits (); i != b->end_circuits (); ++i) {
|
|
size_t cat = circuit_categorizer.cat_for_circuit (i.operator-> ());
|
|
if (cat) {
|
|
cat2circuits[cat].second.push_back (i.operator-> ());
|
|
}
|
|
}
|
|
|
|
size_t na = 0, nb = 0;
|
|
for (std::map<size_t, std::pair<std::vector<db::Circuit *>, std::vector<db::Circuit *> > >::const_iterator i = cat2circuits.begin (); i != cat2circuits.end (); ++i) {
|
|
if (i->second.first.empty ()) {
|
|
nb += i->second.second.size ();
|
|
} else if (i->second.second.empty ()) {
|
|
na += i->second.first.size ();
|
|
}
|
|
}
|
|
|
|
in_a.reserve (na);
|
|
in_b.reserve (nb);
|
|
|
|
for (std::map<size_t, std::pair<std::vector<db::Circuit *>, std::vector<db::Circuit *> > >::const_iterator i = cat2circuits.begin (); i != cat2circuits.end (); ++i) {
|
|
if (i->second.first.empty ()) {
|
|
in_b.insert (in_b.end (), i->second.second.begin (), i->second.second.end ());
|
|
} else if (i->second.second.empty ()) {
|
|
in_a.insert (in_a.end (), i->second.first.begin (), i->second.first.end ());
|
|
}
|
|
}
|
|
}
|
|
|
|
static void clear_primary_classes (const db::Netlist *nl)
|
|
{
|
|
for (db::Netlist::const_device_class_iterator dc = nl->begin_device_classes (); dc != nl->end_device_classes (); ++dc) {
|
|
dc->set_primary_class (0);
|
|
}
|
|
}
|
|
|
|
bool
|
|
NetlistComparer::compare (const db::Netlist *a, const db::Netlist *b) const
|
|
{
|
|
bool res = false;
|
|
|
|
try {
|
|
res = compare_impl (a, b);
|
|
clear_primary_classes (a);
|
|
clear_primary_classes (b);
|
|
} catch (...) {
|
|
clear_primary_classes (a);
|
|
clear_primary_classes (b);
|
|
throw;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a consolidated list of identical nets for a circuit pair (aka "same_nets")
|
|
*
|
|
* The list is reduced by duplicates of the first net such, that the
|
|
* last "same_nets" entry wins.
|
|
*
|
|
* The return value is a list of net pairs and a flag indicating "must_match" mode.
|
|
* The second net can be null if "must_match" is true, indicating that no schematic
|
|
* net with the same name was found - hence a mismatch exists.
|
|
*/
|
|
std::vector<std::pair<std::pair<const Net *, const Net *>, bool> >
|
|
NetlistComparer::get_net_identity (const db::Circuit *ca, const db::Circuit *cb) const
|
|
{
|
|
std::vector<std::pair<std::pair<const Net *, const Net *>, bool> > net_identity;
|
|
|
|
std::map<std::pair<const db::Circuit *, const db::Circuit *>, std::vector<std::pair<std::pair<const Net *, const Net *>, bool> > >::const_iterator sn = m_same_nets.find (std::make_pair (ca, cb));
|
|
if (sn != m_same_nets.end ()) {
|
|
|
|
const std::vector<std::pair<std::pair<const Net *, const Net *>, bool> > &ni = sn->second;
|
|
|
|
// take last definition for a given first net
|
|
net_identity.reserve (ni.size ());
|
|
std::set<const Net *> seen;
|
|
for (auto i = ni.end (); i != ni.begin (); ) {
|
|
--i;
|
|
const Net *main_net = i->first.first ? i->first.first : i->first.second;
|
|
const Net *other_net = i->first.first ? i->first.second : i->first.first;
|
|
if (seen.find (main_net) == seen.end () && (! other_net || seen.find (other_net) == seen.end ())) {
|
|
net_identity.push_back (*i);
|
|
seen.insert (main_net);
|
|
if (other_net) {
|
|
seen.insert (other_net);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::reverse (net_identity.begin (), net_identity.end ());
|
|
|
|
}
|
|
|
|
return net_identity;
|
|
}
|
|
|
|
bool
|
|
NetlistComparer::compare_impl (const db::Netlist *a, const db::Netlist *b) const
|
|
{
|
|
clear_primary_classes (a);
|
|
clear_primary_classes (b);
|
|
|
|
m_case_sensitive = combined_case_sensitive (a, b);
|
|
|
|
// we need to create a copy because this method is supposed to be const.
|
|
db::CircuitCategorizer circuit_categorizer = *mp_circuit_categorizer;
|
|
db::DeviceCategorizer device_categorizer = *mp_device_categorizer;
|
|
db::CircuitPinCategorizer circuit_pin_mapper = *mp_circuit_pin_categorizer;
|
|
|
|
circuit_categorizer.set_case_sensitive (m_case_sensitive);
|
|
device_categorizer.set_case_sensitive (m_case_sensitive);
|
|
|
|
bool good = true;
|
|
|
|
std::map<size_t, std::pair<std::vector<const db::Circuit *>, std::vector<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-> ());
|
|
if (cat) {
|
|
cat2circuits[cat].first.push_back (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-> ());
|
|
if (cat) {
|
|
cat2circuits[cat].second.push_back (i.operator-> ());
|
|
} else {
|
|
// skip circuit (but count it as verified)
|
|
verified_circuits_b.insert (i.operator-> ());
|
|
}
|
|
}
|
|
|
|
if (mp_logger) {
|
|
mp_logger->begin_netlist (a, b);
|
|
}
|
|
|
|
// check for device classes that don't match
|
|
|
|
std::map<size_t, std::pair<const db::DeviceClass *, const db::DeviceClass *> > cat2dc;
|
|
|
|
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-> ());
|
|
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-> ());
|
|
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) {
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Register the primary netlist's device classes as primary ones for the second netlist's device classes.
|
|
// This way, the tolerances and parameter definitions are imposed on the second netlist, creating a common basis.
|
|
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) {
|
|
i->second.second->set_primary_class (i->second.first);
|
|
}
|
|
}
|
|
|
|
// decide whether to use a device category in strict mode
|
|
|
|
device_categorizer.clear_strict_device_categories ();
|
|
|
|
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 && (i->second.first->is_strict () || i->second.second->is_strict ())) {
|
|
device_categorizer.set_strict_device_category (i->first);
|
|
}
|
|
}
|
|
|
|
// check for circuits that don't match
|
|
|
|
for (std::map<size_t, std::pair<std::vector<const db::Circuit *>, std::vector<const db::Circuit *> > >::const_iterator i = cat2circuits.begin (); i != cat2circuits.end (); ++i) {
|
|
if (i->second.first.empty ()) {
|
|
good = false;
|
|
if (mp_logger) {
|
|
for (std::vector<const db::Circuit *>::const_iterator j = i->second.second.begin (); j != i->second.second.end (); ++j) {
|
|
mp_logger->circuit_mismatch (0, *j);
|
|
}
|
|
}
|
|
}
|
|
if (i->second.second.empty ()) {
|
|
good = false;
|
|
if (mp_logger) {
|
|
for (std::vector<const db::Circuit *>::const_iterator j = i->second.first.begin (); j != i->second.first.end (); ++j) {
|
|
mp_logger->circuit_mismatch (*j, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::map<const db::Circuit *, CircuitMapper> c12_pin_mapping, c22_pin_mapping;
|
|
|
|
tl::RelativeProgress progress (tl::to_string (tr ("Comparing netlists")), a->circuit_count (), 1);
|
|
|
|
for (db::Netlist::const_bottom_up_circuit_iterator c = a->begin_bottom_up (); c != a->end_bottom_up (); ++c) {
|
|
|
|
const db::Circuit *ca = c.operator-> ();
|
|
|
|
size_t ccat = circuit_categorizer.cat_for_circuit (ca);
|
|
if (! ccat) {
|
|
continue;
|
|
}
|
|
|
|
std::map<size_t, std::pair<std::vector<const db::Circuit *>, std::vector<const db::Circuit *> > >::const_iterator i = cat2circuits.find (ccat);
|
|
tl_assert (i != cat2circuits.end ());
|
|
tl_assert (! i->second.first.empty ());
|
|
if (i->second.second.empty ()) {
|
|
continue;
|
|
}
|
|
|
|
// NOTE: there can only be one schematic circuit
|
|
tl_assert (i->second.second.size () == size_t (1));
|
|
const db::Circuit *cb = i->second.second.front ();
|
|
|
|
if (all_subcircuits_verified (ca, verified_circuits_a) && all_subcircuits_verified (cb, verified_circuits_b)) {
|
|
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
tl::info << "----------------------------------------------------------------------";
|
|
tl::info << "treating circuit: " << ca->name () << " vs. " << cb->name ();
|
|
}
|
|
if (mp_logger) {
|
|
mp_logger->begin_circuit (ca, cb);
|
|
}
|
|
|
|
bool pin_mismatch = false;
|
|
bool g = compare_circuits (ca, cb, device_categorizer, circuit_categorizer, circuit_pin_mapper, get_net_identity (ca, cb), pin_mismatch, c12_pin_mapping, c22_pin_mapping);
|
|
if (! g) {
|
|
good = false;
|
|
}
|
|
|
|
if (! pin_mismatch) {
|
|
verified_circuits_a.insert (ca);
|
|
verified_circuits_b.insert (cb);
|
|
}
|
|
|
|
derive_pin_equivalence (ca, cb, &circuit_pin_mapper);
|
|
|
|
if (mp_logger) {
|
|
mp_logger->end_circuit (ca, cb, g);
|
|
}
|
|
|
|
} else {
|
|
|
|
if (mp_logger) {
|
|
|
|
std::string msg = generate_subcircuits_not_verified_warning (ca, verified_circuits_a, cb, verified_circuits_b);
|
|
|
|
if (m_with_log) {
|
|
mp_logger->log_entry (db::Error, msg);
|
|
}
|
|
|
|
mp_logger->circuit_skipped (ca, cb, msg);
|
|
good = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
++progress;
|
|
|
|
}
|
|
|
|
if (mp_logger) {
|
|
mp_logger->end_netlist (a, b);
|
|
}
|
|
|
|
return good;
|
|
}
|
|
|
|
static
|
|
std::vector<size_t> collect_anonymous_empty_pins (const db::Circuit *c, CircuitPinCategorizer *circuit_pin_mapper)
|
|
{
|
|
std::vector<size_t> pins;
|
|
|
|
for (db::Circuit::const_pin_iterator p = c->begin_pins (); p != c->end_pins (); ++p) {
|
|
if (p->name ().empty () && ! circuit_pin_mapper->is_mapped (c, p->id ())) {
|
|
const db::Net *net = c->net_for_pin (p->id ());
|
|
if (! net || net->is_passive ()) {
|
|
pins.push_back (p->id ());
|
|
}
|
|
}
|
|
}
|
|
|
|
return pins;
|
|
}
|
|
|
|
void
|
|
NetlistComparer::derive_pin_equivalence (const db::Circuit *ca, const db::Circuit *cb, CircuitPinCategorizer *circuit_pin_mapper)
|
|
{
|
|
// NOTE: All unnamed pins with empty nets are treated as equivalent. There is no other criterion to match these pins.
|
|
|
|
std::vector<size_t> pa, pb;
|
|
pa = collect_anonymous_empty_pins (ca, circuit_pin_mapper);
|
|
pb = collect_anonymous_empty_pins (cb, circuit_pin_mapper);
|
|
|
|
circuit_pin_mapper->map_pins (ca, pa);
|
|
circuit_pin_mapper->map_pins (cb, pb);
|
|
}
|
|
|
|
static bool is_valid_circuit (const db::Circuit *c)
|
|
{
|
|
// typical via subcircuits attach through one pin. We can safely ignore such subcircuits because they don't
|
|
// contribute graph edges.
|
|
return c->pin_count () > 1;
|
|
}
|
|
|
|
bool
|
|
NetlistComparer::all_subcircuits_verified (const db::Circuit *c, const std::set<const db::Circuit *> &verified_circuits) const
|
|
{
|
|
for (db::Circuit::const_subcircuit_iterator sc = c->begin_subcircuits (); sc != c->end_subcircuits (); ++sc) {
|
|
const db::Circuit *cr = sc->circuit_ref ();
|
|
if (is_valid_circuit (cr) && verified_circuits.find (cr) == verified_circuits.end ()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static std::vector<std::string> unverified_names (const db::Circuit *c, const std::set<const db::Circuit *> &verified_circuits)
|
|
{
|
|
std::vector<std::string> names;
|
|
|
|
std::set<const db::Circuit *> seen;
|
|
for (db::Circuit::const_subcircuit_iterator sc = c->begin_subcircuits (); sc != c->end_subcircuits (); ++sc) {
|
|
const db::Circuit *cr = sc->circuit_ref ();
|
|
if (is_valid_circuit (cr) && seen.find (cr) == seen.end () && verified_circuits.find (cr) == verified_circuits.end ()) {
|
|
seen.insert (cr);
|
|
names.push_back (cr->name ());
|
|
}
|
|
}
|
|
|
|
std::sort (names.begin (), names.end ());
|
|
return names;
|
|
}
|
|
|
|
std::string
|
|
NetlistComparer::generate_subcircuits_not_verified_warning (const db::Circuit *ca, const std::set<const db::Circuit *> &verified_circuits_a, const db::Circuit *cb, const std::set<const db::Circuit *> &verified_circuits_b) const
|
|
{
|
|
std::string msg = tl::sprintf (tl::to_string (tr ("Circuits %s and %s could not be compared because the following subcircuits failed to compare:")), ca->name (), cb->name ());
|
|
|
|
std::vector<std::string> names_a = unverified_names (ca, verified_circuits_a);
|
|
if (! names_a.empty ()) {
|
|
msg += "\n A: " + tl::join (names_a, ",");
|
|
}
|
|
|
|
std::vector<std::string> names_b = unverified_names (cb, verified_circuits_b);
|
|
if (! names_b.empty ()) {
|
|
msg += "\n B: " + tl::join (names_b, ",");
|
|
}
|
|
|
|
return msg;
|
|
}
|
|
|
|
static size_t translate_terminal_id (size_t tid, const db::Device *device)
|
|
{
|
|
return device->device_class () ? device->device_class ()->normalize_terminal_id (tid) : tid;
|
|
}
|
|
|
|
static std::vector<std::pair<size_t, size_t> >
|
|
compute_device_key (const db::Device &device, const db::NetGraph &g, bool strict)
|
|
{
|
|
std::vector<std::pair<size_t, size_t> > k;
|
|
|
|
const std::vector<db::DeviceTerminalDefinition> &td = device.device_class ()->terminal_definitions ();
|
|
for (std::vector<db::DeviceTerminalDefinition>::const_iterator t = td.begin (); t != td.end (); ++t) {
|
|
size_t terminal_id = strict ? t->id () : translate_terminal_id (t->id (), &device);
|
|
const db::Net *net = device.net_for_terminal (t->id ());
|
|
size_t net_id = g.node_index_for_net (net);
|
|
k.push_back (std::make_pair (terminal_id, net_id));
|
|
}
|
|
|
|
return k;
|
|
}
|
|
|
|
static std::vector<std::pair<size_t, size_t> >
|
|
compute_device_key_for_this (const db::Device &device, const db::NetGraph &g, bool strict, bool &mapped)
|
|
{
|
|
std::vector<std::pair<size_t, size_t> > k = compute_device_key (device, g, strict);
|
|
|
|
mapped = true;
|
|
for (std::vector<std::pair<size_t, size_t> >::iterator i = k.begin (); i != k.end (); ++i) {
|
|
if (! g.begin () [i->second].has_other ()) {
|
|
i->second = invalid_id; // normalization
|
|
mapped = false;
|
|
}
|
|
}
|
|
|
|
std::sort (k.begin (), k.end ());
|
|
return k;
|
|
}
|
|
|
|
static std::vector<std::pair<size_t, size_t> >
|
|
compute_device_key_for_other (const db::Device &device, const db::NetGraph &g, bool strict, bool &mapped)
|
|
{
|
|
std::vector<std::pair<size_t, size_t> > k = compute_device_key (device, g, strict);
|
|
|
|
mapped = true;
|
|
for (std::vector<std::pair<size_t, size_t> >::iterator i = k.begin (); i != k.end (); ++i) {
|
|
if (! g.begin () [i->second].has_other ()) {
|
|
i->second = invalid_id; // normalization
|
|
mapped = false;
|
|
} else {
|
|
i->second = g.begin () [i->second].other_net_index ();
|
|
}
|
|
}
|
|
|
|
std::sort (k.begin (), k.end ());
|
|
return k;
|
|
}
|
|
|
|
static bool
|
|
compute_subcircuit_key (const db::SubCircuit &subcircuit, const db::NetGraph &g, const std::map<const db::Circuit *, CircuitMapper> *circuit_map, const CircuitPinCategorizer *pin_map, std::vector<std::pair<size_t, size_t> > &k)
|
|
{
|
|
const db::Circuit *cr = subcircuit.circuit_ref ();
|
|
|
|
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 does not exist - report invalid mapping.
|
|
return false;
|
|
}
|
|
|
|
const CircuitMapper *cm = & icm->second;
|
|
cr = cm->other ();
|
|
|
|
// NOTE: cr is given in terms of the canonical "other" circuit.
|
|
|
|
for (db::Circuit::const_pin_iterator p = cr->begin_pins (); p != cr->end_pins (); ++p) {
|
|
|
|
if (cm->has_this_pin_for_other_pin (p->id ())) {
|
|
|
|
size_t this_pin_id = cm->this_pin_from_other_pin (p->id ());
|
|
size_t pin_id = pin_map->normalize_pin_id (cr, p->id ());
|
|
|
|
const db::Net *net = subcircuit.net_for_pin (this_pin_id);
|
|
size_t net_id = g.node_index_for_net (net);
|
|
k.push_back (std::make_pair (pin_id, net_id));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::vector<std::pair<size_t, size_t> >
|
|
compute_subcircuit_key_for_this (const db::SubCircuit &subcircuit, const db::NetGraph &g, const std::map<const db::Circuit *, CircuitMapper> *circuit_map, const CircuitPinCategorizer *pin_map, bool &mapped, bool &valid)
|
|
{
|
|
valid = true;
|
|
std::vector<std::pair<size_t, size_t> > k;
|
|
if (! compute_subcircuit_key (subcircuit, g, circuit_map, pin_map, k)) {
|
|
valid = false;
|
|
}
|
|
|
|
mapped = true;
|
|
for (std::vector<std::pair<size_t, size_t> >::iterator i = k.begin (); i != k.end () && mapped; ++i) {
|
|
if (! g.begin () [i->second].has_other ()) {
|
|
mapped = false;
|
|
}
|
|
}
|
|
|
|
std::sort (k.begin (), k.end ());
|
|
return k;
|
|
}
|
|
|
|
static std::vector<std::pair<size_t, size_t> >
|
|
compute_subcircuit_key_for_other (const db::SubCircuit &subcircuit, const db::NetGraph &g, const std::map<const db::Circuit *, CircuitMapper> *circuit_map, const CircuitPinCategorizer *pin_map, bool &mapped, bool &valid)
|
|
{
|
|
valid = true;
|
|
std::vector<std::pair<size_t, size_t> > k;
|
|
if (! compute_subcircuit_key (subcircuit, g, circuit_map, pin_map, k)) {
|
|
valid = false;
|
|
}
|
|
|
|
mapped = true;
|
|
for (std::vector<std::pair<size_t, size_t> >::iterator i = k.begin (); i != k.end (); ++i) {
|
|
if (! g.begin () [i->second].has_other ()) {
|
|
mapped = false;
|
|
} else {
|
|
i->second = g.begin () [i->second].other_net_index ();
|
|
}
|
|
}
|
|
|
|
std::sort (k.begin (), k.end ());
|
|
return k;
|
|
}
|
|
|
|
namespace {
|
|
|
|
inline double size_dist (size_t a, size_t b)
|
|
{
|
|
double d = a - b;
|
|
return d * d;
|
|
}
|
|
|
|
struct KeyDistance
|
|
{
|
|
typedef std::pair<std::vector<std::pair<size_t, size_t> >, const db::SubCircuit *> value_type;
|
|
|
|
double operator() (const value_type &a, const value_type &b) const
|
|
{
|
|
tl_assert (a.first.size () == b.first.size ());
|
|
double d = 0.0;
|
|
for (std::vector<std::pair<size_t, size_t> >::const_iterator i = a.first.begin (), j = b.first.begin (); i != a.first.end (); ++i, ++j) {
|
|
d += size_dist (i->first, j->first) + size_dist (i->second, j->second);
|
|
}
|
|
return d;
|
|
}
|
|
};
|
|
|
|
struct KeySize
|
|
{
|
|
typedef std::pair<std::vector<std::pair<size_t, size_t> >, const db::SubCircuit *> value_type;
|
|
|
|
bool operator() (const value_type &a, const value_type &b) const
|
|
{
|
|
return (a.first.size () < b.first.size ());
|
|
}
|
|
};
|
|
|
|
struct DeviceConnectionDistance
|
|
{
|
|
typedef std::pair<std::vector<std::pair<size_t, size_t> >, std::pair<const db::Device *, size_t> > value_type;
|
|
|
|
double operator() (const value_type &a, const value_type &b) const
|
|
{
|
|
int d = 0.0;
|
|
for (std::vector<std::pair<size_t, size_t> >::const_iterator i = a.first.begin (), j = b.first.begin (); i != a.first.end () && j != b.first.end (); ++i, ++j) {
|
|
if (i->second != j->second || i->second == invalid_id || j->second == invalid_id) {
|
|
++d;
|
|
}
|
|
}
|
|
return double (d);
|
|
}
|
|
};
|
|
|
|
struct DeviceParametersCompare
|
|
{
|
|
typedef std::pair<std::vector<std::pair<size_t, size_t> >, std::pair<const db::Device *, size_t> > value_type;
|
|
|
|
bool operator() (const value_type &a, const value_type &b) const
|
|
{
|
|
// category and parameters
|
|
return m_dc (a.second, b.second);
|
|
}
|
|
|
|
bool equals (const value_type &a, const value_type &b) const
|
|
{
|
|
// category and parameters
|
|
return m_dc.equals (a.second, b.second);
|
|
}
|
|
|
|
private:
|
|
db::DeviceCompare m_dc;
|
|
};
|
|
|
|
template <class Iter, class Distance>
|
|
void align (Iter i1, Iter i2, Iter j1, Iter j2, Distance distance)
|
|
{
|
|
// TODO: this can probably be done more efficiently
|
|
|
|
std::vector<Iter> vi, vj;
|
|
vi.reserve (std::max (i2 - i1, j2 - j1));
|
|
vj.reserve (std::max (i2 - i1, j2 - j1));
|
|
|
|
for (Iter i = i1; i != i2; ++i) {
|
|
vi.push_back (i);
|
|
}
|
|
|
|
for (Iter j = j1; j != j2; ++j) {
|
|
vj.push_back (j);
|
|
}
|
|
|
|
size_t sz = std::max (vi.size (), vj.size ());
|
|
|
|
if (sz <= 1) {
|
|
return;
|
|
}
|
|
|
|
// Caution: this is an O(2) algorithm ...
|
|
bool any_swapped = true;
|
|
for (size_t n = 0; n < sz - 1 && any_swapped; ++n) {
|
|
|
|
any_swapped = false;
|
|
|
|
for (size_t m = n + 1; m < sz; ++m) {
|
|
if (n >= vi.size () || m >= vi.size () || n >= vj.size () || m >= vj.size ()) {
|
|
continue;
|
|
} else if (distance (*vi [n], *vj [m]) + distance (*vi [m], *vj [n]) < distance (*vi [n], *vj [n]) + distance (*vi [m], *vj [m])) {
|
|
// this will reduce the overall distance:
|
|
std::swap (*vj [n], *vj [m]);
|
|
any_swapped = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief Returns true, if the given net is passive
|
|
* A passive net does not have devices nor pins except those which are ignored (not mapped).
|
|
*/
|
|
static bool is_passive_net (const db::Net *net, const std::map<const db::Circuit *, CircuitMapper> &circuit_and_pin_mapping)
|
|
{
|
|
if (net->terminal_count () != 0) {
|
|
return false;
|
|
}
|
|
|
|
if (net->subcircuit_pin_count () == 0) {
|
|
return true;
|
|
}
|
|
|
|
for (db::Net::const_subcircuit_pin_iterator p = net->begin_subcircuit_pins (); p != net->end_subcircuit_pins (); ++p) {
|
|
const db::Circuit *c = p->subcircuit ()->circuit_ref ();
|
|
std::map<const db::Circuit *, CircuitMapper>::const_iterator ic;
|
|
ic = circuit_and_pin_mapping.find (c);
|
|
if (ic != circuit_and_pin_mapping.end () && ic->second.has_other_pin_for_this_pin (p->pin_id ())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
/**
|
|
* @brief A class for eliminating redundant nodes from the next-generation deduction step
|
|
*
|
|
* The purpose of this class is to reduce the number of attempts to derive new relationships from
|
|
* already matched nodes. Such nodes may be redundant - specifically in bus-like configurations
|
|
* where many nets attach to the same devices or subcircuits, but different terminals or pins.
|
|
* Such nodes are redundant - they do not contribute new knowledge about additional matches.
|
|
*
|
|
* This cache is intended to filter these out. It is built from a graph with "fill". "is_redundant" can
|
|
* be used to check whether any node is redundant to different node (one of them will not be marked
|
|
* as redundant).
|
|
*
|
|
* Redundancy is defined in terms of attached subcircuits or devices, not by the type of pin or
|
|
* terminal which the node's net attaches to.
|
|
*/
|
|
class RedundantNodeCache
|
|
{
|
|
public:
|
|
RedundantNodeCache ()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
void fill (const db::NetGraph &g)
|
|
{
|
|
std::set<std::pair<subcircuit_signature_t, device_signature_t> > signatures;
|
|
|
|
for (db::NetGraph::node_iterator n = g.begin (); n != g.end (); ++n) {
|
|
std::pair<subcircuit_signature_t, device_signature_t> key (subcircuit_signature (*n), device_signature (*n));
|
|
if (signatures.find (key) == signatures.end ()) {
|
|
signatures.insert (key);
|
|
} else {
|
|
m_redundant.insert (n->net ());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool is_redundant (const db::NetGraphNode &n) const
|
|
{
|
|
return m_redundant.find (n.net ()) != m_redundant.end ();
|
|
}
|
|
|
|
private:
|
|
typedef std::vector<std::pair<const db::SubCircuit *, size_t> > subcircuit_signature_t;
|
|
typedef std::vector<std::pair<const db::Device *, size_t> > device_signature_t;
|
|
|
|
std::set<const db::Net *> m_redundant;
|
|
|
|
subcircuit_signature_t subcircuit_signature (const db::NetGraphNode &n) const
|
|
{
|
|
std::map<const db::SubCircuit *, size_t> count;
|
|
for (db::NetGraphNode::edge_iterator e = n.begin (); e != n.end (); ++e) {
|
|
for (std::vector<Transition>::const_iterator t = e->first.begin (); t != e->first.end (); ++t) {
|
|
if (t->is_for_subcircuit ()) {
|
|
count [t->subcircuit ()] += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return subcircuit_signature_t (count.begin (), count.end ());
|
|
}
|
|
|
|
device_signature_t device_signature (const db::NetGraphNode &n) const
|
|
{
|
|
std::map<const db::Device *, size_t> count;
|
|
for (db::NetGraphNode::edge_iterator e = n.begin (); e != n.end (); ++e) {
|
|
for (std::vector<Transition>::const_iterator t = e->first.begin (); t != e->first.end (); ++t) {
|
|
if (! t->is_for_subcircuit ()) {
|
|
count [t->device ()] += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return device_signature_t (count.begin (), count.end ());
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
bool
|
|
NetlistComparer::compare_circuits (const db::Circuit *c1, const db::Circuit *c2,
|
|
db::DeviceCategorizer &device_categorizer,
|
|
db::CircuitCategorizer &circuit_categorizer,
|
|
db::CircuitPinCategorizer &circuit_pin_mapper,
|
|
const std::vector<std::pair<std::pair<const Net *, const Net *>, bool> > &net_identity,
|
|
bool &pin_mismatch,
|
|
std::map<const db::Circuit *, CircuitMapper> &c12_circuit_and_pin_mapping,
|
|
std::map<const db::Circuit *, CircuitMapper> &c22_circuit_and_pin_mapping) const
|
|
{
|
|
tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Comparing circuits ")) + c1->name () + "/" + c2->name ());
|
|
|
|
db::DeviceFilter device_filter (m_cap_threshold, m_res_threshold);
|
|
SubCircuitEquivalenceTracker subcircuit_equivalence;
|
|
DeviceEquivalenceTracker device_equivalence;
|
|
|
|
db::NetGraph g1, g2;
|
|
|
|
size_t unique_pin_id = Transition::first_unique_pin_id ();
|
|
|
|
// NOTE: for normalization we map all subcircuits of c1 to c2.
|
|
// Also, pin swapping will only happen there.
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netgraph) {
|
|
tl::info << "Netlist graph:";
|
|
}
|
|
g1.build (c1, device_categorizer, circuit_categorizer, device_filter, &c12_circuit_and_pin_mapping, &circuit_pin_mapper, (size_t *)0);
|
|
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netgraph) {
|
|
tl::info << "Other netlist graph:";
|
|
}
|
|
// NOTE: the second netlist graph is the reference (schematic). We treat it a little more carefully by using pins from subcircuits which
|
|
// lead to passive nets but connect to non-trivial nets on the outside. This is done by specifying a unique_pin_id counter for the last argument.
|
|
g2.build (c2, device_categorizer, circuit_categorizer, device_filter, &c22_circuit_and_pin_mapping, &circuit_pin_mapper, &unique_pin_id);
|
|
|
|
// Match dummy nodes for null nets
|
|
g1.identify (0, 0);
|
|
g2.identify (0, 0);
|
|
|
|
for (std::vector<std::pair<std::pair<const Net *, const Net *>, bool> >::const_iterator p = net_identity.begin (); p != net_identity.end (); ++p) {
|
|
|
|
// NOTE: nets may vanish, hence there
|
|
if (g1.has_node_index_for_net (p->first.first) && g2.has_node_index_for_net (p->first.second)) {
|
|
|
|
size_t ni1 = g1.node_index_for_net (p->first.first);
|
|
size_t ni2 = g2.node_index_for_net (p->first.second);
|
|
|
|
bool exact_match = (g1.node (ni1) == g2.node (ni2));
|
|
|
|
g1.identify (ni1, ni2, exact_match);
|
|
g2.identify (ni2, ni1, exact_match);
|
|
|
|
// in must_match mode, check if the nets are identical
|
|
if (mp_logger) {
|
|
if (p->second && ! exact_match) {
|
|
if (m_with_log) {
|
|
if (! p->first.first) {
|
|
mp_logger->log_entry (db::Error,
|
|
tl::sprintf (tl::to_string (tr ("Right-side net %s is paired explicitly with a left-side one, but no net is present there")), expanded_name (p->first.second)));
|
|
} else if (! p->first.second) {
|
|
mp_logger->log_entry (db::Error,
|
|
tl::sprintf (tl::to_string (tr ("Left-side net %s is paired explicitly with a right-side one, but no net is present there")), expanded_name (p->first.first)));
|
|
} else {
|
|
mp_logger->log_entry (db::Error,
|
|
tl::sprintf (tl::to_string (tr ("Nets %s are paired explicitly, but are not identical topologically")), nets2string (p->first)));
|
|
}
|
|
}
|
|
mp_logger->net_mismatch (p->first.first, p->first.second);
|
|
} else {
|
|
mp_logger->match_nets (p->first.first, p->first.second);
|
|
}
|
|
}
|
|
|
|
} else if (p->second && g1.has_node_index_for_net (p->first.first)) {
|
|
|
|
if (mp_logger) {
|
|
mp_logger->net_mismatch (p->first.first, p->first.second);
|
|
if (m_with_log && p->first.second) {
|
|
mp_logger->log_entry (db::Error,
|
|
tl::sprintf (tl::to_string (tr ("Nets %s are paired explicitly, but are not identical topologically")), nets2string (p->first)));
|
|
}
|
|
}
|
|
|
|
size_t ni1 = g1.node_index_for_net (p->first.first);
|
|
g1.identify (ni1, 0);
|
|
|
|
} else if (p->second && g2.has_node_index_for_net (p->first.second)) {
|
|
|
|
if (mp_logger) {
|
|
mp_logger->net_mismatch (p->first.first, p->first.second);
|
|
if (m_with_log && p->first.first) {
|
|
mp_logger->log_entry (db::Error,
|
|
tl::sprintf (tl::to_string (tr ("Nets %s are paired explicitly, but are not identical topologically")), nets2string (p->first)));
|
|
}
|
|
}
|
|
|
|
size_t ni2 = g2.node_index_for_net (p->first.second);
|
|
g2.identify (ni2, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int iter = 0;
|
|
|
|
tl::RelativeProgress progress (tl::to_string (tr ("Comparing circuits ")) + c1->name () + "/" + c2->name (), std::max (g1.end () - g1.begin (), g2.end () - g2.begin ()), 1);
|
|
|
|
bool good = false;
|
|
|
|
// check if there is anything to do
|
|
|
|
bool has_any_unresolved = false;
|
|
for (db::NetGraph::node_iterator i1 = g1.begin (); i1 != g1.end () && ! has_any_unresolved; ++i1) {
|
|
has_any_unresolved = (! i1->has_other () && i1->net ());
|
|
}
|
|
for (db::NetGraph::node_iterator i2 = g2.begin (); i2 != g2.end () && ! has_any_unresolved; ++i2) {
|
|
has_any_unresolved = (! i2->has_other () && i2->net ());
|
|
}
|
|
|
|
if (! has_any_unresolved) {
|
|
good = true;
|
|
}
|
|
|
|
// build the redundant node cache
|
|
|
|
RedundantNodeCache redundant_nodes;
|
|
redundant_nodes.fill (g1);
|
|
|
|
// Three passes: one without ambiguities, the second one with ambiguities and names (optional) and a third with ambiguities with topology
|
|
|
|
int num_passes = 3;
|
|
for (int pass = 0; pass < num_passes && ! good; ++pass) {
|
|
|
|
if (pass == 1 && m_dont_consider_net_names) {
|
|
// skip the named pass in "don't consider net names" mode
|
|
continue;
|
|
}
|
|
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
if (pass > 0) {
|
|
if (pass == 1) {
|
|
tl::info << "including ambiguous nodes now (with names)";
|
|
} else {
|
|
tl::info << "including ambiguous nodes now (ignoreing names)";
|
|
}
|
|
}
|
|
}
|
|
|
|
NetlistCompareCore compare (&g1, &g2);
|
|
compare.max_depth = m_max_depth;
|
|
compare.max_n_branch = m_max_n_branch;
|
|
compare.depth_first = m_depth_first;
|
|
compare.dont_consider_net_names = (pass > 1);
|
|
compare.with_ambiguous = (pass > 0);
|
|
compare.circuit_pin_mapper = &circuit_pin_mapper;
|
|
compare.subcircuit_equivalence = &subcircuit_equivalence;
|
|
compare.device_equivalence = &device_equivalence;
|
|
compare.logger = mp_logger;
|
|
compare.with_log = m_with_log;
|
|
compare.progress = &progress;
|
|
|
|
std::vector<NodeEdgePair> nodes, other_nodes;
|
|
|
|
good = true;
|
|
while (true) {
|
|
|
|
++iter;
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
tl::info << "new compare iteration #" << iter;
|
|
tl::info << "deducing from present nodes ...";
|
|
}
|
|
|
|
// deduce new identities from known nodes if possible
|
|
|
|
size_t new_identities = 0;
|
|
|
|
for (db::NetGraph::node_iterator i1 = g1.begin (); i1 != g1.end (); ++i1) {
|
|
|
|
// note: ambiguous nodes don't contribute additional paths, so skip them
|
|
if (i1->has_other () && i1->net () && i1->other_net_index () > 0 && i1->exact_match ()) {
|
|
|
|
if (! redundant_nodes.is_redundant (*i1)) {
|
|
|
|
size_t ni = compare.derive_node_identities (i1 - g1.begin ());
|
|
if (ni > 0 && ni != failed_match) {
|
|
new_identities += ni;
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
tl::info << ni << " new identities.";
|
|
}
|
|
}
|
|
|
|
} else if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
tl::info << "skipped redundant node: " << i1->net()->expanded_name ();
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
tl::info << "checking topological identity ...";
|
|
}
|
|
|
|
// derive new identities through topology: first collect all nets with the same topological signature
|
|
|
|
nodes.clear ();
|
|
other_nodes.clear ();
|
|
|
|
std::vector<NetGraphNode::edge_type> no_edges;
|
|
no_edges.push_back (NetGraphNode::edge_type ());
|
|
|
|
nodes.reserve (g1.end () - g1.begin ());
|
|
for (db::NetGraph::node_iterator i1 = g1.begin (); i1 != g1.end (); ++i1) {
|
|
if (! i1->has_other () && i1->net ()) {
|
|
nodes.push_back (NodeEdgePair (i1.operator-> (), no_edges.begin ()));
|
|
}
|
|
}
|
|
|
|
other_nodes.reserve (g2.end () - g2.begin ());
|
|
for (db::NetGraph::node_iterator i2 = g2.begin (); i2 != g2.end (); ++i2) {
|
|
if (! i2->has_other () && i2->net ()) {
|
|
other_nodes.push_back (NodeEdgePair (i2.operator-> (), no_edges.begin ()));
|
|
}
|
|
}
|
|
|
|
if (nodes.empty () || other_nodes.empty ()) {
|
|
// active mismatched nodes give an error
|
|
for (std::vector<NodeEdgePair>::const_iterator n = nodes.begin (); n != nodes.end () && good; ++n) {
|
|
good = is_passive_net (n->node->net (), c12_circuit_and_pin_mapping);
|
|
}
|
|
for (std::vector<NodeEdgePair >::const_iterator n = other_nodes.begin (); n != other_nodes.end () && good; ++n) {
|
|
good = is_passive_net (n->node->net (), c22_circuit_and_pin_mapping);
|
|
}
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
tl::info << "Stopped with " << nodes.size () << "/" << other_nodes.size () << " nodes left unresolved " << (good ? "(accepted)" : "(not accepted)");
|
|
}
|
|
// this assumes that we don't gain anything here. Stop now.
|
|
break;
|
|
}
|
|
|
|
std::sort (nodes.begin (), nodes.end (), CompareNodeEdgePair ());
|
|
std::sort (other_nodes.begin (), other_nodes.end (), CompareNodeEdgePair ());
|
|
|
|
size_t ni = compare.derive_node_identities_from_node_set (nodes, other_nodes);
|
|
if (ni > 0 && ni != failed_match) {
|
|
new_identities += ni;
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
tl::info << ni << " new identities.";
|
|
}
|
|
}
|
|
|
|
if (new_identities == 0) {
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
tl::info << "Stopped with " << nodes.size () << "/" << other_nodes.size () << " nodes left unresolved.";
|
|
}
|
|
good = false;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (pass + 1 == num_passes && ! good && mp_logger && m_with_log) {
|
|
compare.analyze_failed_matches ();
|
|
}
|
|
|
|
}
|
|
|
|
// Report missing net assignment
|
|
|
|
for (db::NetGraph::node_iterator i = g1.begin (); i != g1.end (); ++i) {
|
|
if (! i->has_other ()) {
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare || tl::verbosity () >= 40) {
|
|
tl::info << "Unresolved net from left: " << i->net ()->expanded_name () << " " << (good ? "(accepted)" : "(not accepted)");
|
|
}
|
|
if (mp_logger) {
|
|
if (good) {
|
|
mp_logger->match_nets (i->net (), 0);
|
|
} else {
|
|
mp_logger->net_mismatch (i->net (), 0);
|
|
}
|
|
}
|
|
if (good) {
|
|
// in the "good" case, match the nets against 0
|
|
g1.identify (g1.node_index_for_net (i->net ()), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (db::NetGraph::node_iterator i = g2.begin (); i != g2.end (); ++i) {
|
|
if (! i->has_other ()) {
|
|
if (db::NetlistCompareGlobalOptions::options ()->debug_netcompare) {
|
|
tl::info << "Unresolved net from right: " << i->net ()->expanded_name () << " " << (good ? "(accepted)" : "(not accepted)");
|
|
}
|
|
if (mp_logger) {
|
|
if (good) {
|
|
mp_logger->match_nets (0, i->net ());
|
|
} else {
|
|
mp_logger->net_mismatch (0, i->net ());
|
|
}
|
|
}
|
|
if (good) {
|
|
// in the "good" case, match the nets against 0
|
|
g2.identify (g2.node_index_for_net (i->net ()), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
do_pin_assignment (c1, g1, c2, g2, c12_circuit_and_pin_mapping, c22_circuit_and_pin_mapping, pin_mismatch, good);
|
|
do_device_assignment (c1, g1, c2, g2, device_filter, device_categorizer, device_equivalence, good);
|
|
do_subcircuit_assignment (c1, g1, c2, g2, circuit_categorizer, circuit_pin_mapper, c12_circuit_and_pin_mapping, c22_circuit_and_pin_mapping, subcircuit_equivalence, good);
|
|
|
|
return good;
|
|
}
|
|
|
|
static void
|
|
analyze_pin_mismatch (const db::Pin *pin1, const db::Circuit *c1, const db::Pin *pin2, const db::Circuit * /*c2*/, db::NetlistCompareLogger *logger)
|
|
{
|
|
if (! pin1) {
|
|
logger->log_entry (db::Error, tl::sprintf (tl::to_string (tr ("No equivalent pin %s from reference netlist found in netlist.\nThis is an indication that a physical connection is not made to the subcircuit.")), pin2->expanded_name ()));
|
|
}
|
|
|
|
if (! pin2) {
|
|
|
|
logger->log_entry (db::Error, tl::sprintf (tl::to_string (tr ("No equivalent pin %s from netlist found in reference netlist.\nThis is an indication that additional physical connections are made to the subcircuit cell.")), pin1->expanded_name ()));
|
|
|
|
// attempt to identify pins which are creating invalid connections
|
|
for (auto p = c1->begin_parents (); p != c1->end_parents (); ++p) {
|
|
for (auto c = p->begin_subcircuits (); c != p->end_subcircuits (); ++c) {
|
|
const db::SubCircuit &sc = *c;
|
|
if (sc.circuit_ref () == c1) {
|
|
const db::Net *net = sc.net_for_pin (pin1->id ());
|
|
if (net && (net->subcircuit_pin_count () > 1 || net->terminal_count () > 0 || net->pin_count () > 0)) {
|
|
logger->log_entry (db::Info, tl::sprintf (tl::to_string (tr ("Potential invalid connection in circuit %s, subcircuit cell reference at %s")), p->name (), sc.trans ().to_string ()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
bool
|
|
NetlistComparer::handle_pin_mismatch (const db::NetGraph &g1, const db::Circuit *c1, const db::Pin *pin1, const db::NetGraph &g2, const db::Circuit *c2, const db::Pin *pin2) const
|
|
{
|
|
const db::Circuit *c = pin1 ? c1 : c2;
|
|
const db::Pin *pin = pin1 ? pin1 : pin2;
|
|
const db::NetGraph *graph = pin1 ? &g1 : &g2;
|
|
const db::Net *net = c->net_for_pin (pin->id ());
|
|
|
|
// Nets which are paired with "null" are "safely to be ignored" and
|
|
// pin matching against "null" is valid.
|
|
if (net) {
|
|
const db::NetGraphNode &n = graph->node (graph->node_index_for_net (net));
|
|
if (n.has_other () && n.other_net_index () == 0) {
|
|
if (mp_logger) {
|
|
mp_logger->match_pins (pin1, pin2);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Determine whether the pin in question is used - only in this case we will report an error.
|
|
// Otherwise, the report will be "match" against 0.
|
|
// "used" follows a heuristic criterion derived from the subcircuits which make use of this circuit:
|
|
// if one of these connects the pin to a net with either connections upwards, other subcircuits or
|
|
// devices, the pin is regarded "used".
|
|
// TODO: it would be better probably to have a global concept of "used pins" which considers all
|
|
// devices and propagates their presence as "used" property upwards, then downwards to the subcircuit
|
|
// pins.
|
|
|
|
bool is_not_connected = true;
|
|
for (db::Circuit::const_refs_iterator r = c->begin_refs (); r != c->end_refs () && is_not_connected; ++r) {
|
|
const db::SubCircuit *sc = r.operator-> ();
|
|
const db::Net *net = sc->net_for_pin (pin->id ());
|
|
if (net && ((net->terminal_count () + net->pin_count ()) > 0 || net->subcircuit_pin_count () > 1)) {
|
|
is_not_connected = false;
|
|
}
|
|
}
|
|
|
|
if (is_not_connected) {
|
|
if (mp_logger) {
|
|
mp_logger->match_pins (pin1, pin2);
|
|
}
|
|
return true;
|
|
} else {
|
|
|
|
if (mp_logger) {
|
|
if (m_with_log) {
|
|
analyze_pin_mismatch (pin1, c1, pin2, c2, mp_logger);
|
|
}
|
|
mp_logger->pin_mismatch (pin1, pin2);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
NetlistComparer::do_pin_assignment (const db::Circuit *c1, const db::NetGraph &g1, const db::Circuit *c2, const db::NetGraph &g2, std::map<const db::Circuit *, CircuitMapper> &c12_circuit_and_pin_mapping, std::map<const db::Circuit *, CircuitMapper> &c22_circuit_and_pin_mapping, bool &pin_mismatch, bool &good) const
|
|
{
|
|
// Report pin assignment
|
|
// This step also does the pin identity mapping.
|
|
|
|
// try to assign abstract pins by name with higher prio
|
|
// NOTE: An "abstract" pin is a concept that is used in the context of circuit abstracts where the circuits is
|
|
// intentionally emptied. An "abstract pin" is not just a floating pin. An abstract does not have a net while
|
|
// a floating pin has a net, but this net is passive - i.e. not attached to any device.
|
|
std::map<std::string, std::pair<const db::Pin *, const db::Pin *> > abstract_pins_by_name;
|
|
|
|
for (db::Circuit::const_pin_iterator p = c2->begin_pins (); p != c2->end_pins (); ++p) {
|
|
const db::Net *net = c2->net_for_pin (p->id ());
|
|
if (!net && !p->name ().empty ()) {
|
|
abstract_pins_by_name.insert (std::make_pair (db::Netlist::normalize_name (m_case_sensitive, p->name ()), std::make_pair ((const db::Pin *) 0, (const db::Pin *) 0))).first->second.second = p.operator-> ();
|
|
}
|
|
}
|
|
|
|
for (db::Circuit::const_pin_iterator p = c1->begin_pins (); p != c1->end_pins (); ++p) {
|
|
const db::Net *net = c1->net_for_pin (p->id ());
|
|
if (!net && !p->name ().empty ()) {
|
|
abstract_pins_by_name.insert (std::make_pair (db::Netlist::normalize_name (m_case_sensitive, p->name ()), std::make_pair ((const db::Pin *) 0, (const db::Pin *) 0))).first->second.first = p.operator-> ();
|
|
}
|
|
}
|
|
|
|
std::map<const db::Pin *, const db::Pin *> abstract_pin_name_mapping;
|
|
for (std::map<std::string, std::pair<const db::Pin *, const db::Pin *> >::const_iterator i = abstract_pins_by_name.begin (); i != abstract_pins_by_name.end (); ++i) {
|
|
if (i->second.first && i->second.second) {
|
|
abstract_pin_name_mapping [i->second.first] = i->second.second;
|
|
abstract_pin_name_mapping [i->second.second] = i->second.first;
|
|
}
|
|
}
|
|
|
|
std::vector<const db::Pin *> abstract_pins2;
|
|
std::multimap<size_t, const db::Pin *> net2pin2;
|
|
for (db::Circuit::const_pin_iterator p = c2->begin_pins (); p != c2->end_pins (); ++p) {
|
|
const db::Net *net = c2->net_for_pin (p->id ());
|
|
if (net) {
|
|
net2pin2.insert (std::make_pair (g2.node_index_for_net (net), p.operator-> ()));
|
|
} else if (abstract_pin_name_mapping.find (p.operator-> ()) == abstract_pin_name_mapping.end ()) {
|
|
abstract_pins2.push_back (p.operator-> ());
|
|
}
|
|
}
|
|
|
|
// collect missing assignment for circuit 1
|
|
std::multimap<size_t, const db::Pin *> net2pin1;
|
|
|
|
std::vector<const db::Pin *>::iterator next_abstract = abstract_pins2.begin ();
|
|
|
|
CircuitMapper &c12_pin_mapping = c12_circuit_and_pin_mapping [c1];
|
|
c12_pin_mapping.set_other (c2);
|
|
|
|
// dummy mapping: we show this circuit is used.
|
|
CircuitMapper &c22_pin_mapping = c22_circuit_and_pin_mapping [c2];
|
|
c22_pin_mapping.set_other (c2);
|
|
|
|
for (db::Circuit::const_pin_iterator p = c1->begin_pins (); p != c1->end_pins (); ++p) {
|
|
|
|
const db::Net *net = c1->net_for_pin (p->id ());
|
|
if (! net) {
|
|
|
|
std::map<const db::Pin *, const db::Pin *>::const_iterator fp = abstract_pin_name_mapping.find (p.operator-> ());
|
|
if (fp != abstract_pin_name_mapping.end ()) {
|
|
|
|
// assign an abstract pin - this is a dummy assignment which is mitigated
|
|
// by declaring the pins equivalent in derive_pin_equivalence
|
|
if (mp_logger) {
|
|
mp_logger->match_pins (p.operator-> (), fp->second);
|
|
}
|
|
c12_pin_mapping.map_pin (p->id (), fp->second->id ());
|
|
c22_pin_mapping.map_pin (fp->second->id (), fp->second->id ());
|
|
|
|
} else if (next_abstract != abstract_pins2.end ()) {
|
|
|
|
// assign an abstract pin - this is a dummy assignment which is mitigated
|
|
// by declaring the pins equivalent in derive_pin_equivalence
|
|
if (mp_logger) {
|
|
mp_logger->match_pins (p.operator-> (), *next_abstract);
|
|
}
|
|
c12_pin_mapping.map_pin (p->id (), (*next_abstract)->id ());
|
|
c22_pin_mapping.map_pin ((*next_abstract)->id (), (*next_abstract)->id ());
|
|
|
|
++next_abstract;
|
|
|
|
} else {
|
|
|
|
// otherwise this is an error for subcircuits or worth a report for top-level circuits
|
|
if (! handle_pin_mismatch (g1, c1, p.operator-> (), g2, c2, 0)) {
|
|
good = false;
|
|
pin_mismatch = true;
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const db::NetGraphNode &n = *(g1.begin () + g1.node_index_for_net (net));
|
|
|
|
if (! n.has_other ()) {
|
|
// remember and handle later when we know which pins are not mapped
|
|
net2pin1.insert (std::make_pair (g1.node_index_for_net (net), p.operator-> ()));
|
|
continue;
|
|
}
|
|
|
|
std::multimap<size_t, const db::Pin *>::iterator np = net2pin2.find (n.other_net_index ());
|
|
for (db::Net::const_pin_iterator pi = net->begin_pins (); pi != net->end_pins (); ++pi) {
|
|
|
|
if (np != net2pin2.end () && np->first == n.other_net_index ()) {
|
|
|
|
if (mp_logger) {
|
|
mp_logger->match_pins (pi->pin (), np->second);
|
|
}
|
|
c12_pin_mapping.map_pin (pi->pin ()->id (), np->second->id ());
|
|
// dummy mapping: we show this pin is used.
|
|
c22_pin_mapping.map_pin (np->second->id (), np->second->id ());
|
|
|
|
std::multimap<size_t, const db::Pin *>::iterator np_delete = np;
|
|
++np;
|
|
net2pin2.erase (np_delete);
|
|
|
|
} else {
|
|
|
|
// remember and handle later when we know which pins are not mapped
|
|
net2pin1.insert (std::make_pair (g1.node_index_for_net (net), p.operator-> ()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (std::multimap<size_t, const db::Pin *>::iterator np = net2pin1.begin (); np != net2pin1.end (); ++np) {
|
|
if (! handle_pin_mismatch (g1, c1, np->second, g2, c2, 0)) {
|
|
good = false;
|
|
pin_mismatch = true;
|
|
}
|
|
}
|
|
|
|
for (std::multimap<size_t, const db::Pin *>::iterator np = net2pin2.begin (); np != net2pin2.end (); ++np) {
|
|
if (! handle_pin_mismatch (g1, c1, 0, g2, c2, np->second)) {
|
|
good = false;
|
|
pin_mismatch = true;
|
|
}
|
|
}
|
|
|
|
// abstract pins must match.
|
|
while (next_abstract != abstract_pins2.end ()) {
|
|
if (! handle_pin_mismatch (g1, c1, 0, g2, c2, *next_abstract)) {
|
|
good = false;
|
|
pin_mismatch = true;
|
|
}
|
|
++next_abstract;
|
|
}
|
|
}
|
|
|
|
void
|
|
NetlistComparer::do_device_assignment (const db::Circuit *c1, const db::NetGraph &g1, const db::Circuit *c2, const db::NetGraph &g2, const db::DeviceFilter &device_filter, db::DeviceCategorizer &device_categorizer, DeviceEquivalenceTracker &device_eq, bool &good) const
|
|
{
|
|
// Report device assignment
|
|
|
|
std::multimap<std::vector<std::pair<size_t, size_t> >, std::pair<const db::Device *, size_t> > device_map;
|
|
|
|
typedef std::vector<std::pair<std::vector<std::pair<size_t, size_t> >, std::pair<const db::Device *, size_t> > > unmatched_list;
|
|
unmatched_list unmatched_a, unmatched_b;
|
|
|
|
// check mapping of devices whose equivalence is established topologically
|
|
|
|
for (db::Circuit::const_device_iterator d = c1->begin_devices (); d != c1->end_devices (); ++d) {
|
|
|
|
if (! device_filter.filter (d.operator-> ())) {
|
|
continue;
|
|
}
|
|
|
|
if (device_eq.other (d.operator-> ())) {
|
|
continue;
|
|
}
|
|
|
|
size_t device_cat = device_categorizer.cat_for_device (d.operator-> ());
|
|
if (! device_cat) {
|
|
// device is ignored
|
|
continue;
|
|
}
|
|
|
|
bool mapped = true;
|
|
std::vector<std::pair<size_t, size_t> > k = compute_device_key_for_this (*d, g1, device_categorizer.is_strict_device_category (device_cat), mapped);
|
|
|
|
if (! mapped) {
|
|
if (mp_logger) {
|
|
unmatched_a.push_back (std::make_pair (k, std::make_pair (d.operator-> (), device_cat)));
|
|
}
|
|
good = false;
|
|
} else {
|
|
// TODO: report devices which cannot be distinguished topologically?
|
|
device_map.insert (std::make_pair (k, std::make_pair (d.operator-> (), device_cat)));
|
|
}
|
|
|
|
}
|
|
|
|
for (db::Circuit::const_device_iterator d = c2->begin_devices (); d != c2->end_devices (); ++d) {
|
|
|
|
if (! device_filter.filter (d.operator-> ())) {
|
|
continue;
|
|
}
|
|
|
|
size_t device_cat = device_categorizer.cat_for_device (d.operator-> ());
|
|
if (! device_cat) {
|
|
// device is ignored
|
|
continue;
|
|
}
|
|
|
|
const db::Device *c1_device = 0;
|
|
size_t c1_device_cat = 0;
|
|
|
|
const db::Device *d_this = device_eq.other (d.operator-> ());
|
|
if (d_this) {
|
|
|
|
size_t device_cat_this = device_categorizer.cat_for_device (d_this);
|
|
if (! device_cat_this) {
|
|
// device is ignored
|
|
continue;
|
|
}
|
|
|
|
bool mapped1 = true, mapped2 = true;
|
|
std::vector<std::pair<size_t, size_t> > k_this = compute_device_key_for_this (*d_this, g1, device_categorizer.is_strict_device_category (device_cat_this), mapped1);
|
|
std::vector<std::pair<size_t, size_t> > k = compute_device_key_for_other (*d, g2, device_categorizer.is_strict_device_category (device_cat), mapped2);
|
|
|
|
if (! mapped1 || ! mapped2 || k != k_this) {
|
|
|
|
// topological mismatch
|
|
if (mp_logger) {
|
|
mp_logger->device_mismatch (d_this, d.operator-> ());
|
|
}
|
|
good = false;
|
|
|
|
} else {
|
|
|
|
c1_device = d_this;
|
|
c1_device_cat = device_cat_this;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bool mapped = true;
|
|
std::vector<std::pair<size_t, size_t> > k = compute_device_key_for_other (*d, g2, device_categorizer.is_strict_device_category (device_cat), mapped);
|
|
|
|
auto dm = device_map.find (k);
|
|
|
|
if (! mapped || dm == device_map.end () || dm->first != k) {
|
|
|
|
if (mp_logger) {
|
|
unmatched_b.push_back (std::make_pair (k, std::make_pair (d.operator-> (), device_cat)));
|
|
}
|
|
good = false;
|
|
|
|
} else {
|
|
|
|
auto dmm = dm;
|
|
++dmm;
|
|
size_t n = 1;
|
|
while (dmm != device_map.end () && dmm->first == k) {
|
|
++dmm;
|
|
++n;
|
|
}
|
|
|
|
if (n > 1) {
|
|
|
|
// device ambiguities may arise from different devices being connected in parallel:
|
|
// try to identify the device which matches
|
|
|
|
db::DeviceCompare dc;
|
|
|
|
for (auto i = dm; i != dmm; ++i) {
|
|
if (dc.equals (std::make_pair (i->second.first, i->second.second), std::make_pair (d.operator-> (), device_cat))) {
|
|
dm = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
c1_device = dm->second.first;
|
|
c1_device_cat = dm->second.second;
|
|
|
|
device_map.erase (dm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (c1_device) {
|
|
|
|
db::DeviceCompare dc;
|
|
|
|
if (! dc.equals (std::make_pair (c1_device, c1_device_cat), std::make_pair (d.operator-> (), device_cat))) {
|
|
if (c1_device_cat != device_cat) {
|
|
if (mp_logger) {
|
|
mp_logger->match_devices_with_different_device_classes (c1_device, d.operator-> ());
|
|
}
|
|
good = false;
|
|
} else {
|
|
if (mp_logger) {
|
|
mp_logger->match_devices_with_different_parameters (c1_device, d.operator-> ());
|
|
}
|
|
good = false;
|
|
}
|
|
} else {
|
|
if (mp_logger) {
|
|
mp_logger->match_devices (c1_device, d.operator-> ());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (std::multimap<std::vector<std::pair<size_t, size_t> >, std::pair<const db::Device *, size_t> >::const_iterator dm = device_map.begin (); dm != device_map.end (); ++dm) {
|
|
if (mp_logger) {
|
|
unmatched_a.push_back (*dm);
|
|
}
|
|
good = false;
|
|
}
|
|
|
|
// try to do some better mapping of unmatched devices - they will still be reported as mismatching, but their pairing gives some hint
|
|
// what to fix.
|
|
|
|
if (mp_logger) {
|
|
|
|
size_t max_analysis_set = 1000;
|
|
if (unmatched_a.size () + unmatched_b.size () > max_analysis_set) {
|
|
|
|
// don't try too much analysis - this may be a waste of time
|
|
for (unmatched_list::const_iterator i = unmatched_a.begin (); i != unmatched_a.end (); ++i) {
|
|
mp_logger->device_mismatch (i->second.first, 0);
|
|
}
|
|
for (unmatched_list::const_iterator i = unmatched_b.begin (); i != unmatched_b.end (); ++i) {
|
|
mp_logger->device_mismatch (0, i->second.first);
|
|
}
|
|
|
|
} else {
|
|
|
|
DeviceParametersCompare cmp;
|
|
|
|
std::sort (unmatched_a.begin (), unmatched_a.end (), cmp);
|
|
std::sort (unmatched_b.begin (), unmatched_b.end (), cmp);
|
|
|
|
for (unmatched_list::iterator i = unmatched_a.begin (), j = unmatched_b.begin (); i != unmatched_a.end () || j != unmatched_b.end (); ) {
|
|
|
|
while (j != unmatched_b.end () && (i == unmatched_a.end () || !cmp.equals (*j, *i))) {
|
|
mp_logger->device_mismatch (0, j->second.first);
|
|
++j;
|
|
}
|
|
|
|
while (i != unmatched_a.end () && (j == unmatched_b.end () || !cmp.equals (*i, *j))) {
|
|
mp_logger->device_mismatch (i->second.first, 0);
|
|
++i;
|
|
}
|
|
|
|
if (i == unmatched_a.end () || j == unmatched_b.end ()) {
|
|
break;
|
|
}
|
|
|
|
unmatched_list::iterator ii = i, jj = j;
|
|
++i, ++j;
|
|
|
|
while (i != unmatched_a.end () && cmp.equals (*i, *ii)) {
|
|
++i;
|
|
}
|
|
|
|
while (j != unmatched_b.end () && cmp.equals (*j, *jj)) {
|
|
++j;
|
|
}
|
|
|
|
align (ii, i, jj, j, DeviceConnectionDistance ());
|
|
|
|
for ( ; ii != i && jj != j; ++ii, ++jj) {
|
|
mp_logger->device_mismatch (ii->second.first, jj->second.first);
|
|
}
|
|
|
|
for ( ; jj != j; ++jj) {
|
|
mp_logger->device_mismatch (0, jj->second.first);
|
|
}
|
|
|
|
for ( ; ii != i; ++ii) {
|
|
mp_logger->device_mismatch (ii->second.first, 0);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
NetlistComparer::do_subcircuit_assignment (const db::Circuit *c1, const db::NetGraph &g1, const db::Circuit *c2, const db::NetGraph &g2, CircuitCategorizer &circuit_categorizer, const CircuitPinCategorizer &circuit_pin_mapper, std::map<const Circuit *, CircuitMapper> &c12_circuit_and_pin_mapping, std::map<const Circuit *, CircuitMapper> &c22_circuit_and_pin_mapping, SubCircuitEquivalenceTracker &subcircuit_eq, bool &good) const
|
|
{
|
|
// Report subcircuit assignment
|
|
|
|
std::multimap<std::vector<std::pair<size_t, size_t> >, std::pair<const db::SubCircuit *, size_t> > subcircuit_map;
|
|
|
|
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;
|
|
}
|
|
|
|
if (subcircuit_eq.other (sc.operator-> ())) {
|
|
continue;
|
|
}
|
|
|
|
bool mapped = true, valid = true;
|
|
std::vector<std::pair<size_t, size_t> > k = compute_subcircuit_key_for_this (*sc, g1, &c12_circuit_and_pin_mapping, &circuit_pin_mapper, mapped, valid);
|
|
|
|
if (! mapped) {
|
|
if (mp_logger) {
|
|
mp_logger->subcircuit_mismatch (sc.operator-> (), 0);
|
|
}
|
|
good = false;
|
|
} else if (valid) {
|
|
// TODO: report devices which cannot be distinguished topologically?
|
|
subcircuit_map.insert (std::make_pair (k, std::make_pair (sc.operator-> (), sc_cat)));
|
|
} else {
|
|
// emit a mismatch event but do not consider that an error - this may happen if the circuit has been dropped intentionally (e.g. via cells)
|
|
if (mp_logger) {
|
|
mp_logger->subcircuit_mismatch (sc.operator-> (), 0);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
typedef std::vector<std::pair<std::vector<std::pair<size_t, size_t> >, const db::SubCircuit *> > unmatched_list;
|
|
unmatched_list unmatched_a, unmatched_b;
|
|
|
|
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;
|
|
}
|
|
|
|
const db::SubCircuit *sc_this = subcircuit_eq.other (sc.operator-> ());
|
|
if (sc_this) {
|
|
|
|
size_t sc_cat_this = circuit_categorizer.cat_for_subcircuit (sc_this);
|
|
if (! sc_cat_this) {
|
|
// subcircuit is ignored
|
|
continue;
|
|
}
|
|
|
|
bool mapped1 = true, mapped2 = true;
|
|
bool valid1 = true, valid2 = true;
|
|
std::vector<std::pair<size_t, size_t> > k_this = compute_subcircuit_key_for_this (*sc_this, g1, &c12_circuit_and_pin_mapping, &circuit_pin_mapper, mapped1, valid1);
|
|
std::vector<std::pair<size_t, size_t> > k = compute_subcircuit_key_for_other (*sc, g2, &c22_circuit_and_pin_mapping, &circuit_pin_mapper, mapped2, valid2);
|
|
|
|
if (! valid1 || ! valid2 || ! mapped1 || ! mapped2 || k_this != k || sc_cat != sc_cat_this) {
|
|
if (mp_logger) {
|
|
mp_logger->subcircuit_mismatch (sc_this, sc.operator-> ());
|
|
}
|
|
good = false;
|
|
} else {
|
|
if (mp_logger) {
|
|
mp_logger->match_subcircuits (sc_this, sc.operator-> ());
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
bool mapped = true, valid = true;
|
|
std::vector<std::pair<size_t, size_t> > k = compute_subcircuit_key_for_other (*sc, g2, &c22_circuit_and_pin_mapping, &circuit_pin_mapper, mapped, valid);
|
|
|
|
std::multimap<std::vector<std::pair<size_t, size_t> >, std::pair<const db::SubCircuit *, size_t> >::iterator scm = subcircuit_map.find (k);
|
|
|
|
if (! mapped || scm == subcircuit_map.end () || scm->first != k) {
|
|
|
|
if (mp_logger) {
|
|
unmatched_b.push_back (std::make_pair (k, sc.operator-> ()));
|
|
}
|
|
good = false;
|
|
|
|
} else {
|
|
|
|
db::SubCircuitCompare scc;
|
|
|
|
std::multimap<std::vector<std::pair<size_t, size_t> >, std::pair<const db::SubCircuit *, size_t> >::iterator scm_start = scm;
|
|
|
|
bool found = false;
|
|
size_t nscm = 0;
|
|
while (! found && scm != subcircuit_map.end () && scm->first == k) {
|
|
++nscm;
|
|
if (scc.equals (scm->second, std::make_pair (sc.operator-> (), sc_cat))) {
|
|
found = true;
|
|
} else {
|
|
++scm;
|
|
}
|
|
}
|
|
|
|
if (! found) {
|
|
|
|
if (nscm == 1) {
|
|
|
|
// unique match, but doesn't fit: report this one as paired, but mismatching:
|
|
if (mp_logger) {
|
|
mp_logger->subcircuit_mismatch (scm_start->second.first, sc.operator-> ());
|
|
}
|
|
|
|
// no longer look for this one
|
|
subcircuit_map.erase (scm_start);
|
|
|
|
} else {
|
|
|
|
// no unique match
|
|
if (mp_logger) {
|
|
mp_logger->subcircuit_mismatch (0, sc.operator-> ());
|
|
}
|
|
|
|
}
|
|
|
|
good = false;
|
|
|
|
} else {
|
|
|
|
if (mp_logger) {
|
|
mp_logger->match_subcircuits (scm->second.first, sc.operator-> ());
|
|
}
|
|
|
|
// no longer look for this one
|
|
subcircuit_map.erase (scm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (std::multimap<std::vector<std::pair<size_t, size_t> >, std::pair<const db::SubCircuit *, size_t> >::const_iterator scm = subcircuit_map.begin (); scm != subcircuit_map.end (); ++scm) {
|
|
if (mp_logger) {
|
|
unmatched_a.push_back (std::make_pair (scm->first, scm->second.first));
|
|
}
|
|
good = false;
|
|
}
|
|
|
|
// try to do some pairing between the mismatching subcircuits - even though we will still report them as
|
|
// mismatches it will give some better hint about what needs to be fixed
|
|
|
|
if (mp_logger) {
|
|
|
|
size_t max_analysis_set = 1000;
|
|
if (unmatched_a.size () + unmatched_b.size () > max_analysis_set) {
|
|
|
|
// don't try too much analysis - this may be a waste of time
|
|
for (unmatched_list::const_iterator i = unmatched_a.begin (); i != unmatched_a.end (); ++i) {
|
|
mp_logger->subcircuit_mismatch (i->second, 0);
|
|
}
|
|
for (unmatched_list::const_iterator i = unmatched_b.begin (); i != unmatched_b.end (); ++i) {
|
|
mp_logger->subcircuit_mismatch (0, i->second);
|
|
}
|
|
|
|
} else {
|
|
|
|
std::sort (unmatched_a.begin (), unmatched_a.end (), KeySize ());
|
|
std::sort (unmatched_b.begin (), unmatched_b.end (), KeySize ());
|
|
|
|
for (unmatched_list::iterator i = unmatched_a.begin (), j = unmatched_b.begin (); i != unmatched_a.end () || j != unmatched_b.end (); ) {
|
|
|
|
while (j != unmatched_b.end () && (i == unmatched_a.end () || j->first.size () < i->first.size ())) {
|
|
mp_logger->subcircuit_mismatch (0, j->second);
|
|
++j;
|
|
}
|
|
|
|
while (i != unmatched_a.end () && (j == unmatched_b.end () || i->first.size () < j->first.size ())) {
|
|
mp_logger->subcircuit_mismatch (i->second, 0);
|
|
++i;
|
|
}
|
|
|
|
if (i == unmatched_a.end () || j == unmatched_b.end ()) {
|
|
break;
|
|
}
|
|
|
|
unmatched_list::iterator ii = i, jj = j;
|
|
++i, ++j;
|
|
size_t n = ii->first.size ();
|
|
tl_assert (n == jj->first.size ());
|
|
|
|
while (i != unmatched_a.end () && i->first.size () == n) {
|
|
++i;
|
|
}
|
|
|
|
while (j != unmatched_b.end () && j->first.size () == n) {
|
|
++j;
|
|
}
|
|
|
|
align (ii, i, jj, j, KeyDistance ());
|
|
|
|
for ( ; ii != i && jj != j; ++ii, ++jj) {
|
|
mp_logger->subcircuit_mismatch (ii->second, jj->second);
|
|
}
|
|
|
|
for ( ; jj != j; ++jj) {
|
|
mp_logger->subcircuit_mismatch (0, jj->second);
|
|
}
|
|
|
|
for ( ; ii != i; ++ii) {
|
|
mp_logger->subcircuit_mismatch (ii->second, 0);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static bool derive_symmetry_groups (const db::NetGraph &graph, const tl::equivalence_clusters<const NetGraphNode *> &identical_nodes, std::set<size_t> &considered_nodes, const std::set<size_t> &symmetry_group, std::vector<std::set<size_t> > &symmetry_groups)
|
|
{
|
|
std::set<size_t> cids;
|
|
std::set<size_t> new_symmetry_group;
|
|
NetGraphNode::edge_type::first_type edge;
|
|
|
|
std::vector<NetGraphNode::edge_type> common_nodes_first;
|
|
|
|
bool has_candidate = true;
|
|
|
|
for (std::set<size_t>::const_iterator g = symmetry_group.begin (); g != symmetry_group.end () && has_candidate; ++g) {
|
|
|
|
std::vector<NetGraphNode::edge_type> common_nodes;
|
|
|
|
const NetGraphNode &n = graph.node (*g);
|
|
for (NetGraphNode::edge_iterator e = n.begin (); e != n.end () && has_candidate; ++e) {
|
|
|
|
if (considered_nodes.find (e->second.first) != considered_nodes.end ()) {
|
|
continue;
|
|
}
|
|
|
|
const NetGraphNode *other = &graph.node (e->second.first);
|
|
// NOTE: nodes with pins don't go into a symmetry group as we don't know what the pins connect to
|
|
if (other->net ()->pin_count () == 0 && identical_nodes.has_attribute (other)) {
|
|
|
|
if (cids.empty ()) {
|
|
edge = e->first;
|
|
} else if (edge != e->first) {
|
|
has_candidate = false;
|
|
}
|
|
|
|
if (has_candidate) {
|
|
cids.insert (identical_nodes.cluster_id (other));
|
|
if (cids.size () > 1) {
|
|
has_candidate = false;
|
|
} else {
|
|
new_symmetry_group.insert (e->second.first);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
common_nodes.push_back (*e);
|
|
}
|
|
|
|
}
|
|
|
|
if (has_candidate) {
|
|
|
|
// all other edges need to have identical destinations for the symmetry group to be
|
|
// actually symmetric
|
|
std::sort (common_nodes.begin (), common_nodes.end ());
|
|
if (g == symmetry_group.begin ()) {
|
|
common_nodes_first.swap (common_nodes);
|
|
} else if (common_nodes_first != common_nodes) {
|
|
has_candidate = false;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (has_candidate && ! cids.empty () && new_symmetry_group.size () > 1) {
|
|
|
|
considered_nodes.insert (new_symmetry_group.begin (), new_symmetry_group.end ());
|
|
|
|
if (derive_symmetry_groups (graph, identical_nodes, considered_nodes, new_symmetry_group, symmetry_groups)) {
|
|
|
|
symmetry_groups.push_back (new_symmetry_group);
|
|
|
|
} else {
|
|
|
|
std::set<size_t> cn;
|
|
std::set_difference (considered_nodes.begin (), considered_nodes.end (), new_symmetry_group.begin (), new_symmetry_group.end (), std::inserter (cn, cn.begin ()));
|
|
considered_nodes.swap (cn);
|
|
|
|
has_candidate = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return has_candidate;
|
|
}
|
|
|
|
void
|
|
NetlistComparer::join_symmetric_nets (db::Circuit *circuit)
|
|
{
|
|
if (! circuit) {
|
|
return;
|
|
}
|
|
|
|
tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Join symmetric nodes for circuit: ")) + circuit->name ());
|
|
|
|
db::DeviceFilter device_filter (m_cap_threshold, m_res_threshold);
|
|
db::CircuitPinCategorizer circuit_pin_equivalence;
|
|
std::map<const db::Circuit *, CircuitMapper> circuit_and_pin_mapping;
|
|
|
|
db::NetGraph graph;
|
|
db::CircuitCategorizer circuit_categorizer;
|
|
db::DeviceCategorizer device_categorizer;
|
|
graph.build (circuit, device_categorizer, circuit_categorizer, device_filter, &circuit_and_pin_mapping, &circuit_pin_equivalence, (size_t *) 0);
|
|
|
|
// sort the nodes so we can easily identify the identical ones (in terms of topology)
|
|
// nodes are identical if the attached devices and circuits are of the same kind and with the same parameters
|
|
// and connect to other nodes in identical configurations.
|
|
|
|
std::vector<NodeEdgePair> nodes;
|
|
|
|
std::vector<NetGraphNode::edge_type> no_edges;
|
|
no_edges.push_back (NetGraphNode::edge_type ());
|
|
|
|
nodes.reserve (graph.end () - graph.begin ());
|
|
for (db::NetGraph::node_iterator i = graph.begin (); i != graph.end (); ++i) {
|
|
if (! i->has_other () && i->net ()) {
|
|
nodes.push_back (NodeEdgePair (i.operator-> (), no_edges.begin ()));
|
|
}
|
|
}
|
|
|
|
std::sort (nodes.begin (), nodes.end (), CompareNodeEdgePair ());
|
|
|
|
// Identical nodes leading to the same nodes on the other side are candidates for symmetry.
|
|
|
|
tl::equivalence_clusters<const NetGraphNode *> identical_nodes;
|
|
|
|
for (std::vector<NodeEdgePair>::const_iterator np = nodes.begin (); np + 1 != nodes.end (); ++np) {
|
|
if (*np[0].node == *np[1].node) {
|
|
identical_nodes.same (np[0].node, np[1].node);
|
|
}
|
|
}
|
|
|
|
std::vector<std::set<size_t> > symmetry_groups;
|
|
std::set<size_t> visited;
|
|
|
|
for (std::vector<NodeEdgePair>::const_iterator np = nodes.begin (); np != nodes.end (); ++np) {
|
|
|
|
size_t node_id = graph.node_index_for_net (np[0].node->net ());
|
|
if (visited.find (node_id) != visited.end ()) {
|
|
continue;
|
|
}
|
|
|
|
std::set<size_t> considered_nodes;
|
|
considered_nodes.insert (node_id);
|
|
|
|
std::set<size_t> symmetry_group;
|
|
symmetry_group.insert (node_id);
|
|
|
|
derive_symmetry_groups (graph, identical_nodes, considered_nodes, symmetry_group, symmetry_groups);
|
|
|
|
visited.insert (considered_nodes.begin (), considered_nodes.end ());
|
|
|
|
}
|
|
|
|
std::sort (symmetry_groups.begin (), symmetry_groups.end ());
|
|
symmetry_groups.erase (std::unique (symmetry_groups.begin (), symmetry_groups.end ()), symmetry_groups.end ());
|
|
|
|
if (! symmetry_groups.empty () && tl::verbosity () >= 30) {
|
|
tl::info << tl::to_string (tr ("Symmetry groups:"));
|
|
int index = 0;
|
|
for (std::vector<std::set<size_t> >::const_iterator g = symmetry_groups.begin (); g != symmetry_groups.end (); ++g) {
|
|
tl::info << " [" << index << "] " << tl::noendl;
|
|
for (std::set<size_t>::const_iterator i = g->begin (); i != g->end (); ++i) {
|
|
tl::info << (i == g->begin () ? "" : ",") << (graph.node (*i).net () ? graph.node (*i).net ()->expanded_name ().c_str () : "(null)") << tl::noendl;
|
|
}
|
|
++index;
|
|
tl::info << "";
|
|
}
|
|
}
|
|
|
|
// join the nets
|
|
|
|
for (std::vector<std::set<size_t> >::const_iterator g = symmetry_groups.begin (); g != symmetry_groups.end (); ++g) {
|
|
for (std::set<size_t>::const_iterator i = g->begin (); i != g->end (); ++i) {
|
|
if (i != g->begin ()) {
|
|
circuit->join_nets (const_cast<db::Net *> (graph.net_by_node_index (*g->begin ())), const_cast<db::Net *> (graph.net_by_node_index (*i)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|