From 302405075c23f2937b6e3a94c1db54af01600b7c Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 12:28:25 +0800 Subject: [PATCH 01/16] feat: add ecc_py binding census with CI gate --- .github/workflows/ci.yml | 17 + scripts/binding_census/README.md | 91 + scripts/binding_census/baseline_diff.md | 102 + scripts/binding_census/binding_census.py | 88 + scripts/binding_census/binding_spec.json | 2251 ++++++++++++++ .../binding_census/binding_spec.schema.json | 67 + scripts/binding_census/dead_bindings.md | 278 ++ scripts/binding_census/dead_bindings.py | 152 + scripts/binding_census/lexer.py | 468 +++ scripts/binding_census/manifest.json | 2623 +++++++++++++++++ scripts/binding_census/manifest.py | 296 ++ scripts/binding_census/manifest.schema.json | 65 + scripts/binding_census/test_binding_census.py | 333 +++ 13 files changed, 6831 insertions(+) create mode 100644 scripts/binding_census/README.md create mode 100644 scripts/binding_census/baseline_diff.md create mode 100755 scripts/binding_census/binding_census.py create mode 100644 scripts/binding_census/binding_spec.json create mode 100644 scripts/binding_census/binding_spec.schema.json create mode 100644 scripts/binding_census/dead_bindings.md create mode 100755 scripts/binding_census/dead_bindings.py create mode 100755 scripts/binding_census/lexer.py create mode 100644 scripts/binding_census/manifest.json create mode 100755 scripts/binding_census/manifest.py create mode 100644 scripts/binding_census/manifest.schema.json create mode 100755 scripts/binding_census/test_binding_census.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfd4345582..b7a68108fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,7 @@ on: - 'CMakeLists.txt' - 'pyproject.toml' - 'build.sh' + - 'scripts/binding_census/**' - '.github/**' push: branches: [main] @@ -35,3 +36,19 @@ jobs: name: ecc-tools-wheel path: dist/wheel/repaired/*.whl if-no-files-found: error + + binding-census: + name: Binding Census + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Run census parser tests + run: uv run --no-project --with jsonschema --with pytest python -m pytest scripts/binding_census/test_binding_census.py -q + + - name: Check census manifest + run: uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py --check diff --git a/scripts/binding_census/README.md b/scripts/binding_census/README.md new file mode 100644 index 0000000000..93644639c3 --- /dev/null +++ b/scripts/binding_census/README.md @@ -0,0 +1,91 @@ +# Binding census + +Machine-verified census of the `ecc_py` pybind11 bindings whose parameters +carry strings (`std::string` / `std::vector` and similar string +containers). It is the source of truth for widening path-carrying parameters +to `std::filesystem::path` / `std::optional`. + +## Layout + +- `binding_census.py` — thin CLI entry point (argument parsing and + orchestration only). The implementation is split into sibling modules: + `lexer.py` (lexical discovery scanner), `manifest.py` (schema validation, + spec/manifest join, `--check` gate), and `dead_bindings.py` (the + `--ecc-root` cross-repo audit). Together they need only the stdlib plus + `jsonschema`. +- `binding_spec.json` — curated, human-reviewed semantics per in-scope + parameter (types, defaults, shape, path/non_path/ambiguous classification + with rationale). Validated against `binding_spec.schema.json`. +- `manifest.json` — generated join of discovery + spec, one entry per + (binding, parameter). Validated against `manifest.schema.json`. Committed; + never edit by hand. +- `dead_bindings.md` — generated audit of the ecc wrapper's calls against + this census (needs the outer repo, see below). +- `baseline_diff.md` — count comparison of the manifest against the reviewed + classification baseline, with a written rationale for every deviation. + +## Why discovery + curated spec + +Discovery is a small lexer (a state machine over code / line comment / block +comment / string / char literals with paren-brace-bracket depth tracking — no +cross-line regex) that finds every module-level `m.def(` in +`src/interface/python/py_*/py_register_*.h` and `.../py_register_*.cpp`, +including multiline and commented-out statements, extracts the +`py::arg("name") = default` entries at paren depth 1, and resolves whether a +binding is `active` or `disabled` (commented statement, or its enclosing +`register_*` function is never called from `python_moodule.cc`). + +Semantics — which string parameter is actually a filesystem path, and what +the converted type and default should be — cannot be derived from arbitrary +C++ declarations without writing half a C++ parser. They live in the curated +spec instead, one row per parameter, each with a written rationale. The +manifest is the deterministic join of the two. + +## Regenerating + +From the repository root: + +```sh +uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py +``` + +Running it twice with unchanged inputs produces identical bytes +(`--no-project` matters: plain `uv run` in the repo root would trigger a +project sync and a full C++ build). + +To also regenerate the dead-binding audit you need a checkout of the outer +ecc repo (read-only): + +```sh +uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py \ + --ecc-root /path/to/ecc +``` + +## CI gate + +```sh +uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py --check +``` + +Exits nonzero with one message per violation when any of these fail: + +1. regeneration is not byte-stable against the committed `manifest.json`; +2. schema validation of `manifest.json` or `binding_spec.json`; +3. coverage: every discovered active binding with a `py::arg` string-literal + default has spec entries for at least those parameters, every parameter + named in the classification baseline is spec'd as `path`, and every spec + entry names a discovered binding/parameter; +4. every `ambiguous` classification carries a non-empty rationale; +5. every `path`-classified parameter of an active binding carries the + converted `new_type` (`std::filesystem::path` when required, + `std::optional` when optional, + `std::vector` for lists). + +## Known limitation + +The lexer discovers parameters from `py::arg(...)` entries. String parameters +of `py::arg`-free bindings (e.g. `idb_init`'s `config_path`, `tech_lef_init`'s +`techlef_path`) and string parameters without literal defaults are +curated-only: they exist in the spec (named after the C++ declaration) but +discovery cannot cross-check them. `binding_spec.json` covers them; the +coverage gate cross-checks everything discovery can see. diff --git a/scripts/binding_census/baseline_diff.md b/scripts/binding_census/baseline_diff.md new file mode 100644 index 0000000000..7cd83f5aed --- /dev/null +++ b/scripts/binding_census/baseline_diff.md @@ -0,0 +1,102 @@ +# Manifest vs classification baseline + +Comparison of the generated `manifest.json` against the reviewed +classification baseline. The manifest is the final authority; every deviation +has a written rationale below. + +## Totals + +| Measure | Baseline | Manifest | Delta | +|---|---|---|---| +| path scalars | 69 | 69 | 0 | +| path lists | 4 | 4 | 0 | +| required path scalars | 41 | 40 | -1 | +| optional path scalars | 28 | 29 | +1 | + +The single scalar that moves between the required and optional columns is +`sdc_init.sdc_path`; see the deviation rationale. The expected post-review +targets (40 required + 29 optional = 28 draft-optional + the `sdc_init` +reclassification) match the manifest exactly. + +## Per-module rows (path parameters) + +| Module | Path scalars (req/opt) | Path lists | Matches baseline | +|---|---|---|---| +| py_config | 8 (1/7) | 2 | yes | +| py_eval | 11 (5/6) | 0 | yes | +| py_feature | 13 (13/0) | 0 | yes | +| py_icts | 3 (3/0) | 0 | yes | +| py_idb | 18 (16/2) | 2 | yes, with the `sdc_init` deviation below | +| py_idrc | 4 (0/4) | 0 | yes | +| py_irt | 2 (0/2) | 0 | yes | +| py_ista | 1 (0/1) | 0 | yes | +| py_ircx | 1 (1/0) | 0 | yes | +| py_izh | 2 (0/2) | 0 | yes | +| py_report | 6 (1/5) | 0 | yes | +| **Total** | **69 (40/29)** | **4** | | + +Modules with no path parameters (`py_ifp`, `py_ipdn`, `py_instance`, +`py_imp`, `py_flow`) contribute only `non_path` adjudication rows +(`py_ifp` 22, `py_ipdn` 31, `py_instance` 4; `py_imp` and `py_flow` have no +string-carrying parameters in their active bindings and therefore no rows). + +## Deviations and rationales + +1. **`py_idb.sdc_init.sdc_path`: required in the baseline, `optional` in the + spec.** Its C++ body (`initSdc` in `py_db.cpp`) stores the value as-is and + the timing flow treats empty as unset, and the production harden flow + passes `None` natively (`runner.py` passes `workspace.pdk.sdc`, typed + `Path | None`). Reclassification to + `std::optional` with `py::none()` default was + confirmed during design review. This is the only count deviation: optional + 28 -> 29, required 41 -> 40. + +2. **`py_idb.tech_lef_init` parameter is named `techlef_path`, not + `tech_lef_path`.** The binding is `py::arg`-free, so the parameter name is + curated from the `initTechLef(const std::string& techlef_path)` + declaration (curated-only limitation). Same parameter, same + classification; no count impact. + +3. **The manifest carries adjudication rows the baseline does not itemize.** + The baseline lists path parameters and a handful of named non-paths + (`step`, `net`, `json_format`, `pdk`). The census scopes in *every* + string-carrying parameter of every active binding, so the manifest + additionally holds `non_path` rows for name candidates — including the + container-typed ones `netlist_save.exclude_cell_names` + (`std::set`), `write_soc_json.harden_cores`, and + `report_place_distribution.prefixes` (`std::vector`), plus + the `py_ifp`/`py_ipdn`/`py_instance` name parameters. All keep + `std::string` (`new_type == old_type`) with a written rationale; they do + not affect the path totals above. + +4. **Disabled bindings have no spec rows.** `runMP`, `runRef`, the commented + `SAPlaceSeqPairInt64`/`write_placement_back` duplicates, and the whole + `py_vec` family are discovered (with file:line) but excluded from the + spec, because only active bindings are conversion targets. Their status is + reported by the dead-binding audit (`dead_bindings.md`); `binding_status` + in the manifest is therefore `active` for every row by construction, and + `absent` appears only inside that audit. + +## Confirmed ambiguous-name rulings + +Per the ambiguous-name rule (stay `std::string` unless call-path evidence +proves filesystem semantics), with the evidence recorded in each row's +`classification_rationale`: + +- `idb_get.file_name` = `path` (optional): `idbGet` forwards it to + `rptInst->reportInstance/reportNet` (`py_db_op.h`), which write the report + to that file. +- `place_instance.source` = `non_path`: `DataManager::placeInst` forwards it + to `instance->set_type` (`idm_design_inst.cpp`) — a provenance tag, no + filesystem semantics. +- The `config` parameters of `init_rt`, `run_ert`, `init_sta`, `init_rcx`, + `fix_fanout`, `insert_filler`, `init_drc`/`run_drc` = `path`: the wrapper + passes config file paths via `path_text(...)` at the call sites in + `chipcompiler/tools/ecc/module.py` (cited per row). +- `init_rcx.pdk` = `non_path`: a PDK identifier (already + `std::optional`), not a path. +- `feature_tool.step`, `feature_cong_map.step`, `report_route.net`, + `view_json_save.json_format` = `non_path`: selector/name strings forwarded + unchanged to the feature/report APIs. + +No parameter remains `ambiguous` in the committed spec. diff --git a/scripts/binding_census/binding_census.py b/scripts/binding_census/binding_census.py new file mode 100755 index 0000000000..39c6f1cadc --- /dev/null +++ b/scripts/binding_census/binding_census.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +"""Binding census for the ecc_py pybind11 module — CLI entry point. + +Two cleanly separated parts: + +1. Discovery (``lexer``): a small lexical scanner (no cross-line regex) that + walks every register file with a state machine (code / line comment / + block comment / string literal / char literal) and tracks paren/brace/ + bracket depth. It finds every module-level ``m.def(`` statement + (including multiline and commented-out ones), extracts the + ``py::arg("name") = default`` entries at paren depth 1, and resolves + whether the binding is active or disabled (commented statement, or its + enclosing register function is never called from ``python_moodule.cc``). + +2. Semantics (curated spec): a hand-maintained JSON table + (``binding_spec.json``) carrying, per binding parameter, the old/new C++ + types and defaults, scalar/list shape, required/optional shape, and the + path / non_path / ambiguous classification with a written rationale. + +The manifest (``manifest.json``) is the generated join of discovery + spec +(``manifest`` module), one entry per (binding, in-scope parameter). Only +string-carrying parameters (``std::string`` / ``std::vector``- +typed, plus string containers such as ``std::set``) are in +scope: they are the path candidates and name candidates needing adjudication. + +Run via uv from the repository root: + + uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py + uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py --check +""" +import argparse +import json +import sys +from pathlib import Path + +from dead_bindings import audit_wrapper, render_dead_bindings_md +from lexer import discover, discovery_to_json +from manifest import check, generate_manifest_bytes + +CENSUS_DIR = Path(__file__).resolve().parent +DEFAULT_REPO_ROOT = CENSUS_DIR.parents[1] + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--check", action="store_true", help="run the census gates and exit nonzero on failure") + parser.add_argument("--discovery", action="store_true", help="print discovery JSON to stdout") + parser.add_argument( + "--ecc-root", + type=Path, + default=None, + help="path to the outer ecc repo; additionally writes dead_bindings.md", + ) + parser.add_argument("--repo-root", type=Path, default=DEFAULT_REPO_ROOT, help=argparse.SUPPRESS) + parser.add_argument("--census-dir", type=Path, default=CENSUS_DIR, help=argparse.SUPPRESS) + args = parser.parse_args(argv) + + repo_root: Path = args.repo_root.resolve() + census_dir: Path = args.census_dir.resolve() + + if args.discovery: + print(json.dumps(discovery_to_json(discover(repo_root)), indent=2, sort_keys=True)) + return 0 + + if args.check: + failures = check(repo_root, census_dir) + if failures: + for failure in failures: + print(f"binding census check FAILED: {failure}", file=sys.stderr) + return 1 + print("binding census check passed") + return 0 + + manifest_bytes = generate_manifest_bytes(repo_root, census_dir) + (census_dir / "manifest.json").write_bytes(manifest_bytes) + print(f"wrote {census_dir / 'manifest.json'}") + + if args.ecc_root is not None: + module_py = args.ecc_root.resolve() / "chipcompiler/tools/ecc/module.py" + audit = audit_wrapper(module_py, discover(repo_root)) + out = census_dir / "dead_bindings.md" + out.write_text(render_dead_bindings_md(audit)) + print(f"wrote {out}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/binding_census/binding_spec.json b/scripts/binding_census/binding_spec.json new file mode 100644 index 0000000000..2bb6211f55 --- /dev/null +++ b/scripts/binding_census/binding_spec.json @@ -0,0 +1,2251 @@ +{ + "bindings": [ + { + "module": "py_config", + "params": [ + { + "classification": "path", + "classification_rationale": "flow config JSON file; wrapper init_config passes it positionally (chipcompiler/tools/ecc/module.py:86)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "flow_config", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "flow_init" + }, + { + "module": "py_config", + "params": [ + { + "classification": "path", + "classification_rationale": "db config JSON file; wrapper init_config/update_step_paths pass path_text(db_config)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "config_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "technology LEF file; empty string is the unset sentinel in db_init's C++ body", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "tech_lef_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "cell LEF file list; empty list is the unset sentinel, no optional-ization", + "new_default": "std::vector{}", + "new_type": "std::vector", + "old_default": "std::vector {}", + "old_type": "const std::vector&", + "param": "lef_paths", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "classification": "path", + "classification_rationale": "DEF file; empty string is the unset sentinel", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "def_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "netlist Verilog file; empty string is the unset sentinel", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "verilog_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "output directory; wrapper passes path_text(output_dir)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "output_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "feature output directory; wrapper passes path_text(feature_dir)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "feature_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "Liberty file list; wrapper update_sta_data_config passes path_texts(lib_paths); empty list is the unset sentinel", + "new_default": "std::vector{}", + "new_type": "std::vector", + "old_default": "std::vector{}", + "old_type": "const std::vector&", + "param": "lib_paths", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "classification": "path", + "classification_rationale": "SDC constraints file; empty string is the unset sentinel", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "sdc_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "db_init" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "cell_density" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "pin_density" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "net_density" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "rudy_congestion" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "lut_rudy_congestion" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "egr_congestion" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "eval_cell_hierarchy" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "eval_macro_hierarchy" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "eval_macro_connection" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "eval_macro_pin_connection" + }, + { + "module": "py_eval", + "params": [ + { + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "eval_macro_io_pin_connection" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "feature summary output file (featureInst->save_summary target)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_summary" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "tool feature output file (featureInst->save_tools target)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "flow step selector string forwarded to save_tools, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "step", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_tool" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "placement eval JSON output file", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "json_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_pl_eval" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "CTS eval JSON output file", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "json_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_cts_eval" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "eval map output file (featureInst->save_eval_map target)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_eval_map" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "route feature output file", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_route" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "route feature input file read back by the feature API", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_route_read" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "macro DRC feature output file", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "DRC report input file consumed by the macro DRC feature builder", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "drc_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_macro_drc" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "eval summary output file", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_eval_summary" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "timing eval summary output file", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_timing_eval_summary" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "path", + "classification_rationale": "net eval output file", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_net_eval" + }, + { + "module": "py_feature", + "params": [ + { + "classification": "non_path", + "classification_rationale": "flow step selector string forwarded to save_cong_map, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "step", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "congestion map output directory (featureInst->save_cong_map target)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "dir", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "feature_cong_map" + }, + { + "module": "py_icts", + "params": [ + { + "classification": "path", + "classification_rationale": "CTS config JSON file; wrapper run_cts passes path_text(config) (chipcompiler/tools/ecc/module.py:401)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "cts_config", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "CTS working directory; wrapper run_cts passes path_text(output) (chipcompiler/tools/ecc/module.py:401)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "cts_work_dir", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "run_cts" + }, + { + "module": "py_icts", + "params": [ + { + "classification": "path", + "classification_rationale": "CTS report output file", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "cts_report" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "idb config JSON file; wrapper idb_init passes path_text(config_path) (chipcompiler/tools/ecc/module.py:114); py::arg-free binding, parameter name from the initIdb declaration", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "config_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "idb_init" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "technology LEF file; wrapper init_techlef passes path_text(tech_lef_path) (chipcompiler/tools/ecc/module.py:180); py::arg-free binding, parameter name from the initTechLef declaration", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "techlef_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "tech_lef_init" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "cell LEF file list; wrapper init_lefs passes path_texts(lef_paths) (chipcompiler/tools/ecc/module.py:184); empty list is the unset sentinel", + "new_default": null, + "new_type": "std::vector", + "old_default": null, + "old_type": "const std::vector&", + "param": "lef_paths", + "required_or_optional": "required", + "scalar_or_list": "list" + } + ], + "py_name": "lef_init" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "DEF file; wrapper read_def passes path_text(path) (chipcompiler/tools/ecc/module.py:188)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "def_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "def_init" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "netlist Verilog file; wrapper read_verilog passes path_text(verilog)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "verilog_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "design top module name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "top_module", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "verilog_init" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "Liberty file list; wrapper passes path_texts(lib_paths) (chipcompiler/tools/ecc/module.py:907); empty list is the unset sentinel", + "new_default": null, + "new_type": "std::vector", + "old_default": null, + "old_type": "const std::vector&", + "param": "lib_paths", + "required_or_optional": "required", + "scalar_or_list": "list" + } + ], + "py_name": "lib_init" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "SDC constraints file; initSdc stores the value as-is and the timing flow treats empty as unset; the production harden flow passes None natively (runner.py passes workspace.pdk.sdc which is Path|None), so the converted binding takes an optional path", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": null, + "old_type": "const std::string&", + "param": "sdc_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "sdc_init" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "SPEF parasitics file; wrapper passes path_text(spef_path)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "spef_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "spef_init" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "DEF output file; wrapper def_save passes path_text(def_path)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "def_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "def_save" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "macro placement TCL output file (saveMacroTCL writes a .tcl file)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "tcl_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "tcl_save" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "netlist output file (saveNetList writes a Verilog netlist)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "netlist_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "cell master names to exclude from the netlist, not filesystem paths", + "new_default": "std::set{}", + "new_type": "std::set", + "old_default": "std::set{}", + "old_type": "std::set", + "param": "exclude_cell_names", + "required_or_optional": "optional", + "scalar_or_list": "list" + } + ], + "py_name": "netlist_save" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "GDSII output file; wrapper gds_save passes path_text(output_path)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "gds_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "gds_save" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "idb JSON output file (saveJson serializes the database)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "json_save" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "view JSON output directory", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "output_dir", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "serialization format keyword (e.g. \"pretty\"), not a filesystem path", + "new_default": "\"pretty\"", + "new_type": "const std::string&", + "old_default": "\"pretty\"", + "old_type": "const std::string&", + "param": "json_format", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "view_json_save" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "view JSON edits input file read by applyViewJsonEdits", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "edits_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "view_json_apply_edits" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "serialized database output file/directory (saveData persists the DataManager state)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "save_data" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "serialized database input file/directory read by loadData", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "load_data" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "SoC JSON output file", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "harden core instance names embedded in the JSON, not filesystem paths", + "new_default": "std::vector{}", + "new_type": "const std::vector&", + "old_default": "std::vector{}", + "old_type": "const std::vector&", + "param": "harden_cores", + "required_or_optional": "optional", + "scalar_or_list": "list" + } + ], + "py_name": "write_soc_json" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "path", + "classification_rationale": "abstract LEF output file; wrapper write_abstract_lef passes path_text (chipcompiler/tools/ecc/module.py:1001)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "output_lef_path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "write_abstract_lef" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "non_path", + "classification_rationale": "net name in the design database, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "net type keyword (signal/power/ground), not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_type", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "set_net" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "non_path", + "classification_rationale": "blockage type keyword, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "type", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "clear_blockage" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "non_path", + "classification_rationale": "instance name filter, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "inst_name", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "net name filter, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "net_name", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "report output file: idbGet forwards file_name to rptInst->reportInstance/reportNet (py_db_op.h), which write the report to that file", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "file_name", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "idb_get" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "non_path", + "classification_rationale": "instance name in the design database, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "delete_inst" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "non_path", + "classification_rationale": "net name in the design database, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "delete_net" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "non_path", + "classification_rationale": "instance name to create, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "cell master name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "cell_master", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "placement orientation keyword, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "orient", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "instance type tag, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "type", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "placement status keyword, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "status", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "create_inst" + }, + { + "module": "py_idb", + "params": [ + { + "classification": "non_path", + "classification_rationale": "net name to create, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "connection type keyword, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "conn_type", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "create_net" + }, + { + "module": "py_idrc", + "params": [ + { + "classification": "path", + "classification_rationale": "DRC working directory; wrapper init_drc passes path_text(output_dir) (chipcompiler/tools/ecc/module.py:419)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "temp_directory_path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "init_drc" + }, + { + "module": "py_idrc", + "params": [ + { + "classification": "path", + "classification_rationale": "DRC config JSON file; wrapper run_drc passes path_text(config) (chipcompiler/tools/ecc/module.py:425)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "config", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "path", + "classification_rationale": "DRC report output file; wrapper run_drc passes path_text(report_path) (chipcompiler/tools/ecc/module.py:425)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "report", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "run_drc" + }, + { + "module": "py_idrc", + "params": [ + { + "classification": "path", + "classification_rationale": "DRC feature output file; wrapper save_drc passes path_text(feature_path) (chipcompiler/tools/ecc/module.py:431)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "save_drc" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "die rectangle as a coordinate string (\"llx lly urx ury\"), not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "die_area", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "core rectangle as a coordinate string, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "core_area", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "core site name from the technology LEF, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "core_site", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "IO site name from the technology LEF, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "io_site", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "corner site name from the technology LEF, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "corner_site", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "init_floorplan" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "routing layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "gern_track" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "pin layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "die side names (e.g. \"left\"/\"right\"), not filesystem paths", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "sides", + "required_or_optional": "required", + "scalar_or_list": "list" + } + ], + "py_name": "auto_place_pins" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "pin name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "pin_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "pin layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "place_port" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "IO filler cell master names, not filesystem paths", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "filler_types", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "classification": "non_path", + "classification_rationale": "instance name prefix for created filler cells, not a filesystem path", + "new_default": "\"IOFill\"", + "new_type": "const std::string&", + "old_default": "\"IOFill\"", + "old_type": "const std::string&", + "param": "prefix", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "place_io_filler" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "blockage rectangle as a coordinate string, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "box", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "add_placement_blockage" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "instance name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "halo distance encoded as a string, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "distance", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "add_placement_halo" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "routing layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "blockage rectangle as a coordinate string, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "box", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "add_routing_blockage" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "routing layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "halo distance encoded as a string, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "distance", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "instance name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "add_routing_halo" + }, + { + "module": "py_ifp", + "params": [ + { + "classification": "non_path", + "classification_rationale": "tapcell master name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "tapcell", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "endcap master name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "endcap", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "tapcell" + }, + { + "module": "py_instance", + "params": [ + { + "classification": "non_path", + "classification_rationale": "instance name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "placement orientation keyword, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "orient", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "cell master name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "cellmaster", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "instance source tag: DataManager::placeInst forwards source to instance->set_type (idm_design_inst.cpp), a provenance label with no filesystem semantics", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "source", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "place_instance" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "IO pin name, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "pin_name", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "pin direction keyword, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "direction", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "add_pdn_io" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "instance pin name pattern, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "instance_pin_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "global_net_connect" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "pin name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "pin_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "IO cell master name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "io_cell_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "place_pdn_port" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "power net name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name_power", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "ground net name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name_ground", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "create_grid" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "power net name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name_power", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "ground net name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name_ground", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "create_stripe" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "layer name pair to connect, not filesystem paths", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "layers", + "required_or_optional": "required", + "scalar_or_list": "list" + } + ], + "py_name": "connect_two_layer" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "macro pin layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "pin_layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "PDN layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "pdn_layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "power pin names, not filesystem paths", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "power_pins", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "classification": "non_path", + "classification_rationale": "ground pin names, not filesystem paths", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "ground_pins", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "classification": "non_path", + "classification_rationale": "orientation keyword, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "orient", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "connectMacroPdn" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "connectIoPinToPower" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "connectPowerStripe" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "net_name", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "start layer name, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "layer_start", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "end layer name, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "layer_end", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "add_segment_stripe" + }, + { + "module": "py_ipdn", + "params": [ + { + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "layer", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "top layer name, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "top_layer", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "bottom layer name, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "bottom_layer", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "add_segment_via" + }, + { + "module": "py_ircx", + "params": [ + { + "classification": "path", + "classification_rationale": "RCX config JSON file; wrapper init_rcx passes path_text(config) (chipcompiler/tools/ecc/module.py:884)", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "config", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "PDK identifier string (e.g. \"ics55\") selecting a built-in rule set, already std::optional; not a filesystem path", + "new_default": "py::none()", + "new_type": "const std::optional&", + "old_default": "py::none()", + "old_type": "const std::optional&", + "param": "pdk", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "init_rcx" + }, + { + "module": "py_irt", + "params": [ + { + "classification": "path", + "classification_rationale": "router config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:852)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "std::string&", + "param": "config", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "init_rt" + }, + { + "module": "py_irt", + "params": [ + { + "classification": "path", + "classification_rationale": "early-router config JSON file; wrapper run_ert passes path_text(config) (chipcompiler/tools/ecc/module.py:849)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "std::string&", + "param": "config", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "run_ert" + }, + { + "module": "py_ista", + "params": [ + { + "classification": "path", + "classification_rationale": "STA config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:913)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "std::string&", + "param": "config", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "init_sta" + }, + { + "module": "py_izh", + "params": [ + { + "classification": "path", + "classification_rationale": "fanout-fix config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:1293)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "config", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "fix_fanout" + }, + { + "module": "py_izh", + "params": [ + { + "classification": "path", + "classification_rationale": "filler config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:802)", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "config", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "insert_filler" + }, + { + "module": "py_report", + "params": [ + { + "classification": "path", + "classification_rationale": "wirelength report output file; empty string reports to stdout only", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "report_wirelength" + }, + { + "module": "py_report", + "params": [ + { + "classification": "path", + "classification_rationale": "database summary report output file; empty string reports to stdout only", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "report_db" + }, + { + "module": "py_report", + "params": [ + { + "classification": "path", + "classification_rationale": "congestion report output file; empty string reports to stdout only", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "report_congestion" + }, + { + "module": "py_report", + "params": [ + { + "classification": "path", + "classification_rationale": "dangling-net report output file; empty string reports to stdout only", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "report_dangling_net" + }, + { + "module": "py_report", + "params": [ + { + "classification": "path", + "classification_rationale": "route report output file; empty string reports to stdout only", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "classification": "non_path", + "classification_rationale": "net name filter for the route report, not a filesystem path", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "net", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "py_name": "report_route" + }, + { + "module": "py_report", + "params": [ + { + "classification": "non_path", + "classification_rationale": "instance name prefixes to bucket, not filesystem paths", + "new_default": "std::vector{}", + "new_type": "const std::vector&", + "old_default": "std::vector{}", + "old_type": "const std::vector&", + "param": "prefixes", + "required_or_optional": "optional", + "scalar_or_list": "list" + } + ], + "py_name": "report_place_distribution" + }, + { + "module": "py_report", + "params": [ + { + "classification": "non_path", + "classification_rationale": "instance name prefix to report on, not a filesystem path", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "prefix", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "report_prefixed_instance" + }, + { + "module": "py_report", + "params": [ + { + "classification": "path", + "classification_rationale": "DRC report output path by API shape; the currently bound one-argument ReportManager::reportDRC overload is a stub with its write lines commented out (report_manager.cpp:186), pending re-enable", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "required_or_optional": "required", + "scalar_or_list": "scalar" + } + ], + "py_name": "report_drc" + } + ], + "version": 1 +} diff --git a/scripts/binding_census/binding_spec.schema.json b/scripts/binding_census/binding_spec.schema.json new file mode 100644 index 0000000000..3dfeecc045 --- /dev/null +++ b/scripts/binding_census/binding_spec.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "ecc_py binding spec (curated)", + "type": "object", + "additionalProperties": false, + "required": ["version", "bindings"], + "properties": { + "version": {"const": 1}, + "bindings": { + "type": "array", + "items": {"$ref": "#/$defs/binding"} + } + }, + "$defs": { + "binding": { + "type": "object", + "additionalProperties": false, + "required": ["module", "py_name", "params"], + "properties": { + "module": {"type": "string", "pattern": "^py_[a-z]+$"}, + "py_name": {"type": "string", "minLength": 1}, + "params": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/param"} + } + } + }, + "param": { + "type": "object", + "additionalProperties": false, + "required": [ + "param", + "old_type", + "old_default", + "new_type", + "new_default", + "scalar_or_list", + "required_or_optional", + "classification", + "classification_rationale" + ], + "properties": { + "param": {"type": "string", "minLength": 1}, + "old_type": {"type": "string", "minLength": 1}, + "old_default": {"type": ["string", "null"]}, + "new_type": {"type": "string", "minLength": 1}, + "new_default": {"type": ["string", "null"]}, + "scalar_or_list": {"enum": ["scalar", "list"]}, + "required_or_optional": {"enum": ["required", "optional"]}, + "classification": {"enum": ["path", "non_path", "ambiguous"]}, + "classification_rationale": {"type": "string"} + }, + "allOf": [ + { + "if": { + "properties": {"classification": {"const": "ambiguous"}}, + "required": ["classification"] + }, + "then": { + "properties": {"classification_rationale": {"minLength": 1}} + } + } + ] + } + } +} diff --git a/scripts/binding_census/dead_bindings.md b/scripts/binding_census/dead_bindings.md new file mode 100644 index 0000000000..67a251c7b4 --- /dev/null +++ b/scripts/binding_census/dead_bindings.md @@ -0,0 +1,278 @@ +# Dead-binding audit + +Generated by `binding_census.py --ecc-root` from the ecc wrapper (`chipcompiler/tools/ecc/module.py`) joined against the binding census. Regenerate with: + +```sh +uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py \ + --ecc-root +``` + +Statuses: `active` = bound and registered in `ecc_py`; `disabled (file:line)` = an `m.def` exists but is commented out or its register function is never called; `absent` = no `m.def` anywhere in the `ecc_py` census (the call would raise `AttributeError` at runtime). Note that a sibling native module (e.g. `ipower_cpp`, built from `src/operation/refactor`) may bind the same name under a different Python module; that does not make the `ecc_py` call live. + +| Wrapper method | Called binding | Status | +|---|---|---| +| `exit` | `flow_exit` | active | +| `get_dmInst_ptr` | `get_dmInst` | active | +| `pydb` | `pydb` | active | +| `build_macro_connection_map` | `build_macro_connection_map` | absent | +| `build_connection_map` | `build_connection_map` | absent | +| `reset_data` | `reset_data` | active | +| `init_config` | `flow_init` | active | +| `init_config` | `db_init` | active | +| `update_step_paths` | `db_init` | active | +| `update_sta_data_config` | `db_init` | active | +| `idb_init` | `idb_init` | active | +| `set_net` | `set_net` | active | +| `remove_except_pg_net` | `remove_except_pg_net` | active | +| `clear_blockage` | `clear_blockage` | active | +| `idb_get` | `idb_get` | active | +| `delete_inst` | `delete_inst` | active | +| `delete_net` | `delete_net` | active | +| `create_inst` | `create_inst` | active | +| `create_net` | `create_net` | active | +| `write_placement_back` | `write_placement_back` | active | +| `init_techlef` | `tech_lef_init` | active | +| `init_lefs` | `lef_init` | active | +| `read_def` | `def_init` | active | +| `read_verilog` | `verilog_init` | active | +| `def_save` | `def_save` | active | +| `gds_save` | `gds_save` | active | +| `tcl_save` | `tcl_save` | active | +| `verilog_save` | `netlist_save` | active | +| `json_save` | `json_save` | active | +| `view_json_save` | `view_json_save` | active | +| `view_json_apply_edits` | `view_json_apply_edits` | active | +| `save_data` | `save_data` | active | +| `load_data` | `load_data` | active | +| `write_soc_json` | `write_soc_json` | active | +| `feature_sammry` | `feature_summary` | active | +| `feature_step` | `feature_tool` | active | +| `feature_eval_map` | `feature_eval_map` | active | +| `feature_eval_summary` | `feature_eval_summary` | active | +| `feature_timing_eval_summary` | `feature_timing_eval_summary` | active | +| `feature_net_eval` | `feature_net_eval` | active | +| `feature_cong_map` | `feature_cong_map` | active | +| `report_wirelength` | `report_wirelength` | active | +| `report_summary` | `report_db` | active | +| `report_congestion` | `report_congestion` | active | +| `report_dangling_net` | `report_dangling_net` | active | +| `report_route` | `report_route` | active | +| `report_place_distribution` | `report_place_distribution` | active | +| `report_prefixed_instance` | `report_prefixed_instance` | active | +| `report_drc` | `report_drc` | active | +| `read_vcd_cpp` | `read_vcd_cpp` | absent | +| `read_pg_spef` | `read_pg_spef` | absent | +| `report_power_cpp` | `report_power_cpp` | absent | +| `report_power` | `report_power` | absent | +| `report_ir_drop` | `report_ir_drop` | absent | +| `get_wire_timing_power_data` | `get_wire_timing_power_data` | absent | +| `run_cts` | `run_cts` | active | +| `report_cts` | `cts_report` | active | +| `feature_cts_map` | `feature_cts_eval` | active | +| `init_drc` | `init_drc` | active | +| `run_drc` | `run_drc` | active | +| `save_drc` | `save_drc` | active | +| `init_floorplan` | `init_floorplan` | active | +| `gern_track` | `gern_track` | active | +| `place_port` | `place_port` | active | +| `place_io_filler` | `place_io_filler` | active | +| `add_placement_blockage` | `add_placement_blockage` | active | +| `add_placement_halo` | `add_placement_halo` | active | +| `add_routing_blockage` | `add_routing_blockage` | active | +| `add_routing_halo` | `add_routing_halo` | active | +| `place_instance` | `place_instance` | active | +| `add_pdn_io` | `add_pdn_io` | active | +| `global_net_connect` | `global_net_connect` | active | +| `place_pdn_port` | `place_pdn_port` | active | +| `create_pdn_grid` | `create_grid` | active | +| `create_pdn_stripe` | `create_stripe` | active | +| `connect_pdn_layers` | `connect_two_layer` | active | +| `connectMacroPdn` | `connectMacroPdn` | active | +| `connectIoPinToPower` | `connectIoPinToPower` | active | +| `connectPowerStripe` | `connectPowerStripe` | active | +| `add_segment_stripe` | `add_segment_stripe` | active | +| `add_segment_via` | `add_segment_via` | active | +| `auto_place_pins` | `auto_place_pins` | active | +| `tapcell` | `tapcell` | active | +| `pnp` | `run_pnp` | absent | +| `run_placement` | `run_placer` | absent | +| `init_pl` | `init_pl` | absent | +| `destroy_pl` | `destroy_pl` | absent | +| `feature_placement_map` | `feature_pl_eval` | active | +| `run_incremental_flow` | `run_incremental_flow` | absent | +| `run_legalize` | `run_incremental_lg` | absent | +| `run_filler` | `insert_filler` | active | +| `run_macro_placement` | `runMP` | disabled (src/interface/python/py_imp/py_register_imp.cpp:158) | +| `run_refinement` | `runRef` | disabled (src/interface/python/py_imp/py_register_imp.cpp:159) | +| `run_ai_placement` | `run_ai_placement` | absent | +| `placer_run_mp` | `placer_run_mp` | absent | +| `placer_run_gp` | `placer_run_gp` | absent | +| `placer_run_lg` | `placer_run_lg` | absent | +| `placer_run_dp` | `placer_run_dp` | absent | +| `feature_macro_drc_distribution` | `feature_macro_drc` | active | +| `run_ert` | `run_ert` | active | +| `run_routing` | `init_rt` | active | +| `run_routing` | `run_rt` | active | +| `run_routing` | `destroy_rt` | active | +| `close_routing` | `destroy_rt` | active | +| `feature_route_read` | `feature_route_read` | active | +| `feature_route` | `feature_route` | active | +| `init_rcx` | `init_rcx` | active | +| `run_rcx` | `run_rcx` | active | +| `report_rcx` | `report_rcx` | active | +| `run_timing` | `lib_init` | active | +| `run_timing` | `sdc_init` | active | +| `run_timing` | `spef_init` | active | +| `run_timing` | `init_sta` | active | +| `run_timing` | `run_sta` | active | +| `run_timing` | `destroy_sta` | active | +| `write_abstract_lef` | `write_abstract_lef` | active | +| `write_timing_model` | `lib_init` | active | +| `write_timing_model` | `sdc_init` | active | +| `write_timing_model` | `spef_init` | active | +| `write_timing_model` | `init_sta` | active | +| `write_timing_model` | `extract_lib` | active | +| `write_timing_model` | `destroy_sta` | active | +| `run_to` | `run_to` | absent | +| `run_timing_opt_drv` | `run_to_drv` | absent | +| `run_timing_opt_hold` | `run_to_hold` | absent | +| `run_timing_opt_setup` | `run_to_setup` | absent | +| `layout_patchs` | `layout_patchs` | disabled (src/interface/python/py_vec/py_register_vec.h:27) | +| `layout_graph` | `layout_graph` | disabled (src/interface/python/py_vec/py_register_vec.h:28) | +| `generate_vectors` | `generate_vectors` | disabled (src/interface/python/py_vec/py_register_vec.h:29) | +| `vectors_nets_to_def` | `read_vectors_nets` | disabled (src/interface/python/py_vec/py_register_vec.h:30) | +| `vectors_nets_patterns_to_def` | `read_vectors_nets_patterns` | disabled (src/interface/python/py_vec/py_register_vec.h:31) | +| `get_timing_wire_graph` | `get_timing_wire_graph` | disabled (src/interface/python/py_vec/py_register_vec.h:47) | +| `get_timing_instance_graph` | `get_timing_instance_graph` | disabled (src/interface/python/py_vec/py_register_vec.h:48) | +| `total_wirelength_dict` | `total_wirelength_dict` | active | +| `cell_density` | `cell_density` | active | +| `pin_density` | `pin_density` | active | +| `net_density` | `net_density` | active | +| `rudy_congestion` | `rudy_congestion` | active | +| `lut_rudy_congestion` | `lut_rudy_congestion` | active | +| `egr_congestion` | `egr_congestion` | active | +| `timing_power_hpwl` | `timing_power_hpwl` | active | +| `timing_power_stwl` | `timing_power_stwl` | active | +| `timing_power_egr` | `timing_power_egr` | active | +| `eval_macro_margin` | `eval_macro_margin` | active | +| `eval_continuous_white_space` | `eval_continuous_white_space` | active | +| `eval_macro_channel` | `eval_macro_channel` | active | +| `eval_cell_hierarchy` | `eval_cell_hierarchy` | active | +| `eval_macro_hierarchy` | `eval_macro_hierarchy` | active | +| `eval_macro_connection` | `eval_macro_connection` | active | +| `eval_macro_pin_connection` | `eval_macro_pin_connection` | active | +| `eval_macro_io_pin_connection` | `eval_macro_io_pin_connection` | active | +| `eval_overflow` | `eval_overflow` | active | +| `run_net_opt` | `fix_fanout` | active | +| `build_rc_tree_from_flat_data` | `build_rc_tree_from_flat_data` | absent | +| `update_and_get_all_pin_timings` | `update_and_get_all_pin_timings` | absent | + +## Dead-method candidates + +Wrapper methods whose every `ecc_py` call is disabled or absent: + +- `build_connection_map` +- `build_macro_connection_map` +- `build_rc_tree_from_flat_data` +- `destroy_pl` +- `generate_vectors` +- `get_timing_instance_graph` +- `get_timing_wire_graph` +- `get_wire_timing_power_data` +- `init_pl` +- `layout_graph` +- `layout_patchs` +- `placer_run_dp` +- `placer_run_gp` +- `placer_run_lg` +- `placer_run_mp` +- `pnp` +- `read_pg_spef` +- `read_vcd_cpp` +- `report_ir_drop` +- `report_power` +- `report_power_cpp` +- `run_ai_placement` +- `run_incremental_flow` +- `run_legalize` +- `run_macro_placement` +- `run_placement` +- `run_refinement` +- `run_timing_opt_drv` +- `run_timing_opt_hold` +- `run_timing_opt_setup` +- `run_to` +- `update_and_get_all_pin_timings` +- `vectors_nets_patterns_to_def` +- `vectors_nets_to_def` + +## Wrapper methods with no `ecc_py` calls + +Informational only (stubs or pure-Python helpers; not evaluated by the disabled/absent rule): + +- `__init__` +- `build_timing_graph` +- `close` +- `convert_idb_to_timing_netlist` +- `create_data_flow` +- `get_ecc` +- `get_net_name` +- `get_segment_capacitance` +- `get_segment_resistance` +- `get_used_libs` +- `get_wire_timing_data` +- `init_floorplan_by_area` +- `init_floorplan_by_core_utilization` +- `init_log` +- `init_sta` +- `is_db_data_exists` +- `is_rt_timing_enable` +- `link_design` +- `make_rc_tree_edge` +- `make_rc_tree_inner_node` +- `make_rc_tree_obj_node` +- `read_lef_def` +- `read_liberty` +- `read_netlist` +- `read_sdc` +- `read_spef` +- `release_sta` +- `report_sta` +- `report_timing` +- `run_sta` +- `set_design_workspace` +- `set_exclude_cell_names` +- `update_clock_timing` +- `update_rc_tree_info` +- `update_timing` + +## List-element `None` audit + +Today the wrapper normalizes list arguments with `path_texts()` +(`chipcompiler/utility/path.py`), which silently drops `None` elements. After +the `std::vector` conversion a `None` element raises +`TypeError` instead. The lists reaching `db_init` / `lef_init` / `lib_init` +were traced through every producer in `chipcompiler/tools/ecc/module.py` and +its callers: + +- `init_lefs(workspace.pdk.lefs)` (`runner.py`): `pdk.lefs` is built in + `chipcompiler/data/pdk.py` as `[path for path in lef_paths if path.is_file()]` + — a list of existing `Path` objects; elements are never `None`. +- `run_timing(lib_paths=...)` (`runner.py`): receives `workspace.pdk.libs` + (same filtered `Path` list construction) and validates every element with + `os.path.isfile` before the call; `module.py` itself coerces a `None` *list* + to `[]` (`if lib_paths is None: lib_paths = []`). +- `write_timing_model(lib_paths=signoff_item["liberty_files"])` (`runner.py`): + `collect_sta_signoff_items` reads `liberty.get("path", [])` from the sta + config JSON — a JSON array of strings. The parallel `run_sta` flow validates + the same list with `os.path.exists` per element, which already raises + `TypeError` on a `null` element before any binding call. +- `update_sta_data_config(lib_paths=...)`: only exercised by tests with real + `Path` lists. + +Conclusion: `None` *elements* cannot occur in any current producer — the only +`None` value in play is the list argument itself, which `module.py` already +coerces to `[]` before calling `path_texts`. A hand-edited sta config JSON +with a `null` array element is the only theoretical injection point; there the +conversion changes a silent drop into a loud `TypeError`, which is the desired +behavior. diff --git a/scripts/binding_census/dead_bindings.py b/scripts/binding_census/dead_bindings.py new file mode 100755 index 0000000000..06d5a69ff4 --- /dev/null +++ b/scripts/binding_census/dead_bindings.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +"""Dead-binding audit for the ecc_py binding census (--ecc-root). + +Parses the ecc wrapper (``chipcompiler/tools/ecc/module.py`` in the outer +repo, read-only) with :mod:`ast`, classifies every ``self.ecc.(`` call +against the census as active / disabled (file:line) / absent, and renders +``dead_bindings.md``. +""" +import ast +from pathlib import Path + + +def audit_wrapper(module_py: Path, discovery: dict) -> dict: + """Parse the ecc wrapper module and classify every ``self.ecc.(`` call. + + Returns a structured audit: per-method call rows, the dead-method + candidates (every call disabled or absent), and methods with no calls. + """ + tree = ast.parse(module_py.read_text()) + wrapper_class = next( + (node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == "ECCToolsModule"), + None, + ) + if wrapper_class is None: + raise ValueError(f"ECCToolsModule class not found in {module_py}") + status_by_name: dict[str, str] = {} + for binding in discovery["bindings"]: + label = ( + "active" + if binding.status_in_source == "active" + else f"disabled ({binding.file}:{binding.line})" + ) + previous = status_by_name.get(binding.py_name) + if previous is None or (previous.startswith("disabled") and binding.status_in_source == "active"): + status_by_name[binding.py_name] = label + rows: list[dict] = [] + dead: list[str] = [] + without_calls: list[str] = [] + for node in wrapper_class.body: + if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + continue + calls: list[dict] = [] + seen: set[str] = set() + for sub in ast.walk(node): + if ( + isinstance(sub, ast.Call) + and isinstance(sub.func, ast.Attribute) + and isinstance(sub.func.value, ast.Attribute) + and sub.func.value.attr == "ecc" + and isinstance(sub.func.value.value, ast.Name) + and sub.func.value.value.id == "self" + ): + name = sub.func.attr + if name in seen: + continue + seen.add(name) + calls.append({"binding": name, "status": status_by_name.get(name, "absent")}) + if not calls: + without_calls.append(node.name) + continue + rows.append({"method": node.name, "line": node.lineno, "calls": calls}) + if all(call["status"] != "active" for call in calls): + dead.append(node.name) + return { + "rows": rows, + "dead_method_candidates": sorted(dead), + "methods_without_calls": sorted(without_calls), + } + + +LIST_NONE_AUDIT = """\ +## List-element `None` audit + +Today the wrapper normalizes list arguments with `path_texts()` +(`chipcompiler/utility/path.py`), which silently drops `None` elements. After +the `std::vector` conversion a `None` element raises +`TypeError` instead. The lists reaching `db_init` / `lef_init` / `lib_init` +were traced through every producer in `chipcompiler/tools/ecc/module.py` and +its callers: + +- `init_lefs(workspace.pdk.lefs)` (`runner.py`): `pdk.lefs` is built in + `chipcompiler/data/pdk.py` as `[path for path in lef_paths if path.is_file()]` + — a list of existing `Path` objects; elements are never `None`. +- `run_timing(lib_paths=...)` (`runner.py`): receives `workspace.pdk.libs` + (same filtered `Path` list construction) and validates every element with + `os.path.isfile` before the call; `module.py` itself coerces a `None` *list* + to `[]` (`if lib_paths is None: lib_paths = []`). +- `write_timing_model(lib_paths=signoff_item["liberty_files"])` (`runner.py`): + `collect_sta_signoff_items` reads `liberty.get("path", [])` from the sta + config JSON — a JSON array of strings. The parallel `run_sta` flow validates + the same list with `os.path.exists` per element, which already raises + `TypeError` on a `null` element before any binding call. +- `update_sta_data_config(lib_paths=...)`: only exercised by tests with real + `Path` lists. + +Conclusion: `None` *elements* cannot occur in any current producer — the only +`None` value in play is the list argument itself, which `module.py` already +coerces to `[]` before calling `path_texts`. A hand-edited sta config JSON +with a `null` array element is the only theoretical injection point; there the +conversion changes a silent drop into a loud `TypeError`, which is the desired +behavior. +""" + + +def render_dead_bindings_md(audit: dict) -> str: + lines = [ + "# Dead-binding audit", + "", + "Generated by `binding_census.py --ecc-root` from the ecc wrapper " + "(`chipcompiler/tools/ecc/module.py`) joined against the binding " + "census. Regenerate with:", + "", + "```sh", + "uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py \\", + " --ecc-root ", + "```", + "", + "Statuses: `active` = bound and registered in `ecc_py`; " + "`disabled (file:line)` = an `m.def` exists but is commented out or its " + "register function is never called; `absent` = no `m.def` anywhere in " + "the `ecc_py` census (the call would raise `AttributeError` at runtime). " + "Note that a sibling native module (e.g. `ipower_cpp`, built from " + "`src/operation/refactor`) may bind the same name under a different " + "Python module; that does not make the `ecc_py` call live.", + "", + "| Wrapper method | Called binding | Status |", + "|---|---|---|", + ] + for row in audit["rows"]: + for call in row["calls"]: + lines.append(f"| `{row['method']}` | `{call['binding']}` | {call['status']} |") + lines += [ + "", + "## Dead-method candidates", + "", + "Wrapper methods whose every `ecc_py` call is disabled or absent:", + "", + ] + for name in audit["dead_method_candidates"]: + lines.append(f"- `{name}`") + lines += [ + "", + "## Wrapper methods with no `ecc_py` calls", + "", + "Informational only (stubs or pure-Python helpers; not evaluated by the " + "disabled/absent rule):", + "", + ] + for name in audit["methods_without_calls"]: + lines.append(f"- `{name}`") + lines += ["", LIST_NONE_AUDIT.strip(), ""] + return "\n".join(lines) diff --git a/scripts/binding_census/lexer.py b/scripts/binding_census/lexer.py new file mode 100755 index 0000000000..92462b5253 --- /dev/null +++ b/scripts/binding_census/lexer.py @@ -0,0 +1,468 @@ +#!/usr/bin/env python +"""Lexical discovery scanner for the ecc_py binding census. + +A small lexer (a state machine over code / line comment / block comment / +string literal / char literal with paren-brace-bracket depth tracking — no +cross-line regex) that finds every module-level ``m.def(`` in the register +files, including multiline and commented-out statements, extracts the +``py::arg("name") = default`` entries at paren depth 1, and resolves whether +a binding is ``active`` or ``disabled`` (commented statement, or its +enclosing ``register_*`` function is never called from +``python_moodule.cc``). +""" +import re +from dataclasses import dataclass, field +from pathlib import Path + +PYTHON_INTERFACE_DIR = Path("src/interface/python") +MODULE_CC = PYTHON_INTERFACE_DIR / "python_moodule.cc" + + +@dataclass(frozen=True) +class Span: + kind: str # "code" | "line_comment" | "block_comment" | "string" | "char" + start: int + end: int # exclusive + start_line: int # 1-based line of span start + + +@dataclass(frozen=True) +class DiscoveredParam: + name: str + default: str | None + + def to_json(self) -> dict: + return {"name": self.name, "default": self.default} + + +@dataclass +class DiscoveredBinding: + module: str + file: str + line: int + py_name: str + cpp_target: str + register_function: str | None + commented: bool + status_in_source: str # "active" | "disabled" + params: list[DiscoveredParam] = field(default_factory=list) + raw: str = "" + + def to_json(self) -> dict: + return { + "module": self.module, + "file": self.file, + "line": self.line, + "py_name": self.py_name, + "cpp_target": self.cpp_target, + "register_function": self.register_function, + "commented": self.commented, + "status_in_source": self.status_in_source, + "params": [p.to_json() for p in self.params], + } + + +def lex_spans(text: str) -> list[Span]: + """Split C++ source into lexical spans with a state machine. + + States: code, line comment, block comment, string literal (with escapes), + char literal (with escapes). Newlines inside block comments and literals + are tracked so every span carries an accurate start line. + """ + spans: list[Span] = [] + i = 0 + n = len(text) + line = 1 + state = "code" + span_start = 0 + span_line = 1 + + def emit(kind: str, end: int) -> None: + if end > span_start: + spans.append(Span(kind, span_start, end, span_line)) + + while i < n: + c = text[i] + nxt = text[i + 1] if i + 1 < n else "" + if state == "code": + if c == "/" and nxt == "/": + emit("code", i) + state, span_start, span_line = "line_comment", i, line + i += 2 + continue + if c == "/" and nxt == "*": + emit("code", i) + state, span_start, span_line = "block_comment", i, line + i += 2 + continue + if c == '"': + emit("code", i) + state, span_start, span_line = "string", i, line + i += 1 + continue + if c == "'": + emit("code", i) + state, span_start, span_line = "char", i, line + i += 1 + continue + if c == "\n": + line += 1 + i += 1 + elif state == "line_comment": + if c == "\n": + emit("line_comment", i) + state, span_start, span_line = "code", i, line + line += 1 + i += 1 + elif state == "block_comment": + if c == "*" and nxt == "/": + emit("block_comment", i + 2) + state, span_start, span_line = "code", i + 2, line + i += 2 + continue + if c == "\n": + line += 1 + i += 1 + else: # string or char literal + quote = '"' if state == "string" else "'" + if c == "\\": + i += 2 + continue + if c == quote: + emit(state, i + 1) + state, span_start, span_line = "code", i + 1, line + i += 1 + continue + if c == "\n": + line += 1 + i += 1 + emit(state, n) + return spans + + +_IDENT_CHARS = frozenset("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") + + +def _span_index_at(spans: list[Span], pos: int) -> int: + """Binary search: index of the span containing ``pos``.""" + lo, hi = 0, len(spans) - 1 + while lo < hi: + mid = (lo + hi) // 2 + if pos >= spans[mid].end: + lo = mid + 1 + else: + hi = mid + return lo + + +def _find_m_def_calls(text: str, spans: list[Span]) -> list[tuple[int, bool]]: + """Locate every ``m.def(`` occurrence in code and comment spans. + + Returns (position of the ``m``, commented) pairs. Class-member ``.def(`` + chained on a ``py::class_<...>(m, ...)`` object is rejected because the + character before ``.def`` is not the module identifier ``m``. + """ + hits: list[tuple[int, bool]] = [] + for span in spans: + if span.kind not in ("code", "line_comment", "block_comment"): + continue + seg = text[span.start : span.end] + j = 0 + while True: + k = seg.find("m.def", j) + if k == -1: + break + j = k + 5 + abs_pos = span.start + k + before = text[abs_pos - 1] if abs_pos > 0 else "" + if before in _IDENT_CHARS or before == ".": + continue + rest = seg[k + 5 :] + if re.match(r"\s*\(", rest): + hits.append((abs_pos, span.kind != "code")) + return hits + + +def _skip_span(spans: list[Span], si: int, i: int) -> tuple[int, int]: + """Advance (span index, position) past the current position.""" + while si + 1 < len(spans) and i >= spans[si + 1].start: + si += 1 + return si, i + + +def _matching_paren(text: str, spans: list[Span], open_paren: int) -> int: + """Return the index just past the ')' matching text[open_paren].""" + depth = 0 + si = _span_index_at(spans, open_paren) + i = open_paren + while i < len(text): + span = spans[si] + if span.kind != "code": + i = span.end + si, i = _skip_span(spans, si, i) + continue + c = text[i] + if c == "(": + depth += 1 + elif c == ")": + depth -= 1 + if depth == 0: + return i + 1 + i += 1 + raise ValueError(f"unbalanced parens from offset {open_paren}") + + +def _split_top_level_args(text: str, spans: list[Span], start: int, end: int) -> list[tuple[int, int]]: + """Split the call argument range (start=index of '(', end=past ')') into + top-level argument ranges. + + Commas nested inside parens, braces (lambda bodies), brackets, string or + char literals, comments, and template angle brackets do not split. Angle + brackets are tracked heuristically (a ``>`` only closes when angle depth + is positive and it is not part of ``->``); sufficient for the default + expressions used in these register files, e.g. + ``std::map{}``. + """ + args: list[tuple[int, int]] = [] + paren = brace = bracket = angle = 0 + arg_start = start + 1 + si = _span_index_at(spans, start) + i = start + 1 + limit = end - 1 + while i < limit: + span = spans[si] + if span.kind != "code": + i = span.end + si, i = _skip_span(spans, si, i) + continue + c = text[i] + if c == "(": + paren += 1 + elif c == ")": + paren -= 1 + elif c == "{": + brace += 1 + elif c == "}": + brace -= 1 + elif c == "[": + bracket += 1 + elif c == "]": + bracket -= 1 + elif c == "<": + angle += 1 + elif c == ">": + if angle > 0 and text[i - 1] != "-": + angle -= 1 + elif c == "," and paren == 0 and brace == 0 and bracket == 0 and angle == 0: + args.append((arg_start, i)) + arg_start = i + 1 + i += 1 + if text[arg_start:limit].strip(): + args.append((arg_start, limit)) + return args + + +def _decode_string_literal(literal: str) -> str: + """Decode the contents of a double-quoted C++ string literal.""" + body = literal[1:-1] + out: list[str] = [] + i = 0 + escapes = {"n": "\n", "t": "\t", "r": "\r", "0": "\0", "\\": "\\", '"': '"', "'": "'"} + while i < len(body): + c = body[i] + if c == "\\" and i + 1 < len(body): + out.append(escapes.get(body[i + 1], body[i + 1])) + i += 2 + else: + out.append(c) + i += 1 + return "".join(out) + + +def _normalize_default(text: str) -> str: + """Whitespace-normalize a captured default expression.""" + return " ".join(text.split()) + + +def _string_literal_at(text: str, spans: list[Span], start: int, end: int) -> str | None: + """If the range holds exactly one string literal (plus whitespace), decode it.""" + si = _span_index_at(spans, start) + while si < len(spans) and spans[si].start < end: + span = spans[si] + if span.kind == "string" and span.start >= start and span.end <= end: + if text[start : span.start].strip() or text[span.end : end].strip(): + return None + return _decode_string_literal(text[span.start : span.end]) + si += 1 + return None + + +def _parse_py_arg(text: str, spans: list[Span], start: int, end: int) -> DiscoveredParam | None: + """Parse one top-level argument range as ``py::arg("name") = default``.""" + stripped = text[start:end] + match = re.match(r"\s*py::arg\s*\(", stripped) + if not match: + return None + open_paren = start + match.end() - 1 + close_paren = _matching_paren(text, spans, open_paren) + name = _string_literal_at(text, spans, open_paren + 1, close_paren - 1) + if name is None: + raise ValueError(f"py::arg without a plain string name at offset {start}") + rest = text[close_paren:end].strip() + default = None + if rest.startswith("="): + default = _normalize_default(rest[1:]) + elif rest: + raise ValueError(f"unexpected trailing tokens after py::arg at offset {start}: {rest!r}") + return DiscoveredParam(name=name, default=default) + + +def _parse_m_def_core( + text: str, spans: list[Span], m_pos: int +) -> tuple[str, str, list[DiscoveredParam], str]: + """Parse one m.def call whose ``m`` is at m_pos in lexed code text.""" + open_paren = text.index("(", m_pos) + end = _matching_paren(text, spans, open_paren) + args = _split_top_level_args(text, spans, open_paren, end) + if len(args) < 2: + raise ValueError(f"m.def with fewer than two arguments at offset {m_pos}") + py_name = _string_literal_at(text, spans, args[0][0], args[0][1]) + if py_name is None: + raise ValueError(f"m.def first argument is not a string literal at offset {m_pos}") + target_text = text[args[1][0] : args[1][1]].strip() + if target_text.startswith("["): + cpp_target = "" + else: + cpp_target = target_text.lstrip("&").split()[0].rstrip(",") + params: list[DiscoveredParam] = [] + for arg_start, arg_end in args[2:]: + param = _parse_py_arg(text, spans, arg_start, arg_end) + if param is not None: + params.append(param) + return py_name, cpp_target, params, text[m_pos:end] + + +def _parse_m_def_statement( + text: str, spans: list[Span], m_pos: int, commented: bool +) -> tuple[str, str, int, list[DiscoveredParam], str]: + """Parse one m.def statement starting at the ``m`` of ``m.def``. + + Returns (py_name, cpp_target, start_line, params, raw_statement). For + commented-out statements the enclosing comment's content is stripped of + its comment markers and re-lexed as code, so strings and nesting inside + it are handled by the same machinery. + """ + start_line = text.count("\n", 0, m_pos) + 1 + if commented: + span = spans[_span_index_at(spans, m_pos)] + sub = text[m_pos : span.end] + if span.kind == "block_comment": + lines = sub.split("\n") + lines = [lines[0]] + [re.sub(r"^\s*\*", "", line) for line in lines[1:]] + sub = re.sub(r"\*/\s*$", "", "\n".join(lines)) + py_name, cpp_target, params, raw = _parse_m_def_core(sub, lex_spans(sub), 0) + return py_name, cpp_target, start_line, params, raw + py_name, cpp_target, params, raw = _parse_m_def_core(text, spans, m_pos) + return py_name, cpp_target, start_line, params, raw + + +def _find_register_function_bodies(text: str, spans: list[Span]) -> list[tuple[str, int, int]]: + """Return (name, body_start, body_end) for every ``register_*`` function + definition in the file.""" + code_only = list(text) + for span in spans: + if span.kind != "code": + for pos in range(span.start, span.end): + if code_only[pos] != "\n": + code_only[pos] = " " + code_text = "".join(code_only) + bodies = [] + for match in re.finditer(r"\bregister_\w+\s*\([^)]*\)\s*\{", code_text): + name = match.group(0).split("(")[0].strip() + open_brace = match.end() - 1 + depth = 0 + i = open_brace + while i < len(code_text): + if code_text[i] == "{": + depth += 1 + elif code_text[i] == "}": + depth -= 1 + if depth == 0: + break + i += 1 + bodies.append((name, open_brace, i + 1)) + return bodies + + +def parse_called_register_functions(text: str) -> set[str]: + """Parse python_moodule.cc for the set of ``register_*`` calls that are + actually made (commented-out calls are excluded).""" + spans = lex_spans(text) + called: set[str] = set() + for span in spans: + if span.kind != "code": + continue + for match in re.finditer(r"\b(register_\w+)\s*\(", text[span.start : span.end]): + called.add(match.group(1)) + return called + + +def discover_bindings_in_text( + text: str, module: str, file: str, called_registers: set[str] +) -> list[DiscoveredBinding]: + """Discover every module-level m.def binding in one register file.""" + spans = lex_spans(text) + bodies = _find_register_function_bodies(text, spans) + bindings: list[DiscoveredBinding] = [] + for m_pos, commented in _find_m_def_calls(text, spans): + py_name, cpp_target, line, params, raw = _parse_m_def_statement(text, spans, m_pos, commented) + register_function = next( + (name for name, body_start, body_end in bodies if body_start <= m_pos < body_end), None + ) + if commented or register_function is None or register_function not in called_registers: + status = "disabled" + else: + status = "active" + bindings.append( + DiscoveredBinding( + module=module, + file=file, + line=line, + py_name=py_name, + cpp_target=cpp_target, + register_function=register_function, + commented=commented, + status_in_source=status, + params=params, + raw=raw, + ) + ) + return bindings + + +def _register_files(repo_root: Path) -> list[Path]: + base = repo_root / PYTHON_INTERFACE_DIR + files = sorted(base.glob("py_*/py_register_*.h")) + sorted(base.glob("py_*/py_register_*.cpp")) + return files + + +def discover(repo_root: Path) -> dict: + """Run discovery over the whole python interface tree.""" + module_cc = repo_root / MODULE_CC + called = parse_called_register_functions(module_cc.read_text()) + bindings: list[DiscoveredBinding] = [] + for path in _register_files(repo_root): + rel = path.relative_to(repo_root).as_posix() + module = path.parent.name + bindings.extend(discover_bindings_in_text(path.read_text(), module, rel, called)) + return { + "called_register_functions": sorted(called), + "bindings": bindings, + } + + +def discovery_to_json(discovery: dict) -> dict: + return { + "called_register_functions": discovery["called_register_functions"], + "bindings": [b.to_json() for b in discovery["bindings"]], + } diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json new file mode 100644 index 0000000000..a9376ed5cf --- /dev/null +++ b/scripts/binding_census/manifest.json @@ -0,0 +1,2623 @@ +{ + "entries": [ + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "db config JSON file; wrapper init_config/update_step_paths pass path_text(db_config)", + "cpp_target": "db_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 27, + "module": "py_config", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "config_path", + "py_name": "db_init", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "DEF file; empty string is the unset sentinel", + "cpp_target": "db_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 27, + "module": "py_config", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "def_path", + "py_name": "db_init", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "feature output directory; wrapper passes path_text(feature_dir)", + "cpp_target": "db_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 27, + "module": "py_config", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "feature_path", + "py_name": "db_init", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "cell LEF file list; empty list is the unset sentinel, no optional-ization", + "cpp_target": "db_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 27, + "module": "py_config", + "new_default": "std::vector{}", + "new_type": "std::vector", + "old_default": "std::vector {}", + "old_type": "const std::vector&", + "param": "lef_paths", + "py_name": "db_init", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "Liberty file list; wrapper update_sta_data_config passes path_texts(lib_paths); empty list is the unset sentinel", + "cpp_target": "db_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 27, + "module": "py_config", + "new_default": "std::vector{}", + "new_type": "std::vector", + "old_default": "std::vector{}", + "old_type": "const std::vector&", + "param": "lib_paths", + "py_name": "db_init", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "output directory; wrapper passes path_text(output_dir)", + "cpp_target": "db_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 27, + "module": "py_config", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "output_path", + "py_name": "db_init", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "SDC constraints file; empty string is the unset sentinel", + "cpp_target": "db_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 27, + "module": "py_config", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "sdc_path", + "py_name": "db_init", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "technology LEF file; empty string is the unset sentinel in db_init's C++ body", + "cpp_target": "db_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 27, + "module": "py_config", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "tech_lef_path", + "py_name": "db_init", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "netlist Verilog file; empty string is the unset sentinel", + "cpp_target": "db_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 27, + "module": "py_config", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "verilog_path", + "py_name": "db_init", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "flow config JSON file; wrapper init_config passes it positionally (chipcompiler/tools/ecc/module.py:86)", + "cpp_target": "flow_init", + "file": "src/interface/python/py_config/py_register_config.h", + "line": 25, + "module": "py_config", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "flow_config", + "py_name": "flow_init", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "cpp_target": "", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 49, + "module": "py_eval", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "py_name": "cell_density", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "cpp_target": "", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 76, + "module": "py_eval", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "py_name": "egr_congestion", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "cpp_target": "eval_cell_hierarchy", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 170, + "module": "py_eval", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "py_name": "eval_cell_hierarchy", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "cpp_target": "eval_macro_connection", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 172, + "module": "py_eval", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "py_name": "eval_macro_connection", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "cpp_target": "eval_macro_hierarchy", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 171, + "module": "py_eval", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "py_name": "eval_macro_hierarchy", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "cpp_target": "eval_macro_io_pin_connection", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 174, + "module": "py_eval", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "py_name": "eval_macro_io_pin_connection", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", + "cpp_target": "eval_macro_pin_connection", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 173, + "module": "py_eval", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "plot_path", + "py_name": "eval_macro_pin_connection", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "cpp_target": "", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 71, + "module": "py_eval", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "py_name": "lut_rudy_congestion", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "cpp_target": "", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 59, + "module": "py_eval", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "py_name": "net_density", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "cpp_target": "", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 54, + "module": "py_eval", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "py_name": "pin_density", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", + "cpp_target": "", + "file": "src/interface/python/py_eval/py_register_eval.h", + "line": 66, + "module": "py_eval", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "save_path", + "py_name": "rudy_congestion", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "congestion map output directory (featureInst->save_cong_map target)", + "cpp_target": "feature_cong_map", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 39, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "dir", + "py_name": "feature_cong_map", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "flow step selector string forwarded to save_cong_map, not a filesystem path", + "cpp_target": "feature_cong_map", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 39, + "module": "py_feature", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "step", + "py_name": "feature_cong_map", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "CTS eval JSON output file", + "cpp_target": "feature_cts_eval", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 30, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "json_path", + "py_name": "feature_cts_eval", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "eval map output file (featureInst->save_eval_map target)", + "cpp_target": "feature_eval_map", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 32, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "feature_eval_map", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "eval summary output file", + "cpp_target": "feature_eval_summary", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 36, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "feature_eval_summary", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "DRC report input file consumed by the macro DRC feature builder", + "cpp_target": "feature_macro_drc", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 35, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "drc_path", + "py_name": "feature_macro_drc", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "macro DRC feature output file", + "cpp_target": "feature_macro_drc", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 35, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "feature_macro_drc", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "net eval output file", + "cpp_target": "feature_net_eval", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 38, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "feature_net_eval", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "placement eval JSON output file", + "cpp_target": "feature_pl_eval", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 29, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "json_path", + "py_name": "feature_pl_eval", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "route feature output file", + "cpp_target": "feature_route", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 33, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "feature_route", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "route feature input file read back by the feature API", + "cpp_target": "feature_route_read", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 34, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "feature_route_read", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "feature summary output file (featureInst->save_summary target)", + "cpp_target": "feature_summary", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 27, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "feature_summary", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "timing eval summary output file", + "cpp_target": "feature_timing_eval_summary", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 37, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "feature_timing_eval_summary", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "tool feature output file (featureInst->save_tools target)", + "cpp_target": "feature_tool", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 28, + "module": "py_feature", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "feature_tool", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "flow step selector string forwarded to save_tools, not a filesystem path", + "cpp_target": "feature_tool", + "file": "src/interface/python/py_feature/py_register_feature.h", + "line": 28, + "module": "py_feature", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "step", + "py_name": "feature_tool", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "CTS report output file", + "cpp_target": "CtsReport", + "file": "src/interface/python/py_icts/py_register_icts.h", + "line": 28, + "module": "py_icts", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "cts_report", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "CTS config JSON file; wrapper run_cts passes path_text(config) (chipcompiler/tools/ecc/module.py:401)", + "cpp_target": "CtsAutoRun", + "file": "src/interface/python/py_icts/py_register_icts.h", + "line": 27, + "module": "py_icts", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "cts_config", + "py_name": "run_cts", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "CTS working directory; wrapper run_cts passes path_text(output) (chipcompiler/tools/ecc/module.py:401)", + "cpp_target": "CtsAutoRun", + "file": "src/interface/python/py_icts/py_register_icts.h", + "line": 27, + "module": "py_icts", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "cts_work_dir", + "py_name": "run_cts", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "blockage type keyword, not a filesystem path", + "cpp_target": "clearBlockage", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 61, + "module": "py_idb", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "type", + "py_name": "clear_blockage", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "cell master name, not a filesystem path", + "cpp_target": "idbCreateInstance", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 65, + "module": "py_idb", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "cell_master", + "py_name": "create_inst", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance name to create, not a filesystem path", + "cpp_target": "idbCreateInstance", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 65, + "module": "py_idb", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "py_name": "create_inst", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "placement orientation keyword, not a filesystem path", + "cpp_target": "idbCreateInstance", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 65, + "module": "py_idb", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "orient", + "py_name": "create_inst", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "placement status keyword, not a filesystem path", + "cpp_target": "idbCreateInstance", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 65, + "module": "py_idb", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "status", + "py_name": "create_inst", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance type tag, not a filesystem path", + "cpp_target": "idbCreateInstance", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 65, + "module": "py_idb", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "type", + "py_name": "create_inst", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "connection type keyword, not a filesystem path", + "cpp_target": "idbCreateNet", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 67, + "module": "py_idb", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "conn_type", + "py_name": "create_net", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name to create, not a filesystem path", + "cpp_target": "idbCreateNet", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 67, + "module": "py_idb", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "py_name": "create_net", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "DEF file; wrapper read_def passes path_text(path) (chipcompiler/tools/ecc/module.py:188)", + "cpp_target": "initDef", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 36, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "def_path", + "py_name": "def_init", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "DEF output file; wrapper def_save passes path_text(def_path)", + "cpp_target": "saveDef", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 41, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "def_name", + "py_name": "def_save", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance name in the design database, not a filesystem path", + "cpp_target": "idbDeleteInstance", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 63, + "module": "py_idb", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "py_name": "delete_inst", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name in the design database, not a filesystem path", + "cpp_target": "idbDeleteNet", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 64, + "module": "py_idb", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "py_name": "delete_net", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "GDSII output file; wrapper gds_save passes path_text(output_path)", + "cpp_target": "saveGDSII", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 46, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "gds_name", + "py_name": "gds_save", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "report output file: idbGet forwards file_name to rptInst->reportInstance/reportNet (py_db_op.h), which write the report to that file", + "cpp_target": "idbGet", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 62, + "module": "py_idb", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "file_name", + "py_name": "idb_get", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance name filter, not a filesystem path", + "cpp_target": "idbGet", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 62, + "module": "py_idb", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "inst_name", + "py_name": "idb_get", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name filter, not a filesystem path", + "cpp_target": "idbGet", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 62, + "module": "py_idb", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "net_name", + "py_name": "idb_get", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "idb config JSON file; wrapper idb_init passes path_text(config_path) (chipcompiler/tools/ecc/module.py:114); py::arg-free binding, parameter name from the initIdb declaration", + "cpp_target": "initIdb", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 33, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "config_path", + "py_name": "idb_init", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "idb JSON output file (saveJson serializes the database)", + "cpp_target": "saveJson", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 47, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "json_save", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "cell LEF file list; wrapper init_lefs passes path_texts(lef_paths) (chipcompiler/tools/ecc/module.py:184); empty list is the unset sentinel", + "cpp_target": "initLef", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 35, + "module": "py_idb", + "new_default": null, + "new_type": "std::vector", + "old_default": null, + "old_type": "const std::vector&", + "param": "lef_paths", + "py_name": "lef_init", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "Liberty file list; wrapper passes path_texts(lib_paths) (chipcompiler/tools/ecc/module.py:907); empty list is the unset sentinel", + "cpp_target": "initLib", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 38, + "module": "py_idb", + "new_default": null, + "new_type": "std::vector", + "old_default": null, + "old_type": "const std::vector&", + "param": "lib_paths", + "py_name": "lib_init", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "serialized database input file/directory read by loadData", + "cpp_target": "loadData", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 52, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "load_data", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "cell master names to exclude from the netlist, not filesystem paths", + "cpp_target": "saveNetList", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 44, + "module": "py_idb", + "new_default": "std::set{}", + "new_type": "std::set", + "old_default": "std::set{}", + "old_type": "std::set", + "param": "exclude_cell_names", + "py_name": "netlist_save", + "required_or_optional": "optional", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "netlist output file (saveNetList writes a Verilog netlist)", + "cpp_target": "saveNetList", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 44, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "netlist_path", + "py_name": "netlist_save", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "serialized database output file/directory (saveData persists the DataManager state)", + "cpp_target": "saveData", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 50, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "save_data", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "SDC constraints file; initSdc stores the value as-is and the timing flow treats empty as unset; the production harden flow passes None natively (runner.py passes workspace.pdk.sdc which is Path|None), so the converted binding takes an optional path", + "cpp_target": "initSdc", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 39, + "module": "py_idb", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": null, + "old_type": "const std::string&", + "param": "sdc_path", + "py_name": "sdc_init", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name in the design database, not a filesystem path", + "cpp_target": "setNet", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 59, + "module": "py_idb", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "py_name": "set_net", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net type keyword (signal/power/ground), not a filesystem path", + "cpp_target": "setNet", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 59, + "module": "py_idb", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_type", + "py_name": "set_net", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "SPEF parasitics file; wrapper passes path_text(spef_path)", + "cpp_target": "initSpef", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 40, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "spef_path", + "py_name": "spef_init", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "macro placement TCL output file (saveMacroTCL writes a .tcl file)", + "cpp_target": "saveMacroTCL", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 43, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "tcl_name", + "py_name": "tcl_save", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "technology LEF file; wrapper init_techlef passes path_text(tech_lef_path) (chipcompiler/tools/ecc/module.py:180); py::arg-free binding, parameter name from the initTechLef declaration", + "cpp_target": "initTechLef", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 34, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "techlef_path", + "py_name": "tech_lef_init", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "design top module name, not a filesystem path", + "cpp_target": "initVerilog", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 37, + "module": "py_idb", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "top_module", + "py_name": "verilog_init", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "netlist Verilog file; wrapper read_verilog passes path_text(verilog)", + "cpp_target": "initVerilog", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 37, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "verilog_path", + "py_name": "verilog_init", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "view JSON edits input file read by applyViewJsonEdits", + "cpp_target": "applyViewJsonEdits", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 49, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "edits_path", + "py_name": "view_json_apply_edits", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "serialization format keyword (e.g. \"pretty\"), not a filesystem path", + "cpp_target": "saveViewJson", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 48, + "module": "py_idb", + "new_default": "\"pretty\"", + "new_type": "const std::string&", + "old_default": "\"pretty\"", + "old_type": "const std::string&", + "param": "json_format", + "py_name": "view_json_save", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "view JSON output directory", + "cpp_target": "saveViewJson", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 48, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "output_dir", + "py_name": "view_json_save", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "abstract LEF output file; wrapper write_abstract_lef passes path_text (chipcompiler/tools/ecc/module.py:1001)", + "cpp_target": "writeAbstractLef", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 54, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "output_lef_path", + "py_name": "write_abstract_lef", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "harden core instance names embedded in the JSON, not filesystem paths", + "cpp_target": "writeSocJson", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 53, + "module": "py_idb", + "new_default": "std::vector{}", + "new_type": "const std::vector&", + "old_default": "std::vector{}", + "old_type": "const std::vector&", + "param": "harden_cores", + "py_name": "write_soc_json", + "required_or_optional": "optional", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "SoC JSON output file", + "cpp_target": "writeSocJson", + "file": "src/interface/python/py_idb/py_register_idb.h", + "line": 53, + "module": "py_idb", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "write_soc_json", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "DRC working directory; wrapper init_drc passes path_text(output_dir) (chipcompiler/tools/ecc/module.py:419)", + "cpp_target": "init_drc", + "file": "src/interface/python/py_idrc/py_register_idrc.h", + "line": 28, + "module": "py_idrc", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "temp_directory_path", + "py_name": "init_drc", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "DRC config JSON file; wrapper run_drc passes path_text(config) (chipcompiler/tools/ecc/module.py:425)", + "cpp_target": "run_drc", + "file": "src/interface/python/py_idrc/py_register_idrc.h", + "line": 29, + "module": "py_idrc", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "config", + "py_name": "run_drc", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "DRC report output file; wrapper run_drc passes path_text(report_path) (chipcompiler/tools/ecc/module.py:425)", + "cpp_target": "run_drc", + "file": "src/interface/python/py_idrc/py_register_idrc.h", + "line": 29, + "module": "py_idrc", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "report", + "py_name": "run_drc", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "DRC feature output file; wrapper save_drc passes path_text(feature_path) (chipcompiler/tools/ecc/module.py:431)", + "cpp_target": "save_drc", + "file": "src/interface/python/py_idrc/py_register_idrc.h", + "line": 30, + "module": "py_idrc", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "py_name": "save_drc", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "blockage rectangle as a coordinate string, not a filesystem path", + "cpp_target": "fpAddPlacementBlockage", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 33, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "box", + "py_name": "add_placement_blockage", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "halo distance encoded as a string, not a filesystem path", + "cpp_target": "fpAddPlacementHalo", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 34, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "distance", + "py_name": "add_placement_halo", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance name, not a filesystem path", + "cpp_target": "fpAddPlacementHalo", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 34, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "py_name": "add_placement_halo", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "blockage rectangle as a coordinate string, not a filesystem path", + "cpp_target": "fpAddRoutingBlockage", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 35, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "box", + "py_name": "add_routing_blockage", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "routing layer name, not a filesystem path", + "cpp_target": "fpAddRoutingBlockage", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 35, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "py_name": "add_routing_blockage", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "halo distance encoded as a string, not a filesystem path", + "cpp_target": "fpAddRoutingHalo", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 36, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "distance", + "py_name": "add_routing_halo", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance name, not a filesystem path", + "cpp_target": "fpAddRoutingHalo", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 36, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "py_name": "add_routing_halo", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "routing layer name, not a filesystem path", + "cpp_target": "fpAddRoutingHalo", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 36, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "py_name": "add_routing_halo", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "pin layer name, not a filesystem path", + "cpp_target": "fpPlacePins", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 29, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "py_name": "auto_place_pins", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "die side names (e.g. \"left\"/\"right\"), not filesystem paths", + "cpp_target": "fpPlacePins", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 29, + "module": "py_ifp", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "sides", + "py_name": "auto_place_pins", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "routing layer name, not a filesystem path", + "cpp_target": "fpMakeTracks", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 28, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "py_name": "gern_track", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "core rectangle as a coordinate string, not a filesystem path", + "cpp_target": "fpInit", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 26, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "core_area", + "py_name": "init_floorplan", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "core site name from the technology LEF, not a filesystem path", + "cpp_target": "fpInit", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 26, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "core_site", + "py_name": "init_floorplan", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "corner site name from the technology LEF, not a filesystem path", + "cpp_target": "fpInit", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 26, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "corner_site", + "py_name": "init_floorplan", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "die rectangle as a coordinate string (\"llx lly urx ury\"), not a filesystem path", + "cpp_target": "fpInit", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 26, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "die_area", + "py_name": "init_floorplan", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "IO site name from the technology LEF, not a filesystem path", + "cpp_target": "fpInit", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 26, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "io_site", + "py_name": "init_floorplan", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "IO filler cell master names, not filesystem paths", + "cpp_target": "fpPlaceIOFiller", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 32, + "module": "py_ifp", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "filler_types", + "py_name": "place_io_filler", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance name prefix for created filler cells, not a filesystem path", + "cpp_target": "fpPlaceIOFiller", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 32, + "module": "py_ifp", + "new_default": "\"IOFill\"", + "new_type": "const std::string&", + "old_default": "\"IOFill\"", + "old_type": "const std::string&", + "param": "prefix", + "py_name": "place_io_filler", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "pin layer name, not a filesystem path", + "cpp_target": "fpPlacePort", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 30, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "py_name": "place_port", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "pin name, not a filesystem path", + "cpp_target": "fpPlacePort", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 30, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "pin_name", + "py_name": "place_port", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "endcap master name, not a filesystem path", + "cpp_target": "fpTapCell", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 37, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "endcap", + "py_name": "tapcell", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "tapcell master name, not a filesystem path", + "cpp_target": "fpTapCell", + "file": "src/interface/python/py_ifp/py_register_ifp.h", + "line": 37, + "module": "py_ifp", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "tapcell", + "py_name": "tapcell", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "cell master name, not a filesystem path", + "cpp_target": "fpPlaceInst", + "file": "src/interface/python/py_instance/py_register_inst.h", + "line": 26, + "module": "py_instance", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "cellmaster", + "py_name": "place_instance", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance name, not a filesystem path", + "cpp_target": "fpPlaceInst", + "file": "src/interface/python/py_instance/py_register_inst.h", + "line": 26, + "module": "py_instance", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "inst_name", + "py_name": "place_instance", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "placement orientation keyword, not a filesystem path", + "cpp_target": "fpPlaceInst", + "file": "src/interface/python/py_instance/py_register_inst.h", + "line": 26, + "module": "py_instance", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "orient", + "py_name": "place_instance", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance source tag: DataManager::placeInst forwards source to instance->set_type (idm_design_inst.cpp), a provenance label with no filesystem semantics", + "cpp_target": "fpPlaceInst", + "file": "src/interface/python/py_instance/py_register_inst.h", + "line": 26, + "module": "py_instance", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "source", + "py_name": "place_instance", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "pin direction keyword, not a filesystem path", + "cpp_target": "pdnAddIO", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 26, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "direction", + "py_name": "add_pdn_io", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "cpp_target": "pdnAddIO", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 26, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "py_name": "add_pdn_io", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "IO pin name, not a filesystem path", + "cpp_target": "pdnAddIO", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 26, + "module": "py_ipdn", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "pin_name", + "py_name": "add_pdn_io", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "cpp_target": "pdnAddSegmentStripe", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 39, + "module": "py_ipdn", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "layer", + "py_name": "add_segment_stripe", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "end layer name, not a filesystem path", + "cpp_target": "pdnAddSegmentStripe", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 39, + "module": "py_ipdn", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "layer_end", + "py_name": "add_segment_stripe", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "start layer name, not a filesystem path", + "cpp_target": "pdnAddSegmentStripe", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 39, + "module": "py_ipdn", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "layer_start", + "py_name": "add_segment_stripe", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "cpp_target": "pdnAddSegmentStripe", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 39, + "module": "py_ipdn", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "net_name", + "py_name": "add_segment_stripe", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "bottom layer name, not a filesystem path", + "cpp_target": "pdnAddSegmentVia", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 42, + "module": "py_ipdn", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "bottom_layer", + "py_name": "add_segment_via", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "cpp_target": "pdnAddSegmentVia", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 42, + "module": "py_ipdn", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "layer", + "py_name": "add_segment_via", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "cpp_target": "pdnAddSegmentVia", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 42, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "py_name": "add_segment_via", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "top layer name, not a filesystem path", + "cpp_target": "pdnAddSegmentVia", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 42, + "module": "py_ipdn", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "top_layer", + "py_name": "add_segment_via", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "cpp_target": "pdnConnectIOPin", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 37, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "py_name": "connectIoPinToPower", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "ground pin names, not filesystem paths", + "cpp_target": "pdnConnectMacro", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 35, + "module": "py_ipdn", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "ground_pins", + "py_name": "connectMacroPdn", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "orientation keyword, not a filesystem path", + "cpp_target": "pdnConnectMacro", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 35, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "orient", + "py_name": "connectMacroPdn", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "PDN layer name, not a filesystem path", + "cpp_target": "pdnConnectMacro", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 35, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "pdn_layer", + "py_name": "connectMacroPdn", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "macro pin layer name, not a filesystem path", + "cpp_target": "pdnConnectMacro", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 35, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "pin_layer", + "py_name": "connectMacroPdn", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "power pin names, not filesystem paths", + "cpp_target": "pdnConnectMacro", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 35, + "module": "py_ipdn", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "power_pins", + "py_name": "connectMacroPdn", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "cpp_target": "pdnConnectStripe", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 38, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "py_name": "connectPowerStripe", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "cpp_target": "pdnConnectStripe", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 38, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "py_name": "connectPowerStripe", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "layer name pair to connect, not filesystem paths", + "cpp_target": "pdnConnectLayer", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 34, + "module": "py_ipdn", + "new_default": null, + "new_type": "std::vector&", + "old_default": null, + "old_type": "std::vector&", + "param": "layers", + "py_name": "connect_two_layer", + "required_or_optional": "required", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "cpp_target": "pdnCreateGrid", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 30, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer_name", + "py_name": "create_grid", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "ground net name, not a filesystem path", + "cpp_target": "pdnCreateGrid", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 30, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name_ground", + "py_name": "create_grid", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "power net name, not a filesystem path", + "cpp_target": "pdnCreateGrid", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 30, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name_power", + "py_name": "create_grid", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "cpp_target": "pdnCreateStripe", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 32, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer_name", + "py_name": "create_stripe", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "ground net name, not a filesystem path", + "cpp_target": "pdnCreateStripe", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 32, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name_ground", + "py_name": "create_stripe", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "power net name, not a filesystem path", + "cpp_target": "pdnCreateStripe", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 32, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name_power", + "py_name": "create_stripe", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance pin name pattern, not a filesystem path", + "cpp_target": "pdnGlobalConnect", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 27, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "instance_pin_name", + "py_name": "global_net_connect", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name, not a filesystem path", + "cpp_target": "pdnGlobalConnect", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 27, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "net_name", + "py_name": "global_net_connect", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "IO cell master name, not a filesystem path", + "cpp_target": "pdnPlacePort", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 28, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "io_cell_name", + "py_name": "place_pdn_port", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "layer name, not a filesystem path", + "cpp_target": "pdnPlacePort", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 28, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "layer", + "py_name": "place_pdn_port", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "pin name, not a filesystem path", + "cpp_target": "pdnPlacePort", + "file": "src/interface/python/py_ipdn/py_register_ipdn.h", + "line": 28, + "module": "py_ipdn", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "pin_name", + "py_name": "place_pdn_port", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "RCX config JSON file; wrapper init_rcx passes path_text(config) (chipcompiler/tools/ecc/module.py:884)", + "cpp_target": "init_rcx", + "file": "src/interface/python/py_ircx/py_register_ircx.h", + "line": 28, + "module": "py_ircx", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "config", + "py_name": "init_rcx", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "PDK identifier string (e.g. \"ics55\") selecting a built-in rule set, already std::optional; not a filesystem path", + "cpp_target": "init_rcx", + "file": "src/interface/python/py_ircx/py_register_ircx.h", + "line": 28, + "module": "py_ircx", + "new_default": "py::none()", + "new_type": "const std::optional&", + "old_default": "py::none()", + "old_type": "const std::optional&", + "param": "pdk", + "py_name": "init_rcx", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "router config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:852)", + "cpp_target": "initRT", + "file": "src/interface/python/py_irt/py_register_irt.h", + "line": 28, + "module": "py_irt", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "std::string&", + "param": "config", + "py_name": "init_rt", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "early-router config JSON file; wrapper run_ert passes path_text(config) (chipcompiler/tools/ecc/module.py:849)", + "cpp_target": "runERT", + "file": "src/interface/python/py_irt/py_register_irt.h", + "line": 29, + "module": "py_irt", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "std::string&", + "param": "config", + "py_name": "run_ert", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "STA config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:913)", + "cpp_target": "initSTA", + "file": "src/interface/python/py_ista/py_register_ista.h", + "line": 29, + "module": "py_ista", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "std::string&", + "param": "config", + "py_name": "init_sta", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "fanout-fix config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:1293)", + "cpp_target": "fix_fanout", + "file": "src/interface/python/py_izh/py_register_izh.h", + "line": 28, + "module": "py_izh", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "config", + "py_name": "fix_fanout", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "filler config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:802)", + "cpp_target": "insert_filler", + "file": "src/interface/python/py_izh/py_register_izh.h", + "line": 29, + "module": "py_izh", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "config", + "py_name": "insert_filler", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "congestion report output file; empty string reports to stdout only", + "cpp_target": "reportCong", + "file": "src/interface/python/py_report/py_register_report.h", + "line": 29, + "module": "py_report", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "py_name": "report_congestion", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "dangling-net report output file; empty string reports to stdout only", + "cpp_target": "reportDanglingNet", + "file": "src/interface/python/py_report/py_register_report.h", + "line": 30, + "module": "py_report", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "py_name": "report_dangling_net", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "database summary report output file; empty string reports to stdout only", + "cpp_target": "reportDbSummary", + "file": "src/interface/python/py_report/py_register_report.h", + "line": 28, + "module": "py_report", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "py_name": "report_db", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "DRC report output path by API shape; the currently bound one-argument ReportManager::reportDRC overload is a stub with its write lines commented out (report_manager.cpp:186), pending re-enable", + "cpp_target": "reportDRC", + "file": "src/interface/python/py_report/py_register_report.h", + "line": 34, + "module": "py_report", + "new_default": null, + "new_type": "std::filesystem::path", + "old_default": null, + "old_type": "const std::string&", + "param": "path", + "py_name": "report_drc", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance name prefixes to bucket, not filesystem paths", + "cpp_target": "reportPlaceDistribution", + "file": "src/interface/python/py_report/py_register_report.h", + "line": 32, + "module": "py_report", + "new_default": "std::vector{}", + "new_type": "const std::vector&", + "old_default": "std::vector{}", + "old_type": "const std::vector&", + "param": "prefixes", + "py_name": "report_place_distribution", + "required_or_optional": "optional", + "scalar_or_list": "list" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "instance name prefix to report on, not a filesystem path", + "cpp_target": "reportPrefixedInst", + "file": "src/interface/python/py_report/py_register_report.h", + "line": 33, + "module": "py_report", + "new_default": null, + "new_type": "const std::string&", + "old_default": null, + "old_type": "const std::string&", + "param": "prefix", + "py_name": "report_prefixed_instance", + "required_or_optional": "required", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "non_path", + "classification_rationale": "net name filter for the route report, not a filesystem path", + "cpp_target": "reportRoute", + "file": "src/interface/python/py_report/py_register_report.h", + "line": 31, + "module": "py_report", + "new_default": "\"\"", + "new_type": "const std::string&", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "net", + "py_name": "report_route", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "route report output file; empty string reports to stdout only", + "cpp_target": "reportRoute", + "file": "src/interface/python/py_report/py_register_report.h", + "line": 31, + "module": "py_report", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "py_name": "report_route", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + }, + { + "binding_status": "active", + "classification": "path", + "classification_rationale": "wirelength report output file; empty string reports to stdout only", + "cpp_target": "reportWireLength", + "file": "src/interface/python/py_report/py_register_report.h", + "line": 27, + "module": "py_report", + "new_default": "py::none()", + "new_type": "std::optional", + "old_default": "\"\"", + "old_type": "const std::string&", + "param": "path", + "py_name": "report_wirelength", + "required_or_optional": "optional", + "scalar_or_list": "scalar" + } + ], + "version": 1 +} diff --git a/scripts/binding_census/manifest.py b/scripts/binding_census/manifest.py new file mode 100755 index 0000000000..1088b4e840 --- /dev/null +++ b/scripts/binding_census/manifest.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python +"""Spec/manifest join, schema validation, and the --check gate for the +ecc_py binding census. + +The curated spec (``binding_spec.json``) carries, per in-scope (binding, +parameter), the old/new C++ types and defaults, scalar/list shape, +required/optional shape, and the path / non_path / ambiguous classification +with a written rationale. The manifest (``manifest.json``) is the +deterministic generated join of lexer discovery + spec. +""" +import json +from pathlib import Path + +from lexer import DiscoveredBinding, discover + +MANIFEST_VERSION = 1 + +PATH_TYPE_REQUIRED = "std::filesystem::path" +PATH_TYPE_OPTIONAL = "std::optional" +PATH_TYPE_LIST = "std::vector" + +# Machine-readable form of the reviewed classification baseline: the bindings +# and parameters that must exist in the curated spec with a `path` +# classification. Used by --check for coverage; the spec/manifest remain the +# final authority (any deviation is documented in baseline_diff.md). +BASELINE_PATH_PARAMS: dict[tuple[str, str], list[str]] = { + ("py_config", "flow_init"): ["flow_config"], + ("py_config", "db_init"): [ + "config_path", "tech_lef_path", "lef_paths", "def_path", "verilog_path", + "output_path", "feature_path", "lib_paths", "sdc_path", + ], + ("py_eval", "cell_density"): ["save_path"], + ("py_eval", "pin_density"): ["save_path"], + ("py_eval", "net_density"): ["save_path"], + ("py_eval", "rudy_congestion"): ["save_path"], + ("py_eval", "lut_rudy_congestion"): ["save_path"], + ("py_eval", "egr_congestion"): ["save_path"], + ("py_eval", "eval_cell_hierarchy"): ["plot_path"], + ("py_eval", "eval_macro_hierarchy"): ["plot_path"], + ("py_eval", "eval_macro_connection"): ["plot_path"], + ("py_eval", "eval_macro_pin_connection"): ["plot_path"], + ("py_eval", "eval_macro_io_pin_connection"): ["plot_path"], + ("py_feature", "feature_summary"): ["path"], + ("py_feature", "feature_tool"): ["path"], + ("py_feature", "feature_pl_eval"): ["json_path"], + ("py_feature", "feature_cts_eval"): ["json_path"], + ("py_feature", "feature_eval_map"): ["path"], + ("py_feature", "feature_route"): ["path"], + ("py_feature", "feature_route_read"): ["path"], + ("py_feature", "feature_macro_drc"): ["path", "drc_path"], + ("py_feature", "feature_eval_summary"): ["path"], + ("py_feature", "feature_timing_eval_summary"): ["path"], + ("py_feature", "feature_net_eval"): ["path"], + ("py_feature", "feature_cong_map"): ["dir"], + ("py_icts", "run_cts"): ["cts_config", "cts_work_dir"], + ("py_icts", "cts_report"): ["path"], + ("py_idb", "idb_init"): ["config_path"], + ("py_idb", "tech_lef_init"): ["techlef_path"], + ("py_idb", "def_init"): ["def_path"], + ("py_idb", "verilog_init"): ["verilog_path"], + ("py_idb", "sdc_init"): ["sdc_path"], + ("py_idb", "spef_init"): ["spef_path"], + ("py_idb", "lef_init"): ["lef_paths"], + ("py_idb", "lib_init"): ["lib_paths"], + ("py_idb", "def_save"): ["def_name"], + ("py_idb", "tcl_save"): ["tcl_name"], + ("py_idb", "gds_save"): ["gds_name"], + ("py_idb", "netlist_save"): ["netlist_path"], + ("py_idb", "json_save"): ["path"], + ("py_idb", "save_data"): ["path"], + ("py_idb", "load_data"): ["path"], + ("py_idb", "write_soc_json"): ["path"], + ("py_idb", "write_abstract_lef"): ["output_lef_path"], + ("py_idb", "view_json_save"): ["output_dir"], + ("py_idb", "view_json_apply_edits"): ["edits_path"], + ("py_idb", "idb_get"): ["file_name"], + ("py_idrc", "init_drc"): ["temp_directory_path"], + ("py_idrc", "run_drc"): ["config", "report"], + ("py_idrc", "save_drc"): ["path"], + ("py_irt", "init_rt"): ["config"], + ("py_irt", "run_ert"): ["config"], + ("py_ista", "init_sta"): ["config"], + ("py_ircx", "init_rcx"): ["config"], + ("py_izh", "fix_fanout"): ["config"], + ("py_izh", "insert_filler"): ["config"], + ("py_report", "report_wirelength"): ["path"], + ("py_report", "report_db"): ["path"], + ("py_report", "report_congestion"): ["path"], + ("py_report", "report_dangling_net"): ["path"], + ("py_report", "report_route"): ["path"], + ("py_report", "report_drc"): ["path"], +} + + +def load_json(path: Path) -> dict: + return json.loads(path.read_text()) + + +def load_schema(path: Path) -> dict: + return load_json(path) + + +def validate_spec(spec: dict, schema: dict) -> None: + import jsonschema + + jsonschema.validate(spec, schema) + + +def validate_manifest(manifest: dict, schema: dict) -> None: + import jsonschema + + jsonschema.validate(manifest, schema) + + +def build_manifest(discovery: dict, spec: dict) -> dict: + """Join the curated spec against discovery into manifest entries.""" + index: dict[tuple[str, str], DiscoveredBinding] = {} + for binding in discovery["bindings"]: + index.setdefault((binding.module, binding.py_name), binding) + entries: list[dict] = [] + for spec_binding in spec["bindings"]: + key = (spec_binding["module"], spec_binding["py_name"]) + discovered = index.get(key) + if discovered is None: + raise ValueError(f"spec binding not discovered: {key[0]}.{key[1]}") + for param in spec_binding["params"]: + entries.append( + { + "module": discovered.module, + "file": discovered.file, + "line": discovered.line, + "py_name": discovered.py_name, + "cpp_target": discovered.cpp_target, + "param": param["param"], + "old_type": param["old_type"], + "old_default": param["old_default"], + "new_type": param["new_type"], + "new_default": param["new_default"], + "scalar_or_list": param["scalar_or_list"], + "required_or_optional": param["required_or_optional"], + "binding_status": discovered.status_in_source, + "classification": param["classification"], + "classification_rationale": param["classification_rationale"], + } + ) + entries.sort(key=lambda e: (e["module"], e["py_name"], e["param"])) + return {"version": MANIFEST_VERSION, "entries": entries} + + +def _dumps(obj: dict) -> bytes: + return (json.dumps(obj, indent=2, sort_keys=True) + "\n").encode() + + +def generate_manifest_bytes(repo_root: Path, census_dir: Path) -> bytes: + discovery = discover(repo_root) + spec = load_json(census_dir / "binding_spec.json") + validate_spec(spec, load_schema(census_dir / "binding_spec.schema.json")) + manifest = build_manifest(discovery, spec) + validate_manifest(manifest, load_schema(census_dir / "manifest.schema.json")) + return _dumps(manifest) + + +def _is_string_literal_default(default: str | None) -> bool: + return default is not None and default.lstrip().startswith('"') + + +def check(repo_root: Path, census_dir: Path) -> list[str]: + """Run every census gate; return a list of failure messages (empty = pass).""" + failures: list[str] = [] + discovery = discover(repo_root) + manifest_path = census_dir / "manifest.json" + + loaded: dict[str, dict] = {} + for name in ("binding_spec.json", "manifest.json", "binding_spec.schema.json", "manifest.schema.json"): + try: + loaded[name] = load_json(census_dir / name) + except json.JSONDecodeError as exc: + failures.append(f"{name} is not valid JSON: {exc}") + except OSError as exc: + failures.append(f"{name} is missing or unreadable: {exc}") + if failures: + return failures + spec = loaded["binding_spec.json"] + spec_schema = loaded["binding_spec.schema.json"] + manifest_schema = loaded["manifest.schema.json"] + + # (b) schema validation for spec and committed manifest + import jsonschema + + try: + validate_spec(spec, spec_schema) + except jsonschema.ValidationError as exc: + failures.append(f"binding_spec.json fails schema validation: {exc.message}") + manifest = loaded["manifest.json"] + try: + validate_manifest(manifest, manifest_schema) + except jsonschema.ValidationError as exc: + failures.append(f"manifest.json fails schema validation: {exc.message}") + + # (a) regeneration must be byte-stable against the committed manifest + try: + rebuilt = build_manifest(discovery, spec) + except (KeyError, ValueError) as exc: + failures.append(f"manifest regeneration failed: {exc}") + return failures + regenerated = _dumps(rebuilt) + if regenerated != manifest_path.read_bytes(): + failures.append( + "manifest.json is out of date: regeneration is not byte-stable against the committed file" + ) + + bindings = discovery["bindings"] + discovered_index: dict[tuple[str, str], DiscoveredBinding] = {} + for binding in bindings: + discovered_index.setdefault((binding.module, binding.py_name), binding) + spec_index: dict[tuple[str, str], dict[str, dict]] = {} + for spec_binding in spec["bindings"]: + spec_index[(spec_binding["module"], spec_binding["py_name"])] = { + p["param"]: p for p in spec_binding["params"] + } + + # (c) coverage, forward: active bindings with string-literal py::arg defaults + for binding in bindings: + if binding.status_in_source != "active": + continue + spec_params = spec_index.get((binding.module, binding.py_name), {}) + for param in binding.params: + if _is_string_literal_default(param.default) and param.name not in spec_params: + failures.append( + f"coverage: active binding {binding.py_name} ({binding.file}:{binding.line}) has " + f'py::arg("{param.name}") with a string-literal default but no spec entry' + ) + + # (c) coverage, baseline-named path parameters must be spec'd as path + for (module, py_name), params in BASELINE_PATH_PARAMS.items(): + binding = discovered_index.get((module, py_name)) + if binding is None: + failures.append(f"baseline binding not discovered: {module}.{py_name}") + continue + if binding.status_in_source != "active": + failures.append(f"baseline binding {module}.{py_name} is not active") + continue + spec_params = spec_index.get((module, py_name), {}) + for param in params: + row = spec_params.get(param) + if row is None: + failures.append(f"coverage: baseline path parameter {module}.{py_name}.{param} has no spec entry") + elif row["classification"] != "path": + failures.append( + f"coverage: baseline path parameter {module}.{py_name}.{param} is classified " + f"{row['classification']} in the spec" + ) + + # (c) coverage, reverse: every spec entry names a discovered binding/param + for spec_binding in spec["bindings"]: + key = (spec_binding["module"], spec_binding["py_name"]) + binding = discovered_index.get(key) + if binding is None: + failures.append(f"spec entry names an undiscovered binding: {key[0]}.{key[1]}") + continue + if binding.params: + discovered_params = {p.name for p in binding.params} + for param in spec_binding["params"]: + if param["param"] not in discovered_params: + failures.append( + f"spec entry {key[0]}.{key[1]}.{param['param']} does not match any discovered " + f"py::arg of {key[1]}" + ) + # py::arg-free bindings: params are curated-only (documented limitation) + + # (d) ambiguous classifications must carry a rationale + for spec_binding in spec["bindings"]: + for param in spec_binding["params"]: + if param["classification"] == "ambiguous" and not param["classification_rationale"].strip(): + failures.append( + f"ambiguous classification without rationale: " + f"{spec_binding['module']}.{spec_binding['py_name']}.{param['param']}" + ) + + # (e) path-classified params of active bindings must carry the converted types + for entry in rebuilt["entries"]: + if entry["classification"] != "path" or entry["binding_status"] != "active": + continue + expected = ( + PATH_TYPE_LIST + if entry["scalar_or_list"] == "list" + else PATH_TYPE_OPTIONAL + if entry["required_or_optional"] == "optional" + else PATH_TYPE_REQUIRED + ) + if entry["new_type"] != expected: + failures.append( + f"new_type: {entry['module']}.{entry['py_name']}.{entry['param']} is {entry['scalar_or_list']}/" + f"{entry['required_or_optional']} path but has new_type {entry['new_type']!r} (expected {expected!r})" + ) + return failures diff --git a/scripts/binding_census/manifest.schema.json b/scripts/binding_census/manifest.schema.json new file mode 100644 index 0000000000..1d8ba8b4d1 --- /dev/null +++ b/scripts/binding_census/manifest.schema.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "ecc_py binding census manifest (generated)", + "type": "object", + "additionalProperties": false, + "required": ["version", "entries"], + "properties": { + "version": {"const": 1}, + "entries": { + "type": "array", + "items": {"$ref": "#/$defs/entry"} + } + }, + "$defs": { + "entry": { + "type": "object", + "additionalProperties": false, + "required": [ + "module", + "file", + "line", + "py_name", + "cpp_target", + "param", + "old_type", + "old_default", + "new_type", + "new_default", + "scalar_or_list", + "required_or_optional", + "binding_status", + "classification", + "classification_rationale" + ], + "properties": { + "module": {"type": "string", "pattern": "^py_[a-z]+$"}, + "file": {"type": "string", "minLength": 1}, + "line": {"type": "integer", "minimum": 1}, + "py_name": {"type": "string", "minLength": 1}, + "cpp_target": {"type": "string", "minLength": 1}, + "param": {"type": "string", "minLength": 1}, + "old_type": {"type": "string", "minLength": 1}, + "old_default": {"type": ["string", "null"]}, + "new_type": {"type": "string", "minLength": 1}, + "new_default": {"type": ["string", "null"]}, + "scalar_or_list": {"enum": ["scalar", "list"]}, + "required_or_optional": {"enum": ["required", "optional"]}, + "binding_status": {"enum": ["active", "disabled"]}, + "classification": {"enum": ["path", "non_path", "ambiguous"]}, + "classification_rationale": {"type": "string"} + }, + "allOf": [ + { + "if": { + "properties": {"classification": {"const": "ambiguous"}}, + "required": ["classification"] + }, + "then": { + "properties": {"classification_rationale": {"minLength": 1}} + } + } + ] + } + } +} diff --git a/scripts/binding_census/test_binding_census.py b/scripts/binding_census/test_binding_census.py new file mode 100755 index 0000000000..5989ee9db2 --- /dev/null +++ b/scripts/binding_census/test_binding_census.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python +"""Tests for the ecc_py binding census lexer, manifest join, and --check gate.""" + +import json +import shutil +from pathlib import Path + +import pytest +from jsonschema import ValidationError + +import dead_bindings +import lexer +import manifest as census_manifest + +CENSUS_DIR = Path(__file__).resolve().parent +REPO_ROOT = CENSUS_DIR.parents[1] + +CALLED = {"register_eval", "register_imp", "register_ipdn", "register_irt", "register_good"} + + +def discover(text, module="py_test", called=CALLED): + return lexer.discover_bindings_in_text(text, module=module, file=f"{module}/py_register_test.h", called_registers=called) + + +# --------------------------------------------------------------------------- +# Lexer fixtures +# --------------------------------------------------------------------------- + +MULTILINE = """\ +void register_imp(pybind11::module& m) +{ + m.def( + "pydb", + [](idm::DataManager* db, int num_routing_grids_x, int num_routing_grids_y, bool with_routability, bool with_sta) { + return PyPlaceDB(db, num_routing_grids_x, num_routing_grids_y, with_routability, with_sta); + }, + "Convert PlaceDB to PyPlaceDB"); +} +""" + +COMMENTED = """\ +void register_imp(pybind11::module& m) +{ + m.def("active_one", active_one); + // m.def("runMP", runMP, py::arg("config"), py::arg("output_tcl") = ""); + /* + * m.def("runRef", runRef, py::arg("output_tcl") = ""); + */ +} +""" + +COMMENT_MARKERS_IN_STRINGS = """\ +void register_eval(py::module& m) +{ + m.def("fetch//doc", fetch, py::arg("url") = "http://example.com/*x*/y", py::arg("note") = "a // b"); +} +""" + +LAMBDA_WITH_BODY = """\ +void register_eval(py::module& m) +{ + m.def("cell_density", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::string& save_path = "") -> py::tuple { + auto [max_density, avg_density] = cell_density(bin_cnt_x, bin_cnt_y, save_path); + return py::make_tuple(max_density, avg_density); + }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = ""); +} +""" + +MAP_DEFAULT = """\ +void register_irt(py::module& m) +{ + m.def("init_rt", initRT, py::arg("config") = "", py::arg("config_dict") = std::map{}); +} +""" + +UPPERCASE_NAMES = """\ +void register_ipdn(py::module& m) +{ + m.def("connectMacroPdn", pdnConnectMacro, py::arg("pin_layer"), py::arg("orient")); + m.def("get_dmInst", &getDMInst, "A function which returns a DataManager instance", pybind11::return_value_policy::reference); +} +""" + +CLASS_CHAIN = """\ +void register_eval(py::module& m) +{ + py::class_(m, "TotalWLSummary") + .def_readwrite("HPWL", &ieval::TotalWLSummary::HPWL) + .def_readwrite("FLUTE", &ieval::TotalWLSummary::FLUTE) + .def("summary", &ieval::TotalWLSummary::summary); + m.def("total_wirelength_dict", []() -> py::dict { + py::dict result; + result["1"] = 0; + return result; + }); +} +""" + + +def test_multiline_m_def(): + (binding,) = discover(MULTILINE, module="py_imp") + assert binding.py_name == "pydb" + assert binding.cpp_target == "" + assert binding.line == 3 + assert binding.status_in_source == "active" + assert binding.params == [] + + +def test_line_and_block_commented_m_def(): + bindings = {b.py_name: b for b in discover(COMMENTED, module="py_imp")} + assert set(bindings) == {"active_one", "runMP", "runRef"} + assert bindings["active_one"].status_in_source == "active" + assert bindings["runMP"].status_in_source == "disabled" + assert bindings["runRef"].status_in_source == "disabled" + assert [(p.name, p.default) for p in bindings["runMP"].params] == [("config", None), ("output_tcl", '""')] + + +def test_comment_markers_inside_string_literals(): + (binding,) = discover(COMMENT_MARKERS_IN_STRINGS) + assert binding.py_name == "fetch//doc" + assert binding.status_in_source == "active" + assert [(p.name, p.default) for p in binding.params] == [ + ("url", '"http://example.com/*x*/y"'), + ("note", '"a // b"'), + ] + + +def test_lambda_target_with_commas_and_braces_in_body(): + (binding,) = discover(LAMBDA_WITH_BODY) + assert binding.cpp_target == "" + assert [(p.name, p.default) for p in binding.params] == [ + ("bin_cnt_x", "256"), + ("bin_cnt_y", "256"), + ("save_path", '""'), + ] + + +def test_py_arg_extraction_at_depth_one_with_template_commas(): + (binding,) = discover(MAP_DEFAULT) + assert [(p.name, p.default) for p in binding.params] == [ + ("config", '""'), + ("config_dict", "std::map{}"), + ] + + +def test_uppercase_binding_names_and_reference_target(): + bindings = {b.py_name: b for b in discover(UPPERCASE_NAMES)} + assert bindings["connectMacroPdn"].cpp_target == "pdnConnectMacro" + assert bindings["get_dmInst"].cpp_target == "getDMInst" + + +def test_class_chain_def_and_def_readwrite_not_collected(): + (binding,) = discover(CLASS_CHAIN) + assert binding.py_name == "total_wirelength_dict" + assert binding.cpp_target == "" + + +def _write_fake_repo(root: Path) -> None: + py_dir = root / "src" / "interface" / "python" + (py_dir / "py_good").mkdir(parents=True) + (py_dir / "py_gone").mkdir(parents=True) + (py_dir / "python_moodule.cc").write_text( + "PYBIND11_MODULE(ecc_py, m)\n" + "{\n" + " register_good(m);\n" + " // register_gone(m); // disabled: module removed\n" + "}\n" + ) + (py_dir / "py_good" / "py_register_good.h").write_text( + 'void register_good(py::module& m)\n{\n m.def("good_one", good_one);\n}\n' + ) + (py_dir / "py_gone" / "py_register_gone.h").write_text( + 'void register_gone(py::module& m)\n{\n m.def("gone_one", gone_one, py::arg("path") = "");\n}\n' + ) + + +def test_module_level_disable_via_uncalled_register_function(tmp_path): + _write_fake_repo(tmp_path) + discovery = lexer.discover(tmp_path) + bindings = {b.py_name: b for b in discovery["bindings"]} + assert bindings["good_one"].status_in_source == "active" + assert bindings["gone_one"].status_in_source == "disabled" + assert bindings["gone_one"].register_function == "register_gone" + assert bindings["good_one"].register_function == "register_good" + + +# --------------------------------------------------------------------------- +# Schema + --check gate +# --------------------------------------------------------------------------- + + +def _spec_param(**overrides): + param = { + "param": "save_path", + "old_type": "const std::string&", + "old_default": '""', + "new_type": "std::optional", + "new_default": "py::none()", + "scalar_or_list": "scalar", + "required_or_optional": "optional", + "classification": "path", + "classification_rationale": "output file path with empty-string unset sentinel", + } + param.update(overrides) + return param + + +def test_ambiguous_without_rationale_fails_schema_validation(): + spec = { + "version": 1, + "bindings": [ + { + "module": "py_eval", + "py_name": "cell_density", + "params": [_spec_param(classification="ambiguous", classification_rationale="")], + } + ], + } + with pytest.raises(ValidationError): + census_manifest.validate_spec(spec, census_manifest.load_schema(CENSUS_DIR / "binding_spec.schema.json")) + + +def _write_fake_census(census_dir: Path, spec: dict, manifest: dict) -> None: + census_dir.mkdir(parents=True, exist_ok=True) + for name in ("binding_spec.schema.json", "manifest.schema.json"): + shutil.copy(CENSUS_DIR / name, census_dir / name) + (census_dir / "binding_spec.json").write_text(json.dumps(spec, indent=2, sort_keys=True) + "\n") + (census_dir / "manifest.json").write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + + +def test_check_fails_when_active_string_param_missing_from_spec(tmp_path): + repo = tmp_path / "repo" + _write_fake_repo(repo) + # An active binding whose py::arg carries a string-literal default. + register = repo / "src" / "interface" / "python" / "py_good" / "py_register_good.h" + register.write_text( + 'void register_good(py::module& m)\n{\n m.def("good_one", good_one, py::arg("save_path") = "");\n}\n' + ) + spec = {"version": 1, "bindings": []} + manifest = {"version": 1, "entries": []} + census_dir = tmp_path / "census" + _write_fake_census(census_dir, spec, manifest) + failures = census_manifest.check(repo, census_dir) + assert failures, "expected --check to fail on a missing active string param" + assert any("good_one" in failure and "save_path" in failure for failure in failures) + + +def test_check_fails_on_stale_manifest(tmp_path): + repo = tmp_path / "repo" + _write_fake_repo(repo) + spec = {"version": 1, "bindings": []} + manifest = {"version": 1, "entries": [{"stale": True}]} + census_dir = tmp_path / "census" + _write_fake_census(census_dir, spec, manifest) + failures = census_manifest.check(repo, census_dir) + assert any("byte-stable" in failure or "out of date" in failure for failure in failures) + + +# --------------------------------------------------------------------------- +# Real-repo integration +# --------------------------------------------------------------------------- + + +def test_real_repo_discovery_statuses(): + discovery = lexer.discover(REPO_ROOT) + bindings = {b.py_name: b for b in discovery["bindings"]} + assert bindings["flow_init"].status_in_source == "active" + assert bindings["pydb"].status_in_source == "active" + assert bindings["runMP"].status_in_source == "disabled" + assert bindings["runRef"].status_in_source == "disabled" + # py_vec's register function is never called -> whole module disabled. + for name in ("layout_patchs", "layout_graph", "generate_vectors", "read_vectors_nets", "get_timing_wire_graph"): + assert bindings[name].status_in_source == "disabled", name + assert bindings[name].register_function == "register_vectorization" + + +def test_real_repo_check_is_green(): + assert census_manifest.check(REPO_ROOT, CENSUS_DIR) == [] + + +def test_real_repo_generation_is_byte_stable(): + first = census_manifest.generate_manifest_bytes(REPO_ROOT, CENSUS_DIR) + second = census_manifest.generate_manifest_bytes(REPO_ROOT, CENSUS_DIR) + assert first == second + assert first == (CENSUS_DIR / "manifest.json").read_bytes() + + +# --------------------------------------------------------------------------- +# Dead-binding audit (synthetic wrapper) +# --------------------------------------------------------------------------- + +SYNTHETIC_WRAPPER = '''\ +class ECCToolsModule: + def live(self): + return self.ecc.flow_init("x") + + def dead_mp(self, config): + return self.ecc.runMP(config) + + def dead_absent(self): + self.ecc.run_pnp("c") + self.ecc.run_placer("c") + + def mixed(self): + self.ecc.runMP("c") + self.ecc.flow_init("x") + + def no_calls(self): + return None +''' + + +def test_dead_binding_audit_classifies_calls(tmp_path): + register = """\ +void register_imp(pybind11::module& m) +{ + m.def("flow_init", flow_init, py::arg("flow_config")); + // m.def("runMP", runMP, py::arg("config"), py::arg("output_tcl") = ""); +} +""" + discovery_bindings = lexer.discover_bindings_in_text( + register, module="py_imp", file="py_imp/py_register_imp.cpp", called_registers={"register_imp"} + ) + wrapper = tmp_path / "module.py" + wrapper.write_text(SYNTHETIC_WRAPPER) + audit = dead_bindings.audit_wrapper(wrapper, {"bindings": discovery_bindings}) + statuses = {call["binding"]: call["status"] for row in audit["rows"] for call in row["calls"]} + assert statuses["flow_init"] == "active" + assert statuses["runMP"].startswith("disabled") + assert statuses["run_pnp"] == "absent" + assert statuses["run_placer"] == "absent" + assert audit["dead_method_candidates"] == ["dead_absent", "dead_mp"] + assert audit["methods_without_calls"] == ["no_calls"] From 82df37dab5bef53f0095e0d7d4b297e7469ae612 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 13:12:41 +0800 Subject: [PATCH 02/16] feat: add path_or_empty canonicalization helper with unit fixture --- .github/workflows/ci.yml | 5 ++ scripts/binding_census/README.md | 10 ++++ src/interface/python/py_path_utils.h | 38 +++++++++++++++ .../python/test/py_path_utils_test.cc | 46 +++++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 src/interface/python/py_path_utils.h create mode 100644 src/interface/python/test/py_path_utils_test.cc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7a68108fe..162dd59b6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,3 +52,8 @@ jobs: - name: Check census manifest run: uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py --check + + - name: Compile and run path helper fixture + run: | + g++ -std=c++20 -Wall -Werror src/interface/python/test/py_path_utils_test.cc -o /tmp/py_path_utils_test + /tmp/py_path_utils_test diff --git a/scripts/binding_census/README.md b/scripts/binding_census/README.md index 93644639c3..4598b37f55 100644 --- a/scripts/binding_census/README.md +++ b/scripts/binding_census/README.md @@ -81,6 +81,16 @@ Exits nonzero with one message per violation when any of these fail: `std::optional` when optional, `std::vector` for lists). +## Interface unit fixture + +The converted bindings canonicalize optional path parameters through +`path_or_empty` in `src/interface/python/py_path_utils.h`. Its self-contained +compile-plus-assert fixture builds and runs with the system compiler: + +```sh +g++ -std=c++20 -Wall -Werror src/interface/python/test/py_path_utils_test.cc -o /tmp/py_path_utils_test && /tmp/py_path_utils_test +``` + ## Known limitation The lexer discovers parameters from `py::arg(...)` entries. String parameters diff --git a/src/interface/python/py_path_utils.h b/src/interface/python/py_path_utils.h new file mode 100644 index 0000000000..8149202c68 --- /dev/null +++ b/src/interface/python/py_path_utils.h @@ -0,0 +1,38 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#pragma once + +#include +#include +#include + +namespace python_interface { + +// Canonicalize an optional path parameter: std::nullopt and an empty path +// both map to "", a non-empty path maps to its .string(). This preserves the +// empty-string unset sentinel the interface internals check with .empty(), +// so an omitted/None argument and an explicitly passed "" stay +// indistinguishable after canonicalization. +inline std::string path_or_empty(const std::optional& path) +{ + if (not path.has_value() || path->empty()) { + return ""; + } + return path->string(); +} + +} // namespace python_interface diff --git a/src/interface/python/test/py_path_utils_test.cc b/src/interface/python/test/py_path_utils_test.cc new file mode 100644 index 0000000000..df934bdfcb --- /dev/null +++ b/src/interface/python/test/py_path_utils_test.cc @@ -0,0 +1,46 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +// Self-contained compile-plus-assert fixture for py_path_utils.h. Build: +// g++ -std=c++20 -Wall -Werror src/interface/python/test/py_path_utils_test.cc -o /tmp/py_path_utils_test +#include +#include +#include +#include + +#include "../py_path_utils.h" + +// Compile-time guard against calling .empty() directly on a converted +// optional path parameter: std::optional has no such +// member, so misuse fails to compile. The concept must stay dependent on T; +// a bare requires-expression on a concrete type is a hard error, not a +// false constraint. +template +concept has_empty_member = requires(T t) { t.empty(); }; +static_assert(not has_empty_member>); +static_assert(has_empty_member); // control + +int main() +{ + using python_interface::path_or_empty; + + assert(path_or_empty(std::nullopt) == ""); + assert(path_or_empty(std::optional{std::filesystem::path{}}) == ""); + assert(path_or_empty(std::optional{std::filesystem::path{""}}) == ""); + assert(path_or_empty(std::optional{std::filesystem::path{"foo/bar"}}) == "foo/bar"); + + return 0; +} From dd88fbdaa4bb0f09071f5bdf39f22cf58de08226 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 13:17:42 +0800 Subject: [PATCH 03/16] refactor: cite wrapper methods instead of line numbers in census rationales --- scripts/binding_census/binding_spec.json | 38 ++++++++++++------------ scripts/binding_census/manifest.json | 38 ++++++++++++------------ 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/scripts/binding_census/binding_spec.json b/scripts/binding_census/binding_spec.json index 2bb6211f55..4c0cef1a53 100644 --- a/scripts/binding_census/binding_spec.json +++ b/scripts/binding_census/binding_spec.json @@ -5,7 +5,7 @@ "params": [ { "classification": "path", - "classification_rationale": "flow config JSON file; wrapper init_config passes it positionally (chipcompiler/tools/ecc/module.py:86)", + "classification_rationale": "flow config JSON file; wrapper init_config passes it positionally (ECCToolsModule.init_config)", "new_default": null, "new_type": "std::filesystem::path", "old_default": null, @@ -551,7 +551,7 @@ "params": [ { "classification": "path", - "classification_rationale": "CTS config JSON file; wrapper run_cts passes path_text(config) (chipcompiler/tools/ecc/module.py:401)", + "classification_rationale": "CTS config JSON file; ECCToolsModule.run_cts passes path_text(config)", "new_default": null, "new_type": "std::filesystem::path", "old_default": null, @@ -562,7 +562,7 @@ }, { "classification": "path", - "classification_rationale": "CTS working directory; wrapper run_cts passes path_text(output) (chipcompiler/tools/ecc/module.py:401)", + "classification_rationale": "CTS working directory; ECCToolsModule.run_cts passes path_text(output)", "new_default": null, "new_type": "std::filesystem::path", "old_default": null, @@ -596,7 +596,7 @@ "params": [ { "classification": "path", - "classification_rationale": "idb config JSON file; wrapper idb_init passes path_text(config_path) (chipcompiler/tools/ecc/module.py:114); py::arg-free binding, parameter name from the initIdb declaration", + "classification_rationale": "idb config JSON file; ECCToolsModule.idb_init passes path_text(config_path); py::arg-free binding, parameter name from the initIdb declaration", "new_default": null, "new_type": "std::filesystem::path", "old_default": null, @@ -613,7 +613,7 @@ "params": [ { "classification": "path", - "classification_rationale": "technology LEF file; wrapper init_techlef passes path_text(tech_lef_path) (chipcompiler/tools/ecc/module.py:180); py::arg-free binding, parameter name from the initTechLef declaration", + "classification_rationale": "technology LEF file; ECCToolsModule.init_techlef passes path_text(tech_lef_path); py::arg-free binding, parameter name from the initTechLef declaration", "new_default": null, "new_type": "std::filesystem::path", "old_default": null, @@ -630,7 +630,7 @@ "params": [ { "classification": "path", - "classification_rationale": "cell LEF file list; wrapper init_lefs passes path_texts(lef_paths) (chipcompiler/tools/ecc/module.py:184); empty list is the unset sentinel", + "classification_rationale": "cell LEF file list; ECCToolsModule.init_lefs passes path_texts(lef_paths); empty list is the unset sentinel", "new_default": null, "new_type": "std::vector", "old_default": null, @@ -647,7 +647,7 @@ "params": [ { "classification": "path", - "classification_rationale": "DEF file; wrapper read_def passes path_text(path) (chipcompiler/tools/ecc/module.py:188)", + "classification_rationale": "DEF file; ECCToolsModule.read_def passes path_text(path)", "new_default": null, "new_type": "std::filesystem::path", "old_default": null, @@ -692,7 +692,7 @@ "params": [ { "classification": "path", - "classification_rationale": "Liberty file list; wrapper passes path_texts(lib_paths) (chipcompiler/tools/ecc/module.py:907); empty list is the unset sentinel", + "classification_rationale": "Liberty file list; ECCToolsModule.run_timing passes path_texts(lib_paths); empty list is the unset sentinel", "new_default": null, "new_type": "std::vector", "old_default": null, @@ -946,7 +946,7 @@ "params": [ { "classification": "path", - "classification_rationale": "abstract LEF output file; wrapper write_abstract_lef passes path_text (chipcompiler/tools/ecc/module.py:1001)", + "classification_rationale": "abstract LEF output file; ECCToolsModule.write_abstract_lef passes path_text(output_lef_path)", "new_default": null, "new_type": "std::filesystem::path", "old_default": null, @@ -1170,7 +1170,7 @@ "params": [ { "classification": "path", - "classification_rationale": "DRC working directory; wrapper init_drc passes path_text(output_dir) (chipcompiler/tools/ecc/module.py:419)", + "classification_rationale": "DRC working directory; ECCToolsModule.init_drc passes path_text(output_dir)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", @@ -1187,7 +1187,7 @@ "params": [ { "classification": "path", - "classification_rationale": "DRC config JSON file; wrapper run_drc passes path_text(config) (chipcompiler/tools/ecc/module.py:425)", + "classification_rationale": "DRC config JSON file; ECCToolsModule.run_drc passes path_text(config)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", @@ -1198,7 +1198,7 @@ }, { "classification": "path", - "classification_rationale": "DRC report output file; wrapper run_drc passes path_text(report_path) (chipcompiler/tools/ecc/module.py:425)", + "classification_rationale": "DRC report output file; ECCToolsModule.run_drc passes path_text(report_path)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", @@ -1215,7 +1215,7 @@ "params": [ { "classification": "path", - "classification_rationale": "DRC feature output file; wrapper save_drc passes path_text(feature_path) (chipcompiler/tools/ecc/module.py:431)", + "classification_rationale": "DRC feature output file; ECCToolsModule.save_drc passes path_text(feature_path)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", @@ -1991,7 +1991,7 @@ "params": [ { "classification": "path", - "classification_rationale": "RCX config JSON file; wrapper init_rcx passes path_text(config) (chipcompiler/tools/ecc/module.py:884)", + "classification_rationale": "RCX config JSON file; ECCToolsModule.init_rcx passes path_text(config)", "new_default": null, "new_type": "std::filesystem::path", "old_default": null, @@ -2019,7 +2019,7 @@ "params": [ { "classification": "path", - "classification_rationale": "router config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:852)", + "classification_rationale": "router config JSON file; ECCToolsModule.init_rt passes path_text(config)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", @@ -2036,7 +2036,7 @@ "params": [ { "classification": "path", - "classification_rationale": "early-router config JSON file; wrapper run_ert passes path_text(config) (chipcompiler/tools/ecc/module.py:849)", + "classification_rationale": "early-router config JSON file; ECCToolsModule.run_ert passes path_text(config)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", @@ -2053,7 +2053,7 @@ "params": [ { "classification": "path", - "classification_rationale": "STA config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:913)", + "classification_rationale": "STA config JSON file; ECCToolsModule.run_timing passes path_text(config)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", @@ -2070,7 +2070,7 @@ "params": [ { "classification": "path", - "classification_rationale": "fanout-fix config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:1293)", + "classification_rationale": "fanout-fix config JSON file; ECCToolsModule.run_net_opt passes path_text(config)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", @@ -2087,7 +2087,7 @@ "params": [ { "classification": "path", - "classification_rationale": "filler config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:802)", + "classification_rationale": "filler config JSON file; ECCToolsModule.run_filler passes path_text(config)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json index a9376ed5cf..a0248adcbc 100644 --- a/scripts/binding_census/manifest.json +++ b/scripts/binding_census/manifest.json @@ -156,7 +156,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "flow config JSON file; wrapper init_config passes it positionally (chipcompiler/tools/ecc/module.py:86)", + "classification_rationale": "flow config JSON file; wrapper init_config passes it positionally (ECCToolsModule.init_config)", "cpp_target": "flow_init", "file": "src/interface/python/py_config/py_register_config.h", "line": 25, @@ -632,7 +632,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "CTS config JSON file; wrapper run_cts passes path_text(config) (chipcompiler/tools/ecc/module.py:401)", + "classification_rationale": "CTS config JSON file; ECCToolsModule.run_cts passes path_text(config)", "cpp_target": "CtsAutoRun", "file": "src/interface/python/py_icts/py_register_icts.h", "line": 27, @@ -649,7 +649,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "CTS working directory; wrapper run_cts passes path_text(output) (chipcompiler/tools/ecc/module.py:401)", + "classification_rationale": "CTS working directory; ECCToolsModule.run_cts passes path_text(output)", "cpp_target": "CtsAutoRun", "file": "src/interface/python/py_icts/py_register_icts.h", "line": 27, @@ -802,7 +802,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "DEF file; wrapper read_def passes path_text(path) (chipcompiler/tools/ecc/module.py:188)", + "classification_rationale": "DEF file; ECCToolsModule.read_def passes path_text(path)", "cpp_target": "initDef", "file": "src/interface/python/py_idb/py_register_idb.h", "line": 36, @@ -938,7 +938,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "idb config JSON file; wrapper idb_init passes path_text(config_path) (chipcompiler/tools/ecc/module.py:114); py::arg-free binding, parameter name from the initIdb declaration", + "classification_rationale": "idb config JSON file; ECCToolsModule.idb_init passes path_text(config_path); py::arg-free binding, parameter name from the initIdb declaration", "cpp_target": "initIdb", "file": "src/interface/python/py_idb/py_register_idb.h", "line": 33, @@ -972,7 +972,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "cell LEF file list; wrapper init_lefs passes path_texts(lef_paths) (chipcompiler/tools/ecc/module.py:184); empty list is the unset sentinel", + "classification_rationale": "cell LEF file list; ECCToolsModule.init_lefs passes path_texts(lef_paths); empty list is the unset sentinel", "cpp_target": "initLef", "file": "src/interface/python/py_idb/py_register_idb.h", "line": 35, @@ -989,7 +989,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "Liberty file list; wrapper passes path_texts(lib_paths) (chipcompiler/tools/ecc/module.py:907); empty list is the unset sentinel", + "classification_rationale": "Liberty file list; ECCToolsModule.run_timing passes path_texts(lib_paths); empty list is the unset sentinel", "cpp_target": "initLib", "file": "src/interface/python/py_idb/py_register_idb.h", "line": 38, @@ -1159,7 +1159,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "technology LEF file; wrapper init_techlef passes path_text(tech_lef_path) (chipcompiler/tools/ecc/module.py:180); py::arg-free binding, parameter name from the initTechLef declaration", + "classification_rationale": "technology LEF file; ECCToolsModule.init_techlef passes path_text(tech_lef_path); py::arg-free binding, parameter name from the initTechLef declaration", "cpp_target": "initTechLef", "file": "src/interface/python/py_idb/py_register_idb.h", "line": 34, @@ -1261,7 +1261,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "abstract LEF output file; wrapper write_abstract_lef passes path_text (chipcompiler/tools/ecc/module.py:1001)", + "classification_rationale": "abstract LEF output file; ECCToolsModule.write_abstract_lef passes path_text(output_lef_path)", "cpp_target": "writeAbstractLef", "file": "src/interface/python/py_idb/py_register_idb.h", "line": 54, @@ -1312,7 +1312,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "DRC working directory; wrapper init_drc passes path_text(output_dir) (chipcompiler/tools/ecc/module.py:419)", + "classification_rationale": "DRC working directory; ECCToolsModule.init_drc passes path_text(output_dir)", "cpp_target": "init_drc", "file": "src/interface/python/py_idrc/py_register_idrc.h", "line": 28, @@ -1329,7 +1329,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "DRC config JSON file; wrapper run_drc passes path_text(config) (chipcompiler/tools/ecc/module.py:425)", + "classification_rationale": "DRC config JSON file; ECCToolsModule.run_drc passes path_text(config)", "cpp_target": "run_drc", "file": "src/interface/python/py_idrc/py_register_idrc.h", "line": 29, @@ -1346,7 +1346,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "DRC report output file; wrapper run_drc passes path_text(report_path) (chipcompiler/tools/ecc/module.py:425)", + "classification_rationale": "DRC report output file; ECCToolsModule.run_drc passes path_text(report_path)", "cpp_target": "run_drc", "file": "src/interface/python/py_idrc/py_register_idrc.h", "line": 29, @@ -1363,7 +1363,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "DRC feature output file; wrapper save_drc passes path_text(feature_path) (chipcompiler/tools/ecc/module.py:431)", + "classification_rationale": "DRC feature output file; ECCToolsModule.save_drc passes path_text(feature_path)", "cpp_target": "save_drc", "file": "src/interface/python/py_idrc/py_register_idrc.h", "line": 30, @@ -2349,7 +2349,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "RCX config JSON file; wrapper init_rcx passes path_text(config) (chipcompiler/tools/ecc/module.py:884)", + "classification_rationale": "RCX config JSON file; ECCToolsModule.init_rcx passes path_text(config)", "cpp_target": "init_rcx", "file": "src/interface/python/py_ircx/py_register_ircx.h", "line": 28, @@ -2383,7 +2383,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "router config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:852)", + "classification_rationale": "router config JSON file; ECCToolsModule.init_rt passes path_text(config)", "cpp_target": "initRT", "file": "src/interface/python/py_irt/py_register_irt.h", "line": 28, @@ -2400,7 +2400,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "early-router config JSON file; wrapper run_ert passes path_text(config) (chipcompiler/tools/ecc/module.py:849)", + "classification_rationale": "early-router config JSON file; ECCToolsModule.run_ert passes path_text(config)", "cpp_target": "runERT", "file": "src/interface/python/py_irt/py_register_irt.h", "line": 29, @@ -2417,7 +2417,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "STA config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:913)", + "classification_rationale": "STA config JSON file; ECCToolsModule.run_timing passes path_text(config)", "cpp_target": "initSTA", "file": "src/interface/python/py_ista/py_register_ista.h", "line": 29, @@ -2434,7 +2434,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "fanout-fix config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:1293)", + "classification_rationale": "fanout-fix config JSON file; ECCToolsModule.run_net_opt passes path_text(config)", "cpp_target": "fix_fanout", "file": "src/interface/python/py_izh/py_register_izh.h", "line": 28, @@ -2451,7 +2451,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "filler config JSON file; wrapper passes path_text(config) (chipcompiler/tools/ecc/module.py:802)", + "classification_rationale": "filler config JSON file; ECCToolsModule.run_filler passes path_text(config)", "cpp_target": "insert_filler", "file": "src/interface/python/py_izh/py_register_izh.h", "line": 29, From cd5daf7ace6eafc6c6fc9524bb056408f1190525 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 13:30:55 +0800 Subject: [PATCH 04/16] feat: accept os.PathLike in py_idb path bindings --- scripts/binding_census/manifest.json | 76 +++++------ src/interface/python/py_idb/py_db.cpp | 118 +++++++++++------- src/interface/python/py_idb/py_db.h | 40 +++--- src/interface/python/py_idb/py_db_op.h | 10 +- src/interface/python/py_idb/py_register_idb.h | 5 +- 5 files changed, 141 insertions(+), 108 deletions(-) diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json index a0248adcbc..0c3d38c167 100644 --- a/scripts/binding_census/manifest.json +++ b/scripts/binding_census/manifest.json @@ -669,7 +669,7 @@ "classification_rationale": "blockage type keyword, not a filesystem path", "cpp_target": "clearBlockage", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 61, + "line": 62, "module": "py_idb", "new_default": null, "new_type": "const std::string&", @@ -686,7 +686,7 @@ "classification_rationale": "cell master name, not a filesystem path", "cpp_target": "idbCreateInstance", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 65, + "line": 66, "module": "py_idb", "new_default": null, "new_type": "const std::string&", @@ -703,7 +703,7 @@ "classification_rationale": "instance name to create, not a filesystem path", "cpp_target": "idbCreateInstance", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 65, + "line": 66, "module": "py_idb", "new_default": null, "new_type": "const std::string&", @@ -720,7 +720,7 @@ "classification_rationale": "placement orientation keyword, not a filesystem path", "cpp_target": "idbCreateInstance", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 65, + "line": 66, "module": "py_idb", "new_default": "\"\"", "new_type": "const std::string&", @@ -737,7 +737,7 @@ "classification_rationale": "placement status keyword, not a filesystem path", "cpp_target": "idbCreateInstance", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 65, + "line": 66, "module": "py_idb", "new_default": "\"\"", "new_type": "const std::string&", @@ -754,7 +754,7 @@ "classification_rationale": "instance type tag, not a filesystem path", "cpp_target": "idbCreateInstance", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 65, + "line": 66, "module": "py_idb", "new_default": "\"\"", "new_type": "const std::string&", @@ -771,7 +771,7 @@ "classification_rationale": "connection type keyword, not a filesystem path", "cpp_target": "idbCreateNet", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 67, + "line": 68, "module": "py_idb", "new_default": "\"\"", "new_type": "const std::string&", @@ -788,7 +788,7 @@ "classification_rationale": "net name to create, not a filesystem path", "cpp_target": "idbCreateNet", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 67, + "line": 68, "module": "py_idb", "new_default": null, "new_type": "const std::string&", @@ -805,7 +805,7 @@ "classification_rationale": "DEF file; ECCToolsModule.read_def passes path_text(path)", "cpp_target": "initDef", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 36, + "line": 37, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -822,7 +822,7 @@ "classification_rationale": "DEF output file; wrapper def_save passes path_text(def_path)", "cpp_target": "saveDef", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 41, + "line": 42, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -839,7 +839,7 @@ "classification_rationale": "instance name in the design database, not a filesystem path", "cpp_target": "idbDeleteInstance", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 63, + "line": 64, "module": "py_idb", "new_default": null, "new_type": "const std::string&", @@ -856,7 +856,7 @@ "classification_rationale": "net name in the design database, not a filesystem path", "cpp_target": "idbDeleteNet", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 64, + "line": 65, "module": "py_idb", "new_default": null, "new_type": "const std::string&", @@ -873,7 +873,7 @@ "classification_rationale": "GDSII output file; wrapper gds_save passes path_text(output_path)", "cpp_target": "saveGDSII", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 46, + "line": 47, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -890,7 +890,7 @@ "classification_rationale": "report output file: idbGet forwards file_name to rptInst->reportInstance/reportNet (py_db_op.h), which write the report to that file", "cpp_target": "idbGet", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 62, + "line": 63, "module": "py_idb", "new_default": "py::none()", "new_type": "std::optional", @@ -907,7 +907,7 @@ "classification_rationale": "instance name filter, not a filesystem path", "cpp_target": "idbGet", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 62, + "line": 63, "module": "py_idb", "new_default": "\"\"", "new_type": "const std::string&", @@ -924,7 +924,7 @@ "classification_rationale": "net name filter, not a filesystem path", "cpp_target": "idbGet", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 62, + "line": 63, "module": "py_idb", "new_default": "\"\"", "new_type": "const std::string&", @@ -941,7 +941,7 @@ "classification_rationale": "idb config JSON file; ECCToolsModule.idb_init passes path_text(config_path); py::arg-free binding, parameter name from the initIdb declaration", "cpp_target": "initIdb", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 33, + "line": 34, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -958,7 +958,7 @@ "classification_rationale": "idb JSON output file (saveJson serializes the database)", "cpp_target": "saveJson", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 47, + "line": 48, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -975,7 +975,7 @@ "classification_rationale": "cell LEF file list; ECCToolsModule.init_lefs passes path_texts(lef_paths); empty list is the unset sentinel", "cpp_target": "initLef", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 35, + "line": 36, "module": "py_idb", "new_default": null, "new_type": "std::vector", @@ -992,7 +992,7 @@ "classification_rationale": "Liberty file list; ECCToolsModule.run_timing passes path_texts(lib_paths); empty list is the unset sentinel", "cpp_target": "initLib", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 38, + "line": 39, "module": "py_idb", "new_default": null, "new_type": "std::vector", @@ -1009,7 +1009,7 @@ "classification_rationale": "serialized database input file/directory read by loadData", "cpp_target": "loadData", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 52, + "line": 53, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1026,7 +1026,7 @@ "classification_rationale": "cell master names to exclude from the netlist, not filesystem paths", "cpp_target": "saveNetList", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 44, + "line": 45, "module": "py_idb", "new_default": "std::set{}", "new_type": "std::set", @@ -1043,7 +1043,7 @@ "classification_rationale": "netlist output file (saveNetList writes a Verilog netlist)", "cpp_target": "saveNetList", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 44, + "line": 45, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1060,7 +1060,7 @@ "classification_rationale": "serialized database output file/directory (saveData persists the DataManager state)", "cpp_target": "saveData", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 50, + "line": 51, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1077,7 +1077,7 @@ "classification_rationale": "SDC constraints file; initSdc stores the value as-is and the timing flow treats empty as unset; the production harden flow passes None natively (runner.py passes workspace.pdk.sdc which is Path|None), so the converted binding takes an optional path", "cpp_target": "initSdc", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 39, + "line": 40, "module": "py_idb", "new_default": "py::none()", "new_type": "std::optional", @@ -1094,7 +1094,7 @@ "classification_rationale": "net name in the design database, not a filesystem path", "cpp_target": "setNet", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 59, + "line": 60, "module": "py_idb", "new_default": null, "new_type": "const std::string&", @@ -1111,7 +1111,7 @@ "classification_rationale": "net type keyword (signal/power/ground), not a filesystem path", "cpp_target": "setNet", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 59, + "line": 60, "module": "py_idb", "new_default": null, "new_type": "const std::string&", @@ -1128,7 +1128,7 @@ "classification_rationale": "SPEF parasitics file; wrapper passes path_text(spef_path)", "cpp_target": "initSpef", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 40, + "line": 41, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1145,7 +1145,7 @@ "classification_rationale": "macro placement TCL output file (saveMacroTCL writes a .tcl file)", "cpp_target": "saveMacroTCL", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 43, + "line": 44, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1162,7 +1162,7 @@ "classification_rationale": "technology LEF file; ECCToolsModule.init_techlef passes path_text(tech_lef_path); py::arg-free binding, parameter name from the initTechLef declaration", "cpp_target": "initTechLef", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 34, + "line": 35, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1179,7 +1179,7 @@ "classification_rationale": "design top module name, not a filesystem path", "cpp_target": "initVerilog", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 37, + "line": 38, "module": "py_idb", "new_default": null, "new_type": "const std::string&", @@ -1196,7 +1196,7 @@ "classification_rationale": "netlist Verilog file; wrapper read_verilog passes path_text(verilog)", "cpp_target": "initVerilog", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 37, + "line": 38, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1213,7 +1213,7 @@ "classification_rationale": "view JSON edits input file read by applyViewJsonEdits", "cpp_target": "applyViewJsonEdits", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 49, + "line": 50, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1230,7 +1230,7 @@ "classification_rationale": "serialization format keyword (e.g. \"pretty\"), not a filesystem path", "cpp_target": "saveViewJson", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 48, + "line": 49, "module": "py_idb", "new_default": "\"pretty\"", "new_type": "const std::string&", @@ -1247,7 +1247,7 @@ "classification_rationale": "view JSON output directory", "cpp_target": "saveViewJson", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 48, + "line": 49, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1264,7 +1264,7 @@ "classification_rationale": "abstract LEF output file; ECCToolsModule.write_abstract_lef passes path_text(output_lef_path)", "cpp_target": "writeAbstractLef", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 54, + "line": 55, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", @@ -1281,7 +1281,7 @@ "classification_rationale": "harden core instance names embedded in the JSON, not filesystem paths", "cpp_target": "writeSocJson", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 53, + "line": 54, "module": "py_idb", "new_default": "std::vector{}", "new_type": "const std::vector&", @@ -1298,7 +1298,7 @@ "classification_rationale": "SoC JSON output file", "cpp_target": "writeSocJson", "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 53, + "line": 54, "module": "py_idb", "new_default": null, "new_type": "std::filesystem::path", diff --git a/src/interface/python/py_idb/py_db.cpp b/src/interface/python/py_idb/py_db.cpp index 1823ceed1e..9835ea0337 100644 --- a/src/interface/python/py_idb/py_db.cpp +++ b/src/interface/python/py_idb/py_db.cpp @@ -16,107 +16,132 @@ // *************************************************************************************** #include "py_db.h" +#include "../py_path_utils.h" #include "db_fm/file_soc.h" #include #include "view_json_io.h" namespace python_interface { -bool initIdb(const std::string& config_path) +bool initIdb(const std::filesystem::path& config_path) { - return dmInst->init(config_path); + const std::string config_path_ = config_path.string(); + return dmInst->init(config_path_); } -bool initTechLef(const std::string& techlef_path) +bool initTechLef(const std::filesystem::path& techlef_path) { - dmInst->get_config().set_tech_lef_path(techlef_path); - return dmInst->readLef(vector{techlef_path}, true); + const std::string techlef_path_ = techlef_path.string(); + dmInst->get_config().set_tech_lef_path(techlef_path_); + return dmInst->readLef(vector{techlef_path_}, true); } -bool initLef(const std::vector& lef_paths) +bool initLef(const std::vector& lef_paths) { - dmInst->get_config().set_lef_paths(lef_paths); - return dmInst->readLef(lef_paths); + std::vector lef_paths_; + lef_paths_.reserve(lef_paths.size()); + for (const auto& lef_path : lef_paths) { + lef_paths_.push_back(lef_path.string()); + } + dmInst->get_config().set_lef_paths(lef_paths_); + return dmInst->readLef(lef_paths_); } -bool initDef(const std::string& def_path) +bool initDef(const std::filesystem::path& def_path) { - dmInst->get_config().set_def_path(def_path); - return dmInst->readDef(def_path); + const std::string def_path_ = def_path.string(); + dmInst->get_config().set_def_path(def_path_); + return dmInst->readDef(def_path_); } -bool initVerilog(const std::string& verilog_path, const std::string& top_module) +bool initVerilog(const std::filesystem::path& verilog_path, const std::string& top_module) { - dmInst->get_config().set_verilog_path(verilog_path); - return dmInst->readVerilog(verilog_path, top_module); + const std::string verilog_path_ = verilog_path.string(); + dmInst->get_config().set_verilog_path(verilog_path_); + return dmInst->readVerilog(verilog_path_, top_module); } -bool initLib(const std::vector& lib_paths) +bool initLib(const std::vector& lib_paths) { - dmInst->get_config().set_lib_paths(lib_paths); - return dmInst->readLib(lib_paths); + std::vector lib_paths_; + lib_paths_.reserve(lib_paths.size()); + for (const auto& lib_path : lib_paths) { + lib_paths_.push_back(lib_path.string()); + } + dmInst->get_config().set_lib_paths(lib_paths_); + return dmInst->readLib(lib_paths_); } -bool initSdc(const std::string& sdc_path) +bool initSdc(const std::optional& sdc_path) { - dmInst->get_config().set_sdc_path(sdc_path); + const std::string sdc_path_ = path_or_empty(sdc_path); + dmInst->get_config().set_sdc_path(sdc_path_); return true; } -bool initSpef(const std::string& spef_path) +bool initSpef(const std::filesystem::path& spef_path) { - dmInst->get_config().set_spef_path(spef_path); - return dmInst->readSpef(spef_path); + const std::string spef_path_ = spef_path.string(); + dmInst->get_config().set_spef_path(spef_path_); + return dmInst->readSpef(spef_path_); } -bool saveDef(const std::string& def_name) +bool saveDef(const std::filesystem::path& def_name) { - return dmInst->saveDef(def_name); + const std::string def_name_ = def_name.string(); + return dmInst->saveDef(def_name_); } -bool saveMacroTCL(const std::string& def_name) +bool saveMacroTCL(const std::filesystem::path& def_name) { - return dmInst->saveMacroTCL(def_name); + const std::string def_name_ = def_name.string(); + return dmInst->saveMacroTCL(def_name_); } -bool saveNetList(const std::string& netlist_path, std::set exclude_cell_names /* = {} */, +bool saveNetList(const std::filesystem::path& netlist_path, std::set exclude_cell_names /* = {} */, bool is_add_space_for_escape_name /* = false*/) { - dmInst->saveVerilog(netlist_path, std::move(exclude_cell_names), is_add_space_for_escape_name); + const std::string netlist_path_ = netlist_path.string(); + dmInst->saveVerilog(netlist_path_, std::move(exclude_cell_names), is_add_space_for_escape_name); return true; } -bool saveGDSII(const std::string& gds_name, bool is_hardened /* = false */) +bool saveGDSII(const std::filesystem::path& gds_name, bool is_hardened /* = false */) { - return dmInst->saveGDSII(gds_name, is_hardened); + const std::string gds_name_ = gds_name.string(); + return dmInst->saveGDSII(gds_name_, is_hardened); } -bool saveJson(const std::string& path) +bool saveJson(const std::filesystem::path& path) { + const std::string path_ = path.string(); std::string options = ""; - return dmInst->saveJSON(path, options); + return dmInst->saveJSON(path_, options); } -bool saveViewJson(const std::string& output_dir, const std::string& json_format, bool compress) +bool saveViewJson(const std::filesystem::path& output_dir, const std::string& json_format, bool compress) { + const std::string output_dir_ = output_dir.string(); idb::ViewJsonWriteOptions options; if (!idb::parseViewJsonFormat(json_format, options.format)) { std::cout << "Save view json failed: unsupported json_format `" << json_format << "`, expected `pretty` or `compact`." << std::endl; return false; } options.compress = compress; - return dmInst->saveViewJson(output_dir, options); + return dmInst->saveViewJson(output_dir_, options); } -bool applyViewJsonEdits(const std::string& edits_path, bool compress) +bool applyViewJsonEdits(const std::filesystem::path& edits_path, bool compress) { - return dmInst->applyViewJsonEdits(edits_path, compress); + const std::string edits_path_ = edits_path.string(); + return dmInst->applyViewJsonEdits(edits_path_, compress); } -bool saveData(const std::string& path) +bool saveData(const std::filesystem::path& path) { - return dmInst->saveData(path); + const std::string path_ = path.string(); + return dmInst->saveData(path_); } bool resetData() @@ -125,22 +150,23 @@ bool resetData() return true; } -bool loadData(const std::string& path) +bool loadData(const std::filesystem::path& path) { - return dmInst->loadData(path); + const std::string path_ = path.string(); + return dmInst->loadData(path_); } -bool writeSocJson(const std::string& path, const std::vector& harden_cores /* = {} */) +bool writeSocJson(const std::filesystem::path& path, const std::vector& harden_cores /* = {} */) { - idb::JsonSoc soc_file(path, harden_cores); + const std::string path_ = path.string(); + idb::JsonSoc soc_file(path_, harden_cores); return soc_file.saveFileData(); } -bool writeAbstractLef(const std::string& output_lef_path) +bool writeAbstractLef(const std::filesystem::path& output_lef_path) { - namespace fs = std::filesystem; - - return dmInst->saveLef(output_lef_path); + const std::string output_lef_path_ = output_lef_path.string(); + return dmInst->saveLef(output_lef_path_); } } // namespace python_interface diff --git a/src/interface/python/py_idb/py_db.h b/src/interface/python/py_idb/py_db.h index 72d3fcd49e..66342711f9 100644 --- a/src/interface/python/py_idb/py_db.h +++ b/src/interface/python/py_idb/py_db.h @@ -16,31 +16,33 @@ // *************************************************************************************** #pragma once +#include +#include #include #include #include namespace python_interface { -bool initIdb(const std::string& config_path); -bool initTechLef(const std::string& techlef_path); -bool initLef(const std::vector& lef_paths); -bool initDef(const std::string& def_path); -bool initVerilog(const std::string& verilog_path, const std::string& top_module); -bool initLib(const std::vector& lib_paths); -bool initSdc(const std::string& sdc_path); -bool initSpef(const std::string& spef_path); -bool saveDef(const std::string& def_name); -bool saveMacroTCL(const std::string& tcl_name); -bool saveNetList(const std::string& netlist_path, std::set exclude_cell_names = {}, bool is_add_space_for_escape_name = false); -bool saveGDSII(const std::string& gds_name, bool is_harden = false); -bool saveJson(const std::string& path); -bool saveViewJson(const std::string& output_dir, const std::string& json_format = "pretty", bool compress = false); -bool applyViewJsonEdits(const std::string& edits_path, bool compress = false); -bool saveData(const std::string& path); +bool initIdb(const std::filesystem::path& config_path); +bool initTechLef(const std::filesystem::path& techlef_path); +bool initLef(const std::vector& lef_paths); +bool initDef(const std::filesystem::path& def_path); +bool initVerilog(const std::filesystem::path& verilog_path, const std::string& top_module); +bool initLib(const std::vector& lib_paths); +bool initSdc(const std::optional& sdc_path); +bool initSpef(const std::filesystem::path& spef_path); +bool saveDef(const std::filesystem::path& def_name); +bool saveMacroTCL(const std::filesystem::path& tcl_name); +bool saveNetList(const std::filesystem::path& netlist_path, std::set exclude_cell_names = {}, bool is_add_space_for_escape_name = false); +bool saveGDSII(const std::filesystem::path& gds_name, bool is_harden = false); +bool saveJson(const std::filesystem::path& path); +bool saveViewJson(const std::filesystem::path& output_dir, const std::string& json_format = "pretty", bool compress = false); +bool applyViewJsonEdits(const std::filesystem::path& edits_path, bool compress = false); +bool saveData(const std::filesystem::path& path); bool resetData(); -bool loadData(const std::string& path); -bool writeSocJson(const std::string& path, const std::vector& harden_cores = {}); -bool writeAbstractLef(const std::string& output_lef_path); +bool loadData(const std::filesystem::path& path); +bool writeSocJson(const std::filesystem::path& path, const std::vector& harden_cores = {}); +bool writeAbstractLef(const std::filesystem::path& output_lef_path); } // namespace python_interface diff --git a/src/interface/python/py_idb/py_db_op.h b/src/interface/python/py_idb/py_db_op.h index 6502ef788f..3c24ce17da 100644 --- a/src/interface/python/py_idb/py_db_op.h +++ b/src/interface/python/py_idb/py_db_op.h @@ -17,12 +17,15 @@ #pragma once #include +#include +#include #include #include #include #include #include +#include "../py_path_utils.h" #include "IdbEnum.h" #include "IdbInstance.h" @@ -44,14 +47,15 @@ bool clearBlockage(const std::string& type) return true; } -bool idbGet(const std::string& inst_name, const std::string& net_name, const std::string& file_name) +bool idbGet(const std::string& inst_name, const std::string& net_name, const std::optional& file_name) { + const std::string file_name_ = path_or_empty(file_name); bool ok = false; if (not inst_name.empty()) { - ok |= rptInst->reportInstance(file_name, inst_name); + ok |= rptInst->reportInstance(file_name_, inst_name); } if (not net_name.empty()) { - ok |= rptInst->reportNet(file_name, net_name); + ok |= rptInst->reportNet(file_name_, net_name); } return ok; } diff --git a/src/interface/python/py_idb/py_register_idb.h b/src/interface/python/py_idb/py_register_idb.h index 98cd7f9397..a8bf2a527a 100644 --- a/src/interface/python/py_idb/py_register_idb.h +++ b/src/interface/python/py_idb/py_register_idb.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include @@ -36,7 +37,7 @@ void register_idb(py::module& m) m.def("def_init", initDef, py::arg("def_path")); m.def("verilog_init", initVerilog, py::arg("verilog_path"), py::arg("top_module")); m.def("lib_init", initLib, py::arg("lib_paths")); - m.def("sdc_init", initSdc, py::arg("sdc_path")); + m.def("sdc_init", initSdc, py::arg("sdc_path") = py::none()); m.def("spef_init", initSpef, py::arg("spef_path")); m.def("def_save", saveDef, py::arg("def_name")); // TODO: @@ -59,7 +60,7 @@ void register_idb_op(pybind11::module& m) m.def("set_net", setNet, py::arg("net_name"), py::arg("net_type")); m.def("remove_except_pg_net", removeExceptPgNet); m.def("clear_blockage", clearBlockage, py::arg("type")); - m.def("idb_get", idbGet, py::arg("inst_name") = "", py::arg("net_name") = "", py::arg("file_name") = ""); + m.def("idb_get", idbGet, py::arg("inst_name") = "", py::arg("net_name") = "", py::arg("file_name") = py::none()); m.def("delete_inst", idbDeleteInstance, py::arg("inst_name")); m.def("delete_net", idbDeleteNet, py::arg("net_name")); m.def("create_inst", idbCreateInstance, py::arg("inst_name"), py::arg("cell_master"), py::arg("coord_x") = 0, py::arg("coord_y") = 0, From 406dd4c5fe3cf69494148360121989e3ed5a7c14 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 13:52:01 +0800 Subject: [PATCH 05/16] fix: correct init_rt rationale to the run_routing wrapper method --- scripts/binding_census/binding_spec.json | 2 +- scripts/binding_census/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/binding_census/binding_spec.json b/scripts/binding_census/binding_spec.json index 4c0cef1a53..41866152c3 100644 --- a/scripts/binding_census/binding_spec.json +++ b/scripts/binding_census/binding_spec.json @@ -2019,7 +2019,7 @@ "params": [ { "classification": "path", - "classification_rationale": "router config JSON file; ECCToolsModule.init_rt passes path_text(config)", + "classification_rationale": "router config JSON file; ECCToolsModule.run_routing passes path_text(config)", "new_default": "py::none()", "new_type": "std::optional", "old_default": "\"\"", diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json index 0c3d38c167..5e99581048 100644 --- a/scripts/binding_census/manifest.json +++ b/scripts/binding_census/manifest.json @@ -2383,7 +2383,7 @@ { "binding_status": "active", "classification": "path", - "classification_rationale": "router config JSON file; ECCToolsModule.init_rt passes path_text(config)", + "classification_rationale": "router config JSON file; ECCToolsModule.run_routing passes path_text(config)", "cpp_target": "initRT", "file": "src/interface/python/py_irt/py_register_irt.h", "line": 28, From b1dbc3080650e040316bb125bb22f19602ea6291 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 13:59:58 +0800 Subject: [PATCH 06/16] feat: accept os.PathLike in py_config path bindings --- scripts/binding_census/manifest.json | 20 +++--- src/interface/python/py_config/py_config.cpp | 68 ++++++++++++------- src/interface/python/py_config/py_config.h | 12 ++-- .../python/py_config/py_register_config.h | 19 +++--- 4 files changed, 73 insertions(+), 46 deletions(-) diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json index 5e99581048..b148f885c6 100644 --- a/scripts/binding_census/manifest.json +++ b/scripts/binding_census/manifest.json @@ -6,7 +6,7 @@ "classification_rationale": "db config JSON file; wrapper init_config/update_step_paths pass path_text(db_config)", "cpp_target": "db_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 27, + "line": 28, "module": "py_config", "new_default": "py::none()", "new_type": "std::optional", @@ -23,7 +23,7 @@ "classification_rationale": "DEF file; empty string is the unset sentinel", "cpp_target": "db_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 27, + "line": 28, "module": "py_config", "new_default": "py::none()", "new_type": "std::optional", @@ -40,7 +40,7 @@ "classification_rationale": "feature output directory; wrapper passes path_text(feature_dir)", "cpp_target": "db_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 27, + "line": 28, "module": "py_config", "new_default": "py::none()", "new_type": "std::optional", @@ -57,7 +57,7 @@ "classification_rationale": "cell LEF file list; empty list is the unset sentinel, no optional-ization", "cpp_target": "db_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 27, + "line": 28, "module": "py_config", "new_default": "std::vector{}", "new_type": "std::vector", @@ -74,7 +74,7 @@ "classification_rationale": "Liberty file list; wrapper update_sta_data_config passes path_texts(lib_paths); empty list is the unset sentinel", "cpp_target": "db_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 27, + "line": 28, "module": "py_config", "new_default": "std::vector{}", "new_type": "std::vector", @@ -91,7 +91,7 @@ "classification_rationale": "output directory; wrapper passes path_text(output_dir)", "cpp_target": "db_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 27, + "line": 28, "module": "py_config", "new_default": "py::none()", "new_type": "std::optional", @@ -108,7 +108,7 @@ "classification_rationale": "SDC constraints file; empty string is the unset sentinel", "cpp_target": "db_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 27, + "line": 28, "module": "py_config", "new_default": "py::none()", "new_type": "std::optional", @@ -125,7 +125,7 @@ "classification_rationale": "technology LEF file; empty string is the unset sentinel in db_init's C++ body", "cpp_target": "db_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 27, + "line": 28, "module": "py_config", "new_default": "py::none()", "new_type": "std::optional", @@ -142,7 +142,7 @@ "classification_rationale": "netlist Verilog file; empty string is the unset sentinel", "cpp_target": "db_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 27, + "line": 28, "module": "py_config", "new_default": "py::none()", "new_type": "std::optional", @@ -159,7 +159,7 @@ "classification_rationale": "flow config JSON file; wrapper init_config passes it positionally (ECCToolsModule.init_config)", "cpp_target": "flow_init", "file": "src/interface/python/py_config/py_register_config.h", - "line": 25, + "line": 26, "module": "py_config", "new_default": null, "new_type": "std::filesystem::path", diff --git a/src/interface/python/py_config/py_config.cpp b/src/interface/python/py_config/py_config.cpp index b54b0fffcd..70d9d624e1 100644 --- a/src/interface/python/py_config/py_config.cpp +++ b/src/interface/python/py_config/py_config.cpp @@ -16,52 +16,74 @@ // *************************************************************************************** #include "py_config.h" +#include "../py_path_utils.h" #include #include #include namespace python_interface { -bool flow_init(const std::string& flow_config) +bool flow_init(const std::filesystem::path& flow_config) { - bool init_ok = iplf::plfInst->initFlow(flow_config); + const std::string flow_config_ = flow_config.string(); + bool init_ok = iplf::plfInst->initFlow(flow_config_); return init_ok; } -bool db_init(const std::string& config_path, const std::string& tech_lef_path, const std::vector& lef_paths, - const std::string& def_path, const std::string& verilog_path, const std::string& output_path, const std::string& feature_path, - const std::vector& lib_paths, const std::string& sdc_path) +bool db_init(const std::optional& config_path, const std::optional& tech_lef_path, + const std::vector& lef_paths, const std::optional& def_path, + const std::optional& verilog_path, const std::optional& output_path, + const std::optional& feature_path, const std::vector& lib_paths, + const std::optional& sdc_path) { + const std::string config_path_ = path_or_empty(config_path); + const std::string tech_lef_path_ = path_or_empty(tech_lef_path); + std::vector lef_paths_; + lef_paths_.reserve(lef_paths.size()); + for (const auto& lef_path : lef_paths) { + lef_paths_.push_back(lef_path.string()); + } + const std::string def_path_ = path_or_empty(def_path); + const std::string verilog_path_ = path_or_empty(verilog_path); + const std::string output_path_ = path_or_empty(output_path); + const std::string feature_path_ = path_or_empty(feature_path); + std::vector lib_paths_; + lib_paths_.reserve(lib_paths.size()); + for (const auto& lib_path : lib_paths) { + lib_paths_.push_back(lib_path.string()); + } + const std::string sdc_path_ = path_or_empty(sdc_path); + idm::DataConfig& dm_config = dmInst->get_config(); - if (not config_path.empty()) { - bool init_ok = dm_config.initConfig(config_path); + if (not config_path_.empty()) { + bool init_ok = dm_config.initConfig(config_path_); if (not init_ok) { return false; } } - if (not tech_lef_path.empty()) { - dm_config.set_tech_lef_path(tech_lef_path); + if (not tech_lef_path_.empty()) { + dm_config.set_tech_lef_path(tech_lef_path_); } - if (not lef_paths.empty()) { - dm_config.set_lef_paths(lef_paths); + if (not lef_paths_.empty()) { + dm_config.set_lef_paths(lef_paths_); } - if (not def_path.empty()) { - dm_config.set_def_path(def_path); + if (not def_path_.empty()) { + dm_config.set_def_path(def_path_); } - if (not verilog_path.empty()) { - dm_config.set_verilog_path(verilog_path); + if (not verilog_path_.empty()) { + dm_config.set_verilog_path(verilog_path_); } - if (not output_path.empty()) { - dm_config.set_output_path(output_path); + if (not output_path_.empty()) { + dm_config.set_output_path(output_path_); } - if (not lib_paths.empty()) { - dm_config.set_lib_paths(lib_paths); + if (not lib_paths_.empty()) { + dm_config.set_lib_paths(lib_paths_); } - if (not sdc_path.empty()) { - dm_config.set_sdc_path(sdc_path); + if (not sdc_path_.empty()) { + dm_config.set_sdc_path(sdc_path_); } - if (not feature_path.empty()) { - dm_config.set_feature_path(feature_path); + if (not feature_path_.empty()) { + dm_config.set_feature_path(feature_path_); } return true; } diff --git a/src/interface/python/py_config/py_config.h b/src/interface/python/py_config/py_config.h index fd1657b5da..cfc82784df 100644 --- a/src/interface/python/py_config/py_config.h +++ b/src/interface/python/py_config/py_config.h @@ -16,13 +16,17 @@ // *************************************************************************************** #pragma once +#include +#include #include #include namespace python_interface { -bool flow_init(const std::string& flow_config); +bool flow_init(const std::filesystem::path& flow_config); -bool db_init(const std::string& config_path, const std::string& tech_lef_path, const std::vector& lef_paths, - const std::string& def_path, const std::string& verilog_path, const std::string& output_path, const std::string& feature_path, - const std::vector& lib_paths, const std::string& sdc_path); +bool db_init(const std::optional& config_path, const std::optional& tech_lef_path, + const std::vector& lef_paths, const std::optional& def_path, + const std::optional& verilog_path, const std::optional& output_path, + const std::optional& feature_path, const std::vector& lib_paths, + const std::optional& sdc_path); } // namespace python_interface \ No newline at end of file diff --git a/src/interface/python/py_config/py_register_config.h b/src/interface/python/py_config/py_register_config.h index c041da6f92..d5402f5d5c 100644 --- a/src/interface/python/py_config/py_register_config.h +++ b/src/interface/python/py_config/py_register_config.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include "py_config.h" namespace python_interface { namespace py = pybind11; @@ -25,15 +26,15 @@ void register_config(pybind11::module& m){ m.def("flow_init", flow_init, py::arg("flow_config")); m.def("db_init", db_init, - py::arg("config_path") = "", - py::arg("tech_lef_path") = "", - py::arg("lef_paths") = std::vector {}, - py::arg("def_path") = "", - py::arg("verilog_path") = "", - py::arg("output_path") = "", - py::arg("feature_path") = "", - py::arg("lib_paths") = std::vector{}, - py::arg("sdc_path") = "" + py::arg("config_path") = py::none(), + py::arg("tech_lef_path") = py::none(), + py::arg("lef_paths") = std::vector{}, + py::arg("def_path") = py::none(), + py::arg("verilog_path") = py::none(), + py::arg("output_path") = py::none(), + py::arg("feature_path") = py::none(), + py::arg("lib_paths") = std::vector{}, + py::arg("sdc_path") = py::none() ); } } // namespace python_interface \ No newline at end of file From 607687d44362f088eb277c2e6f9020e3e91d55b7 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 14:23:25 +0800 Subject: [PATCH 07/16] feat: accept os.PathLike in py_eval path bindings --- scripts/binding_census/manifest.json | 22 ++++----- src/interface/python/py_eval/py_eval.cpp | 10 ++-- src/interface/python/py_eval/py_eval.h | 11 +++-- .../python/py_eval/py_register_eval.h | 47 ++++++++++++------- 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json index b148f885c6..f44bcf96e1 100644 --- a/scripts/binding_census/manifest.json +++ b/scripts/binding_census/manifest.json @@ -176,7 +176,7 @@ "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", "cpp_target": "", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 49, + "line": 54, "module": "py_eval", "new_default": "py::none()", "new_type": "std::optional", @@ -193,7 +193,7 @@ "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", "cpp_target": "", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 76, + "line": 86, "module": "py_eval", "new_default": "py::none()", "new_type": "std::optional", @@ -210,7 +210,7 @@ "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", "cpp_target": "eval_cell_hierarchy", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 170, + "line": 181, "module": "py_eval", "new_default": null, "new_type": "std::filesystem::path", @@ -227,7 +227,7 @@ "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", "cpp_target": "eval_macro_connection", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 172, + "line": 183, "module": "py_eval", "new_default": null, "new_type": "std::filesystem::path", @@ -244,7 +244,7 @@ "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", "cpp_target": "eval_macro_hierarchy", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 171, + "line": 182, "module": "py_eval", "new_default": null, "new_type": "std::filesystem::path", @@ -261,7 +261,7 @@ "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", "cpp_target": "eval_macro_io_pin_connection", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 174, + "line": 185, "module": "py_eval", "new_default": null, "new_type": "std::filesystem::path", @@ -278,7 +278,7 @@ "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", "cpp_target": "eval_macro_pin_connection", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 173, + "line": 184, "module": "py_eval", "new_default": null, "new_type": "std::filesystem::path", @@ -295,7 +295,7 @@ "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", "cpp_target": "", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 71, + "line": 80, "module": "py_eval", "new_default": "py::none()", "new_type": "std::optional", @@ -312,7 +312,7 @@ "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", "cpp_target": "", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 59, + "line": 66, "module": "py_eval", "new_default": "py::none()", "new_type": "std::optional", @@ -329,7 +329,7 @@ "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", "cpp_target": "", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 54, + "line": 60, "module": "py_eval", "new_default": "py::none()", "new_type": "std::optional", @@ -346,7 +346,7 @@ "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", "cpp_target": "", "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 66, + "line": 74, "module": "py_eval", "new_default": "py::none()", "new_type": "std::optional", diff --git a/src/interface/python/py_eval/py_eval.cpp b/src/interface/python/py_eval/py_eval.cpp index 74bfb98f09..23a95241a1 100644 --- a/src/interface/python/py_eval/py_eval.cpp +++ b/src/interface/python/py_eval/py_eval.cpp @@ -178,23 +178,23 @@ void eval_macro_channel(float die_size_ratio) { } -void eval_cell_hierarchy(const std::string& plot_path, int level, int forward) +void eval_cell_hierarchy(const std::filesystem::path& plot_path, int level, int forward) { } -void eval_macro_hierarchy(const std::string& plot_path, int level, int forward) +void eval_macro_hierarchy(const std::filesystem::path& plot_path, int level, int forward) { } -void eval_macro_connection(const std::string& plot_path, int level, int forward) +void eval_macro_connection(const std::filesystem::path& plot_path, int level, int forward) { } -void eval_macro_pin_connection(const std::string& plot_path, int level, int forward) +void eval_macro_pin_connection(const std::filesystem::path& plot_path, int level, int forward) { } -void eval_macro_io_pin_connection(const std::string& plot_path, int level, int forward) +void eval_macro_io_pin_connection(const std::filesystem::path& plot_path, int level, int forward) { } diff --git a/src/interface/python/py_eval/py_eval.h b/src/interface/python/py_eval/py_eval.h index c4933e428b..cc6cc63ab9 100644 --- a/src/interface/python/py_eval/py_eval.h +++ b/src/interface/python/py_eval/py_eval.h @@ -16,6 +16,7 @@ // *************************************************************************************** #pragma once +#include #include #include @@ -50,11 +51,11 @@ ieval::TimingSummary timing_power_egr(); void eval_macro_margin(); void eval_macro_channel(float die_size_ratio = 0.5); void eval_continuous_white_space(); -void eval_cell_hierarchy(const std::string& plot_path, int level = 1, int forward = 1); -void eval_macro_hierarchy(const std::string& plot_path, int level = 1, int forward = 1); -void eval_macro_connection(const std::string& plot_path, int level = 1, int forward = 1); -void eval_macro_pin_connection(const std::string& plot_path, int level = 1, int forward = 1); -void eval_macro_io_pin_connection(const std::string& plot_path, int level = 1, int forward = 1); +void eval_cell_hierarchy(const std::filesystem::path& plot_path, int level = 1, int forward = 1); +void eval_macro_hierarchy(const std::filesystem::path& plot_path, int level = 1, int forward = 1); +void eval_macro_connection(const std::filesystem::path& plot_path, int level = 1, int forward = 1); +void eval_macro_pin_connection(const std::filesystem::path& plot_path, int level = 1, int forward = 1); +void eval_macro_io_pin_connection(const std::filesystem::path& plot_path, int level = 1, int forward = 1); std::vector eval_overflow(); diff --git a/src/interface/python/py_eval/py_register_eval.h b/src/interface/python/py_eval/py_register_eval.h index 3173d41ff1..008ac86a91 100644 --- a/src/interface/python/py_eval/py_register_eval.h +++ b/src/interface/python/py_eval/py_register_eval.h @@ -17,7 +17,12 @@ #pragma once #include #include +#include +#include +#include + +#include "../py_path_utils.h" #include "py_eval.h" namespace python_interface { @@ -46,37 +51,43 @@ void register_eval(py::module& m) // density evaluation functions - m.def("cell_density", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::string& save_path = "") -> py::tuple { - auto [max_density, avg_density] = cell_density(bin_cnt_x, bin_cnt_y, save_path); + m.def("cell_density", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::optional& save_path = std::nullopt) -> py::tuple { + const std::string save_path_ = path_or_empty(save_path); + auto [max_density, avg_density] = cell_density(bin_cnt_x, bin_cnt_y, save_path_); return py::make_tuple(max_density, avg_density); - }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = ""); + }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = py::none()); - m.def("pin_density", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::string& save_path = "") -> py::tuple { - auto [max_density, avg_density] = pin_density(bin_cnt_x, bin_cnt_y, save_path); + m.def("pin_density", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::optional& save_path = std::nullopt) -> py::tuple { + const std::string save_path_ = path_or_empty(save_path); + auto [max_density, avg_density] = pin_density(bin_cnt_x, bin_cnt_y, save_path_); return py::make_tuple(max_density, avg_density); - }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = ""); + }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = py::none()); - m.def("net_density", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::string& save_path = "") -> py::tuple { - auto [max_density, avg_density] = net_density(bin_cnt_x, bin_cnt_y, save_path); + m.def("net_density", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::optional& save_path = std::nullopt) -> py::tuple { + const std::string save_path_ = path_or_empty(save_path); + auto [max_density, avg_density] = net_density(bin_cnt_x, bin_cnt_y, save_path_); return py::make_tuple(max_density, avg_density); - }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = ""); + }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = py::none()); // congestion evalation - m.def("rudy_congestion", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::string& save_path = "") -> py::tuple { - auto [max_congestion, total_congestion] = rudy_congestion(bin_cnt_x, bin_cnt_y, save_path); + m.def("rudy_congestion", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::optional& save_path = std::nullopt) -> py::tuple { + const std::string save_path_ = path_or_empty(save_path); + auto [max_congestion, total_congestion] = rudy_congestion(bin_cnt_x, bin_cnt_y, save_path_); return py::make_tuple(max_congestion, total_congestion); - }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = ""); + }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = py::none()); - m.def("lut_rudy_congestion", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::string& save_path = "") -> py::tuple { - auto [max_congestion, total_congestion] = lut_rudy_congestion(bin_cnt_x, bin_cnt_y, save_path); + m.def("lut_rudy_congestion", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::optional& save_path = std::nullopt) -> py::tuple { + const std::string save_path_ = path_or_empty(save_path); + auto [max_congestion, total_congestion] = lut_rudy_congestion(bin_cnt_x, bin_cnt_y, save_path_); return py::make_tuple(max_congestion, total_congestion); - }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = ""); + }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = py::none()); - m.def("egr_congestion", [](const std::string& save_path = "") -> py::tuple { - auto [max_congestion, total_congestion] = egr_congestion(save_path); + m.def("egr_congestion", [](const std::optional& save_path = std::nullopt) -> py::tuple { + const std::string save_path_ = path_or_empty(save_path); + auto [max_congestion, total_congestion] = egr_congestion(save_path_); return py::make_tuple(max_congestion, total_congestion); - }, py::arg("save_path") = ""); + }, py::arg("save_path") = py::none()); // timing and power evaluation From a91c061ac6a7ffb1f6b033af102d4726976dcb08 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 14:49:42 +0800 Subject: [PATCH 08/16] fix: canonicalize converted params in empty eval stubs --- src/interface/python/py_eval/py_eval.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/interface/python/py_eval/py_eval.cpp b/src/interface/python/py_eval/py_eval.cpp index 23a95241a1..23fd81fe68 100644 --- a/src/interface/python/py_eval/py_eval.cpp +++ b/src/interface/python/py_eval/py_eval.cpp @@ -180,22 +180,27 @@ void eval_macro_channel(float die_size_ratio) void eval_cell_hierarchy(const std::filesystem::path& plot_path, int level, int forward) { + [[maybe_unused]] const std::string plot_path_ = plot_path.string(); } void eval_macro_hierarchy(const std::filesystem::path& plot_path, int level, int forward) { + [[maybe_unused]] const std::string plot_path_ = plot_path.string(); } void eval_macro_connection(const std::filesystem::path& plot_path, int level, int forward) { + [[maybe_unused]] const std::string plot_path_ = plot_path.string(); } void eval_macro_pin_connection(const std::filesystem::path& plot_path, int level, int forward) { + [[maybe_unused]] const std::string plot_path_ = plot_path.string(); } void eval_macro_io_pin_connection(const std::filesystem::path& plot_path, int level, int forward) { + [[maybe_unused]] const std::string plot_path_ = plot_path.string(); } From e91a5ed1ee4d07d52d72da1b642c798fbcefeb1a Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 14:54:46 +0800 Subject: [PATCH 09/16] feat: accept os.PathLike in py_feature and py_icts path bindings --- scripts/binding_census/manifest.json | 36 +++++------ .../python/py_feature/py_feature.cpp | 61 +++++++++++-------- src/interface/python/py_feature/py_feature.h | 25 ++++---- .../python/py_feature/py_register_feature.h | 1 + src/interface/python/py_icts/py_icts.cpp | 11 ++-- src/interface/python/py_icts/py_icts.h | 5 +- .../python/py_icts/py_register_icts.h | 1 + 7 files changed, 80 insertions(+), 60 deletions(-) diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json index f44bcf96e1..1f1848dd3a 100644 --- a/scripts/binding_census/manifest.json +++ b/scripts/binding_census/manifest.json @@ -363,7 +363,7 @@ "classification_rationale": "congestion map output directory (featureInst->save_cong_map target)", "cpp_target": "feature_cong_map", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 39, + "line": 40, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -380,7 +380,7 @@ "classification_rationale": "flow step selector string forwarded to save_cong_map, not a filesystem path", "cpp_target": "feature_cong_map", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 39, + "line": 40, "module": "py_feature", "new_default": null, "new_type": "const std::string&", @@ -397,7 +397,7 @@ "classification_rationale": "CTS eval JSON output file", "cpp_target": "feature_cts_eval", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 30, + "line": 31, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -414,7 +414,7 @@ "classification_rationale": "eval map output file (featureInst->save_eval_map target)", "cpp_target": "feature_eval_map", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 32, + "line": 33, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -431,7 +431,7 @@ "classification_rationale": "eval summary output file", "cpp_target": "feature_eval_summary", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 36, + "line": 37, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -448,7 +448,7 @@ "classification_rationale": "DRC report input file consumed by the macro DRC feature builder", "cpp_target": "feature_macro_drc", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 35, + "line": 36, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -465,7 +465,7 @@ "classification_rationale": "macro DRC feature output file", "cpp_target": "feature_macro_drc", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 35, + "line": 36, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -482,7 +482,7 @@ "classification_rationale": "net eval output file", "cpp_target": "feature_net_eval", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 38, + "line": 39, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -499,7 +499,7 @@ "classification_rationale": "placement eval JSON output file", "cpp_target": "feature_pl_eval", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 29, + "line": 30, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -516,7 +516,7 @@ "classification_rationale": "route feature output file", "cpp_target": "feature_route", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 33, + "line": 34, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -533,7 +533,7 @@ "classification_rationale": "route feature input file read back by the feature API", "cpp_target": "feature_route_read", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 34, + "line": 35, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -550,7 +550,7 @@ "classification_rationale": "feature summary output file (featureInst->save_summary target)", "cpp_target": "feature_summary", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 27, + "line": 28, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -567,7 +567,7 @@ "classification_rationale": "timing eval summary output file", "cpp_target": "feature_timing_eval_summary", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 37, + "line": 38, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -584,7 +584,7 @@ "classification_rationale": "tool feature output file (featureInst->save_tools target)", "cpp_target": "feature_tool", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 28, + "line": 29, "module": "py_feature", "new_default": null, "new_type": "std::filesystem::path", @@ -601,7 +601,7 @@ "classification_rationale": "flow step selector string forwarded to save_tools, not a filesystem path", "cpp_target": "feature_tool", "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 28, + "line": 29, "module": "py_feature", "new_default": null, "new_type": "const std::string&", @@ -618,7 +618,7 @@ "classification_rationale": "CTS report output file", "cpp_target": "CtsReport", "file": "src/interface/python/py_icts/py_register_icts.h", - "line": 28, + "line": 29, "module": "py_icts", "new_default": null, "new_type": "std::filesystem::path", @@ -635,7 +635,7 @@ "classification_rationale": "CTS config JSON file; ECCToolsModule.run_cts passes path_text(config)", "cpp_target": "CtsAutoRun", "file": "src/interface/python/py_icts/py_register_icts.h", - "line": 27, + "line": 28, "module": "py_icts", "new_default": null, "new_type": "std::filesystem::path", @@ -652,7 +652,7 @@ "classification_rationale": "CTS working directory; ECCToolsModule.run_cts passes path_text(output)", "cpp_target": "CtsAutoRun", "file": "src/interface/python/py_icts/py_register_icts.h", - "line": 27, + "line": 28, "module": "py_icts", "new_default": null, "new_type": "std::filesystem::path", diff --git a/src/interface/python/py_feature/py_feature.cpp b/src/interface/python/py_feature/py_feature.cpp index ac9391cfa4..a78b81d9ba 100644 --- a/src/interface/python/py_feature/py_feature.cpp +++ b/src/interface/python/py_feature/py_feature.cpp @@ -20,44 +20,53 @@ namespace python_interface { -bool feature_summary(const std::string& path) +bool feature_summary(const std::filesystem::path& path) { - return featureInst->save_summary(path); + const std::string path_ = path.string(); + return featureInst->save_summary(path_); } -bool feature_tool(const std::string& path, const std::string& step) +bool feature_tool(const std::filesystem::path& path, const std::string& step) { - return featureInst->save_tools(path, step); + const std::string path_ = path.string(); + return featureInst->save_tools(path_, step); } -bool feature_eval_map(const std::string& path, const int& bin_cnt_x, const int& bin_cnt_y) +bool feature_eval_map(const std::filesystem::path& path, const int& bin_cnt_x, const int& bin_cnt_y) { - return featureInst->save_eval_map(path, bin_cnt_x, bin_cnt_y); + const std::string path_ = path.string(); + return featureInst->save_eval_map(path_, bin_cnt_x, bin_cnt_y); } -bool feature_net_eval(const std::string& path) +bool feature_net_eval(const std::filesystem::path& path) { - return featureInst->save_net_eval(path); + const std::string path_ = path.string(); + return featureInst->save_net_eval(path_); } -bool feature_route(const std::string& path) +bool feature_route(const std::filesystem::path& path) { - return featureInst->save_route_data(path); + const std::string path_ = path.string(); + return featureInst->save_route_data(path_); } -bool feature_route_read(const std::string& path) +bool feature_route_read(const std::filesystem::path& path) { - return featureInst->read_route_data(path); + const std::string path_ = path.string(); + return featureInst->read_route_data(path_); } -bool feature_macro_drc(const std::string& path, const std::string& drc_path) +bool feature_macro_drc(const std::filesystem::path& path, const std::filesystem::path& drc_path) { - return featureInst->feature_macro_drc(path, drc_path); + const std::string path_ = path.string(); + const std::string drc_path_ = drc_path.string(); + return featureInst->feature_macro_drc(path_, drc_path_); } -bool feature_eval_summary(const std::string& path, int32_t grid_size) +bool feature_eval_summary(const std::filesystem::path& path, int32_t grid_size) { - return featureInst->save_eval_summary(path, grid_size); + const std::string path_ = path.string(); + return featureInst->save_eval_summary(path_, grid_size); } bool feature_eval_union(const std::string& jsonl_path, const std::string& csv_path, int32_t grid_size) @@ -65,24 +74,28 @@ bool feature_eval_union(const std::string& jsonl_path, const std::string& csv_pa return featureInst->save_eval_union(jsonl_path, csv_path, grid_size); } -bool feature_pl_eval(const std::string& json_path, int32_t grid_size) +bool feature_pl_eval(const std::filesystem::path& json_path, int32_t grid_size) { - return featureInst->save_pl_eval(json_path, grid_size); + const std::string json_path_ = json_path.string(); + return featureInst->save_pl_eval(json_path_, grid_size); } -bool feature_cts_eval(const std::string& json_path, int32_t grid_size) +bool feature_cts_eval(const std::filesystem::path& json_path, int32_t grid_size) { - return featureInst->save_cts_eval(json_path, grid_size); + const std::string json_path_ = json_path.string(); + return featureInst->save_cts_eval(json_path_, grid_size); } -bool feature_timing_eval_summary(const std::string& path) +bool feature_timing_eval_summary(const std::filesystem::path& path) { - return featureInst->save_timing_eval_summary(path); + const std::string path_ = path.string(); + return featureInst->save_timing_eval_summary(path_); } -bool feature_cong_map(const std::string& step, const std::string& dir) +bool feature_cong_map(const std::string& step, const std::filesystem::path& dir) { - return featureInst->save_cong_map(step, dir); + const std::string dir_ = dir.string(); + return featureInst->save_cong_map(step, dir_); } } // namespace python_interface \ No newline at end of file diff --git a/src/interface/python/py_feature/py_feature.h b/src/interface/python/py_feature/py_feature.h index e7afffbc95..021ec4bf85 100644 --- a/src/interface/python/py_feature/py_feature.h +++ b/src/interface/python/py_feature/py_feature.h @@ -16,22 +16,23 @@ // *************************************************************************************** #pragma once +#include #include namespace python_interface { -bool feature_summary(const std::string& path); -bool feature_tool(const std::string& path, const std::string& step); -bool feature_pl_eval(const std::string& json_path, int32_t grid_size = 1); -bool feature_cts_eval(const std::string& json_path, int32_t grid_size = 1); +bool feature_summary(const std::filesystem::path& path); +bool feature_tool(const std::filesystem::path& path, const std::string& step); +bool feature_pl_eval(const std::filesystem::path& json_path, int32_t grid_size = 1); +bool feature_cts_eval(const std::filesystem::path& json_path, int32_t grid_size = 1); -bool feature_eval_map(const std::string& path, const int& bin_cnt_x, const int& bin_cnt_y); -bool feature_route(const std::string& path); -bool feature_route_read(const std::string& path); -bool feature_macro_drc(const std::string& path, const std::string& drc_path); -bool feature_eval_summary(const std::string& path, int32_t grid_size); -bool feature_timing_eval_summary(const std::string& path); -bool feature_net_eval(const std::string& path); -bool feature_cong_map(const std::string& step, const std::string& dir); +bool feature_eval_map(const std::filesystem::path& path, const int& bin_cnt_x, const int& bin_cnt_y); +bool feature_route(const std::filesystem::path& path); +bool feature_route_read(const std::filesystem::path& path); +bool feature_macro_drc(const std::filesystem::path& path, const std::filesystem::path& drc_path); +bool feature_eval_summary(const std::filesystem::path& path, int32_t grid_size); +bool feature_timing_eval_summary(const std::filesystem::path& path); +bool feature_net_eval(const std::filesystem::path& path); +bool feature_cong_map(const std::string& step, const std::filesystem::path& dir); } // namespace python_interface \ No newline at end of file diff --git a/src/interface/python/py_feature/py_register_feature.h b/src/interface/python/py_feature/py_register_feature.h index 1a2c06df02..8ff6d9c2c6 100644 --- a/src/interface/python/py_feature/py_register_feature.h +++ b/src/interface/python/py_feature/py_register_feature.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include "py_feature.h" diff --git a/src/interface/python/py_icts/py_icts.cpp b/src/interface/python/py_icts/py_icts.cpp index e6c879b13e..e5d989259f 100644 --- a/src/interface/python/py_icts/py_icts.cpp +++ b/src/interface/python/py_icts/py_icts.cpp @@ -19,15 +19,18 @@ #include namespace python_interface { -bool CtsAutoRun(const std::string& cts_config, const std::string& cts_work_dir) +bool CtsAutoRun(const std::filesystem::path& cts_config, const std::filesystem::path& cts_work_dir) { - bool cts_run_ok = iplf::tmInst->autoRunCTS(cts_config, cts_work_dir); + const std::string cts_config_ = cts_config.string(); + const std::string cts_work_dir_ = cts_work_dir.string(); + bool cts_run_ok = iplf::tmInst->autoRunCTS(cts_config_, cts_work_dir_); return cts_run_ok; } -bool CtsReport(const std::string& path) +bool CtsReport(const std::filesystem::path& path) { - return iplf::tmInst->reportCTS(path); + const std::string path_ = path.string(); + return iplf::tmInst->reportCTS(path_); } } // namespace python_interface diff --git a/src/interface/python/py_icts/py_icts.h b/src/interface/python/py_icts/py_icts.h index 5a9f406adb..9818dc9910 100644 --- a/src/interface/python/py_icts/py_icts.h +++ b/src/interface/python/py_icts/py_icts.h @@ -15,9 +15,10 @@ // See the Mulan PSL v2 for more details. // *************************************************************************************** #pragma once +#include #include namespace python_interface { -bool CtsAutoRun(const std::string& cts_config, const std::string& cts_work_dir); -bool CtsReport(const std::string& path); +bool CtsAutoRun(const std::filesystem::path& cts_config, const std::filesystem::path& cts_work_dir); +bool CtsReport(const std::filesystem::path& path); } // namespace python_interface diff --git a/src/interface/python/py_icts/py_register_icts.h b/src/interface/python/py_icts/py_register_icts.h index c5d7a58272..c3ececd077 100644 --- a/src/interface/python/py_icts/py_register_icts.h +++ b/src/interface/python/py_icts/py_register_icts.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include "py_icts.h" From c0d88ae7bf8a5627cb0a33a631962e03eb8a6ea3 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 15:20:31 +0800 Subject: [PATCH 10/16] feat: accept os.PathLike in py_idrc, py_izh and py_report path bindings --- scripts/binding_census/manifest.json | 30 ++++++++--------- src/interface/python/py_idrc/py_idrc.cpp | 19 +++++++---- src/interface/python/py_idrc/py_idrc.h | 8 +++-- .../python/py_idrc/py_register_idrc.h | 7 ++-- src/interface/python/py_izh/py_izh.cpp | 11 ++++--- src/interface/python/py_izh/py_izh.h | 6 ++-- src/interface/python/py_izh/py_register_izh.h | 5 +-- .../python/py_report/py_register_report.h | 11 ++++--- src/interface/python/py_report/py_report.cpp | 32 ++++++++++++------- src/interface/python/py_report/py_report.h | 14 ++++---- 10 files changed, 84 insertions(+), 59 deletions(-) diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json index 1f1848dd3a..ff9f6e3335 100644 --- a/scripts/binding_census/manifest.json +++ b/scripts/binding_census/manifest.json @@ -1315,7 +1315,7 @@ "classification_rationale": "DRC working directory; ECCToolsModule.init_drc passes path_text(output_dir)", "cpp_target": "init_drc", "file": "src/interface/python/py_idrc/py_register_idrc.h", - "line": 28, + "line": 29, "module": "py_idrc", "new_default": "py::none()", "new_type": "std::optional", @@ -1332,7 +1332,7 @@ "classification_rationale": "DRC config JSON file; ECCToolsModule.run_drc passes path_text(config)", "cpp_target": "run_drc", "file": "src/interface/python/py_idrc/py_register_idrc.h", - "line": 29, + "line": 30, "module": "py_idrc", "new_default": "py::none()", "new_type": "std::optional", @@ -1349,7 +1349,7 @@ "classification_rationale": "DRC report output file; ECCToolsModule.run_drc passes path_text(report_path)", "cpp_target": "run_drc", "file": "src/interface/python/py_idrc/py_register_idrc.h", - "line": 29, + "line": 30, "module": "py_idrc", "new_default": "py::none()", "new_type": "std::optional", @@ -1366,7 +1366,7 @@ "classification_rationale": "DRC feature output file; ECCToolsModule.save_drc passes path_text(feature_path)", "cpp_target": "save_drc", "file": "src/interface/python/py_idrc/py_register_idrc.h", - "line": 30, + "line": 31, "module": "py_idrc", "new_default": "py::none()", "new_type": "std::optional", @@ -2437,7 +2437,7 @@ "classification_rationale": "fanout-fix config JSON file; ECCToolsModule.run_net_opt passes path_text(config)", "cpp_target": "fix_fanout", "file": "src/interface/python/py_izh/py_register_izh.h", - "line": 28, + "line": 29, "module": "py_izh", "new_default": "py::none()", "new_type": "std::optional", @@ -2454,7 +2454,7 @@ "classification_rationale": "filler config JSON file; ECCToolsModule.run_filler passes path_text(config)", "cpp_target": "insert_filler", "file": "src/interface/python/py_izh/py_register_izh.h", - "line": 29, + "line": 30, "module": "py_izh", "new_default": "py::none()", "new_type": "std::optional", @@ -2471,7 +2471,7 @@ "classification_rationale": "congestion report output file; empty string reports to stdout only", "cpp_target": "reportCong", "file": "src/interface/python/py_report/py_register_report.h", - "line": 29, + "line": 30, "module": "py_report", "new_default": "py::none()", "new_type": "std::optional", @@ -2488,7 +2488,7 @@ "classification_rationale": "dangling-net report output file; empty string reports to stdout only", "cpp_target": "reportDanglingNet", "file": "src/interface/python/py_report/py_register_report.h", - "line": 30, + "line": 31, "module": "py_report", "new_default": "py::none()", "new_type": "std::optional", @@ -2505,7 +2505,7 @@ "classification_rationale": "database summary report output file; empty string reports to stdout only", "cpp_target": "reportDbSummary", "file": "src/interface/python/py_report/py_register_report.h", - "line": 28, + "line": 29, "module": "py_report", "new_default": "py::none()", "new_type": "std::optional", @@ -2522,7 +2522,7 @@ "classification_rationale": "DRC report output path by API shape; the currently bound one-argument ReportManager::reportDRC overload is a stub with its write lines commented out (report_manager.cpp:186), pending re-enable", "cpp_target": "reportDRC", "file": "src/interface/python/py_report/py_register_report.h", - "line": 34, + "line": 35, "module": "py_report", "new_default": null, "new_type": "std::filesystem::path", @@ -2539,7 +2539,7 @@ "classification_rationale": "instance name prefixes to bucket, not filesystem paths", "cpp_target": "reportPlaceDistribution", "file": "src/interface/python/py_report/py_register_report.h", - "line": 32, + "line": 33, "module": "py_report", "new_default": "std::vector{}", "new_type": "const std::vector&", @@ -2556,7 +2556,7 @@ "classification_rationale": "instance name prefix to report on, not a filesystem path", "cpp_target": "reportPrefixedInst", "file": "src/interface/python/py_report/py_register_report.h", - "line": 33, + "line": 34, "module": "py_report", "new_default": null, "new_type": "const std::string&", @@ -2573,7 +2573,7 @@ "classification_rationale": "net name filter for the route report, not a filesystem path", "cpp_target": "reportRoute", "file": "src/interface/python/py_report/py_register_report.h", - "line": 31, + "line": 32, "module": "py_report", "new_default": "\"\"", "new_type": "const std::string&", @@ -2590,7 +2590,7 @@ "classification_rationale": "route report output file; empty string reports to stdout only", "cpp_target": "reportRoute", "file": "src/interface/python/py_report/py_register_report.h", - "line": 31, + "line": 32, "module": "py_report", "new_default": "py::none()", "new_type": "std::optional", @@ -2607,7 +2607,7 @@ "classification_rationale": "wirelength report output file; empty string reports to stdout only", "cpp_target": "reportWireLength", "file": "src/interface/python/py_report/py_register_report.h", - "line": 27, + "line": 28, "module": "py_report", "new_default": "py::none()", "new_type": "std::optional", diff --git a/src/interface/python/py_idrc/py_idrc.cpp b/src/interface/python/py_idrc/py_idrc.cpp index 4a60d608bd..2f7f463963 100644 --- a/src/interface/python/py_idrc/py_idrc.cpp +++ b/src/interface/python/py_idrc/py_idrc.cpp @@ -18,15 +18,17 @@ #include +#include "../py_path_utils.h" #include "DRCInterface.hpp" namespace python_interface { -bool init_drc(const std::string& temp_directory_path, const int& thread_number) +bool init_drc(const std::optional& temp_directory_path, const int& thread_number) { + const std::string temp_directory_path_ = path_or_empty(temp_directory_path); std::map config_map; - if (temp_directory_path != "") { - config_map.insert(std::make_pair("-temp_directory_path", temp_directory_path)); + if (temp_directory_path_ != "") { + config_map.insert(std::make_pair("-temp_directory_path", temp_directory_path_)); } config_map.insert(std::make_pair("-thread_number", thread_number)); @@ -35,14 +37,17 @@ bool init_drc(const std::string& temp_directory_path, const int& thread_number) return true; } -bool run_drc(const std::string& config, const std::string& report) +bool run_drc(const std::optional& config, const std::optional& report) { - return iplf::tmInst->autoRunDRC(config, report, true); + const std::string config_ = path_or_empty(config); + const std::string report_ = path_or_empty(report); + return iplf::tmInst->autoRunDRC(config_, report_, true); } -bool save_drc(const std::string& path) +bool save_drc(const std::optional& path) { - return iplf::tmInst->saveDrcDetailToFile(path); + const std::string path_ = path_or_empty(path); + return iplf::tmInst->saveDrcDetailToFile(path_); } } // namespace python_interface \ No newline at end of file diff --git a/src/interface/python/py_idrc/py_idrc.h b/src/interface/python/py_idrc/py_idrc.h index ea52fb8754..687617fbbd 100644 --- a/src/interface/python/py_idrc/py_idrc.h +++ b/src/interface/python/py_idrc/py_idrc.h @@ -16,11 +16,13 @@ // *************************************************************************************** #pragma once +#include +#include #include namespace python_interface { -bool init_drc(const std::string& temp_directory_path, const int& thread_number); -bool run_drc(const std::string& config, const std::string& report); -bool save_drc(const std::string& path); +bool init_drc(const std::optional& temp_directory_path, const int& thread_number); +bool run_drc(const std::optional& config, const std::optional& report); +bool save_drc(const std::optional& path); } // namespace python_interface \ No newline at end of file diff --git a/src/interface/python/py_idrc/py_register_idrc.h b/src/interface/python/py_idrc/py_register_idrc.h index 2e061497ed..0dabd6d78d 100644 --- a/src/interface/python/py_idrc/py_register_idrc.h +++ b/src/interface/python/py_idrc/py_register_idrc.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include "py_idrc.h" @@ -25,9 +26,9 @@ namespace py = pybind11; void register_idrc(py::module& m) { - m.def("init_drc", init_drc, py::arg("temp_directory_path") = "", py::arg("thread_number") = 128); - m.def("run_drc", run_drc, py::arg("config") = "", py::arg("report") = ""); - m.def("save_drc", save_drc, py::arg("path") = ""); + m.def("init_drc", init_drc, py::arg("temp_directory_path") = py::none(), py::arg("thread_number") = 128); + m.def("run_drc", run_drc, py::arg("config") = py::none(), py::arg("report") = py::none()); + m.def("save_drc", save_drc, py::arg("path") = py::none()); } } // namespace python_interface \ No newline at end of file diff --git a/src/interface/python/py_izh/py_izh.cpp b/src/interface/python/py_izh/py_izh.cpp index d4da49aec7..391af61429 100644 --- a/src/interface/python/py_izh/py_izh.cpp +++ b/src/interface/python/py_izh/py_izh.cpp @@ -20,18 +20,20 @@ #include #include +#include "../py_path_utils.h" #include "ZHInterface.hpp" namespace python_interface { bool initZHConfigMapByJSON(const std::string& config, std::map& config_map); -bool fix_fanout(const std::string& config) +bool fix_fanout(const std::optional& config) { + const std::string config_ = path_or_empty(config); std::map config_map; bool pass = false; - pass = !pass ? initZHConfigMapByJSON(config, config_map) : pass; + pass = !pass ? initZHConfigMapByJSON(config_, config_map) : pass; if (!pass) { return false; } @@ -40,12 +42,13 @@ bool fix_fanout(const std::string& config) return true; } -bool insert_filler(const std::string& config) +bool insert_filler(const std::optional& config) { + const std::string config_ = path_or_empty(config); std::map config_map; bool pass = false; - pass = !pass ? initZHConfigMapByJSON(config, config_map) : pass; + pass = !pass ? initZHConfigMapByJSON(config_, config_map) : pass; if (!pass) { return false; } diff --git a/src/interface/python/py_izh/py_izh.h b/src/interface/python/py_izh/py_izh.h index 553ef8cc63..3288cb2ca6 100644 --- a/src/interface/python/py_izh/py_izh.h +++ b/src/interface/python/py_izh/py_izh.h @@ -16,11 +16,13 @@ // *************************************************************************************** #pragma once +#include +#include #include namespace python_interface { -bool fix_fanout(const std::string& config); -bool insert_filler(const std::string& config); +bool fix_fanout(const std::optional& config); +bool insert_filler(const std::optional& config); } // namespace python_interface diff --git a/src/interface/python/py_izh/py_register_izh.h b/src/interface/python/py_izh/py_register_izh.h index d1961a89d4..45605f141d 100644 --- a/src/interface/python/py_izh/py_register_izh.h +++ b/src/interface/python/py_izh/py_register_izh.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include "py_izh.h" @@ -25,8 +26,8 @@ namespace py = pybind11; void register_izh(py::module& m) { - m.def("fix_fanout", fix_fanout, py::arg("config") = ""); - m.def("insert_filler", insert_filler, py::arg("config") = ""); + m.def("fix_fanout", fix_fanout, py::arg("config") = py::none()); + m.def("insert_filler", insert_filler, py::arg("config") = py::none()); } } // namespace python_interface diff --git a/src/interface/python/py_report/py_register_report.h b/src/interface/python/py_report/py_register_report.h index 8c85f5cc4d..411ab23af1 100644 --- a/src/interface/python/py_report/py_register_report.h +++ b/src/interface/python/py_report/py_register_report.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include "py_report.h" @@ -24,11 +25,11 @@ namespace python_interface { namespace py = pybind11; void register_report(py::module& m) { - m.def("report_wirelength", reportWireLength, py::arg("path") = ""); - m.def("report_db", reportDbSummary, py::arg("path") = ""); - m.def("report_congestion", reportCong, py::arg("path") = ""); - m.def("report_dangling_net", reportDanglingNet, py::arg("path") = ""); - m.def("report_route", reportRoute, py::arg("path") = "", py::arg("net") = "", py::arg("summary") = true); + m.def("report_wirelength", reportWireLength, py::arg("path") = py::none()); + m.def("report_db", reportDbSummary, py::arg("path") = py::none()); + m.def("report_congestion", reportCong, py::arg("path") = py::none()); + m.def("report_dangling_net", reportDanglingNet, py::arg("path") = py::none()); + m.def("report_route", reportRoute, py::arg("path") = py::none(), py::arg("net") = "", py::arg("summary") = true); m.def("report_place_distribution", reportPlaceDistribution, py::arg("prefixes") = std::vector{}); m.def("report_prefixed_instance", reportPrefixedInst, py::arg("prefix"), py::arg("level") = 1, py::arg("num_threshold") = 1); m.def("report_drc", reportDRC, py::arg("path")); diff --git a/src/interface/python/py_report/py_report.cpp b/src/interface/python/py_report/py_report.cpp index 6e3a649167..dbf8efa57e 100644 --- a/src/interface/python/py_report/py_report.cpp +++ b/src/interface/python/py_report/py_report.cpp @@ -18,28 +18,35 @@ #include +#include "../py_path_utils.h" + namespace python_interface { -bool reportDbSummary(const std::string& path) +bool reportDbSummary(const std::optional& path) { - return rptInst->reportDBSummary(path); + const std::string path_ = path_or_empty(path); + return rptInst->reportDBSummary(path_); } -bool reportWireLength(const std::string& path) +bool reportWireLength(const std::optional& path) { - return rptInst->reportWL(path); + const std::string path_ = path_or_empty(path); + return rptInst->reportWL(path_); } -bool reportCong(const std::string& path) +bool reportCong(const std::optional& path) { - return rptInst->reportCongestion(path); + const std::string path_ = path_or_empty(path); + return rptInst->reportCongestion(path_); } -bool reportDanglingNet(const std::string& path) +bool reportDanglingNet(const std::optional& path) { - return rptInst->reportDanglingNet(path); + const std::string path_ = path_or_empty(path); + return rptInst->reportDanglingNet(path_); } -bool reportRoute(const std::string& path, const std::string& netname, bool summary) +bool reportRoute(const std::optional& path, const std::string& netname, bool summary) { - return rptInst->reportRoute(path, netname, summary); + const std::string path_ = path_or_empty(path); + return rptInst->reportRoute(path_, netname, summary); } bool reportPlaceDistribution(const std::vector& prefixes) @@ -51,7 +58,8 @@ bool reportPrefixedInst(const std::string& prefix, int level, int num_threshold) return rptInst->reportInstLevel(prefix, level, num_threshold); } -bool reportDRC(const std::string& filename){ - return rptInst->reportDRC(filename); +bool reportDRC(const std::filesystem::path& filename){ + const std::string filename_ = filename.string(); + return rptInst->reportDRC(filename_); } } // namespace python_interface \ No newline at end of file diff --git a/src/interface/python/py_report/py_report.h b/src/interface/python/py_report/py_report.h index 89dca69ef7..a684b81eba 100644 --- a/src/interface/python/py_report/py_report.h +++ b/src/interface/python/py_report/py_report.h @@ -16,18 +16,20 @@ // *************************************************************************************** #pragma once +#include +#include #include #include #include "report_manager.h" namespace python_interface { -bool reportDbSummary(const std::string& path); -bool reportWireLength(const std::string& path); -bool reportCong(const std::string& path); -bool reportDanglingNet(const std::string& path); -bool reportRoute(const std::string& path, const std::string& netname, bool summary); +bool reportDbSummary(const std::optional& path); +bool reportWireLength(const std::optional& path); +bool reportCong(const std::optional& path); +bool reportDanglingNet(const std::optional& path); +bool reportRoute(const std::optional& path, const std::string& netname, bool summary); bool reportPlaceDistribution(const std::vector& prefixes); bool reportPrefixedInst(const std::string& prefix, int level, int num_threshold); -bool reportDRC(const std::string& filename); +bool reportDRC(const std::filesystem::path& filename); } // namespace python_interface \ No newline at end of file From 0d525eb93daee148a670aa1e7193673327df035e Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 15:46:19 +0800 Subject: [PATCH 11/16] feat: accept os.PathLike in py_irt, py_ista and py_ircx path bindings --- scripts/binding_census/manifest.json | 10 +++++----- src/interface/python/py_ircx/py_ircx.cpp | 7 ++++--- src/interface/python/py_ircx/py_ircx.h | 3 ++- src/interface/python/py_ircx/py_register_ircx.h | 1 + src/interface/python/py_irt/py_irt.cpp | 11 +++++++---- src/interface/python/py_irt/py_irt.h | 7 +++++-- src/interface/python/py_irt/py_register_irt.h | 5 +++-- src/interface/python/py_ista/py_ista.cpp | 6 ++++-- src/interface/python/py_ista/py_ista.h | 5 ++++- src/interface/python/py_ista/py_register_ista.h | 3 ++- 10 files changed, 37 insertions(+), 21 deletions(-) diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json index ff9f6e3335..98c7efb15a 100644 --- a/scripts/binding_census/manifest.json +++ b/scripts/binding_census/manifest.json @@ -2352,7 +2352,7 @@ "classification_rationale": "RCX config JSON file; ECCToolsModule.init_rcx passes path_text(config)", "cpp_target": "init_rcx", "file": "src/interface/python/py_ircx/py_register_ircx.h", - "line": 28, + "line": 29, "module": "py_ircx", "new_default": null, "new_type": "std::filesystem::path", @@ -2369,7 +2369,7 @@ "classification_rationale": "PDK identifier string (e.g. \"ics55\") selecting a built-in rule set, already std::optional; not a filesystem path", "cpp_target": "init_rcx", "file": "src/interface/python/py_ircx/py_register_ircx.h", - "line": 28, + "line": 29, "module": "py_ircx", "new_default": "py::none()", "new_type": "const std::optional&", @@ -2386,7 +2386,7 @@ "classification_rationale": "router config JSON file; ECCToolsModule.run_routing passes path_text(config)", "cpp_target": "initRT", "file": "src/interface/python/py_irt/py_register_irt.h", - "line": 28, + "line": 29, "module": "py_irt", "new_default": "py::none()", "new_type": "std::optional", @@ -2403,7 +2403,7 @@ "classification_rationale": "early-router config JSON file; ECCToolsModule.run_ert passes path_text(config)", "cpp_target": "runERT", "file": "src/interface/python/py_irt/py_register_irt.h", - "line": 29, + "line": 30, "module": "py_irt", "new_default": "py::none()", "new_type": "std::optional", @@ -2420,7 +2420,7 @@ "classification_rationale": "STA config JSON file; ECCToolsModule.run_timing passes path_text(config)", "cpp_target": "initSTA", "file": "src/interface/python/py_ista/py_register_ista.h", - "line": 29, + "line": 30, "module": "py_ista", "new_default": "py::none()", "new_type": "std::optional", diff --git a/src/interface/python/py_ircx/py_ircx.cpp b/src/interface/python/py_ircx/py_ircx.cpp index ce10d49874..84c2b20ce6 100644 --- a/src/interface/python/py_ircx/py_ircx.cpp +++ b/src/interface/python/py_ircx/py_ircx.cpp @@ -50,8 +50,9 @@ bool validate_pdk(const std::optional& pdk) } // namespace -bool init_rcx(const std::string& config, const std::optional& pdk) +bool init_rcx(const std::filesystem::path& config, const std::optional& pdk) { + const std::string config_ = config.string(); active_backend = RcxBackend::kUninitialized; if (!validate_pdk(pdk)) { @@ -59,7 +60,7 @@ bool init_rcx(const std::string& config, const std::optional& pdk) } if (is_ics55_pdk(pdk)) { - if (ircx_ics55_init(config.c_str()) != 0) { + if (ircx_ics55_init(config_.c_str()) != 0) { active_backend = RcxBackend::kIcs55; return true; } @@ -67,7 +68,7 @@ bool init_rcx(const std::string& config, const std::optional& pdk) return false; } - if (RCX_API_INST.init(config)) { + if (RCX_API_INST.init(config_)) { active_backend = RcxBackend::kNative; return true; } diff --git a/src/interface/python/py_ircx/py_ircx.h b/src/interface/python/py_ircx/py_ircx.h index bd4802371b..a5313710db 100644 --- a/src/interface/python/py_ircx/py_ircx.h +++ b/src/interface/python/py_ircx/py_ircx.h @@ -16,6 +16,7 @@ // *************************************************************************************** #pragma once +#include #include #include @@ -23,7 +24,7 @@ namespace python_interface { -bool init_rcx(const std::string& config, const std::optional& pdk = std::nullopt); +bool init_rcx(const std::filesystem::path& config, const std::optional& pdk = std::nullopt); bool run_rcx(); bool report_rcx(); diff --git a/src/interface/python/py_ircx/py_register_ircx.h b/src/interface/python/py_ircx/py_register_ircx.h index 8cec8b2532..93cab77aa6 100644 --- a/src/interface/python/py_ircx/py_register_ircx.h +++ b/src/interface/python/py_ircx/py_register_ircx.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include "py_ircx.h" diff --git a/src/interface/python/py_irt/py_irt.cpp b/src/interface/python/py_irt/py_irt.cpp index c9bda9042f..39da69db64 100644 --- a/src/interface/python/py_irt/py_irt.cpp +++ b/src/interface/python/py_irt/py_irt.cpp @@ -20,6 +20,7 @@ #include +#include "../py_path_utils.h" #include "RTInterface.hpp" #include "flow_config.h" namespace python_interface { @@ -32,12 +33,13 @@ bool destroyRT() return true; } -bool runERT(std::string& config, std::map& config_dict) +bool runERT(const std::optional& config, std::map& config_dict) { + const std::string config_ = path_or_empty(config); std::map config_map; bool pass = false; - pass = !pass ? initConfigMapByJSON(config, config_map) : pass; + pass = !pass ? initConfigMapByJSON(config_, config_map) : pass; if (!pass) { return false; } @@ -51,14 +53,15 @@ bool runRT() return true; } -bool initRT(std::string& config, std::map& config_dict) +bool initRT(const std::optional& config, std::map& config_dict) { + const std::string config_ = path_or_empty(config); iplf::flowConfigInst->set_status_stage("iRT - Routing"); std::map config_map; bool pass = false; - pass = !pass ? initConfigMapByJSON(config, config_map) : pass; + pass = !pass ? initConfigMapByJSON(config_, config_map) : pass; if (!pass) { return false; } diff --git a/src/interface/python/py_irt/py_irt.h b/src/interface/python/py_irt/py_irt.h index fb74fff1f6..44622e3096 100644 --- a/src/interface/python/py_irt/py_irt.h +++ b/src/interface/python/py_irt/py_irt.h @@ -18,12 +18,15 @@ #include +#include +#include + namespace python_interface { bool destroyRT(); -bool initRT(std::string& config, std::map& config_dict); +bool initRT(const std::optional& config, std::map& config_dict); bool runDR(); -bool runERT(std::string& config, std::map& config_dict); +bool runERT(const std::optional& config, std::map& config_dict); bool runRT(); } // namespace python_interface diff --git a/src/interface/python/py_irt/py_register_irt.h b/src/interface/python/py_irt/py_register_irt.h index 04b3becf3f..4c8be8a687 100644 --- a/src/interface/python/py_irt/py_register_irt.h +++ b/src/interface/python/py_irt/py_register_irt.h @@ -17,6 +17,7 @@ #pragma once #include #include +#include #include "ScriptEngine.hh" #include "py_irt.h" @@ -25,8 +26,8 @@ namespace py = pybind11; void register_irt(py::module& m) { m.def("destroy_rt", destroyRT); - m.def("init_rt", initRT, py::arg("config") = "", py::arg("config_dict") = std::map{}); - m.def("run_ert", runERT, py::arg("config") = "", py::arg("config_dict") = std::map{}); + m.def("init_rt", initRT, py::arg("config") = py::none(), py::arg("config_dict") = std::map{}); + m.def("run_ert", runERT, py::arg("config") = py::none(), py::arg("config_dict") = std::map{}); m.def("run_rt", runRT); } } // namespace python_interface \ No newline at end of file diff --git a/src/interface/python/py_ista/py_ista.cpp b/src/interface/python/py_ista/py_ista.cpp index 333c137091..29ce527898 100644 --- a/src/interface/python/py_ista/py_ista.cpp +++ b/src/interface/python/py_ista/py_ista.cpp @@ -16,6 +16,7 @@ // *************************************************************************************** #include "py_ista.h" +#include "../py_path_utils.h" #include "STAInterface.hpp" namespace python_interface { @@ -23,12 +24,13 @@ namespace python_interface { bool initStaConfigMapByJSON(const std::string& config, std::map& config_map); void initStaConfigMapByDict(std::map& config_dict, std::map& config_map); -bool initSTA(std::string& config, std::map& config_dict) +bool initSTA(const std::optional& config, std::map& config_dict) { + const std::string config_ = path_or_empty(config); std::map config_map; bool pass = false; - pass = config.empty() ? true : initStaConfigMapByJSON(config, config_map); + pass = config_.empty() ? true : initStaConfigMapByJSON(config_, config_map); if (!pass) { return false; } diff --git a/src/interface/python/py_ista/py_ista.h b/src/interface/python/py_ista/py_ista.h index 8a7268075b..d1a607b804 100644 --- a/src/interface/python/py_ista/py_ista.h +++ b/src/interface/python/py_ista/py_ista.h @@ -18,9 +18,12 @@ #include +#include +#include + namespace python_interface { -bool initSTA(std::string& config, std::map& config_dict); +bool initSTA(const std::optional& config, std::map& config_dict); bool runSTA(); bool extractLib(); bool destroySTA(); diff --git a/src/interface/python/py_ista/py_register_ista.h b/src/interface/python/py_ista/py_register_ista.h index f7e9a7a278..0f5dd97481 100644 --- a/src/interface/python/py_ista/py_register_ista.h +++ b/src/interface/python/py_ista/py_register_ista.h @@ -18,6 +18,7 @@ #include #include +#include #include "py_ista.h" @@ -26,7 +27,7 @@ namespace py = pybind11; void register_ista(py::module& m) { - m.def("init_sta", initSTA, py::arg("config") = "", py::arg("config_dict") = std::map{}); + m.def("init_sta", initSTA, py::arg("config") = py::none(), py::arg("config_dict") = std::map{}); m.def("run_sta", runSTA); m.def("extract_lib", extractLib); m.def("destroy_sta", destroySTA); From bb87a6b86db1947391cf0f998d92cae5067b6c23 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 16:18:14 +0800 Subject: [PATCH 12/16] fix: strip trailing whitespace in py_eval registration and normalize saveMacroTCL param name --- src/interface/python/py_eval/py_register_eval.h | 2 +- src/interface/python/py_idb/py_db.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/interface/python/py_eval/py_register_eval.h b/src/interface/python/py_eval/py_register_eval.h index 008ac86a91..f15630d0d9 100644 --- a/src/interface/python/py_eval/py_register_eval.h +++ b/src/interface/python/py_eval/py_register_eval.h @@ -87,7 +87,7 @@ void register_eval(py::module& m) const std::string save_path_ = path_or_empty(save_path); auto [max_congestion, total_congestion] = egr_congestion(save_path_); return py::make_tuple(max_congestion, total_congestion); - }, py::arg("save_path") = py::none()); + }, py::arg("save_path") = py::none()); // timing and power evaluation diff --git a/src/interface/python/py_idb/py_db.cpp b/src/interface/python/py_idb/py_db.cpp index 9835ea0337..20ac14802c 100644 --- a/src/interface/python/py_idb/py_db.cpp +++ b/src/interface/python/py_idb/py_db.cpp @@ -92,10 +92,10 @@ bool saveDef(const std::filesystem::path& def_name) return dmInst->saveDef(def_name_); } -bool saveMacroTCL(const std::filesystem::path& def_name) +bool saveMacroTCL(const std::filesystem::path& tcl_name) { - const std::string def_name_ = def_name.string(); - return dmInst->saveMacroTCL(def_name_); + const std::string tcl_name_ = tcl_name.string(); + return dmInst->saveMacroTCL(tcl_name_); } bool saveNetList(const std::filesystem::path& netlist_path, std::set exclude_cell_names /* = {} */, From ae321b34bff7225ca30129152887279867e80600 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 16:33:13 +0800 Subject: [PATCH 13/16] feat: add installed-wheel behavior contract suite --- .github/workflows/ci.yml | 13 ++ tests/test_pathlike_contract.py | 351 ++++++++++++++++++++++++++++++++ 2 files changed, 364 insertions(+) create mode 100644 tests/test_pathlike_contract.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 162dd59b6d..d9e4551c6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,19 @@ jobs: - name: Build wheel uses: ./.github/actions/build-wheel + - name: Compile and run path helper fixture + run: | + g++ -std=c++20 -Wall -Werror src/interface/python/test/py_path_utils_test.cc -o /tmp/py_path_utils_test + /tmp/py_path_utils_test + + - name: Smoke test installed wheel + run: | + python -m venv /tmp/wheel-smoke + /tmp/wheel-smoke/bin/pip install dist/wheel/repaired/*.whl pytest + # Run from outside the checkout so `import ecc_tools_bin` resolves + # to the installed wheel, not the source-tree package. + cd /tmp && /tmp/wheel-smoke/bin/python -m pytest "$GITHUB_WORKSPACE/tests/test_pathlike_contract.py" -q + - name: Upload repaired wheel uses: actions/upload-artifact@v4 with: diff --git a/tests/test_pathlike_contract.py b/tests/test_pathlike_contract.py new file mode 100644 index 0000000000..d3a620628c --- /dev/null +++ b/tests/test_pathlike_contract.py @@ -0,0 +1,351 @@ +"""Behavior contract suite for the os.PathLike bindings in ecc_py. + +Runs against an INSTALLED ecc-tools-bin wheel, not the source tree: the +source tree ships an ``ecc_tools_bin`` package without the compiled +extension, so the import guard below fails loudly if the suite is +accidentally run with the repo root on ``sys.path``. + +Calls that mutate global C++ state (the ``*_init`` family, ``save_data``, +``idb_get``) are executed in a fresh subprocess each so one test cannot +poison the next through module-level state. +""" + +import subprocess +import sys +import textwrap + +import pytest + +from ecc_tools_bin import ecc_py + + +def test_installed_wheel_is_imported(): + # The source tree's ecc_tools_bin/ has no compiled extension; the + # installed wheel resolves ecc_py to a .so inside site-packages. + path = getattr(ecc_py, "__file__", "") or "" + print(f"ecc_py imported from: {path}") + assert path.endswith(".so"), f"ecc_py is not the compiled extension: {path!r}" + assert "site-packages" in path, f"ecc_py not imported from an installed wheel: {path!r}" + + +def _run(body, cwd): + """Run ``body`` in a fresh interpreter; assertions inside it gate the exit code.""" + script = "from ecc_tools_bin import ecc_py\nfrom pathlib import Path\n\n" + textwrap.dedent(body) + proc = subprocess.run( + [sys.executable, "-c", script], + cwd=cwd, + capture_output=True, + text=True, + ) + assert proc.returncode == 0, ( + f"subprocess failed with exit code {proc.returncode}\n" + f"--- stdout ---\n{proc.stdout}\n--- stderr ---\n{proc.stderr}" + ) + + +# --------------------------------------------------------------------------- +# 1. input-file path: def_init +# --------------------------------------------------------------------------- + + +def test_def_init_accepts_str_path_and_custom_pathlike(tmp_path): + _run( + """ + class CustomPath: + def __init__(self, path): + self._path = path + def __fspath__(self): + return self._path + + target = '/nonexistent/input.def' + expected = ecc_py.def_init(target) + assert expected is False + assert ecc_py.def_init(Path(target)) == expected + assert ecc_py.def_init(CustomPath(target)) == expected + """, + cwd=tmp_path, + ) + + +def test_def_init_rejects_non_pathlike(tmp_path): + _run( + """ + for bad in (None, 5): + try: + ecc_py.def_init(bad) + except TypeError: + pass + else: + raise AssertionError(f'def_init({bad!r}) did not raise TypeError') + """, + cwd=tmp_path, + ) + + +# --------------------------------------------------------------------------- +# 2. output-file path: save_data +# --------------------------------------------------------------------------- + + +def test_save_data_path_matches_str(tmp_path): + _run( + """ + expected = ecc_py.save_data('/nonexistent/out.db') + assert expected is False + assert ecc_py.save_data(Path('/nonexistent/out.db')) == expected + """, + cwd=tmp_path, + ) + + +def test_save_data_rejects_none(tmp_path): + _run( + """ + try: + ecc_py.save_data(None) + except TypeError: + pass + else: + raise AssertionError('save_data(None) did not raise TypeError') + """, + cwd=tmp_path, + ) + + +# --------------------------------------------------------------------------- +# 3. optional config path: init_rt +# --------------------------------------------------------------------------- + + +def test_init_rt_none_and_empty_match_omitted(tmp_path): + _run( + """ + omitted = ecc_py.init_rt(config_dict={}) + assert ecc_py.init_rt(config=None, config_dict={}) == omitted + assert ecc_py.init_rt(config='', config_dict={}) == omitted + """, + cwd=tmp_path, + ) + + +def test_init_rt_path_matches_str(tmp_path): + _run( + """ + expected = ecc_py.init_rt(config='/nonexistent/rt.toml', config_dict={}) + assert ecc_py.init_rt(config=Path('/nonexistent/rt.toml'), config_dict={}) == expected + """, + cwd=tmp_path, + ) + + +def test_init_rt_rejects_non_pathlike_config(tmp_path): + _run( + """ + try: + ecc_py.init_rt(config=5, config_dict={}) + except TypeError: + pass + else: + raise AssertionError('init_rt(config=5) did not raise TypeError') + """, + cwd=tmp_path, + ) + + +def test_init_rt_config_dict_still_requires_str_values(tmp_path): + _run( + """ + try: + ecc_py.init_rt(config='', config_dict={'-temp_directory_path': Path('/tmp/x')}) + except TypeError: + pass + else: + raise AssertionError('init_rt accepted a Path value in config_dict') + """, + cwd=tmp_path, + ) + + +# --------------------------------------------------------------------------- +# 4. optional report/save path: idb_get +# --------------------------------------------------------------------------- + + +def test_idb_get_none_and_empty_match_omitted(tmp_path): + _run( + """ + omitted = ecc_py.idb_get() + assert ecc_py.idb_get(file_name=None) == omitted + assert ecc_py.idb_get(file_name='') == omitted + """, + cwd=tmp_path, + ) + + +def test_idb_get_path_matches_str(tmp_path): + _run( + """ + expected = ecc_py.idb_get(file_name='/nonexistent/out.db') + assert ecc_py.idb_get(file_name=Path('/nonexistent/out.db')) == expected + """, + cwd=tmp_path, + ) + + +def test_idb_get_rejects_non_pathlike_file_name(tmp_path): + _run( + """ + try: + ecc_py.idb_get(file_name=5) + except TypeError: + pass + else: + raise AssertionError('idb_get(file_name=5) did not raise TypeError') + """, + cwd=tmp_path, + ) + + +# --------------------------------------------------------------------------- +# 5. list path: lef_init +# --------------------------------------------------------------------------- + + +def test_lef_init_accepts_str_path_mixed_and_custom(tmp_path): + _run( + """ + class CustomPath: + def __init__(self, path): + self._path = path + def __fspath__(self): + return self._path + + a = '/nonexistent/a.lef' + b = '/nonexistent/b.lef' + expected = ecc_py.lef_init([a, b]) + assert expected is True + assert ecc_py.lef_init([Path(a), Path(b)]) == expected + assert ecc_py.lef_init([a, Path(b), CustomPath(a)]) == expected + """, + cwd=tmp_path, + ) + + +def test_lef_init_rejects_non_pathlike_elements(tmp_path): + _run( + """ + for bad in ([None], [3]): + try: + ecc_py.lef_init(bad) + except TypeError: + pass + else: + raise AssertionError(f'lef_init({bad!r}) did not raise TypeError') + """, + cwd=tmp_path, + ) + + +# --------------------------------------------------------------------------- +# 6. sdc equivalence: sdc_init +# --------------------------------------------------------------------------- + + +def test_sdc_init_none_and_empty_match_omitted(tmp_path): + _run( + """ + omitted = ecc_py.sdc_init() + assert omitted is True + assert ecc_py.sdc_init(None) == omitted + assert ecc_py.sdc_init('') == omitted + """, + cwd=tmp_path, + ) + + +def test_sdc_init_path_matches_str(tmp_path): + _run( + """ + expected = ecc_py.sdc_init('/nonexistent.sdc') + assert ecc_py.sdc_init(Path('/nonexistent.sdc')) == expected + """, + cwd=tmp_path, + ) + + +def test_sdc_init_rejects_non_pathlike(tmp_path): + _run( + """ + try: + ecc_py.sdc_init(5) + except TypeError: + pass + else: + raise AssertionError('sdc_init(5) did not raise TypeError') + """, + cwd=tmp_path, + ) + + +# --------------------------------------------------------------------------- +# 7. doc smoke: rendered signatures advertise os.PathLike +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + "name", + [ + "sdc_init", + "def_init", + "lef_init", + "init_rt", + "idb_get", + "cell_density", + "report_drc", + "init_rcx", + "db_init", + ], +) +def test_doc_mentions_pathlike(name): + doc = getattr(ecc_py, name).__doc__ or "" + assert "os.PathLike" in doc, f"{name} doc does not mention os.PathLike: {doc!r}" + + +@pytest.mark.parametrize( + "name,param", + [ + ("sdc_init", "sdc_path"), + ("init_rt", "config"), + ("idb_get", "file_name"), + ("cell_density", "save_path"), + ("db_init", "config_path"), + ("db_init", "def_path"), + ("db_init", "sdc_path"), + ], +) +def test_doc_optional_pathlike_defaults_to_none(name, param): + doc = getattr(ecc_py, name).__doc__ or "" + assert f"{param}: Optional[os.PathLike] = None" in doc, ( + f"{name} doc does not render '{param}: Optional[os.PathLike] = None': {doc!r}" + ) + + +def test_doc_lef_init_renders_list_of_pathlike(): + doc = ecc_py.lef_init.__doc__ or "" + assert "lef_paths: List[os.PathLike]" in doc, f"lef_init doc: {doc!r}" + + +def test_doc_verilog_init_top_module_still_str(): + doc = ecc_py.verilog_init.__doc__ or "" + assert "top_module: str" in doc, f"verilog_init doc: {doc!r}" + + +def test_doc_init_rcx_pdk_still_optional_str(): + doc = ecc_py.init_rcx.__doc__ or "" + assert "pdk: Optional[str] = None" in doc, f"init_rcx doc: {doc!r}" + + +def test_doc_init_rt_config_dict_still_dict(): + doc = ecc_py.init_rt.__doc__ or "" + assert "config_dict: Dict" in doc, f"init_rt doc: {doc!r}" + assert "config_dict: os.PathLike" not in doc, f"init_rt doc: {doc!r}" From d44a181aad2da8a5339beb22dd3556c0f942bd72 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 16:33:30 +0800 Subject: [PATCH 14/16] chore: bump version to v0.1.0-alpha.8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fb7bbaa9f5..339e08bbae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "ecc-tools-bin" -version = "0.1.0-alpha.7" +version = "0.1.0-alpha.8" requires-python = ">=3.11" dependencies = [ "numpy", From aded044617d9ae6a626e0ef06d1919a3a8ffa5bc Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 17:31:10 +0800 Subject: [PATCH 15/16] test: anchor absolute return values in contract equivalence tests --- tests/test_pathlike_contract.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_pathlike_contract.py b/tests/test_pathlike_contract.py index d3a620628c..910ce0aa26 100644 --- a/tests/test_pathlike_contract.py +++ b/tests/test_pathlike_contract.py @@ -121,6 +121,7 @@ def test_init_rt_none_and_empty_match_omitted(tmp_path): _run( """ omitted = ecc_py.init_rt(config_dict={}) + assert omitted is False assert ecc_py.init_rt(config=None, config_dict={}) == omitted assert ecc_py.init_rt(config='', config_dict={}) == omitted """, @@ -132,6 +133,7 @@ def test_init_rt_path_matches_str(tmp_path): _run( """ expected = ecc_py.init_rt(config='/nonexistent/rt.toml', config_dict={}) + assert expected is False assert ecc_py.init_rt(config=Path('/nonexistent/rt.toml'), config_dict={}) == expected """, cwd=tmp_path, @@ -175,6 +177,7 @@ def test_idb_get_none_and_empty_match_omitted(tmp_path): _run( """ omitted = ecc_py.idb_get() + assert omitted is False assert ecc_py.idb_get(file_name=None) == omitted assert ecc_py.idb_get(file_name='') == omitted """, @@ -186,6 +189,7 @@ def test_idb_get_path_matches_str(tmp_path): _run( """ expected = ecc_py.idb_get(file_name='/nonexistent/out.db') + assert expected is False assert ecc_py.idb_get(file_name=Path('/nonexistent/out.db')) == expected """, cwd=tmp_path, From d607f0bf6ac62c89fc3254f8e4f4c0aad0b0978a Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Jul 2026 17:45:07 +0800 Subject: [PATCH 16/16] chore: remove binding census tooling The census was process scaffolding for the path binding conversion, which is now complete. Ongoing verification lives in tests/test_pathlike_contract.py and the interface fixture src/interface/python/test/py_path_utils_test.cc. --- .github/workflows/ci.yml | 21 - scripts/binding_census/README.md | 101 - scripts/binding_census/baseline_diff.md | 102 - scripts/binding_census/binding_census.py | 88 - scripts/binding_census/binding_spec.json | 2251 -------------- .../binding_census/binding_spec.schema.json | 67 - scripts/binding_census/dead_bindings.md | 278 -- scripts/binding_census/dead_bindings.py | 152 - scripts/binding_census/lexer.py | 468 --- scripts/binding_census/manifest.json | 2623 ----------------- scripts/binding_census/manifest.py | 296 -- scripts/binding_census/manifest.schema.json | 65 - scripts/binding_census/test_binding_census.py | 333 --- 13 files changed, 6845 deletions(-) delete mode 100644 scripts/binding_census/README.md delete mode 100644 scripts/binding_census/baseline_diff.md delete mode 100755 scripts/binding_census/binding_census.py delete mode 100644 scripts/binding_census/binding_spec.json delete mode 100644 scripts/binding_census/binding_spec.schema.json delete mode 100644 scripts/binding_census/dead_bindings.md delete mode 100755 scripts/binding_census/dead_bindings.py delete mode 100755 scripts/binding_census/lexer.py delete mode 100644 scripts/binding_census/manifest.json delete mode 100755 scripts/binding_census/manifest.py delete mode 100644 scripts/binding_census/manifest.schema.json delete mode 100755 scripts/binding_census/test_binding_census.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9e4551c6a..1db8c2b670 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,6 @@ on: - 'CMakeLists.txt' - 'pyproject.toml' - 'build.sh' - - 'scripts/binding_census/**' - '.github/**' push: branches: [main] @@ -50,23 +49,3 @@ jobs: path: dist/wheel/repaired/*.whl if-no-files-found: error - binding-census: - name: Binding Census - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v5 - - - name: Run census parser tests - run: uv run --no-project --with jsonschema --with pytest python -m pytest scripts/binding_census/test_binding_census.py -q - - - name: Check census manifest - run: uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py --check - - - name: Compile and run path helper fixture - run: | - g++ -std=c++20 -Wall -Werror src/interface/python/test/py_path_utils_test.cc -o /tmp/py_path_utils_test - /tmp/py_path_utils_test diff --git a/scripts/binding_census/README.md b/scripts/binding_census/README.md deleted file mode 100644 index 4598b37f55..0000000000 --- a/scripts/binding_census/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# Binding census - -Machine-verified census of the `ecc_py` pybind11 bindings whose parameters -carry strings (`std::string` / `std::vector` and similar string -containers). It is the source of truth for widening path-carrying parameters -to `std::filesystem::path` / `std::optional`. - -## Layout - -- `binding_census.py` — thin CLI entry point (argument parsing and - orchestration only). The implementation is split into sibling modules: - `lexer.py` (lexical discovery scanner), `manifest.py` (schema validation, - spec/manifest join, `--check` gate), and `dead_bindings.py` (the - `--ecc-root` cross-repo audit). Together they need only the stdlib plus - `jsonschema`. -- `binding_spec.json` — curated, human-reviewed semantics per in-scope - parameter (types, defaults, shape, path/non_path/ambiguous classification - with rationale). Validated against `binding_spec.schema.json`. -- `manifest.json` — generated join of discovery + spec, one entry per - (binding, parameter). Validated against `manifest.schema.json`. Committed; - never edit by hand. -- `dead_bindings.md` — generated audit of the ecc wrapper's calls against - this census (needs the outer repo, see below). -- `baseline_diff.md` — count comparison of the manifest against the reviewed - classification baseline, with a written rationale for every deviation. - -## Why discovery + curated spec - -Discovery is a small lexer (a state machine over code / line comment / block -comment / string / char literals with paren-brace-bracket depth tracking — no -cross-line regex) that finds every module-level `m.def(` in -`src/interface/python/py_*/py_register_*.h` and `.../py_register_*.cpp`, -including multiline and commented-out statements, extracts the -`py::arg("name") = default` entries at paren depth 1, and resolves whether a -binding is `active` or `disabled` (commented statement, or its enclosing -`register_*` function is never called from `python_moodule.cc`). - -Semantics — which string parameter is actually a filesystem path, and what -the converted type and default should be — cannot be derived from arbitrary -C++ declarations without writing half a C++ parser. They live in the curated -spec instead, one row per parameter, each with a written rationale. The -manifest is the deterministic join of the two. - -## Regenerating - -From the repository root: - -```sh -uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py -``` - -Running it twice with unchanged inputs produces identical bytes -(`--no-project` matters: plain `uv run` in the repo root would trigger a -project sync and a full C++ build). - -To also regenerate the dead-binding audit you need a checkout of the outer -ecc repo (read-only): - -```sh -uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py \ - --ecc-root /path/to/ecc -``` - -## CI gate - -```sh -uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py --check -``` - -Exits nonzero with one message per violation when any of these fail: - -1. regeneration is not byte-stable against the committed `manifest.json`; -2. schema validation of `manifest.json` or `binding_spec.json`; -3. coverage: every discovered active binding with a `py::arg` string-literal - default has spec entries for at least those parameters, every parameter - named in the classification baseline is spec'd as `path`, and every spec - entry names a discovered binding/parameter; -4. every `ambiguous` classification carries a non-empty rationale; -5. every `path`-classified parameter of an active binding carries the - converted `new_type` (`std::filesystem::path` when required, - `std::optional` when optional, - `std::vector` for lists). - -## Interface unit fixture - -The converted bindings canonicalize optional path parameters through -`path_or_empty` in `src/interface/python/py_path_utils.h`. Its self-contained -compile-plus-assert fixture builds and runs with the system compiler: - -```sh -g++ -std=c++20 -Wall -Werror src/interface/python/test/py_path_utils_test.cc -o /tmp/py_path_utils_test && /tmp/py_path_utils_test -``` - -## Known limitation - -The lexer discovers parameters from `py::arg(...)` entries. String parameters -of `py::arg`-free bindings (e.g. `idb_init`'s `config_path`, `tech_lef_init`'s -`techlef_path`) and string parameters without literal defaults are -curated-only: they exist in the spec (named after the C++ declaration) but -discovery cannot cross-check them. `binding_spec.json` covers them; the -coverage gate cross-checks everything discovery can see. diff --git a/scripts/binding_census/baseline_diff.md b/scripts/binding_census/baseline_diff.md deleted file mode 100644 index 7cd83f5aed..0000000000 --- a/scripts/binding_census/baseline_diff.md +++ /dev/null @@ -1,102 +0,0 @@ -# Manifest vs classification baseline - -Comparison of the generated `manifest.json` against the reviewed -classification baseline. The manifest is the final authority; every deviation -has a written rationale below. - -## Totals - -| Measure | Baseline | Manifest | Delta | -|---|---|---|---| -| path scalars | 69 | 69 | 0 | -| path lists | 4 | 4 | 0 | -| required path scalars | 41 | 40 | -1 | -| optional path scalars | 28 | 29 | +1 | - -The single scalar that moves between the required and optional columns is -`sdc_init.sdc_path`; see the deviation rationale. The expected post-review -targets (40 required + 29 optional = 28 draft-optional + the `sdc_init` -reclassification) match the manifest exactly. - -## Per-module rows (path parameters) - -| Module | Path scalars (req/opt) | Path lists | Matches baseline | -|---|---|---|---| -| py_config | 8 (1/7) | 2 | yes | -| py_eval | 11 (5/6) | 0 | yes | -| py_feature | 13 (13/0) | 0 | yes | -| py_icts | 3 (3/0) | 0 | yes | -| py_idb | 18 (16/2) | 2 | yes, with the `sdc_init` deviation below | -| py_idrc | 4 (0/4) | 0 | yes | -| py_irt | 2 (0/2) | 0 | yes | -| py_ista | 1 (0/1) | 0 | yes | -| py_ircx | 1 (1/0) | 0 | yes | -| py_izh | 2 (0/2) | 0 | yes | -| py_report | 6 (1/5) | 0 | yes | -| **Total** | **69 (40/29)** | **4** | | - -Modules with no path parameters (`py_ifp`, `py_ipdn`, `py_instance`, -`py_imp`, `py_flow`) contribute only `non_path` adjudication rows -(`py_ifp` 22, `py_ipdn` 31, `py_instance` 4; `py_imp` and `py_flow` have no -string-carrying parameters in their active bindings and therefore no rows). - -## Deviations and rationales - -1. **`py_idb.sdc_init.sdc_path`: required in the baseline, `optional` in the - spec.** Its C++ body (`initSdc` in `py_db.cpp`) stores the value as-is and - the timing flow treats empty as unset, and the production harden flow - passes `None` natively (`runner.py` passes `workspace.pdk.sdc`, typed - `Path | None`). Reclassification to - `std::optional` with `py::none()` default was - confirmed during design review. This is the only count deviation: optional - 28 -> 29, required 41 -> 40. - -2. **`py_idb.tech_lef_init` parameter is named `techlef_path`, not - `tech_lef_path`.** The binding is `py::arg`-free, so the parameter name is - curated from the `initTechLef(const std::string& techlef_path)` - declaration (curated-only limitation). Same parameter, same - classification; no count impact. - -3. **The manifest carries adjudication rows the baseline does not itemize.** - The baseline lists path parameters and a handful of named non-paths - (`step`, `net`, `json_format`, `pdk`). The census scopes in *every* - string-carrying parameter of every active binding, so the manifest - additionally holds `non_path` rows for name candidates — including the - container-typed ones `netlist_save.exclude_cell_names` - (`std::set`), `write_soc_json.harden_cores`, and - `report_place_distribution.prefixes` (`std::vector`), plus - the `py_ifp`/`py_ipdn`/`py_instance` name parameters. All keep - `std::string` (`new_type == old_type`) with a written rationale; they do - not affect the path totals above. - -4. **Disabled bindings have no spec rows.** `runMP`, `runRef`, the commented - `SAPlaceSeqPairInt64`/`write_placement_back` duplicates, and the whole - `py_vec` family are discovered (with file:line) but excluded from the - spec, because only active bindings are conversion targets. Their status is - reported by the dead-binding audit (`dead_bindings.md`); `binding_status` - in the manifest is therefore `active` for every row by construction, and - `absent` appears only inside that audit. - -## Confirmed ambiguous-name rulings - -Per the ambiguous-name rule (stay `std::string` unless call-path evidence -proves filesystem semantics), with the evidence recorded in each row's -`classification_rationale`: - -- `idb_get.file_name` = `path` (optional): `idbGet` forwards it to - `rptInst->reportInstance/reportNet` (`py_db_op.h`), which write the report - to that file. -- `place_instance.source` = `non_path`: `DataManager::placeInst` forwards it - to `instance->set_type` (`idm_design_inst.cpp`) — a provenance tag, no - filesystem semantics. -- The `config` parameters of `init_rt`, `run_ert`, `init_sta`, `init_rcx`, - `fix_fanout`, `insert_filler`, `init_drc`/`run_drc` = `path`: the wrapper - passes config file paths via `path_text(...)` at the call sites in - `chipcompiler/tools/ecc/module.py` (cited per row). -- `init_rcx.pdk` = `non_path`: a PDK identifier (already - `std::optional`), not a path. -- `feature_tool.step`, `feature_cong_map.step`, `report_route.net`, - `view_json_save.json_format` = `non_path`: selector/name strings forwarded - unchanged to the feature/report APIs. - -No parameter remains `ambiguous` in the committed spec. diff --git a/scripts/binding_census/binding_census.py b/scripts/binding_census/binding_census.py deleted file mode 100755 index 39c6f1cadc..0000000000 --- a/scripts/binding_census/binding_census.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python -"""Binding census for the ecc_py pybind11 module — CLI entry point. - -Two cleanly separated parts: - -1. Discovery (``lexer``): a small lexical scanner (no cross-line regex) that - walks every register file with a state machine (code / line comment / - block comment / string literal / char literal) and tracks paren/brace/ - bracket depth. It finds every module-level ``m.def(`` statement - (including multiline and commented-out ones), extracts the - ``py::arg("name") = default`` entries at paren depth 1, and resolves - whether the binding is active or disabled (commented statement, or its - enclosing register function is never called from ``python_moodule.cc``). - -2. Semantics (curated spec): a hand-maintained JSON table - (``binding_spec.json``) carrying, per binding parameter, the old/new C++ - types and defaults, scalar/list shape, required/optional shape, and the - path / non_path / ambiguous classification with a written rationale. - -The manifest (``manifest.json``) is the generated join of discovery + spec -(``manifest`` module), one entry per (binding, in-scope parameter). Only -string-carrying parameters (``std::string`` / ``std::vector``- -typed, plus string containers such as ``std::set``) are in -scope: they are the path candidates and name candidates needing adjudication. - -Run via uv from the repository root: - - uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py - uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py --check -""" -import argparse -import json -import sys -from pathlib import Path - -from dead_bindings import audit_wrapper, render_dead_bindings_md -from lexer import discover, discovery_to_json -from manifest import check, generate_manifest_bytes - -CENSUS_DIR = Path(__file__).resolve().parent -DEFAULT_REPO_ROOT = CENSUS_DIR.parents[1] - - -def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) - parser.add_argument("--check", action="store_true", help="run the census gates and exit nonzero on failure") - parser.add_argument("--discovery", action="store_true", help="print discovery JSON to stdout") - parser.add_argument( - "--ecc-root", - type=Path, - default=None, - help="path to the outer ecc repo; additionally writes dead_bindings.md", - ) - parser.add_argument("--repo-root", type=Path, default=DEFAULT_REPO_ROOT, help=argparse.SUPPRESS) - parser.add_argument("--census-dir", type=Path, default=CENSUS_DIR, help=argparse.SUPPRESS) - args = parser.parse_args(argv) - - repo_root: Path = args.repo_root.resolve() - census_dir: Path = args.census_dir.resolve() - - if args.discovery: - print(json.dumps(discovery_to_json(discover(repo_root)), indent=2, sort_keys=True)) - return 0 - - if args.check: - failures = check(repo_root, census_dir) - if failures: - for failure in failures: - print(f"binding census check FAILED: {failure}", file=sys.stderr) - return 1 - print("binding census check passed") - return 0 - - manifest_bytes = generate_manifest_bytes(repo_root, census_dir) - (census_dir / "manifest.json").write_bytes(manifest_bytes) - print(f"wrote {census_dir / 'manifest.json'}") - - if args.ecc_root is not None: - module_py = args.ecc_root.resolve() / "chipcompiler/tools/ecc/module.py" - audit = audit_wrapper(module_py, discover(repo_root)) - out = census_dir / "dead_bindings.md" - out.write_text(render_dead_bindings_md(audit)) - print(f"wrote {out}") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/binding_census/binding_spec.json b/scripts/binding_census/binding_spec.json deleted file mode 100644 index 41866152c3..0000000000 --- a/scripts/binding_census/binding_spec.json +++ /dev/null @@ -1,2251 +0,0 @@ -{ - "bindings": [ - { - "module": "py_config", - "params": [ - { - "classification": "path", - "classification_rationale": "flow config JSON file; wrapper init_config passes it positionally (ECCToolsModule.init_config)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "flow_config", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "flow_init" - }, - { - "module": "py_config", - "params": [ - { - "classification": "path", - "classification_rationale": "db config JSON file; wrapper init_config/update_step_paths pass path_text(db_config)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "config_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "technology LEF file; empty string is the unset sentinel in db_init's C++ body", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "tech_lef_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "cell LEF file list; empty list is the unset sentinel, no optional-ization", - "new_default": "std::vector{}", - "new_type": "std::vector", - "old_default": "std::vector {}", - "old_type": "const std::vector&", - "param": "lef_paths", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "classification": "path", - "classification_rationale": "DEF file; empty string is the unset sentinel", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "def_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "netlist Verilog file; empty string is the unset sentinel", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "verilog_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "output directory; wrapper passes path_text(output_dir)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "output_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "feature output directory; wrapper passes path_text(feature_dir)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "feature_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "Liberty file list; wrapper update_sta_data_config passes path_texts(lib_paths); empty list is the unset sentinel", - "new_default": "std::vector{}", - "new_type": "std::vector", - "old_default": "std::vector{}", - "old_type": "const std::vector&", - "param": "lib_paths", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "classification": "path", - "classification_rationale": "SDC constraints file; empty string is the unset sentinel", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "sdc_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "db_init" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "cell_density" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "pin_density" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "net_density" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "rudy_congestion" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "lut_rudy_congestion" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "egr_congestion" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "eval_cell_hierarchy" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "eval_macro_hierarchy" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "eval_macro_connection" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "eval_macro_pin_connection" - }, - { - "module": "py_eval", - "params": [ - { - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "eval_macro_io_pin_connection" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "feature summary output file (featureInst->save_summary target)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_summary" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "tool feature output file (featureInst->save_tools target)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "flow step selector string forwarded to save_tools, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "step", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_tool" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "placement eval JSON output file", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "json_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_pl_eval" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "CTS eval JSON output file", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "json_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_cts_eval" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "eval map output file (featureInst->save_eval_map target)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_eval_map" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "route feature output file", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_route" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "route feature input file read back by the feature API", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_route_read" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "macro DRC feature output file", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "DRC report input file consumed by the macro DRC feature builder", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "drc_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_macro_drc" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "eval summary output file", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_eval_summary" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "timing eval summary output file", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_timing_eval_summary" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "path", - "classification_rationale": "net eval output file", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_net_eval" - }, - { - "module": "py_feature", - "params": [ - { - "classification": "non_path", - "classification_rationale": "flow step selector string forwarded to save_cong_map, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "step", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "congestion map output directory (featureInst->save_cong_map target)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "dir", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "feature_cong_map" - }, - { - "module": "py_icts", - "params": [ - { - "classification": "path", - "classification_rationale": "CTS config JSON file; ECCToolsModule.run_cts passes path_text(config)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "cts_config", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "CTS working directory; ECCToolsModule.run_cts passes path_text(output)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "cts_work_dir", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "run_cts" - }, - { - "module": "py_icts", - "params": [ - { - "classification": "path", - "classification_rationale": "CTS report output file", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "cts_report" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "idb config JSON file; ECCToolsModule.idb_init passes path_text(config_path); py::arg-free binding, parameter name from the initIdb declaration", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "config_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "idb_init" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "technology LEF file; ECCToolsModule.init_techlef passes path_text(tech_lef_path); py::arg-free binding, parameter name from the initTechLef declaration", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "techlef_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "tech_lef_init" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "cell LEF file list; ECCToolsModule.init_lefs passes path_texts(lef_paths); empty list is the unset sentinel", - "new_default": null, - "new_type": "std::vector", - "old_default": null, - "old_type": "const std::vector&", - "param": "lef_paths", - "required_or_optional": "required", - "scalar_or_list": "list" - } - ], - "py_name": "lef_init" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "DEF file; ECCToolsModule.read_def passes path_text(path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "def_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "def_init" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "netlist Verilog file; wrapper read_verilog passes path_text(verilog)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "verilog_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "design top module name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "top_module", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "verilog_init" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "Liberty file list; ECCToolsModule.run_timing passes path_texts(lib_paths); empty list is the unset sentinel", - "new_default": null, - "new_type": "std::vector", - "old_default": null, - "old_type": "const std::vector&", - "param": "lib_paths", - "required_or_optional": "required", - "scalar_or_list": "list" - } - ], - "py_name": "lib_init" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "SDC constraints file; initSdc stores the value as-is and the timing flow treats empty as unset; the production harden flow passes None natively (runner.py passes workspace.pdk.sdc which is Path|None), so the converted binding takes an optional path", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": null, - "old_type": "const std::string&", - "param": "sdc_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "sdc_init" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "SPEF parasitics file; wrapper passes path_text(spef_path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "spef_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "spef_init" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "DEF output file; wrapper def_save passes path_text(def_path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "def_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "def_save" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "macro placement TCL output file (saveMacroTCL writes a .tcl file)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "tcl_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "tcl_save" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "netlist output file (saveNetList writes a Verilog netlist)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "netlist_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "cell master names to exclude from the netlist, not filesystem paths", - "new_default": "std::set{}", - "new_type": "std::set", - "old_default": "std::set{}", - "old_type": "std::set", - "param": "exclude_cell_names", - "required_or_optional": "optional", - "scalar_or_list": "list" - } - ], - "py_name": "netlist_save" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "GDSII output file; wrapper gds_save passes path_text(output_path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "gds_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "gds_save" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "idb JSON output file (saveJson serializes the database)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "json_save" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "view JSON output directory", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "output_dir", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "serialization format keyword (e.g. \"pretty\"), not a filesystem path", - "new_default": "\"pretty\"", - "new_type": "const std::string&", - "old_default": "\"pretty\"", - "old_type": "const std::string&", - "param": "json_format", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "view_json_save" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "view JSON edits input file read by applyViewJsonEdits", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "edits_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "view_json_apply_edits" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "serialized database output file/directory (saveData persists the DataManager state)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "save_data" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "serialized database input file/directory read by loadData", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "load_data" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "SoC JSON output file", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "harden core instance names embedded in the JSON, not filesystem paths", - "new_default": "std::vector{}", - "new_type": "const std::vector&", - "old_default": "std::vector{}", - "old_type": "const std::vector&", - "param": "harden_cores", - "required_or_optional": "optional", - "scalar_or_list": "list" - } - ], - "py_name": "write_soc_json" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "path", - "classification_rationale": "abstract LEF output file; ECCToolsModule.write_abstract_lef passes path_text(output_lef_path)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "output_lef_path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "write_abstract_lef" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "non_path", - "classification_rationale": "net name in the design database, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "net type keyword (signal/power/ground), not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_type", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "set_net" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "non_path", - "classification_rationale": "blockage type keyword, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "type", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "clear_blockage" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "non_path", - "classification_rationale": "instance name filter, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "inst_name", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "net name filter, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "net_name", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "report output file: idbGet forwards file_name to rptInst->reportInstance/reportNet (py_db_op.h), which write the report to that file", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "file_name", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "idb_get" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "non_path", - "classification_rationale": "instance name in the design database, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "delete_inst" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "non_path", - "classification_rationale": "net name in the design database, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "delete_net" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "non_path", - "classification_rationale": "instance name to create, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "cell master name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "cell_master", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "placement orientation keyword, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "orient", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "instance type tag, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "type", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "placement status keyword, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "status", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "create_inst" - }, - { - "module": "py_idb", - "params": [ - { - "classification": "non_path", - "classification_rationale": "net name to create, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "connection type keyword, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "conn_type", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "create_net" - }, - { - "module": "py_idrc", - "params": [ - { - "classification": "path", - "classification_rationale": "DRC working directory; ECCToolsModule.init_drc passes path_text(output_dir)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "temp_directory_path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "init_drc" - }, - { - "module": "py_idrc", - "params": [ - { - "classification": "path", - "classification_rationale": "DRC config JSON file; ECCToolsModule.run_drc passes path_text(config)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "config", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "path", - "classification_rationale": "DRC report output file; ECCToolsModule.run_drc passes path_text(report_path)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "report", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "run_drc" - }, - { - "module": "py_idrc", - "params": [ - { - "classification": "path", - "classification_rationale": "DRC feature output file; ECCToolsModule.save_drc passes path_text(feature_path)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "save_drc" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "die rectangle as a coordinate string (\"llx lly urx ury\"), not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "die_area", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "core rectangle as a coordinate string, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "core_area", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "core site name from the technology LEF, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "core_site", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "IO site name from the technology LEF, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "io_site", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "corner site name from the technology LEF, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "corner_site", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "init_floorplan" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "routing layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "gern_track" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "pin layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "die side names (e.g. \"left\"/\"right\"), not filesystem paths", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "sides", - "required_or_optional": "required", - "scalar_or_list": "list" - } - ], - "py_name": "auto_place_pins" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "pin name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "pin_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "pin layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "place_port" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "IO filler cell master names, not filesystem paths", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "filler_types", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "classification": "non_path", - "classification_rationale": "instance name prefix for created filler cells, not a filesystem path", - "new_default": "\"IOFill\"", - "new_type": "const std::string&", - "old_default": "\"IOFill\"", - "old_type": "const std::string&", - "param": "prefix", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "place_io_filler" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "blockage rectangle as a coordinate string, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "box", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "add_placement_blockage" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "instance name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "halo distance encoded as a string, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "distance", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "add_placement_halo" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "routing layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "blockage rectangle as a coordinate string, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "box", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "add_routing_blockage" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "routing layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "halo distance encoded as a string, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "distance", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "instance name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "add_routing_halo" - }, - { - "module": "py_ifp", - "params": [ - { - "classification": "non_path", - "classification_rationale": "tapcell master name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "tapcell", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "endcap master name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "endcap", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "tapcell" - }, - { - "module": "py_instance", - "params": [ - { - "classification": "non_path", - "classification_rationale": "instance name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "placement orientation keyword, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "orient", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "cell master name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "cellmaster", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "instance source tag: DataManager::placeInst forwards source to instance->set_type (idm_design_inst.cpp), a provenance label with no filesystem semantics", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "source", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "place_instance" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "IO pin name, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "pin_name", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "pin direction keyword, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "direction", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "add_pdn_io" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "instance pin name pattern, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "instance_pin_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "global_net_connect" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "pin name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "pin_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "IO cell master name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "io_cell_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "place_pdn_port" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "power net name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name_power", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "ground net name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name_ground", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "create_grid" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "power net name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name_power", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "ground net name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name_ground", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "create_stripe" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "layer name pair to connect, not filesystem paths", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "layers", - "required_or_optional": "required", - "scalar_or_list": "list" - } - ], - "py_name": "connect_two_layer" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "macro pin layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "pin_layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "PDN layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "pdn_layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "power pin names, not filesystem paths", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "power_pins", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "classification": "non_path", - "classification_rationale": "ground pin names, not filesystem paths", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "ground_pins", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "classification": "non_path", - "classification_rationale": "orientation keyword, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "orient", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "connectMacroPdn" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "connectIoPinToPower" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "connectPowerStripe" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "net_name", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "start layer name, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "layer_start", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "end layer name, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "layer_end", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "add_segment_stripe" - }, - { - "module": "py_ipdn", - "params": [ - { - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "layer", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "top layer name, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "top_layer", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "bottom layer name, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "bottom_layer", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "add_segment_via" - }, - { - "module": "py_ircx", - "params": [ - { - "classification": "path", - "classification_rationale": "RCX config JSON file; ECCToolsModule.init_rcx passes path_text(config)", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "config", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "PDK identifier string (e.g. \"ics55\") selecting a built-in rule set, already std::optional; not a filesystem path", - "new_default": "py::none()", - "new_type": "const std::optional&", - "old_default": "py::none()", - "old_type": "const std::optional&", - "param": "pdk", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "init_rcx" - }, - { - "module": "py_irt", - "params": [ - { - "classification": "path", - "classification_rationale": "router config JSON file; ECCToolsModule.run_routing passes path_text(config)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "std::string&", - "param": "config", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "init_rt" - }, - { - "module": "py_irt", - "params": [ - { - "classification": "path", - "classification_rationale": "early-router config JSON file; ECCToolsModule.run_ert passes path_text(config)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "std::string&", - "param": "config", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "run_ert" - }, - { - "module": "py_ista", - "params": [ - { - "classification": "path", - "classification_rationale": "STA config JSON file; ECCToolsModule.run_timing passes path_text(config)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "std::string&", - "param": "config", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "init_sta" - }, - { - "module": "py_izh", - "params": [ - { - "classification": "path", - "classification_rationale": "fanout-fix config JSON file; ECCToolsModule.run_net_opt passes path_text(config)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "config", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "fix_fanout" - }, - { - "module": "py_izh", - "params": [ - { - "classification": "path", - "classification_rationale": "filler config JSON file; ECCToolsModule.run_filler passes path_text(config)", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "config", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "insert_filler" - }, - { - "module": "py_report", - "params": [ - { - "classification": "path", - "classification_rationale": "wirelength report output file; empty string reports to stdout only", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "report_wirelength" - }, - { - "module": "py_report", - "params": [ - { - "classification": "path", - "classification_rationale": "database summary report output file; empty string reports to stdout only", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "report_db" - }, - { - "module": "py_report", - "params": [ - { - "classification": "path", - "classification_rationale": "congestion report output file; empty string reports to stdout only", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "report_congestion" - }, - { - "module": "py_report", - "params": [ - { - "classification": "path", - "classification_rationale": "dangling-net report output file; empty string reports to stdout only", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "report_dangling_net" - }, - { - "module": "py_report", - "params": [ - { - "classification": "path", - "classification_rationale": "route report output file; empty string reports to stdout only", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "classification": "non_path", - "classification_rationale": "net name filter for the route report, not a filesystem path", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "net", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "py_name": "report_route" - }, - { - "module": "py_report", - "params": [ - { - "classification": "non_path", - "classification_rationale": "instance name prefixes to bucket, not filesystem paths", - "new_default": "std::vector{}", - "new_type": "const std::vector&", - "old_default": "std::vector{}", - "old_type": "const std::vector&", - "param": "prefixes", - "required_or_optional": "optional", - "scalar_or_list": "list" - } - ], - "py_name": "report_place_distribution" - }, - { - "module": "py_report", - "params": [ - { - "classification": "non_path", - "classification_rationale": "instance name prefix to report on, not a filesystem path", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "prefix", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "report_prefixed_instance" - }, - { - "module": "py_report", - "params": [ - { - "classification": "path", - "classification_rationale": "DRC report output path by API shape; the currently bound one-argument ReportManager::reportDRC overload is a stub with its write lines commented out (report_manager.cpp:186), pending re-enable", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "required_or_optional": "required", - "scalar_or_list": "scalar" - } - ], - "py_name": "report_drc" - } - ], - "version": 1 -} diff --git a/scripts/binding_census/binding_spec.schema.json b/scripts/binding_census/binding_spec.schema.json deleted file mode 100644 index 3dfeecc045..0000000000 --- a/scripts/binding_census/binding_spec.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "ecc_py binding spec (curated)", - "type": "object", - "additionalProperties": false, - "required": ["version", "bindings"], - "properties": { - "version": {"const": 1}, - "bindings": { - "type": "array", - "items": {"$ref": "#/$defs/binding"} - } - }, - "$defs": { - "binding": { - "type": "object", - "additionalProperties": false, - "required": ["module", "py_name", "params"], - "properties": { - "module": {"type": "string", "pattern": "^py_[a-z]+$"}, - "py_name": {"type": "string", "minLength": 1}, - "params": { - "type": "array", - "minItems": 1, - "items": {"$ref": "#/$defs/param"} - } - } - }, - "param": { - "type": "object", - "additionalProperties": false, - "required": [ - "param", - "old_type", - "old_default", - "new_type", - "new_default", - "scalar_or_list", - "required_or_optional", - "classification", - "classification_rationale" - ], - "properties": { - "param": {"type": "string", "minLength": 1}, - "old_type": {"type": "string", "minLength": 1}, - "old_default": {"type": ["string", "null"]}, - "new_type": {"type": "string", "minLength": 1}, - "new_default": {"type": ["string", "null"]}, - "scalar_or_list": {"enum": ["scalar", "list"]}, - "required_or_optional": {"enum": ["required", "optional"]}, - "classification": {"enum": ["path", "non_path", "ambiguous"]}, - "classification_rationale": {"type": "string"} - }, - "allOf": [ - { - "if": { - "properties": {"classification": {"const": "ambiguous"}}, - "required": ["classification"] - }, - "then": { - "properties": {"classification_rationale": {"minLength": 1}} - } - } - ] - } - } -} diff --git a/scripts/binding_census/dead_bindings.md b/scripts/binding_census/dead_bindings.md deleted file mode 100644 index 67a251c7b4..0000000000 --- a/scripts/binding_census/dead_bindings.md +++ /dev/null @@ -1,278 +0,0 @@ -# Dead-binding audit - -Generated by `binding_census.py --ecc-root` from the ecc wrapper (`chipcompiler/tools/ecc/module.py`) joined against the binding census. Regenerate with: - -```sh -uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py \ - --ecc-root -``` - -Statuses: `active` = bound and registered in `ecc_py`; `disabled (file:line)` = an `m.def` exists but is commented out or its register function is never called; `absent` = no `m.def` anywhere in the `ecc_py` census (the call would raise `AttributeError` at runtime). Note that a sibling native module (e.g. `ipower_cpp`, built from `src/operation/refactor`) may bind the same name under a different Python module; that does not make the `ecc_py` call live. - -| Wrapper method | Called binding | Status | -|---|---|---| -| `exit` | `flow_exit` | active | -| `get_dmInst_ptr` | `get_dmInst` | active | -| `pydb` | `pydb` | active | -| `build_macro_connection_map` | `build_macro_connection_map` | absent | -| `build_connection_map` | `build_connection_map` | absent | -| `reset_data` | `reset_data` | active | -| `init_config` | `flow_init` | active | -| `init_config` | `db_init` | active | -| `update_step_paths` | `db_init` | active | -| `update_sta_data_config` | `db_init` | active | -| `idb_init` | `idb_init` | active | -| `set_net` | `set_net` | active | -| `remove_except_pg_net` | `remove_except_pg_net` | active | -| `clear_blockage` | `clear_blockage` | active | -| `idb_get` | `idb_get` | active | -| `delete_inst` | `delete_inst` | active | -| `delete_net` | `delete_net` | active | -| `create_inst` | `create_inst` | active | -| `create_net` | `create_net` | active | -| `write_placement_back` | `write_placement_back` | active | -| `init_techlef` | `tech_lef_init` | active | -| `init_lefs` | `lef_init` | active | -| `read_def` | `def_init` | active | -| `read_verilog` | `verilog_init` | active | -| `def_save` | `def_save` | active | -| `gds_save` | `gds_save` | active | -| `tcl_save` | `tcl_save` | active | -| `verilog_save` | `netlist_save` | active | -| `json_save` | `json_save` | active | -| `view_json_save` | `view_json_save` | active | -| `view_json_apply_edits` | `view_json_apply_edits` | active | -| `save_data` | `save_data` | active | -| `load_data` | `load_data` | active | -| `write_soc_json` | `write_soc_json` | active | -| `feature_sammry` | `feature_summary` | active | -| `feature_step` | `feature_tool` | active | -| `feature_eval_map` | `feature_eval_map` | active | -| `feature_eval_summary` | `feature_eval_summary` | active | -| `feature_timing_eval_summary` | `feature_timing_eval_summary` | active | -| `feature_net_eval` | `feature_net_eval` | active | -| `feature_cong_map` | `feature_cong_map` | active | -| `report_wirelength` | `report_wirelength` | active | -| `report_summary` | `report_db` | active | -| `report_congestion` | `report_congestion` | active | -| `report_dangling_net` | `report_dangling_net` | active | -| `report_route` | `report_route` | active | -| `report_place_distribution` | `report_place_distribution` | active | -| `report_prefixed_instance` | `report_prefixed_instance` | active | -| `report_drc` | `report_drc` | active | -| `read_vcd_cpp` | `read_vcd_cpp` | absent | -| `read_pg_spef` | `read_pg_spef` | absent | -| `report_power_cpp` | `report_power_cpp` | absent | -| `report_power` | `report_power` | absent | -| `report_ir_drop` | `report_ir_drop` | absent | -| `get_wire_timing_power_data` | `get_wire_timing_power_data` | absent | -| `run_cts` | `run_cts` | active | -| `report_cts` | `cts_report` | active | -| `feature_cts_map` | `feature_cts_eval` | active | -| `init_drc` | `init_drc` | active | -| `run_drc` | `run_drc` | active | -| `save_drc` | `save_drc` | active | -| `init_floorplan` | `init_floorplan` | active | -| `gern_track` | `gern_track` | active | -| `place_port` | `place_port` | active | -| `place_io_filler` | `place_io_filler` | active | -| `add_placement_blockage` | `add_placement_blockage` | active | -| `add_placement_halo` | `add_placement_halo` | active | -| `add_routing_blockage` | `add_routing_blockage` | active | -| `add_routing_halo` | `add_routing_halo` | active | -| `place_instance` | `place_instance` | active | -| `add_pdn_io` | `add_pdn_io` | active | -| `global_net_connect` | `global_net_connect` | active | -| `place_pdn_port` | `place_pdn_port` | active | -| `create_pdn_grid` | `create_grid` | active | -| `create_pdn_stripe` | `create_stripe` | active | -| `connect_pdn_layers` | `connect_two_layer` | active | -| `connectMacroPdn` | `connectMacroPdn` | active | -| `connectIoPinToPower` | `connectIoPinToPower` | active | -| `connectPowerStripe` | `connectPowerStripe` | active | -| `add_segment_stripe` | `add_segment_stripe` | active | -| `add_segment_via` | `add_segment_via` | active | -| `auto_place_pins` | `auto_place_pins` | active | -| `tapcell` | `tapcell` | active | -| `pnp` | `run_pnp` | absent | -| `run_placement` | `run_placer` | absent | -| `init_pl` | `init_pl` | absent | -| `destroy_pl` | `destroy_pl` | absent | -| `feature_placement_map` | `feature_pl_eval` | active | -| `run_incremental_flow` | `run_incremental_flow` | absent | -| `run_legalize` | `run_incremental_lg` | absent | -| `run_filler` | `insert_filler` | active | -| `run_macro_placement` | `runMP` | disabled (src/interface/python/py_imp/py_register_imp.cpp:158) | -| `run_refinement` | `runRef` | disabled (src/interface/python/py_imp/py_register_imp.cpp:159) | -| `run_ai_placement` | `run_ai_placement` | absent | -| `placer_run_mp` | `placer_run_mp` | absent | -| `placer_run_gp` | `placer_run_gp` | absent | -| `placer_run_lg` | `placer_run_lg` | absent | -| `placer_run_dp` | `placer_run_dp` | absent | -| `feature_macro_drc_distribution` | `feature_macro_drc` | active | -| `run_ert` | `run_ert` | active | -| `run_routing` | `init_rt` | active | -| `run_routing` | `run_rt` | active | -| `run_routing` | `destroy_rt` | active | -| `close_routing` | `destroy_rt` | active | -| `feature_route_read` | `feature_route_read` | active | -| `feature_route` | `feature_route` | active | -| `init_rcx` | `init_rcx` | active | -| `run_rcx` | `run_rcx` | active | -| `report_rcx` | `report_rcx` | active | -| `run_timing` | `lib_init` | active | -| `run_timing` | `sdc_init` | active | -| `run_timing` | `spef_init` | active | -| `run_timing` | `init_sta` | active | -| `run_timing` | `run_sta` | active | -| `run_timing` | `destroy_sta` | active | -| `write_abstract_lef` | `write_abstract_lef` | active | -| `write_timing_model` | `lib_init` | active | -| `write_timing_model` | `sdc_init` | active | -| `write_timing_model` | `spef_init` | active | -| `write_timing_model` | `init_sta` | active | -| `write_timing_model` | `extract_lib` | active | -| `write_timing_model` | `destroy_sta` | active | -| `run_to` | `run_to` | absent | -| `run_timing_opt_drv` | `run_to_drv` | absent | -| `run_timing_opt_hold` | `run_to_hold` | absent | -| `run_timing_opt_setup` | `run_to_setup` | absent | -| `layout_patchs` | `layout_patchs` | disabled (src/interface/python/py_vec/py_register_vec.h:27) | -| `layout_graph` | `layout_graph` | disabled (src/interface/python/py_vec/py_register_vec.h:28) | -| `generate_vectors` | `generate_vectors` | disabled (src/interface/python/py_vec/py_register_vec.h:29) | -| `vectors_nets_to_def` | `read_vectors_nets` | disabled (src/interface/python/py_vec/py_register_vec.h:30) | -| `vectors_nets_patterns_to_def` | `read_vectors_nets_patterns` | disabled (src/interface/python/py_vec/py_register_vec.h:31) | -| `get_timing_wire_graph` | `get_timing_wire_graph` | disabled (src/interface/python/py_vec/py_register_vec.h:47) | -| `get_timing_instance_graph` | `get_timing_instance_graph` | disabled (src/interface/python/py_vec/py_register_vec.h:48) | -| `total_wirelength_dict` | `total_wirelength_dict` | active | -| `cell_density` | `cell_density` | active | -| `pin_density` | `pin_density` | active | -| `net_density` | `net_density` | active | -| `rudy_congestion` | `rudy_congestion` | active | -| `lut_rudy_congestion` | `lut_rudy_congestion` | active | -| `egr_congestion` | `egr_congestion` | active | -| `timing_power_hpwl` | `timing_power_hpwl` | active | -| `timing_power_stwl` | `timing_power_stwl` | active | -| `timing_power_egr` | `timing_power_egr` | active | -| `eval_macro_margin` | `eval_macro_margin` | active | -| `eval_continuous_white_space` | `eval_continuous_white_space` | active | -| `eval_macro_channel` | `eval_macro_channel` | active | -| `eval_cell_hierarchy` | `eval_cell_hierarchy` | active | -| `eval_macro_hierarchy` | `eval_macro_hierarchy` | active | -| `eval_macro_connection` | `eval_macro_connection` | active | -| `eval_macro_pin_connection` | `eval_macro_pin_connection` | active | -| `eval_macro_io_pin_connection` | `eval_macro_io_pin_connection` | active | -| `eval_overflow` | `eval_overflow` | active | -| `run_net_opt` | `fix_fanout` | active | -| `build_rc_tree_from_flat_data` | `build_rc_tree_from_flat_data` | absent | -| `update_and_get_all_pin_timings` | `update_and_get_all_pin_timings` | absent | - -## Dead-method candidates - -Wrapper methods whose every `ecc_py` call is disabled or absent: - -- `build_connection_map` -- `build_macro_connection_map` -- `build_rc_tree_from_flat_data` -- `destroy_pl` -- `generate_vectors` -- `get_timing_instance_graph` -- `get_timing_wire_graph` -- `get_wire_timing_power_data` -- `init_pl` -- `layout_graph` -- `layout_patchs` -- `placer_run_dp` -- `placer_run_gp` -- `placer_run_lg` -- `placer_run_mp` -- `pnp` -- `read_pg_spef` -- `read_vcd_cpp` -- `report_ir_drop` -- `report_power` -- `report_power_cpp` -- `run_ai_placement` -- `run_incremental_flow` -- `run_legalize` -- `run_macro_placement` -- `run_placement` -- `run_refinement` -- `run_timing_opt_drv` -- `run_timing_opt_hold` -- `run_timing_opt_setup` -- `run_to` -- `update_and_get_all_pin_timings` -- `vectors_nets_patterns_to_def` -- `vectors_nets_to_def` - -## Wrapper methods with no `ecc_py` calls - -Informational only (stubs or pure-Python helpers; not evaluated by the disabled/absent rule): - -- `__init__` -- `build_timing_graph` -- `close` -- `convert_idb_to_timing_netlist` -- `create_data_flow` -- `get_ecc` -- `get_net_name` -- `get_segment_capacitance` -- `get_segment_resistance` -- `get_used_libs` -- `get_wire_timing_data` -- `init_floorplan_by_area` -- `init_floorplan_by_core_utilization` -- `init_log` -- `init_sta` -- `is_db_data_exists` -- `is_rt_timing_enable` -- `link_design` -- `make_rc_tree_edge` -- `make_rc_tree_inner_node` -- `make_rc_tree_obj_node` -- `read_lef_def` -- `read_liberty` -- `read_netlist` -- `read_sdc` -- `read_spef` -- `release_sta` -- `report_sta` -- `report_timing` -- `run_sta` -- `set_design_workspace` -- `set_exclude_cell_names` -- `update_clock_timing` -- `update_rc_tree_info` -- `update_timing` - -## List-element `None` audit - -Today the wrapper normalizes list arguments with `path_texts()` -(`chipcompiler/utility/path.py`), which silently drops `None` elements. After -the `std::vector` conversion a `None` element raises -`TypeError` instead. The lists reaching `db_init` / `lef_init` / `lib_init` -were traced through every producer in `chipcompiler/tools/ecc/module.py` and -its callers: - -- `init_lefs(workspace.pdk.lefs)` (`runner.py`): `pdk.lefs` is built in - `chipcompiler/data/pdk.py` as `[path for path in lef_paths if path.is_file()]` - — a list of existing `Path` objects; elements are never `None`. -- `run_timing(lib_paths=...)` (`runner.py`): receives `workspace.pdk.libs` - (same filtered `Path` list construction) and validates every element with - `os.path.isfile` before the call; `module.py` itself coerces a `None` *list* - to `[]` (`if lib_paths is None: lib_paths = []`). -- `write_timing_model(lib_paths=signoff_item["liberty_files"])` (`runner.py`): - `collect_sta_signoff_items` reads `liberty.get("path", [])` from the sta - config JSON — a JSON array of strings. The parallel `run_sta` flow validates - the same list with `os.path.exists` per element, which already raises - `TypeError` on a `null` element before any binding call. -- `update_sta_data_config(lib_paths=...)`: only exercised by tests with real - `Path` lists. - -Conclusion: `None` *elements* cannot occur in any current producer — the only -`None` value in play is the list argument itself, which `module.py` already -coerces to `[]` before calling `path_texts`. A hand-edited sta config JSON -with a `null` array element is the only theoretical injection point; there the -conversion changes a silent drop into a loud `TypeError`, which is the desired -behavior. diff --git a/scripts/binding_census/dead_bindings.py b/scripts/binding_census/dead_bindings.py deleted file mode 100755 index 06d5a69ff4..0000000000 --- a/scripts/binding_census/dead_bindings.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python -"""Dead-binding audit for the ecc_py binding census (--ecc-root). - -Parses the ecc wrapper (``chipcompiler/tools/ecc/module.py`` in the outer -repo, read-only) with :mod:`ast`, classifies every ``self.ecc.(`` call -against the census as active / disabled (file:line) / absent, and renders -``dead_bindings.md``. -""" -import ast -from pathlib import Path - - -def audit_wrapper(module_py: Path, discovery: dict) -> dict: - """Parse the ecc wrapper module and classify every ``self.ecc.(`` call. - - Returns a structured audit: per-method call rows, the dead-method - candidates (every call disabled or absent), and methods with no calls. - """ - tree = ast.parse(module_py.read_text()) - wrapper_class = next( - (node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == "ECCToolsModule"), - None, - ) - if wrapper_class is None: - raise ValueError(f"ECCToolsModule class not found in {module_py}") - status_by_name: dict[str, str] = {} - for binding in discovery["bindings"]: - label = ( - "active" - if binding.status_in_source == "active" - else f"disabled ({binding.file}:{binding.line})" - ) - previous = status_by_name.get(binding.py_name) - if previous is None or (previous.startswith("disabled") and binding.status_in_source == "active"): - status_by_name[binding.py_name] = label - rows: list[dict] = [] - dead: list[str] = [] - without_calls: list[str] = [] - for node in wrapper_class.body: - if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - continue - calls: list[dict] = [] - seen: set[str] = set() - for sub in ast.walk(node): - if ( - isinstance(sub, ast.Call) - and isinstance(sub.func, ast.Attribute) - and isinstance(sub.func.value, ast.Attribute) - and sub.func.value.attr == "ecc" - and isinstance(sub.func.value.value, ast.Name) - and sub.func.value.value.id == "self" - ): - name = sub.func.attr - if name in seen: - continue - seen.add(name) - calls.append({"binding": name, "status": status_by_name.get(name, "absent")}) - if not calls: - without_calls.append(node.name) - continue - rows.append({"method": node.name, "line": node.lineno, "calls": calls}) - if all(call["status"] != "active" for call in calls): - dead.append(node.name) - return { - "rows": rows, - "dead_method_candidates": sorted(dead), - "methods_without_calls": sorted(without_calls), - } - - -LIST_NONE_AUDIT = """\ -## List-element `None` audit - -Today the wrapper normalizes list arguments with `path_texts()` -(`chipcompiler/utility/path.py`), which silently drops `None` elements. After -the `std::vector` conversion a `None` element raises -`TypeError` instead. The lists reaching `db_init` / `lef_init` / `lib_init` -were traced through every producer in `chipcompiler/tools/ecc/module.py` and -its callers: - -- `init_lefs(workspace.pdk.lefs)` (`runner.py`): `pdk.lefs` is built in - `chipcompiler/data/pdk.py` as `[path for path in lef_paths if path.is_file()]` - — a list of existing `Path` objects; elements are never `None`. -- `run_timing(lib_paths=...)` (`runner.py`): receives `workspace.pdk.libs` - (same filtered `Path` list construction) and validates every element with - `os.path.isfile` before the call; `module.py` itself coerces a `None` *list* - to `[]` (`if lib_paths is None: lib_paths = []`). -- `write_timing_model(lib_paths=signoff_item["liberty_files"])` (`runner.py`): - `collect_sta_signoff_items` reads `liberty.get("path", [])` from the sta - config JSON — a JSON array of strings. The parallel `run_sta` flow validates - the same list with `os.path.exists` per element, which already raises - `TypeError` on a `null` element before any binding call. -- `update_sta_data_config(lib_paths=...)`: only exercised by tests with real - `Path` lists. - -Conclusion: `None` *elements* cannot occur in any current producer — the only -`None` value in play is the list argument itself, which `module.py` already -coerces to `[]` before calling `path_texts`. A hand-edited sta config JSON -with a `null` array element is the only theoretical injection point; there the -conversion changes a silent drop into a loud `TypeError`, which is the desired -behavior. -""" - - -def render_dead_bindings_md(audit: dict) -> str: - lines = [ - "# Dead-binding audit", - "", - "Generated by `binding_census.py --ecc-root` from the ecc wrapper " - "(`chipcompiler/tools/ecc/module.py`) joined against the binding " - "census. Regenerate with:", - "", - "```sh", - "uv run --no-project --with jsonschema python scripts/binding_census/binding_census.py \\", - " --ecc-root ", - "```", - "", - "Statuses: `active` = bound and registered in `ecc_py`; " - "`disabled (file:line)` = an `m.def` exists but is commented out or its " - "register function is never called; `absent` = no `m.def` anywhere in " - "the `ecc_py` census (the call would raise `AttributeError` at runtime). " - "Note that a sibling native module (e.g. `ipower_cpp`, built from " - "`src/operation/refactor`) may bind the same name under a different " - "Python module; that does not make the `ecc_py` call live.", - "", - "| Wrapper method | Called binding | Status |", - "|---|---|---|", - ] - for row in audit["rows"]: - for call in row["calls"]: - lines.append(f"| `{row['method']}` | `{call['binding']}` | {call['status']} |") - lines += [ - "", - "## Dead-method candidates", - "", - "Wrapper methods whose every `ecc_py` call is disabled or absent:", - "", - ] - for name in audit["dead_method_candidates"]: - lines.append(f"- `{name}`") - lines += [ - "", - "## Wrapper methods with no `ecc_py` calls", - "", - "Informational only (stubs or pure-Python helpers; not evaluated by the " - "disabled/absent rule):", - "", - ] - for name in audit["methods_without_calls"]: - lines.append(f"- `{name}`") - lines += ["", LIST_NONE_AUDIT.strip(), ""] - return "\n".join(lines) diff --git a/scripts/binding_census/lexer.py b/scripts/binding_census/lexer.py deleted file mode 100755 index 92462b5253..0000000000 --- a/scripts/binding_census/lexer.py +++ /dev/null @@ -1,468 +0,0 @@ -#!/usr/bin/env python -"""Lexical discovery scanner for the ecc_py binding census. - -A small lexer (a state machine over code / line comment / block comment / -string literal / char literal with paren-brace-bracket depth tracking — no -cross-line regex) that finds every module-level ``m.def(`` in the register -files, including multiline and commented-out statements, extracts the -``py::arg("name") = default`` entries at paren depth 1, and resolves whether -a binding is ``active`` or ``disabled`` (commented statement, or its -enclosing ``register_*`` function is never called from -``python_moodule.cc``). -""" -import re -from dataclasses import dataclass, field -from pathlib import Path - -PYTHON_INTERFACE_DIR = Path("src/interface/python") -MODULE_CC = PYTHON_INTERFACE_DIR / "python_moodule.cc" - - -@dataclass(frozen=True) -class Span: - kind: str # "code" | "line_comment" | "block_comment" | "string" | "char" - start: int - end: int # exclusive - start_line: int # 1-based line of span start - - -@dataclass(frozen=True) -class DiscoveredParam: - name: str - default: str | None - - def to_json(self) -> dict: - return {"name": self.name, "default": self.default} - - -@dataclass -class DiscoveredBinding: - module: str - file: str - line: int - py_name: str - cpp_target: str - register_function: str | None - commented: bool - status_in_source: str # "active" | "disabled" - params: list[DiscoveredParam] = field(default_factory=list) - raw: str = "" - - def to_json(self) -> dict: - return { - "module": self.module, - "file": self.file, - "line": self.line, - "py_name": self.py_name, - "cpp_target": self.cpp_target, - "register_function": self.register_function, - "commented": self.commented, - "status_in_source": self.status_in_source, - "params": [p.to_json() for p in self.params], - } - - -def lex_spans(text: str) -> list[Span]: - """Split C++ source into lexical spans with a state machine. - - States: code, line comment, block comment, string literal (with escapes), - char literal (with escapes). Newlines inside block comments and literals - are tracked so every span carries an accurate start line. - """ - spans: list[Span] = [] - i = 0 - n = len(text) - line = 1 - state = "code" - span_start = 0 - span_line = 1 - - def emit(kind: str, end: int) -> None: - if end > span_start: - spans.append(Span(kind, span_start, end, span_line)) - - while i < n: - c = text[i] - nxt = text[i + 1] if i + 1 < n else "" - if state == "code": - if c == "/" and nxt == "/": - emit("code", i) - state, span_start, span_line = "line_comment", i, line - i += 2 - continue - if c == "/" and nxt == "*": - emit("code", i) - state, span_start, span_line = "block_comment", i, line - i += 2 - continue - if c == '"': - emit("code", i) - state, span_start, span_line = "string", i, line - i += 1 - continue - if c == "'": - emit("code", i) - state, span_start, span_line = "char", i, line - i += 1 - continue - if c == "\n": - line += 1 - i += 1 - elif state == "line_comment": - if c == "\n": - emit("line_comment", i) - state, span_start, span_line = "code", i, line - line += 1 - i += 1 - elif state == "block_comment": - if c == "*" and nxt == "/": - emit("block_comment", i + 2) - state, span_start, span_line = "code", i + 2, line - i += 2 - continue - if c == "\n": - line += 1 - i += 1 - else: # string or char literal - quote = '"' if state == "string" else "'" - if c == "\\": - i += 2 - continue - if c == quote: - emit(state, i + 1) - state, span_start, span_line = "code", i + 1, line - i += 1 - continue - if c == "\n": - line += 1 - i += 1 - emit(state, n) - return spans - - -_IDENT_CHARS = frozenset("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") - - -def _span_index_at(spans: list[Span], pos: int) -> int: - """Binary search: index of the span containing ``pos``.""" - lo, hi = 0, len(spans) - 1 - while lo < hi: - mid = (lo + hi) // 2 - if pos >= spans[mid].end: - lo = mid + 1 - else: - hi = mid - return lo - - -def _find_m_def_calls(text: str, spans: list[Span]) -> list[tuple[int, bool]]: - """Locate every ``m.def(`` occurrence in code and comment spans. - - Returns (position of the ``m``, commented) pairs. Class-member ``.def(`` - chained on a ``py::class_<...>(m, ...)`` object is rejected because the - character before ``.def`` is not the module identifier ``m``. - """ - hits: list[tuple[int, bool]] = [] - for span in spans: - if span.kind not in ("code", "line_comment", "block_comment"): - continue - seg = text[span.start : span.end] - j = 0 - while True: - k = seg.find("m.def", j) - if k == -1: - break - j = k + 5 - abs_pos = span.start + k - before = text[abs_pos - 1] if abs_pos > 0 else "" - if before in _IDENT_CHARS or before == ".": - continue - rest = seg[k + 5 :] - if re.match(r"\s*\(", rest): - hits.append((abs_pos, span.kind != "code")) - return hits - - -def _skip_span(spans: list[Span], si: int, i: int) -> tuple[int, int]: - """Advance (span index, position) past the current position.""" - while si + 1 < len(spans) and i >= spans[si + 1].start: - si += 1 - return si, i - - -def _matching_paren(text: str, spans: list[Span], open_paren: int) -> int: - """Return the index just past the ')' matching text[open_paren].""" - depth = 0 - si = _span_index_at(spans, open_paren) - i = open_paren - while i < len(text): - span = spans[si] - if span.kind != "code": - i = span.end - si, i = _skip_span(spans, si, i) - continue - c = text[i] - if c == "(": - depth += 1 - elif c == ")": - depth -= 1 - if depth == 0: - return i + 1 - i += 1 - raise ValueError(f"unbalanced parens from offset {open_paren}") - - -def _split_top_level_args(text: str, spans: list[Span], start: int, end: int) -> list[tuple[int, int]]: - """Split the call argument range (start=index of '(', end=past ')') into - top-level argument ranges. - - Commas nested inside parens, braces (lambda bodies), brackets, string or - char literals, comments, and template angle brackets do not split. Angle - brackets are tracked heuristically (a ``>`` only closes when angle depth - is positive and it is not part of ``->``); sufficient for the default - expressions used in these register files, e.g. - ``std::map{}``. - """ - args: list[tuple[int, int]] = [] - paren = brace = bracket = angle = 0 - arg_start = start + 1 - si = _span_index_at(spans, start) - i = start + 1 - limit = end - 1 - while i < limit: - span = spans[si] - if span.kind != "code": - i = span.end - si, i = _skip_span(spans, si, i) - continue - c = text[i] - if c == "(": - paren += 1 - elif c == ")": - paren -= 1 - elif c == "{": - brace += 1 - elif c == "}": - brace -= 1 - elif c == "[": - bracket += 1 - elif c == "]": - bracket -= 1 - elif c == "<": - angle += 1 - elif c == ">": - if angle > 0 and text[i - 1] != "-": - angle -= 1 - elif c == "," and paren == 0 and brace == 0 and bracket == 0 and angle == 0: - args.append((arg_start, i)) - arg_start = i + 1 - i += 1 - if text[arg_start:limit].strip(): - args.append((arg_start, limit)) - return args - - -def _decode_string_literal(literal: str) -> str: - """Decode the contents of a double-quoted C++ string literal.""" - body = literal[1:-1] - out: list[str] = [] - i = 0 - escapes = {"n": "\n", "t": "\t", "r": "\r", "0": "\0", "\\": "\\", '"': '"', "'": "'"} - while i < len(body): - c = body[i] - if c == "\\" and i + 1 < len(body): - out.append(escapes.get(body[i + 1], body[i + 1])) - i += 2 - else: - out.append(c) - i += 1 - return "".join(out) - - -def _normalize_default(text: str) -> str: - """Whitespace-normalize a captured default expression.""" - return " ".join(text.split()) - - -def _string_literal_at(text: str, spans: list[Span], start: int, end: int) -> str | None: - """If the range holds exactly one string literal (plus whitespace), decode it.""" - si = _span_index_at(spans, start) - while si < len(spans) and spans[si].start < end: - span = spans[si] - if span.kind == "string" and span.start >= start and span.end <= end: - if text[start : span.start].strip() or text[span.end : end].strip(): - return None - return _decode_string_literal(text[span.start : span.end]) - si += 1 - return None - - -def _parse_py_arg(text: str, spans: list[Span], start: int, end: int) -> DiscoveredParam | None: - """Parse one top-level argument range as ``py::arg("name") = default``.""" - stripped = text[start:end] - match = re.match(r"\s*py::arg\s*\(", stripped) - if not match: - return None - open_paren = start + match.end() - 1 - close_paren = _matching_paren(text, spans, open_paren) - name = _string_literal_at(text, spans, open_paren + 1, close_paren - 1) - if name is None: - raise ValueError(f"py::arg without a plain string name at offset {start}") - rest = text[close_paren:end].strip() - default = None - if rest.startswith("="): - default = _normalize_default(rest[1:]) - elif rest: - raise ValueError(f"unexpected trailing tokens after py::arg at offset {start}: {rest!r}") - return DiscoveredParam(name=name, default=default) - - -def _parse_m_def_core( - text: str, spans: list[Span], m_pos: int -) -> tuple[str, str, list[DiscoveredParam], str]: - """Parse one m.def call whose ``m`` is at m_pos in lexed code text.""" - open_paren = text.index("(", m_pos) - end = _matching_paren(text, spans, open_paren) - args = _split_top_level_args(text, spans, open_paren, end) - if len(args) < 2: - raise ValueError(f"m.def with fewer than two arguments at offset {m_pos}") - py_name = _string_literal_at(text, spans, args[0][0], args[0][1]) - if py_name is None: - raise ValueError(f"m.def first argument is not a string literal at offset {m_pos}") - target_text = text[args[1][0] : args[1][1]].strip() - if target_text.startswith("["): - cpp_target = "" - else: - cpp_target = target_text.lstrip("&").split()[0].rstrip(",") - params: list[DiscoveredParam] = [] - for arg_start, arg_end in args[2:]: - param = _parse_py_arg(text, spans, arg_start, arg_end) - if param is not None: - params.append(param) - return py_name, cpp_target, params, text[m_pos:end] - - -def _parse_m_def_statement( - text: str, spans: list[Span], m_pos: int, commented: bool -) -> tuple[str, str, int, list[DiscoveredParam], str]: - """Parse one m.def statement starting at the ``m`` of ``m.def``. - - Returns (py_name, cpp_target, start_line, params, raw_statement). For - commented-out statements the enclosing comment's content is stripped of - its comment markers and re-lexed as code, so strings and nesting inside - it are handled by the same machinery. - """ - start_line = text.count("\n", 0, m_pos) + 1 - if commented: - span = spans[_span_index_at(spans, m_pos)] - sub = text[m_pos : span.end] - if span.kind == "block_comment": - lines = sub.split("\n") - lines = [lines[0]] + [re.sub(r"^\s*\*", "", line) for line in lines[1:]] - sub = re.sub(r"\*/\s*$", "", "\n".join(lines)) - py_name, cpp_target, params, raw = _parse_m_def_core(sub, lex_spans(sub), 0) - return py_name, cpp_target, start_line, params, raw - py_name, cpp_target, params, raw = _parse_m_def_core(text, spans, m_pos) - return py_name, cpp_target, start_line, params, raw - - -def _find_register_function_bodies(text: str, spans: list[Span]) -> list[tuple[str, int, int]]: - """Return (name, body_start, body_end) for every ``register_*`` function - definition in the file.""" - code_only = list(text) - for span in spans: - if span.kind != "code": - for pos in range(span.start, span.end): - if code_only[pos] != "\n": - code_only[pos] = " " - code_text = "".join(code_only) - bodies = [] - for match in re.finditer(r"\bregister_\w+\s*\([^)]*\)\s*\{", code_text): - name = match.group(0).split("(")[0].strip() - open_brace = match.end() - 1 - depth = 0 - i = open_brace - while i < len(code_text): - if code_text[i] == "{": - depth += 1 - elif code_text[i] == "}": - depth -= 1 - if depth == 0: - break - i += 1 - bodies.append((name, open_brace, i + 1)) - return bodies - - -def parse_called_register_functions(text: str) -> set[str]: - """Parse python_moodule.cc for the set of ``register_*`` calls that are - actually made (commented-out calls are excluded).""" - spans = lex_spans(text) - called: set[str] = set() - for span in spans: - if span.kind != "code": - continue - for match in re.finditer(r"\b(register_\w+)\s*\(", text[span.start : span.end]): - called.add(match.group(1)) - return called - - -def discover_bindings_in_text( - text: str, module: str, file: str, called_registers: set[str] -) -> list[DiscoveredBinding]: - """Discover every module-level m.def binding in one register file.""" - spans = lex_spans(text) - bodies = _find_register_function_bodies(text, spans) - bindings: list[DiscoveredBinding] = [] - for m_pos, commented in _find_m_def_calls(text, spans): - py_name, cpp_target, line, params, raw = _parse_m_def_statement(text, spans, m_pos, commented) - register_function = next( - (name for name, body_start, body_end in bodies if body_start <= m_pos < body_end), None - ) - if commented or register_function is None or register_function not in called_registers: - status = "disabled" - else: - status = "active" - bindings.append( - DiscoveredBinding( - module=module, - file=file, - line=line, - py_name=py_name, - cpp_target=cpp_target, - register_function=register_function, - commented=commented, - status_in_source=status, - params=params, - raw=raw, - ) - ) - return bindings - - -def _register_files(repo_root: Path) -> list[Path]: - base = repo_root / PYTHON_INTERFACE_DIR - files = sorted(base.glob("py_*/py_register_*.h")) + sorted(base.glob("py_*/py_register_*.cpp")) - return files - - -def discover(repo_root: Path) -> dict: - """Run discovery over the whole python interface tree.""" - module_cc = repo_root / MODULE_CC - called = parse_called_register_functions(module_cc.read_text()) - bindings: list[DiscoveredBinding] = [] - for path in _register_files(repo_root): - rel = path.relative_to(repo_root).as_posix() - module = path.parent.name - bindings.extend(discover_bindings_in_text(path.read_text(), module, rel, called)) - return { - "called_register_functions": sorted(called), - "bindings": bindings, - } - - -def discovery_to_json(discovery: dict) -> dict: - return { - "called_register_functions": discovery["called_register_functions"], - "bindings": [b.to_json() for b in discovery["bindings"]], - } diff --git a/scripts/binding_census/manifest.json b/scripts/binding_census/manifest.json deleted file mode 100644 index 98c7efb15a..0000000000 --- a/scripts/binding_census/manifest.json +++ /dev/null @@ -1,2623 +0,0 @@ -{ - "entries": [ - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "db config JSON file; wrapper init_config/update_step_paths pass path_text(db_config)", - "cpp_target": "db_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 28, - "module": "py_config", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "config_path", - "py_name": "db_init", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "DEF file; empty string is the unset sentinel", - "cpp_target": "db_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 28, - "module": "py_config", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "def_path", - "py_name": "db_init", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "feature output directory; wrapper passes path_text(feature_dir)", - "cpp_target": "db_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 28, - "module": "py_config", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "feature_path", - "py_name": "db_init", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "cell LEF file list; empty list is the unset sentinel, no optional-ization", - "cpp_target": "db_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 28, - "module": "py_config", - "new_default": "std::vector{}", - "new_type": "std::vector", - "old_default": "std::vector {}", - "old_type": "const std::vector&", - "param": "lef_paths", - "py_name": "db_init", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "Liberty file list; wrapper update_sta_data_config passes path_texts(lib_paths); empty list is the unset sentinel", - "cpp_target": "db_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 28, - "module": "py_config", - "new_default": "std::vector{}", - "new_type": "std::vector", - "old_default": "std::vector{}", - "old_type": "const std::vector&", - "param": "lib_paths", - "py_name": "db_init", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "output directory; wrapper passes path_text(output_dir)", - "cpp_target": "db_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 28, - "module": "py_config", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "output_path", - "py_name": "db_init", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "SDC constraints file; empty string is the unset sentinel", - "cpp_target": "db_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 28, - "module": "py_config", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "sdc_path", - "py_name": "db_init", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "technology LEF file; empty string is the unset sentinel in db_init's C++ body", - "cpp_target": "db_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 28, - "module": "py_config", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "tech_lef_path", - "py_name": "db_init", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "netlist Verilog file; empty string is the unset sentinel", - "cpp_target": "db_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 28, - "module": "py_config", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "verilog_path", - "py_name": "db_init", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "flow config JSON file; wrapper init_config passes it positionally (ECCToolsModule.init_config)", - "cpp_target": "flow_init", - "file": "src/interface/python/py_config/py_register_config.h", - "line": 26, - "module": "py_config", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "flow_config", - "py_name": "flow_init", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "cpp_target": "", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 54, - "module": "py_eval", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "py_name": "cell_density", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "cpp_target": "", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 86, - "module": "py_eval", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "py_name": "egr_congestion", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "cpp_target": "eval_cell_hierarchy", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 181, - "module": "py_eval", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "py_name": "eval_cell_hierarchy", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "cpp_target": "eval_macro_connection", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 183, - "module": "py_eval", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "py_name": "eval_macro_connection", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "cpp_target": "eval_macro_hierarchy", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 182, - "module": "py_eval", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "py_name": "eval_macro_hierarchy", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "cpp_target": "eval_macro_io_pin_connection", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 185, - "module": "py_eval", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "py_name": "eval_macro_io_pin_connection", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "hierarchy tree plot output file (eval_*_hierarchy declarations take const std::string& plot_path)", - "cpp_target": "eval_macro_pin_connection", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 184, - "module": "py_eval", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "plot_path", - "py_name": "eval_macro_pin_connection", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "cpp_target": "", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 80, - "module": "py_eval", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "py_name": "lut_rudy_congestion", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "cpp_target": "", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 66, - "module": "py_eval", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "py_name": "net_density", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "cpp_target": "", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 60, - "module": "py_eval", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "py_name": "pin_density", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "plot output file; empty string skips saving (lambda forwards save_path to the eval implementation)", - "cpp_target": "", - "file": "src/interface/python/py_eval/py_register_eval.h", - "line": 74, - "module": "py_eval", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "save_path", - "py_name": "rudy_congestion", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "congestion map output directory (featureInst->save_cong_map target)", - "cpp_target": "feature_cong_map", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 40, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "dir", - "py_name": "feature_cong_map", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "flow step selector string forwarded to save_cong_map, not a filesystem path", - "cpp_target": "feature_cong_map", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 40, - "module": "py_feature", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "step", - "py_name": "feature_cong_map", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "CTS eval JSON output file", - "cpp_target": "feature_cts_eval", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 31, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "json_path", - "py_name": "feature_cts_eval", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "eval map output file (featureInst->save_eval_map target)", - "cpp_target": "feature_eval_map", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 33, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "feature_eval_map", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "eval summary output file", - "cpp_target": "feature_eval_summary", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 37, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "feature_eval_summary", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "DRC report input file consumed by the macro DRC feature builder", - "cpp_target": "feature_macro_drc", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 36, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "drc_path", - "py_name": "feature_macro_drc", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "macro DRC feature output file", - "cpp_target": "feature_macro_drc", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 36, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "feature_macro_drc", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "net eval output file", - "cpp_target": "feature_net_eval", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 39, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "feature_net_eval", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "placement eval JSON output file", - "cpp_target": "feature_pl_eval", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 30, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "json_path", - "py_name": "feature_pl_eval", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "route feature output file", - "cpp_target": "feature_route", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 34, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "feature_route", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "route feature input file read back by the feature API", - "cpp_target": "feature_route_read", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 35, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "feature_route_read", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "feature summary output file (featureInst->save_summary target)", - "cpp_target": "feature_summary", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 28, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "feature_summary", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "timing eval summary output file", - "cpp_target": "feature_timing_eval_summary", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 38, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "feature_timing_eval_summary", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "tool feature output file (featureInst->save_tools target)", - "cpp_target": "feature_tool", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 29, - "module": "py_feature", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "feature_tool", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "flow step selector string forwarded to save_tools, not a filesystem path", - "cpp_target": "feature_tool", - "file": "src/interface/python/py_feature/py_register_feature.h", - "line": 29, - "module": "py_feature", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "step", - "py_name": "feature_tool", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "CTS report output file", - "cpp_target": "CtsReport", - "file": "src/interface/python/py_icts/py_register_icts.h", - "line": 29, - "module": "py_icts", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "cts_report", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "CTS config JSON file; ECCToolsModule.run_cts passes path_text(config)", - "cpp_target": "CtsAutoRun", - "file": "src/interface/python/py_icts/py_register_icts.h", - "line": 28, - "module": "py_icts", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "cts_config", - "py_name": "run_cts", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "CTS working directory; ECCToolsModule.run_cts passes path_text(output)", - "cpp_target": "CtsAutoRun", - "file": "src/interface/python/py_icts/py_register_icts.h", - "line": 28, - "module": "py_icts", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "cts_work_dir", - "py_name": "run_cts", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "blockage type keyword, not a filesystem path", - "cpp_target": "clearBlockage", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 62, - "module": "py_idb", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "type", - "py_name": "clear_blockage", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "cell master name, not a filesystem path", - "cpp_target": "idbCreateInstance", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 66, - "module": "py_idb", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "cell_master", - "py_name": "create_inst", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance name to create, not a filesystem path", - "cpp_target": "idbCreateInstance", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 66, - "module": "py_idb", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "py_name": "create_inst", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "placement orientation keyword, not a filesystem path", - "cpp_target": "idbCreateInstance", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 66, - "module": "py_idb", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "orient", - "py_name": "create_inst", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "placement status keyword, not a filesystem path", - "cpp_target": "idbCreateInstance", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 66, - "module": "py_idb", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "status", - "py_name": "create_inst", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance type tag, not a filesystem path", - "cpp_target": "idbCreateInstance", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 66, - "module": "py_idb", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "type", - "py_name": "create_inst", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "connection type keyword, not a filesystem path", - "cpp_target": "idbCreateNet", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 68, - "module": "py_idb", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "conn_type", - "py_name": "create_net", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name to create, not a filesystem path", - "cpp_target": "idbCreateNet", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 68, - "module": "py_idb", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "py_name": "create_net", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "DEF file; ECCToolsModule.read_def passes path_text(path)", - "cpp_target": "initDef", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 37, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "def_path", - "py_name": "def_init", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "DEF output file; wrapper def_save passes path_text(def_path)", - "cpp_target": "saveDef", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 42, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "def_name", - "py_name": "def_save", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance name in the design database, not a filesystem path", - "cpp_target": "idbDeleteInstance", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 64, - "module": "py_idb", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "py_name": "delete_inst", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name in the design database, not a filesystem path", - "cpp_target": "idbDeleteNet", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 65, - "module": "py_idb", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "py_name": "delete_net", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "GDSII output file; wrapper gds_save passes path_text(output_path)", - "cpp_target": "saveGDSII", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 47, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "gds_name", - "py_name": "gds_save", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "report output file: idbGet forwards file_name to rptInst->reportInstance/reportNet (py_db_op.h), which write the report to that file", - "cpp_target": "idbGet", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 63, - "module": "py_idb", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "file_name", - "py_name": "idb_get", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance name filter, not a filesystem path", - "cpp_target": "idbGet", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 63, - "module": "py_idb", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "inst_name", - "py_name": "idb_get", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name filter, not a filesystem path", - "cpp_target": "idbGet", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 63, - "module": "py_idb", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "net_name", - "py_name": "idb_get", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "idb config JSON file; ECCToolsModule.idb_init passes path_text(config_path); py::arg-free binding, parameter name from the initIdb declaration", - "cpp_target": "initIdb", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 34, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "config_path", - "py_name": "idb_init", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "idb JSON output file (saveJson serializes the database)", - "cpp_target": "saveJson", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 48, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "json_save", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "cell LEF file list; ECCToolsModule.init_lefs passes path_texts(lef_paths); empty list is the unset sentinel", - "cpp_target": "initLef", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 36, - "module": "py_idb", - "new_default": null, - "new_type": "std::vector", - "old_default": null, - "old_type": "const std::vector&", - "param": "lef_paths", - "py_name": "lef_init", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "Liberty file list; ECCToolsModule.run_timing passes path_texts(lib_paths); empty list is the unset sentinel", - "cpp_target": "initLib", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 39, - "module": "py_idb", - "new_default": null, - "new_type": "std::vector", - "old_default": null, - "old_type": "const std::vector&", - "param": "lib_paths", - "py_name": "lib_init", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "serialized database input file/directory read by loadData", - "cpp_target": "loadData", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 53, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "load_data", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "cell master names to exclude from the netlist, not filesystem paths", - "cpp_target": "saveNetList", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 45, - "module": "py_idb", - "new_default": "std::set{}", - "new_type": "std::set", - "old_default": "std::set{}", - "old_type": "std::set", - "param": "exclude_cell_names", - "py_name": "netlist_save", - "required_or_optional": "optional", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "netlist output file (saveNetList writes a Verilog netlist)", - "cpp_target": "saveNetList", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 45, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "netlist_path", - "py_name": "netlist_save", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "serialized database output file/directory (saveData persists the DataManager state)", - "cpp_target": "saveData", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 51, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "save_data", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "SDC constraints file; initSdc stores the value as-is and the timing flow treats empty as unset; the production harden flow passes None natively (runner.py passes workspace.pdk.sdc which is Path|None), so the converted binding takes an optional path", - "cpp_target": "initSdc", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 40, - "module": "py_idb", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": null, - "old_type": "const std::string&", - "param": "sdc_path", - "py_name": "sdc_init", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name in the design database, not a filesystem path", - "cpp_target": "setNet", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 60, - "module": "py_idb", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "py_name": "set_net", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net type keyword (signal/power/ground), not a filesystem path", - "cpp_target": "setNet", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 60, - "module": "py_idb", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_type", - "py_name": "set_net", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "SPEF parasitics file; wrapper passes path_text(spef_path)", - "cpp_target": "initSpef", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 41, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "spef_path", - "py_name": "spef_init", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "macro placement TCL output file (saveMacroTCL writes a .tcl file)", - "cpp_target": "saveMacroTCL", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 44, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "tcl_name", - "py_name": "tcl_save", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "technology LEF file; ECCToolsModule.init_techlef passes path_text(tech_lef_path); py::arg-free binding, parameter name from the initTechLef declaration", - "cpp_target": "initTechLef", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 35, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "techlef_path", - "py_name": "tech_lef_init", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "design top module name, not a filesystem path", - "cpp_target": "initVerilog", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 38, - "module": "py_idb", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "top_module", - "py_name": "verilog_init", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "netlist Verilog file; wrapper read_verilog passes path_text(verilog)", - "cpp_target": "initVerilog", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 38, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "verilog_path", - "py_name": "verilog_init", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "view JSON edits input file read by applyViewJsonEdits", - "cpp_target": "applyViewJsonEdits", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 50, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "edits_path", - "py_name": "view_json_apply_edits", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "serialization format keyword (e.g. \"pretty\"), not a filesystem path", - "cpp_target": "saveViewJson", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 49, - "module": "py_idb", - "new_default": "\"pretty\"", - "new_type": "const std::string&", - "old_default": "\"pretty\"", - "old_type": "const std::string&", - "param": "json_format", - "py_name": "view_json_save", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "view JSON output directory", - "cpp_target": "saveViewJson", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 49, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "output_dir", - "py_name": "view_json_save", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "abstract LEF output file; ECCToolsModule.write_abstract_lef passes path_text(output_lef_path)", - "cpp_target": "writeAbstractLef", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 55, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "output_lef_path", - "py_name": "write_abstract_lef", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "harden core instance names embedded in the JSON, not filesystem paths", - "cpp_target": "writeSocJson", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 54, - "module": "py_idb", - "new_default": "std::vector{}", - "new_type": "const std::vector&", - "old_default": "std::vector{}", - "old_type": "const std::vector&", - "param": "harden_cores", - "py_name": "write_soc_json", - "required_or_optional": "optional", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "SoC JSON output file", - "cpp_target": "writeSocJson", - "file": "src/interface/python/py_idb/py_register_idb.h", - "line": 54, - "module": "py_idb", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "write_soc_json", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "DRC working directory; ECCToolsModule.init_drc passes path_text(output_dir)", - "cpp_target": "init_drc", - "file": "src/interface/python/py_idrc/py_register_idrc.h", - "line": 29, - "module": "py_idrc", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "temp_directory_path", - "py_name": "init_drc", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "DRC config JSON file; ECCToolsModule.run_drc passes path_text(config)", - "cpp_target": "run_drc", - "file": "src/interface/python/py_idrc/py_register_idrc.h", - "line": 30, - "module": "py_idrc", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "config", - "py_name": "run_drc", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "DRC report output file; ECCToolsModule.run_drc passes path_text(report_path)", - "cpp_target": "run_drc", - "file": "src/interface/python/py_idrc/py_register_idrc.h", - "line": 30, - "module": "py_idrc", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "report", - "py_name": "run_drc", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "DRC feature output file; ECCToolsModule.save_drc passes path_text(feature_path)", - "cpp_target": "save_drc", - "file": "src/interface/python/py_idrc/py_register_idrc.h", - "line": 31, - "module": "py_idrc", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "py_name": "save_drc", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "blockage rectangle as a coordinate string, not a filesystem path", - "cpp_target": "fpAddPlacementBlockage", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 33, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "box", - "py_name": "add_placement_blockage", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "halo distance encoded as a string, not a filesystem path", - "cpp_target": "fpAddPlacementHalo", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 34, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "distance", - "py_name": "add_placement_halo", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance name, not a filesystem path", - "cpp_target": "fpAddPlacementHalo", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 34, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "py_name": "add_placement_halo", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "blockage rectangle as a coordinate string, not a filesystem path", - "cpp_target": "fpAddRoutingBlockage", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 35, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "box", - "py_name": "add_routing_blockage", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "routing layer name, not a filesystem path", - "cpp_target": "fpAddRoutingBlockage", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 35, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "py_name": "add_routing_blockage", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "halo distance encoded as a string, not a filesystem path", - "cpp_target": "fpAddRoutingHalo", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 36, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "distance", - "py_name": "add_routing_halo", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance name, not a filesystem path", - "cpp_target": "fpAddRoutingHalo", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 36, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "py_name": "add_routing_halo", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "routing layer name, not a filesystem path", - "cpp_target": "fpAddRoutingHalo", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 36, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "py_name": "add_routing_halo", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "pin layer name, not a filesystem path", - "cpp_target": "fpPlacePins", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 29, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "py_name": "auto_place_pins", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "die side names (e.g. \"left\"/\"right\"), not filesystem paths", - "cpp_target": "fpPlacePins", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 29, - "module": "py_ifp", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "sides", - "py_name": "auto_place_pins", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "routing layer name, not a filesystem path", - "cpp_target": "fpMakeTracks", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 28, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "py_name": "gern_track", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "core rectangle as a coordinate string, not a filesystem path", - "cpp_target": "fpInit", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 26, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "core_area", - "py_name": "init_floorplan", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "core site name from the technology LEF, not a filesystem path", - "cpp_target": "fpInit", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 26, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "core_site", - "py_name": "init_floorplan", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "corner site name from the technology LEF, not a filesystem path", - "cpp_target": "fpInit", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 26, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "corner_site", - "py_name": "init_floorplan", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "die rectangle as a coordinate string (\"llx lly urx ury\"), not a filesystem path", - "cpp_target": "fpInit", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 26, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "die_area", - "py_name": "init_floorplan", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "IO site name from the technology LEF, not a filesystem path", - "cpp_target": "fpInit", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 26, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "io_site", - "py_name": "init_floorplan", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "IO filler cell master names, not filesystem paths", - "cpp_target": "fpPlaceIOFiller", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 32, - "module": "py_ifp", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "filler_types", - "py_name": "place_io_filler", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance name prefix for created filler cells, not a filesystem path", - "cpp_target": "fpPlaceIOFiller", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 32, - "module": "py_ifp", - "new_default": "\"IOFill\"", - "new_type": "const std::string&", - "old_default": "\"IOFill\"", - "old_type": "const std::string&", - "param": "prefix", - "py_name": "place_io_filler", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "pin layer name, not a filesystem path", - "cpp_target": "fpPlacePort", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 30, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "py_name": "place_port", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "pin name, not a filesystem path", - "cpp_target": "fpPlacePort", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 30, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "pin_name", - "py_name": "place_port", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "endcap master name, not a filesystem path", - "cpp_target": "fpTapCell", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 37, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "endcap", - "py_name": "tapcell", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "tapcell master name, not a filesystem path", - "cpp_target": "fpTapCell", - "file": "src/interface/python/py_ifp/py_register_ifp.h", - "line": 37, - "module": "py_ifp", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "tapcell", - "py_name": "tapcell", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "cell master name, not a filesystem path", - "cpp_target": "fpPlaceInst", - "file": "src/interface/python/py_instance/py_register_inst.h", - "line": 26, - "module": "py_instance", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "cellmaster", - "py_name": "place_instance", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance name, not a filesystem path", - "cpp_target": "fpPlaceInst", - "file": "src/interface/python/py_instance/py_register_inst.h", - "line": 26, - "module": "py_instance", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "inst_name", - "py_name": "place_instance", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "placement orientation keyword, not a filesystem path", - "cpp_target": "fpPlaceInst", - "file": "src/interface/python/py_instance/py_register_inst.h", - "line": 26, - "module": "py_instance", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "orient", - "py_name": "place_instance", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance source tag: DataManager::placeInst forwards source to instance->set_type (idm_design_inst.cpp), a provenance label with no filesystem semantics", - "cpp_target": "fpPlaceInst", - "file": "src/interface/python/py_instance/py_register_inst.h", - "line": 26, - "module": "py_instance", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "source", - "py_name": "place_instance", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "pin direction keyword, not a filesystem path", - "cpp_target": "pdnAddIO", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 26, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "direction", - "py_name": "add_pdn_io", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "cpp_target": "pdnAddIO", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 26, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "py_name": "add_pdn_io", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "IO pin name, not a filesystem path", - "cpp_target": "pdnAddIO", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 26, - "module": "py_ipdn", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "pin_name", - "py_name": "add_pdn_io", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "cpp_target": "pdnAddSegmentStripe", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 39, - "module": "py_ipdn", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "layer", - "py_name": "add_segment_stripe", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "end layer name, not a filesystem path", - "cpp_target": "pdnAddSegmentStripe", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 39, - "module": "py_ipdn", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "layer_end", - "py_name": "add_segment_stripe", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "start layer name, not a filesystem path", - "cpp_target": "pdnAddSegmentStripe", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 39, - "module": "py_ipdn", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "layer_start", - "py_name": "add_segment_stripe", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "cpp_target": "pdnAddSegmentStripe", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 39, - "module": "py_ipdn", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "net_name", - "py_name": "add_segment_stripe", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "bottom layer name, not a filesystem path", - "cpp_target": "pdnAddSegmentVia", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 42, - "module": "py_ipdn", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "bottom_layer", - "py_name": "add_segment_via", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "cpp_target": "pdnAddSegmentVia", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 42, - "module": "py_ipdn", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "layer", - "py_name": "add_segment_via", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "cpp_target": "pdnAddSegmentVia", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 42, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "py_name": "add_segment_via", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "top layer name, not a filesystem path", - "cpp_target": "pdnAddSegmentVia", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 42, - "module": "py_ipdn", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "top_layer", - "py_name": "add_segment_via", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "cpp_target": "pdnConnectIOPin", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 37, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "py_name": "connectIoPinToPower", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "ground pin names, not filesystem paths", - "cpp_target": "pdnConnectMacro", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 35, - "module": "py_ipdn", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "ground_pins", - "py_name": "connectMacroPdn", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "orientation keyword, not a filesystem path", - "cpp_target": "pdnConnectMacro", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 35, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "orient", - "py_name": "connectMacroPdn", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "PDN layer name, not a filesystem path", - "cpp_target": "pdnConnectMacro", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 35, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "pdn_layer", - "py_name": "connectMacroPdn", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "macro pin layer name, not a filesystem path", - "cpp_target": "pdnConnectMacro", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 35, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "pin_layer", - "py_name": "connectMacroPdn", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "power pin names, not filesystem paths", - "cpp_target": "pdnConnectMacro", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 35, - "module": "py_ipdn", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "power_pins", - "py_name": "connectMacroPdn", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "cpp_target": "pdnConnectStripe", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 38, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "py_name": "connectPowerStripe", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "cpp_target": "pdnConnectStripe", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 38, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "py_name": "connectPowerStripe", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "layer name pair to connect, not filesystem paths", - "cpp_target": "pdnConnectLayer", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 34, - "module": "py_ipdn", - "new_default": null, - "new_type": "std::vector&", - "old_default": null, - "old_type": "std::vector&", - "param": "layers", - "py_name": "connect_two_layer", - "required_or_optional": "required", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "cpp_target": "pdnCreateGrid", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 30, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer_name", - "py_name": "create_grid", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "ground net name, not a filesystem path", - "cpp_target": "pdnCreateGrid", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 30, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name_ground", - "py_name": "create_grid", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "power net name, not a filesystem path", - "cpp_target": "pdnCreateGrid", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 30, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name_power", - "py_name": "create_grid", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "cpp_target": "pdnCreateStripe", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 32, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer_name", - "py_name": "create_stripe", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "ground net name, not a filesystem path", - "cpp_target": "pdnCreateStripe", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 32, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name_ground", - "py_name": "create_stripe", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "power net name, not a filesystem path", - "cpp_target": "pdnCreateStripe", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 32, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name_power", - "py_name": "create_stripe", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance pin name pattern, not a filesystem path", - "cpp_target": "pdnGlobalConnect", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 27, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "instance_pin_name", - "py_name": "global_net_connect", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name, not a filesystem path", - "cpp_target": "pdnGlobalConnect", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 27, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "net_name", - "py_name": "global_net_connect", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "IO cell master name, not a filesystem path", - "cpp_target": "pdnPlacePort", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 28, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "io_cell_name", - "py_name": "place_pdn_port", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "layer name, not a filesystem path", - "cpp_target": "pdnPlacePort", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 28, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "layer", - "py_name": "place_pdn_port", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "pin name, not a filesystem path", - "cpp_target": "pdnPlacePort", - "file": "src/interface/python/py_ipdn/py_register_ipdn.h", - "line": 28, - "module": "py_ipdn", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "pin_name", - "py_name": "place_pdn_port", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "RCX config JSON file; ECCToolsModule.init_rcx passes path_text(config)", - "cpp_target": "init_rcx", - "file": "src/interface/python/py_ircx/py_register_ircx.h", - "line": 29, - "module": "py_ircx", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "config", - "py_name": "init_rcx", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "PDK identifier string (e.g. \"ics55\") selecting a built-in rule set, already std::optional; not a filesystem path", - "cpp_target": "init_rcx", - "file": "src/interface/python/py_ircx/py_register_ircx.h", - "line": 29, - "module": "py_ircx", - "new_default": "py::none()", - "new_type": "const std::optional&", - "old_default": "py::none()", - "old_type": "const std::optional&", - "param": "pdk", - "py_name": "init_rcx", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "router config JSON file; ECCToolsModule.run_routing passes path_text(config)", - "cpp_target": "initRT", - "file": "src/interface/python/py_irt/py_register_irt.h", - "line": 29, - "module": "py_irt", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "std::string&", - "param": "config", - "py_name": "init_rt", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "early-router config JSON file; ECCToolsModule.run_ert passes path_text(config)", - "cpp_target": "runERT", - "file": "src/interface/python/py_irt/py_register_irt.h", - "line": 30, - "module": "py_irt", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "std::string&", - "param": "config", - "py_name": "run_ert", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "STA config JSON file; ECCToolsModule.run_timing passes path_text(config)", - "cpp_target": "initSTA", - "file": "src/interface/python/py_ista/py_register_ista.h", - "line": 30, - "module": "py_ista", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "std::string&", - "param": "config", - "py_name": "init_sta", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "fanout-fix config JSON file; ECCToolsModule.run_net_opt passes path_text(config)", - "cpp_target": "fix_fanout", - "file": "src/interface/python/py_izh/py_register_izh.h", - "line": 29, - "module": "py_izh", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "config", - "py_name": "fix_fanout", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "filler config JSON file; ECCToolsModule.run_filler passes path_text(config)", - "cpp_target": "insert_filler", - "file": "src/interface/python/py_izh/py_register_izh.h", - "line": 30, - "module": "py_izh", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "config", - "py_name": "insert_filler", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "congestion report output file; empty string reports to stdout only", - "cpp_target": "reportCong", - "file": "src/interface/python/py_report/py_register_report.h", - "line": 30, - "module": "py_report", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "py_name": "report_congestion", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "dangling-net report output file; empty string reports to stdout only", - "cpp_target": "reportDanglingNet", - "file": "src/interface/python/py_report/py_register_report.h", - "line": 31, - "module": "py_report", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "py_name": "report_dangling_net", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "database summary report output file; empty string reports to stdout only", - "cpp_target": "reportDbSummary", - "file": "src/interface/python/py_report/py_register_report.h", - "line": 29, - "module": "py_report", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "py_name": "report_db", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "DRC report output path by API shape; the currently bound one-argument ReportManager::reportDRC overload is a stub with its write lines commented out (report_manager.cpp:186), pending re-enable", - "cpp_target": "reportDRC", - "file": "src/interface/python/py_report/py_register_report.h", - "line": 35, - "module": "py_report", - "new_default": null, - "new_type": "std::filesystem::path", - "old_default": null, - "old_type": "const std::string&", - "param": "path", - "py_name": "report_drc", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance name prefixes to bucket, not filesystem paths", - "cpp_target": "reportPlaceDistribution", - "file": "src/interface/python/py_report/py_register_report.h", - "line": 33, - "module": "py_report", - "new_default": "std::vector{}", - "new_type": "const std::vector&", - "old_default": "std::vector{}", - "old_type": "const std::vector&", - "param": "prefixes", - "py_name": "report_place_distribution", - "required_or_optional": "optional", - "scalar_or_list": "list" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "instance name prefix to report on, not a filesystem path", - "cpp_target": "reportPrefixedInst", - "file": "src/interface/python/py_report/py_register_report.h", - "line": 34, - "module": "py_report", - "new_default": null, - "new_type": "const std::string&", - "old_default": null, - "old_type": "const std::string&", - "param": "prefix", - "py_name": "report_prefixed_instance", - "required_or_optional": "required", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "non_path", - "classification_rationale": "net name filter for the route report, not a filesystem path", - "cpp_target": "reportRoute", - "file": "src/interface/python/py_report/py_register_report.h", - "line": 32, - "module": "py_report", - "new_default": "\"\"", - "new_type": "const std::string&", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "net", - "py_name": "report_route", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "route report output file; empty string reports to stdout only", - "cpp_target": "reportRoute", - "file": "src/interface/python/py_report/py_register_report.h", - "line": 32, - "module": "py_report", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "py_name": "report_route", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - }, - { - "binding_status": "active", - "classification": "path", - "classification_rationale": "wirelength report output file; empty string reports to stdout only", - "cpp_target": "reportWireLength", - "file": "src/interface/python/py_report/py_register_report.h", - "line": 28, - "module": "py_report", - "new_default": "py::none()", - "new_type": "std::optional", - "old_default": "\"\"", - "old_type": "const std::string&", - "param": "path", - "py_name": "report_wirelength", - "required_or_optional": "optional", - "scalar_or_list": "scalar" - } - ], - "version": 1 -} diff --git a/scripts/binding_census/manifest.py b/scripts/binding_census/manifest.py deleted file mode 100755 index 1088b4e840..0000000000 --- a/scripts/binding_census/manifest.py +++ /dev/null @@ -1,296 +0,0 @@ -#!/usr/bin/env python -"""Spec/manifest join, schema validation, and the --check gate for the -ecc_py binding census. - -The curated spec (``binding_spec.json``) carries, per in-scope (binding, -parameter), the old/new C++ types and defaults, scalar/list shape, -required/optional shape, and the path / non_path / ambiguous classification -with a written rationale. The manifest (``manifest.json``) is the -deterministic generated join of lexer discovery + spec. -""" -import json -from pathlib import Path - -from lexer import DiscoveredBinding, discover - -MANIFEST_VERSION = 1 - -PATH_TYPE_REQUIRED = "std::filesystem::path" -PATH_TYPE_OPTIONAL = "std::optional" -PATH_TYPE_LIST = "std::vector" - -# Machine-readable form of the reviewed classification baseline: the bindings -# and parameters that must exist in the curated spec with a `path` -# classification. Used by --check for coverage; the spec/manifest remain the -# final authority (any deviation is documented in baseline_diff.md). -BASELINE_PATH_PARAMS: dict[tuple[str, str], list[str]] = { - ("py_config", "flow_init"): ["flow_config"], - ("py_config", "db_init"): [ - "config_path", "tech_lef_path", "lef_paths", "def_path", "verilog_path", - "output_path", "feature_path", "lib_paths", "sdc_path", - ], - ("py_eval", "cell_density"): ["save_path"], - ("py_eval", "pin_density"): ["save_path"], - ("py_eval", "net_density"): ["save_path"], - ("py_eval", "rudy_congestion"): ["save_path"], - ("py_eval", "lut_rudy_congestion"): ["save_path"], - ("py_eval", "egr_congestion"): ["save_path"], - ("py_eval", "eval_cell_hierarchy"): ["plot_path"], - ("py_eval", "eval_macro_hierarchy"): ["plot_path"], - ("py_eval", "eval_macro_connection"): ["plot_path"], - ("py_eval", "eval_macro_pin_connection"): ["plot_path"], - ("py_eval", "eval_macro_io_pin_connection"): ["plot_path"], - ("py_feature", "feature_summary"): ["path"], - ("py_feature", "feature_tool"): ["path"], - ("py_feature", "feature_pl_eval"): ["json_path"], - ("py_feature", "feature_cts_eval"): ["json_path"], - ("py_feature", "feature_eval_map"): ["path"], - ("py_feature", "feature_route"): ["path"], - ("py_feature", "feature_route_read"): ["path"], - ("py_feature", "feature_macro_drc"): ["path", "drc_path"], - ("py_feature", "feature_eval_summary"): ["path"], - ("py_feature", "feature_timing_eval_summary"): ["path"], - ("py_feature", "feature_net_eval"): ["path"], - ("py_feature", "feature_cong_map"): ["dir"], - ("py_icts", "run_cts"): ["cts_config", "cts_work_dir"], - ("py_icts", "cts_report"): ["path"], - ("py_idb", "idb_init"): ["config_path"], - ("py_idb", "tech_lef_init"): ["techlef_path"], - ("py_idb", "def_init"): ["def_path"], - ("py_idb", "verilog_init"): ["verilog_path"], - ("py_idb", "sdc_init"): ["sdc_path"], - ("py_idb", "spef_init"): ["spef_path"], - ("py_idb", "lef_init"): ["lef_paths"], - ("py_idb", "lib_init"): ["lib_paths"], - ("py_idb", "def_save"): ["def_name"], - ("py_idb", "tcl_save"): ["tcl_name"], - ("py_idb", "gds_save"): ["gds_name"], - ("py_idb", "netlist_save"): ["netlist_path"], - ("py_idb", "json_save"): ["path"], - ("py_idb", "save_data"): ["path"], - ("py_idb", "load_data"): ["path"], - ("py_idb", "write_soc_json"): ["path"], - ("py_idb", "write_abstract_lef"): ["output_lef_path"], - ("py_idb", "view_json_save"): ["output_dir"], - ("py_idb", "view_json_apply_edits"): ["edits_path"], - ("py_idb", "idb_get"): ["file_name"], - ("py_idrc", "init_drc"): ["temp_directory_path"], - ("py_idrc", "run_drc"): ["config", "report"], - ("py_idrc", "save_drc"): ["path"], - ("py_irt", "init_rt"): ["config"], - ("py_irt", "run_ert"): ["config"], - ("py_ista", "init_sta"): ["config"], - ("py_ircx", "init_rcx"): ["config"], - ("py_izh", "fix_fanout"): ["config"], - ("py_izh", "insert_filler"): ["config"], - ("py_report", "report_wirelength"): ["path"], - ("py_report", "report_db"): ["path"], - ("py_report", "report_congestion"): ["path"], - ("py_report", "report_dangling_net"): ["path"], - ("py_report", "report_route"): ["path"], - ("py_report", "report_drc"): ["path"], -} - - -def load_json(path: Path) -> dict: - return json.loads(path.read_text()) - - -def load_schema(path: Path) -> dict: - return load_json(path) - - -def validate_spec(spec: dict, schema: dict) -> None: - import jsonschema - - jsonschema.validate(spec, schema) - - -def validate_manifest(manifest: dict, schema: dict) -> None: - import jsonschema - - jsonschema.validate(manifest, schema) - - -def build_manifest(discovery: dict, spec: dict) -> dict: - """Join the curated spec against discovery into manifest entries.""" - index: dict[tuple[str, str], DiscoveredBinding] = {} - for binding in discovery["bindings"]: - index.setdefault((binding.module, binding.py_name), binding) - entries: list[dict] = [] - for spec_binding in spec["bindings"]: - key = (spec_binding["module"], spec_binding["py_name"]) - discovered = index.get(key) - if discovered is None: - raise ValueError(f"spec binding not discovered: {key[0]}.{key[1]}") - for param in spec_binding["params"]: - entries.append( - { - "module": discovered.module, - "file": discovered.file, - "line": discovered.line, - "py_name": discovered.py_name, - "cpp_target": discovered.cpp_target, - "param": param["param"], - "old_type": param["old_type"], - "old_default": param["old_default"], - "new_type": param["new_type"], - "new_default": param["new_default"], - "scalar_or_list": param["scalar_or_list"], - "required_or_optional": param["required_or_optional"], - "binding_status": discovered.status_in_source, - "classification": param["classification"], - "classification_rationale": param["classification_rationale"], - } - ) - entries.sort(key=lambda e: (e["module"], e["py_name"], e["param"])) - return {"version": MANIFEST_VERSION, "entries": entries} - - -def _dumps(obj: dict) -> bytes: - return (json.dumps(obj, indent=2, sort_keys=True) + "\n").encode() - - -def generate_manifest_bytes(repo_root: Path, census_dir: Path) -> bytes: - discovery = discover(repo_root) - spec = load_json(census_dir / "binding_spec.json") - validate_spec(spec, load_schema(census_dir / "binding_spec.schema.json")) - manifest = build_manifest(discovery, spec) - validate_manifest(manifest, load_schema(census_dir / "manifest.schema.json")) - return _dumps(manifest) - - -def _is_string_literal_default(default: str | None) -> bool: - return default is not None and default.lstrip().startswith('"') - - -def check(repo_root: Path, census_dir: Path) -> list[str]: - """Run every census gate; return a list of failure messages (empty = pass).""" - failures: list[str] = [] - discovery = discover(repo_root) - manifest_path = census_dir / "manifest.json" - - loaded: dict[str, dict] = {} - for name in ("binding_spec.json", "manifest.json", "binding_spec.schema.json", "manifest.schema.json"): - try: - loaded[name] = load_json(census_dir / name) - except json.JSONDecodeError as exc: - failures.append(f"{name} is not valid JSON: {exc}") - except OSError as exc: - failures.append(f"{name} is missing or unreadable: {exc}") - if failures: - return failures - spec = loaded["binding_spec.json"] - spec_schema = loaded["binding_spec.schema.json"] - manifest_schema = loaded["manifest.schema.json"] - - # (b) schema validation for spec and committed manifest - import jsonschema - - try: - validate_spec(spec, spec_schema) - except jsonschema.ValidationError as exc: - failures.append(f"binding_spec.json fails schema validation: {exc.message}") - manifest = loaded["manifest.json"] - try: - validate_manifest(manifest, manifest_schema) - except jsonschema.ValidationError as exc: - failures.append(f"manifest.json fails schema validation: {exc.message}") - - # (a) regeneration must be byte-stable against the committed manifest - try: - rebuilt = build_manifest(discovery, spec) - except (KeyError, ValueError) as exc: - failures.append(f"manifest regeneration failed: {exc}") - return failures - regenerated = _dumps(rebuilt) - if regenerated != manifest_path.read_bytes(): - failures.append( - "manifest.json is out of date: regeneration is not byte-stable against the committed file" - ) - - bindings = discovery["bindings"] - discovered_index: dict[tuple[str, str], DiscoveredBinding] = {} - for binding in bindings: - discovered_index.setdefault((binding.module, binding.py_name), binding) - spec_index: dict[tuple[str, str], dict[str, dict]] = {} - for spec_binding in spec["bindings"]: - spec_index[(spec_binding["module"], spec_binding["py_name"])] = { - p["param"]: p for p in spec_binding["params"] - } - - # (c) coverage, forward: active bindings with string-literal py::arg defaults - for binding in bindings: - if binding.status_in_source != "active": - continue - spec_params = spec_index.get((binding.module, binding.py_name), {}) - for param in binding.params: - if _is_string_literal_default(param.default) and param.name not in spec_params: - failures.append( - f"coverage: active binding {binding.py_name} ({binding.file}:{binding.line}) has " - f'py::arg("{param.name}") with a string-literal default but no spec entry' - ) - - # (c) coverage, baseline-named path parameters must be spec'd as path - for (module, py_name), params in BASELINE_PATH_PARAMS.items(): - binding = discovered_index.get((module, py_name)) - if binding is None: - failures.append(f"baseline binding not discovered: {module}.{py_name}") - continue - if binding.status_in_source != "active": - failures.append(f"baseline binding {module}.{py_name} is not active") - continue - spec_params = spec_index.get((module, py_name), {}) - for param in params: - row = spec_params.get(param) - if row is None: - failures.append(f"coverage: baseline path parameter {module}.{py_name}.{param} has no spec entry") - elif row["classification"] != "path": - failures.append( - f"coverage: baseline path parameter {module}.{py_name}.{param} is classified " - f"{row['classification']} in the spec" - ) - - # (c) coverage, reverse: every spec entry names a discovered binding/param - for spec_binding in spec["bindings"]: - key = (spec_binding["module"], spec_binding["py_name"]) - binding = discovered_index.get(key) - if binding is None: - failures.append(f"spec entry names an undiscovered binding: {key[0]}.{key[1]}") - continue - if binding.params: - discovered_params = {p.name for p in binding.params} - for param in spec_binding["params"]: - if param["param"] not in discovered_params: - failures.append( - f"spec entry {key[0]}.{key[1]}.{param['param']} does not match any discovered " - f"py::arg of {key[1]}" - ) - # py::arg-free bindings: params are curated-only (documented limitation) - - # (d) ambiguous classifications must carry a rationale - for spec_binding in spec["bindings"]: - for param in spec_binding["params"]: - if param["classification"] == "ambiguous" and not param["classification_rationale"].strip(): - failures.append( - f"ambiguous classification without rationale: " - f"{spec_binding['module']}.{spec_binding['py_name']}.{param['param']}" - ) - - # (e) path-classified params of active bindings must carry the converted types - for entry in rebuilt["entries"]: - if entry["classification"] != "path" or entry["binding_status"] != "active": - continue - expected = ( - PATH_TYPE_LIST - if entry["scalar_or_list"] == "list" - else PATH_TYPE_OPTIONAL - if entry["required_or_optional"] == "optional" - else PATH_TYPE_REQUIRED - ) - if entry["new_type"] != expected: - failures.append( - f"new_type: {entry['module']}.{entry['py_name']}.{entry['param']} is {entry['scalar_or_list']}/" - f"{entry['required_or_optional']} path but has new_type {entry['new_type']!r} (expected {expected!r})" - ) - return failures diff --git a/scripts/binding_census/manifest.schema.json b/scripts/binding_census/manifest.schema.json deleted file mode 100644 index 1d8ba8b4d1..0000000000 --- a/scripts/binding_census/manifest.schema.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "ecc_py binding census manifest (generated)", - "type": "object", - "additionalProperties": false, - "required": ["version", "entries"], - "properties": { - "version": {"const": 1}, - "entries": { - "type": "array", - "items": {"$ref": "#/$defs/entry"} - } - }, - "$defs": { - "entry": { - "type": "object", - "additionalProperties": false, - "required": [ - "module", - "file", - "line", - "py_name", - "cpp_target", - "param", - "old_type", - "old_default", - "new_type", - "new_default", - "scalar_or_list", - "required_or_optional", - "binding_status", - "classification", - "classification_rationale" - ], - "properties": { - "module": {"type": "string", "pattern": "^py_[a-z]+$"}, - "file": {"type": "string", "minLength": 1}, - "line": {"type": "integer", "minimum": 1}, - "py_name": {"type": "string", "minLength": 1}, - "cpp_target": {"type": "string", "minLength": 1}, - "param": {"type": "string", "minLength": 1}, - "old_type": {"type": "string", "minLength": 1}, - "old_default": {"type": ["string", "null"]}, - "new_type": {"type": "string", "minLength": 1}, - "new_default": {"type": ["string", "null"]}, - "scalar_or_list": {"enum": ["scalar", "list"]}, - "required_or_optional": {"enum": ["required", "optional"]}, - "binding_status": {"enum": ["active", "disabled"]}, - "classification": {"enum": ["path", "non_path", "ambiguous"]}, - "classification_rationale": {"type": "string"} - }, - "allOf": [ - { - "if": { - "properties": {"classification": {"const": "ambiguous"}}, - "required": ["classification"] - }, - "then": { - "properties": {"classification_rationale": {"minLength": 1}} - } - } - ] - } - } -} diff --git a/scripts/binding_census/test_binding_census.py b/scripts/binding_census/test_binding_census.py deleted file mode 100755 index 5989ee9db2..0000000000 --- a/scripts/binding_census/test_binding_census.py +++ /dev/null @@ -1,333 +0,0 @@ -#!/usr/bin/env python -"""Tests for the ecc_py binding census lexer, manifest join, and --check gate.""" - -import json -import shutil -from pathlib import Path - -import pytest -from jsonschema import ValidationError - -import dead_bindings -import lexer -import manifest as census_manifest - -CENSUS_DIR = Path(__file__).resolve().parent -REPO_ROOT = CENSUS_DIR.parents[1] - -CALLED = {"register_eval", "register_imp", "register_ipdn", "register_irt", "register_good"} - - -def discover(text, module="py_test", called=CALLED): - return lexer.discover_bindings_in_text(text, module=module, file=f"{module}/py_register_test.h", called_registers=called) - - -# --------------------------------------------------------------------------- -# Lexer fixtures -# --------------------------------------------------------------------------- - -MULTILINE = """\ -void register_imp(pybind11::module& m) -{ - m.def( - "pydb", - [](idm::DataManager* db, int num_routing_grids_x, int num_routing_grids_y, bool with_routability, bool with_sta) { - return PyPlaceDB(db, num_routing_grids_x, num_routing_grids_y, with_routability, with_sta); - }, - "Convert PlaceDB to PyPlaceDB"); -} -""" - -COMMENTED = """\ -void register_imp(pybind11::module& m) -{ - m.def("active_one", active_one); - // m.def("runMP", runMP, py::arg("config"), py::arg("output_tcl") = ""); - /* - * m.def("runRef", runRef, py::arg("output_tcl") = ""); - */ -} -""" - -COMMENT_MARKERS_IN_STRINGS = """\ -void register_eval(py::module& m) -{ - m.def("fetch//doc", fetch, py::arg("url") = "http://example.com/*x*/y", py::arg("note") = "a // b"); -} -""" - -LAMBDA_WITH_BODY = """\ -void register_eval(py::module& m) -{ - m.def("cell_density", [](int bin_cnt_x = 256, int bin_cnt_y = 256, const std::string& save_path = "") -> py::tuple { - auto [max_density, avg_density] = cell_density(bin_cnt_x, bin_cnt_y, save_path); - return py::make_tuple(max_density, avg_density); - }, py::arg("bin_cnt_x") = 256, py::arg("bin_cnt_y") = 256, py::arg("save_path") = ""); -} -""" - -MAP_DEFAULT = """\ -void register_irt(py::module& m) -{ - m.def("init_rt", initRT, py::arg("config") = "", py::arg("config_dict") = std::map{}); -} -""" - -UPPERCASE_NAMES = """\ -void register_ipdn(py::module& m) -{ - m.def("connectMacroPdn", pdnConnectMacro, py::arg("pin_layer"), py::arg("orient")); - m.def("get_dmInst", &getDMInst, "A function which returns a DataManager instance", pybind11::return_value_policy::reference); -} -""" - -CLASS_CHAIN = """\ -void register_eval(py::module& m) -{ - py::class_(m, "TotalWLSummary") - .def_readwrite("HPWL", &ieval::TotalWLSummary::HPWL) - .def_readwrite("FLUTE", &ieval::TotalWLSummary::FLUTE) - .def("summary", &ieval::TotalWLSummary::summary); - m.def("total_wirelength_dict", []() -> py::dict { - py::dict result; - result["1"] = 0; - return result; - }); -} -""" - - -def test_multiline_m_def(): - (binding,) = discover(MULTILINE, module="py_imp") - assert binding.py_name == "pydb" - assert binding.cpp_target == "" - assert binding.line == 3 - assert binding.status_in_source == "active" - assert binding.params == [] - - -def test_line_and_block_commented_m_def(): - bindings = {b.py_name: b for b in discover(COMMENTED, module="py_imp")} - assert set(bindings) == {"active_one", "runMP", "runRef"} - assert bindings["active_one"].status_in_source == "active" - assert bindings["runMP"].status_in_source == "disabled" - assert bindings["runRef"].status_in_source == "disabled" - assert [(p.name, p.default) for p in bindings["runMP"].params] == [("config", None), ("output_tcl", '""')] - - -def test_comment_markers_inside_string_literals(): - (binding,) = discover(COMMENT_MARKERS_IN_STRINGS) - assert binding.py_name == "fetch//doc" - assert binding.status_in_source == "active" - assert [(p.name, p.default) for p in binding.params] == [ - ("url", '"http://example.com/*x*/y"'), - ("note", '"a // b"'), - ] - - -def test_lambda_target_with_commas_and_braces_in_body(): - (binding,) = discover(LAMBDA_WITH_BODY) - assert binding.cpp_target == "" - assert [(p.name, p.default) for p in binding.params] == [ - ("bin_cnt_x", "256"), - ("bin_cnt_y", "256"), - ("save_path", '""'), - ] - - -def test_py_arg_extraction_at_depth_one_with_template_commas(): - (binding,) = discover(MAP_DEFAULT) - assert [(p.name, p.default) for p in binding.params] == [ - ("config", '""'), - ("config_dict", "std::map{}"), - ] - - -def test_uppercase_binding_names_and_reference_target(): - bindings = {b.py_name: b for b in discover(UPPERCASE_NAMES)} - assert bindings["connectMacroPdn"].cpp_target == "pdnConnectMacro" - assert bindings["get_dmInst"].cpp_target == "getDMInst" - - -def test_class_chain_def_and_def_readwrite_not_collected(): - (binding,) = discover(CLASS_CHAIN) - assert binding.py_name == "total_wirelength_dict" - assert binding.cpp_target == "" - - -def _write_fake_repo(root: Path) -> None: - py_dir = root / "src" / "interface" / "python" - (py_dir / "py_good").mkdir(parents=True) - (py_dir / "py_gone").mkdir(parents=True) - (py_dir / "python_moodule.cc").write_text( - "PYBIND11_MODULE(ecc_py, m)\n" - "{\n" - " register_good(m);\n" - " // register_gone(m); // disabled: module removed\n" - "}\n" - ) - (py_dir / "py_good" / "py_register_good.h").write_text( - 'void register_good(py::module& m)\n{\n m.def("good_one", good_one);\n}\n' - ) - (py_dir / "py_gone" / "py_register_gone.h").write_text( - 'void register_gone(py::module& m)\n{\n m.def("gone_one", gone_one, py::arg("path") = "");\n}\n' - ) - - -def test_module_level_disable_via_uncalled_register_function(tmp_path): - _write_fake_repo(tmp_path) - discovery = lexer.discover(tmp_path) - bindings = {b.py_name: b for b in discovery["bindings"]} - assert bindings["good_one"].status_in_source == "active" - assert bindings["gone_one"].status_in_source == "disabled" - assert bindings["gone_one"].register_function == "register_gone" - assert bindings["good_one"].register_function == "register_good" - - -# --------------------------------------------------------------------------- -# Schema + --check gate -# --------------------------------------------------------------------------- - - -def _spec_param(**overrides): - param = { - "param": "save_path", - "old_type": "const std::string&", - "old_default": '""', - "new_type": "std::optional", - "new_default": "py::none()", - "scalar_or_list": "scalar", - "required_or_optional": "optional", - "classification": "path", - "classification_rationale": "output file path with empty-string unset sentinel", - } - param.update(overrides) - return param - - -def test_ambiguous_without_rationale_fails_schema_validation(): - spec = { - "version": 1, - "bindings": [ - { - "module": "py_eval", - "py_name": "cell_density", - "params": [_spec_param(classification="ambiguous", classification_rationale="")], - } - ], - } - with pytest.raises(ValidationError): - census_manifest.validate_spec(spec, census_manifest.load_schema(CENSUS_DIR / "binding_spec.schema.json")) - - -def _write_fake_census(census_dir: Path, spec: dict, manifest: dict) -> None: - census_dir.mkdir(parents=True, exist_ok=True) - for name in ("binding_spec.schema.json", "manifest.schema.json"): - shutil.copy(CENSUS_DIR / name, census_dir / name) - (census_dir / "binding_spec.json").write_text(json.dumps(spec, indent=2, sort_keys=True) + "\n") - (census_dir / "manifest.json").write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") - - -def test_check_fails_when_active_string_param_missing_from_spec(tmp_path): - repo = tmp_path / "repo" - _write_fake_repo(repo) - # An active binding whose py::arg carries a string-literal default. - register = repo / "src" / "interface" / "python" / "py_good" / "py_register_good.h" - register.write_text( - 'void register_good(py::module& m)\n{\n m.def("good_one", good_one, py::arg("save_path") = "");\n}\n' - ) - spec = {"version": 1, "bindings": []} - manifest = {"version": 1, "entries": []} - census_dir = tmp_path / "census" - _write_fake_census(census_dir, spec, manifest) - failures = census_manifest.check(repo, census_dir) - assert failures, "expected --check to fail on a missing active string param" - assert any("good_one" in failure and "save_path" in failure for failure in failures) - - -def test_check_fails_on_stale_manifest(tmp_path): - repo = tmp_path / "repo" - _write_fake_repo(repo) - spec = {"version": 1, "bindings": []} - manifest = {"version": 1, "entries": [{"stale": True}]} - census_dir = tmp_path / "census" - _write_fake_census(census_dir, spec, manifest) - failures = census_manifest.check(repo, census_dir) - assert any("byte-stable" in failure or "out of date" in failure for failure in failures) - - -# --------------------------------------------------------------------------- -# Real-repo integration -# --------------------------------------------------------------------------- - - -def test_real_repo_discovery_statuses(): - discovery = lexer.discover(REPO_ROOT) - bindings = {b.py_name: b for b in discovery["bindings"]} - assert bindings["flow_init"].status_in_source == "active" - assert bindings["pydb"].status_in_source == "active" - assert bindings["runMP"].status_in_source == "disabled" - assert bindings["runRef"].status_in_source == "disabled" - # py_vec's register function is never called -> whole module disabled. - for name in ("layout_patchs", "layout_graph", "generate_vectors", "read_vectors_nets", "get_timing_wire_graph"): - assert bindings[name].status_in_source == "disabled", name - assert bindings[name].register_function == "register_vectorization" - - -def test_real_repo_check_is_green(): - assert census_manifest.check(REPO_ROOT, CENSUS_DIR) == [] - - -def test_real_repo_generation_is_byte_stable(): - first = census_manifest.generate_manifest_bytes(REPO_ROOT, CENSUS_DIR) - second = census_manifest.generate_manifest_bytes(REPO_ROOT, CENSUS_DIR) - assert first == second - assert first == (CENSUS_DIR / "manifest.json").read_bytes() - - -# --------------------------------------------------------------------------- -# Dead-binding audit (synthetic wrapper) -# --------------------------------------------------------------------------- - -SYNTHETIC_WRAPPER = '''\ -class ECCToolsModule: - def live(self): - return self.ecc.flow_init("x") - - def dead_mp(self, config): - return self.ecc.runMP(config) - - def dead_absent(self): - self.ecc.run_pnp("c") - self.ecc.run_placer("c") - - def mixed(self): - self.ecc.runMP("c") - self.ecc.flow_init("x") - - def no_calls(self): - return None -''' - - -def test_dead_binding_audit_classifies_calls(tmp_path): - register = """\ -void register_imp(pybind11::module& m) -{ - m.def("flow_init", flow_init, py::arg("flow_config")); - // m.def("runMP", runMP, py::arg("config"), py::arg("output_tcl") = ""); -} -""" - discovery_bindings = lexer.discover_bindings_in_text( - register, module="py_imp", file="py_imp/py_register_imp.cpp", called_registers={"register_imp"} - ) - wrapper = tmp_path / "module.py" - wrapper.write_text(SYNTHETIC_WRAPPER) - audit = dead_bindings.audit_wrapper(wrapper, {"bindings": discovery_bindings}) - statuses = {call["binding"]: call["status"] for row in audit["rows"] for call in row["calls"]} - assert statuses["flow_init"] == "active" - assert statuses["runMP"].startswith("disabled") - assert statuses["run_pnp"] == "absent" - assert statuses["run_placer"] == "absent" - assert audit["dead_method_candidates"] == ["dead_absent", "dead_mp"] - assert audit["methods_without_calls"] == ["no_calls"]