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:
Matthias Koefferlein 2019-06-26 20:41:49 +02:00
parent 37012efba0
commit 0cbfa698f0
9 changed files with 408 additions and 67 deletions

View File

@ -23,6 +23,7 @@
#include "dbLayoutToNetlistWriter.h"
#include "dbLayoutToNetlist.h"
#include "dbLayoutToNetlistFormatDefs.h"
#include "dbPolygonTools.h"
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;
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);
} else {
write_points (*mp_stream, poly, t, m_ref, relative);

View File

@ -275,9 +275,40 @@ public:
void same_class (const db::DeviceClass *ca, const db::DeviceClass *cb)
{
++m_next_cat;
m_cat_by_ptr.insert (std::make_pair (ca, m_next_cat));
m_cat_by_ptr.insert (std::make_pair (cb, m_next_cat));
// reuse existing category if one is assigned already -> this allows associating
// multiple categories to other ones (A->C, B->C)
std::map<const db::DeviceClass *, size_t>::const_iterator cpa = m_cat_by_ptr.find (ca);
std::map<const db::DeviceClass *, size_t>::const_iterator cpb = m_cat_by_ptr.find (cb);
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)

View File

@ -65,11 +65,27 @@ void NetlistSpiceReaderDelegate::error (const std::string &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> &params)
{
std::string cn = model;
db::DeviceClass *cls = circuit->netlist ()->device_class_by_name (cn);
db::DeviceClass *new_cls = 0;
if (cls) {
// use given class
@ -77,53 +93,46 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin
if (cn.empty ()) {
cn = "RES";
}
new_cls = new db::DeviceClassResistor ();
cls = make_device_class<db::DeviceClassResistor> (circuit, cn);
} else if (element == "L") {
if (cn.empty ()) {
cn = "IND";
}
new_cls = new db::DeviceClassInductor ();
cls = make_device_class<db::DeviceClassInductor> (circuit, cn);
} else if (element == "C") {
if (cn.empty ()) {
cn = "CAP";
}
new_cls = new db::DeviceClassCapacitor ();
cls = make_device_class<db::DeviceClassCapacitor> (circuit, cn);
} else if (element == "D") {
if (cn.empty ()) {
cn = "DIODE";
}
new_cls = new db::DeviceClassDiode ();
cls = make_device_class<db::DeviceClassDiode> (circuit, cn);
} else if (element == "Q") {
if (nets.size () == 3) {
if (cn.empty ()) {
cn = "BJT3";
}
new_cls = new db::DeviceClassBJT3Transistor ();
cls = make_device_class<db::DeviceClassBJT3Transistor> (circuit, cn);
} else if (nets.size () == 4) {
if (cn.empty ()) {
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") {
if (nets.size () == 3) {
if (cn.empty ()) {
cn = "MOS3";
}
new_cls = new db::DeviceClassMOS3Transistor ();
} else if (nets.size () == 4) {
if (nets.size () == 4) {
if (cn.empty ()) {
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")));
}
}
if (new_cls) {
cls = new_cls;
cls->set_name (cn);
circuit->netlist ()->add_device_class (cls);
} else if (! cls) {
} else {
error (tl::sprintf (tl::to_string (tr ("Not a known element type: '%s'")), element));
}

View File

@ -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"
"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"
"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"
"This method employs the native format of KLayout.\n"
) +

View File

@ -28,7 +28,7 @@ module DRC
@output_rdb = nil
@output_rdb_file = nil
@output_rdb_cell = nil
@output_l2ndb = nil
@show_l2ndb = nil
@output_l2ndb_file = nil
@target_netlist_file = nil
@target_netlist_format = nil
@ -42,6 +42,7 @@ module DRC
@dss = nil
@deep = false
@netter = nil
@netter_data = nil
@verbose = false
@ -770,7 +771,7 @@ module DRC
# will also be written to the given file.
def report_netlist(filename = nil)
@output_l2ndb = true
@show_l2ndb = true
if filename
filename.is_a?(String) || raise("Argument must be string in report_netlist")
end
@ -1337,7 +1338,7 @@ CODE
# save the report database if requested
if @output_rdb_file
rdb_file = make_path(@output_rdb_file)
rdb_file = _make_path(@output_rdb_file)
info("Writing report database: #{rdb_file} ..")
@output_rdb.save(rdb_file)
end
@ -1403,35 +1404,34 @@ CODE
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} ..")
self.netlist.write(netlist_file, writer, @target_netlist_comment || "")
end
# 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)
info("Writing netlist database: #{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
l2ndb_file = _make_path(@output_l2ndb_file)
info("Writing netlist database: #{l2ndb_file} ..")
@netter.l2n_data.write_l2n(l2ndb_file)
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_file = nil
@output_cell = nil
@ -1439,12 +1439,15 @@ CODE
@output_rdb_cell = nil
@output_rdb = nil
@output_rdb_index = nil
@output_l2ndb = nil
@show_l2ndb = nil
@output_l2ndb_file = nil
# clean up temp data
@dss && @dss._destroy
@dss = nil
@netter && @netter._finish
@netter = nil
@netter_data = nil
if final && @log_file
@log_file.close
@ -1453,6 +1456,33 @@ CODE
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
@dss
end
@ -1461,9 +1491,7 @@ CODE
@netter ||= DRC::DRCNetter::new(self)
end
private
def make_path(file)
def _make_path(file)
# resolves the file path relative to the source's path
sp = self.source.path
if sp
@ -1473,6 +1501,8 @@ CODE
end
end
private
def _make_string(v)
if v.class.respond_to?(:from_s)
v.class.to_s + "::from_s(" + v.to_s.inspect + ")"

View File

@ -2,7 +2,7 @@
module DRC
# The netter object
# The DRC netter object
# %DRC%
# @scope
@ -13,6 +13,17 @@ module DRC
# as global functions too where they act on a default incarnation
# of the netter. Usually it's not required to instantiate a Netter
# 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
# need to be kept for multiple extractions. If you really need
@ -162,7 +173,7 @@ module DRC
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")
@ -188,8 +199,7 @@ module DRC
def clear_connections
@netlisted = false
@connect_implicit = ""
@l2n && @l2n._destroy
@l2n = nil
_clear_data
end
# %DRC%
@ -336,7 +346,7 @@ module DRC
def l2n_data
ensure_l2n
ensure_data
# run extraction in a timed environment
if ! @netlisted
@ -364,7 +374,12 @@ module DRC
clear_connections
end
def _take_l2n_data
def _clear_data
@l2n && @l2n._destroy
@l2n = nil
end
def _take_data
l2ndb = self.l2n_data
@l2n = nil
l2ndb
@ -376,17 +391,16 @@ module DRC
@netlisted && clear_connections
end
def ensure_l2n
@l2n || make_l2n
def ensure_data
if !@l2n
@layers = {}
make_data
end
end
def make_l2n
@layers = {}
def make_data
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")
@l2n = RBA::LayoutToNetlist::new(@engine._dss)
else
@ -405,7 +419,7 @@ module DRC
return
end
ensure_l2n
ensure_data
@layers[id] = data

View File

@ -23,7 +23,59 @@ module LVS
def initialize
super
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

View File

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

View File

@ -7,6 +7,7 @@
<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_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_interpreters.lym">built-in-macros/lvs_interpreters.lym</file>
<file alias="install.lym">built-in-macros/install.lym</file>