From c01b67b0b61cde0dd61e5a288155d3e5e2f5bbcd Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Mon, 18 May 2026 18:44:49 +0100 Subject: [PATCH] GF180MCU: power-pin + wired-filler shortcuts for real post-P&R netlists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 6 brought GF180MCU up to "synthesized-core-only" support; Phase 7 (real wafer.space chip_top netlists) surfaced two remaining gaps that the synthetic timing_test fixtures didn't exercise: 1. Antenna / endcap / fillcap / etc. cell models declare power as `inout VDD, VSS;`. `build.rs::generate_gf180mcu_pin_table` only parses `input` / `output`, so the generated `GF180MCU_PIN_TABLE` was missing power entries. The first wired `.VDD(...)` on an antenna instance in `chip_top.pnl.v` panics NetlistDB parse. 2. `cor` / `fill*` / `dvdd` / `dvss` were assumed to instantiate with empty port lists. The `gf180mcu-project-template` LibreLane flow wires all four power pins on every filler — the corner pad `gf180mcu_fd_io__cor` ships with `.DVDD .DVSS .VDD .VSS`. With no pin-table entry these also panicked. Fix is a two-step shortcut ahead of the per-cell pin lookup: - Any pin named `VDD` / `VSS` / `VNW` / `VPW` / `VPB` / `VNB` / `VPWR` / `VGND` / `DVDD` / `DVSS` / `AVDD` / `AVSS` resolves to `Direction::I` (the enum has no `Inout` variant; power-pin semantics — driven from supply, never driven by the cell — match "constant external driver"). - Any pin on a known filler cell (via `is_filler_cell`) falls through to `Direction::I`. Fillers carry no logic, so power, ground, and signal pins are all sim-irrelevant. Three new tests in `gf180mcu::tests`: - `pin_provider_returns_directions_for_power_and_ground_pins` — spot-checks all 12 power-pin names on an antenna cell. - `pin_provider_handles_wired_filler_and_corner_cells` — covers cor / fill10 / filltie / endcap / dvdd / dvss with realistic pin lists. - `netlist_db_parses_wired_chip_top_pad_ring` — integration fixture mirroring a real `gf180mcu-project-template` post-P&R chip_top. This advances the GF180MCU enablement plan's Phase 7 from "synthetic fixtures only" to "real wafer.space post-P&R netlists parse end-to- end". Note: Phase 7 still has a separate blocker on third-party SRAM IP (`gf180mcu_fd_ip_sram` / `gf180mcu_ocd_ip_sram` cell tables); that's a richer modelling problem and is out of scope for this PR. Co-developed-by: Claude Code v2.1.143 (claude-opus-4-7) --- CHANGELOG.md | 20 +++++++ src/gf180mcu.rs | 142 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df25d57e..f9f25d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,26 @@ the authoritative record. - **`opensta-to-ir` golden-IR regression corpus** (WS4, commits `90558bb` / `6997096` / `9e25bc2`): seed entry `aigpdk_dff_chain` covers all four IR record types; runner + regen helper + CI hookup in place. +- **GF180MCU power-pin + wired-filler shortcuts** in + `GF180MCULeafPins::direction_of`. Phase 6 left two gaps that only + surfaced on real post-P&R chip-top netlists (`gf180mcu-project- + template` / wafer.space LibreLane flow): (1) Verilog cell models + declare power as `inout VDD, VSS;`, but `build.rs` only parses + `input`/`output` — so the generated pin table lacked VDD/VSS + entries, panicking on the first `.VDD(...)` wired in + `antenna`/`endcap`/`fillcap`/etc.; (2) `cor`/`fill*`/`dvdd`/`dvss` + etc. were assumed to instantiate with empty port lists, but real + netlists wire all four power pins on every filler instance. New + short-circuit ahead of the pin-table lookup: any pin named + `VDD`/`VSS`/`VNW`/`VPW`/`VPB`/`VNB`/`VPWR`/`VGND`/`DVDD`/`DVSS`/ + `AVDD`/`AVSS` resolves to `Direction::I` (constant external + driver — the enum has no `Inout`), and any pin on a known filler + cell falls through to the same. Three new tests in + `gf180mcu::tests` cover the power-pin shortcut, the wired-filler + shortcut, and a realistic chip-top fixture matching the + `gf180mcu-project-template` netlist shape. Advances Phase 7 of + the GF180MCU enablement plan from "synthetic fixtures only" to + "real wafer.space post-P&R netlists parse end-to-end". ### Changed diff --git a/src/gf180mcu.rs b/src/gf180mcu.rs index dc715416..bba3a80d 100644 --- a/src/gf180mcu.rs +++ b/src/gf180mcu.rs @@ -176,7 +176,37 @@ impl LeafPinProvider for GF180MCULeafPins { pin_name: &CompactString, _pin_idx: Option, ) -> Direction { + // Power / ground pins follow a fixed naming convention across both + // the standard-cell and pad libraries and are always Inout. Short- + // circuiting here sidesteps per-cell table drift: the vendored + // Verilog cell models declare power pins as `inout` (e.g. + // `inout VDD, VSS;` on antenna) but build.rs only parses + // `input`/`output` declarations, so the generated GF180MCU_PIN_TABLE + // is missing them. The post-PnR netlist connects them explicitly, + // which otherwise panics on the first VDD lookup. Local + // apitronix-integration patch (2026-05-16) — file upstream once + // verified across enough cells. + if matches!( + pin_name.as_str(), + "VDD" | "VSS" | "VNW" | "VPW" | "VPB" | "VNB" | "VPWR" | "VGND" + | "DVDD" | "DVSS" | "AVDD" | "AVSS" + ) { + // Direction enum in eda-infra-rs has no `Inout` variant + // (I / O / Unknown only). Power pins are effectively inputs — + // driven from the supply, never driven by the cell — so `I` + // matches the simulator's "constant external driver" semantics. + // DVDD/DVSS/AVDD/AVSS are wafer.space-PDK power-pad naming. + return Direction::I; + } let cell_type = extract_cell_type(macro_name); + // Filler / endcap / fill1-10 / corner pads: upstream assumes these + // instantiate with `()` (no port connections) in post-P&R netlists, + // but the wafer.space `gf180mcu-project-template` wires + // VDD/VSS/DVDD/DVSS/etc. on every filler instance. Treat any wired + // pin on a known filler as a constant input — no logic effect. + if crate::gf180mcu_pdk::is_filler_cell(cell_type) { + return Direction::I; + } // Standard cells first (the hot path). for (ct, pins) in GF180MCU_PIN_TABLE { if *ct == cell_type { @@ -507,6 +537,118 @@ endmodule ); } + #[test] + fn pin_provider_returns_directions_for_power_and_ground_pins() { + // Antenna / endcap / decoupling cells declare `inout VDD, VSS;` in + // their Verilog models; `build.rs` only parses `input` / `output`, + // so VDD / VSS were missing from the generated pin table. Real + // post-P&R netlists wire them — without the power-pin shortcut, + // NetlistDB parse panics on the first wired `.VDD(...)`. + let provider = GF180MCULeafPins; + let dir = |cell: &str, pin: &str| { + provider.direction_of( + &CompactString::from(cell), + &CompactString::from(pin), + None, + ) + }; + for pin in [ + "VDD", "VSS", "VNW", "VPW", "VPB", "VNB", "VPWR", "VGND", + "DVDD", "DVSS", "AVDD", "AVSS", + ] { + // Shortcut is per-pin-name, not per-cell, so any cell works. + assert_eq!( + dir("gf180mcu_fd_sc_mcu7t5v0__antenna", pin), + Direction::I, + "{pin} should resolve to Direction::I on antenna" + ); + } + } + + #[test] + fn pin_provider_handles_wired_filler_and_corner_cells() { + // wafer.space `gf180mcu-project-template` post-P&R netlists wire + // power pins on every filler / corner / power-pad instance, e.g. + // gf180mcu_fd_io__cor IO_CORNER_NE (.DVDD(VDD), .DVSS(VSS), + // .VDD(VDD), .VSS(VSS)); + // Upstream assumed these always instantiate with `()` (no + // connections), so the pin table has no entry for filler pins. + // The filler-cell shortcut treats any pin on a known filler as + // Direction::I — the cell has no logic, so power/ground/signal + // are all "constant external driver" from the AIG's perspective. + let provider = GF180MCULeafPins; + let dir = |cell: &str, pin: &str| { + provider.direction_of( + &CompactString::from(cell), + &CompactString::from(pin), + None, + ) + }; + // Corner pad (`cor`) — wired with DVDD / DVSS / VDD / VSS. + for pin in ["DVDD", "DVSS", "VDD", "VSS"] { + assert_eq!(dir("gf180mcu_fd_io__cor", pin), Direction::I); + } + // IO filler — wired with VDD / VSS. + for pin in ["VDD", "VSS"] { + assert_eq!(dir("gf180mcu_fd_io__fill10", pin), Direction::I); + } + // Standard-cell filler / endcap — wired with VDD / VSS. + for pin in ["VDD", "VSS"] { + assert_eq!(dir("gf180mcu_fd_sc_mcu9t5v0__filltie", pin), Direction::I); + assert_eq!(dir("gf180mcu_fd_sc_mcu9t5v0__endcap", pin), Direction::I); + } + // wafer.space power pads — DVDD / DVSS instances themselves. + for pin in ["DVDD", "DVSS", "VDD", "VSS"] { + assert_eq!(dir("gf180mcu_ws_io__dvdd", pin), Direction::I); + assert_eq!(dir("gf180mcu_ws_io__dvss", pin), Direction::I); + } + } + + #[test] + fn netlist_db_parses_wired_chip_top_pad_ring() { + // Same shape as the `_parses_chip_top_pad_ring` test above, but + // with realistic wired-up power/ground connections matching what + // OpenROAD emits in post-P&R netlists (`gf180mcu-project-template` + // LibreLane flow). Catches regressions where the pin table or + // shortcuts drift from real netlists. + const VERILOG: &str = r#" +module chip_top(clk_PAD); + inout clk_PAD; + wire clk_core, mid, VDD, VSS; + // Logic-bearing pad — already covered by the pad pin table. + gf180mcu_fd_io__in_s clk_pad (.PAD(clk_PAD), .PU(1'b0), .PD(1'b0), .Y(clk_core)); + // Antenna cell with wired `inout VDD, VNW, VPW, VSS`. + gf180mcu_fd_sc_mcu7t5v0__antenna ANTENNA_1 ( + .I(clk_core), .VDD(VDD), .VNW(VDD), .VPW(VSS), .VSS(VSS) + ); + // Inverter so the design is non-degenerate. + gf180mcu_fd_sc_mcu9t5v0__inv_1 U1 (.I(clk_core), .ZN(mid)); + // Corner pad — wired DVDD / DVSS / VDD / VSS, no signal pins. + gf180mcu_fd_io__cor IO_CORNER_NE_INST ( + .DVDD(VDD), .DVSS(VSS), .VDD(VDD), .VSS(VSS) + ); + // IO filler — wired VDD / VSS. + gf180mcu_fd_io__fill10 IO_FILL_1 (.VDD(VDD), .VSS(VSS)); + // Standard-cell filltie / endcap — wired VDD / VSS. + gf180mcu_fd_sc_mcu9t5v0__filltie FILLTIE_1 (.VDD(VDD), .VSS(VSS)); + gf180mcu_fd_sc_mcu9t5v0__endcap ENDCAP_1 (.VDD(VDD), .VSS(VSS)); + // wafer.space power pads. + gf180mcu_ws_io__dvdd DVDD_INST (.DVDD(VDD), .DVSS(VSS), .VDD(VDD), .VSS(VSS)); + gf180mcu_ws_io__dvss DVSS_INST (.DVDD(VDD), .DVSS(VSS), .VDD(VDD), .VSS(VSS)); +endmodule +"#; + let nl = netlistdb::NetlistDB::from_sverilog_source( + VERILOG, + Some("chip_top"), + &GF180MCULeafPins, + ); + assert!( + nl.is_some(), + "NetlistDB parse failed for wired chip_top pad ring — \ + power-pin or filler-cell shortcut missing", + ); + } + #[test] fn netlist_db_round_trips_a_mixed_7t_9t_design() { // Exercise the full LeafPinProvider contract through NetlistDB —