diff --git a/.github/workflows/precheck.yaml b/.github/workflows/precheck.yaml index c8fee69e..d20933ba 100644 --- a/.github/workflows/precheck.yaml +++ b/.github/workflows/precheck.yaml @@ -3,7 +3,7 @@ name: mpw_precheck on: workflow_dispatch: workflow_run: - workflows: ["gds"] + workflows: ['gds'] types: [completed] jobs: @@ -71,3 +71,117 @@ jobs: with: name: precheck_results path: tinytapeout-submission/precheck_results + + mpw_tapeout_checks: + env: + USER_ID: 8086 + PROJECT: caravel_openframe + KLAYOUT_VERSION: 0.29.2 + MAGIC_VERSION: 8.3.471 + PDK_ROOT: ${{ github.workspace }}/pdk + SKY130_PDK_VERSION: 0fe599b2afb6708d281543108caf8310912f54af + CARAVEL_ROOT: ${{ github.workspace }}/caravel + DRC_ROOT: ${{ github.workspace }}/sky130_tapeout_drc + + runs-on: ubuntu-22.04 + steps: + - name: checkout repo + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Checkout Caravel + uses: actions/checkout@v4 + with: + repository: efabless/caravel + path: ${{ env.CARAVEL_ROOT }} + + - name: Install KLayout + run: | + wget https://www.klayout.org/downloads/Ubuntu-22/klayout_${KLAYOUT_VERSION}-1_amd64.deb + sudo apt-get install -y ./klayout_${KLAYOUT_VERSION}-1_amd64.deb + pip install klayout==${KLAYOUT_VERSION} + + - name: Install magic VLSI + run: | + sudo apt-get install -y m4 python3 libx11-dev tcl-dev tk-dev libcairo2-dev mesa-common-dev libglu1-mesa-dev + git clone -b ${MAGIC_VERSION} https://github.com/RTimothyEdwards/magic magic + cd magic + ./configure + make -j4 + sudo make install + + - name: Install xpath + run: sudo apt-get install -y libxml-xpath-perl + + - name: Install Sky130 PDK + uses: TinyTapeout/volare-action@v1 + with: + pdk_name: sky130 + pdk_version: ${{ env.SKY130_PDK_VERSION }} + pdk_root: ${{ env.PDK_ROOT }} + + - name: Download artifact (run id = ${{ github.event.workflow_run.id }}) + uses: dawidd6/action-download-artifact@v3 + id: download_artifact + with: + workflow: gds.yaml + run_id: ${{ github.event.workflow_run.id }} + workflow_conclusion: success + name: efabless_submission + path: tinytapeout-submission + + - name: Create final .oas file + working-directory: ${{ env.CARAVEL_ROOT }} + run: | + sed -i 's/@cp -r /#@cp -r /' Makefile + cp ../tinytapeout-submission/gds/openframe_project_wrapper.gds gds + make openframe + make generate_fill + make final + strm2oas gds/caravel_$USER_ID.gds caravel_$USER_ID.oas + + - name: FOM Density DRC + working-directory: ${{ env.CARAVEL_ROOT }} + env: + REPORT_FILE: ${{ env.DRC_ROOT }}/klayout_fom_density_report.xml + run: | + klayout -b -r $DRC_ROOT/fom_density.drc -rd gds_input=gds/caravel_$USER_ID.gds -rd top_cell=caravel_$USER_ID -rd report_file=$REPORT_FILE + python ${{ github.workspace }}/sky130_tapeout_drc/check_drc_report.py $REPORT_FILE + + - name: Poly Density DRC + working-directory: ${{ env.CARAVEL_ROOT }} + env: + REPORT_FILE: ${{ env.DRC_ROOT }}/klayout_poly_density_report.xml + run: | + klayout -b -r $DRC_ROOT/poly_density.drc -rd gds_input=gds/caravel_$USER_ID.gds -rd top_cell=caravel_$USER_ID -rd report_file=$REPORT_FILE + python ${{ github.workspace }}/sky130_tapeout_drc/check_drc_report.py $REPORT_FILE + + - name: Tiled Metal Density DRC + working-directory: ${{ env.CARAVEL_ROOT }} + env: + REPORT_FILE: ${{ env.DRC_ROOT }}/klayout_tiled_met_density_report.xml + run: | + klayout -b -r $DRC_ROOT/met_density_tiled.drc -rd gds_input=gds/caravel_$USER_ID.gds -rd top_cell=caravel_$USER_ID -rd report_file=$REPORT_FILE + python ${{ github.workspace }}/sky130_tapeout_drc/check_drc_report.py $REPORT_FILE + + - name: Publish final .gds file + uses: actions/upload-artifact@v4 + if: always() + with: + name: final_gds + path: caravel/gds/caravel_*.gds + + - name: Publish final .oas file + uses: actions/upload-artifact@v4 + if: always() + with: + name: final_oas + path: caravel/caravel_*.oas + + - name: Publish DRC reports + uses: actions/upload-artifact@v4 + if: always() + with: + name: drc_reports + path: ${{ env.DRC_ROOT }}/klayout_*_report.xml diff --git a/sky130_tapeout_drc/check_drc_report.py b/sky130_tapeout_drc/check_drc_report.py new file mode 100644 index 00000000..4552cf33 --- /dev/null +++ b/sky130_tapeout_drc/check_drc_report.py @@ -0,0 +1,12 @@ +import klayout.rdb as rdb +import sys + +report_file = sys.argv[1] + +report = rdb.ReportDatabase("DRC") +report.load(report_file) + +if report.num_items() > 0: + raise RuntimeError(f"Klayout DRC failed with {report.num_items()} DRC violations") +else: + print("Klayout DRC passed") diff --git a/sky130_tapeout_drc/fom_density.drc b/sky130_tapeout_drc/fom_density.drc new file mode 100644 index 00000000..bfd903b4 --- /dev/null +++ b/sky130_tapeout_drc/fom_density.drc @@ -0,0 +1,162 @@ + +errs = 0 +log("fom_density.drc:: sourcing design file=#{$gds_input} topcell=#{$top_cell} ...") +src = source($gds_input, $top_cell) +layout = src.layout +log("done.") + + +report("Density Checks", $report_file) + +verbose(false) + +diff_wildcard = "65/20" +tap_wildcard = "65/44" +fomfill_wildcard = "65,23/28" +fommk_wildcard = "23/0" + +#$chip_boundary = input(235,4) +log("flattening chip boundary...") +$chip_boundary = input(235,4).flatten +log("done.") + +$bbox = $chip_boundary.bbox + +window_size = 700 + +if $step + step_size = $step.to_f +else + step_size = 70 +end +log("step size = #{step_size}") + +llx, lly, urx, ury = $bbox.left, $bbox.bottom, $bbox.right, $bbox.top +log("llx=#{llx} lly=#{lly} urx=#{urx} ury=#{ury}") +if urx-llx <= 0 || ury-lly <= 0 + STDERR.puts "ERROR: fom_density.drc: fatal error, chip_boundary bbox empty or malformed" + errs += 1 +end + +cnt = 0 +x_cnt = ((urx-step_size-llx) / step_size).ceil() +y_cnt = ((ury-step_size-lly) / step_size ).ceil() +tot = x_cnt * y_cnt +$fom_area_map = array = Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } } +$tile_area_map = array = Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } } + +log("x_cnt = #{x_cnt}") +log("y_cnt = #{y_cnt}") + +if x_cnt < 1 || y_cnt < 1 + STDERR.puts "ERROR: fom_density.drc: fatal error, x-count or y-count < 1: #{x_cnt} #{y_cnt}" + errs += 1 +end + +# any errors till now?: abort. +if errs > 0 + exit 1 +end + +class FomArea < RBA::TileOutputReceiver + def put(ix, iy, tile, obj, dbu, clip) + if tile + fom_area_scaled = obj.to_f * dbu * dbu + #tile_area_scaled = tile.area * dbu * dbu + tile_area_scaled = (tile & ($bbox * (1.0 / dbu))).area * dbu * dbu + #puts "got area for tile #{ix+1},#{iy+1}: #{fom_area_scaled.to_s} #{tile_area_scaled.to_s}" + $fom_area_map[ix][iy] += fom_area_scaled + $tile_area_map[ix][iy] = tile_area_scaled + else + puts "empty tile #{ix+1},#{iy+1}" + end + end + def finish(success) + puts "finish received: success = #{success}" + end + +end + +tp = RBA::TilingProcessor::new + +# register the custom receiver +tp.output("my_receiver", FomArea::new) + +tp.input("diff", layout.top_cell().begin_shapes_rec(layout.layer(65,20) ) ) +tp.input("tap", layout.top_cell().begin_shapes_rec(layout.layer(65,44) ) ) +tp.input("fomfill", layout.top_cell().begin_shapes_rec(layout.layer(23,28) ) ) + +tp.frame=$bbox +tp.tile_size(step_size, step_size) # 70x70 um tile size +log("dbu = #{layout.dbu}") +log("bbox_area = #{$bbox.area}") +tp.dbu = layout.dbu +tp.threads = $thr.to_i +# tp.scale_to_dbu = false + +# The script clips the input at the tile and computes the (merged) area: +tp.queue("_output(my_receiver, _tile && ((diff | tap | fomfill) & _tile).area)") + +log("calculating subtile areas (= #{tot})...") +tp.execute("Job description") + +cnt = 0 +big_x_cnt = ((urx-window_size-llx) / step_size).ceil() +big_y_cnt = ((ury-window_size-lly) / step_size).ceil() +big_tot = big_x_cnt * big_y_cnt +tiles_per_step = (window_size / step_size).ceil() +log("tiles per step = #{tiles_per_step}") +log("calculating window step densities (= #{big_tot})...") +markers_min = polygon_layer +markers_max = polygon_layer +min_err = 0 +max_err = 0 +min_density = 1 +max_density = 0 +for x in (0 ... big_x_cnt) + log("{{ CHECK }} #{cnt}/#{big_tot.round}") + for y in (0 ... big_y_cnt) + fom_area = 0 + tile_area = 0 + fom_density = 0 + #$fom_area_map.slice(x, tiles_per_step).each { |a| a.slice(y, tiles_per_step).each { |b| puts "#{x},#{y} = #{b}" } } + $fom_area_map.slice(x, tiles_per_step).each { |a| a.slice(y, tiles_per_step).each { |b| fom_area+=b } } + $tile_area_map.slice(x, tiles_per_step).each { |a| a.slice(y, tiles_per_step).each { |b| tile_area+=b } } + marker_box = box(x * step_size, y* step_size, (x+tiles_per_step)*step_size, (y+tiles_per_step)*step_size) + if tile_area > 0 + fom_density = fom_area / tile_area + #log("fom density #{cnt}/#{big_tot.round} is #{fom_density} for area #{tile_area}") + if fom_density < min_density + min_density = fom_density + end + if fom_density > max_density + max_density = fom_density + end + if fom_density < 0.33 + min_err += 1 + markers_min.insert(marker_box) + log("fom density below 0.28 : #{cnt}/#{big_tot.round} is #{"%.4f" % fom_density} for tile (#{marker_box.to_s}), area = #{tile_area.round}") + elsif fom_density > 0.57 + max_err += 1 + markers_max.insert(marker_box) + log("fom density above 0.57 : #{cnt}/#{big_tot.round} is #{"%.4f" % fom_density} for tile (#{marker_box.to_s}), area = #{tile_area.round}") + end + end + cnt += 1 + end +end + +log("minimum fom density = #{"%.4f" % min_density}") +log("maximum fom density = #{"%.4f" % max_density}") + +if min_err > 0 + log("cfom.pd.1d violations = #{min_err}") + markers_min.output("cfom.pd.1d", "0.33 min fom pattern density") +end + +if max_err > 0 + log("cfom.pd.1e violations = #{max_err}") + markers_max.output("cfom.pd.1e", "0.57 max fom pattern density") +end + + diff --git a/sky130_tapeout_drc/met_density_tiled.drc b/sky130_tapeout_drc/met_density_tiled.drc new file mode 100644 index 00000000..989c2500 --- /dev/null +++ b/sky130_tapeout_drc/met_density_tiled.drc @@ -0,0 +1,249 @@ +# the sizing function below in the case statement is added to replicate Skywater's new metal oxide density check. +# It is in DBU where 600=0.6um actual. Skywater uses 0.6um for M1 and M2, 1.15um for M3, M4, and M5. I have +# decided to use M1= 425, M2=375, M3=600, and M4=550 after empiracally testing shuttle 2406. This set of +# numbers give some positive margin to the Skywater results. These sizing numbers could be adjust back to +# Skywater values if a die were to fail in the future by a slim margin. DLindley, July 31, 2024 +# +# Added M5 check. Per Nick Jones of Skywater, sizing is the same as M3/M4 +# Removed M5 check. It is only required when viatop layer is present, which it is not in sky130. DL 9-12-24 + +errs = 0 +log("met_tiled_density.drc:: sourcing design file=#{$gds_input} topcell=#{$top_cell} ...") +src = source($gds_input, $top_cell) +layout = src.layout +log("done.") + +report("Tiled Metal Density Checks", $report_file) + +verbose(false) + +log("flattening chip boundary...") +$chip_boundary = input(235,4).flatten +log("done.") + +$bbox = $chip_boundary.bbox + +window_size = 700 + +if $step + step_size = $step.to_f +else + step_size = 70 +end +log("step size = #{step_size}") + +llx, lly, urx, ury = $bbox.left, $bbox.bottom, $bbox.right, $bbox.top +log("llx=#{llx} lly=#{lly} urx=#{urx} ury=#{ury}") +if urx-llx <= 0 || ury-lly <= 0 + STDERR.puts "ERROR: met_tiled_density.drc: fatal error, chip_boundary bbox empty or malformed" + errs += 1 +end + +cnt = 0 +x_cnt = ((urx-step_size-llx) / step_size).ceil() +y_cnt = ((ury-step_size-lly) / step_size ).ceil() +tot = x_cnt * y_cnt +#$met_tiled_area_map = array = Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } } +#$tile_area_map = array = Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } } +$met_tiled_area_map = { 'm1' => Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } }, + 'm2' => Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } }, + 'm3' => Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } }, + 'm4' => Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } } } +$tile_area_map = { 'm1' => Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } }, + 'm2' => Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } }, + 'm3' => Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } }, + 'm4' => Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } } } + +log("x_cnt = #{x_cnt}") +log("y_cnt = #{y_cnt}") + +if x_cnt < 1 || y_cnt < 1 + STDERR.puts "ERROR: met_tiled_density.drc: fatal error, x-count or y-count < 1: #{x_cnt} #{y_cnt}" + errs += 1 +end + +# any errors till now?: abort. +if errs > 0 + exit 1 +end + +class Met1Area < RBA::TileOutputReceiver + def put(ix, iy, tile, obj, dbu, clip) + if tile + met_tiled_area_scaled = obj.to_f * dbu * dbu + tile_area_scaled = (tile & ($bbox * (1.0 / dbu))).area * dbu * dbu + $met_tiled_area_map['m1'][ix][iy] += met_tiled_area_scaled + $tile_area_map['m1'][ix][iy] = tile_area_scaled + else + puts "empty tile m1: #{ix+1},#{iy+1}" + end + end + def finish(success) + puts "finish received: success = #{success}" + end + +end + + +class Met2Area < RBA::TileOutputReceiver + def put(ix, iy, tile, obj, dbu, clip) + if tile + met_tiled_area_scaled = obj.to_f * dbu * dbu + tile_area_scaled = (tile & ($bbox * (1.0 / dbu))).area * dbu * dbu + $met_tiled_area_map['m2'][ix][iy] += met_tiled_area_scaled + $tile_area_map['m2'][ix][iy] = tile_area_scaled + else + puts "empty tile m2: #{ix+1},#{iy+1}" + end + end + def finish(success) + puts "finish received: success = #{success}" + end + +end + + +class Met3Area < RBA::TileOutputReceiver + def put(ix, iy, tile, obj, dbu, clip) + if tile + met_tiled_area_scaled = obj.to_f * dbu * dbu + tile_area_scaled = (tile & ($bbox * (1.0 / dbu))).area * dbu * dbu + $met_tiled_area_map['m3'][ix][iy] += met_tiled_area_scaled + $tile_area_map['m3'][ix][iy] = tile_area_scaled + else + puts "empty tile m3: #{ix+1},#{iy+1}" + end + end + def finish(success) + puts "finish received: success = #{success}" + end + +end + + +class Met4Area < RBA::TileOutputReceiver + def put(ix, iy, tile, obj, dbu, clip) + if tile + met_tiled_area_scaled = obj.to_f * dbu * dbu + tile_area_scaled = (tile & ($bbox * (1.0 / dbu))).area * dbu * dbu + $met_tiled_area_map['m4'][ix][iy] += met_tiled_area_scaled + $tile_area_map['m4'][ix][iy] = tile_area_scaled + else + puts "empty tile m4: #{ix+1},#{iy+1}" + end + end + def finish(success) + puts "finish received: success = #{success}" + end + +end + +tp = { 'm1' => RBA::TilingProcessor::new, + 'm2' => RBA::TilingProcessor::new, + 'm3' => RBA::TilingProcessor::new, + 'm4' => RBA::TilingProcessor::new } + +# register the custom receiver +tp['m1'].output("my_receiver", Met1Area::new) +tp['m2'].output("my_receiver", Met2Area::new) +tp['m3'].output("my_receiver", Met3Area::new) +tp['m4'].output("my_receiver", Met4Area::new) + +tp.keys.each do |t| + tp[t].frame=$bbox + tp[t].tile_size(step_size, step_size) # 70x70 um tile size + tp[t].dbu = layout.dbu + tp[t].threads = $thr.to_i +end + +log("dbu = #{layout.dbu}") +log("bbox_area = #{$bbox.area}") + +log("calculating subtile areas (= #{tot})...") + +cnt = 0 +big_x_cnt = ((urx-window_size-llx) / step_size).ceil() +big_y_cnt = ((ury-window_size-lly) / step_size).ceil() +big_tot = big_x_cnt * big_y_cnt +tiles_per_step = (window_size / step_size).ceil() +log("tiles per step = #{tiles_per_step}") +log("calculating window step densities (= #{big_tot})...") +markers_min = polygon_layer +markers_max = polygon_layer + + +checks = ['m1','m2','m3','m4'] + +min_err = { 'm1' => 0, 'm2' => 0, 'm3' => 0, 'm4' => 0 } +max_err = { 'm1' => 0, 'm2' => 0, 'm3' => 0, 'm4' => 0 } +min_density = { 'm1' => 1.0, 'm2' => 1.0, 'm3' => 1.0, 'm4' => 1.0 } +max_density = { 'm1' => 0.0, 'm2' => 0.0, 'm3' => 0.0, 'm4' => 0.0 } + +checks.each do |m| + case m + when 'm1' + #$met_tiled_area_map.map { |a| a.map {0} } + #$tile_area_map.map { |a| a.map {0} } + tp[m].input("m1", layout.top_cell().begin_shapes_rec(layout.layer(68,20) ) ) + tp[m].input("m1fill", layout.top_cell().begin_shapes_rec(layout.layer(36,28) ) ) + tp[m].queue("_output(my_receiver, _tile && ((m1.sized(425) | m1fill.sized(425)) & _tile).area)") + tp[m].execute("Job description") + when 'm2' + #$met_tiled_area_map.map { |a| a.map {0} } + #$tile_area_map.map { |a| a.map {0} } + tp[m].input("m2", layout.top_cell().begin_shapes_rec(layout.layer(69,20) ) ) + tp[m].input("m2fill", layout.top_cell().begin_shapes_rec(layout.layer(41,28) ) ) + tp[m].queue("_output(my_receiver, _tile && ((m2.sized(375) | m2fill.sized(375)) & _tile).area)") + tp[m].execute("Job description") + when 'm3' + #$met_tiled_area_map.map { |a| a.map {0} } + #$tile_area_map.map { |a| a.map {0} } + tp[m].input("m3", layout.top_cell().begin_shapes_rec(layout.layer(70,20) ) ) + tp[m].input("m3fill", layout.top_cell().begin_shapes_rec(layout.layer(34,28) ) ) + tp[m].queue("_output(my_receiver, _tile && ((m3.sized(600) | m3fill.sized(600)) & _tile).area)") + tp[m].execute("Job description") + when 'm4' + #$met_tiled_area_map.map { |a| a.map {0} } + #$tile_area_map.map { |a| a.map {0} } + tp[m].input("m4", layout.top_cell().begin_shapes_rec(layout.layer(71,20) ) ) + tp[m].input("m4fill", layout.top_cell().begin_shapes_rec(layout.layer(51,28) ) ) + tp[m].queue("_output(my_receiver, _tile && ((m4.sized(550) | m4fill.sized(550)) & _tile).area)") + tp[m].execute("Job description") + end + for x in (0 ... big_x_cnt) + #log("{{ CHECK }} #{cnt}/#{big_tot.round}") + for y in (0 ... big_y_cnt) + met_tiled_area = 0 + tile_area = 0 + met_tiled_density = 0 + $met_tiled_area_map[m].slice(x, tiles_per_step).each { |a| a.slice(y, tiles_per_step).each { |b| met_tiled_area+=b } } + $tile_area_map[m].slice(x, tiles_per_step).each { |a| a.slice(y, tiles_per_step).each { |b| tile_area+=b } } + marker_box = box(x * step_size, y* step_size, (x+tiles_per_step)*step_size, (y+tiles_per_step)*step_size) + if tile_area > 0 + met_tiled_density = met_tiled_area / tile_area + if met_tiled_density < min_density[m] + min_density[m] = met_tiled_density + end + if met_tiled_density > max_density[m] + max_density[m] = met_tiled_density + end + if met_tiled_density < 0.80 + max_err[m] += 1 + markers_max.insert(marker_box) + log("#{m} met_tiled density greater than 0.80 : #{cnt}/#{big_tot.round} is #{"%.4f" % met_tiled_density} for tile (#{marker_box.to_s}), area = #{tile_area.round}") + end + end + cnt += 1 + end + end + + log("#{m} minimum met_tiled density = #{"%.4f" % min_density[m]}") + log("#{m} maximum met_tiled density = #{"%.4f" % max_density[m]}") + + if max_err[m] > 0 + log("#{m} met_tiled.pd violations = #{max_err}") + markers_max.output("#{m}.met_tiled.pd", "0.80 max met_tiled pattern density") + end + +end + diff --git a/sky130_tapeout_drc/poly_density.drc b/sky130_tapeout_drc/poly_density.drc new file mode 100644 index 00000000..cd67c94f --- /dev/null +++ b/sky130_tapeout_drc/poly_density.drc @@ -0,0 +1,159 @@ + +errs = 0 +log("poly_density.drc:: sourcing design file=#{$gds_input} topcell=#{$top_cell} ...") +src = source($gds_input, $top_cell) +layout = src.layout +log("done.") + + +report("Density Checks", $report_file) + +verbose(false) + +diff_wildcard = "65/20" +tap_wildcard = "65/44" +polyfill_wildcard = "65,23/28" +polymk_wildcard = "23/0" + +#$chip_boundary = input(235,4) +log("flattening chip boundary...") +$chip_boundary = input(235,4).flatten +log("done.") + +$bbox = $chip_boundary.bbox + +window_size = 700 + +if $step + step_size = $step.to_f +else + step_size = 70 +end +log("step size = #{step_size}") + +llx, lly, urx, ury = $bbox.left, $bbox.bottom, $bbox.right, $bbox.top +log("llx=#{llx} lly=#{lly} urx=#{urx} ury=#{ury}") +if urx-llx <= 0 || ury-lly <= 0 + STDERR.puts "ERROR: poly_density.drc: fatal error, chip_boundary bbox empty or malformed" + errs += 1 +end + +cnt = 0 +x_cnt = ((urx-step_size-llx) / step_size).ceil() +y_cnt = ((ury-step_size-lly) / step_size ).ceil() +tot = x_cnt * y_cnt +$poly_area_map = array = Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } } +$tile_area_map = array = Array.new(x_cnt+1) { Array.new(y_cnt+1) { 0.0 } } + +log("x_cnt = #{x_cnt}") +log("y_cnt = #{y_cnt}") + +if x_cnt < 1 || y_cnt < 1 + STDERR.puts "ERROR: poly_density.drc: fatal error, x-count or y-count < 1: #{x_cnt} #{y_cnt}" + errs += 1 +end + +# any errors till now?: abort. +if errs > 0 + exit 1 +end + +class PolyArea < RBA::TileOutputReceiver + def put(ix, iy, tile, obj, dbu, clip) + if tile + poly_area_scaled = obj.to_f * dbu * dbu + #tile_area_scaled = tile.area * dbu * dbu + tile_area_scaled = (tile & ($bbox * (1.0 / dbu))).area * dbu * dbu + #puts "got area for tile #{ix+1},#{iy+1}: #{poly_area_scaled.to_s} #{tile_area_scaled.to_s}" + $poly_area_map[ix][iy] += poly_area_scaled + $tile_area_map[ix][iy] = tile_area_scaled + else + puts "empty tile #{ix+1},#{iy+1}" + end + end + def finish(success) + puts "finish received: success = #{success}" + end + +end + +tp = RBA::TilingProcessor::new + +# register the custom receiver +tp.output("my_receiver", PolyArea::new) + +tp.input("poly", layout.top_cell().begin_shapes_rec(layout.layer(66,20) ) ) +tp.input("polyfill", layout.top_cell().begin_shapes_rec(layout.layer(28,28) ) ) + +tp.frame=$bbox +tp.tile_size(step_size, step_size) # 70x70 um tile size +log("dbu = #{layout.dbu}") +log("bbox_area = #{$bbox.area}") +tp.dbu = layout.dbu +tp.threads = $thr.to_i +# tp.scale_to_dbu = false + +# The script clips the input at the tile and computes the (merged) area: +tp.queue("_output(my_receiver, _tile && ((poly | polyfill) & _tile).area)") + +log("calculating subtile areas (= #{tot})...") +tp.execute("Job description") + +cnt = 0 +big_x_cnt = ((urx-window_size-llx) / step_size).ceil() +big_y_cnt = ((ury-window_size-lly) / step_size).ceil() +big_tot = big_x_cnt * big_y_cnt +tiles_per_step = (window_size / step_size).ceil() +log("tiles per step = #{tiles_per_step}") +log("calculating window step densities (= #{big_tot})...") +markers_min = polygon_layer +markers_max = polygon_layer +min_err = 0 +max_err = 0 +min_density = 1 +max_density = 0 +for x in (0 ... big_x_cnt) + log("{{ CHECK }} #{cnt}/#{big_tot.round}") + for y in (0 ... big_y_cnt) + poly_area = 0 + tile_area = 0 + poly_density = 0 + #$poly_area_map.slice(x, tiles_per_step).each { |a| a.slice(y, tiles_per_step).each { |b| puts "#{x},#{y} = #{b}" } } + $poly_area_map.slice(x, tiles_per_step).each { |a| a.slice(y, tiles_per_step).each { |b| poly_area+=b } } + $tile_area_map.slice(x, tiles_per_step).each { |a| a.slice(y, tiles_per_step).each { |b| tile_area+=b } } + marker_box = box(x * step_size, y* step_size, (x+tiles_per_step)*step_size, (y+tiles_per_step)*step_size) + if tile_area > 0 + poly_density = poly_area / tile_area + #log("poly density #{cnt}/#{big_tot.round} is #{poly_density} for area #{tile_area}") + if poly_density < min_density + min_density = poly_density + end + if poly_density > max_density + max_density = poly_density + end + if poly_density < 0.08 + min_err += 1 + markers_min.insert(marker_box) + log("poly density below 0.08 : #{cnt}/#{big_tot.round} is #{"%.4f" % poly_density} for tile (#{marker_box.to_s}), area = #{tile_area.round}") + elsif poly_density > 0.38 + max_err += 1 + markers_max.insert(marker_box) + log("poly density above 0.38 : #{cnt}/#{big_tot.round} is #{"%.4f" % poly_density} for tile (#{marker_box.to_s}), area = #{tile_area.round}") + end + end + cnt += 1 + end +end + +log("minimum poly density = #{"%.4f" % min_density}") +log("maximum poly density = #{"%.4f" % max_density}") + +if min_err > 0 + log("cpoly.pd.1d violations = #{min_err}") + markers_min.output("cpoly.pd.1d", "0.08 min poly pattern density") +end + +if max_err > 0 + log("cpoly.pd.1e violations = #{max_err}") + markers_max.output("cpoly.pd.1e", "0.38 max poly pattern density") +end