WIP: enhancements to DRC DSL for net extraction, some bug fixes in L2N browser etc.

This commit is contained in:
Matthias Koefferlein 2019-06-23 23:23:36 +02:00
parent 0f9c50c405
commit 464a1f35fb
11 changed files with 357 additions and 40 deletions

View File

@ -235,6 +235,11 @@ struct DeepShapeStore::LayoutHolder
return m_empty_layer;
}
unsigned int new_empty_layer ()
{
return layout.insert_layer ();
}
void add_layer_ref (unsigned int layer)
{
layer_refs [layer] += 1;
@ -577,6 +582,17 @@ DeepLayer DeepShapeStore::empty_layer () const
return empty_layer (0);
}
DeepLayer DeepShapeStore::new_empty_layer (unsigned int layout_index) const
{
return DeepLayer (const_cast<DeepShapeStore *> (this), layout_index, m_layouts[layout_index]->new_empty_layer ());
}
DeepLayer DeepShapeStore::new_empty_layer () const
{
require_singular ();
return new_empty_layer (0);
}
DeepLayer DeepShapeStore::create_custom_layer (const db::RecursiveShapeIterator &si, HierarchyBuilderShapeReceiver *pipe, const db::ICplxTrans &trans)
{
unsigned int layout_index = layout_for_iter (si, trans);

View File

@ -356,6 +356,19 @@ public:
*/
DeepLayer empty_layer () const;
/**
* @brief Gets a new empty working layer
*
* This method will deliver an empty layer for the given layout index. This layer is a fresh one and can be
* modified.
*/
DeepLayer new_empty_layer (unsigned int layout_index) const;
/**
* @brief Gets a new empty working layer for the singular layout
*/
DeepLayer new_empty_layer () const;
/**
* @brief Inserts the deep layer's shapes into some target layout
*/

View File

@ -92,6 +92,14 @@ LayoutToNetlist::~LayoutToNetlist ()
m_net_clusters.clear ();
}
void LayoutToNetlist::keep_dss ()
{
if (mp_dss.get () && ! mp_internal_dss.get ()) {
mp_dss->keep ();
mp_internal_dss.reset (mp_dss.get ());
}
}
void LayoutToNetlist::init ()
{
dss ().set_text_enlargement (1);

View File

@ -114,6 +114,11 @@ public:
*/
~LayoutToNetlist ();
/**
* @brief Makes the extractor take over ownership over the DSS when it was created with an external DSS
*/
void keep_dss ();
/**
* @brief Gets the database description
*/

View File

@ -154,7 +154,8 @@ void NetlistDeviceExtractor::extract (db::DeepShapeStore &dss, unsigned int layo
l = layer_map.find (ln);
}
throw tl::Exception (tl::to_string (tr ("Missing input layer for device extraction: ")) + layer_names);
// TODO: maybe use empty layers for optional ones?
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Missing input layer for device extraction (device %s): %s")), name (), layer_names));
}
@ -167,8 +168,9 @@ void NetlistDeviceExtractor::extract (db::DeepShapeStore &dss, unsigned int layo
// use deep layer alias for a given flat one (if found)
layers.push_back (alias.second.layer ());
} else if (l->second->empty ()) {
// provide a substitute empty layer
layers.push_back (dss.empty_layer (layout_index).layer ());
// provide a substitute empty layer (CAUTION: we can't use the
// singleton "empty_layer" because this may be used as OUTPUT).
layers.push_back (dss.new_empty_layer (layout_index).layer ());
} else {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid region passed to input layer '%s' for device extraction (device %s): must be of deep region kind")), ld->name, name ()));
}

View File

@ -147,7 +147,9 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
"add layers (regions) inside the 'dss' object.\n"
"\n"
"The make_... methods will not create new layers as there is no particular place "
"defined where to create the layers."
"defined where to create the layers.\n"
"\n"
"The extractor will not take ownership of the dss object unless you call \\keep_dss."
) +
gsi::constructor ("new", &make_l2n_from_existing_dss_with_layout, gsi::arg ("dss"), gsi::arg ("layout_index"),
"@brief Creates a new extractor object reusing an existing \\DeepShapeStore object\n"
@ -167,6 +169,12 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
"The database unit is mandatory because the physical parameter extraction "
"for devices requires this unit for translation of layout to physical dimensions.\n"
) +
gsi::method ("dss", (db::DeepShapeStore &(db::LayoutToNetlist::*) ()) &db::LayoutToNetlist::dss,
"@brief Gets a reference to the internal DSS object.\n"
) +
gsi::method ("keep_dss", &db::LayoutToNetlist::keep_dss,
"@brief Resumes ownership over the DSS object if created with an external one.\n"
) +
gsi::method ("threads=", &db::LayoutToNetlist::set_threads, gsi::arg ("n"),
"@brief Sets the number of threads to use for operations which support multiple threads\n"
) +

