mirror of https://github.com/KLayout/klayout.git
WIP: debugging, development
- LVS DSL debugging, enhancements - Allow polygons with holes in L2N - Spice Reader: was creating too many class objects - Device class categorizer: allow associating A->C,B-C - ...
This commit is contained in:
parent
37012efba0
commit
0cbfa698f0
|
|
@ -23,6 +23,7 @@
|
||||||
#include "dbLayoutToNetlistWriter.h"
|
#include "dbLayoutToNetlistWriter.h"
|
||||||
#include "dbLayoutToNetlist.h"
|
#include "dbLayoutToNetlist.h"
|
||||||
#include "dbLayoutToNetlistFormatDefs.h"
|
#include "dbLayoutToNetlistFormatDefs.h"
|
||||||
|
#include "dbPolygonTools.h"
|
||||||
|
|
||||||
namespace db
|
namespace db
|
||||||
{
|
{
|
||||||
|
|
@ -341,7 +342,7 @@ void std_writer_impl<Keys>::write (const db::PolygonRef *s, const db::ICplxTrans
|
||||||
|
|
||||||
*mp_stream << Keys::polygon_key << "(" << lname;
|
*mp_stream << Keys::polygon_key << "(" << lname;
|
||||||
if (poly.holes () > 0) {
|
if (poly.holes () > 0) {
|
||||||
db::SimplePolygon sp (poly);
|
db::SimplePolygon sp = db::polygon_to_simple_polygon (poly);
|
||||||
write_points (*mp_stream, sp, t, m_ref, relative);
|
write_points (*mp_stream, sp, t, m_ref, relative);
|
||||||
} else {
|
} else {
|
||||||
write_points (*mp_stream, poly, t, m_ref, relative);
|
write_points (*mp_stream, poly, t, m_ref, relative);
|
||||||
|
|
|
||||||
|
|
@ -275,9 +275,40 @@ public:
|
||||||
|
|
||||||
void same_class (const db::DeviceClass *ca, const db::DeviceClass *cb)
|
void same_class (const db::DeviceClass *ca, const db::DeviceClass *cb)
|
||||||
{
|
{
|
||||||
++m_next_cat;
|
// reuse existing category if one is assigned already -> this allows associating
|
||||||
m_cat_by_ptr.insert (std::make_pair (ca, m_next_cat));
|
// multiple categories to other ones (A->C, B->C)
|
||||||
m_cat_by_ptr.insert (std::make_pair (cb, m_next_cat));
|
std::map<const db::DeviceClass *, size_t>::const_iterator cpa = m_cat_by_ptr.find (ca);
|
||||||
|
std::map<const db::DeviceClass *, size_t>::const_iterator cpb = m_cat_by_ptr.find (cb);
|
||||||
|
|
||||||
|
if (cpa != m_cat_by_ptr.end () && cpb != m_cat_by_ptr.end ()) {
|
||||||
|
|
||||||
|
if (cpa->second != cpb->second) {
|
||||||
|
// join categories (cat(B)->cat(A))
|
||||||
|
for (std::map<const db::DeviceClass *, size_t>::iterator cp = m_cat_by_ptr.begin (); cp != m_cat_by_ptr.end (); ++cp) {
|
||||||
|
if (cp->second == cpb->second) {
|
||||||
|
cp->second = cpa->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (cpb != m_cat_by_ptr.end ()) {
|
||||||
|
|
||||||
|
// reuse cat(B) category
|
||||||
|
m_cat_by_ptr.insert (std::make_pair (ca, cpb->second));
|
||||||
|
|
||||||
|
} else if (cpa != m_cat_by_ptr.end ()) {
|
||||||
|
|
||||||
|
// reuse cat(A) category
|
||||||
|
m_cat_by_ptr.insert (std::make_pair (cb, cpa->second));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// new category
|
||||||
|
++m_next_cat;
|
||||||
|
m_cat_by_ptr.insert (std::make_pair (ca, m_next_cat));
|
||||||
|
m_cat_by_ptr.insert (std::make_pair (cb, m_next_cat));
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t cat_for_device (const db::Device *device)
|
size_t cat_for_device (const db::Device *device)
|
||||||
|
|
|
||||||
|
|
@ -65,11 +65,27 @@ void NetlistSpiceReaderDelegate::error (const std::string &msg)
|
||||||
throw tl::Exception (msg);
|
throw tl::Exception (msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class Cls>
|
||||||
|
static db::DeviceClass *make_device_class (db::Circuit *circuit, const std::string &name)
|
||||||
|
{
|
||||||
|
if (! circuit || ! circuit->netlist ()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (name);
|
||||||
|
if (! cls) {
|
||||||
|
cls = new Cls ();
|
||||||
|
cls->set_name (name);
|
||||||
|
circuit->netlist ()->add_device_class (cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, double> ¶ms)
|
bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::string &element, const std::string &name, const std::string &model, double value, const std::vector<db::Net *> &nets, const std::map<std::string, double> ¶ms)
|
||||||
{
|
{
|
||||||
std::string cn = model;
|
std::string cn = model;
|
||||||
db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (cn);
|
db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (cn);
|
||||||
db::DeviceClass *new_cls = 0;
|
|
||||||
|
|
||||||
if (cls) {
|
if (cls) {
|
||||||
// use given class
|
// use given class
|
||||||
|
|
@ -77,53 +93,46 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
|
||||||
if (cn.empty ()) {
|
if (cn.empty ()) {
|
||||||
cn = "RES";
|
cn = "RES";
|
||||||
}
|
}
|
||||||
new_cls = new db::DeviceClassResistor ();
|
cls = make_device_class<db::DeviceClassResistor> (circuit, cn);
|
||||||
} else if (element == "L") {
|
} else if (element == "L") {
|
||||||
if (cn.empty ()) {
|
if (cn.empty ()) {
|
||||||
cn = "IND";
|
cn = "IND";
|
||||||
}
|
}
|
||||||
new_cls = new db::DeviceClassInductor ();
|
cls = make_device_class<db::DeviceClassInductor> (circuit, cn);
|
||||||
} else if (element == "C") {
|
} else if (element == "C") {
|
||||||
if (cn.empty ()) {
|
if (cn.empty ()) {
|
||||||
cn = "CAP";
|
cn = "CAP";
|
||||||
}
|
}
|
||||||
new_cls = new db::DeviceClassCapacitor ();
|
cls = make_device_class<db::DeviceClassCapacitor> (circuit, cn);
|
||||||
} else if (element == "D") {
|
} else if (element == "D") {
|
||||||
if (cn.empty ()) {
|
if (cn.empty ()) {
|
||||||
cn = "DIODE";
|
cn = "DIODE";
|
||||||
}
|
}
|
||||||
new_cls = new db::DeviceClassDiode ();
|
cls = make_device_class<db::DeviceClassDiode> (circuit, cn);
|
||||||
} else if (element == "Q") {
|
} else if (element == "Q") {
|
||||||
if (nets.size () == 3) {
|
if (nets.size () == 3) {
|
||||||
if (cn.empty ()) {
|
if (cn.empty ()) {
|
||||||
cn = "BJT3";
|
cn = "BJT3";
|
||||||
}
|
}
|
||||||
new_cls = new db::DeviceClassBJT3Transistor ();
|
cls = make_device_class<db::DeviceClassBJT3Transistor> (circuit, cn);
|
||||||
} else if (nets.size () == 4) {
|
} else if (nets.size () == 4) {
|
||||||
if (cn.empty ()) {
|
if (cn.empty ()) {
|
||||||
cn = "BJT4";
|
cn = "BJT4";
|
||||||
}
|
}
|
||||||
new_cls = new db::DeviceClassBJT4Transistor ();
|
cls = make_device_class<db::DeviceClassBJT4Transistor> (circuit, cn);
|
||||||
|
} else {
|
||||||
|
error (tl::to_string (tr ("'Q' element needs to have 3 or 4 terminals")));
|
||||||
}
|
}
|
||||||
} else if (element == "M") {
|
} else if (element == "M") {
|
||||||
if (nets.size () == 3) {
|
if (nets.size () == 4) {
|
||||||
if (cn.empty ()) {
|
|
||||||
cn = "MOS3";
|
|
||||||
}
|
|
||||||
new_cls = new db::DeviceClassMOS3Transistor ();
|
|
||||||
} else if (nets.size () == 4) {
|
|
||||||
if (cn.empty ()) {
|
if (cn.empty ()) {
|
||||||
cn = "MOS4";
|
cn = "MOS4";
|
||||||
}
|
}
|
||||||
new_cls = new db::DeviceClassMOS4Transistor ();
|
cls = make_device_class<db::DeviceClassMOS4Transistor> (circuit, cn);
|
||||||
|
} else {
|
||||||
|
error (tl::to_string (tr ("'M' element needs to have 4 terminals")));
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if (new_cls) {
|
|
||||||
cls = new_cls;
|
|
||||||
cls->set_name (cn);
|
|
||||||
circuit->netlist ()->add_device_class (cls);
|
|
||||||
} else if (! cls) {
|
|
||||||
error (tl::sprintf (tl::to_string (tr ("Not a known element type: '%s'")), element));
|
error (tl::sprintf (tl::to_string (tr ("Not a known element type: '%s'")), element));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -480,11 +480,11 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
|
||||||
"This variant accepts a database-unit location. The location is given in the\n"
|
"This variant accepts a database-unit location. The location is given in the\n"
|
||||||
"coordinate space of the initial cell.\n"
|
"coordinate space of the initial cell.\n"
|
||||||
) +
|
) +
|
||||||
gsi::method ("write", &db::LayoutToNetlist::save, gsi::arg ("path"), gsi::arg ("short_format", false),
|
gsi::method ("write|write_l2n", &db::LayoutToNetlist::save, gsi::arg ("path"), gsi::arg ("short_format", false),
|
||||||
"@brief Writes the extracted netlist to a file.\n"
|
"@brief Writes the extracted netlist to a file.\n"
|
||||||
"This method employs the native format of KLayout.\n"
|
"This method employs the native format of KLayout.\n"
|
||||||
) +
|
) +
|
||||||
gsi::method ("read", &db::LayoutToNetlist::load, gsi::arg ("path"),
|
gsi::method ("read|read_l2n", &db::LayoutToNetlist::load, gsi::arg ("path"),
|
||||||
"@brief Reads the extracted netlist from the file.\n"
|
"@brief Reads the extracted netlist from the file.\n"
|
||||||
"This method employs the native format of KLayout.\n"
|
"This method employs the native format of KLayout.\n"
|
||||||
) +
|
) +
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ module DRC
|
||||||
@output_rdb = nil
|
@output_rdb = nil
|
||||||
@output_rdb_file = nil
|
@output_rdb_file = nil
|
||||||
@output_rdb_cell = nil
|
@output_rdb_cell = nil
|
||||||
@output_l2ndb = nil
|
@show_l2ndb = nil
|
||||||
@output_l2ndb_file = nil
|
@output_l2ndb_file = nil
|
||||||
@target_netlist_file = nil
|
@target_netlist_file = nil
|
||||||
@target_netlist_format = nil
|
@target_netlist_format = nil
|
||||||
|
|
@ -42,6 +42,7 @@ module DRC
|
||||||
@dss = nil
|
@dss = nil
|
||||||
@deep = false
|
@deep = false
|
||||||
@netter = nil
|
@netter = nil
|
||||||
|
@netter_data = nil
|
||||||
|
|
||||||
@verbose = false
|
@verbose = false
|
||||||
|
|
||||||
|
|
@ -770,7 +771,7 @@ module DRC
|
||||||
# will also be written to the given file.
|
# will also be written to the given file.
|
||||||
|
|
||||||
def report_netlist(filename = nil)
|
def report_netlist(filename = nil)
|
||||||
@output_l2ndb = true
|
@show_l2ndb = true
|
||||||
if filename
|
if filename
|
||||||
filename.is_a?(String) || raise("Argument must be string in report_netlist")
|
filename.is_a?(String) || raise("Argument must be string in report_netlist")
|
||||||
end
|
end
|
||||||
|
|
@ -1337,7 +1338,7 @@ CODE
|
||||||
|
|
||||||
# save the report database if requested
|
# save the report database if requested
|
||||||
if @output_rdb_file
|
if @output_rdb_file
|
||||||
rdb_file = make_path(@output_rdb_file)
|
rdb_file = _make_path(@output_rdb_file)
|
||||||
info("Writing report database: #{rdb_file} ..")
|
info("Writing report database: #{rdb_file} ..")
|
||||||
@output_rdb.save(rdb_file)
|
@output_rdb.save(rdb_file)
|
||||||
end
|
end
|
||||||
|
|
@ -1403,35 +1404,34 @@ CODE
|
||||||
|
|
||||||
writer = @target_netlist_format || RBA::NetlistSpiceWriter::new
|
writer = @target_netlist_format || RBA::NetlistSpiceWriter::new
|
||||||
|
|
||||||
netlist_file = make_path(@target_netlist_file)
|
netlist_file = _make_path(@target_netlist_file)
|
||||||
info("Writing netlist: #{netlist_file} ..")
|
info("Writing netlist: #{netlist_file} ..")
|
||||||
self.netlist.write(netlist_file, writer, @target_netlist_comment || "")
|
self.netlist.write(netlist_file, writer, @target_netlist_comment || "")
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# save the netlist database if requested
|
# save the netlist database if requested
|
||||||
if @output_l2ndb && @netter && @netter.l2n_data
|
if @output_l2ndb_file && @netter && @netter.l2n_data
|
||||||
|
|
||||||
if @output_l2ndb_file
|
l2ndb_file = _make_path(@output_l2ndb_file)
|
||||||
l2ndb_file = make_path(@output_l2ndb_file)
|
info("Writing netlist database: #{l2ndb_file} ..")
|
||||||
info("Writing netlist database: #{l2ndb_file} ..")
|
@netter.l2n_data.write_l2n(l2ndb_file)
|
||||||
@netter.l2n_data.save(l2ndb_file)
|
|
||||||
end
|
|
||||||
if @output_l2ndb && final && view
|
|
||||||
# NOTE: to prevent the netter destroying the database, we need to take it
|
|
||||||
l2ndb = @netter._take_l2n_data
|
|
||||||
# we also need to make the extractor take over ownership over the DSS
|
|
||||||
# because otherwise we can't free the resources.
|
|
||||||
if l2ndb.dss == @dss
|
|
||||||
l2ndb.keep_dss
|
|
||||||
@dss = nil
|
|
||||||
end
|
|
||||||
l2ndb_index = view.add_l2ndb(l2ndb)
|
|
||||||
view.show_l2ndb(l2ndb_index, view.active_cellview_index)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# give derived classes to perform actions
|
||||||
|
_before_cleanup
|
||||||
|
|
||||||
|
# show the data in the browser
|
||||||
|
if @show_l2ndb && @netter && @netter.l2n_data
|
||||||
|
|
||||||
|
# NOTE: to prevent the netter destroying the database, we need to take it
|
||||||
|
l2ndb = _take_data
|
||||||
|
l2ndb_index = view.add_l2ndb(l2ndb)
|
||||||
|
view.show_l2ndb(l2ndb_index, view.active_cellview_index)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
@output_layout = nil
|
@output_layout = nil
|
||||||
@output_layout_file = nil
|
@output_layout_file = nil
|
||||||
@output_cell = nil
|
@output_cell = nil
|
||||||
|
|
@ -1439,12 +1439,15 @@ CODE
|
||||||
@output_rdb_cell = nil
|
@output_rdb_cell = nil
|
||||||
@output_rdb = nil
|
@output_rdb = nil
|
||||||
@output_rdb_index = nil
|
@output_rdb_index = nil
|
||||||
@output_l2ndb = nil
|
@show_l2ndb = nil
|
||||||
@output_l2ndb_file = nil
|
@output_l2ndb_file = nil
|
||||||
|
|
||||||
# clean up temp data
|
# clean up temp data
|
||||||
@dss && @dss._destroy
|
@dss && @dss._destroy
|
||||||
|
@dss = nil
|
||||||
@netter && @netter._finish
|
@netter && @netter._finish
|
||||||
|
@netter = nil
|
||||||
|
@netter_data = nil
|
||||||
|
|
||||||
if final && @log_file
|
if final && @log_file
|
||||||
@log_file.close
|
@log_file.close
|
||||||
|
|
@ -1453,6 +1456,33 @@ CODE
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def _take_data
|
||||||
|
|
||||||
|
if ! @netter
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if ! @netter_data
|
||||||
|
|
||||||
|
@netter_data = @netter._take_data
|
||||||
|
|
||||||
|
# we also need to make the extractor take over ownership over the DSS
|
||||||
|
# because otherwise we can't free the resources.
|
||||||
|
if @netter_data.dss == @dss
|
||||||
|
@netter_data.keep_dss
|
||||||
|
@dss = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
@netter_data
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def _before_cleanup
|
||||||
|
# nothing yet
|
||||||
|
end
|
||||||
|
|
||||||
def _dss
|
def _dss
|
||||||
@dss
|
@dss
|
||||||
end
|
end
|
||||||
|
|
@ -1461,9 +1491,7 @@ CODE
|
||||||
@netter ||= DRC::DRCNetter::new(self)
|
@netter ||= DRC::DRCNetter::new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def _make_path(file)
|
||||||
|
|
||||||
def make_path(file)
|
|
||||||
# resolves the file path relative to the source's path
|
# resolves the file path relative to the source's path
|
||||||
sp = self.source.path
|
sp = self.source.path
|
||||||
if sp
|
if sp
|
||||||
|
|
@ -1473,6 +1501,8 @@ CODE
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
def _make_string(v)
|
def _make_string(v)
|
||||||
if v.class.respond_to?(:from_s)
|
if v.class.respond_to?(:from_s)
|
||||||
v.class.to_s + "::from_s(" + v.to_s.inspect + ")"
|
v.class.to_s + "::from_s(" + v.to_s.inspect + ")"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
module DRC
|
module DRC
|
||||||
|
|
||||||
# The netter object
|
# The DRC netter object
|
||||||
|
|
||||||
# %DRC%
|
# %DRC%
|
||||||
# @scope
|
# @scope
|
||||||
|
|
@ -13,6 +13,17 @@ module DRC
|
||||||
# as global functions too where they act on a default incarnation
|
# as global functions too where they act on a default incarnation
|
||||||
# of the netter. Usually it's not required to instantiate a Netter
|
# of the netter. Usually it's not required to instantiate a Netter
|
||||||
# object, but it serves as a container for this functionality.
|
# object, but it serves as a container for this functionality.
|
||||||
|
|
||||||
|
# %LVS%
|
||||||
|
# @scope
|
||||||
|
# @name Netter
|
||||||
|
# @brief LVS Reference: Netter object
|
||||||
|
# The Netter object provides services related to network extraction
|
||||||
|
# from a layout plus comparison against a reference netlist.
|
||||||
|
# Similar to the DRC netter (which lacks the compare ability), the
|
||||||
|
# relevant method of this object are available as global functions too
|
||||||
|
# where they act on a default incarnation. Usually it's not required
|
||||||
|
# to instantiate a Netter object explicitly.
|
||||||
#
|
#
|
||||||
# An individual netter object can be created, if the netter results
|
# An individual netter object can be created, if the netter results
|
||||||
# need to be kept for multiple extractions. If you really need
|
# need to be kept for multiple extractions. If you really need
|
||||||
|
|
@ -162,7 +173,7 @@ module DRC
|
||||||
|
|
||||||
def extract_devices(devex, layer_selection)
|
def extract_devices(devex, layer_selection)
|
||||||
|
|
||||||
ensure_l2n
|
ensure_data
|
||||||
|
|
||||||
devex.is_a?(RBA::DeviceExtractorBase) || raise("First argument of Netter#extract_devices must be a device extractor instance in the two-arguments form")
|
devex.is_a?(RBA::DeviceExtractorBase) || raise("First argument of Netter#extract_devices must be a device extractor instance in the two-arguments form")
|
||||||
|
|
||||||
|
|
@ -188,8 +199,7 @@ module DRC
|
||||||
def clear_connections
|
def clear_connections
|
||||||
@netlisted = false
|
@netlisted = false
|
||||||
@connect_implicit = ""
|
@connect_implicit = ""
|
||||||
@l2n && @l2n._destroy
|
_clear_data
|
||||||
@l2n = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# %DRC%
|
# %DRC%
|
||||||
|
|
@ -336,7 +346,7 @@ module DRC
|
||||||
|
|
||||||
def l2n_data
|
def l2n_data
|
||||||
|
|
||||||
ensure_l2n
|
ensure_data
|
||||||
|
|
||||||
# run extraction in a timed environment
|
# run extraction in a timed environment
|
||||||
if ! @netlisted
|
if ! @netlisted
|
||||||
|
|
@ -364,7 +374,12 @@ module DRC
|
||||||
clear_connections
|
clear_connections
|
||||||
end
|
end
|
||||||
|
|
||||||
def _take_l2n_data
|
def _clear_data
|
||||||
|
@l2n && @l2n._destroy
|
||||||
|
@l2n = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def _take_data
|
||||||
l2ndb = self.l2n_data
|
l2ndb = self.l2n_data
|
||||||
@l2n = nil
|
@l2n = nil
|
||||||
l2ndb
|
l2ndb
|
||||||
|
|
@ -376,17 +391,16 @@ module DRC
|
||||||
@netlisted && clear_connections
|
@netlisted && clear_connections
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_l2n
|
def ensure_data
|
||||||
@l2n || make_l2n
|
if !@l2n
|
||||||
|
@layers = {}
|
||||||
|
make_data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_l2n
|
def make_data
|
||||||
|
|
||||||
@layers = {}
|
|
||||||
|
|
||||||
if @engine._dss
|
if @engine._dss
|
||||||
# TODO: check whether all layers are deep and come from the dss and layout index,
|
|
||||||
# then use this layout index. This will remove the need for this check:
|
|
||||||
@engine._dss.is_singular? || raise("The DRC script features more than one or no layout source - network extraction cannot be performed in such configurations")
|
@engine._dss.is_singular? || raise("The DRC script features more than one or no layout source - network extraction cannot be performed in such configurations")
|
||||||
@l2n = RBA::LayoutToNetlist::new(@engine._dss)
|
@l2n = RBA::LayoutToNetlist::new(@engine._dss)
|
||||||
else
|
else
|
||||||
|
|
@ -405,7 +419,7 @@ module DRC
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
ensure_l2n
|
ensure_data
|
||||||
|
|
||||||
@layers[id] = data
|
@layers[id] = data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,59 @@ module LVS
|
||||||
def initialize
|
def initialize
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def _netter
|
||||||
|
@netter ||= LVS::LVSNetter::new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def _before_cleanup
|
||||||
|
|
||||||
|
# save the netlist database if requested
|
||||||
|
if @output_lvsdb_file && @netter && @netter.lvs_data && @netter.lvs_data.xref
|
||||||
|
|
||||||
|
lvsdb_file = _make_path(@output_lvsdb_file)
|
||||||
|
info("Writing LVS database: #{lvsdb_file} ..")
|
||||||
|
@netter.lvs_data.write(lvsdb_file)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# %DRC%
|
||||||
|
# @name report_lvs
|
||||||
|
# @brief Specifies an LVS report for output
|
||||||
|
# @synopsis report_lvs([ filename ])
|
||||||
|
# After the comparison step, the LVS database will be shown
|
||||||
|
# in the netlist database browser in a cross-reference view.
|
||||||
|
# If a filename is given, the LVS database is also written to
|
||||||
|
# this file.
|
||||||
|
#
|
||||||
|
# If this method is called together with report_netlist and two files each, two
|
||||||
|
# files can be generated - one for the extracted netlist (L2N database) and one for the
|
||||||
|
# LVS database. However, report_netlist will only write the extracted netlist
|
||||||
|
# while report_lvs will write the LVS database which also includes the
|
||||||
|
# extracted netlist.
|
||||||
|
#
|
||||||
|
# report_lvs is only effective if a comparison step is included.
|
||||||
|
|
||||||
|
def report_lvs(filename = nil)
|
||||||
|
@show_l2ndb = true
|
||||||
|
if filename
|
||||||
|
filename.is_a?(String) || raise("Argument must be string in report_lvs")
|
||||||
|
end
|
||||||
|
@output_lvsdb_file = filename
|
||||||
|
end
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
%w(schematic compare same_nets same_circuits same_device_classes equivalent_pins min_caps max_res max_depth max_branch_complexity).each do |f|
|
||||||
|
eval <<"CODE"
|
||||||
|
def #{f}(*args)
|
||||||
|
_netter.#{f}(*args)
|
||||||
|
end
|
||||||
|
CODE
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
# $autorun-early
|
||||||
|
|
||||||
|
module LVS
|
||||||
|
|
||||||
|
include DRC
|
||||||
|
|
||||||
|
# The LVS netter object
|
||||||
|
|
||||||
|
# %LVS%
|
||||||
|
# @scope
|
||||||
|
# @name Netter
|
||||||
|
# @brief LVS Reference: Netter object
|
||||||
|
# The Netter object provides services related to network extraction
|
||||||
|
# from a layout plus comparison against a reference netlist.
|
||||||
|
# Similar to the DRC netter (which lacks the compare ability), the
|
||||||
|
# relevant method of this object are available as global functions too
|
||||||
|
# where they act on a default incarnation. Usually it's not required
|
||||||
|
# to instantiate a Netter object explicitly.
|
||||||
|
#
|
||||||
|
# An individual netter object can be created, if the netter results
|
||||||
|
# need to be kept for multiple extractions. If you really need
|
||||||
|
# a Netter object, use the global \netter function:
|
||||||
|
#
|
||||||
|
# @code
|
||||||
|
# # create a new Netter object:
|
||||||
|
# nx = netter
|
||||||
|
# nx.connect(poly, contact)
|
||||||
|
# ...
|
||||||
|
# @/code
|
||||||
|
#
|
||||||
|
# ...
|
||||||
|
|
||||||
|
class LVSNetter < DRCNetter
|
||||||
|
|
||||||
|
def initialize(engine)
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_data
|
||||||
|
|
||||||
|
if @engine._dss
|
||||||
|
@engine._dss.is_singular? || raise("The LVS script features more than one or no layout source - network extraction cannot be performed in such configurations")
|
||||||
|
@lvs = RBA::LayoutVsSchematic::new(@engine._dss)
|
||||||
|
else
|
||||||
|
layout = @engine.source.layout
|
||||||
|
@lvs = RBA::LayoutVsSchematic::new(layout.top_cell.name, layout.dbu)
|
||||||
|
end
|
||||||
|
|
||||||
|
@l2n = @lvs
|
||||||
|
@comparer = RBA::NetlistComparer::new
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def lvs_data
|
||||||
|
l2n_data
|
||||||
|
@lvs
|
||||||
|
end
|
||||||
|
|
||||||
|
def _clear_data
|
||||||
|
super
|
||||||
|
@lvs = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def _take_data
|
||||||
|
data = super
|
||||||
|
@lvs = nil
|
||||||
|
data
|
||||||
|
end
|
||||||
|
|
||||||
|
def compare
|
||||||
|
@lvs.compare(@comparer)
|
||||||
|
end
|
||||||
|
|
||||||
|
def _ensure_two_netlists
|
||||||
|
|
||||||
|
netlist || raise("No netlist present (not extracted?)")
|
||||||
|
lvs_data.reference || raise("No reference schematic present (no set with 'schematic'?)")
|
||||||
|
|
||||||
|
[ netlist, lvs_data.reference ]
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def same_nets(*args)
|
||||||
|
|
||||||
|
pins.each do |a|
|
||||||
|
a.is_a?(String) || raise("All arguments of 'same_nets' need to be strings")
|
||||||
|
end
|
||||||
|
if args.size < 3
|
||||||
|
raise("Too few arguments to 'same_nets' (need at least 3)")
|
||||||
|
end
|
||||||
|
if args.size > 4
|
||||||
|
raise("Too many arguments to 'same_nets' (need max 4)")
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.size == 3
|
||||||
|
( ca, a, b ) = args
|
||||||
|
cb = ca
|
||||||
|
else
|
||||||
|
( ca, a, cb, b ) = args
|
||||||
|
end
|
||||||
|
|
||||||
|
( nl_a, nl_b ) = _ensure_two_netlists
|
||||||
|
|
||||||
|
circuit_a = nl_a.circuit_by_name(ca) || raise("Not a valid circuit name in extracted netlist: #{ca}")
|
||||||
|
circuit_b = nl_b.circuit_by_name(cb) || raise("Not a valid circuit name in reference netlist: #{cb}")
|
||||||
|
|
||||||
|
net_a = circuit_a.net_by_name(a) || raise("Not a valid net name in extracted netlist: #{a} (for circuit #{circuit_a})")
|
||||||
|
net_b = circuit_b.net_by_name(b) || raise("Not a valid net name in reference netlist: #{b} (for circuit #{circuit_b})")
|
||||||
|
|
||||||
|
@comparer.same_nets(net_a, net_b)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def same_circuits(a, b)
|
||||||
|
|
||||||
|
a.is_a?(String) || b.is_a?(String) || raise("Both arguments of 'same_circuits' need to be strings")
|
||||||
|
|
||||||
|
( nl_a, nl_b ) = _ensure_two_netlists
|
||||||
|
|
||||||
|
circuit_a = nl_a.circuit_by_name(a) || raise("Not a valid circuit name in extracted netlist: #{a}")
|
||||||
|
circuit_b = nl_b.circuit_by_name(b) || raise("Not a valid circuit name in reference netlist: #{b}")
|
||||||
|
|
||||||
|
@comparer.same_circuits(circuit_a, circuit_b)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def same_device_classes(a, b)
|
||||||
|
|
||||||
|
a.is_a?(String) || b.is_a?(String) || raise("Both arguments of 'same_device_classes' need to be strings")
|
||||||
|
|
||||||
|
( nl_a, nl_b ) = _ensure_two_netlists
|
||||||
|
|
||||||
|
dc_a = nl_a.device_class_by_name(a) || raise("Not a valid device class in extracted netlist: #{a}")
|
||||||
|
dc_b = nl_b.device_class_by_name(b) || raise("Not a valid device class in reference netlist: #{b}")
|
||||||
|
|
||||||
|
@comparer.same_device_classes(dc_a, dc_b)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def equivalent_pins(circuit, *pins)
|
||||||
|
|
||||||
|
circuit.is_a?(String) || raise("Circuit arguments of 'equivalent_pins' needs to be a string")
|
||||||
|
pins.each do |a|
|
||||||
|
a.is_a?(String) || raise("All pin arguments of 'equivalent_pins' need to be strings")
|
||||||
|
end
|
||||||
|
|
||||||
|
( nl_a, nl_b ) = _ensure_two_netlists
|
||||||
|
|
||||||
|
circuit_b = nl_b.circuit_by_name(circuit) || raise("Not a valid circuit name in reference netlist: #{circuit}")
|
||||||
|
|
||||||
|
pin_ids_b = pins.collect do |p|
|
||||||
|
pin = circuit_b.pin_by_name(p) || raise("Not a valid pin name in circuit '#{circuit}': #{p}")
|
||||||
|
pin.id
|
||||||
|
end
|
||||||
|
|
||||||
|
@comparer.equivalent_pins(circuit, pin_ids_b)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def schematic(filename, reader = nil)
|
||||||
|
|
||||||
|
filename.is_a?(String) || raise("First argument must be string in 'schematic'")
|
||||||
|
|
||||||
|
if reader
|
||||||
|
reader.is_a?(RBA::NetlistReader) || raise("Second argument must be netlist reader object in 'schematic'")
|
||||||
|
else
|
||||||
|
reader = RBA::NetlistSpiceReader::new
|
||||||
|
end
|
||||||
|
|
||||||
|
netlist_file = @engine._make_path(filename)
|
||||||
|
@engine.info("Reading netlist: #{netlist_file} ..")
|
||||||
|
|
||||||
|
netlist = RBA::Netlist::new
|
||||||
|
netlist.read(netlist_file, reader)
|
||||||
|
|
||||||
|
lvs_data.reference = netlist
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def min_caps(value)
|
||||||
|
lvs_data
|
||||||
|
@comparer.min_capacitance = value.to_f
|
||||||
|
end
|
||||||
|
|
||||||
|
def max_res(value)
|
||||||
|
lvs_data
|
||||||
|
@comparer.max_resistance = value.to_f
|
||||||
|
end
|
||||||
|
|
||||||
|
def max_depth(value)
|
||||||
|
lvs_data
|
||||||
|
@comparer.max_depth = value.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def max_branch_complexity(value)
|
||||||
|
lvs_data
|
||||||
|
@comparer.max_branch_complexity = value.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
<file alias="_drc_source.rb">built-in-macros/_drc_source.rb</file>
|
<file alias="_drc_source.rb">built-in-macros/_drc_source.rb</file>
|
||||||
<file alias="_drc_tags.rb">built-in-macros/_drc_tags.rb</file>
|
<file alias="_drc_tags.rb">built-in-macros/_drc_tags.rb</file>
|
||||||
<file alias="drc_interpreters.lym">built-in-macros/drc_interpreters.lym</file>
|
<file alias="drc_interpreters.lym">built-in-macros/drc_interpreters.lym</file>
|
||||||
|
<file alias="_lvs_netter.rb">built-in-macros/_lvs_netter.rb</file>
|
||||||
<file alias="_lvs_engine.rb">built-in-macros/_lvs_engine.rb</file>
|
<file alias="_lvs_engine.rb">built-in-macros/_lvs_engine.rb</file>
|
||||||
<file alias="lvs_interpreters.lym">built-in-macros/lvs_interpreters.lym</file>
|
<file alias="lvs_interpreters.lym">built-in-macros/lvs_interpreters.lym</file>
|
||||||
<file alias="install.lym">built-in-macros/install.lym</file>
|
<file alias="install.lym">built-in-macros/install.lym</file>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue