Using primary(layout) netlist as reference for primary parameters and compare delegate - this also removes some potential glitches

This commit is contained in:
Matthias Koefferlein 2021-07-29 01:15:35 +02:00
parent a90e14b692
commit 8b970039c0
13 changed files with 168 additions and 53 deletions

View File

@ -90,6 +90,16 @@ void Device::set_circuit (Circuit *circuit)
mp_circuit = circuit;
}
const Netlist *Device::netlist () const
{
return mp_circuit ? mp_circuit->netlist () : 0;
}
Netlist *Device::netlist ()
{
return mp_circuit ? mp_circuit->netlist () : 0;
}
void Device::set_name (const std::string &n)
{
m_name = n;

View File

@ -193,6 +193,16 @@ public:
return mp_circuit;
}
/**
* @brief Gets the netlist, the device lives in
*/
const Netlist *netlist () const;
/**
* @brief Gets the netlist, the device lives in
*/
Netlist *netlist ();
/**
* @brief Sets the name
*/

View File

@ -22,11 +22,34 @@
#include "dbDeviceClass.h"
#include "dbDevice.h"
#include "dbNetlist.h"
#include "tlClassRegistry.h"
namespace db
{
// --------------------------------------------------------------------------------
/**
* @brief Returns the primary device class for both given devices
* One of the devices lives in a primary netlist. This one is taken for the device class.
*/
static const db::DeviceClass *primary_device_class (const db::Device &a, const db::Device &b)
{
tl_assert (a.device_class () != 0);
tl_assert (b.device_class () != 0);
const db::DeviceClass *dca = a.device_class ()->primary_class () ? a.device_class ()->primary_class () : a.device_class ();
const db::DeviceClass *dcb = b.device_class ()->primary_class () ? b.device_class ()->primary_class () : b.device_class ();
if (dca != dcb) {
// different devices, same category while sorting devices - take the one with the "lower" name
return dca->name () < dcb->name () ? dca : dcb;
} else {
return dca;
}
}
// --------------------------------------------------------------------------------
// EqualDeviceParameters implementation
@ -36,7 +59,6 @@ const double default_relative_tolerance = 1e-6;
const double default_absolute_tolerance = 0.0;
static int compare_parameters (double pa, double pb, double absolute = default_absolute_tolerance, double relative = default_relative_tolerance)
{
// absolute value < 0 means: ignore this parameter (= always match)
@ -112,21 +134,14 @@ bool EqualDeviceParameters::less (const db::Device &a, const db::Device &b) cons
seen.insert (c->first);
}
const std::vector<db::DeviceParameterDefinition> &pd = a.device_class ()->parameter_definitions ();
const std::vector<db::DeviceParameterDefinition> &pd = primary_device_class (a, b)->parameter_definitions ();
for (std::vector<db::DeviceParameterDefinition>::const_iterator p = pd.begin (); p != pd.end (); ++p) {
if (seen.find (p->id ()) != seen.end ()) {
continue;
}
const db::DeviceParameterDefinition *pdb = b.device_class ()->parameter_definition (p->id ());
if (pdb && pdb->is_primary () && p->is_primary ()) {
if (p->is_primary () && seen.find (p->id ()) == seen.end ()) {
int cmp = compare_parameters (a.parameter_value (p->id ()), b.parameter_value (p->id ()));
if (cmp != 0) {
return cmp < 0;
}
}
}
return false;
@ -166,13 +181,13 @@ bool AllDeviceParametersAreEqual::less (const db::Device &a, const db::Device &b
// DeviceClass class implementation
DeviceClass::DeviceClass ()
: m_strict (false), mp_netlist (0), m_supports_parallel_combination (false), m_supports_serial_combination (false)
: m_strict (false), mp_netlist (0), m_supports_parallel_combination (false), m_supports_serial_combination (false), mp_primary_class (0)
{
// .. nothing yet ..
}
DeviceClass::DeviceClass (const DeviceClass &other)
: gsi::ObjectBase (other), tl::Object (other), tl::UniqueId (other), m_strict (false), mp_netlist (0), m_supports_parallel_combination (false), m_supports_serial_combination (false)
: gsi::ObjectBase (other), tl::Object (other), tl::UniqueId (other), m_strict (false), mp_netlist (0), m_supports_parallel_combination (false), m_supports_serial_combination (false), mp_primary_class (0)
{
operator= (other);
}
@ -299,10 +314,7 @@ bool DeviceClass::less (const db::Device &a, const db::Device &b)
tl_assert (a.device_class () != 0);
tl_assert (b.device_class () != 0);
const db::DeviceParameterCompareDelegate *pcd = a.device_class ()->mp_pc_delegate.get ();
if (! pcd) {
pcd = b.device_class ()->mp_pc_delegate.get ();
}
const db::DeviceParameterCompareDelegate *pcd = primary_device_class (a, b)->parameter_compare_delegate ();
if (! pcd) {
pcd = &default_compare;
}
@ -315,10 +327,7 @@ bool DeviceClass::equal (const db::Device &a, const db::Device &b)
tl_assert (a.device_class () != 0);
tl_assert (b.device_class () != 0);
const db::DeviceParameterCompareDelegate *pcd = a.device_class ()->mp_pc_delegate.get ();
if (! pcd) {
pcd = b.device_class ()->mp_pc_delegate.get ();
}
const db::DeviceParameterCompareDelegate *pcd = primary_device_class (a, b)->parameter_compare_delegate ();
if (! pcd) {
pcd = &default_compare;
}

View File

@ -734,6 +734,22 @@ public:
return mp_device_combiner.get ();
}
/**
* @brief Internally used by the netlist comparer to temporarily attach a device class pointing to the primary one
*/
void set_primary_class (const db::DeviceClass *primary) const
{
mp_primary_class = primary;
}
/**
* @brief Internally used by the netlist comparer to temporarily attach a device class pointing to the primary one
*/
const db::DeviceClass *primary_class () const
{
return mp_primary_class;
}
/**
* @brief Generate memory statistics
*/
@ -762,6 +778,7 @@ private:
bool m_supports_parallel_combination;
bool m_supports_serial_combination;
std::map<size_t, size_t> m_equivalent_terminal_ids;
mutable const db::DeviceClass *mp_primary_class;
void set_netlist (db::Netlist *nl)
{

View File

@ -44,7 +44,7 @@ Netlist::Netlist (NetlistManipulationCallbacks *callbacks)
}
Netlist::Netlist (const Netlist &other)
: gsi::ObjectBase (other), tl::Object (other), m_case_sensitive (true),
: gsi::ObjectBase (other), tl::Object (other),
m_valid_topology (false), m_lock_count (0),
m_circuit_by_name (this, &Netlist::begin_circuits, &Netlist::end_circuits),
m_circuit_by_cell_index (this, &Netlist::begin_circuits, &Netlist::end_circuits),

View File

@ -3059,9 +3059,37 @@ NetlistComparer::unmatched_circuits (db::Netlist *a, db::Netlist *b, std::vector
}
}
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;
}
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.
@ -3130,21 +3158,12 @@ NetlistComparer::compare (const db::Netlist *a, const db::Netlist *b) const
}
}
// impose the compare tolerances of the layout (first netlist) on the schematic (second netlist)
// TODO: this is kind of clumsy. But it's very important to use the same device sorting for both netlists, so we play this trick.
// A better solution was to have a common compare framework for both netlists.
// 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) {
const db::DeviceClass *da = i->second.first;
db::DeviceClass *db = const_cast<db::DeviceClass *> (i->second.second);
const db::DeviceParameterCompareDelegate *cmp = da->parameter_compare_delegate ();
db->set_parameter_compare_delegate (const_cast<db::DeviceParameterCompareDelegate *> (cmp));
i->second.second->set_primary_class (i->second.first);
}
}
// decide whether to use a device category in strict mode

