Integration of LayoutToNetlist into DRC framework ('Netter')

First steps are made towards providing an antenna check
This commit is contained in:
Matthias Koefferlein 2019-03-02 10:41:07 +01:00
parent 261fb027fd
commit 5c9b652771
1 changed files with 304 additions and 23 deletions

View File

@ -3159,6 +3159,26 @@ CODE
@data
end
def requires_region(f)
@data.is_a?(RBA::Region) || raise("#{f}: Requires a polygon layer")
end
def requires_edge_pairs(f)
@data.is_a?(RBA::EdgePairs) || raise("#{f}: Requires a edge pair layer")
end
def requires_edges(f)
@data.is_a?(RBA::Edges) || raise("#{f}: Requires an edge layer")
end
def requires_edges_or_region(f)
@data.is_a?(RBA::Edges) || @data.is_a?(RBA::Region) || raise("#{f}: Requires an edge or polygon layer")
end
def requires_same_type(other, f)
@data.class == other.data.class || raise("#{f}: Requires input of the same kind")
end
private
def insert_object_into(container, object, dbu_trans)
@ -3208,28 +3228,6 @@ CODE
end
end
protected
def requires_region(f)
@data.is_a?(RBA::Region) || raise("#{f}: Requires a polygon layer")
end
def requires_edge_pairs(f)
@data.is_a?(RBA::EdgePairs) || raise("#{f}: Requires a edge pair layer")
end
def requires_edges(f)
@data.is_a?(RBA::Edges) || raise("#{f}: Requires an edge layer")
end
def requires_edges_or_region(f)
@data.is_a?(RBA::Edges) || @data.is_a?(RBA::Region) || raise("#{f}: Requires an edge or polygon layer")
end
def requires_same_type(other, f)
@data.class == other.data.class || raise("#{f}: Requires input of the same kind")
end
end
# A layout source representative object.
@ -3595,6 +3593,244 @@ CODE
end
# The netter object
# %DRC%
# @scope
# @name Netter
# @brief DRC Reference: Netter object
# The Netter object provides services related to network extraction
# from a layout. The relevant methods of this object are available
# 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.
#
# 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
#
# Network formation:
#
# A basic Service the Netter object provides is the formation of
# connected networks of conductive shapes. To do so, the Netter
# must be given a connection specification. This happens by calling
# "connect" with two polygon layers. The Netter will then regard all
# overlaps of shapes on these layers as connections between the
# respective materials. Networks are the basis for netlist extraction,
# network geometry deduction and the antenna check.
#
# Connections can be cleared with "clear_connections". If not,
# connections add atop of the already defined ones. Here is an
# example for the antenna check:
#
# @code
# # build connction of poly+gate to metal1
# connect(gate, poly)
# connect(poly, contact)
# connect(contact, metal1)
#
# # runs an antenna check for metal1 with a ratio of 50
# m1_antenna_errors = antenna_check(gate, metal1, 50.0)
#
# # add connections to metal2
# connect(metal1, via1)
# connect(via1, metal2)
#
# # runs an antenna check for metal2 with a ratio of 70.0
# m2_antenna_errors = antenna_check(gate, metal2, 70.0)
#
# # this will remove all connections made
# clear_connections
# @/code
class DRCNetter
def initialize(engine)
@engine = engine
clear_connections
end
# %DRC%
# @name connect
# @brief Specifies a connection between two layers
# @synopsis connect(a, b)
# a and b must be polygon layers. After calling this function, the
# Netter regards all overlapping or touching shapes on these layers
# to form an electrical connection between the materials formed by
# these layers. This also implies intra-layer connections: shapes
# on these layers touching or overlapping other shapes on these
# layers will form bigger, electrically connected areas.
#
# Multiple connect calls must be made to form larger connectivity
# stacks across multiple layers. Such stacks may include forks and
# joins.
#
# Connections are accumulated. The connections defined so far
# can be cleared with \clear_connections.
def connect(a, b)
a.is_a?(DRC::DRCLayer) || raise("First argument of Netter#connect must be a layer")
b.is_a?(DRC::DRCLayer) || raise("Second argument of Netter#connect must be a layer")
a.requires_region("Netter#connect (first argument)")
b.requires_region("Netter#connect (second argument)")
[ a, b ].each { |l| @layers[l.data.data_id] = l.data }
@connections << [ a, b ].collect { |l| l.data.data_id }
modified
end
# %DRC%
# @name clear_connections
# @brief Clears all connections stored so far
# @synopsis clear_connections
# See \connect for more details.
def clear_connections
@connections = []
@layers = {}
modified
end
# %DRC%
# @brief Performs an antenna check
# @name antenna_check
# @synopsis antenna_check(gate, metal, ratio, [ diode_specs ... ])
#
# The antenna check is used to avoid plasma induced damage. Physically,
# the damage happes if during the manufacturing of a metal layer with
# plasma etching charge accumulates on the metal islands. On reaching a
# certain threshold, this charge may discarge over gate oxide attached of
# devices attached to such metal areas hence damaging it.
#
# Antenna checks are performed by collecting all connected nets up to
# a certain metal layer and then computing the area of all metal shapes
# and all connected gates of a certain kind (e.g. thin and thick oxide gates).
# The ratio of metal area divided by the gate area must not exceed a certain
# threshold.
#
# A simple antenna check is this:
#
# @code
# poly = ... # poly layer
# diff = ... # diffusion layer
# contact = ... # contact layer
# metal1 = ... # metal layer
#
# # compute gate area
# gate = poly & diff
#
# # note that gate and poly have to be included - gate is
# # a subset of poly, but forms the sensitive area
# connect(gate, poly)
# connect(poly, contact)
# connect(contact, metal1)
# errors = antenna_check(gate, metal1, 50.0)
# @/code
#
# Plasma induced damage can be rectified by including diodes
# which create a safe current path for discharging the metal
# islands. Such diodes can be identified with a recognition layer
# (usually the diffusion area of a certain kind). You can include
# such diode recognition layers in the antenna check. If a connection
# is detected to a diode, the respective network is skipped:
#
# @code
# ...
# diode = ... # diode recognition layer
#
# connect(diode, contact)
# errors = antenna_check(gate, metal1, 50.0, diode)
# @/code
#
# You can also make diode connections decreases the
# sensitivity of the antenna check depending on the size
# of the diode. The following specification makes
# diode connections increase the ratio threshold by
# 10 per square micrometer of diode area:
#
# @code
# ...
# diode = ... # diode recognition layer
#
# connect(diode, contact)
# # each square micrometer of diode area connected to a network
# # will add 10 to the ratio:
# errors = antenna_check(gate, metal1, 50.0, [ diode, 10.0 ])
# @/code
#
# Multiple diode specifications are allowed. Just add them
# to the antenna_check call.
#
# The error shapes produced by the antenna check are a copy
# of the metal shapes on the metal layers of each network
# violating the antenna rule.
def antenna_check(gate, metal, ratio, *diodes)
gate.is_a?(DRC::DRCLayer) || raise("gate argument of Netter#antenna_check must be a layer")
gate.requires_region("Netter#antenna_check (gate argument)")
metal.is_a?(DRC::DRCLayer) || raise("metal argument of Netter#antenna_check must be a layer")
metal.requires_region("Netter#antenna_check (metal argument)")
if !ratio.is_a?(1.class) && !ratio.is_a?(Float)
raise("ratio argument Netter#antenna_check is not a number")
end
dl = diodes.collect do |d|
if d.is_a?(Array)
d.size == 2 || raise("diode specification pair expects two elements")
d[0].requires_region("Netter#antenna_check (diode layer)")
[ d[0].data, d[1].to_f ]
else
d.requires_region("Netter#antenna_check (diode layer)")
[ d.data, 0.0 ]
end
end
@l2n || make_l2n
DRC::DRCLayer::new(@engine, @engine._cmd(@l2n, :antenna_check, gate.data, metal.data, ratio, dl))
end
private
def modified
@l2n && @l2n._destroy
@l2n = nil
end
def make_l2n
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
layout = @engine.source.layout
@l2n = RBA::LayoutToNetlist::new(layout.top_cell.name, layout.dbu)
end
@layers.each { |id,l| @l2n.register(l, "l" + id.to_s) }
@layers.each { |id,l| @l2n.connect(l) }
@connections.each { |a,b| @l2n.connect(@layers[a], @layers[b]) }
# run extraction in a timed environment
@engine._cmd(@l2n, :extract_netlist)
@l2n
end
end
# The DRC engine
# %DRC%
@ -3628,6 +3864,7 @@ CODE
@log_file = nil
@dss = nil
@deep = false
@netter = nil
@verbose = false
@ -4491,6 +4728,42 @@ CODE
CODE
end
# %DRC%
# @name netter
# @brief Creates a new netter object
# @synopsis netter
# See \Netter for more details
def netter
DRC::DRCNetter::new
end
# %DRC%
# @name connect
# @brief Specifies a connection between two layers
# @synopsis connect(a, b)
# See \Netter#connect for a description of that function.
# %DRC%
# @name clear_connections
# @brief Clears all connections stored so far
# @synopsis clear_connections
# See \Netter#clear_connections 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
%w(connect clear_connections antenna_check).each do |f|
eval <<"CODE"
def #{f}(*args)
_netter.#{f}(*args)
end
CODE
end
def src_line
cc = caller.find do |c|
c !~ /drc.lym:/ && c !~ /\(eval\)/
@ -4730,7 +5003,15 @@ CODE
end
end
def _dss
@dss
end
def _netter
@netter ||= DRC::DRCNetter::new(self)
end
private
def _make_string(v)