mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 12:46:08 +01:00
1052 lines
42 KiB
XML
1052 lines
42 KiB
XML
<?xml version='1.0' encoding='utf-8'?>
|
|
<!--
|
|
Copyright 2024 IHP PDK Authors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
-->
|
|
<klayout-macro>
|
|
<description />
|
|
<version />
|
|
<category>drc</category>
|
|
<prolog />
|
|
<epilog />
|
|
<doc />
|
|
<autorun>false</autorun>
|
|
<autorun-early>false</autorun-early>
|
|
<shortcut />
|
|
<show-in-menu>true</show-in-menu>
|
|
<group-name>drc_scripts</group-name>
|
|
<menu-path>tools_menu.drc.end</menu-path>
|
|
<interpreter>dsl</interpreter>
|
|
<dsl-interpreter-name>drc-dsl-xml</dsl-interpreter-name>
|
|
<text># Supported variables that can be set using "-rd <name>=<value>" on the command line:
|
|
# log_file - path to the log file [default: no log file]
|
|
# in_gds - path to the GDS layout to check (required in batch mode)
|
|
# cell - name of the cell to check
|
|
# report_file - path to the report database [default: sg13g2_minimal.lyrdb in the script directory]
|
|
|
|
# to set logfile: -rd logfile="sg13g2_minimal.log"
|
|
if $log_file
|
|
log_file($log_file)
|
|
end
|
|
|
|
application = RBA::Application.instance
|
|
main_window = application.main_window
|
|
if main_window
|
|
curr_layout_view = main_window.current_view()
|
|
unless curr_layout_view
|
|
layout_path = RBA::FileDialog::ask_open_file_name("Chose your layout file.", ".", "GDSII files (*.GDS *.gds *.GDS.gz *.gds.gz *.GDS2 *.gds2 *.GDS2.gz *.gds2.gz);; All files (*)")
|
|
main_window.load_layout(layout_path, 1)
|
|
curr_layout_view = main_window.current_view()
|
|
end
|
|
active_layout = RBA::CellView::active.layout
|
|
active_cellname = RBA::CellView::active.cell_name
|
|
source(active_layout, active_cellname)
|
|
else
|
|
log("DRC: batch mode")
|
|
# to set input layout: -rd in_gds="path to GDS file"
|
|
# to set cell: -rd cell="topcell"
|
|
if $cell
|
|
active_cellname = $cell
|
|
log("Active cell: " + active_cellname)
|
|
source($in_gds, active_cellname)
|
|
active_layout = source.layout
|
|
else
|
|
source($in_gds)
|
|
active_layout = source.layout
|
|
active_cellname = source.cell_name
|
|
end
|
|
end
|
|
|
|
unless active_layout or $in_gds
|
|
raise("layout file must be defined on command line or via 'in_gds' script variable")
|
|
end
|
|
|
|
if active_layout.dbu != 0.001
|
|
puts "WARNING: Layout dbu value (" + active_layout.dbu.to_s + " ) deviates from rule file dbu value (0.001). This will scale the layout and may not be intended."
|
|
end
|
|
|
|
report_file = __dir__ + "/sg13g2_minimal.lyrdb"
|
|
# to set report file: -rd report_file="sg13g2_minimal.lyrdb"
|
|
if $report_file
|
|
report_file = File.expand_path($report_file)
|
|
end
|
|
|
|
report("design rules: sg13g2_minimal | layout cell: " + active_cellname, report_file)
|
|
|
|
deep
|
|
|
|
$drc_error_count = 0
|
|
|
|
class DRC::DRCLayer
|
|
unless method_defined?(:original_output)
|
|
alias_method :original_output, :output
|
|
end
|
|
|
|
def output(*args)
|
|
$drc_error_count += self.count()
|
|
original_output(*args)
|
|
end
|
|
|
|
def ext_merged(min_coherence, overlap_count)
|
|
args = [min_coherence, overlap_count]
|
|
return DRCLayer::new(@engine, @engine._tcmd(self.data, 0, self.data.class, :merged, *args))
|
|
end
|
|
|
|
def ext_merge(min_coherence, overlap_count)
|
|
args = [min_coherence, overlap_count]
|
|
if @engine.is_tiled?
|
|
self.data = @engine._tcmd(self.data, 0, self.data.class, :merged, *args)
|
|
else
|
|
@engine._tcmd(self.data, 0, self.data.class, :merge, *args)
|
|
end
|
|
return self
|
|
end
|
|
end
|
|
|
|
# Initial definitions of control flow variables
|
|
# Strings from the command line have to be converted
|
|
if defined? $density
|
|
$density = $density.to_s.downcase == "true"
|
|
else
|
|
$density = true
|
|
end
|
|
if defined? $sanityRules
|
|
$sanityRules = $sanityRules.to_s.downcase == "true"
|
|
else
|
|
$sanityRules = true
|
|
end
|
|
|
|
class DRC::DRCEngine
|
|
def space_width_separation_overlap_check(value,
|
|
error_edge_pairs_90,
|
|
error_edge_pairs_180,
|
|
inverse_error_edge_pairs_90 = nil,
|
|
inverse_error_edge_pairs_180 = nil,
|
|
options = {})
|
|
consider_intersecting_edges = options.fetch(:consider_intersecting_edges, false)
|
|
consider_touch_points = options.fetch(:consider_touch_points, false)
|
|
ignore_non_axis_aligned_edges = options.fetch(:ignore_non_axis_aligned_edges, false)
|
|
min_angle = options.fetch(:min_angle, 0)
|
|
max_angle = options.fetch(:max_angle, 90)
|
|
include_min_angle = options.fetch(:include_min_angle, true)
|
|
include_max_angle = options.fetch(:include_max_angle, false)
|
|
area_of_right_angle = value**2/2
|
|
errors_ep = RBA::EdgePairs::new()
|
|
touch_point_errors_ep = RBA::EdgePairs::new()
|
|
intersecting_edges_errors_ep = RBA::EdgePairs::new()
|
|
intersecting_edges_error_candidates = Hash.new()
|
|
no_touch_point_error = Hash.new()
|
|
error_edge_pairs_90.data.each do |edge_pair|
|
|
ip = nil
|
|
if edge_pair.first.p1 == edge_pair.second.p1 or
|
|
edge_pair.first.p1 == edge_pair.second.p2
|
|
ip = edge_pair.first.p1
|
|
elsif edge_pair.first.p2 == edge_pair.second.p1 or
|
|
edge_pair.first.p2 == edge_pair.second.p2
|
|
ip = edge_pair.first.p2
|
|
end
|
|
if ip
|
|
intersecting_edges_error_candidates[ip] = edge_pair
|
|
if !edge_pair.first.is_degenerate? and !edge_pair.second.is_degenerate?
|
|
if (edge_pair.first.contains?(edge_pair.second.p1) and
|
|
edge_pair.first.contains?(edge_pair.second.p2)) or
|
|
(edge_pair.second.contains?(edge_pair.first.p1) and
|
|
edge_pair.second.contains?(edge_pair.first.p2))
|
|
no_touch_point_error[ip] = true
|
|
end
|
|
end
|
|
else
|
|
errors_ep.insert(edge_pair)
|
|
end
|
|
end
|
|
if consider_intersecting_edges or consider_touch_points
|
|
touch_point_candidates = Hash.new()
|
|
touch_point_errors = Hash.new()
|
|
if inverse_error_edge_pairs_90 and inverse_error_edge_pairs_180
|
|
(inverse_error_edge_pairs_90 + inverse_error_edge_pairs_180).data.each do |edge_pair|
|
|
ip = edge_pair.first.intersection_point(edge_pair.second)
|
|
if ip
|
|
if touch_point_candidates[ip]
|
|
touch_point_errors[ip] = true
|
|
touch_point_candidates.delete(ip)
|
|
elsif !no_touch_point_error[ip]
|
|
touch_point_candidates[ip] = edge_pair
|
|
end
|
|
end
|
|
end
|
|
end
|
|
touch_point_candidates = Hash.new()
|
|
(error_edge_pairs_90 + error_edge_pairs_180).data.each do |edge_pair|
|
|
ip = nil
|
|
if edge_pair.first.p1 == edge_pair.second.p1 or
|
|
edge_pair.first.p1 == edge_pair.second.p2
|
|
ip = edge_pair.first.p1
|
|
elsif edge_pair.first.p2 == edge_pair.second.p1 or
|
|
edge_pair.first.p2 == edge_pair.second.p2
|
|
ip = edge_pair.first.p2
|
|
end
|
|
if ip
|
|
if edge_pair.area == area_of_right_angle or max_angle == 180
|
|
intersecting_edges_error_candidates[ip] = edge_pair
|
|
end
|
|
if touch_point_errors[ip]
|
|
touch_point_errors_ep.insert(edge_pair)
|
|
intersecting_edges_error_candidates.delete(ip)
|
|
elsif touch_point_candidates[ip]
|
|
touch_point_errors_ep.insert(edge_pair)
|
|
touch_point_errors_ep.insert(touch_point_candidates[ip])
|
|
touch_point_candidates.delete(ip)
|
|
intersecting_edges_error_candidates.delete(ip)
|
|
elsif !no_touch_point_error[ip]
|
|
touch_point_candidates[ip] = edge_pair
|
|
end
|
|
end
|
|
end
|
|
if consider_intersecting_edges
|
|
intersecting_edges_errors_ep = RBA::EdgePairs::new(intersecting_edges_error_candidates.values)
|
|
if max_angle != 180
|
|
intersecting_edges_errors_ep = intersecting_edges_errors_ep.with_internal_angle(min_angle, max_angle, false, include_min_angle, include_max_angle)
|
|
end
|
|
errors_ep = errors_ep + intersecting_edges_errors_ep
|
|
end
|
|
end
|
|
if ignore_non_axis_aligned_edges
|
|
errors_ep = errors_ep.with_angle_both(RBA::Edges::OrthoEdges, false)
|
|
end
|
|
if consider_touch_points
|
|
errors_ep = errors_ep + touch_point_errors_ep
|
|
end
|
|
return DRC::DRCLayer::new(self, errors_ep)
|
|
end
|
|
end
|
|
|
|
class DRC::DRCLayer
|
|
def ext_and(other)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
other_min_coherence_state = other.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
other.data.min_coherence = true
|
|
output_layer = self & other
|
|
self.data.min_coherence = self_min_coherence_state
|
|
other.data.min_coherence = other_min_coherence_state
|
|
return output_layer
|
|
end
|
|
|
|
def ext_with_area(constraint)
|
|
lower_bound = nil
|
|
upper_bound = nil
|
|
output_layer = nil
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
constraint.each do |expression|
|
|
relation = expression[0]
|
|
value = expression[1]
|
|
if relation == ">"
|
|
lower_bound = value + 1e-6
|
|
elsif relation == "<"
|
|
upper_bound = value
|
|
elsif relation == "=="
|
|
output_layer = self.with_area(value)
|
|
elsif relation == "!="
|
|
output_layer = self.without_area(value)
|
|
elsif relation == ">="
|
|
lower_bound = value
|
|
elsif relation == "<="
|
|
upper_bound = value + 1e-6
|
|
else
|
|
raise "invalid expression"
|
|
end
|
|
end
|
|
if lower_bound or upper_bound
|
|
output_layer = self.with_area(lower_bound, upper_bound)
|
|
end
|
|
self.data.min_coherence = self_min_coherence_state
|
|
return output_layer
|
|
end
|
|
|
|
def ext_constraint_satisfied(value, constraint)
|
|
output_bool = true
|
|
constraint.each do |expression|
|
|
if expression[0] == ">"
|
|
output_bool = output_bool && (value > expression[1])
|
|
elsif expression[0] == "<"
|
|
output_bool = output_bool && (value < expression[1])
|
|
elsif expression[0] == "=="
|
|
output_bool = output_bool && (value == expression[1])
|
|
elsif expression[0] == "!="
|
|
output_bool = output_bool && (value != expression[1])
|
|
elsif expression[0] == ">="
|
|
output_bool = output_bool && (value >= expression[1])
|
|
elsif expression[0] == "<="
|
|
output_bool = output_bool && (value <= expression[1])
|
|
else
|
|
raise "invalid expression"
|
|
end
|
|
end
|
|
return output_bool
|
|
end
|
|
|
|
def ext_covering(other)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
other_min_coherence_state = other.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
other.data.min_coherence = true
|
|
output_layer = self.covering(other.inside(self))
|
|
self.data.min_coherence = self_min_coherence_state
|
|
other.data.min_coherence = other_min_coherence_state
|
|
return output_layer
|
|
end
|
|
|
|
def ext_separation(other,
|
|
value,
|
|
metric=RBA::Region::Euclidian,
|
|
consider_intersecting_edges=false,
|
|
consider_touch_points=false,
|
|
ignore_non_axis_aligned_edges=false,
|
|
min_angle=0,
|
|
max_angle=90,
|
|
include_min_angle=true,
|
|
include_max_angle=false,
|
|
polygons=false)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
other_min_coherence_state = other.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
other.data.min_coherence = true
|
|
if metric.is_a?(DRC::DRCMetrics)
|
|
metric = metric.value
|
|
end
|
|
error_edge_pairs_90 = DRC::DRCLayer::new(@engine,
|
|
self.data.separation_check(other.data, value, false, metric, 90, 1, nil))
|
|
error_edge_pairs_180 = DRC::DRCLayer::new(@engine,
|
|
self.data.separation_check(other.data, value, false, metric, 180, nil, 1))
|
|
width_error_edge_pairs_90 = DRC::DRCLayer::new(@engine,
|
|
self.data.width_check(value, false, metric, 90, 1, nil) +
|
|
other.data.width_check(value, false, metric, 90, 1, nil))
|
|
width_error_edge_pairs_180 = DRC::DRCLayer::new(@engine,
|
|
self.data.width_check(value, false, metric, 180, nil, 1) +
|
|
other.data.width_check(value, false, metric, 180, nil, 1))
|
|
separation_errors = @engine.space_width_separation_overlap_check(
|
|
value,
|
|
error_edge_pairs_90,
|
|
error_edge_pairs_180,
|
|
width_error_edge_pairs_90,
|
|
width_error_edge_pairs_180,
|
|
{
|
|
consider_intersecting_edges: consider_intersecting_edges,
|
|
consider_touch_points: consider_touch_points,
|
|
ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges,
|
|
min_angle: min_angle,
|
|
max_angle: max_angle,
|
|
include_min_angle: include_min_angle,
|
|
include_max_angle: include_max_angle
|
|
}
|
|
)
|
|
self.data.min_coherence = self_min_coherence_state
|
|
other.data.min_coherence = other_min_coherence_state
|
|
if polygons
|
|
return separation_errors.polygons.merge(true, 0)
|
|
else
|
|
return separation_errors
|
|
end
|
|
end
|
|
|
|
def ext_fast_separation(other,
|
|
value,
|
|
metric: @engine.euclidian,
|
|
consider_intersecting_edges: true,
|
|
consider_touch_points: true,
|
|
ignore_non_axis_aligned_edges: false,
|
|
min_angle: 0,
|
|
max_angle: 90,
|
|
include_min_angle: true,
|
|
include_max_angle: false,
|
|
polygon_output: false)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
other_min_coherence_state = other.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
other.data.min_coherence = true
|
|
output_layer = self.separation(other, value, metric, @engine.angle_limit(max_angle))
|
|
if !consider_intersecting_edges and !consider_touch_points
|
|
output_layer = output_layer.with_distance(1, nil)
|
|
if ignore_non_axis_aligned_edges
|
|
output_layer = output_layer.with_angle(@engine.ortho, @engine.both)
|
|
end
|
|
elsif consider_intersecting_edges ^ consider_touch_points
|
|
candidate_layer1 = self.interacting(output_layer.edges)
|
|
candidate_layer2 = other.interacting(output_layer.edges)
|
|
output_layer = candidate_layer1.ext_separation(
|
|
candidate_layer2,
|
|
value,
|
|
metric,
|
|
consider_intersecting_edges,
|
|
consider_touch_points,
|
|
ignore_non_axis_aligned_edges,
|
|
min_angle,
|
|
max_angle,
|
|
include_min_angle,
|
|
include_max_angle,
|
|
false)
|
|
end
|
|
self.data.min_coherence = self_min_coherence_state
|
|
other.data.min_coherence = other_min_coherence_state
|
|
if polygon_output
|
|
return output_layer.polygons.merge(true, 0)
|
|
else
|
|
return output_layer
|
|
end
|
|
end
|
|
|
|
def ext_not(other)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
other_min_coherence_state = other.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
other.data.min_coherence = true
|
|
output_layer = self - other
|
|
self.data.min_coherence = self_min_coherence_state
|
|
other.data.min_coherence = other_min_coherence_state
|
|
return output_layer
|
|
end
|
|
|
|
def ext_or(other, *further_layers)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
other_min_coherence_state = other.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
other.data.min_coherence = true
|
|
output_layer = self.join(other)
|
|
further_layers.each do |further_layer|
|
|
further_layer_min_coherence_state = further_layer.data.min_coherence?
|
|
further_layer.data.min_coherence = true
|
|
output_layer = output_layer.join(further_layer)
|
|
further_layer.data.min_coherence = further_layer_min_coherence_state
|
|
end
|
|
self.data.min_coherence = self_min_coherence_state
|
|
other.data.min_coherence = other_min_coherence_state
|
|
return output_layer
|
|
end
|
|
|
|
def ext_rectangles(axis_aligned = false, use_bbox = false, constraint1 = nil, constraint2 = nil, aspect_ratio_constraint = nil, inverted: false)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
if ( ( constraint1 && ( !constraint2 || constraint1.length() > 1 || constraint1[0][0] != "==") ) ||
|
|
( constraint2 && ( constraint2.length() > 1 || constraint2[0][0] != "==" ) ) ||
|
|
( constraint1 && constraint2 && constraint1[0][1] != constraint2[0][1] ) )
|
|
raise "ext_rectangle: unsupported options"
|
|
end
|
|
square = constraint1 ? true : false
|
|
shape_filter =
|
|
if use_bbox
|
|
@engine.extents
|
|
elsif axis_aligned
|
|
@engine.rectangles
|
|
else
|
|
@engine.if_all((@engine.corners == 270).count == 4, @engine.corners.count == 4)
|
|
end
|
|
if square
|
|
if use_bbox
|
|
shape_filter = @engine.if_all((@engine.extents.length == constraint1[0][1]).count == 4)
|
|
else
|
|
square_filter = (@engine.length == constraint1[0][1]).count == 4
|
|
shape_filter = @engine.if_all(shape_filter, square_filter)
|
|
end
|
|
end
|
|
if inverted
|
|
output_layer = self.drc(! shape_filter)
|
|
else
|
|
output_layer = self.drc(shape_filter)
|
|
end
|
|
self.data.min_coherence = self_min_coherence_state
|
|
return output_layer
|
|
end
|
|
|
|
def ext_space(value,
|
|
metric=RBA::Region::Euclidian,
|
|
consider_intersecting_edges=false,
|
|
consider_touch_points=false,
|
|
ignore_non_axis_aligned_edges=false,
|
|
min_angle=0,
|
|
max_angle=90,
|
|
include_min_angle=true,
|
|
include_max_angle=false,
|
|
polygons=false)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
if metric.is_a?(DRC::DRCMetrics)
|
|
metric = metric.value
|
|
end
|
|
error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.space_check(value, false, metric, 90, 1, nil))
|
|
error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.space_check(value, false, metric, 180, nil, 1))
|
|
width_error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.width_check(value, false, metric, 90, 1, nil))
|
|
width_error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.width_check(value, false, metric, 180, nil, 1))
|
|
space_errors = @engine.space_width_separation_overlap_check(
|
|
value,
|
|
error_edge_pairs_90,
|
|
error_edge_pairs_180,
|
|
width_error_edge_pairs_90,
|
|
width_error_edge_pairs_180,
|
|
{
|
|
consider_intersecting_edges: consider_intersecting_edges,
|
|
consider_touch_points: consider_touch_points,
|
|
ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges,
|
|
min_angle: min_angle,
|
|
max_angle: max_angle,
|
|
include_min_angle: include_min_angle,
|
|
include_max_angle: include_max_angle
|
|
}
|
|
)
|
|
self.data.min_coherence = self_min_coherence_state
|
|
if polygons
|
|
return space_errors.polygons.merge(true, 0)
|
|
else
|
|
return space_errors
|
|
end
|
|
end
|
|
|
|
def ext_fast_space(value,
|
|
metric: @engine.euclidian,
|
|
consider_intersecting_edges: true,
|
|
consider_touch_points: true,
|
|
ignore_non_axis_aligned_edges: false,
|
|
min_angle: 0,
|
|
max_angle: 90,
|
|
include_min_angle: true,
|
|
include_max_angle: false,
|
|
polygon_output: false)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
output_layer = self.space(value, metric, @engine.angle_limit(max_angle))
|
|
if !consider_intersecting_edges and !consider_touch_points
|
|
output_layer = output_layer.with_distance(1, nil)
|
|
if ignore_non_axis_aligned_edges
|
|
output_layer = output_layer.with_angle(@engine.ortho, @engine.both)
|
|
end
|
|
elsif consider_intersecting_edges ^ consider_touch_points
|
|
candidate_layer = self.interacting(output_layer.edges)
|
|
output_layer = candidate_layer.ext_space(
|
|
value,
|
|
metric,
|
|
consider_intersecting_edges,
|
|
consider_touch_points,
|
|
ignore_non_axis_aligned_edges,
|
|
min_angle,
|
|
max_angle,
|
|
include_min_angle,
|
|
include_max_angle,
|
|
false)
|
|
end
|
|
self.data.min_coherence = self_min_coherence_state
|
|
if polygon_output
|
|
return output_layer.polygons.ext_merge(true, 0)
|
|
else
|
|
return output_layer
|
|
end
|
|
end
|
|
|
|
def ext_fast_width(value, polygon_output: false)
|
|
self_min_coherence_state = self.data.min_coherence?
|
|
self.data.min_coherence = true
|
|
output_layer = self.width(value)
|
|
self.data.min_coherence = self_min_coherence_state
|
|
if polygon_output
|
|
return output_layer.polygons.ext_merge(true, 0)
|
|
else
|
|
return output_layer
|
|
end
|
|
end
|
|
|
|
def ext_interacting_with_text(text_layer, text)
|
|
if text_layer.is_a? Integer
|
|
text_layer = @engine.labels(text_layer)
|
|
end
|
|
initial_merged_semantics = self.data.merged_semantics?
|
|
self.data.merged_semantics = false
|
|
result = self.interacting(text_layer.texts(text))
|
|
self.data.merged_semantics = initial_merged_semantics
|
|
return result
|
|
end
|
|
|
|
def ext_with_density(range, *args)
|
|
if self.is_empty?
|
|
return DRC::DRCLayer::new(@engine, RBA::Region::new())
|
|
end
|
|
if self.is_merged?
|
|
merged_layer = self
|
|
else
|
|
merged_layer = self.ext_merged(true, 0)
|
|
end
|
|
origin = 'cc'
|
|
tile_size = nil
|
|
tile_step = nil
|
|
arguments = [range]
|
|
args.each do |a|
|
|
if a.is_a?(DRC::DRCTileSize)
|
|
tile_size = a
|
|
arguments.push(tile_size)
|
|
elsif a.is_a?(DRC::DRCTileStep)
|
|
tile_step = a
|
|
arguments.push(tile_step)
|
|
elsif a.is_a?(String)
|
|
origin = a
|
|
else
|
|
raise "argument error"
|
|
end
|
|
end
|
|
bbox = @engine.extent.bbox
|
|
if origin == 'll'
|
|
origin_x = bbox.left
|
|
origin_y = bbox.bottom
|
|
if tile_size and tile_step and (tile_size.get[0] != tile_step.get[0] or tile_size.get[1] != tile_step.get[1])
|
|
origin_x = bbox.left + tile_step.get[0]/2
|
|
origin_y = bbox.bottom + tile_step.get[1]/2
|
|
end
|
|
tile_origin = DRC::DRCTileOrigin::new(origin_x, origin_y)
|
|
arguments.push(tile_origin)
|
|
elsif origin != 'cc'
|
|
raise "Unknown origin: 'cc' or 'll' expected"
|
|
end
|
|
if tile_size
|
|
boundary_layer = DRC::DRCLayer::new(@engine, RBA::Region::new(bbox.to_itype(@engine.dbu)))
|
|
tile_boundary = DRC::DRCTileBoundary::new(boundary_layer)
|
|
return merged_layer.with_density(*arguments, tile_boundary, @engine.padding_ignore)
|
|
else
|
|
tile_size = DRC::DRCTileSize::new(bbox.width, bbox.height)
|
|
tile_count = DRC::DRCTileCount::new(1,2)
|
|
enlarged_bbox = bbox.enlarged(1.1).to_itype(@engine.dbu)
|
|
boundary_layer = DRC::DRCLayer::new(@engine, RBA::Region::new(enlarged_bbox))
|
|
tile_boundary = DRC::DRCTileBoundary::new(boundary_layer)
|
|
result = merged_layer.with_density(*arguments, tile_size, tile_count, tile_boundary, @engine.padding_ignore)
|
|
return result.raw.overlapping(DRC::DRCLayer::new(@engine, RBA::Region::new(bbox.to_itype(@engine.dbu))))
|
|
end
|
|
end
|
|
end
|
|
|
|
Activ = source.polygons("1/0")
|
|
Activ_pin = source.polygons("1/2")
|
|
Activ_filler = source.polygons("1/22")
|
|
BiWind = source.polygons("3/0")
|
|
GatPoly = source.polygons("5/0")
|
|
GatPoly_pin = source.polygons("5/2")
|
|
GatPoly_filler = source.polygons("5/22")
|
|
Cont = source.polygons("6/0")
|
|
Metal1 = source.polygons("8/0")
|
|
Metal1_pin = source.polygons("8/2")
|
|
Metal1_filler = source.polygons("8/22")
|
|
Metal1_slit = source.polygons("8/24")
|
|
Passiv = source.polygons("9/0")
|
|
Metal2 = source.polygons("10/0")
|
|
Metal2_pin = source.polygons("10/2")
|
|
Metal2_filler = source.polygons("10/22")
|
|
Metal2_slit = source.polygons("10/24")
|
|
BasPoly = source.polygons("13/0")
|
|
Via1 = source.polygons("19/0")
|
|
SRAM = source.polygons("25/0")
|
|
TRANS = source.polygons("26/0")
|
|
Via2 = source.polygons("29/0")
|
|
Metal3 = source.polygons("30/0")
|
|
Metal3_pin = source.polygons("30/2")
|
|
Metal3_filler = source.polygons("30/22")
|
|
Metal3_slit = source.polygons("30/24")
|
|
DeepCo = source.polygons("35/0")
|
|
EdgeSeal = source.polygons("39/0")
|
|
ThickGateOx = source.polygons("44/0")
|
|
Via3 = source.polygons("49/0")
|
|
Metal4 = source.polygons("50/0")
|
|
Metal4_pin = source.polygons("50/2")
|
|
Metal4_filler = source.polygons("50/22")
|
|
Metal4_slit = source.polygons("50/24")
|
|
EmPoly = source.polygons("55/0")
|
|
TEXT_0 = source.labels("63/0")
|
|
Via4 = source.polygons("66/0")
|
|
Metal5 = source.polygons("67/0")
|
|
Metal5_pin = source.polygons("67/2")
|
|
Metal5_filler = source.polygons("67/22")
|
|
Metal5_slit = source.polygons("67/24")
|
|
TopVia1 = source.polygons("125/0")
|
|
TopMetal1 = source.polygons("126/0")
|
|
TopMetal1_pin = source.polygons("126/2")
|
|
TopMetal1_filler = source.polygons("126/22")
|
|
TopMetal1_slit = source.polygons("126/24")
|
|
Vmim = source.polygons("129/0")
|
|
TopVia2 = source.polygons("133/0")
|
|
TopMetal2 = source.polygons("134/0")
|
|
TopMetal2_pin = source.polygons("134/2")
|
|
TopMetal2_filler = source.polygons("134/22")
|
|
TopMetal2_slit = source.polygons("134/24")
|
|
ColWind = source.polygons("139/0")
|
|
LBE = source.polygons("157/0")
|
|
|
|
if $sanityRules
|
|
PEmWind = source.polygons("11/0")
|
|
PEmPoly = source.polygons("53/0")
|
|
LDMOS = source.polygons("57/0")
|
|
PBiWind = source.polygons("58/0")
|
|
Flash = source.polygons("71/0")
|
|
end
|
|
|
|
Activ_Act_a = Activ.ext_fast_width(0.15.um)
|
|
Act_density = Activ.ext_or(Activ_filler)
|
|
Gat_density = GatPoly.ext_or(GatPoly_filler)
|
|
Cont_SQ = Cont.ext_rectangles(true, false, [["==", 0.16.um]], [["==", 0.16.um]], nil)
|
|
ContBar = Cont.ext_with_area([[">", (0.16*0.16).um2]])
|
|
Act_Nsram = Activ.ext_not(SRAM)
|
|
GP_Nsram = GatPoly.ext_not(SRAM)
|
|
M1_Nsram = Metal1.ext_not(SRAM)
|
|
M2_Nsram = Metal2.ext_not(SRAM)
|
|
M3_Nsram = Metal3.ext_not(SRAM)
|
|
Via1_edgC1_out = Via1.ext_not(EdgeSeal)
|
|
Via2_edgC1_out = Via2.ext_not(EdgeSeal)
|
|
Cont_outside_EdgeSeal = Cont.outside(EdgeSeal)
|
|
ThickGateOx_TGO_f = ThickGateOx.ext_fast_width(0.86.um, polygon_output: true)
|
|
Via3_edgC1_out = Via3.ext_not(EdgeSeal)
|
|
M4_Nsram = Metal4.ext_not(SRAM)
|
|
Via4_edgC1_out = Via4.ext_not(EdgeSeal)
|
|
M5_Nsram = Metal5.ext_not(SRAM)
|
|
TopVia1_edgC1_out = TopVia1.ext_not(EdgeSeal)
|
|
TopVia1_or_Vmim = TopVia1.ext_or(Vmim)
|
|
TopVia2_edgC1_out = TopVia2.ext_not(EdgeSeal)
|
|
M1_density = Metal1.ext_or(Metal1_filler).ext_not(Metal1_slit)
|
|
M2_density = Metal2.ext_or(Metal2_filler).ext_not(Metal2_slit)
|
|
emi2Pin = Metal2_pin.ext_and(TRANS).ext_interacting_with_text(TEXT_0, "E")
|
|
M3_density = Metal3.ext_or(Metal3_filler).ext_not(Metal3_slit)
|
|
M4_density = Metal4.ext_or(Metal4_filler).ext_not(Metal4_slit)
|
|
M5_density = Metal5.ext_or(Metal5_filler).ext_not(Metal5_slit)
|
|
TM1_density = TopMetal1.ext_or(TopMetal1_filler).ext_not(TopMetal1_slit)
|
|
TM2_density = TopMetal2.ext_or(TopMetal2_filler).ext_not(TopMetal2_slit)
|
|
GP_Nsram_Gat_a = GP_Nsram.ext_fast_width(0.13.um, polygon_output: true)
|
|
GP_Nsram_Gat_b = GP_Nsram.ext_fast_space(0.18.um, consider_intersecting_edges: false, polygon_output: true)
|
|
transG2L = TRANS.ext_interacting_with_text(TEXT_0, "npn13G2L").ext_covering(emi2Pin)
|
|
-> do
|
|
Activ_Act_a.dup
|
|
end.().output("Act.a", "Min. Activ width = 0.15")
|
|
-> do
|
|
Act_Nsram.ext_fast_space(0.21.um)
|
|
end.().output("Act.b", "Min. Activ space or notch = 0.21")
|
|
|
|
if $density
|
|
#-> do
|
|
# Act_density.ext_with_density(0.0 .. 0.35, 'll')
|
|
#end.().output("AFil.g", "Min. global Activ density [%] = 35.00")
|
|
-> do
|
|
Act_density.ext_with_density(0.55 .. 1.0, 'll')
|
|
end.().output("AFil.g1", "Max. global Activ density [%] = 55.00")
|
|
#-> do
|
|
# Act_density.ext_with_density(0.0 .. 0.25, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
#end.().output("AFil.g2", "Min. Activ coverage ratio for any 800 x 800 µm² chip area [%] = 25.00")
|
|
-> do
|
|
Act_density.ext_with_density(0.65 .. 1.0, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
end.().output("AFil.g3", "Max. Activ coverage ratio for any 800 x 800 µm² chip area [%] = 65.00")
|
|
end
|
|
|
|
-> do
|
|
ThickGateOx_TGO_f.dup
|
|
end.().output("TGO.f", "Min. ThickGateOx width = 0.86")
|
|
-> do
|
|
GP_Nsram_Gat_a.dup
|
|
end.().output("Gat.a", "Min. GatPoly width = 0.13")
|
|
-> do
|
|
GP_Nsram_Gat_b.dup
|
|
end.().output("Gat.b", "Min. GatPoly space or notch = 0.18")
|
|
-> do
|
|
GP_Nsram.ext_fast_separation(Act_Nsram, 0.07.um)
|
|
end.().output("Gat.d", "Min. GatPoly space to Activ = 0.07")
|
|
|
|
if $density
|
|
#-> do
|
|
# Gat_density.ext_with_density(0.0 .. 0.15, 'll')
|
|
#end.().output("GFil.g", "Min. global GatPoly density [%] = 15.00")
|
|
end
|
|
|
|
-> do
|
|
Cont_outside_EdgeSeal.ext_not(ContBar.ext_or(Cont_SQ))
|
|
end.().output("Cnt.a", "Min. and max. Cont width = 0.16")
|
|
-> do
|
|
Cont_outside_EdgeSeal.ext_fast_space(0.18.um, consider_intersecting_edges: false)
|
|
end.().output("Cnt.b", "Min. Cont space = 0.18")
|
|
-> do
|
|
Metal1.ext_fast_width(0.16.um)
|
|
end.().output("M1.a", "Min. Metal1 width = 0.16")
|
|
-> do
|
|
M1_Nsram.ext_fast_space(0.18.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true)
|
|
end.().output("M1.b", "Min. Metal1 space or notch = 0.18")
|
|
|
|
if $density
|
|
#-> do
|
|
# M1_density.ext_with_density(0.0 .. 0.35, 'll')
|
|
#end.().output("M1.j", "Min. global Metal1 density [%] = 35.0")
|
|
-> do
|
|
M1_density.ext_with_density(0.6 .. 1.0, 'll')
|
|
end.().output("M1.k", "Max. global Metal1 density [%] = 60.0")
|
|
end
|
|
|
|
-> do
|
|
Metal2.ext_fast_width(0.2.um)
|
|
end.().output("M2.a", "Min. Metal2 width = 0.20")
|
|
-> do
|
|
M2_Nsram.ext_fast_space(0.21.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true)
|
|
end.().output("M2.b", "Min. Metal2 space or notch = 0.21")
|
|
|
|
if $density
|
|
#-> do
|
|
# M2_density.ext_with_density(0.0 .. 0.35, 'll')
|
|
#end.().output("M2.j", "Min. global Metal2 density [%] = 35.00")
|
|
-> do
|
|
M2_density.ext_with_density(0.6 .. 1.0, 'll')
|
|
end.().output("M2.k", "Max. global Metal2 density [%] = 60.00")
|
|
end
|
|
|
|
-> do
|
|
Metal3.ext_fast_width(0.2.um)
|
|
end.().output("M3.a", "Min. Metal3 width = 0.20")
|
|
-> do
|
|
M3_Nsram.ext_fast_space(0.21.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true)
|
|
end.().output("M3.b", "Min. Metal3 space or notch = 0.21")
|
|
|
|
if $density
|
|
#-> do
|
|
# M3_density.ext_with_density(0.0 .. 0.35, 'll')
|
|
#end.().output("M3.j", "Min. global Metal3 density [%] = 35.00")
|
|
-> do
|
|
M3_density.ext_with_density(0.6 .. 1.0, 'll')
|
|
end.().output("M3.k", "Max. global Metal3 density [%] = 60.00")
|
|
end
|
|
|
|
-> do
|
|
Metal4.ext_fast_width(0.2.um)
|
|
end.().output("M4.a", "Min. Metal4 width = 0.20")
|
|
-> do
|
|
M4_Nsram.ext_fast_space(0.21.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true)
|
|
end.().output("M4.b", "Min. Metal4 space or notch = 0.21")
|
|
|
|
if $density
|
|
#-> do
|
|
# M4_density.ext_with_density(0.0 .. 0.35, 'll')
|
|
#end.().output("M4.j", "Min. global Metal4 density [%] = 35.00")
|
|
-> do
|
|
M4_density.ext_with_density(0.6 .. 1.0, 'll')
|
|
end.().output("M4.k", "Max. global Metal4 density [%] = 60.00")
|
|
end
|
|
|
|
-> do
|
|
Metal5.ext_fast_width(0.2.um)
|
|
end.().output("M5.a", "Min. Metal5 width = 0.20")
|
|
-> do
|
|
M5_Nsram.ext_fast_space(0.21.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true)
|
|
end.().output("M5.b", "Min. Metal5 space or notch = 0.21")
|
|
|
|
if $density
|
|
#-> do
|
|
# M5_density.ext_with_density(0.0 .. 0.35, 'll')
|
|
#end.().output("M5.j", "Min. global Metal5 density [%] = 35.00")
|
|
-> do
|
|
M5_density.ext_with_density(0.6 .. 1.0, 'll')
|
|
end.().output("M5.k", "Max. global Metal5 density [%] = 60.00")
|
|
#-> do
|
|
# M1_density.ext_with_density(0.0 .. 0.25, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
#end.().output("M1Fil.h", "Min. Metal1 and Metal1:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00")
|
|
-> do
|
|
M1_density.ext_with_density(0.75 .. 1.0, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
end.().output("M1Fil.k", "Max. Metal1 and Metal1:filler coverage ratio for any 800 x 800 µm² chip area [%] = 75.00")
|
|
#-> do
|
|
# M2_density.ext_with_density(0.0 .. 0.25, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
#end.().output("M2Fil.h", "Min. Metal2 and Metal2:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00")
|
|
-> do
|
|
M2_density.ext_with_density(0.75 .. 1.0, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
end.().output("M2Fil.k", "Max. Metal2 and Metal2:filler coverage ratio for any 800 x 800 µm² chip area [%] = 75.00")
|
|
#-> do
|
|
# M3_density.ext_with_density(0.0 .. 0.25, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
#end.().output("M3Fil.h", "Min. Metal3 and Metal3:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00")
|
|
-> do
|
|
M3_density.ext_with_density(0.75 .. 1.0, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
end.().output("M3Fil.k", "Max. Metal3 and Metal3:filler coverage ratio for any 800 x 800 µm² chip area [%] = 75.00")
|
|
#-> do
|
|
# M4_density.ext_with_density(0.0 .. 0.25, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
#end.().output("M4Fil.h", "Min. Metal4 and Metal4:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00")
|
|
-> do
|
|
M4_density.ext_with_density(0.75 .. 1.0, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
end.().output("M4Fil.k", "Max. Metal4 and Metal4:filler coverage ratio for any 800 x 800 µm² chip area [%] = 75.00")
|
|
#-> do
|
|
# M5_density.ext_with_density(0.0 .. 0.25, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
#end.().output("M5Fil.h", "Min. Metal5 and Metal5:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00")
|
|
-> do
|
|
M5_density.ext_with_density(0.75 .. 1.0, 'll', tile_size(800.0.um), tile_step(400.0.um))
|
|
end.().output("M5Fil.k", "Max. Metal5 and Metal5:filler coverage ratio for any 800 x 800 µm² chip area [%] = 75.00")
|
|
end
|
|
|
|
-> do
|
|
Via1_edgC1_out.outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true)
|
|
end.().output("V1.a", "Min. and max. Via1 width = 0.19")
|
|
-> do
|
|
Via1_edgC1_out.ext_fast_space(0.22.um, consider_intersecting_edges: false)
|
|
end.().output("V1.b", "Min. Via1 space = 0.22")
|
|
-> do
|
|
Via2_edgC1_out.outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true)
|
|
end.().output("V2.a", "Min. and max. Via2 width = 0.19")
|
|
-> do
|
|
Via2_edgC1_out.ext_fast_space(0.22.um, consider_intersecting_edges: false)
|
|
end.().output("V2.b", "Min. Via2 space = 0.22")
|
|
-> do
|
|
Via3_edgC1_out.outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true)
|
|
end.().output("V3.a", "Min. and max. Via3 width = 0.19")
|
|
-> do
|
|
Via3_edgC1_out.ext_fast_space(0.22.um, consider_intersecting_edges: false)
|
|
end.().output("V3.b", "Min. Via3 space = 0.22")
|
|
-> do
|
|
Via4_edgC1_out.outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true)
|
|
end.().output("V4.a", "Min. and max. Via4 width = 0.19")
|
|
-> do
|
|
Via4_edgC1_out.ext_fast_space(0.22.um, consider_intersecting_edges: false)
|
|
end.().output("V4.b", "Min. Via4 space = 0.22")
|
|
-> do
|
|
TopVia1_edgC1_out.ext_or(Vmim).ext_rectangles(false, false, [["==", 0.42.um]], [["==", 0.42.um]], nil, inverted: true)
|
|
end.().output("TV1.a", "Min. and max. TopVia1 width = 0.42")
|
|
-> do
|
|
TopVia1_or_Vmim.ext_fast_space(0.42.um)
|
|
end.().output("TV1.b", "Min. TopVia1 space = 0.42")
|
|
-> do
|
|
TopMetal1.ext_fast_width(1.64.um)
|
|
end.().output("TM1.a", "Min. TopMetal1 width = 1.64")
|
|
-> do
|
|
TopMetal1.ext_fast_space(1.64.um)
|
|
end.().output("TM1.b", "Min. TopMetal1 space or notch = 1.64")
|
|
|
|
if $density
|
|
#-> do
|
|
# TM1_density.ext_with_density(0.0 .. 0.25, 'll')
|
|
#end.().output("TM1.c", "Min. global TopMetal1 density [%] = 25.00")
|
|
-> do
|
|
TM1_density.ext_with_density(0.7 .. 1.0, 'll')
|
|
end.().output("TM1.d", "Max. global TopMetal1 density [%] = 70.00")
|
|
end
|
|
|
|
-> do
|
|
TopVia2_edgC1_out.ext_rectangles(false, false, [["==", 0.9.um]], [["==", 0.9.um]], nil, inverted: true)
|
|
end.().output("TV2.a", "Min. and max. TopVia2 width = 0.90")
|
|
-> do
|
|
TopVia2.ext_fast_space(1.06.um)
|
|
end.().output("TV2.b", "Min. TopVia2 space = 1.06")
|
|
-> do
|
|
TopMetal2.ext_fast_width(2.0.um)
|
|
end.().output("TM2.a", "Min. TopMetal2 width = 2.00")
|
|
-> do
|
|
TopMetal2.ext_fast_space(2.0.um)
|
|
end.().output("TM2.b", "Min. TopMetal2 space or notch = 2.00")
|
|
|
|
if $density
|
|
#-> do
|
|
# TM2_density.ext_with_density(0.0 .. 0.25, 'll')
|
|
#end.().output("TM2.c", "Min. global TopMetal2 density [%] = 25.00")
|
|
-> do
|
|
TM2_density.ext_with_density(0.7 .. 1.0, 'll')
|
|
end.().output("TM2.d", "Max. global TopMetal2 density [%] = 70.00")
|
|
end
|
|
|
|
-> do
|
|
Passiv.ext_fast_width(2.1.um)
|
|
end.().output("Pas.a", "Min. Passiv width = 2.10")
|
|
-> do
|
|
Passiv.ext_fast_space(3.5.um)
|
|
end.().output("Pas.b", "Min. Passiv space or notch = 3.50")
|
|
|
|
if $sanityRules
|
|
-> do
|
|
Activ_pin.ext_not(Activ)
|
|
end.().output("Pin.a", "Min. Activ enclosure of Activ:pin = 0.00")
|
|
-> do
|
|
GatPoly_pin.ext_not(GatPoly)
|
|
end.().output("Pin.b", "Min. GatPoly enclosure of GatPoly:pin = 0.00")
|
|
-> do
|
|
Metal1_pin.ext_not(Metal1)
|
|
end.().output("Pin.e", "Min. Metal1 enclosure of Metal1:pin = 0.00")
|
|
-> do
|
|
Metal2_pin.ext_not(Metal2)
|
|
end.().output("Pin.f.M2", "Min. Metal2 enclosure of Metal2:pin = 0.00")
|
|
-> do
|
|
Metal3_pin.ext_not(Metal3)
|
|
end.().output("Pin.f.M3", "Min. Metal3 enclosure of Metal3:pin = 0.00")
|
|
-> do
|
|
Metal4_pin.ext_not(Metal4)
|
|
end.().output("Pin.f.M4", "Min. Metal4 enclosure of Metal4:pin = 0.00")
|
|
-> do
|
|
Metal5_pin.ext_not(Metal5)
|
|
end.().output("Pin.f.M5", "Min. Metal5 enclosure of Metal5:pin = 0.00")
|
|
-> do
|
|
TopMetal1_pin.ext_not(TopMetal1)
|
|
end.().output("Pin.g", "Min. TopMetal1 enclosure of TopMetal1:pin = 0.00")
|
|
-> do
|
|
TopMetal2_pin.ext_not(TopMetal2)
|
|
end.().output("Pin.h", "Min. TopMetal2 enclosure of TopMetal2:pin = 0.00")
|
|
end
|
|
|
|
-> do
|
|
LBE.ext_fast_width(100.0.um)
|
|
end.().output("LBE.a", "Min. LBE width = 100.00")
|
|
-> do
|
|
LBE.drc((width(projection) > 1500.0.um).polygons)
|
|
end.().output("LBE.b", "Max. LBE width = 1500.00")
|
|
-> do
|
|
LBE.ext_with_area([[">", 250000.0.um2]])
|
|
end.().output("LBE.b1", "Max. LBE area (µm²) = 250000.00")
|
|
-> do
|
|
LBE.ext_fast_space(100.0.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true)
|
|
end.().output("LBE.c", "Min. LBE space or notch = 100.00")
|
|
-> (;lbe_in_seal) do
|
|
lbe_in_seal = LBE.inside(EdgeSeal.holes.merge)
|
|
lbe_in_seal.ext_fast_separation(EdgeSeal, 150.0.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true)
|
|
end.().output("LBE.d", "Min. LBE space to inner edge of EdgeSeal = 150.00")
|
|
-> do
|
|
LBE.with_holes.dup
|
|
end.().output("LBE.h", "No LBE ring allowed")
|
|
|
|
if $density
|
|
-> do
|
|
LBE.ext_with_density(0.2 .. 1.0, 'll')
|
|
end.().output("LBE.i", "Max. global LBE density [%] = 20.00")
|
|
end
|
|
|
|
|
|
if $sanityRules
|
|
-> do
|
|
BiWind.dup
|
|
end.().output("forbidden.BiWind", "Forbidden drawn layer BiWind on GDS layer 3/0 = 3/0")
|
|
-> do
|
|
PEmWind.dup
|
|
end.().output("forbidden.PEmWind", "Forbidden drawn layer PEmWind on GDS layer 11/0 = 11/0")
|
|
-> do
|
|
BasPoly.dup
|
|
end.().output("forbidden.BasPoly", "Forbidden drawn layer BasPoly on GDS layer 13/0 = 13/0")
|
|
-> do
|
|
DeepCo.dup
|
|
end.().output("forbidden.DeepCo", "Forbidden drawn layer DeepCo on GDS layer 35/0 = 35/0")
|
|
-> do
|
|
PEmPoly.dup
|
|
end.().output("forbidden.PEmPoly", "Forbidden drawn layer PEmPoly on GDS layer 53/0 = 53/0")
|
|
-> do
|
|
EmPoly.dup
|
|
end.().output("forbidden.EmPoly", "Forbidden gen./drawn layer EmPoly on GDS layer 53/0 = 53/0")
|
|
-> do
|
|
LDMOS.dup
|
|
end.().output("forbidden.LDMOS", "Forbidden drawn layer LDMOS on GDS layer 57/0 = 57/0")
|
|
-> do
|
|
PBiWind.dup
|
|
end.().output("forbidden.PBiWind", "Forbidden drawn layer PBiWind on GDS layer 58/0 = 58/0")
|
|
-> do
|
|
Flash.dup
|
|
end.().output("forbidden.Flash", "Forbidden drawn layer Flash on GDS layer 71/0 = 71/0")
|
|
-> do
|
|
ColWind.dup
|
|
end.().output("forbidden.ColWind", "Forbidden drawn layer ColWind on GDS layer 139/0 = 139/0")
|
|
end
|
|
|
|
puts("Number of DRC errors: #{$drc_error_count}")
|
|
</text>
|
|
</klayout-macro> |