View File

@ -354,6 +354,7 @@ private:
NetlistComparer &operator= (const NetlistComparer &);
protected:
bool compare_impl (const db::Netlist *a, const db::Netlist *b) const;
bool compare_circuits (const db::Circuit *c1, const db::Circuit *c2, db::DeviceCategorizer &device_categorizer, db::CircuitCategorizer &circuit_categorizer, db::CircuitPinMapper &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;
bool all_subcircuits_verified (const db::Circuit *c, const std::set<const db::Circuit *> &verified_circuits) const;
std::string 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;

View File

@ -625,9 +625,6 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
std::map<std::string, double>::const_iterator v = params.find (i->name ());
if (v != params.end ()) {
device->set_parameter_value (i->id (), v->second / i->si_scaling ());
// Make given parameters primary. This way they are netlisted again and participate in netlist compare when
// they are made primary in the extracted netlist too.
i->set_is_primary (true);
} else if (i->id () == defp) {
device->set_parameter_value (i->id (), value / i->si_scaling ());
}

View File

@ -404,6 +404,13 @@ TEST(0_EqualDeviceParameters)
d1.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.5);
d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 0.2);
EXPECT_EQ (dc.equal (d1, d2), false);
EXPECT_EQ (dc.equal (d2, d1), false);
EXPECT_EQ (dc.less (d1, d2), false);
EXPECT_EQ (dc.less (d2, d1), true);
*eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_W, true); // ignore W
EXPECT_EQ (dc.equal (d1, d2), true);
EXPECT_EQ (dc.equal (d2, d1), true);
EXPECT_EQ (dc.less (d1, d2), false);
@ -959,7 +966,9 @@ TEST(5_BufferTwoPathsDifferentParameters)
EXPECT_EQ (good, false);
logger.clear ();
nl1.device_class_by_name ("NMOS")->set_parameter_compare_delegate (new db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 1.5, 0.0));
db::EqualDeviceParameters *eql = new db::EqualDeviceParameters ();
*eql += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 1.5, 0.0);
nl1.device_class_by_name ("NMOS")->set_parameter_compare_delegate (eql);
good = comp.compare (&nl1, &nl2);
EXPECT_EQ (logger.text (),

View File

@ -205,6 +205,57 @@ same_device_classes("POLYRES", nil)</pre>
<pre>tolerance("NMOS", "L", 0.05, 0.01)
tolerance("NMOS", "L", :absolute => 0.05, :relative => 0.01)</pre>
<h2>Ignoring parameters</h2>
<p>
Some device parameters can be ignore in the compare.
For example, if you don't want to compare the "L" parameter of the "NMOS" devices, use this statement:
</p>
<pre>ignore_parameter("NMOS", "L")</pre>
<p>
This statement can be put into the script anywhere before the "compare" statement.
</p>
<p>
By default, only "primary" parameters are compared. For a resistor for example, "R" is a primary parameter, the other ones
like "L", "W", "A" and "P" are not. Using "tolerance" will implicitly enable a parameter while "ignore_parameter" will disable
it for compare.
</p>
<h2>Enabling and disabling parameters</h2>
<p>
As mentioned before, some device parameters are primary while other are not. For example, for the resistor device,
"R" (the resistance value) is a primary parameter while the device length ("L") is not. You can make the "L" parameter
primary for a device class called "RES" by using:
</p>
<pre>enable_parameter("RES", "L")</pre>
<p>
This has two effects: first, the "L" parameter is written into the Spice output netlist and second, it is compare against
the schematic "L" parameter, provided that one is given in the netlist. No compare happens if no "L" parameter is present in the
netlist.
</p>
<p>
This behavior is overridden by a "tolerance" or "ignore_parameter" specification for that parameter or if a customer
device comparer is installed.
</p>
<p>
Correspondingly, a primary parameter can be disabled using:
</p>
<pre>disable_parameter("RES", "R")</pre>
<p>
As this will make the parameter to disappear from the netlist, it's often more useful to use "ignore_parameter" instead
which has the same effect on the compare step, but will keep it in the netlist.
</p>
<h2>Pin swapping</h2>
<p>

View File

@ -362,18 +362,11 @@ extract_devices(bjt3(model_name), { "C" => collector, "B" => base, "E" => emitte
</p>
<p>
Parameters can be fully enabled by using "enable_parameter" on the device class.
Hence it is possible to enable "W" and "L" on a resistor type using the following code:
</p>
<pre>dc = extract_devices(resistor("RES", 1), ...)
dc.enable_parameter("W", true)
dc.enable_parameter("L", true)
</pre>
<p>
This will modify the parameters of the generated device class such that "W" and "L" are
fully enabled parameters.
Parameters can be fully enabled by using <a href="/about/lvs_ref_global.xml#enable_parameter">enable_parameter</a>
or disabled using <a href="/about/lvs_ref_global.xml#disable_parameter">disable_parameter</a>.
<a href="/about/lvs_ref_global.xml#tolerance">tolerance</a> can be used to enable a parameter for compare
and to specify a compare tolerance.
<a href="/about/lvs_ref_global.xml#ignore_parameter">ignore_parameter</a> can be used to ignore a parameter in the compare step.
</p>
<p>
@ -397,8 +390,7 @@ extract_devices(resistor("RES", 1, MyResistor), ...)
</pre>
<p>
The effect of this code is the same than the first one, but using a custom
device class opens the option to supply additional parameters for example
Using a custom device class opens the option to supply additional parameters for example
or to implement some entirely new device while using the extraction
mechanics of the resistor extractor. The only requirement is compatibility of
the parameter and terminal definitions.

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE language SYSTEM "klayout_doc.dtd">
<doc>

View File

@ -145,7 +145,7 @@ TEST(15_private)
TEST(16_private)
{
// test_is_long_runner ();
// test_is_long_runner ();lvs-blackbox
run_test (_this, "test_16.lvs", "test_16.cir.gz", "test_16.gds.gz", true);
}