From 0cbfa698f02ce60200dd1804686695fcef13ea35 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 26 Jun 2019 20:41:49 +0200 Subject: [PATCH] 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 - ... --- src/db/db/dbLayoutToNetlistWriter.cc | 3 +- src/db/db/dbNetlistCompare.cc | 37 +++- src/db/db/dbNetlistSpiceReader.cc | 51 +++--- src/db/db/gsiDeclDbLayoutToNetlist.cc | 4 +- src/drc/drc/built-in-macros/_drc_engine.rb | 82 ++++++--- src/drc/drc/built-in-macros/_drc_netter.rb | 42 +++-- src/drc/drc/built-in-macros/_lvs_engine.rb | 52 ++++++ src/drc/drc/built-in-macros/_lvs_netter.rb | 203 +++++++++++++++++++++ src/drc/drc/drcResources.qrc | 1 + 9 files changed, 408 insertions(+), 67 deletions(-) create mode 100644 src/drc/drc/built-in-macros/_lvs_netter.rb diff --git a/src/db/db/dbLayoutToNetlistWriter.cc b/src/db/db/dbLayoutToNetlistWriter.cc index 23d922208..a60405ab8 100644 --- a/src/db/db/dbLayoutToNetlistWriter.cc +++ b/src/db/db/dbLayoutToNetlistWriter.cc @@ -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::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); diff --git a/src/db/db/dbNetlistCompare.cc b/src/db/db/dbNetlistCompare.cc index 3e1cb930b..9a4e11021 100644 --- a/src/db/db/dbNetlistCompare.cc +++ b/src/db/db/dbNetlistCompare.cc @@ -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_iterator cpa = m_cat_by_ptr.find (ca); + std::map::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::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) diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 3b79eee2f..b596d267e 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -65,11 +65,27 @@ void NetlistSpiceReaderDelegate::error (const std::string &msg) throw tl::Exception (msg); } +template +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 &nets, const std::map ¶ms) { 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 (circuit, cn); } else if (element == "L") { if (cn.empty ()) { cn = "IND"; } - new_cls = new db::DeviceClassInductor (); + cls = make_device_class (circuit, cn); } else if (element == "C") { if (cn.empty ()) { cn = "CAP"; } - new_cls = new db::DeviceClassCapacitor (); + cls = make_device_class (circuit, cn); } else if (element == "D") { if (cn.empty ()) { cn = "DIODE"; } - new_cls = new db::DeviceClassDiode (); + cls = make_device_class (circuit, cn); } else if (element == "Q") { if (nets.size () == 3) { if (cn.empty ()) { cn = "BJT3"; } - new_cls = new db::DeviceClassBJT3Transistor (); + cls = make_device_class (circuit, cn); } else if (nets.size () == 4) { if (cn.empty ()) { cn = "BJT4"; } - new_cls = new db::DeviceClassBJT4Transistor (); + cls = make_device_class (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 (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)); } diff --git a/src/db/db/gsiDeclDbLayoutToNetlist.cc b/src/db/db/gsiDeclDbLayoutToNetlist.cc index 02b8d31a1..e1369989f 100644 --- a/src/db/db/gsiDeclDbLayoutToNetlist.cc +++ b/src/db/db/gsiDeclDbLayoutToNetlist.cc @@ -480,11 +480,11 @@ Class 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" ) + diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index b02612ab3..d7b73cf08 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -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 + ")" diff --git a/src/drc/drc/built-in-macros/_drc_netter.rb b/src/drc/drc/built-in-macros/_drc_netter.rb index 9bc3da681..9932a0fbe 100644 --- a/src/drc/drc/built-in-macros/_drc_netter.rb +++ b/src/drc/drc/built-in-macros/_drc_netter.rb @@ -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 diff --git a/src/drc/drc/built-in-macros/_lvs_engine.rb b/src/drc/drc/built-in-macros/_lvs_engine.rb index 7a7a65e06..8e9cf61cb 100644 --- a/src/drc/drc/built-in-macros/_lvs_engine.rb +++ b/src/drc/drc/built-in-macros/_lvs_engine.rb @@ -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 diff --git a/src/drc/drc/built-in-macros/_lvs_netter.rb b/src/drc/drc/built-in-macros/_lvs_netter.rb new file mode 100644 index 000000000..c523eb138 --- /dev/null +++ b/src/drc/drc/built-in-macros/_lvs_netter.rb @@ -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 + diff --git a/src/drc/drc/drcResources.qrc b/src/drc/drc/drcResources.qrc index 8ab94011a..5eaca5614 100644 --- a/src/drc/drc/drcResources.qrc +++ b/src/drc/drc/drcResources.qrc @@ -7,6 +7,7 @@ built-in-macros/_drc_source.rb built-in-macros/_drc_tags.rb built-in-macros/drc_interpreters.lym + built-in-macros/_lvs_netter.rb built-in-macros/_lvs_engine.rb built-in-macros/lvs_interpreters.lym built-in-macros/install.lym