diff --git a/precheck/precheck.py b/precheck/precheck.py index 77bf11a..57a8b29 100644 --- a/precheck/precheck.py +++ b/precheck/precheck.py @@ -81,6 +81,7 @@ def klayout_drc(gds: str, check: str, script=f"{PDK_NAME}_mr.drc", extra_vars=[] f"{check}=true", f"input={gds}", f"report={report_file}", + f"report_file={report_file}", ] klayout_args = ["klayout", "-b", "-r", f"tech-files/{script}"] for v in script_vars + extra_vars: @@ -102,6 +103,10 @@ def klayout_zero_area(gds: str): return klayout_drc(gds, "zero_area", "zeroarea.rb.drc") +def klayout_sg13g2(gds: str): + return klayout_drc(gds, "sg13g2", "sg13g2_mr.lydrc", extra_vars=[f"in_gds={gds}"]) + + def klayout_checks(gds: str, expected_name: str, tech: str): layout = pya.Layout() layout.read(gds) @@ -355,6 +360,11 @@ def main(): "pin_label_purposes_overlapping_drawing.rb.drc", ), }, + { + "name": "KLayout SG13G2 DRC", + "check": lambda: klayout_sg13g2(gds_file), + "tech": "ihp-sg13g2", + }, {"name": "KLayout zero area", "check": lambda: klayout_zero_area(gds_file)}, { "name": "KLayout Checks", diff --git a/precheck/reports/.gitignore b/precheck/reports/.gitignore index e166678..8481468 100644 --- a/precheck/reports/.gitignore +++ b/precheck/reports/.gitignore @@ -4,6 +4,7 @@ drc_offgrid.xml drc_pin_label_purposes_overlapping_drawing.xml drc_zero_area.xml drc_nwell_urpm.xml +drc_sg13g2.xml magic_drc.mag magic_drc.txt results.md diff --git a/precheck/tech-files/README.txt b/precheck/tech-files/README.txt index 2eac892..d785bfb 100644 --- a/precheck/tech-files/README.txt +++ b/precheck/tech-files/README.txt @@ -2,3 +2,9 @@ sky130A_mr.drc from https://github.com/chipfoundry/mpw_precheck revision 3467a4c pin_label_purposes_overlapping_drawing.rb.drc from https://github.com/chipfoundry/mpw_precheck revision f6b9c3d3f00694f96dce8444149449b4719180f0 zeroarea.rb.drc from https://github.com/chipfoundry/mpw_precheck revision 4fd5283b124e931c9e71219f47270075176f84e2 nwell_urpm.drc from https://open-source-silicon.slack.com/archives/C05FW1VMY5A/p1729112262642699?thread_ts=1728921677.220509&cid=C05FW1VMY5A + +sg13g2_mr.lydrc: + from https://github.com/IHP-GmbH/TO_Apr2025 revision 772a369f3ceabc2c6e1eba57d91ae755682c8323 + with the following minimum density rules disabled: + - M1.j, M2.j, M3.j, M4.j, M5.j + - M1Fil.h, M2Fil.h, M3Fil.h, M4Fil.h, M5Fil.h diff --git a/precheck/tech-files/sg13g2_mr.lydrc b/precheck/tech-files/sg13g2_mr.lydrc new file mode 100644 index 0000000..3e1f088 --- /dev/null +++ b/precheck/tech-files/sg13g2_mr.lydrc @@ -0,0 +1,1037 @@ + + + + + + drc + + + + false + false + + true + drc_scripts + tools_menu.drc.end + dsl + drc-dsl-xml + # 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 +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.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.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.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}") + + \ No newline at end of file