mirror of https://github.com/KLayout/klayout.git
Using primary(layout) netlist as reference for primary parameters and compare delegate - this also removes some potential glitches
This commit is contained in:
parent
a90e14b692
commit
8b970039c0
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 ());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (),
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue