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)