View File

@ -20,6 +20,7 @@ module DRC
@def_layout = cv && cv.layout
@def_cell = cv && cv.cell
@def_path = cv && cv.filename
@def_source = nil
@dbu_read = false
use_dbu(@def_layout && @def_layout.dbu)
@ -27,6 +28,11 @@ module DRC
@output_rdb = nil
@output_rdb_file = nil
@output_rdb_cell = nil
@output_l2ndb = nil
@output_l2ndb_file = nil
@target_netlist_file = nil
@target_netlist_format = nil
@target_netlist_comment = nil
@used_output_layers = {}
@output_layers = []
@vnum = 1
@ -109,6 +115,128 @@ module DRC
DRCAsDots::new(false)
end
# %DRC%
# @brief Defines SPICE output format (with options)
# @name write_spice
# @synopsis write_spice([ use_net_names [, with_comments ] ])
# Use this option in \target_netlist for the format parameter to
# specify SPICE format.
# "use_net_names" and "with_comments" are boolean parameters indicating
# whether to use named nets (numbers if false) and whether to add
# information comments such as instance coordinates or pin names.
def write_spice(use_net_names = nil, with_comments = nil)
writer = RBA::NetlistSpiceWriter::new
if use_net_names != nil
writer.use_net_names = use_net_names
end
if with_comments != nil
writer.with_comments = with_comments
end
writer
end
# %DRC%
# @brief Supplies the MOS3 transistor extractor class
# @name mos3
# @synopsis mos3(name)
# Use this class with \device_extract to specify extraction of a
# three-terminal MOS transistor
def mos3(name)
RBA::DeviceExtractorMOS3Transistor::new(name)
end
# %DRC%
# @brief Supplies the MOS4 transistor extractor class
# @name mos4
# @synopsis mos4(name)
# Use this class with \device_extract to specify extraction of a
# four-terminal MOS transistor
def mos4(name)
RBA::DeviceExtractorMOS4Transistor::new(name)
end
# %DRC%
# @brief Supplies the BJT3 transistor extractor class
# @name bjt3
# @synopsis bjt3(name)
# Use this class with \device_extract to specify extraction of a
# bipolar junction transistor
def bjt3(name)
RBA::DeviceExtractorBJT3Transistor::new(name)
end
# %DRC%
# @brief Supplies the BJT4 transistor extractor class
# @name bjt4
# @synopsis bjt4(name)
# Use this class with \device_extract to specify extraction of a
# bipolar junction transistor with a substrate terminal
def bjt4(name)
RBA::DeviceExtractorBJT4Transistor::new(name)
end
# %DRC%
# @brief Supplies the diode extractor class
# @name diode
# @synopsis diode(name)
# Use this class with \device_extract to specify extraction of a
# planar diode
def diode(name)
RBA::DeviceExtractorDiode::new(name)
end
# %DRC%
# @brief Supplies the resistor extractor class
# @name resistor
# @synopsis resistor(name, sheet_rho)
# Use this class with \device_extract to specify extraction of a resistor.
# The sheet_rho value is the sheet resistance in ohms/square.
def resistor(name, sheet_rho)
RBA::DeviceExtractorResistor::new(name, sheet_rho)
end
# %DRC%
# @brief Supplies the resistor extractor class that includes a bulk terminal
# @name resistor_with_bulk
# @synopsis resistor_with_bulk(name)
# Use this class with \device_extract to specify extraction of a resistor
# with a bulk terminal.
# The sheet_rho value is the sheet resistance in ohms/square.
def resistor_with_bulk(name, sheet_rho)
RBA::DeviceExtractorResistorWithBulk::new(name, sheet_rho)
end
# %DRC%
# @brief Supplies the capacitor extractor class
# @name capacitor
# @synopsis capacitor(name, area_cap)
# Use this class with \device_extract to specify extraction of a capacitor.
# The area_cap argument is the capacitance in Farad per square micrometer.
def capacitor(name, area_cap)
RBA::DeviceExtractorCapacitor::new(name, area_cap)
end
# %DRC%
# @brief Supplies the capacitor extractor class that includes a bulk terminal
# @name capacitor_with_bulk
# @synopsis capacitor_with_bulk(name)
# Use this class with \device_extract to specify extraction of a capacitor
# with a bulk terminal.
# The area_cap argument is the capacitance in Farad per square micrometer.
def capacitor_with_bulk(name, area_cap)
RBA::DeviceExtractorCapacitorWithBulk::new(name, area_cap)
end
# %DRC%
# @name verbose?
# @brief Returns true, if verbose mode is enabled
@ -451,7 +579,7 @@ module DRC
(n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}")
cv = view.cellview(n)
cv.is_valid? || raise("Invalid layout @#{n + 1}")
@def_source = make_source(cv.layout, cv.cell)
@def_source = make_source(cv.layout, cv.cell, cv.filename)
else
layout = RBA::Layout::new
info("Reading #{arg} ..")
@ -462,7 +590,7 @@ module DRC
cell = layout.cell(arg2)
cell || raise("Cell name #{arg2} not found in input layout")
end
@def_source = make_source(layout, cell)
@def_source = make_source(layout, cell, arg)
end
elsif arg.is_a?(RBA::Layout)
@ -484,12 +612,13 @@ module DRC
else
@def_source || @def_layout || raise("No layout loaded - no default layout. Use 'layout' or 'source' to explicitly specify a layout.")
@def_source ||= make_source(@def_layout, @def_cell)
@def_source ||= make_source(@def_layout, @def_cell, @def_path)
end
# make default input also default output if none is set yet.
@def_layout ||= @def_source.layout
@def_cell ||= @def_source.cell_obj
@def_path ||= @def_source.path
# use the DBU of the new input as DBU reference
@dbu_read || use_dbu(@def_source.layout.dbu)
@ -545,7 +674,7 @@ module DRC
(n >= 0 && view.cellviews > n) || raise("Invalid layout index @#{n + 1}")
cv = view.cellview(n)
cv.is_valid? || raise("Invalid layout @#{n + 1}")
return make_source(cv.layout, cv.cell)
return make_source(cv.layout, cv.cell, cv.filename)
else
layout = RBA::Layout::new
info("Reading #{arg} ..")
@ -556,7 +685,7 @@ module DRC
cell = layout.cell(arg2)
cell || raise("Cell name #{arg2} not found in input layout")
end
return make_source(layout, cell)
return make_source(layout, cell, arg)
end
elsif arg.is_a?(RBA::Layout)
@ -569,7 +698,7 @@ module DRC
else
@def_source || @def_layout || raise("No layout loaded - no default layout. Use 'layout' or 'source' to explicitly specify a layout.")
@def_source ||= make_source(@def_layout, @def_cell)
@def_source ||= make_source(@def_layout, @def_cell, @def_path)
@def_source
end
@ -627,7 +756,53 @@ module DRC
@output_rdb.description = description
end
# %DRC%
# @name report_netlist
# @brief Specifies an extracted netlist report for output
# @synopsis report_netlist([ filename ])
# This method applies to runsets creating a netlist through
# extraction. Extraction happens when connections and/or device
# extractions are made. If this statement is used, the extracted
# netlist plus the net and device shapes are turned into a
# layout-to-netlist report (L2N database) and shown in the
# netlist browser window. If a file name is given, the report
# will also be written to the given file.
def report_netlist(filename = nil)
@output_l2ndb = true
if filename
filename.is_a?(String) || raise("Argument must be string in report_netlist")
end
@output_l2ndb_file = filename
end
# %DRC%
# @name target_netlist
# @brief With this statement, an extracted netlist is finally written to a file
# @synopsis target_netlist(filename [, format [, comment ] ])
# This method applies to runsets creating a netlist through
# extraction. Extraction happens when connections and/or device
# extractions are made. If this statement is used, the extracted
# netlist is written to the given file.
#
# The format parameter specifies the writer to use. You can use nil
# to use the standard format or produce a SPICE writer with \write_spice.
# See \write_spice for more details.
def target_netlist(filename, format = nil, comment = nil)
filename.is_a?(String) || raise("First argument must be string in target_netlist")
@target_netlist_file = filename
if format
format.is_a?(RBA::NetlistWriter) || raise("Second argument must be netlist writer object in target_netlist")
end
@target_netlist_format = format
if comment
comment.is_a?(String) || raise("Third argument must be string in target_netlist")
end
@target_netlist_comment = comment
end
# %DRC%
# @name output_cell
# @brief Specifies a target cell, but does not change the target layout
@ -958,33 +1133,41 @@ CODE
# @name clear_connections
# @brief Clears all connections stored so far
# @synopsis clear_connections
# See \Netter#clear_connections for a description of that function
# See \Netter#clear_connections for a description of that function.
# %DRC%
# @name join_nets
# @name connect_implicit
# @brief Specifies a label pattern for implicit net connections
# @synopsis join_nets(label_pattern)
# See \Netter#join_nets for a description of that function
# @synopsis connect_implicit(label_pattern)
# See \Netter#connect_implicit for a description of that function.
# %DRC%
# @name antenna_check
# @brief Performs an antenna check
# @synopsis antenna_check(gate, metal, ratio, [ diode_specs ... ])
# See \Netter#antenna_check for a description of that function
# See \Netter#antenna_check for a description of that function.
# %DRC%
# @name l2n_data
# @brief Gets the internal RBA::LayoutToNetlist object for the default \Netter
# @synopsis l2n_data
# See \Netter#l2n_data for a description of that function
# See \Netter#l2n_data for a description of that function.
# %DRC%
# @name extract_devices
# @brief Extracts devices for a given device extractor and device layer selection
# @synopsis extract_devices(extractor, layer_hash)
# See \Netter#extract_devices for a description of that function
# @synopsis extract_devices(extractor_class, name, layer_hash)
# See \Netter#extract_devices for a description of that function.
%w(connect connect_global clear_connections join_nets antenna_check l2n_data extract_devices).each do |f|
# %DRC%
# @name netlist
# @brief Obtains the extracted netlist from the default \Netter
# The netlist is a RBA::Netlist object. If no netlist is extracted
# yet, this method will trigger the extraction process.
# See \Netter#netlist for a description of this function.
%w(connect connect_global clear_connections connect_implicit antenna_check l2n_data extract_devices netlist).each do |f|
eval <<"CODE"
def #{f}(*args)
_netter.#{f}(*args)
@ -1154,8 +1337,9 @@ CODE
# save the report database if requested
if @output_rdb_file
info("Writing #{@output_rdb_file} ..")
@output_rdb.save(@output_rdb_file)
rdb_file = make_path(@output_rdb_file)
info("Writing report database: #{rdb_file} ..")
@output_rdb.save(rdb_file)
end
if @output_rdb && final && view
view.show_rdb(@output_rdb_index, view.active_cellview_index)
@ -1165,7 +1349,7 @@ CODE
if @output_layout && @output_layout_file
opt = RBA::SaveLayoutOptions::new
gzip = opt.set_format_from_filename(@output_layout_file)
info("Writing #{@output_layout_file} ..")
info("Writing layout file: #{@output_layout_file} ..")
@output_layout.write(@output_layout_file, gzip, opt)
end
@ -1213,7 +1397,41 @@ CODE
end
end
# save the netlist if required
if @target_netlist_file && @netter && @netter.l2n_data
writer = @target_netlist_format || RBA::NetlistSpiceWriter::new
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
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
end
@output_layout = nil
@output_layout_file = nil
@output_cell = nil
@ -1221,6 +1439,8 @@ CODE
@output_rdb_cell = nil
@output_rdb = nil
@output_rdb_index = nil
@output_l2ndb = nil
@output_l2ndb_file = nil
# clean up temp data
@dss && @dss._destroy
@ -1243,6 +1463,16 @@ CODE
private
def make_path(file)
# resolves the file path relative to the source's path
sp = self.source.path
if sp
return File::absolute_path(file, File::dirname(sp))
else
return file
end
end
def _make_string(v)
if v.class.respond_to?(:from_s)
v.class.to_s + "::from_s(" + v.to_s.inspect + ")"
@ -1399,11 +1629,11 @@ CODE
end
end
def make_source(layout, cell = nil)
def make_source(layout, cell = nil, path = nil)
name = "layout" + @lnum.to_s
@lnum += 1
@dbu ||= layout.dbu
src = DRCSource::new(self, layout, layout, cell || layout.top_cell)
src = DRCSource::new(self, layout, layout, cell || layout.top_cell, path)
@layout_sources[name] = src
src
end

View File

@ -119,10 +119,14 @@ module DRC
# @name extract_devices
# @brief Extracts devices based on the given extractor class, name and device layer selection
# @synopsis extract_devices(extractor, layer_hash)
# Runs the device extraction for given device extractor class.
# @synopsis extract_devices(extractor_class, name, layer_hash)
# Runs the device extraction for given device extractor class. In the first
# form, the extractor object is given. In the second form, the extractor's
# class object and the new extractor's name is given.
#
# The device extractor is either an instance of one of the predefined extractor
# classes (e.g. RBA::DeviceExtractorMOS4Transistor) or a custom class. It provides the
# classes (e.g. obtained from the utility methods such as \mos4) or a custom class.
# It provides the
# algorithms for deriving the device parameters from the device geometry. It needs
# several device recognition layers which are passed in the layer hash.
#
@ -141,17 +145,17 @@ module DRC
# poly = input(3, 0)
# bulk = make_layer # renders an empty layer used for putting the terminals on
#
# nactive = active - nwell # active area of NMOS
# nsd = nactive - poly # source/drain area
# nactive = active - nwell # active area of NMOS
# nsd = nactive - poly # source/drain area
# gate = nactive & poly # gate area
#
# mos4_ex = RBA::DeviceExtractorMOS4Transistor::new("NMOS4")
# extract_devices(mos4_ex, { :SD => nsd, :G => gate, :P => poly, :W => bulk })
# extract_devices(mos4("NMOS4"), { :SD => nsd, :G => gate, :P => poly, :W => bulk })
# @/code
def extract_devices(devex, layer_selection)
devex.is_a?(RBA::DeviceExtractorBase) || raise("First argument of Netter#extract_devices must be a device extractor instance")
devex.is_a?(RBA::DeviceExtractorBase) || raise("First argument of Netter#extract_devices must be a device extractor instance in the two-arguments form")
layer_selection.is_a?(Hash) || raise("Second argument of Netter#extract_devices must be a hash")
ls = {}
@ -177,20 +181,20 @@ module DRC
@connections = []
@global_connections = []
@layers = {}
@join_nets = ""
@connect_implicit = ""
modified
end
# %DRC%
# @name join_nets
# @name connect_implicit
# @brief Specifies a search pattern for labels which create implicit net connections
# @synopsis join_nets(label_pattern)
# @synopsis connect_implicit(label_pattern)
# Use this method to supply a glob pattern for labels which create implicit net connections
# on the top level circuit. This feature is useful to connect identically labelled nets
# while a component isn't integrated yet. If the component is integrated, net may be connected
# on a higher hierarchy level - e.g. by a power mesh. Inside the component this net consists
# of individual islands. To properly perform netlist extraction and comparison, these islands
# need to be connected even though there isn't a physical connection. "join_nets" can
# need to be connected even though there isn't a physical connection. "connect_implicit" can
# achive this if these islands are labelled with the same text on the top level of the
# component.
#
@ -202,8 +206,8 @@ module DRC
# The search pattern is applied on the next net extraction. The search pattern is cleared
# on "clear_connections".
def join_nets(arg)
@join_nets = arg
def connect_implicit(arg)
@connect_implicit = arg
modified
end
@ -320,6 +324,18 @@ module DRC
@l2n || make_l2n
@l2n
end
# %DRC%
# @name netlist
# @brief Gets the extracted netlist or triggers extraction if not done yet
# @synopsis netlist
# If no extraction has been performed yet, this method will start the
# layout analysis. Hence, all \connect, \connect_global and \connect_implicit
# calls must have been made before this method is used. Further \connect
# statements will clear the netlist and re-extract it again.
def netlist
l2n_data && @l2n.netlist
end
def _finish
clear_connections
@ -327,6 +343,12 @@ module DRC
modified
end
def _take_l2n_data
l2ndb = self.l2n_data
@l2n = nil
l2ndb
end
private
def modified
@ -357,7 +379,7 @@ module DRC
@global_connections.each { |l,n| @l2n.connect_global(@layers[l], n) }
# run extraction in a timed environment
@engine._cmd(@l2n, :extract_netlist, @join_nets)
@engine._cmd(@l2n, :extract_netlist, @connect_implicit)
@l2n
end

View File

@ -16,10 +16,11 @@ module DRC
class DRCSource
def initialize(engine, layout, layout_var, cell)
def initialize(engine, layout, layout_var, cell, path)
@engine = engine
@layout = layout
@layout_var = layout_var
@path = path
@cell = cell
@inside = nil
@box = nil
@ -355,6 +356,13 @@ CODE
def layers
@layout.layer_indices.collect { |li| @layout.get_info(li) }
end
# %DRC%
# @brief Gets the path of the corresponding layout file or nil if there is no path
# @synopsis path
def path
@path
end
private

View File

@ -111,6 +111,6 @@ module DRC
end
end
end
end

View File

@ -708,6 +708,11 @@ NetlistBrowserPage::show_all (bool f)
void
NetlistBrowserPage::set_db (db::LayoutToNetlist *l2ndb)
{
if (l2ndb == mp_database.get ()) {
// not change
return;
}
if (mp_info_dialog) {
delete mp_info_dialog;
mp_info_dialog = 0;