From 7b6e3650c164a357d7636dfb1b1c1d8aee549861 Mon Sep 17 00:00:00 2001 From: mohan-bee Date: Fri, 31 Jul 2026 22:07:05 +0530 Subject: [PATCH 01/10] Place single rail-only decoupling capacitors beside chips --- .../ChipPartitionsSolver.ts | 20 +++++++++++++++---- .../PlaceNetOnlyDecouplingRowsSolver.ts | 4 +++- .../repro-adxl345-sch-auto-layout.snap.svg | 4 ++-- .../repro-power-section.snap.svg | 10 +++++----- ...repro-rp2040-power-supply-section.snap.svg | 12 +++++------ tests/repros/repro-power-section.test.ts | 5 +++++ 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts b/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts index 77e816ea..afc9401f 100644 --- a/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts +++ b/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts @@ -53,6 +53,7 @@ export class ChipPartitionsSolver extends BaseSolver { */ private createPartitions(inputProblem: InputProblem): InputProblem[] { const chipIds = Object.keys(inputProblem.chipMap) + const pinOwnerMap = createPinOwnerMap(inputProblem) // 1) Build crystal-circuit partitions. These take precedence over generic // decoupling-cap detection and connected-component partitioning. @@ -86,8 +87,21 @@ export class ChipPartitionsSolver extends BaseSolver { capsOnly.push(capId) } } - // Only add a partition if there are at least two caps present in the inputProblem - if (capsOnly.length >= 2) { + const hasStrongConnection = Object.entries( + inputProblem.pinStrongConnMap, + ).some(([connection, connected]) => { + if (!connected) return false + const [pinA, pinB] = connection.split("-") + return [pinA, pinB].some((pinId) => + capsOnly.includes(pinOwnerMap.get(pinId!)?.chipId ?? ""), + ) + }) + // A strongly connected singleton stays with its main partition; a + // rail-only singleton needs its own partition for side placement. + if ( + capsOnly.length >= 2 || + (capsOnly.length === 1 && !hasStrongConnection) + ) { decapGroupPartitions.push(capsOnly) // Mark these caps as handled by decoupling-cap partitions for (const capId of capsOnly) { @@ -103,8 +117,6 @@ export class ChipPartitionsSolver extends BaseSolver { ) const adjacencyMap = new Map>() // Index pin ownership once so each strong edge uses direct endpoint lookups. - const pinOwnerMap = createPinOwnerMap(inputProblem) - // Initialize adjacency map for non-decap chips for (const chipId of nonDecapChipIds) { adjacencyMap.set(chipId, new Set()) diff --git a/lib/solvers/PlaceNetOnlyDecouplingRowsSolver/PlaceNetOnlyDecouplingRowsSolver.ts b/lib/solvers/PlaceNetOnlyDecouplingRowsSolver/PlaceNetOnlyDecouplingRowsSolver.ts index e7ecdcd1..7b651d87 100644 --- a/lib/solvers/PlaceNetOnlyDecouplingRowsSolver/PlaceNetOnlyDecouplingRowsSolver.ts +++ b/lib/solvers/PlaceNetOnlyDecouplingRowsSolver/PlaceNetOnlyDecouplingRowsSolver.ts @@ -190,7 +190,9 @@ const placeNetOnlyDecouplingRow = ( { mainPartition, mainChipId, side }, inputProblem, ) - const neighbor = neighborId && layout.chipPlacements[neighborId] + const neighbor = + (neighborId && layout.chipPlacements[neighborId]) ?? + layout.chipPlacements[mainChipId] const rowChipIds = Object.keys(partition.chipMap) const boundsContext = { inputProblem, layout } const mainBounds = getPartitionBounds( diff --git a/tests/repros/__snapshots__/repro-adxl345-sch-auto-layout.snap.svg b/tests/repros/__snapshots__/repro-adxl345-sch-auto-layout.snap.svg index 12e9688c..ece94ad1 100644 --- a/tests/repros/__snapshots__/repro-adxl345-sch-auto-layout.snap.svg +++ b/tests/repros/__snapshots__/repro-adxl345-sch-auto-layout.snap.svg @@ -1,4 +1,4 @@ -U1C1C2C3 + ]]> \ No newline at end of file diff --git a/tests/repros/__snapshots__/repro-rp2040-power-supply-section.snap.svg b/tests/repros/__snapshots__/repro-rp2040-power-supply-section.snap.svg index 2426290b..ac51dc19 100644 --- a/tests/repros/__snapshots__/repro-rp2040-power-supply-section.snap.svg +++ b/tests/repros/__snapshots__/repro-rp2040-power-supply-section.snap.svg @@ -1,4 +1,4 @@ -DPROTF1U2C1C4DPWRR1C18 + ]]> \ No newline at end of file diff --git a/tests/repros/repro-power-section.test.ts b/tests/repros/repro-power-section.test.ts index ec09232a..a5b6516f 100644 --- a/tests/repros/repro-power-section.test.ts +++ b/tests/repros/repro-power-section.test.ts @@ -7,5 +7,10 @@ test("power section schematic auto-layout", async () => { const solver = new LayoutPipelineSolver(inputProblem as InputProblem) solver.solve() + const placements = solver.getOutputLayout().chipPlacements + expect(placements.C1!.x).toBeLessThan(placements.U3!.x) + expect(placements.C1!.y).toBeCloseTo(placements.U3!.y) + expect(solver.checkForOverlaps(solver.getOutputLayout())).toHaveLength(0) + await expect(solver).toMatchSolverSnapshot(import.meta.path) }) From 363a1a24369568f853a90b1f5c07dac73eafe842 Mon Sep 17 00:00:00 2001 From: mohan-bee Date: Fri, 31 Jul 2026 22:20:09 +0530 Subject: [PATCH 02/10] Place grounded input capacitors at the configured chip gap --- .../IdentifyDecouplingCapsSolver.ts | 13 ++++++++----- .../repro-rp2040-power-supply-section.snap.svg | 4 ++-- .../repro-rp2040-power-supply-section.test.ts | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/solvers/IdentifyDecouplingCapsSolver/IdentifyDecouplingCapsSolver.ts b/lib/solvers/IdentifyDecouplingCapsSolver/IdentifyDecouplingCapsSolver.ts index 89adbefa..19cec6df 100644 --- a/lib/solvers/IdentifyDecouplingCapsSolver/IdentifyDecouplingCapsSolver.ts +++ b/lib/solvers/IdentifyDecouplingCapsSolver/IdentifyDecouplingCapsSolver.ts @@ -272,13 +272,17 @@ export class IdentifyDecouplingCapsSolver extends BaseSolver { const positiveNetIds = netPair.filter( (netId) => this.inputProblem.netMap[netId]?.isPositiveVoltageSource, ) + const supplyNetIds = + positiveNetIds.length > 0 + ? positiveNetIds + : netPair.filter((netId) => !this.inputProblem.netMap[netId]?.isGround) const sideCounts = new Map() for (const pinId of mainChip.pins) { const pin = this.inputProblem.chipPinMap[pinId] if (!pin) continue const pinNetIds = this.getNetIdsForPin(pinId) - if (!positiveNetIds.some((netId) => pinNetIds.has(netId))) continue + if (!supplyNetIds.some((netId) => pinNetIds.has(netId))) continue sideCounts.set(pin.side, (sideCounts.get(pin.side) ?? 0) + 1) } @@ -316,14 +320,13 @@ export class IdentifyDecouplingCapsSolver extends BaseSolver { const netPair = this.getNormalizedNetPair(currentChip) if (!netPair) return - // Ensure the net pair corresponds to a true decoupling capacitor: - // one net must be ground and the other a positive voltage source + // The supply net may not be explicitly marked as positive (for example RAW + // regulator inputs), but one side must still be a ground rail. const [n1, n2] = netPair const net1 = this.inputProblem.netMap[n1] const net2 = this.inputProblem.netMap[n2] const isDecouplingNetPair = - (net1?.isGround && net2?.isPositiveVoltageSource) || - (net2?.isGround && net1?.isPositiveVoltageSource) + Boolean(net1?.isGround) !== Boolean(net2?.isGround) if (!isDecouplingNetPair) return // Require a chip for the cap to decouple, found by pin-to-pin connection or, diff --git a/tests/repros/__snapshots__/repro-rp2040-power-supply-section.snap.svg b/tests/repros/__snapshots__/repro-rp2040-power-supply-section.snap.svg index ac51dc19..f0c0a3ac 100644 --- a/tests/repros/__snapshots__/repro-rp2040-power-supply-section.snap.svg +++ b/tests/repros/__snapshots__/repro-rp2040-power-supply-section.snap.svg @@ -1,4 +1,4 @@ -DPROTF1U2C1C4DPWRR1C18