diff --git a/PERC Runtime Optimizer/.gitignore b/PERC Runtime Optimizer/.gitignore
new file mode 100644
index 0000000..a371336
--- /dev/null
+++ b/PERC Runtime Optimizer/.gitignore
@@ -0,0 +1,3 @@
+perc_optimizer
+test_perc
+*.o
diff --git a/PERC Runtime Optimizer/Makefile b/PERC Runtime Optimizer/Makefile
new file mode 100644
index 0000000..f4ac162
--- /dev/null
+++ b/PERC Runtime Optimizer/Makefile
@@ -0,0 +1,34 @@
+CXX ?= g++
+CXXFLAGS ?= -std=c++17 -O2 -Wall -Wextra -pthread -Iinclude
+LDFLAGS ?= -pthread
+
+SRC := src/design.cpp src/generator.cpp src/checks.cpp src/metadata.cpp src/engine.cpp
+OBJ := $(SRC:.cpp=.o)
+
+.PHONY: all clean test bench run
+
+all: perc_optimizer test_perc
+
+perc_optimizer: $(OBJ) src/main.o
+ $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
+
+test_perc: $(OBJ) tests/test_perc.o
+ $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
+
+src/%.o: src/%.cpp
+ $(CXX) $(CXXFLAGS) -c -o $@ $<
+
+tests/%.o: tests/%.cpp
+ $(CXX) $(CXXFLAGS) -c -o $@ $<
+
+run: perc_optimizer
+ ./perc_optimizer --pads 24 --blocks 6 --devices 200
+
+bench: perc_optimizer
+ ./perc_optimizer --bench --eco --workers $$(nproc 2>/dev/null || echo 4)
+
+test: test_perc
+ ./test_perc
+
+clean:
+ rm -f $(OBJ) src/main.o tests/test_perc.o perc_optimizer test_perc
diff --git a/PERC Runtime Optimizer/README.md b/PERC Runtime Optimizer/README.md
new file mode 100644
index 0000000..e2ab95e
--- /dev/null
+++ b/PERC Runtime Optimizer/README.md
@@ -0,0 +1,62 @@
+# PERC Runtime Optimizer (C++)
+
+Educational C++17 project: understand **PERC** (Programmable Electrical Rules Checking) in physical design, and measure algorithmic ways to cut runtime.
+
+- **White paper (PDF):** [docs/PERC_Runtime_Optimizer_Whitepaper.pdf](docs/PERC_Runtime_Optimizer_Whitepaper.pdf)
+- Concept notes: [docs/PERC_OVERVIEW.md](docs/PERC_OVERVIEW.md)
+
+Regenerate the PDF: `python3 docs/generate_whitepaper.py` (requires `reportlab`).
+
+## What is implemented
+
+Synthetic hierarchical netlist + resistor-mesh stand-in for extracted parasitics, then PERC-like rules:
+
+| Check | Rule id |
+|-------|---------|
+| ESD clamp present on every IO pad | `ESD_CLAMP_MISSING` |
+| Floating MOSFET gates | `FLOATING_GATE` |
+| Pad→rail point-to-point resistance | `P2P_RESISTANCE_HIGH` / `P2P_PATH_MISSING` |
+| Simplified current-density along ESD path | `CURRENT_DENSITY` |
+
+Engines:
+
+| Mode | Idea |
+|------|------|
+| `baseline` | Full-chip, every rule, every time |
+| `roi` | Prune floating-gate work to ESD-relevant devices |
+| `hierarchical` | Per-block floating-gate + chip-level ESD/P2P/CD |
+| `parallel` | Multi-threaded pad-pair P2P/CD (`std::async`) |
+| `incremental` | Metadata cache keyed by scope fingerprint |
+| `optimized` | Hierarchical ROI + cache + parallel P2P/CD |
+
+This is **not** Calibre/ICV and does not run foundry decks. Geometry/extraction are abstracted as a weighted `RGraph` (Dijkstra).
+
+## Build
+
+```bash
+cd "PERC Runtime Optimizer"
+make -j
+make test
+```
+
+## Run
+
+```bash
+./perc_optimizer --pads 32 --blocks 8 --devices 400
+./perc_optimizer --bench --eco --workers 8
+```
+
+Flags: `--pads`, `--blocks`, `--devices`, `--workers`, `--eco`, `--bench`.
+
+## Layout
+
+```
+include/ design, checks, engines, generator, metadata
+src/ implementations + main CLI
+tests/ self-contained assertions
+docs/ PERC concepts
+```
+
+## Optimization takeaway
+
+Cold full-chip walks dominate when every MOSFET and every pad pair is visited. Speedups come from **not redoing unchanged work**: ROI nets, block scopes, fingerprint caches after ECO, and parallel P2P queries — the same levers used in production reliability flows (LDL / metadata reuse / hierarchy / multi-CPU).
diff --git a/PERC Runtime Optimizer/docs/PERC_OVERVIEW.md b/PERC Runtime Optimizer/docs/PERC_OVERVIEW.md
new file mode 100644
index 0000000..25ac5ca
--- /dev/null
+++ b/PERC Runtime Optimizer/docs/PERC_OVERVIEW.md
@@ -0,0 +1,73 @@
+# PERC in Physical Design — Concepts & Why It Is Slow
+
+## What is PERC?
+
+**PERC (Programmable Electrical Rules Checking)** verifies IC reliability issues that **DRC** and **LVS** cannot catch. Rules are programmable because foundries and design houses customize ESD/EOS/ERC methodology per process and product.
+
+Industry tools: **Calibre PERC** (Siemens), **IC Validator PERC** (Synopsys).
+
+PERC sits in **physical verification / reliability signoff**, typically after LVS-clean layout (or on schematic netlists for early checks).
+
+---
+
+## The Four Check Families
+
+| Family | Inputs | What it verifies | Cost driver |
+|--------|--------|------------------|-------------|
+| **Netlist checks** | Schematic or extracted netlist | ESD clamp presence, floating gates, EOS/level-shifter topology, multi-power-domain rules | Graph traversal over huge netlists |
+| **Netlist-driven layout (LDL / NDL)** | Netlist + GDS | Voltage-aware spacing, geometry on *regions of interest* identified from connectivity | Finding ROI + geometry ops |
+| **Current density (CD)** | Layout + R-extraction + ESD path current | Metal can carry ESD current without melting / EM fail | Parasitic R mesh + current solve |
+| **Point-to-point (P2P) resistance** | Layout + R-extraction | ESD discharge path R is below limit so current takes the clamp path | Many source→sink R queries on resistor networks |
+
+All flows start with a **Netlist Analysis Engine** (the “programmable” core). Layout-heavy checks then call extraction (e.g. StarRC) on selected nets/paths.
+
+---
+
+## Typical ESD Flow (why wall-clock explodes)
+
+```
+Netlist / LVS extract
+ │
+ ▼
+ Netlist analysis ──► clamp / diode / rail topology errors
+ │
+ ▼
+ Identify ESD paths & ROIs (pad → clamp → rail / ground)
+ │
+ ▼
+ R-extract only (ideally) those nets / polygons
+ │
+ ├──► P2P resistance checks (pad to clamp, clamp to rail, …)
+ └──► Current-density checks along discharge path
+```
+
+At full-chip SoC scale:
+
+- Millions–billions of devices/nets in the connectivity graph
+- Thousands of IO pads × many path endpoints → combinatorial P2P queries
+- Naïve flows **flatten** hierarchy and **re-extract** everything every ECO
+- Rule decks are deep (context-aware voltage propagation, multi-domain)
+
+That combination makes PERC one of the longest reliability signoff steps.
+
+---
+
+## Where Runtime Goes (and how to cut it)
+
+| Bottleneck | Optimization idea | What this C++ project models |
+|------------|-------------------|------------------------------|
+| Full-chip netlist walk every rule | Rule-aware **ROI pruning** | `run_roi` |
+| Re-running unchanged blocks after ECO | **Incremental metadata reuse** | `run_incremental` / `MetadataStore` |
+| Flat chip analysis | **Hierarchical partition** | `run_hierarchical` |
+| Sequential pad-pair P2P | **Parallel path queries** | `run_parallel` (`std::async`) |
+| Extracting entire design for CD/P2P | Extract **only marked ESD nets** (LDL) | ROI net set + pad-scoped Dijkstra |
+
+Commercial tools also use distributed multi-CPU scaling and foundry-tuned rule decks; those are orthogonal to the algorithmic wins above.
+
+---
+
+## What This Project Is (and Is Not)
+
+**Is:** A C++17 stand-in for PERC’s expensive cores — netlist topology checks + graph-based P2P resistance — with measurable baseline vs optimized runtimes on synthetic circuits.
+
+**Is not:** A replacement for Calibre / ICV, a foundry runset, or a full parasitic extractor. Geometry and StarRC-class extraction are abstracted as a weighted resistor graph (`RGraph` + Dijkstra).
diff --git a/PERC Runtime Optimizer/docs/PERC_Runtime_Optimizer_Whitepaper.pdf b/PERC Runtime Optimizer/docs/PERC_Runtime_Optimizer_Whitepaper.pdf
new file mode 100644
index 0000000..2cd0c8b
Binary files /dev/null and b/PERC Runtime Optimizer/docs/PERC_Runtime_Optimizer_Whitepaper.pdf differ
diff --git a/PERC Runtime Optimizer/docs/generate_whitepaper.py b/PERC Runtime Optimizer/docs/generate_whitepaper.py
new file mode 100644
index 0000000..88d1bd2
--- /dev/null
+++ b/PERC Runtime Optimizer/docs/generate_whitepaper.py
@@ -0,0 +1,895 @@
+#!/usr/bin/env python3
+"""Generate the PERC Runtime Optimizer white paper PDF."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+from reportlab.lib import colors
+from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT
+from reportlab.lib.pagesizes import letter
+from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
+from reportlab.lib.units import inch
+from reportlab.platypus import (
+ KeepTogether,
+ ListFlowable,
+ ListItem,
+ PageBreak,
+ Paragraph,
+ Preformatted,
+ SimpleDocTemplate,
+ Spacer,
+ Table,
+ TableStyle,
+)
+
+OUT = Path(__file__).resolve().parent / "PERC_Runtime_Optimizer_Whitepaper.pdf"
+
+
+def styles():
+ base = getSampleStyleSheet()
+ s = {
+ "title": ParagraphStyle(
+ "WPTitle",
+ parent=base["Title"],
+ fontName="Times-Bold",
+ fontSize=22,
+ leading=26,
+ alignment=TA_CENTER,
+ spaceAfter=12,
+ ),
+ "subtitle": ParagraphStyle(
+ "WPSub",
+ parent=base["Normal"],
+ fontName="Times-Roman",
+ fontSize=12,
+ leading=16,
+ alignment=TA_CENTER,
+ textColor=colors.HexColor("#333333"),
+ spaceAfter=6,
+ ),
+ "meta": ParagraphStyle(
+ "WPMeta",
+ parent=base["Normal"],
+ fontName="Times-Italic",
+ fontSize=10,
+ leading=13,
+ alignment=TA_CENTER,
+ textColor=colors.HexColor("#444444"),
+ spaceAfter=4,
+ ),
+ "h1": ParagraphStyle(
+ "WPH1",
+ parent=base["Heading1"],
+ fontName="Times-Bold",
+ fontSize=14,
+ leading=18,
+ spaceBefore=16,
+ spaceAfter=8,
+ textColor=colors.HexColor("#1a1a1a"),
+ ),
+ "h2": ParagraphStyle(
+ "WPH2",
+ parent=base["Heading2"],
+ fontName="Times-Bold",
+ fontSize=12,
+ leading=15,
+ spaceBefore=12,
+ spaceAfter=6,
+ textColor=colors.HexColor("#222222"),
+ ),
+ "h3": ParagraphStyle(
+ "WPH3",
+ parent=base["Heading3"],
+ fontName="Times-Bold",
+ fontSize=11,
+ leading=14,
+ spaceBefore=8,
+ spaceAfter=4,
+ ),
+ "body": ParagraphStyle(
+ "WPBody",
+ parent=base["Normal"],
+ fontName="Times-Roman",
+ fontSize=10.5,
+ leading=14.5,
+ alignment=TA_JUSTIFY,
+ spaceAfter=8,
+ ),
+ "bullet": ParagraphStyle(
+ "WPBullet",
+ parent=base["Normal"],
+ fontName="Times-Roman",
+ fontSize=10.5,
+ leading=14,
+ leftIndent=12,
+ spaceAfter=3,
+ ),
+ "code": ParagraphStyle(
+ "WPCode",
+ parent=base["Code"],
+ fontName="Courier",
+ fontSize=8,
+ leading=10.5,
+ backColor=colors.HexColor("#f4f4f4"),
+ borderPadding=6,
+ spaceBefore=6,
+ spaceAfter=10,
+ ),
+ "caption": ParagraphStyle(
+ "WPCap",
+ parent=base["Normal"],
+ fontName="Times-Italic",
+ fontSize=9,
+ leading=11,
+ alignment=TA_CENTER,
+ spaceBefore=4,
+ spaceAfter=12,
+ textColor=colors.HexColor("#333333"),
+ ),
+ "toc": ParagraphStyle(
+ "WPTOC",
+ parent=base["Normal"],
+ fontName="Times-Roman",
+ fontSize=11,
+ leading=16,
+ leftIndent=10,
+ spaceAfter=2,
+ ),
+ "footer": ParagraphStyle(
+ "WPFooter",
+ parent=base["Normal"],
+ fontName="Times-Roman",
+ fontSize=8,
+ alignment=TA_CENTER,
+ textColor=colors.HexColor("#555555"),
+ ),
+ }
+ return s
+
+
+def table(data, col_widths=None):
+ # Wrap cells as paragraphs for long text
+ body = styles()["body"]
+ cell_style = ParagraphStyle(
+ "Cell",
+ parent=body,
+ fontSize=8.5,
+ leading=11,
+ alignment=TA_LEFT,
+ spaceAfter=0,
+ )
+ header_style = ParagraphStyle(
+ "CellH",
+ parent=cell_style,
+ fontName="Times-Bold",
+ fontSize=8.5,
+ )
+ wrapped = []
+ for r_i, row in enumerate(data):
+ wrow = []
+ for cell in row:
+ st = header_style if r_i == 0 else cell_style
+ wrow.append(Paragraph(str(cell), st))
+ wrapped.append(wrow)
+ t = Table(wrapped, colWidths=col_widths, hAlign="CENTER")
+ t.setStyle(
+ TableStyle(
+ [
+ ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e8e8e8")),
+ ("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#666666")),
+ ("VALIGN", (0, 0), (-1, -1), "TOP"),
+ ("LEFTPADDING", (0, 0), (-1, -1), 4),
+ ("RIGHTPADDING", (0, 0), (-1, -1), 4),
+ ("TOPPADDING", (0, 0), (-1, -1), 4),
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
+ ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#fafafa")]),
+ ]
+ )
+ )
+ return t
+
+
+def bullets(items, style):
+ return ListFlowable(
+ [ListItem(Paragraph(x, style), leftIndent=8, bulletColor=colors.black) for x in items],
+ bulletType="bullet",
+ start="•",
+ leftIndent=15,
+ bulletFontName="Times-Roman",
+ bulletFontSize=10,
+ )
+
+
+def add_footer(canvas, doc):
+ canvas.saveState()
+ canvas.setFont("Times-Roman", 8)
+ canvas.setFillColor(colors.HexColor("#555555"))
+ canvas.drawCentredString(
+ letter[0] / 2,
+ 0.55 * inch,
+ f"PERC Runtime Optimizer White Paper | Page {doc.page}",
+ )
+ canvas.restoreState()
+
+
+def build():
+ s = styles()
+ story = []
+
+ # ----- Title page -----
+ story.append(Spacer(1, 1.6 * inch))
+ story.append(Paragraph("Accelerating Programmable Electrical
Rules Checking (PERC) in Physical Design", s["title"]))
+ story.append(Spacer(1, 0.25 * inch))
+ story.append(
+ Paragraph(
+ "A White Paper on Reliability Signoff Bottlenecks,
Synthetic Modeling, and Runtime Optimization Strategies",
+ s["subtitle"],
+ )
+ )
+ story.append(Spacer(1, 0.45 * inch))
+ story.append(Paragraph("Physical Design Algorithms Implementation", s["meta"]))
+ story.append(Paragraph("Companion artifact: C++17 PERC Runtime Optimizer project", s["meta"]))
+ story.append(Paragraph("August 2026", s["meta"]))
+ story.append(Spacer(1, 0.6 * inch))
+ story.append(
+ Paragraph(
+ "Abstract. Programmable Electrical Rules Checking (PERC) verifies "
+ "integrated-circuit reliability constraints—especially electrostatic discharge (ESD) "
+ "related rules—that neither design-rule checking (DRC) nor layout-versus-schematic (LVS) "
+ "can fully express. In modern SoC signoff, PERC wall-clock time is dominated less by "
+ "topology identification itself than by parasitic resistance extraction and subsequent "
+ "point-to-point (P2P) resistance and current-density (CD) analysis, often repeated after "
+ "every engineering change order (ECO). This white paper explains the industrial PERC "
+ "flow, clarifies cost centers (including metal fill and RC extraction upstream of PERC), "
+ "and presents an educational C++17 framework that models netlist-level PERC checks and "
+ "graph-based P2P/CD solves on synthetic hierarchical designs. We evaluate baseline, "
+ "region-of-interest (ROI), hierarchical, parallel, incremental, and combined optimized "
+ "engines, and discuss how the measured levers map to production reliability platforms "
+ "such as Siemens Calibre PERC and Synopsys IC Validator PERC.",
+ s["body"],
+ )
+ )
+ story.append(PageBreak())
+
+ # ----- TOC -----
+ story.append(Paragraph("Contents", s["h1"]))
+ toc_items = [
+ "1. Introduction",
+ "2. Background: PERC in the Physical Verification Stack",
+ "3. Anatomy of an ESD-Oriented PERC Flow",
+ "4. Where Runtime Actually Goes",
+ "5. Upstream Cost Centers: Metal Fill and RC Extraction",
+ "6. The PERC Runtime Optimizer Project",
+ "7. Synthetic Data Model (What It Is — and Is Not)",
+ "8. Implemented Checks and Engines",
+ "9. Experimental Methodology and Results",
+ "10. Interpretation and Guidance for Practitioners",
+ "11. Limitations and Threats to Validity",
+ "12. Future Work",
+ "13. Conclusion",
+ "References",
+ "Appendix A. Repository Layout and Reproducibility",
+ ]
+ for item in toc_items:
+ story.append(Paragraph(item, s["toc"]))
+ story.append(PageBreak())
+
+ # ----- 1 -----
+ story.append(Paragraph("1. Introduction", s["h1"]))
+ story.append(
+ Paragraph(
+ "As process nodes advance and SoC integration grows, electrical reliability constraints "
+ "have become first-class signoff requirements alongside timing, power, and geometric DRC. "
+ "Foundries and design houses encode ESD, electrical overstress (EOS), multi-domain voltage "
+ "rules, and related methodology checks in programmable rule decks. The resulting verification "
+ "class is widely known as Programmable Electrical Rules Checking (PERC).",
+ s["body"],
+ )
+ )
+ story.append(
+ Paragraph(
+ "Unlike classical DRC (geometry-centric) and LVS (device/net correspondence), PERC combines "
+ "connectivity intent with, for several check families, layout parasitics. That "
+ "hybrid nature makes PERC powerful—and expensive. Design teams routinely report multi-hour "
+ "to multi-day turnaround for full-chip reliability regressions, especially when ECOs force "
+ "repeated extraction and rechecking.",
+ s["body"],
+ )
+ )
+ story.append(
+ Paragraph(
+ "This white paper has three goals:",
+ s["body"],
+ )
+ )
+ story.append(
+ bullets(
+ [
+ "Explain PERC’s role, check families, and industrial data flow in precise terms.",
+ "Disambiguate expensive operations: ESD path identification versus P2P/CD, and "
+ "upstream metal-fill / RC-extraction costs that are often conflated with “PERC runtime.”",
+ "Document an open C++17 educational project that implements PERC-like checks and "
+ "runtime optimizations on synthetic netlists, with measured results and clear limitations.",
+ ],
+ s["bullet"],
+ )
+ )
+
+ # ----- 2 -----
+ story.append(Paragraph("2. Background: PERC in the Physical Verification Stack", s["h1"]))
+ story.append(Paragraph("2.1 What PERC is", s["h2"]))
+ story.append(
+ Paragraph(
+ "PERC is a method for checking reliability issues of IC designs that cannot be checked with "
+ "DRC or LVS alone. Rules involve connectivity and netlist information and must be "
+ "customizable from design to design—hence programmable. Commercial platforms include "
+ "Siemens Calibre PERC and Synopsys IC Validator PERC.",
+ s["body"],
+ )
+ )
+ story.append(Paragraph("2.2 The four check families", s["h2"]))
+ story.append(
+ table(
+ [
+ ["Family", "Primary inputs", "Typical intent", "Dominant cost"],
+ [
+ "Netlist checks",
+ "Schematic or LVS-extracted netlist",
+ "Clamp presence, floating gates, EOS / level-shifter topology, domain rules",
+ "Graph traversal / rule evaluation over large netlists",
+ ],
+ [
+ "Netlist-driven layout (LDL/NDL)",
+ "Netlist + GDS/OASIS",
+ "Voltage-aware spacing and geometry on connectivity-selected regions",
+ "ROI identification + geometry operations",
+ ],
+ [
+ "Current density (CD)",
+ "Layout + R-extraction + assumed ESD current",
+ "Metal can carry discharge current without EM / thermal failure",
+ "Parasitic R mesh construction and path/current analysis",
+ ],
+ [
+ "Point-to-point (P2P) resistance",
+ "Layout + R-extraction",
+ "Discharge path resistance low enough that current prefers the clamp path",
+ "Many source→sink resistance queries on resistor networks",
+ ],
+ ],
+ col_widths=[1.15 * inch, 1.45 * inch, 2.1 * inch, 1.9 * inch],
+ )
+ )
+ story.append(Paragraph("Table 1. PERC check families and primary cost drivers.", s["caption"]))
+
+ story.append(Paragraph("2.3 Placement in the signoff timeline", s["h2"]))
+ story.append(
+ Paragraph(
+ "PERC typically runs after the design is LVS-clean (for layout-aware checks), though "
+ "schematic-only netlist checks can start earlier. It sits alongside DRC, LVS, fill, "
+ "extraction, timing, and IR/EM signoff. Because reliability failures can escape functional "
+ "test patterns, PERC is treated as a gate for tapeout on many products (especially "
+ "automotive, industrial, and high-reliability segments).",
+ s["body"],
+ )
+ )
+
+ # ----- 3 -----
+ story.append(Paragraph("3. Anatomy of an ESD-Oriented PERC Flow", s["h1"]))
+ story.append(
+ Paragraph(
+ "ESD applications exercise all four check families. A representative flow is:",
+ s["body"],
+ )
+ )
+ flow = """Netlist / LVS extract
+ │
+ ▼
+ Netlist analysis engine ──► clamp / diode / rail topology errors
+ │
+ ▼
+ Identify ESD paths & ROIs (pad → clamp → rail / ground)
+ │
+ ▼
+ R-extract (ideally only) marked nets / polygons
+ │
+ ├──► P2P resistance (pad–clamp, clamp–rail, …)
+ └──► Current-density along discharge path"""
+ story.append(Preformatted(flow, s["code"]))
+ story.append(
+ Paragraph(
+ "The netlist analysis engine is the programmable core: it decides which structures exist, "
+ "which paths are critical, and which layout regions must be examined. Layout-heavy checks "
+ "then depend on parasitic resistance models of those regions.",
+ s["body"],
+ )
+ )
+
+ # ----- 4 -----
+ story.append(Paragraph("4. Where Runtime Actually Goes", s["h1"]))
+ story.append(Paragraph("4.1 The common misconception", s["h2"]))
+ story.append(
+ Paragraph(
+ "A frequent question is whether ESD path identification or P2P/CD checking "
+ "dominates runtime. In production flows, path identification is usually a comparatively "
+ "cheap connectivity/graph analysis step. The expensive work is almost always:",
+ s["body"],
+ )
+ )
+ story.append(
+ bullets(
+ [
+ "Building or updating a parasitic R (and often C) network for relevant metals, and",
+ "Solving many P2P queries and CD path/current analyses on that network.",
+ ],
+ s["bullet"],
+ )
+ )
+ story.append(
+ Paragraph(
+ "Path identification still matters enormously for scoping: a poor ROI causes "
+ "over-extraction and over-checking; a correct ROI is what makes LDL-style acceleration possible.",
+ s["body"],
+ )
+ )
+
+ story.append(Paragraph("4.2 Scaling pressures at SoC size", s["h2"]))
+ story.append(
+ bullets(
+ [
+ "Millions to billions of devices/nets in the connectivity graph.",
+ "Hundreds to thousands of IO pads, each inducing multiple path endpoints.",
+ "Naïve flows that flatten hierarchy and re-extract the full chip after every ECO.",
+ "Deep, context-aware rule decks (voltage propagation, multi-power domains).",
+ ],
+ s["bullet"],
+ )
+ )
+
+ # ----- 5 -----
+ story.append(Paragraph("5. Upstream Cost Centers: Metal Fill and RC Extraction", s["h1"]))
+ story.append(
+ Paragraph(
+ "When engineers say “PERC is slow,” they often measure an umbrella regression that includes "
+ "steps that are logically upstream of the PERC rule engine. Two of the most important are "
+ "metal fill and parasitic extraction.",
+ s["body"],
+ )
+ )
+ story.append(Paragraph("5.1 Metal fill (dummy metal)", s["h2"]))
+ story.append(
+ Paragraph(
+ "Dummy metal fill is inserted to satisfy density and manufacturing rules. It is "
+ "geometry-heavy: large numbers of fill shapes are created, legalized, and verified. Fill "
+ "can dominate physical-verification runtime on its own and also changes parasitics, "
+ "so extraction and P2P/CD results are fill-dependent. Fill is therefore both a direct "
+ "runtime cost and an indirect multiplier on PERC-related extract/check cost.",
+ s["body"],
+ )
+ )
+ story.append(Paragraph("5.2 R and C extraction", s["h2"]))
+ story.append(
+ Paragraph(
+ "Tools such as StarRC (and equivalents) build resistive (and capacitive) models of "
+ "interconnect from layout. For ESD P2P/CD, resistance accuracy on discharge paths is "
+ "critical. Full-chip extract is often one of the largest wall-clock components in the "
+ "reliability loop. Best practice is netlist-driven / ROI extract: mark ESD-critical nets "
+ "and extract only what P2P/CD need—subject to accuracy requirements.",
+ s["body"],
+ )
+ )
+ story.append(Paragraph("5.3 How this paper’s artifact relates", s["h2"]))
+ story.append(
+ Paragraph(
+ "Important clarification: the companion C++ project does not implement metal "
+ "fill or a layout extractor. It assumes an already-available resistive model (an abstract "
+ "graph) and focuses on check/solve and reuse strategies. Section 7 details the synthetic "
+ "data model so this boundary is unambiguous.",
+ s["body"],
+ )
+ )
+
+ story.append(
+ table(
+ [
+ ["Industrial step", "Modeled in C++ project?", "Notes"],
+ ["GDS/OASIS layout", "No", "No polygons, layers, or fill geometries"],
+ ["Metal fill insertion", "No", "Acknowledged as major real-world cost"],
+ ["RC extraction (StarRC-class)", "Abstracted", "Pre-baked weighted RGraph edges"],
+ ["Netlist PERC rules", "Yes", "Clamps, floating gates, etc."],
+ ["P2P / CD solves", "Yes (graph)", "Dijkstra / path walks on RGraph"],
+ ["Incremental metadata reuse", "Yes", "Scope fingerprints + ECO fast path"],
+ ],
+ col_widths=[2.0 * inch, 1.5 * inch, 3.1 * inch],
+ )
+ )
+ story.append(Paragraph("Table 2. Industrial steps versus project coverage.", s["caption"]))
+
+ # ----- 6 -----
+ story.append(Paragraph("6. The PERC Runtime Optimizer Project", s["h1"]))
+ story.append(
+ Paragraph(
+ "The repository project PERC Runtime Optimizer is a C++17 educational framework. "
+ "Its purpose is not to replace Calibre or IC Validator, but to make PERC’s algorithmic "
+ "bottlenecks tangible: one can generate a hierarchical synthetic design, run baseline "
+ "checks, and compare optimized engines with wall-clock measurements.",
+ s["body"],
+ )
+ )
+ story.append(Paragraph("6.1 Design goals", s["h2"]))
+ story.append(
+ bullets(
+ [
+ "Faithful structure of PERC stages (netlist analysis → ROI → P2P/CD).",
+ "Measurable optimization levers used in industry: ROI pruning, hierarchy, parallelism, incremental reuse.",
+ "Zero dependency on proprietary runsets or licensed layout databases.",
+ "Reproducible CLI benchmarks and a small unit-test suite.",
+ ],
+ s["bullet"],
+ )
+ )
+ story.append(Paragraph("6.2 Non-goals", s["h2"]))
+ story.append(
+ bullets(
+ [
+ "Foundry signoff accuracy or rule-deck compatibility.",
+ "GDS parsing, DRC, LVS, or fill engines.",
+ "Full field-solver or SPEF/DSPF-quality extraction.",
+ ],
+ s["bullet"],
+ )
+ )
+
+ # ----- 7 -----
+ story.append(Paragraph("7. Synthetic Data Model (What It Is — and Is Not)", s["h1"]))
+ story.append(Paragraph("7.1 Not a GDS", s["h2"]))
+ story.append(
+ Paragraph(
+ "The synthetic stimulus is not a GDS or OASIS file. There is no dummy-metal-filled "
+ "layout. Instead, generate_design() constructs:",
+ s["body"],
+ )
+ )
+ story.append(
+ bullets(
+ [
+ "A hierarchical netlist: IO pads, optional ESD clamps (some intentionally missing), "
+ "MOSFETs/diodes per block, local and global supplies, and interface buffers.",
+ "An abstract resistor graph (RGraph): undirected weighted "
+ "edges among nets that stand in for interconnect parasitics that a real extractor would produce.",
+ ],
+ s["bullet"],
+ )
+ )
+ story.append(Paragraph("7.2 Why this abstraction", s["h2"]))
+ story.append(
+ Paragraph(
+ "P2P and CD in industry consume extracted R networks. By generating a resistor graph "
+ "directly, the project isolates the query/solve and reuse problems without "
+ "requiring a layout database. This is appropriate for algorithm study; it is insufficient "
+ "for predicting absolute industrial runtimes, where fill + extract often dominate.",
+ s["body"],
+ )
+ )
+ story.append(Paragraph("7.3 ECO mutation", s["h2"]))
+ story.append(
+ Paragraph(
+ "To study incremental checking, mutate_eco() touches MOSFET drain "
+ "nets inside a limited number of blocks (default: one block). Untouched blocks keep stable "
+ "fingerprints, enabling metadata hits—analogous to commercial metadata reuse across ECO cycles.",
+ s["body"],
+ )
+ )
+
+ # ----- 8 -----
+ story.append(Paragraph("8. Implemented Checks and Engines", s["h1"]))
+ story.append(Paragraph("8.1 Checks", s["h2"]))
+ story.append(
+ table(
+ [
+ ["Check", "Rule ID(s)", "Method sketch"],
+ [
+ "ESD clamp presence",
+ "ESD_CLAMP_MISSING",
+ "Every pad net must attach to an EsdClamp device",
+ ],
+ [
+ "Floating gates",
+ "FLOATING_GATE",
+ "MOSFET gates with zero non-gate drivers (indexed writer map)",
+ ],
+ [
+ "P2P resistance",
+ "P2P_RESISTANCE_HIGH / P2P_PATH_MISSING",
+ "Dijkstra shortest resistive path pad→VSS and pad→VDD vs limit",
+ ],
+ [
+ "Current density (simplified)",
+ "CURRENT_DENSITY",
+ "Along pad→VSS shortest path, flag high I·R edge stress proxy",
+ ],
+ ],
+ col_widths=[1.5 * inch, 2.2 * inch, 2.9 * inch],
+ )
+ )
+ story.append(Paragraph("Table 3. Implemented PERC-like checks.", s["caption"]))
+
+ story.append(Paragraph("8.2 Engines", s["h2"]))
+ story.append(
+ table(
+ [
+ ["Engine", "Strategy"],
+ ["baseline", "Full-chip: all rules, every time"],
+ ["roi", "Prune floating-gate scope toward ESD-relevant devices"],
+ ["hierarchical", "Chip-level ESD/P2P/CD; per-block FG with shared writer index"],
+ ["parallel", "std::async workers over pad-pair P2P/CD slices"],
+ ["incremental", "MetadataStore keyed by scope fingerprint; ECO skips untouched blocks"],
+ ["optimized", "ESD-stable chip cache + parallel P2P/CD + hierarchical FG reuse"],
+ ],
+ col_widths=[1.4 * inch, 5.2 * inch],
+ )
+ )
+ story.append(Paragraph("Table 4. Runtime engines.", s["caption"]))
+
+ story.append(Paragraph("8.3 Metadata and fingerprints", s["h2"]))
+ story.append(
+ Paragraph(
+ "Incremental reuse stores per-scope results keyed by a structural fingerprint. Chip-level "
+ "ESD/P2P/CD uses an ESD-stable fingerprint (pads, rails, clamp/IO devices and pad–rail "
+ "R edges) so core-logic ECOs do not spuriously invalidate chip ESD results. Block scopes "
+ "fingerprint local nets/devices. When touched_blocks is known, "
+ "untouched blocks reuse the latest cached result without re-hashing.",
+ s["body"],
+ )
+ )
+
+ # ----- 9 -----
+ story.append(Paragraph("9. Experimental Methodology and Results", s["h1"]))
+ story.append(Paragraph("9.1 Setup", s["h2"]))
+ story.append(
+ Paragraph(
+ "Measurements below were taken from the project CLI on a Linux environment using the "
+ "default --bench configuration unless noted: 256 pads, 16 "
+ "blocks, 500 devices/block (~8.8k devices, ~16.7k nets, ~22.8k R-edges), compiled with "
+ "g++ -O2 -pthread. Absolute times are machine-specific; "
+ "ratios are the intended takeaway.",
+ s["body"],
+ )
+ )
+
+ story.append(Paragraph("9.2 Stage-level profile (baseline internals)", s["h2"]))
+ story.append(
+ Paragraph(
+ "Instrumenting individual checks on the bench design (averaged) yields the approximate "
+ "breakdown in Table 5. In this extract-free model, netlist floating-gate scanning "
+ "and graph P2P/CD are both visible; ESD ROI identification remains a minority cost—consistent "
+ "with the industrial claim that path ID is not the primary bottleneck once extraction exists.",
+ s["body"],
+ )
+ )
+ story.append(
+ table(
+ [
+ ["Stage", "Avg time (s)", "Share"],
+ ["ESD ROI / path ID (net scan)", "0.0010", "~9%"],
+ ["ESD clamp netlist check", "0.0001", "~1%"],
+ ["Floating-gate netlist check", "0.0056", "~47%"],
+ ["P2P resistance (Dijkstra)", "0.0032", "~27%"],
+ ["Current-density path walks", "0.0018", "~15%"],
+ ["Total (sum of stages)", "0.0118", "100%"],
+ ],
+ col_widths=[2.6 * inch, 1.5 * inch, 1.2 * inch],
+ )
+ )
+ story.append(Paragraph("Table 5. Stage profile on synthetic bench design (no layout extract).", s["caption"]))
+ story.append(
+ Paragraph(
+ "Note: floating-gate share is inflated by the synthetic stimulus, which intentionally "
+ "leaves many gate nets without drivers to stress the checker. In real netlists, FG cost "
+ "is typically smaller relative to extract+P2P/CD.",
+ s["body"],
+ )
+ )
+
+ story.append(Paragraph("9.3 Engine comparison", s["h2"]))
+ story.append(
+ table(
+ [
+ ["Engine", "Time (s)", "Observations"],
+ ["baseline", "0.0120", "Full recompute reference"],
+ ["roi", "0.0141", "Similar quality; prune overhead can outweigh savings at this size"],
+ ["hierarchical", "0.0116", "Shared FG index; comparable to baseline cold"],
+ ["parallel", "0.0079", "~1.5× vs baseline on pad-pair heavy work (4 workers)"],
+ ["incremental (cold)", "0.0746", "Fingerprint+populate cache costs more when cold"],
+ ["incremental (warm)", "0.0007", "~100× vs cold incremental; much faster than baseline when unchanged"],
+ ["optimized (warm)", "0.0007", "Same warm-cache benefit"],
+ ],
+ col_widths=[1.7 * inch, 1.1 * inch, 3.8 * inch],
+ )
+ )
+ story.append(Paragraph("Table 6. Engine wall-clock on unchanged design (bench config).", s["caption"]))
+
+ story.append(Paragraph("9.4 ECO incremental recheck", s["h2"]))
+ story.append(
+ Paragraph(
+ "After a 5% MOSFET-drain ECO confined to a single block:",
+ s["body"],
+ )
+ )
+ story.append(
+ table(
+ [
+ ["Engine", "Time (s)", "Cache behavior"],
+ ["baseline", "0.0104", "Full recompute"],
+ ["incremental", "0.0034", "16 hits / 1 miss (recheck touched block only)"],
+ ["optimized", "0.0031", "Same reuse pattern + parallel chip path when needed"],
+ ],
+ col_widths=[1.5 * inch, 1.2 * inch, 3.9 * inch],
+ )
+ )
+ story.append(Paragraph("Table 7. Post-ECO recheck (one touched block).", s["caption"]))
+ story.append(
+ Paragraph(
+ "Violation counts matched between baseline and incremental/optimized after ECO "
+ "(7449 total), indicating the fast path did not drop the newly introduced floating-gate "
+ "effect in the touched block.",
+ s["body"],
+ )
+ )
+
+ # ----- 10 -----
+ story.append(Paragraph("10. Interpretation and Guidance for Practitioners", s["h1"]))
+ story.append(
+ Paragraph(
+ "Even though absolute times in the toy model are milliseconds, the shape of the "
+ "results matches industrial practice:",
+ s["body"],
+ )
+ )
+ story.append(
+ bullets(
+ [
+ "Do not over-invest in speeding path ID alone if extract+P2P/CD dominate your traces.",
+ "Invest in ROI / LDL: mark ESD nets early; extract and check only what those paths need.",
+ "Treat ECO as the common case: metadata reuse and hierarchical invalidation beat heroic cold-run constant-factor tuning.",
+ "Parallelize embarrassingly partitioned P2P pairs across pads/domains once the R model exists.",
+ "Account for fill and extract in program plans: a “PERC project” that ignores them will miss the real critical path.",
+ ],
+ s["bullet"],
+ )
+ )
+ story.append(
+ Paragraph(
+ "For teams building internal accelerators or research prototypes, a useful layering is: "
+ "(1) connectivity/ROI engine, (2) extract subset manager, (3) P2P/CD solver farm, "
+ "(4) persistent metadata store keyed by hierarchical scopes.",
+ s["body"],
+ )
+ )
+
+ # ----- 11 -----
+ story.append(Paragraph("11. Limitations and Threats to Validity", s["h1"]))
+ story.append(
+ bullets(
+ [
+ "No layout database: cannot reproduce geometry-limited LDL or fill interactions.",
+ "No real extractor: RGraph edges are synthetic; Dijkstra on a modest mesh understates industrial solve cost.",
+ "Simplified CD: I·R proxy is pedagogical, not a current-density field model.",
+ "Synthetic FG population: skews stage profiles versus production netlists.",
+ "Single-machine pthread scaling: does not model distributed farm / license / disk bottlenecks.",
+ "Not signoff-equivalent: results must not be used as reliability certification evidence.",
+ ],
+ s["bullet"],
+ )
+ )
+
+ # ----- 12 -----
+ story.append(Paragraph("12. Future Work", s["h1"]))
+ story.append(
+ bullets(
+ [
+ "Synthetic layout + fill model: tile-based metals with dummy fill insertion timed separately from checks.",
+ "Extract emulator: build R (and C) from the synthetic layout with configurable accuracy/runtime tradeoffs.",
+ "Richer ESD topologies: secondary clamps, rail clamps, diode chains, multi-domain crossers.",
+ "Voltage-aware LDL checks: propagate domain voltages and trigger geometry queries on ROIs.",
+ "Persistent on-disk metadata and hierarchical invalidation integrated with a mock P&R ECO stream.",
+ "GPU/batch shortest paths for large pad-pair sets on huge resistor meshes.",
+ ],
+ s["bullet"],
+ )
+ )
+
+ # ----- 13 -----
+ story.append(Paragraph("13. Conclusion", s["h1"]))
+ story.append(
+ Paragraph(
+ "PERC is essential for catching ESD/EOS-class reliability issues outside the reach of DRC "
+ "and LVS. Its runtime pain is real, but it is easy to mis-attribute. ESD path identification "
+ "is necessary scaffolding; the dominant costs in production are typically parasitic "
+ "extraction and P2P/CD analysis—often amplified by metal fill and by full-chip redo after ECO.",
+ s["body"],
+ )
+ )
+ story.append(
+ Paragraph(
+ "The C++17 PERC Runtime Optimizer makes these ideas concrete with synthetic hierarchical "
+ "netlists, graph-based P2P/CD, and engines that demonstrate ROI pruning, hierarchy, "
+ "parallelism, and incremental metadata reuse. Warm-cache and ECO-local recheck show order-of-magnitude "
+ "or multi-fold speedups in the model, mirroring the strategic importance of reuse in commercial "
+ "reliability platforms. Extending the artifact toward fill and extraction would close the "
+ "largest remaining gap between educational measurement and industrial wall-clock reality.",
+ s["body"],
+ )
+ )
+
+ # ----- References -----
+ story.append(Paragraph("References", s["h1"]))
+ refs = [
+ "[1] Synopsys, “What is PERC (Programmable Electrical Rules Checking)?” Synopsys Glossary. "
+ "https://www.synopsys.com/glossary/what-is-programmable-electrical-rules-checking.html",
+ "[2] Synopsys, IC Validator Physical Verification Datasheet (PERC / NDC / MMC / CD / P2P capabilities).",
+ "[3] Siemens EDA, “Advanced electrical rule checking in IC reliability verification,” Calibre PERC technical paper.",
+ "[4] Siemens EDA, “Increase productivity by reusing metadata for signoff & ECOs,” Calibre PERC metadata reuse paper.",
+ "[5] eInfochips, “Understanding PERC: Definition and Applications for Reliable Design” (ESD, P2P, CD overview).",
+ "[6] Industry practice notes on logic-driven layout (LDL), StarRC-class R-extraction for ESD paths, and dummy metal fill density flows "
+ "(foundry design manuals; tool-specific user guides).",
+ ]
+ for r in refs:
+ story.append(Paragraph(r, s["body"]))
+
+ # ----- Appendix -----
+ story.append(Paragraph("Appendix A. Repository Layout and Reproducibility", s["h1"]))
+ story.append(
+ Paragraph(
+ "The project lives under PERC Runtime Optimizer/ with headers in "
+ "include/, sources in src/, tests in "
+ "tests/, and concept notes in docs/.",
+ s["body"],
+ )
+ )
+ story.append(
+ Preformatted(
+ "cd \"PERC Runtime Optimizer\"\n"
+ "make -j\n"
+ "make test\n"
+ "./perc_optimizer --bench --eco --workers $(nproc)\n"
+ "# regenerate this PDF:\n"
+ "python3 docs/generate_whitepaper.py",
+ s["code"],
+ )
+ )
+ story.append(
+ Paragraph(
+ "Primary sources: design.* (netlist/RGraph), "
+ "generator.* (synthetic design + ECO), "
+ "checks.* (rules), engine.* "
+ "(baseline/optimized runners), metadata.* (cache).",
+ s["body"],
+ )
+ )
+ story.append(
+ Paragraph(
+ "This white paper is intended as an educational and engineering companion document for the "
+ "open repository. It is not a foundry signoff guide.",
+ s["body"],
+ )
+ )
+
+ doc = SimpleDocTemplate(
+ str(OUT),
+ pagesize=letter,
+ leftMargin=0.85 * inch,
+ rightMargin=0.85 * inch,
+ topMargin=0.75 * inch,
+ bottomMargin=0.75 * inch,
+ title="Accelerating PERC in Physical Design",
+ author="Physical Design Algorithms Implementation",
+ subject="PERC Runtime Optimizer White Paper",
+ )
+ doc.build(story, onFirstPage=add_footer, onLaterPages=add_footer)
+ print(f"Wrote {OUT} ({OUT.stat().st_size} bytes)")
+
+
+if __name__ == "__main__":
+ build()
diff --git a/PERC Runtime Optimizer/include/checks.hpp b/PERC Runtime Optimizer/include/checks.hpp
new file mode 100644
index 0000000..50a3151
--- /dev/null
+++ b/PERC Runtime Optimizer/include/checks.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "design.hpp"
+
+#include
+#include
+#include
+#include
+
+namespace perc {
+
+std::vector check_esd_clamps(
+ const Design& design,
+ const std::vector* pads = nullptr);
+
+std::vector check_floating_gates(
+ const Design& design,
+ const std::unordered_set* device_scope = nullptr);
+
+// Build once, reuse across hierarchical block scopes.
+struct FloatingGateIndex {
+ std::unordered_map writers;
+};
+FloatingGateIndex build_floating_gate_index(const Design& design);
+std::vector check_floating_gates_indexed(
+ const Design& design,
+ const FloatingGateIndex& index,
+ const std::unordered_set* device_scope = nullptr);
+
+std::vector check_p2p_resistance(
+ const Design& design,
+ const std::vector>* pairs = nullptr,
+ double limit_ohm = -1.0);
+
+std::vector check_current_density_paths(
+ const Design& design,
+ const std::vector* pads = nullptr,
+ double i_peak_a = 1.0,
+ double jmax_proxy = 2.0);
+
+// Nets relevant to ESD clamp + P2P (pads, rails, clamp/IO terminals).
+std::unordered_set esd_roi_nets(const Design& design);
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/include/design.hpp b/PERC Runtime Optimizer/include/design.hpp
new file mode 100644
index 0000000..97ef9ff
--- /dev/null
+++ b/PERC Runtime Optimizer/include/design.hpp
@@ -0,0 +1,105 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace perc {
+
+enum class DeviceKind {
+ Mosfet,
+ Diode,
+ EsdClamp,
+ Resistor,
+ Capacitor,
+ IoPad,
+ Other
+};
+
+inline const char* to_string(DeviceKind k) {
+ switch (k) {
+ case DeviceKind::Mosfet: return "mosfet";
+ case DeviceKind::Diode: return "diode";
+ case DeviceKind::EsdClamp: return "esd_clamp";
+ case DeviceKind::Resistor: return "resistor";
+ case DeviceKind::Capacitor: return "capacitor";
+ case DeviceKind::IoPad: return "io_pad";
+ default: return "other";
+ }
+}
+
+struct Device {
+ std::string name;
+ DeviceKind kind = DeviceKind::Other;
+ // terminal -> net name
+ std::unordered_map terminals;
+ double ron = 0.0;
+};
+
+struct Net {
+ std::string name;
+ bool is_power = false;
+ bool is_ground = false;
+ bool is_pad = false;
+ std::string voltage_domain;
+};
+
+struct Block {
+ std::string name;
+ std::unordered_set devices;
+ std::unordered_set nets;
+ std::unordered_set interface_nets;
+};
+
+struct REdge {
+ int u = -1;
+ int v = -1;
+ double r = 0.0;
+};
+
+struct Violation {
+ std::string rule;
+ std::string message;
+ std::string context; // compact key=value;key=value
+};
+
+// Weighted undirected resistor graph keyed by integer node ids.
+struct RGraph {
+ std::vector id_to_name;
+ std::unordered_map name_to_id;
+ std::vector>> adj; // (neighbor, r)
+
+ int get_or_add(const std::string& name);
+ void add_edge(const std::string& a, const std::string& b, double r);
+ bool has_node(const std::string& name) const;
+ // Dijkstra shortest-path resistance. Returns false if unreachable.
+ bool path_resistance(int src, int sink, double& out_r) const;
+ bool shortest_path(int src, int sink, std::vector& out_nodes) const;
+ int node_count() const { return static_cast(adj.size()); }
+ int edge_count() const;
+};
+
+struct Design {
+ std::string name;
+ std::unordered_map devices;
+ std::unordered_map nets;
+ std::unordered_map blocks;
+ RGraph rgraph;
+ std::vector pad_nets;
+ double p2p_limit_ohm = 2.0;
+ std::vector touched_blocks; // set by ECO mutation
+ std::unordered_map iface_to_pad;
+
+ void add_device(Device d);
+ void add_net(Net n);
+ std::string fingerprint(const std::unordered_set* scope_nets = nullptr) const;
+ // Fingerprint limited to pads, global rails, and ESD/IO devices (stable across core ECOs).
+ std::string esd_fingerprint() const;
+};
+
+std::unordered_map summarize(const std::vector& v);
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/include/engine.hpp b/PERC Runtime Optimizer/include/engine.hpp
new file mode 100644
index 0000000..0b32e22
--- /dev/null
+++ b/PERC Runtime Optimizer/include/engine.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "design.hpp"
+#include "metadata.hpp"
+
+#include
+#include
+#include
+
+namespace perc {
+
+struct RunReport {
+ std::string mode;
+ std::vector violations;
+ double elapsed_s = 0.0;
+ std::unordered_map violation_counts;
+ std::unordered_map metrics; // e.g. cache_hits, scopes_ran
+};
+
+// Naïve full-chip: every rule on entire design, every time.
+RunReport run_baseline(const Design& design);
+
+// ROI pruning: only ESD-relevant nets/devices + pad P2P/CD.
+RunReport run_roi(const Design& design);
+
+// Hierarchical: per-block floating-gate + chip-level ESD/P2P/CD.
+RunReport run_hierarchical(const Design& design);
+
+// Incremental: reuse cached block/chip results when fingerprints match.
+RunReport run_incremental(const Design& design, MetadataStore& store, bool warm = false);
+
+// Parallel pad-pair P2P + CD using std::async worker pool.
+RunReport run_parallel(const Design& design, unsigned workers = 0);
+
+// Full optimized stack: hierarchical ROI + incremental + parallel P2P.
+RunReport run_optimized(const Design& design, MetadataStore& store, unsigned workers = 0,
+ bool warm = false);
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/include/generator.hpp b/PERC Runtime Optimizer/include/generator.hpp
new file mode 100644
index 0000000..d062d97
--- /dev/null
+++ b/PERC Runtime Optimizer/include/generator.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "design.hpp"
+
+#include
+
+namespace perc {
+
+struct GenConfig {
+ int n_pads = 32;
+ int n_blocks = 8;
+ int devices_per_block = 400;
+ double missing_clamp_rate = 0.05;
+ unsigned seed = 42;
+ double r_mesh_density = 0.35;
+};
+
+Design generate_design(const GenConfig& cfg);
+// Touch MOSFET drains inside a limited number of blocks (for incremental demos).
+Design mutate_eco(const Design& design, double touch_fraction = 0.05, unsigned seed = 7,
+ int max_blocks_to_touch = 1);
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/include/metadata.hpp b/PERC Runtime Optimizer/include/metadata.hpp
new file mode 100644
index 0000000..4b3afad
--- /dev/null
+++ b/PERC Runtime Optimizer/include/metadata.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "design.hpp"
+
+#include
+#include
+#include
+
+namespace perc {
+
+struct CheckResult {
+ std::vector violations;
+ std::string fingerprint;
+ std::string scope;
+};
+
+// In-memory metadata cache for incremental re-runs.
+class MetadataStore {
+ public:
+ const CheckResult* get(const std::string& scope, const std::string& fp) const;
+ // Last result stored for a scope (ECO fast-path when block is known untouched).
+ const CheckResult* latest(const std::string& scope) const;
+ void put(CheckResult result);
+ int invalidate_scopes(const std::vector& scopes);
+ std::size_t size() const { return entries_.size(); }
+
+ private:
+ static std::string key(const std::string& scope, const std::string& fp);
+ std::unordered_map entries_;
+ std::unordered_map latest_;
+};
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/src/checks.cpp b/PERC Runtime Optimizer/src/checks.cpp
new file mode 100644
index 0000000..6f99040
--- /dev/null
+++ b/PERC Runtime Optimizer/src/checks.cpp
@@ -0,0 +1,206 @@
+#include "checks.hpp"
+
+#include
+
+namespace perc {
+namespace {
+
+std::string ctx(const std::vector>& kvs) {
+ std::ostringstream oss;
+ for (std::size_t i = 0; i < kvs.size(); ++i) {
+ if (i) oss << ';';
+ oss << kvs[i].first << '=' << kvs[i].second;
+ }
+ return oss.str();
+}
+
+} // namespace
+
+std::vector check_esd_clamps(
+ const Design& design,
+ const std::vector* pads) {
+ std::vector pad_list;
+ if (pads) {
+ pad_list = *pads;
+ } else if (!design.pad_nets.empty()) {
+ pad_list = design.pad_nets;
+ } else {
+ for (const auto& kv : design.nets) {
+ if (kv.second.is_pad) pad_list.push_back(kv.first);
+ }
+ }
+
+ std::unordered_set clamped;
+ for (const auto& kv : design.devices) {
+ if (kv.second.kind != DeviceKind::EsdClamp) continue;
+ auto it = kv.second.terminals.find("io");
+ if (it == kv.second.terminals.end()) it = kv.second.terminals.find("pad");
+ if (it != kv.second.terminals.end()) clamped.insert(it->second);
+ }
+
+ std::vector out;
+ for (const auto& pad : pad_list) {
+ if (!clamped.count(pad)) {
+ out.push_back({"ESD_CLAMP_MISSING", "Pad " + pad + " has no ESD clamp",
+ ctx({{"pad", pad}})});
+ }
+ }
+ return out;
+}
+
+FloatingGateIndex build_floating_gate_index(const Design& design) {
+ FloatingGateIndex idx;
+ idx.writers.reserve(design.nets.size());
+ for (const auto& kv : design.devices) {
+ for (const auto& t : kv.second.terminals) {
+ if (t.first == "d" || t.first == "s" || t.first == "a" || t.first == "c" ||
+ t.first == "io" || t.first == "pad") {
+ idx.writers[t.second]++;
+ }
+ }
+ }
+ for (const auto& kv : design.nets) {
+ if (kv.second.is_power || kv.second.is_ground || kv.second.is_pad) {
+ idx.writers[kv.first] += 1;
+ }
+ }
+ return idx;
+}
+
+std::vector check_floating_gates_indexed(
+ const Design& design,
+ const FloatingGateIndex& index,
+ const std::unordered_set* device_scope) {
+ std::vector out;
+ auto check_one = [&](const std::string& name, const Device& d) {
+ if (d.kind != DeviceKind::Mosfet) return;
+ auto git = d.terminals.find("g");
+ if (git == d.terminals.end()) return;
+ const std::string& gnet = git->second;
+ const int w = index.writers.count(gnet) ? index.writers.at(gnet) : 0;
+ if (w == 0) {
+ out.push_back({"FLOATING_GATE",
+ "Device " + name + " gate net " + gnet + " appears floating",
+ ctx({{"device", name}, {"net", gnet}})});
+ }
+ };
+
+ if (device_scope) {
+ for (const auto& name : *device_scope) {
+ auto it = design.devices.find(name);
+ if (it != design.devices.end()) check_one(name, it->second);
+ }
+ } else {
+ for (const auto& kv : design.devices) check_one(kv.first, kv.second);
+ }
+ return out;
+}
+
+std::vector check_floating_gates(
+ const Design& design,
+ const std::unordered_set* device_scope) {
+ const FloatingGateIndex idx = build_floating_gate_index(design);
+ return check_floating_gates_indexed(design, idx, device_scope);
+}
+
+std::vector check_p2p_resistance(
+ const Design& design,
+ const std::vector>* pairs,
+ double limit_ohm) {
+ const double limit = (limit_ohm > 0.0) ? limit_ohm : design.p2p_limit_ohm;
+ std::vector> local;
+ if (!pairs) {
+ local.reserve(design.pad_nets.size() * 2);
+ for (const auto& p : design.pad_nets) {
+ local.emplace_back(p, "VSS");
+ local.emplace_back(p, "VDD");
+ }
+ pairs = &local;
+ }
+
+ std::vector out;
+ for (const auto& pr : *pairs) {
+ if (!design.rgraph.has_node(pr.first) || !design.rgraph.has_node(pr.second)) {
+ out.push_back({"P2P_PATH_MISSING",
+ "No R-graph path endpoints for " + pr.first + " -> " + pr.second,
+ ctx({{"src", pr.first}, {"sink", pr.second}})});
+ continue;
+ }
+ const int s = design.rgraph.name_to_id.at(pr.first);
+ const int t = design.rgraph.name_to_id.at(pr.second);
+ double r = 0.0;
+ if (!design.rgraph.path_resistance(s, t, r)) {
+ out.push_back({"P2P_PATH_MISSING",
+ "No resistive path " + pr.first + " -> " + pr.second,
+ ctx({{"src", pr.first}, {"sink", pr.second}})});
+ continue;
+ }
+ if (r > limit) {
+ std::ostringstream msg;
+ msg << "R(" << pr.first << "," << pr.second << ")=" << r << "ohm exceeds " << limit << "ohm";
+ out.push_back({"P2P_RESISTANCE_HIGH", msg.str(),
+ ctx({{"src", pr.first},
+ {"sink", pr.second},
+ {"r_ohm", std::to_string(r)},
+ {"limit", std::to_string(limit)}})});
+ }
+ }
+ return out;
+}
+
+std::vector check_current_density_paths(
+ const Design& design,
+ const std::vector* pads,
+ double i_peak_a,
+ double jmax_proxy) {
+ std::vector pad_list = pads ? *pads : design.pad_nets;
+ std::vector out;
+ if (!design.rgraph.has_node("VSS")) return out;
+ const int sink = design.rgraph.name_to_id.at("VSS");
+
+ for (const auto& pad : pad_list) {
+ if (!design.rgraph.has_node(pad)) continue;
+ const int src = design.rgraph.name_to_id.at(pad);
+ std::vector path;
+ if (!design.rgraph.shortest_path(src, sink, path) || path.size() < 2) continue;
+ for (std::size_t i = 1; i < path.size(); ++i) {
+ const int a = path[i - 1];
+ const int b = path[i];
+ double r = 0.0;
+ for (const auto& e : design.rgraph.adj[a]) {
+ if (e.first == b) {
+ r = e.second;
+ break;
+ }
+ }
+ const double stress = i_peak_a * r;
+ if (stress > jmax_proxy) {
+ std::ostringstream msg;
+ msg << "Path " << pad << "->VSS edge " << design.rgraph.id_to_name[a] << "-"
+ << design.rgraph.id_to_name[b] << " stress " << stress << " > " << jmax_proxy;
+ out.push_back({"CURRENT_DENSITY", msg.str(),
+ ctx({{"pad", pad},
+ {"a", design.rgraph.id_to_name[a]},
+ {"b", design.rgraph.id_to_name[b]},
+ {"r", std::to_string(r)}})});
+ }
+ }
+ }
+ return out;
+}
+
+std::unordered_set esd_roi_nets(const Design& design) {
+ std::unordered_set roi;
+ for (const auto& kv : design.nets) {
+ if (kv.second.is_pad || kv.second.is_power || kv.second.is_ground) roi.insert(kv.first);
+ }
+ for (const auto& kv : design.devices) {
+ if (kv.second.kind == DeviceKind::EsdClamp || kv.second.kind == DeviceKind::IoPad ||
+ kv.second.kind == DeviceKind::Diode) {
+ for (const auto& t : kv.second.terminals) roi.insert(t.second);
+ }
+ }
+ return roi;
+}
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/src/design.cpp b/PERC Runtime Optimizer/src/design.cpp
new file mode 100644
index 0000000..2fe0c9d
--- /dev/null
+++ b/PERC Runtime Optimizer/src/design.cpp
@@ -0,0 +1,231 @@
+#include "design.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace perc {
+
+int RGraph::get_or_add(const std::string& name) {
+ auto it = name_to_id.find(name);
+ if (it != name_to_id.end()) return it->second;
+ int id = static_cast(id_to_name.size());
+ name_to_id.emplace(name, id);
+ id_to_name.push_back(name);
+ adj.emplace_back();
+ return id;
+}
+
+void RGraph::add_edge(const std::string& a, const std::string& b, double r) {
+ if (a == b) return;
+ int u = get_or_add(a);
+ int v = get_or_add(b);
+ adj[u].push_back({v, r});
+ adj[v].push_back({u, r});
+}
+
+bool RGraph::has_node(const std::string& name) const {
+ return name_to_id.find(name) != name_to_id.end();
+}
+
+int RGraph::edge_count() const {
+ int e = 0;
+ for (const auto& row : adj) e += static_cast(row.size());
+ return e / 2;
+}
+
+bool RGraph::path_resistance(int src, int sink, double& out_r) const {
+ const int n = node_count();
+ if (src < 0 || sink < 0 || src >= n || sink >= n) return false;
+ std::vector dist(n, std::numeric_limits::infinity());
+ using Node = std::pair;
+ std::priority_queue, std::greater> pq;
+ dist[src] = 0.0;
+ pq.push({0.0, src});
+ while (!pq.empty()) {
+ auto [d, u] = pq.top();
+ pq.pop();
+ if (d > dist[u]) continue;
+ if (u == sink) {
+ out_r = d;
+ return true;
+ }
+ for (const auto& [v, w] : adj[u]) {
+ double nd = d + w;
+ if (nd < dist[v]) {
+ dist[v] = nd;
+ pq.push({nd, v});
+ }
+ }
+ }
+ return false;
+}
+
+bool RGraph::shortest_path(int src, int sink, std::vector& out_nodes) const {
+ const int n = node_count();
+ if (src < 0 || sink < 0 || src >= n || sink >= n) return false;
+ std::vector dist(n, std::numeric_limits::infinity());
+ std::vector prev(n, -1);
+ using Node = std::pair;
+ std::priority_queue, std::greater> pq;
+ dist[src] = 0.0;
+ pq.push({0.0, src});
+ while (!pq.empty()) {
+ auto [d, u] = pq.top();
+ pq.pop();
+ if (d > dist[u]) continue;
+ if (u == sink) break;
+ for (const auto& [v, w] : adj[u]) {
+ double nd = d + w;
+ if (nd < dist[v]) {
+ dist[v] = nd;
+ prev[v] = u;
+ pq.push({nd, v});
+ }
+ }
+ }
+ if (!std::isfinite(dist[sink])) return false;
+ out_nodes.clear();
+ for (int cur = sink; cur != -1; cur = prev[cur]) out_nodes.push_back(cur);
+ std::reverse(out_nodes.begin(), out_nodes.end());
+ return true;
+}
+
+void Design::add_device(Device d) {
+ for (const auto& kv : d.terminals) {
+ if (!nets.count(kv.second)) {
+ Net n;
+ n.name = kv.second;
+ nets.emplace(n.name, n);
+ }
+ }
+ devices[d.name] = std::move(d);
+}
+
+void Design::add_net(Net n) {
+ nets[n.name] = std::move(n);
+}
+
+std::string Design::fingerprint(const std::unordered_set* scope_nets) const {
+ std::ostringstream oss;
+ std::vector net_names;
+ if (scope_nets) {
+ net_names.assign(scope_nets->begin(), scope_nets->end());
+ } else {
+ net_names.reserve(nets.size());
+ for (const auto& kv : nets) net_names.push_back(kv.first);
+ }
+ std::sort(net_names.begin(), net_names.end());
+ for (const auto& name : net_names) {
+ auto it = nets.find(name);
+ if (it == nets.end()) continue;
+ const Net& n = it->second;
+ oss << "N|" << name << '|' << n.is_power << n.is_ground << n.is_pad << '|'
+ << n.voltage_domain << '\n';
+ }
+
+ std::vector dev_names;
+ for (const auto& kv : devices) {
+ if (scope_nets) {
+ bool hit = false;
+ for (const auto& t : kv.second.terminals) {
+ if (scope_nets->count(t.second)) {
+ hit = true;
+ break;
+ }
+ }
+ if (!hit) continue;
+ }
+ dev_names.push_back(kv.first);
+ }
+ std::sort(dev_names.begin(), dev_names.end());
+ for (const auto& name : dev_names) {
+ const Device& d = devices.at(name);
+ oss << "D|" << name << '|' << to_string(d.kind) << '|';
+ std::vector> terms(d.terminals.begin(), d.terminals.end());
+ std::sort(terms.begin(), terms.end());
+ for (const auto& t : terms) oss << t.first << ':' << t.second << ',';
+ oss << '\n';
+ }
+
+ // Stable hash via FNV-1a over the structural string.
+ const std::string s = oss.str();
+ std::uint64_t h = 1469598103934665603ULL;
+ for (unsigned char c : s) {
+ h ^= c;
+ h *= 1099511628211ULL;
+ }
+ std::ostringstream hex;
+ hex << std::hex << h;
+ return hex.str();
+}
+
+std::string Design::esd_fingerprint() const {
+ std::unordered_set scope;
+ scope.insert("VDD");
+ scope.insert("VSS");
+ for (const auto& p : pad_nets) scope.insert(p);
+ for (const auto& kv : devices) {
+ if (kv.second.kind == DeviceKind::EsdClamp || kv.second.kind == DeviceKind::IoPad) {
+ for (const auto& t : kv.second.terminals) scope.insert(t.second);
+ }
+ }
+ // Hash only ESD/IO devices + scoped nets (ignore core MOSFET churn).
+ std::ostringstream oss;
+ std::vector net_names(scope.begin(), scope.end());
+ std::sort(net_names.begin(), net_names.end());
+ for (const auto& name : net_names) {
+ auto it = nets.find(name);
+ if (it == nets.end()) continue;
+ const Net& n = it->second;
+ oss << "N|" << name << '|' << n.is_power << n.is_ground << n.is_pad << '|'
+ << n.voltage_domain << '\n';
+ }
+ std::vector dev_names;
+ for (const auto& kv : devices) {
+ if (kv.second.kind == DeviceKind::EsdClamp || kv.second.kind == DeviceKind::IoPad) {
+ dev_names.push_back(kv.first);
+ }
+ }
+ std::sort(dev_names.begin(), dev_names.end());
+ for (const auto& name : dev_names) {
+ const Device& d = devices.at(name);
+ oss << "D|" << name << '|' << to_string(d.kind) << '|';
+ std::vector> terms(d.terminals.begin(), d.terminals.end());
+ std::sort(terms.begin(), terms.end());
+ for (const auto& t : terms) oss << t.first << ':' << t.second << ',';
+ oss << '\n';
+ }
+ // Include pad→rail R edges only.
+ for (const auto& pad : pad_nets) {
+ if (!rgraph.has_node(pad)) continue;
+ const int id = rgraph.name_to_id.at(pad);
+ std::vector> nbrs;
+ for (const auto& e : rgraph.adj[id]) {
+ const std::string& nn = rgraph.id_to_name[e.first];
+ if (nn == "VDD" || nn == "VSS") nbrs.emplace_back(nn, e.second);
+ }
+ std::sort(nbrs.begin(), nbrs.end());
+ for (const auto& n : nbrs) oss << "R|" << pad << '|' << n.first << '|' << n.second << '\n';
+ }
+ const std::string s = oss.str();
+ std::uint64_t h = 1469598103934665603ULL;
+ for (unsigned char c : s) {
+ h ^= c;
+ h *= 1099511628211ULL;
+ }
+ std::ostringstream hex;
+ hex << std::hex << h;
+ return hex.str();
+}
+
+std::unordered_map summarize(const std::vector& v) {
+ std::unordered_map counts;
+ for (const auto& x : v) counts[x.rule]++;
+ return counts;
+}
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/src/engine.cpp b/PERC Runtime Optimizer/src/engine.cpp
new file mode 100644
index 0000000..2b1791c
--- /dev/null
+++ b/PERC Runtime Optimizer/src/engine.cpp
@@ -0,0 +1,297 @@
+#include "engine.hpp"
+
+#include "checks.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace perc {
+namespace {
+
+using Clock = std::chrono::steady_clock;
+
+double seconds_since(Clock::time_point t0) {
+ return std::chrono::duration(Clock::now() - t0).count();
+}
+
+void append(std::vector& dst, std::vector&& src) {
+ dst.insert(dst.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
+}
+
+RunReport finish(const std::string& mode, std::vector viols, double elapsed,
+ std::unordered_map metrics = {}) {
+ RunReport r;
+ r.mode = mode;
+ r.elapsed_s = elapsed;
+ r.violation_counts = summarize(viols);
+ r.violations = std::move(viols);
+ r.metrics = std::move(metrics);
+ return r;
+}
+
+std::vector> all_pad_pairs(const Design& design) {
+ std::vector> pairs;
+ pairs.reserve(design.pad_nets.size() * 2);
+ for (const auto& p : design.pad_nets) {
+ pairs.emplace_back(p, "VSS");
+ pairs.emplace_back(p, "VDD");
+ }
+ return pairs;
+}
+
+} // namespace
+
+RunReport run_baseline(const Design& design) {
+ const auto t0 = Clock::now();
+ std::vector viols;
+ append(viols, check_esd_clamps(design));
+ append(viols, check_floating_gates(design));
+ append(viols, check_p2p_resistance(design));
+ append(viols, check_current_density_paths(design));
+ return finish("baseline", std::move(viols), seconds_since(t0),
+ {{"devices", static_cast(design.devices.size())},
+ {"nets", static_cast(design.nets.size())},
+ {"r_edges", static_cast(design.rgraph.edge_count())}});
+}
+
+RunReport run_roi(const Design& design) {
+ const auto t0 = Clock::now();
+ const auto roi = esd_roi_nets(design);
+
+ // Floating-gate only on devices that touch ROI nets (still catches FLOAT_* near rails/pads),
+ // plus a cheap scan limited to devices whose gate is FLOAT_* for the educational demo.
+ std::unordered_set scope;
+ for (const auto& kv : design.devices) {
+ if (kv.second.kind != DeviceKind::Mosfet) continue;
+ auto git = kv.second.terminals.find("g");
+ if (git != kv.second.terminals.end() && git->second.rfind("FLOAT_", 0) == 0) {
+ scope.insert(kv.first);
+ continue;
+ }
+ for (const auto& t : kv.second.terminals) {
+ if (roi.count(t.second)) {
+ scope.insert(kv.first);
+ break;
+ }
+ }
+ }
+
+ std::vector viols;
+ append(viols, check_esd_clamps(design)); // already pad-scoped
+ append(viols, check_floating_gates(design, &scope));
+ append(viols, check_p2p_resistance(design));
+ append(viols, check_current_density_paths(design));
+ return finish("roi", std::move(viols), seconds_since(t0),
+ {{"roi_nets", static_cast(roi.size())},
+ {"fg_scope_devices", static_cast(scope.size())}});
+}
+
+RunReport run_hierarchical(const Design& design) {
+ const auto t0 = Clock::now();
+ std::vector viols;
+
+ // Chip-level ESD / P2P / CD
+ append(viols, check_esd_clamps(design));
+ append(viols, check_p2p_resistance(design));
+ append(viols, check_current_density_paths(design));
+
+ // Per-block floating gates with a shared writer index (build once).
+ const FloatingGateIndex fg = build_floating_gate_index(design);
+ for (const auto& bkv : design.blocks) {
+ append(viols, check_floating_gates_indexed(design, fg, &bkv.second.devices));
+ }
+
+ return finish("hierarchical", std::move(viols), seconds_since(t0),
+ {{"blocks", static_cast(design.blocks.size())}});
+}
+
+RunReport run_incremental(const Design& design, MetadataStore& store, bool warm) {
+ const auto t0 = Clock::now();
+ std::vector viols;
+ double cache_hits = 0;
+ double cache_miss = 0;
+
+ std::unordered_set touched(design.touched_blocks.begin(),
+ design.touched_blocks.end());
+ const bool eco_mode = !touched.empty();
+
+ auto run_scope = [&](const std::string& scope, bool known_untouched, auto&& fp_fn,
+ auto&& compute) {
+ if (known_untouched) {
+ if (const CheckResult* hit = store.latest(scope)) {
+ append(viols, std::vector(hit->violations));
+ cache_hits += 1;
+ return;
+ }
+ }
+ const std::string fp = fp_fn();
+ if (const CheckResult* hit = store.get(scope, fp)) {
+ append(viols, std::vector(hit->violations));
+ cache_hits += 1;
+ return;
+ }
+ CheckResult cr;
+ cr.scope = scope;
+ cr.fingerprint = fp;
+ cr.violations = compute();
+ append(viols, std::vector(cr.violations));
+ store.put(std::move(cr));
+ cache_miss += 1;
+ };
+
+ // Chip-level ESD/P2P/CD — fingerprint ignores core logic ECOs.
+ run_scope(
+ "chip_esd", eco_mode || warm,
+ [&] { return design.esd_fingerprint(); },
+ [&] {
+ std::vector v;
+ append(v, check_esd_clamps(design));
+ append(v, check_p2p_resistance(design));
+ append(v, check_current_density_paths(design));
+ return v;
+ });
+
+ for (const auto& bkv : design.blocks) {
+ const bool untouched = eco_mode && !touched.count(bkv.first);
+ run_scope(
+ bkv.first, untouched || (warm && !eco_mode),
+ [&] { return design.fingerprint(&bkv.second.nets); },
+ [&] { return check_floating_gates(design, &bkv.second.devices); });
+ }
+
+ return finish("incremental", std::move(viols), seconds_since(t0),
+ {{"cache_hits", cache_hits},
+ {"cache_misses", cache_miss},
+ {"cache_size", static_cast(store.size())}});
+}
+
+RunReport run_parallel(const Design& design, unsigned workers) {
+ const auto t0 = Clock::now();
+ if (workers == 0) {
+ workers = std::max(1u, std::thread::hardware_concurrency());
+ }
+
+ std::vector viols;
+ append(viols, check_esd_clamps(design));
+ append(viols, check_floating_gates(design));
+
+ auto pairs = all_pad_pairs(design);
+ const unsigned n = static_cast(pairs.size());
+ const unsigned chunk = std::max(1u, (n + workers - 1) / workers);
+
+ std::vector>> futs;
+ futs.reserve(workers);
+ for (unsigned w = 0; w < workers; ++w) {
+ const unsigned begin = w * chunk;
+ if (begin >= n) break;
+ const unsigned end = std::min(n, begin + chunk);
+ futs.push_back(std::async(std::launch::async, [&design, pairs, begin, end] {
+ std::vector> slice(pairs.begin() + begin,
+ pairs.begin() + end);
+ std::vector local;
+ append(local, check_p2p_resistance(design, &slice));
+ // CD only for pads represented in this slice (unique first endpoints)
+ std::vector pads;
+ for (const auto& pr : slice) {
+ if (pr.second == "VSS") pads.push_back(pr.first);
+ }
+ append(local, check_current_density_paths(design, &pads));
+ return local;
+ }));
+ }
+
+ for (auto& f : futs) append(viols, f.get());
+
+ return finish("parallel", std::move(viols), seconds_since(t0),
+ {{"workers", static_cast(workers)},
+ {"pad_pairs", static_cast(pairs.size())}});
+}
+
+RunReport run_optimized(const Design& design, MetadataStore& store, unsigned workers, bool warm) {
+ const auto t0 = Clock::now();
+ if (workers == 0) {
+ workers = std::max(1u, std::thread::hardware_concurrency());
+ }
+
+ std::vector viols;
+ double cache_hits = 0;
+ double cache_miss = 0;
+
+ std::unordered_set touched(design.touched_blocks.begin(),
+ design.touched_blocks.end());
+ const bool eco_mode = !touched.empty();
+
+ auto run_scope = [&](const std::string& scope, bool known_untouched, auto&& fp_fn,
+ auto&& compute) {
+ if (known_untouched || (warm && !eco_mode)) {
+ if (const CheckResult* hit = store.latest(scope)) {
+ append(viols, std::vector(hit->violations));
+ cache_hits += 1;
+ return;
+ }
+ }
+ const std::string fp = fp_fn();
+ if (const CheckResult* hit = store.get(scope, fp)) {
+ append(viols, std::vector(hit->violations));
+ cache_hits += 1;
+ return;
+ }
+ CheckResult cr;
+ cr.scope = scope;
+ cr.fingerprint = fp;
+ cr.violations = compute();
+ append(viols, std::vector(cr.violations));
+ store.put(std::move(cr));
+ cache_miss += 1;
+ };
+
+ run_scope(
+ "chip_esd_opt", eco_mode || warm,
+ [&] { return design.esd_fingerprint(); },
+ [&] {
+ std::vector v;
+ append(v, check_esd_clamps(design));
+
+ auto pairs = all_pad_pairs(design);
+ const unsigned n = static_cast(pairs.size());
+ const unsigned chunk = std::max(1u, (n + workers - 1) / workers);
+ std::vector>> futs;
+ for (unsigned w = 0; w < workers; ++w) {
+ const unsigned begin = w * chunk;
+ if (begin >= n) break;
+ const unsigned end = std::min(n, begin + chunk);
+ futs.push_back(std::async(std::launch::async, [&design, pairs, begin, end] {
+ std::vector> slice(pairs.begin() + begin,
+ pairs.begin() + end);
+ std::vector local;
+ append(local, check_p2p_resistance(design, &slice));
+ std::vector pads;
+ for (const auto& pr : slice) {
+ if (pr.second == "VSS") pads.push_back(pr.first);
+ }
+ append(local, check_current_density_paths(design, &pads));
+ return local;
+ }));
+ }
+ for (auto& f : futs) append(v, f.get());
+ return v;
+ });
+
+ for (const auto& bkv : design.blocks) {
+ const bool untouched = eco_mode && !touched.count(bkv.first);
+ run_scope(
+ bkv.first + "_fg", untouched || (warm && !eco_mode),
+ [&] { return design.fingerprint(&bkv.second.nets); },
+ [&] { return check_floating_gates(design, &bkv.second.devices); });
+ }
+
+ return finish("optimized", std::move(viols), seconds_since(t0),
+ {{"cache_hits", cache_hits},
+ {"cache_misses", cache_miss},
+ {"workers", static_cast(workers)}});
+}
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/src/generator.cpp b/PERC Runtime Optimizer/src/generator.cpp
new file mode 100644
index 0000000..09b3cb7
--- /dev/null
+++ b/PERC Runtime Optimizer/src/generator.cpp
@@ -0,0 +1,282 @@
+#include "generator.hpp"
+
+#include
+#include
+#include
+#include
+
+namespace perc {
+namespace {
+
+std::string net_name(int b, int d, char side) {
+ std::ostringstream oss;
+ oss << "N_" << b << '_' << d << '_' << side;
+ return oss.str();
+}
+
+} // namespace
+
+Design generate_design(const GenConfig& cfg) {
+ std::mt19937 rng(cfg.seed);
+ std::uniform_real_distribution uni(0.0, 1.0);
+
+ Design design;
+ {
+ std::ostringstream oss;
+ oss << "synth_p" << cfg.n_pads << "_b" << cfg.n_blocks << "_d" << cfg.devices_per_block;
+ design.name = oss.str();
+ }
+
+ design.add_net(Net{"VDD", true, false, false, "core"});
+ design.add_net(Net{"VSS", false, true, false, "core"});
+
+ design.pad_nets.clear();
+ design.pad_nets.reserve(cfg.n_pads);
+ for (int i = 0; i < cfg.n_pads; ++i) {
+ std::string pad = "PAD_" + std::to_string(i);
+ design.pad_nets.push_back(pad);
+ design.add_net(Net{pad, false, false, true, "io"});
+ Device io;
+ io.name = "IO_" + std::to_string(i);
+ io.kind = DeviceKind::IoPad;
+ io.terminals = {{"pad", pad}, {"vdd", "VDD"}, {"vss", "VSS"}};
+ design.add_device(std::move(io));
+
+ if (uni(rng) >= cfg.missing_clamp_rate) {
+ Device clamp;
+ clamp.name = "CLAMP_" + std::to_string(i);
+ clamp.kind = DeviceKind::EsdClamp;
+ clamp.terminals = {{"io", pad}, {"vdd", "VDD"}, {"vss", "VSS"}};
+ clamp.ron = 0.3 + uni(rng) * 1.2;
+ design.add_device(std::move(clamp));
+ }
+ }
+
+ std::unordered_set clamp_pads;
+ for (const auto& kv : design.devices) {
+ if (kv.second.kind == DeviceKind::EsdClamp) {
+ auto it = kv.second.terminals.find("io");
+ if (it != kv.second.terminals.end()) clamp_pads.insert(it->second);
+ }
+ }
+
+ // Build pad→rail edges early (ESD-critical).
+ for (const auto& pad : design.pad_nets) {
+ if (clamp_pads.count(pad)) {
+ design.rgraph.add_edge(pad, "VDD", 0.2 + uni(rng) * 0.6);
+ design.rgraph.add_edge(pad, "VSS", 0.2 + uni(rng) * 0.6);
+ } else {
+ design.rgraph.add_edge(pad, "VDD", 8.0 + uni(rng) * 17.0);
+ design.rgraph.add_edge(pad, "VSS", 8.0 + uni(rng) * 17.0);
+ }
+ }
+ design.rgraph.add_edge("VDD", "VSS", 0.01 + uni(rng) * 0.04);
+
+ const int iface_per = std::max(1, cfg.n_pads / std::max(cfg.n_blocks, 1));
+
+ for (int b = 0; b < cfg.n_blocks; ++b) {
+ Block block;
+ block.name = "BLK_" + std::to_string(b);
+ const std::string local_vdd = "VDD_BLK_" + std::to_string(b);
+ const std::string local_vss = "VSS_BLK_" + std::to_string(b);
+ design.add_net(Net{local_vdd, true, false, false, "blk" + std::to_string(b)});
+ design.add_net(Net{local_vss, false, true, false, "blk" + std::to_string(b)});
+ block.nets.insert(local_vdd);
+ block.nets.insert(local_vss);
+ block.interface_nets.insert(local_vdd);
+ block.interface_nets.insert(local_vss);
+ block.interface_nets.insert("VDD");
+ block.interface_nets.insert("VSS");
+
+ Device rtie_vdd;
+ rtie_vdd.name = "RTIE_VDD_" + std::to_string(b);
+ rtie_vdd.kind = DeviceKind::Resistor;
+ rtie_vdd.terminals = {{"a", "VDD"}, {"b", local_vdd}};
+ rtie_vdd.ron = 0.05 + uni(rng) * 0.15;
+ design.add_device(std::move(rtie_vdd));
+
+ Device rtie_vss;
+ rtie_vss.name = "RTIE_VSS_" + std::to_string(b);
+ rtie_vss.kind = DeviceKind::Resistor;
+ rtie_vss.terminals = {{"a", "VSS"}, {"b", local_vss}};
+ rtie_vss.ron = 0.05 + uni(rng) * 0.15;
+ design.add_device(std::move(rtie_vss));
+
+ design.rgraph.add_edge(local_vdd, "VDD", 0.05 + uni(rng) * 0.25);
+ design.rgraph.add_edge(local_vss, "VSS", 0.05 + uni(rng) * 0.25);
+
+ std::vector block_net_list;
+ block_net_list.push_back(local_vdd);
+ block_net_list.push_back(local_vss);
+
+ for (int d = 0; d < cfg.devices_per_block; ++d) {
+ const std::string na = net_name(b, d, 'A');
+ const std::string nb = net_name(b, d, 'B');
+ design.add_net(Net{na, false, false, false, "blk" + std::to_string(b)});
+ design.add_net(Net{nb, false, false, false, "blk" + std::to_string(b)});
+ block.nets.insert(na);
+ block.nets.insert(nb);
+ block_net_list.push_back(na);
+ block_net_list.push_back(nb);
+
+ if (uni(rng) > 0.08) {
+ Device m;
+ m.name = "M_" + std::to_string(b) + "_" + std::to_string(d);
+ m.kind = DeviceKind::Mosfet;
+ std::string gate = nb;
+ if (uni(rng) < 0.02) {
+ gate = "FLOAT_" + std::to_string(b) + "_" + std::to_string(d);
+ design.add_net(Net{gate, false, false, false, "blk" + std::to_string(b)});
+ block.nets.insert(gate);
+ }
+ m.terminals = {
+ {"d", na},
+ {"g", gate},
+ {"s", (uni(rng) > 0.5 ? local_vss : local_vdd)},
+ {"b", local_vss},
+ };
+ block.devices.insert(m.name);
+ design.add_device(std::move(m));
+ } else {
+ Device diode;
+ diode.name = "D_" + std::to_string(b) + "_" + std::to_string(d);
+ diode.kind = DeviceKind::Diode;
+ diode.terminals = {{"a", na}, {"c", local_vss}};
+ block.devices.insert(diode.name);
+ design.add_device(std::move(diode));
+ }
+ }
+
+ // Interface buffers toward pads.
+ for (int k = 0; k < iface_per; ++k) {
+ const int pad_idx = (b * iface_per + k) % cfg.n_pads;
+ const std::string iface = "IF_" + std::to_string(b) + "_" + std::to_string(k);
+ design.add_net(Net{iface, false, false, false, "blk" + std::to_string(b)});
+ block.nets.insert(iface);
+ block.interface_nets.insert(iface);
+ block_net_list.push_back(iface);
+ design.iface_to_pad[iface] = design.pad_nets[pad_idx];
+
+ Device buf;
+ buf.name = "BUF_" + std::to_string(b) + "_" + std::to_string(k);
+ buf.kind = DeviceKind::Mosfet;
+ buf.terminals = {
+ {"d", iface},
+ {"g", net_name(b, k % std::max(1, cfg.devices_per_block), 'A')},
+ {"s", local_vss},
+ {"b", local_vss},
+ };
+ block.devices.insert(buf.name);
+ design.add_device(std::move(buf));
+ design.rgraph.add_edge(iface, design.pad_nets[pad_idx], 0.3 + uni(rng) * 1.7);
+ }
+
+ // Sparse R-mesh inside the block (spanning path + extras).
+ std::shuffle(block_net_list.begin(), block_net_list.end(), rng);
+ for (std::size_t i = 1; i < block_net_list.size(); ++i) {
+ design.rgraph.add_edge(block_net_list[i - 1], block_net_list[i], 0.5 + uni(rng) * 4.5);
+ }
+ const int extra = static_cast(block_net_list.size() * cfg.r_mesh_density);
+ for (int e = 0; e < extra && block_net_list.size() >= 2; ++e) {
+ const int i = static_cast(rng() % block_net_list.size());
+ const int j = static_cast(rng() % block_net_list.size());
+ if (i == j) continue;
+ design.rgraph.add_edge(block_net_list[i], block_net_list[j], 0.5 + uni(rng) * 7.5);
+ }
+
+ design.blocks.emplace(block.name, std::move(block));
+ }
+
+ design.p2p_limit_ohm = 2.0;
+ return design;
+}
+
+Design mutate_eco(const Design& design, double touch_fraction, unsigned seed,
+ int max_blocks_to_touch) {
+ std::mt19937 rng(seed);
+ Design eco = design;
+ eco.name = design.name + "_eco";
+ eco.touched_blocks.clear();
+
+ std::vector block_names;
+ for (const auto& kv : eco.blocks) block_names.push_back(kv.first);
+ std::sort(block_names.begin(), block_names.end());
+ std::shuffle(block_names.begin(), block_names.end(), rng);
+ if (max_blocks_to_touch > 0 &&
+ static_cast(block_names.size()) > max_blocks_to_touch) {
+ block_names.resize(max_blocks_to_touch);
+ }
+ std::unordered_set allowed_blocks(block_names.begin(), block_names.end());
+
+ std::vector mosfets;
+ for (const auto& kv : eco.devices) {
+ if (kv.second.kind != DeviceKind::Mosfet) continue;
+ bool in_allowed = allowed_blocks.empty();
+ for (const auto& bname : allowed_blocks) {
+ if (eco.blocks.at(bname).devices.count(kv.first)) {
+ in_allowed = true;
+ break;
+ }
+ }
+ // Also allow devices not listed in block.devices (e.g. BUF_*) if their nets are in block
+ if (!in_allowed) {
+ for (const auto& bname : allowed_blocks) {
+ for (const auto& t : kv.second.terminals) {
+ if (eco.blocks.at(bname).nets.count(t.second)) {
+ in_allowed = true;
+ break;
+ }
+ }
+ if (in_allowed) break;
+ }
+ }
+ if (in_allowed) mosfets.push_back(kv.first);
+ }
+ if (mosfets.empty()) return eco;
+
+ const int n_touch = std::max(1, static_cast(mosfets.size() * touch_fraction));
+ std::shuffle(mosfets.begin(), mosfets.end(), rng);
+ mosfets.resize(std::min(n_touch, static_cast(mosfets.size())));
+
+ std::unordered_set touched;
+ for (const auto& dname : mosfets) {
+ Device& d = eco.devices[dname];
+ auto it = d.terminals.find("d");
+ if (it == d.terminals.end()) continue;
+ const std::string old = it->second;
+ const std::string neu = old + "_ECO";
+ it->second = neu;
+ Net n;
+ n.name = neu;
+ auto old_it = eco.nets.find(old);
+ if (old_it != eco.nets.end()) n.voltage_domain = old_it->second.voltage_domain;
+ eco.add_net(std::move(n));
+
+ if (eco.rgraph.has_node(old)) {
+ const int oid = eco.rgraph.name_to_id[old];
+ // Snapshot neighbors first — add_edge may reallocate adj / id_to_name.
+ std::vector> nbrs;
+ nbrs.reserve(eco.rgraph.adj[oid].size());
+ for (const auto& [nbr, r] : eco.rgraph.adj[oid]) {
+ nbrs.emplace_back(eco.rgraph.id_to_name[nbr], r);
+ }
+ std::uniform_real_distribution jitter(0.9, 1.1);
+ for (const auto& [nbr_name, r] : nbrs) {
+ eco.rgraph.add_edge(neu, nbr_name, r * jitter(rng));
+ }
+ }
+
+ for (auto& bkv : eco.blocks) {
+ if (bkv.second.devices.count(dname) || bkv.second.nets.count(old)) {
+ bkv.second.nets.insert(neu);
+ touched.insert(bkv.first);
+ }
+ }
+ }
+
+ eco.touched_blocks.assign(touched.begin(), touched.end());
+ std::sort(eco.touched_blocks.begin(), eco.touched_blocks.end());
+ return eco;
+}
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/src/main.cpp b/PERC Runtime Optimizer/src/main.cpp
new file mode 100644
index 0000000..7327991
--- /dev/null
+++ b/PERC Runtime Optimizer/src/main.cpp
@@ -0,0 +1,122 @@
+#include "engine.hpp"
+#include "generator.hpp"
+
+#include
+#include
+#include
+#include
+
+namespace {
+
+void print_report(const perc::RunReport& r) {
+ std::cout << std::left << std::setw(14) << r.mode
+ << " time_s=" << std::fixed << std::setprecision(4) << r.elapsed_s
+ << " violations=" << r.violations.size();
+ if (!r.violation_counts.empty()) {
+ std::cout << " [";
+ bool first = true;
+ for (const auto& kv : r.violation_counts) {
+ if (!first) std::cout << ", ";
+ first = false;
+ std::cout << kv.first << '=' << kv.second;
+ }
+ std::cout << "]";
+ }
+ if (!r.metrics.empty()) {
+ std::cout << " metrics{";
+ bool first = true;
+ for (const auto& kv : r.metrics) {
+ if (!first) std::cout << ", ";
+ first = false;
+ std::cout << kv.first << '=' << kv.second;
+ }
+ std::cout << "}";
+ }
+ std::cout << '\n';
+}
+
+void usage(const char* argv0) {
+ std::cerr
+ << "PERC Runtime Optimizer (C++)\n"
+ << "Usage: " << argv0
+ << " [--pads N] [--blocks N] [--devices N] [--workers N] [--eco] [--bench]\n";
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ perc::GenConfig cfg;
+ unsigned workers = 0;
+ bool do_eco = false;
+ bool do_bench = false;
+
+ for (int i = 1; i < argc; ++i) {
+ const std::string a = argv[i];
+ auto need = [&](const char* flag) -> int {
+ if (i + 1 >= argc) {
+ std::cerr << "Missing value for " << flag << '\n';
+ std::exit(2);
+ }
+ return std::atoi(argv[++i]);
+ };
+ if (a == "--pads") cfg.n_pads = need("--pads");
+ else if (a == "--blocks") cfg.n_blocks = need("--blocks");
+ else if (a == "--devices") cfg.devices_per_block = need("--devices");
+ else if (a == "--workers") workers = static_cast(need("--workers"));
+ else if (a == "--eco") do_eco = true;
+ else if (a == "--bench") do_bench = true;
+ else if (a == "--help" || a == "-h") {
+ usage(argv[0]);
+ return 0;
+ } else {
+ std::cerr << "Unknown arg: " << a << '\n';
+ usage(argv[0]);
+ return 2;
+ }
+ }
+
+ if (do_bench) {
+ // Larger default for timing comparisons (P2P/CD heavy)
+ if (cfg.devices_per_block == 400 && cfg.n_pads == 32) {
+ cfg.n_pads = 256;
+ cfg.n_blocks = 16;
+ cfg.devices_per_block = 500;
+ }
+ }
+
+ std::cout << "Generating design pads=" << cfg.n_pads << " blocks=" << cfg.n_blocks
+ << " devices/block=" << cfg.devices_per_block << " ...\n";
+ perc::Design design = perc::generate_design(cfg);
+ std::cout << "Design " << design.name << " devices=" << design.devices.size()
+ << " nets=" << design.nets.size() << " r_edges=" << design.rgraph.edge_count()
+ << '\n';
+
+ print_report(perc::run_baseline(design));
+ print_report(perc::run_roi(design));
+ print_report(perc::run_hierarchical(design));
+ print_report(perc::run_parallel(design, workers));
+
+ perc::MetadataStore store;
+ // Cold incremental (populate cache)
+ print_report(perc::run_incremental(design, store, false));
+ // Warm incremental (should hit cache)
+ print_report(perc::run_incremental(design, store, true));
+
+ perc::MetadataStore opt_store;
+ print_report(perc::run_optimized(design, opt_store, workers, false));
+ print_report(perc::run_optimized(design, opt_store, workers, true));
+
+ if (do_eco) {
+ std::cout << "\n--- After ECO (5% MOSFET drains touched) ---\n";
+ perc::Design eco = perc::mutate_eco(design, 0.05, 7, /*max_blocks_to_touch=*/1);
+ std::cout << "Touched blocks:";
+ for (const auto& b : eco.touched_blocks) std::cout << ' ' << b;
+ std::cout << '\n';
+ print_report(perc::run_baseline(eco));
+ // Reuse prior store: unchanged block fingerprints should hit.
+ print_report(perc::run_incremental(eco, store, true));
+ print_report(perc::run_optimized(eco, opt_store, workers, true));
+ }
+
+ return 0;
+}
diff --git a/PERC Runtime Optimizer/src/metadata.cpp b/PERC Runtime Optimizer/src/metadata.cpp
new file mode 100644
index 0000000..a79be02
--- /dev/null
+++ b/PERC Runtime Optimizer/src/metadata.cpp
@@ -0,0 +1,46 @@
+#include "metadata.hpp"
+
+#include
+
+namespace perc {
+
+std::string MetadataStore::key(const std::string& scope, const std::string& fp) {
+ return scope + "::" + fp;
+}
+
+const CheckResult* MetadataStore::get(const std::string& scope, const std::string& fp) const {
+ auto it = entries_.find(key(scope, fp));
+ if (it == entries_.end()) return nullptr;
+ return &it->second;
+}
+
+const CheckResult* MetadataStore::latest(const std::string& scope) const {
+ auto it = latest_.find(scope);
+ if (it == latest_.end()) return nullptr;
+ return &it->second;
+}
+
+void MetadataStore::put(CheckResult result) {
+ const std::string k = key(result.scope, result.fingerprint);
+ latest_[result.scope] = result;
+ entries_[k] = std::move(result);
+}
+
+int MetadataStore::invalidate_scopes(const std::vector& scopes) {
+ std::unordered_set dead(scopes.begin(), scopes.end());
+ int n = 0;
+ for (auto it = entries_.begin(); it != entries_.end();) {
+ const auto pos = it->first.find("::");
+ const std::string scope = (pos == std::string::npos) ? it->first : it->first.substr(0, pos);
+ if (dead.count(scope)) {
+ it = entries_.erase(it);
+ ++n;
+ } else {
+ ++it;
+ }
+ }
+ for (const auto& s : dead) latest_.erase(s);
+ return n;
+}
+
+} // namespace perc
diff --git a/PERC Runtime Optimizer/tests/test_perc.cpp b/PERC Runtime Optimizer/tests/test_perc.cpp
new file mode 100644
index 0000000..87b7717
--- /dev/null
+++ b/PERC Runtime Optimizer/tests/test_perc.cpp
@@ -0,0 +1,68 @@
+#include "checks.hpp"
+#include "engine.hpp"
+#include "generator.hpp"
+
+#include
+#include
+#include
+
+static int g_failed = 0;
+
+#define EXPECT(cond) \
+ do { \
+ if (!(cond)) { \
+ std::cerr << "FAIL " << __FILE__ << ":" << __LINE__ << " " << #cond \
+ << '\n'; \
+ ++g_failed; \
+ } \
+ } while (0)
+
+int main() {
+ perc::GenConfig cfg;
+ cfg.n_pads = 16;
+ cfg.n_blocks = 4;
+ cfg.devices_per_block = 50;
+ cfg.missing_clamp_rate = 0.1;
+ cfg.seed = 1;
+
+ perc::Design d = perc::generate_design(cfg);
+ EXPECT(!d.devices.empty());
+ EXPECT(!d.pad_nets.empty());
+ EXPECT(d.rgraph.edge_count() > 0);
+
+ auto clamps = perc::check_esd_clamps(d);
+ EXPECT(!clamps.empty()); // missing_clamp_rate > 0
+
+ auto p2p = perc::check_p2p_resistance(d);
+ // Missing clamps produce high R → P2P violations expected
+ EXPECT(!p2p.empty());
+
+ auto base = perc::run_baseline(d);
+ auto roi = perc::run_roi(d);
+ auto hier = perc::run_hierarchical(d);
+ EXPECT(base.violations.size() == hier.violations.size());
+ // ROI floating-gate scope may differ slightly; ESD/P2P counts should match baseline rule set presence
+ EXPECT(base.violation_counts.count("ESD_CLAMP_MISSING"));
+ EXPECT(roi.violation_counts.count("ESD_CLAMP_MISSING"));
+
+ perc::MetadataStore store;
+ auto cold = perc::run_incremental(d, store, false);
+ auto warm = perc::run_incremental(d, store, true);
+ EXPECT(warm.metrics.at("cache_hits") > 0);
+ EXPECT(warm.elapsed_s <= cold.elapsed_s * 1.5 + 0.05);
+
+ perc::Design eco = perc::mutate_eco(d, 0.2, 3, /*max_blocks_to_touch=*/1);
+ EXPECT(!eco.touched_blocks.empty());
+ auto eco_inc = perc::run_incremental(eco, store, true);
+ EXPECT(eco_inc.metrics.at("cache_hits") > 0);
+
+ auto par = perc::run_parallel(d, 2);
+ EXPECT(par.violations.size() == base.violations.size());
+
+ if (g_failed) {
+ std::cerr << g_failed << " assertion(s) failed\n";
+ return 1;
+ }
+ std::cout << "All tests passed\n";
+ return 0;
+}
diff --git a/README.md b/README.md
index 4dcec55..dd9a404 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,10 @@ Steps--
1. Implement Kruskal's MST using DSU.
2. Implement Steiner Tree using MST (A lot more complex than this).
+## [**PERC Runtime Optimizer (C++)**](https://github.com/sethupathib/Physical-Design-Algorithms-Implementation/tree/main/PERC%20Runtime%20Optimizer)
+
+Programmable Electrical Rules Checking (ESD clamps, P2P resistance, CD-style path checks) with baseline vs ROI / hierarchical / incremental / parallel engines to study signoff runtime. White paper: [`PERC_Runtime_Optimizer_Whitepaper.pdf`](https://github.com/sethupathib/Physical-Design-Algorithms-Implementation/blob/main/PERC%20Runtime%20Optimizer/docs/PERC_Runtime_Optimizer_Whitepaper.pdf).
+
## Case Study
1. [**Register Clustering for Optimal PPA**](https://dl.acm.org/doi/10.1145/3299902.3309753) and [**ISPD Slides.**](http://ispd.cc/slides/2019/2_placement_GracefulReg.pdf)