Implemented #570 (perimeter included in antenna check) (#572)

* First implementation of the perimeter factor for antenna check, unit tests.

* Bugfix and unit tests for GSI binding of new antenna check version.

* DRC integration of perimeter-enabled antenna check.

* Enhanced DRC doc for antenna rule
This commit is contained in:
Matthias Köfferlein 2020-05-30 21:45:48 +02:00 committed by GitHub
parent bea3e29421
commit 6601d472bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 372 additions and 68 deletions

View File

@ -1180,7 +1180,7 @@ db::Net *LayoutToNetlist::probe_net (const db::Region &of_region, const db::Poin
}
}
db::Region LayoutToNetlist::antenna_check (const db::Region &gate, const db::Region &metal, double ratio, const std::vector<std::pair<const db::Region *, double> > &diodes)
db::Region LayoutToNetlist::antenna_check (const db::Region &gate, double gate_perimeter_factor, const db::Region &metal, double metal_perimeter_factor, double ratio, const std::vector<std::pair<const db::Region *, double> > &diodes)
{
// TODO: that's basically too much .. we only need the clusters
if (! m_netlist_extracted) {
@ -1211,7 +1211,14 @@ db::Region LayoutToNetlist::antenna_check (const db::Region &gate, const db::Reg
deliver_shapes_of_net_recursive (0, m_net_clusters, *cid, *c, layer_of (metal), db::ICplxTrans (), rmetal, 0);
double agate = rgate.area () * dbu * dbu;
if (fabs (gate_perimeter_factor) > 1e-6) {
agate += rgate.perimeter () * dbu * gate_perimeter_factor;
}
double ametal = rmetal.area () * dbu * dbu;
if (fabs (metal_perimeter_factor) > 1e-6) {
ametal += rmetal.perimeter () * dbu * metal_perimeter_factor;
}
double r = ratio;
bool skip = false;

View File

@ -730,6 +730,14 @@ public:
* the limit ratio all metal shapes are copied to the output region as
* error markers.
*
* The area computation of gate and metal happens by taking the polygon
* area (A) and perimeter (P) into account:
*
* A(antenna) = A + P * f
*
* where f is the perimeter factor. The unit of the area factor is
* micrometers.
*
* The limit ratio can be modified by the presence of connections to
* other layers (specifically designating diodes for charge removal).
* Each of these layers will modify the ratio by adding a value of
@ -742,7 +750,16 @@ public:
* regardless of the diode's area.
* In other words: any diode will make the net safe against antenna discharge.
*/
db::Region antenna_check (const db::Region &gate, const db::Region &metal, double ratio, const std::vector<std::pair<const db::Region *, double> > &diodes = std::vector<std::pair<const db::Region *, double> > ());
db::Region antenna_check (const db::Region &gate, double gate_perimeter_factor, const db::Region &metal, double metal_perimeter_factor, double ratio, const std::vector<std::pair<const db::Region *, double> > &diodes = std::vector<std::pair<const db::Region *, double> > ());
/**
* @brief Variant of the antennna check not using the perimeter
* This version uses 0 for the perimeter factor hence not taking into account the perimeter at all.
*/
db::Region antenna_check (const db::Region &gate, const db::Region &metal, double ratio, const std::vector<std::pair<const db::Region *, double> > &diodes = std::vector<std::pair<const db::Region *, double> > ())
{
return antenna_check (gate, 0.0, metal, 0.0, ratio, diodes);
}
/**
* @brief Saves the database to the given path

View File

@ -98,7 +98,7 @@ static std::vector<std::string> l2n_layer_names (const db::LayoutToNetlist *l2n)
return ln;
}
static db::Region antenna_check (db::LayoutToNetlist *l2n, const db::Region &poly, const db::Region &metal, double ratio, const std::vector<tl::Variant> &diodes)
static db::Region antenna_check2 (db::LayoutToNetlist *l2n, const db::Region &poly, double poly_perimeter_factor, const db::Region &metal, double metal_perimeter_factor, double ratio, const std::vector<tl::Variant> &diodes)
{
std::vector<std::pair<const db::Region *, double> > diode_pairs;
@ -127,7 +127,12 @@ static db::Region antenna_check (db::LayoutToNetlist *l2n, const db::Region &pol
}
return l2n->antenna_check (poly, metal, ratio, diode_pairs);
return l2n->antenna_check (poly, poly_perimeter_factor, metal, metal_perimeter_factor, ratio, diode_pairs);
}
static db::Region antenna_check (db::LayoutToNetlist *l2n, const db::Region &poly, const db::Region &metal, double ratio, const std::vector<tl::Variant> &diodes)
{
return antenna_check2 (l2n, poly, 0, metal, 0, ratio, diodes);
}
Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
@ -609,6 +614,23 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
"# diode_layer1 increases the ratio by 50 per sqaure micrometer area:\n"
"errors = l2n.antenna(poly, metal, 10.0 [ [ diode_layer, 50.0 ] ])\n"
"@/code\n"
) +
gsi::method_ext ("antenna_check", &antenna_check2, gsi::arg ("gate"), gsi::arg ("gate_perimeter_factor"), gsi::arg ("metal"), gsi::arg ("metal_perimeter_factor"), gsi::arg ("ratio"), gsi::arg ("diodes", std::vector<tl::Variant> (), "[]"),
"@brief Runs an antenna check on the extracted clusters taking the perimeter into account\n"
"\n"
"This version of the \\antenna_check method allows taking the perimeter of gate or metal into account. "
"The effective area is computed using:\n"
"\n"
"@code\n"
"Aeff = A + P * f\n"
"@/code\n"
"\n"
"Here Aeff is the area used in the check, A is the polygon area, P the perimeter and f the perimeter factor. "
"This formula applies to gate polygon area/perimeter with 'gate_perimeter_factor' for f and metal polygon area/perimeter "
"with 'metal_perimeter_factor'. The perimeter_factor has the dimension of micrometers and can be thought of as the width "
"of the material. Essentially the side walls of the material are taking into account for the surface area as well.\n"
"\n"
"This variant has been introduced in version 0.26.6.\n"
),
"@brief A generic framework for extracting netlists from layouts\n"
"\n"

View File

@ -2609,6 +2609,71 @@ TEST(10_Antenna)
a4_30.insert_into (&ly2, top2.cell_index (), ly2.insert_layer (db::LayerProperties (402, 0)));
}
{
db::LayoutToNetlist l2n (&dss);
l2n.register_layer (*rpoly, "poly");
l2n.register_layer (*rcont, "cont");
l2n.register_layer (*rmetal1, "metal1");
l2n.register_layer (*rvia1, "via1");
l2n.register_layer (*rmetal2, "metal2");
// Intra-layer
l2n.connect (*rpoly);
l2n.connect (*rcont);
l2n.connect (*rmetal1);
l2n.connect (*rvia1);
l2n.connect (*rmetal2);
// Inter-layer
l2n.connect (*rpoly, *rcont);
l2n.connect (*rcont, *rmetal1);
l2n.connect (*rmetal1, *rvia1);
l2n.connect (*rvia1, *rmetal2);
l2n.extract_netlist ();
db::Region a5_5 = l2n.antenna_check (*rpoly, 0.0, *rmetal2, 1.0, 5);
db::Region a5_15 = l2n.antenna_check (*rpoly, 0.0, *rmetal2, 1.0, 15);
db::Region a5_29 = l2n.antenna_check (*rpoly, 0.0, *rmetal2, 1.0, 29);
a5_5.insert_into (&ly2, top2.cell_index (), ly2.insert_layer (db::LayerProperties (500, 0)));
a5_15.insert_into (&ly2, top2.cell_index (), ly2.insert_layer (db::LayerProperties (501, 0)));
a5_29.insert_into (&ly2, top2.cell_index (), ly2.insert_layer (db::LayerProperties (502, 0)));
}
{
db::LayoutToNetlist l2n (&dss);
l2n.register_layer (*rpoly, "poly");
l2n.register_layer (*rcont, "cont");
l2n.register_layer (*rmetal1, "metal1");
l2n.register_layer (*rvia1, "via1");
l2n.register_layer (*rmetal2, "metal2");
// Intra-layer
l2n.connect (*rpoly);
l2n.connect (*rcont);
l2n.connect (*rmetal1);
l2n.connect (*rvia1);
l2n.connect (*rmetal2);
// Inter-layer
l2n.connect (*rpoly, *rcont);
l2n.connect (*rcont, *rmetal1);
l2n.connect (*rmetal1, *rvia1);
l2n.connect (*rvia1, *rmetal2);
l2n.extract_netlist ();
db::Region a6_3 = l2n.antenna_check (*rpoly, 0.3, *rmetal2, 0.0, 3);
db::Region a6_5 = l2n.antenna_check (*rpoly, 0.3, *rmetal2, 0.0, 5);
db::Region a6_9 = l2n.antenna_check (*rpoly, 0.3, *rmetal2, 0.0, 9);
a6_3.insert_into (&ly2, top2.cell_index (), ly2.insert_layer (db::LayerProperties (600, 0)));
a6_5.insert_into (&ly2, top2.cell_index (), ly2.insert_layer (db::LayerProperties (601, 0)));
a6_9.insert_into (&ly2, top2.cell_index (), ly2.insert_layer (db::LayerProperties (602, 0)));
}
std::string au = tl::testsrc ();
au = tl::combine_path (au, "testdata");
au = tl::combine_path (au, "algo");

View File

@ -121,6 +121,14 @@ module DRC
DRCAsDots::new(false)
end
def area_only(r)
DRCAreaAndPerimeter::new(r, 0.0)
end
def area_and_perimeter(r, f)
DRCAreaAndPerimeter::new(r, f)
end
# %DRC%
# @brief Defines SPICE output format (with options)
# @name write_spice
@ -1767,6 +1775,29 @@ CODE
@l2ndb_index = i
end
def _prep_value(a)
if a.is_a?(RBA::DPoint)
RBA::Point::from_dpoint(a * (1.0 / self.dbu.to_f))
elsif a.is_a?(RBA::DCplxTrans)
RBA::ICplxTrans::from_dtrans(RBA::DCplxTrans::new(1.0 / self.dbu.to_f) * a * RBA::DCplxTrans::new(self.dbu.to_f))
elsif a.is_a?(RBA::DTrans)
RBA::ICplxTrans::from_dtrans(RBA::DCplxTrans::new(1.0 / self.dbu.to_f) * RBA::DCplxTrans::new(a) * RBA::DCplxTrans::new(self.dbu.to_f))
elsif a.is_a?(Float)
(0.5 + a / self.dbu).floor.to_i
else
a
end
end
def _prep_value_area(a)
dbu2 = self.dbu.to_f * self.dbu.to_f
if a.is_a?(Float)
(0.5 + a / dbu2).floor.to_i
else
a
end
end
private
def _make_string(v)

View File

@ -253,12 +253,12 @@ module DRC
if args.size == 1
a = args[0]
if a.is_a?(Range)
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, prep_value_area(a.first), prep_value_area(a.last), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, @engine._prep_value_area(a.first), @engine._prep_value_area(a.last), #{inv.inspect}))
else
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, prep_value_area(a), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, @engine._prep_value_area(a), #{inv.inspect}))
end
elsif args.size == 2
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, prep_value_area(args[0]), prep_value_area(args[1]), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, @engine._prep_value_area(args[0]), @engine._prep_value_area(args[1]), #{inv.inspect}))
else
raise("Invalid number of arguments for method '#{mn}'")
end
@ -403,12 +403,12 @@ CODE
if args.size == 1
a = args[0]
if a.is_a?(Range)
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, prep_value(a.first), prep_value(a.last), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, @engine._prep_value(a.first), @engine._prep_value(a.last), #{inv.inspect}))
else
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, prep_value(a), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, @engine._prep_value(a), #{inv.inspect}))
end
elsif args.size == 2
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, prep_value(args[0]), prep_value(args[1]), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :with_#{f}, @engine._prep_value(args[0]), @engine._prep_value(args[1]), #{inv.inspect}))
else
raise("Invalid number of arguments for method '#{mn}'")
end
@ -452,12 +452,12 @@ CODE
if args.size == 1
a = args[0]
if a.is_a?(Range)
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Edges, :with_#{f}, prep_value(a.first), prep_value(a.last), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Edges, :with_#{f}, @engine._prep_value(a.first), @engine._prep_value(a.last), #{inv.inspect}))
else
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Edges, :with_#{f}, prep_value(a), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Edges, :with_#{f}, @engine._prep_value(a), #{inv.inspect}))
end
elsif args.size == 2
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Edges, :with_#{f}, prep_value(args[0]), prep_value(args[1]), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Edges, :with_#{f}, @engine._prep_value(args[0]), @engine._prep_value(args[1]), #{inv.inspect}))
else
raise("Invalid number of arguments for method '#{mn}'")
end
@ -562,7 +562,7 @@ CODE
def rounded_corners(inner, outer, n)
requires_region("rounded_corners")
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :rounded_corners, prep_value(inner), prep_value(outer), n))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :rounded_corners, @engine._prep_value(inner), @engine._prep_value(outer), n))
end
# %DRC%
@ -579,7 +579,7 @@ CODE
def smoothed(d)
requires_region("smoothed")
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :smoothed, prep_value(d)))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :smoothed, @engine._prep_value(d)))
end
# %DRC%
@ -1106,9 +1106,9 @@ CODE
def ongrid(*args)
requires_region("ongrid")
if args.size == 1
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::EdgePairs, :grid_check, prep_value(args[0]), prep_value(args[0])))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::EdgePairs, :grid_check, @engine._prep_value(args[0]), @engine._prep_value(args[0])))
elsif args.size == 2
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::EdgePairs, :grid_check, prep_value(args[0]), prep_value(args[1])))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::EdgePairs, :grid_check, @engine._prep_value(args[0]), @engine._prep_value(args[1])))
else
raise("Invalid number of arguments for method 'ongrid'")
end
@ -1143,14 +1143,14 @@ CODE
requires_region("#{f}")
gx = gy = 0
if args.size == 1
gx = gy = prep_value(args[0])
gx = gy = @engine._prep_value(args[0])
elsif args.size == 2
gx = prep_value(args[0])
gy = prep_value(args[1])
gx = @engine._prep_value(args[0])
gy = @engine._prep_value(args[1])
else
raise("Invalid number of arguments for method 'ongrid'")
end
aa = args.collect { |a| prep_value(a) }
aa = args.collect { |a| @engine._prep_value(a) }
if :#{f} == :snap && @engine.is_tiled?
# in tiled mode, no modifying versions are available
@data = @engine._tcmd(@data, 0, @data.class, :snapped, gx, gy)
@ -2039,7 +2039,7 @@ CODE
eval <<"CODE"
def #{f}(length, fraction = 0.0)
requires_edges("#{f}")
length = prep_value(length)
length = @engine._prep_value(length)
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Edges, :#{f}, length, fraction))
end
CODE
@ -2087,16 +2087,16 @@ CODE
av = [ 0, 0, 0, 0, false ]
args.each_with_index do |a,i|
if a.is_a?(Hash)
a[:begin] && av[0] = prep_value(a[:begin])
a[:end] && av[1] = prep_value(a[:end])
a[:out] && av[2] = prep_value(a[:out])
a[:in] && av[3] = prep_value(a[:in])
a[:begin] && av[0] = @engine._prep_value(a[:begin])
a[:end] && av[1] = @engine._prep_value(a[:end])
a[:out] && av[2] = @engine._prep_value(a[:out])
a[:in] && av[3] = @engine._prep_value(a[:in])
a[:joined] && av[4] = true
elsif i < 4
if !a.is_a?(1.class) && !a.is_a?(Float)
raise("Invalid type for argument " + (i+1).to_s + " (method '#{f}')")
end
av[i] = prep_value(a)
av[i] = @engine._prep_value(a)
elsif i == 4
if a.is_a?(DRCJoinFlag)
av[i] = a.value
@ -2142,7 +2142,7 @@ CODE
eval <<"CODE"
def #{f}(dist)
requires_edges("#{f}")
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :#{f}, prep_value(dist)))
DRCLayer::new(@engine, @engine._tcmd(@data, 0, RBA::Region, :#{f}, @engine._prep_value(dist)))
end
CODE
end
@ -2635,11 +2635,11 @@ CODE
elsif a.is_a?(DRCLayer)
other = a
elsif a.is_a?(DRCProjectionLimits)
minp = prep_value(a.min)
maxp = prep_value(a.max)
minp = @engine._prep_value(a.min)
maxp = @engine._prep_value(a.max)
elsif a.is_a?(Float) || a.is_a?(1.class)
value && raise("Value already specified")
value = prep_value(a)
value = @engine._prep_value(a)
else
raise("#{f}: Parameter #" + n.to_s + " does not have an expected type")
end
@ -2694,11 +2694,11 @@ CODE
elsif a.is_a?(DRCLayer)
other = a
elsif a.is_a?(DRCProjectionLimits)
minp = prep_value(a.min)
maxp = prep_value(a.max)
minp = @engine._prep_value(a.min)
maxp = @engine._prep_value(a.max)
elsif a.is_a?(Float) || a.is_a?(1.class)
value && raise("Value already specified")
value = prep_value(a)
value = @engine._prep_value(a)
else
raise("#{f}: Parameter #" + n.to_s + " does not have an expected type")
end
@ -2868,7 +2868,7 @@ CODE
values = []
args.each do |a|
if a.is_a?(1.class) || a.is_a?(Float)
v = prep_value(a)
v = @engine._prep_value(a)
v.abs > dist && dist = v.abs
values.push(v)
elsif a.is_a?(DRCSizingMode)
@ -2918,7 +2918,7 @@ CODE
def polygons(*args)
requires_edge_pairs("polygons")
args.size <= 1 || raise("polygons: Method requires 0 or 1 arguments")
aa = args.collect { |a| prep_value(a) }
aa = args.collect { |a| @engine._prep_value(a) }
DRCLayer::new(@engine, @engine._cmd(@data, :polygons, *aa))
end
@ -3007,7 +3007,7 @@ CODE
%w(extents moved transformed).each do |f|
eval <<"CODE"
def #{f}(*args)
aa = args.collect { |a| prep_value(a) }
aa = args.collect { |a| @engine._prep_value(a) }
DRCLayer::new(@engine, @engine._cmd(@data, :#{f}, *aa))
end
CODE
@ -3016,7 +3016,7 @@ CODE
%w(move transform).each do |f|
eval <<"CODE"
def #{f}(*args)
aa = args.collect { |a| prep_value(a) }
aa = args.collect { |a| @engine._prep_value(a) }
@engine._cmd(@data, :#{f}, *aa)
self
end
@ -3062,13 +3062,13 @@ CODE
def merged(*args)
requires_edges_or_region("merged")
aa = args.collect { |a| prep_value(a) }
aa = args.collect { |a| @engine._prep_value(a) }
DRCLayer::new(@engine, @engine._tcmd(@data, 0, @data.class, :merged, *aa))
end
def merge(*args)
requires_edges_or_region("merge")
aa = args.collect { |a| prep_value(a) }
aa = args.collect { |a| @engine._prep_value(a) }
if @engine.is_tiled?
# in tiled mode, no modifying versions are available
@data = @engine._tcmd(@data, 0, @data.class, :merged, *aa)
@ -3172,29 +3172,6 @@ CODE
end
end
def prep_value(a)
if a.is_a?(RBA::DPoint)
RBA::Point::from_dpoint(a * (1.0 / @engine.dbu.to_f))
elsif a.is_a?(RBA::DCplxTrans)
RBA::ICplxTrans::from_dtrans(RBA::DCplxTrans::new(1.0 / @engine.dbu.to_f) * a * RBA::DCplxTrans::new(@engine.dbu.to_f))
elsif a.is_a?(RBA::DTrans)
RBA::ICplxTrans::from_dtrans(RBA::DCplxTrans::new(1.0 / @engine.dbu.to_f) * RBA::DCplxTrans::new(a) * RBA::DCplxTrans::new(@engine.dbu.to_f))
elsif a.is_a?(Float)
(0.5 + a / @engine.dbu).floor.to_i
else
a
end
end
def prep_value_area(a)
dbu2 = @engine.dbu.to_f * @engine.dbu.to_f
if a.is_a?(Float)
(0.5 + a / dbu2).floor.to_i
else
a
end
end
end
end

View File

@ -336,16 +336,74 @@ module DRC
# Multiple diode specifications are allowed. Just add them
# to the antenna_check call.
#
# You can include the perimeter into the area computation for
# the gate or metal layer or both. The physical picture
# is this: the side walls of the material contribute to the
# surface too. As the side wall area can be estimated by taking
# the perimeter times some material thickness, the effective
# area is:
#
# @code
# A(eff) = A + P * t
# @/code
#
# Here A is the area of the polygons and P is their perimeter.
# t is the "thickness" in micrometer units. To specify such
# a condition, use the following notation:
#
# @code
# errors = antenna_check(area_and_perimeter(gate, 0.5), ...)
# @/code
#
# "area_and_perimeter" takes the polygon layer and the
# thickness (0.5 micrometers in this case).
# This notation can be applied to both gate and
# metal layers. A detailed notation for the usual,
# area-only case is available as well for completeness:
#
# @code
# errors = antenna_check(area_only(gate), ...)
#
# # this is equivalent to a zero thickness:
# errors = antenna_check(area_and_perimeter(gate, 0.0), ...)
# # or the standard case:
# errors = antenna_check(gate, ...)
# @/code
#
# The error shapes produced by the antenna check are copies
# of the metal shapes on the metal layers of each network
# violating the antenna rule.
def antenna_check(gate, metal, ratio, *diodes)
def antenna_check(agate, ametal, ratio, *diodes)
gate_perimeter_factor = 0.0
if agate.is_a?(DRC::DRCLayer)
gate = agate
elsif agate.is_a?(DRC::DRCAreaAndPerimeter)
gate = agate.region
gate_perimeter_factor = agate.perimeter_factor
if ! gate.is_a?(DRC::DRCLayer)
raise("gate with area or area_and_perimeter: input argument must be a layer")
end
else
raise("gate argument of Netter#antenna_check must be a layer ")
end
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_perimeter_factor = 0.0
if ametal.is_a?(DRC::DRCLayer)
metal = ametal
elsif ametal.is_a?(DRC::DRCAreaAndPerimeter)
metal = ametal.region
metal_perimeter_factor = ametal.perimeter_factor
if ! metal.is_a?(DRC::DRCLayer)
raise("metal with area or area_and_perimeter: input argument must be a layer")
end
else
raise("metal argument of Netter#antenna_check must be a layer")
end
metal.requires_region("Netter#antenna_check (metal argument)")
if !ratio.is_a?(1.class) && !ratio.is_a?(Float)
@ -363,7 +421,7 @@ module DRC
end
end
DRC::DRCLayer::new(@engine, @engine._cmd(l2n_data, :antenna_check, gate.data, metal.data, ratio, dl))
DRC::DRCLayer::new(@engine, @engine._cmd(l2n_data, :antenna_check, gate.data, gate_perimeter_factor, metal.data, metal_perimeter_factor, ratio, dl))
end

View File

@ -112,5 +112,17 @@ module DRC
end
end
# A wrapper for an input for the antenna check
# This class is used to identify a region plus an
# optional perimeter factor
class DRCAreaAndPerimeter
attr_accessor :region
attr_accessor :perimeter_factor
def initialize(r, f)
self.region = r
self.perimeter_factor = f
end
end
end

View File

@ -133,6 +133,40 @@ errors = antenna_check(gate, metal1, 50.0, [ diode, 10.0 ])
Multiple diode specifications are allowed. Just add them
to the antenna_check call.
</p><p>
You can include the perimeter into the area computation for
the gate or metal layer or both. The physical picture
is this: the side walls of the material contribute to the
surface too. As the side wall area can be estimated by taking
the perimeter times some material thickness, the effective
area is:
</p><p>
<pre>
A(eff) = A + P * t
</pre>
</p><p>
Here A is the area of the polygons and P is their perimeter.
t is the "thickness" in micrometer units. To specify such
a condition, use the following notation:
</p><p>
<pre>
errors = antenna_check(area_and_perimeter(gate, 0.5), ...)
</pre>
</p><p>
"area_and_perimeter" takes the polygon layer and the
thickness (0.5 micrometers in this case).
This notation can be applied to both gate and
metal layers. A detailed notation for the usual,
area-only case is available as well for completeness:
</p><p>
<pre>
errors = antenna_check(area_only(gate), ...)
# this is equivalent to a zero thickness:
errors = antenna_check(area_and_perimeter(gate, 0.0), ...)
# or the standard case:
errors = antenna_check(gate, ...)
</pre>
</p><p>
The error shapes produced by the antenna check are copies
of the metal shapes on the metal layers of each network
violating the antenna rule.

Binary file not shown.

View File

@ -26,6 +26,8 @@ connect(metal1, via1)
connect(via1, metal2)
antenna_check(gate, metal2, 1.0, diode).output(101)
antenna_check(gate, metal2, 1.5, diode).output(102)
antenna_check(gate, metal2, 2.0, diode).output(103)
antenna_check(gate, metal2, 5.0, diode).output(105)
antenna_check(gate, metal2, 10.0, diode).output(110)
antenna_check(gate, metal2, 50.0, diode).output(150)
@ -35,3 +37,18 @@ antenna_check(gate, metal2, 1.0, [ diode, 5.0 ]).output(202)
antenna_check(gate, metal2, 5.0, [ diode, 5.0 ]).output(205)
antenna_check(gate, metal2, 10.0, [ diode, 5.0 ]).output(210)
antenna_check(gate, metal2, 50.0, [ diode, 5.0 ]).output(250)
antenna_check(area_only(gate), area_and_perimeter(metal2, 0.2.um), 1.0, diode).output(301)
antenna_check(area_only(gate), area_and_perimeter(metal2, 0.2.um), 1.5, diode).output(302)
antenna_check(area_only(gate), area_and_perimeter(metal2, 0.2.um), 2.0, diode).output(303)
antenna_check(area_only(gate), area_and_perimeter(metal2, 0.2), 5.0, diode).output(305)
antenna_check(area_only(gate), area_and_perimeter(metal2, 0.0002.mm), 10.0, diode).output(310)
antenna_check(area_only(gate), area_and_perimeter(metal2, 200.nm), 50.0, diode).output(350)
antenna_check(area_and_perimeter(gate, 0.07.um), area_only(metal2), 1.0, diode).output(401)
antenna_check(area_and_perimeter(gate, 0.07.um), area_only(metal2), 1.5, diode).output(402)
antenna_check(area_and_perimeter(gate, 0.07.um), area_only(metal2), 2.0, diode).output(403)
antenna_check(area_and_perimeter(gate, 70.nm), area_only(metal2), 5.0, diode).output(405)
antenna_check(area_and_perimeter(gate, 0.07), area_only(metal2), 10.0, diode).output(410)
antenna_check(area_and_perimeter(gate, 0.07), area_only(metal2), 50.0, diode).output(450)

Binary file not shown.

View File

@ -757,7 +757,7 @@ END
l2n.extract_netlist
a4_3 = l2n.antenna_check(rpoly, rmetal1, 3, [ rdiode ] )
a4_3 = l2n.antenna_check(rpoly, rmetal1, 3, [ rdiode ])
a4_10 = l2n.antenna_check(rpoly, rmetal1, 10, [ rdiode ])
a4_30 = l2n.antenna_check(rpoly, rmetal1, 30, [ rdiode ])
@ -766,6 +766,70 @@ END
assert_equal((a4_10.flatten ^ RBA::Region::new(ly_au.top_cell.begin_shapes_rec(ly_au.layer(401, 0)))).to_s, "")
assert_equal((a4_30.flatten ^ RBA::Region::new(ly_au.top_cell.begin_shapes_rec(ly_au.layer(402, 0)))).to_s, "")
# --- antenna check metal perimeter included
l2n._destroy
l2n = RBA::LayoutToNetlist::new(dss)
l2n.register(rpoly, "poly")
l2n.register(rcont, "cont")
l2n.register(rmetal1, "metal1")
l2n.register(rvia1, "via1")
l2n.register(rmetal2, "metal2")
l2n.connect(rpoly)
l2n.connect(rcont)
l2n.connect(rmetal1)
l2n.connect(rvia1)
l2n.connect(rmetal2)
l2n.connect(rpoly, rcont)
l2n.connect(rcont, rmetal1)
l2n.connect(rmetal1, rvia1)
l2n.connect(rvia1, rmetal2)
l2n.extract_netlist
a5_5 = l2n.antenna_check(rpoly, 0.0, rmetal2, 1.0, 5)
a5_15 = l2n.antenna_check(rpoly, 0.0, rmetal2, 1.0, 15)
a5_29 = l2n.antenna_check(rpoly, 0.0, rmetal2, 1.0, 29)
# Note: flatten.merged performs some normalization
assert_equal((a5_5.flatten ^ RBA::Region::new(ly_au.top_cell.begin_shapes_rec(ly_au.layer(500, 0)))).to_s, "")
assert_equal((a5_15.flatten ^ RBA::Region::new(ly_au.top_cell.begin_shapes_rec(ly_au.layer(501, 0)))).to_s, "")
assert_equal((a5_29.flatten ^ RBA::Region::new(ly_au.top_cell.begin_shapes_rec(ly_au.layer(502, 0)))).to_s, "")
# --- antenna check gate perimeter included
l2n._destroy
l2n = RBA::LayoutToNetlist::new(dss)
l2n.register(rpoly, "poly")
l2n.register(rcont, "cont")
l2n.register(rmetal1, "metal1")
l2n.register(rvia1, "via1")
l2n.register(rmetal2, "metal2")
l2n.connect(rpoly)
l2n.connect(rcont)
l2n.connect(rmetal1)
l2n.connect(rvia1)
l2n.connect(rmetal2)
l2n.connect(rpoly, rcont)
l2n.connect(rcont, rmetal1)
l2n.connect(rmetal1, rvia1)
l2n.connect(rvia1, rmetal2)
l2n.extract_netlist
a6_3 = l2n.antenna_check(rpoly, 0.3, rmetal2, 0.0, 3)
a6_5 = l2n.antenna_check(rpoly, 0.3, rmetal2, 0.0, 5)
a6_9 = l2n.antenna_check(rpoly, 0.3, rmetal2, 0.0, 9)
# Note: flatten.merged performs some normalization
assert_equal((a6_3.flatten ^ RBA::Region::new(ly_au.top_cell.begin_shapes_rec(ly_au.layer(600, 0)))).to_s, "")
assert_equal((a6_5.flatten ^ RBA::Region::new(ly_au.top_cell.begin_shapes_rec(ly_au.layer(601, 0)))).to_s, "")
assert_equal((a6_9.flatten ^ RBA::Region::new(ly_au.top_cell.begin_shapes_rec(ly_au.layer(602, 0)))).to_s, "")
end
end