From f8dc00aeac47ff04a25051587c28c53454b41ef5 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Sat, 20 Apr 2024 00:36:09 +0300 Subject: [PATCH] feat(precheck): add zeroarea check --- precheck/precheck.py | 5 ++ precheck/reports/.gitignore | 1 + precheck/tech-files/README.txt | 1 + precheck/tech-files/zeroarea.rb.drc | 82 +++++++++++++++++++++++++++++ precheck/test_precheck.py | 23 ++++++++ 5 files changed, 112 insertions(+) create mode 100644 precheck/tech-files/zeroarea.rb.drc diff --git a/precheck/precheck.py b/precheck/precheck.py index f8a4100..d045a4a 100644 --- a/precheck/precheck.py +++ b/precheck/precheck.py @@ -87,6 +87,10 @@ def klayout_drc(gds: str, check: str, script=f"{PDK_NAME}_mr.drc"): ) +def klayout_zero_area(gds: str): + return klayout_drc(gds, "zero_area", "zeroarea.rb.drc") + + def klayout_checks(gds: str): layout = pya.Layout() layout.read(gds) @@ -134,6 +138,7 @@ def main(): "pin_label_purposes_overlapping_drawing.rb.drc", ), ], + ["KLayout zero area", lambda: klayout_zero_area(args.gds)], ["KLayout Checks", lambda: klayout_checks(args.gds)], ] diff --git a/precheck/reports/.gitignore b/precheck/reports/.gitignore index ec7b089..f351a8a 100644 --- a/precheck/reports/.gitignore +++ b/precheck/reports/.gitignore @@ -2,6 +2,7 @@ drc_beol.xml drc_feol.xml drc_offgrid.xml drc_pin_label_purposes_overlapping_drawing.xml +drc_zero_area.xml magic_drc.mag magic_drc.txt results.md diff --git a/precheck/tech-files/README.txt b/precheck/tech-files/README.txt index d51a476..8492328 100644 --- a/precheck/tech-files/README.txt +++ b/precheck/tech-files/README.txt @@ -1,2 +1,3 @@ sky130A_mr.drc from https://github.com/efabless/mpw_precheck revision 4fd5283b124e931c9e71219f47270075176f84e2 pin_label_purposes_overlapping_drawing.rb.drc from https://github.com/efabless/mpw_precheck revision f6b9c3d3f00694f96dce8444149449b4719180f0 +zeroarea.rb.drc from https://github.com/efabless/mpw_precheck revision 4fd5283b124e931c9e71219f47270075176f84e2 diff --git a/precheck/tech-files/zeroarea.rb.drc b/precheck/tech-files/zeroarea.rb.drc new file mode 100644 index 0000000..81ece51 --- /dev/null +++ b/precheck/tech-files/zeroarea.rb.drc @@ -0,0 +1,82 @@ +# based in part on: https://www.klayout.de/forum/discussion/173/use-klayout-in-batch-mode + +$errs = 0 +del = 0 +delZLp = true +len0 = 0 +len0Del = 0 + +ly = RBA::Layout.new +source($input, $top_cell_name) +ly.read($input) +report("zero area check", $report) + +ly.layer_indices.each { |li| + ly.each_cell { |cell| + cell.shapes(li).each { |shape| + # TODO: must we check that shape is one of: box, polygon, path? What happens to text? + if shape.is_valid? && (!shape.is_null?) && + (shape.is_box? || shape.is_path? || shape.is_polygon?) && + shape.area == 0 + $errs += 1 + polygons().insert(shape.dpolygon).output("zero_area_shape") + if $cleaned_output + shape.delete + del += 1 + end + elsif delZLp && shape.is_valid? && (!shape.is_null?) && shape.is_path? + # puts "path on layer:#{layer_info} area:#{shape.area}" + lastpt = nil + same = true + # walk the points. First time we identify at least two unique-points, skip: not a zero-length path. + shape.each_point { |pt| + if lastpt && lastpt != pt + same = false + break + end + lastpt = pt + } + if same + # puts "zero-length path on layer:#{layer_info} area:#{shape.area} path:#{shape.to_s}" + len0 += 1 + $errs += 1 + if $cleaned_output + shape.delete + len0Del += 1 + end + end + end + } + } +} + +if $cleaned_output + puts "writing to #{$cleaned_output}" + ly.write($cleaned_output) +end + +puts "#{$errs} zero-area shapes" + +del += len0Del +if delZLp + puts "#{len0} zero-length paths, #{len0Del} zero-length paths deleted." +end +puts "#{$errs} total zero-area objects, #{del} total objects deleted." + +# if we roll-over to 256, exit-status seen by shell is zero. +# uncaught I/O errors will yield (built-in) exit status of 1. +if $errs > 0 + $errs = $errs + 1 +end +if $errs > 127 + $errs = 127 +end +# experimental: report own peak process-stats. BUT: output-file isn't really written +# until we exit (during exit). So these results are not 100% accurate. +# VmHWM: max-resident-size, VmPeak: max virtual-size. +# don't need: pid=Process.pid +if File.readable?("/proc/self/status") + puts File.foreach("/proc/self/status").grep(/^(VmPeak|VmHWM)/) +end +# does not work (to set exit-status) in 0.23.11. Does work in 0.24.2, 0.27. +exit $errs diff --git a/precheck/test_precheck.py b/precheck/test_precheck.py index 0e71711..66dcc5c 100644 --- a/precheck/test_precheck.py +++ b/precheck/test_precheck.py @@ -67,6 +67,20 @@ def gds_fail_metal5_poly(tmp_path_factory: pytest.TempPathFactory): return str(gds_file) +@pytest.fixture(scope="session") +def gds_zero_area(tmp_path_factory: pytest.TempPathFactory): + """Creates a GDS with a zero-area polygon.""" + gds_file = tmp_path_factory.mktemp("gds") / "gds_zero_area.gds" + layout = pya.Layout() + top_cell = layout.create_cell("TEST_zero_area") + met1_info = gds_layers["met1.drawing"] + met1 = layout.layer(met1_info.layer, met1_info.data_type) + rect = pya.DBox(0, 0, 0, 0) + top_cell.shapes(met1).insert(rect) + layout.write(str(gds_file)) + return str(gds_file) + + def test_magic_drc_pass(gds_empty: str): precheck.magic_drc(gds_empty, "TEST_empty") @@ -101,3 +115,12 @@ def test_klayout_checks_pass(gds_empty: str): def test_klayout_checks_fail(gds_fail_metal5_poly: str): with pytest.raises(precheck.PrecheckFailure): precheck.klayout_checks(gds_fail_metal5_poly) + + +def test_klayout_zero_area_drc_pass(gds_empty: str): + precheck.klayout_zero_area(gds_empty) + + +def test_klayout_zero_area_drc_fail(gds_zero_area: str): + with pytest.raises(precheck.PrecheckFailure): + precheck.klayout_zero_area(gds_zero_area)