Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gpu_metal_fill/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build/
*.o
*.gds
*.ppm
67 changes: 67 additions & 0 deletions gpu_metal_fill/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Metal Fill (FEOL/BEOL) — recursive-partitioned dummy fill.
#
# Build is CPU (OpenMP-parallel across partitions). The compute-heavy stages are
# isolated behind FillBackend (see include/metalfill/backend.hpp) so a CUDA
# backend can be dropped in later (GPU offload is future work).

CXX ?= g++
OPT ?= -O2
CXXFLAGS ?= -std=c++17 $(OPT) -Wall -Wextra -Iinclude
LDFLAGS ?=

# OpenMP (parallel partitions / fill). Disable with `make OPENMP=0`.
OPENMP ?= 1
ifeq ($(OPENMP),1)
CXXFLAGS += -fopenmp
LDFLAGS += -fopenmp
endif

BUILD := build
LIB_SRC := $(wildcard src/*.cpp)
LIB_OBJ := $(patsubst src/%.cpp,$(BUILD)/%.o,$(LIB_SRC))

TOOLS := $(BUILD)/make_dummy_gds $(BUILD)/make_gpu_block $(BUILD)/run_fill $(BUILD)/render_layer \
$(BUILD)/gdsinfo
TESTS := $(BUILD)/test_main

.PHONY: all tools test demo clean
all: tools
tools: $(TOOLS)

$(BUILD):
mkdir -p $(BUILD)

$(BUILD)/%.o: src/%.cpp | $(BUILD)
$(CXX) $(CXXFLAGS) -c $< -o $@

$(BUILD)/make_dummy_gds: tools/make_dummy_gds.cpp $(LIB_OBJ) | $(BUILD)
$(CXX) $(CXXFLAGS) $< $(LIB_OBJ) -o $@ $(LDFLAGS)

$(BUILD)/make_gpu_block: tools/make_gpu_block.cpp $(LIB_OBJ) | $(BUILD)
$(CXX) $(CXXFLAGS) $< $(LIB_OBJ) -o $@ $(LDFLAGS)

$(BUILD)/run_fill: tools/run_fill.cpp $(LIB_OBJ) | $(BUILD)
$(CXX) $(CXXFLAGS) $< $(LIB_OBJ) -o $@ $(LDFLAGS)

$(BUILD)/render_layer: tools/render_layer.cpp $(LIB_OBJ) | $(BUILD)
$(CXX) $(CXXFLAGS) $< $(LIB_OBJ) -o $@ $(LDFLAGS)

$(BUILD)/gdsinfo: tools/gdsinfo.cpp $(LIB_OBJ) | $(BUILD)
$(CXX) $(CXXFLAGS) $< $(LIB_OBJ) -o $@ $(LDFLAGS)

$(BUILD)/test_main: tests/test_main.cpp $(LIB_OBJ) | $(BUILD)
$(CXX) $(CXXFLAGS) $< $(LIB_OBJ) -o $@ $(LDFLAGS)

test: $(TESTS)
$(BUILD)/test_main

demo: tools
$(BUILD)/make_dummy_gds -o $(BUILD)/dummy.gds
$(BUILD)/run_fill -i $(BUILD)/dummy.gds -o $(BUILD)/filled.gds -r $(BUILD)/report.txt

gpu-demo: tools
$(BUILD)/make_gpu_block -o $(BUILD)/gpu_block.gds
$(BUILD)/run_fill -i $(BUILD)/gpu_block.gds -o $(BUILD)/gpu_filled.gds -r $(BUILD)/gpu_report.txt

clean:
rm -rf $(BUILD)
111 changes: 111 additions & 0 deletions gpu_metal_fill/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Metal Fill (FEOL / BEOL) — recursive-partitioned dummy fill

> For a thorough design write-up (motivation, every algorithm, the partitioning
> correctness argument, bugs fixed, and results) see **[WHITEPAPER.md](WHITEPAPER.md)**
> (also available as a PDF: [docs/metal_fill_whitepaper.pdf](docs/metal_fill_whitepaper.pdf)).

A small, dependency-free C++17 engine that inserts **dummy metal fill** into a
GDSII layout so that every layer meets CMP density rules. It processes the die by
**recursive quadtree partitioning** (like commercial fill flows) so leaves can be
filled independently and in parallel, then merged.

> Status: CPU implementation (OpenMP-parallel across partitions). The heavy
> stages are isolated behind a `FillBackend` interface so a CUDA/GPU backend can
> be added later. GPU offload is intentionally future work.

## Why dummy fill?

Chemical-Mechanical Polishing (CMP) needs a **uniform** metal density across the
die. Sparse regions dish/erode differently from dense regions, hurting yield and
timing. Foundries therefore require, per layer and per density window:

- a **minimum** density (add fill where too sparse),
- a **maximum** density (don't over-fill),
- a bounded **window-to-window gradient** (no abrupt steps),
- **spacing** from existing geometry (a keep-out halo), and a min fill area.

## Pipeline

For each layer (FEOL base: `OD`, `PO`; BEOL metals: `M1..M14`):

1. **Rasterize** existing geometry into an occupancy grid.
2. **Density** via a summed-area table (integral image): O(1) per window.
3. **Keep-out** = morphological dilation of geometry by the spacing rule.
4. **Fill** on a pitch lattice, per window, up to a per-window target density,
never exceeding max and never entering the keep-out halo.
5. **Iterative DRC**: measure achieved density, then raise the target of any
window that is still below `min` or too far below a denser neighbor
(gradient), and refill. Targets increase monotonically, so it converges.

### Recursive partitioning (the scaling trick)

The die is split by a **quadtree** into leaf partitions that are filled
independently (OpenMP now, GPU later) and then merged by concatenation.

Correctness at partition boundaries is guaranteed by two things:

- **Lattice-aligned cuts**: partitions are cut on a lattice equal to
`lcm(density_window, fill_pitch)`, so a density window and every fill position
is identical whether computed globally or per-partition.
- **Halo / guard band**: each leaf reads a ring of neighboring geometry as
read-only context (so density and keep-out are exact at the edge) but only
*emits* fill inside its **core**. Cores are disjoint and tile the die, so
merging is a plain concatenation — no de-duplication or stitching.

A unit test (`partition == global`) asserts the merged result is bit-for-bit
equivalent to a single-shot global fill.

## Build & run

```sh
make # builds tools into build/
make test # builds and runs the unit tests
make demo # simple synthetic layout -> filled.gds + report.txt
make gpu-demo # realistic GPU-block layout -> gpu_filled.gds + gpu_report.txt

# manual
build/make_gpu_block -o build/gpu_block.gds # synthetic GPU block
build/run_fill -i build/gpu_block.gds -o build/gpu_filled.gds -r build/report.txt
build/render_layer -i build/gpu_filled.gds -o build/m1.ppm -l 10 # visualize M1
# convert the PPM to PNG if you like: ffmpeg -i build/m1.ppm build/m1.png

# confirm fill happened: per-(layer,datatype) polygon counts. Fill = datatype 10.
build/gdsinfo build/gpu_block.gds build/gpu_filled.gds
```

### The GPU-block test design

`make_gpu_block` generates a synthetic — but structurally realistic — GPU block
(it is **not** a real design, just the floorplan-level density structure):

- an `NxN` grid of SM (streaming-multiprocessor) tiles, each with two SRAM
macros (register file + shared memory) and a standard-cell logic region,
- routing channels between tiles (bus routing on mid metals),
- global clock/spine routes (M9/M10), and
- a regular, sparse power grid on the top metals (M11..M14).

Each layer therefore has a distinct density profile — dense macros/logic on the
lower layers, near-empty upper layers with only power straps — so fill has varied
work on every layer.

`make OPENMP=0` builds a serial version.

## Layout of the code

| File | Responsibility |
|------|----------------|
| `include/metalfill/geometry.hpp` | points, bbox, polygons (integer dbu) |
| `gdsii.{hpp,cpp}` | minimal GDSII reader/writer (BOUNDARY/BOX) |
| `raster.{hpp,cpp}` | polygon scanline rasterization |
| `density.{hpp,cpp}` | summed-area table + windowed density |
| `fill.{hpp,cpp}` | keep-out dilation + per-window fill placement |
| `partition.{hpp,cpp}` | recursive quadtree partitioner |
| `drc.{hpp,cpp}` | density / gradient / spacing / min-area checks |
| `backend.hpp`, `backend_cpu.cpp` | compute backend (CPU/OpenMP; CUDA later) |
| `engine.{hpp,cpp}` | orchestration: partition → fill → iterate → merge |
| `layermap.{hpp,cpp}` | default GDS layers + fill rules |
| `tools/` | `make_dummy_gds`, `make_gpu_block`, `run_fill`, `render_layer`, `gdsinfo` |
| `tests/test_main.cpp` | unit + end-to-end tests |

The default layer map and rules are illustrative, **not** a real PDK; edit
`layermap.cpp` to match your technology.
Loading