diff --git a/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts b/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts index 77e816ea..b6c64700 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,24 @@ 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 hasStrongConnectionToMainChip = Object.entries( + inputProblem.pinStrongConnMap, + ).some(([connection, connected]) => { + if (!connected) return false + const [pinA, pinB] = connection.split("-") + const ownerA = pinOwnerMap.get(pinA!)?.chipId + const ownerB = pinOwnerMap.get(pinB!)?.chipId + return ( + (capsOnly.includes(ownerA ?? "") && ownerB === group.mainChipId) || + (capsOnly.includes(ownerB ?? "") && ownerA === group.mainChipId) + ) + }) + // 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 && !hasStrongConnectionToMainChip) + ) { decapGroupPartitions.push(capsOnly) // Mark these caps as handled by decoupling-cap partitions for (const capId of capsOnly) { @@ -103,8 +120,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..1183f079 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( @@ -202,7 +204,7 @@ const placeNetOnlyDecouplingRow = ( const offset = getRowOffset({ side, - chipGap: inputProblem.chipGap, + chipGap: inputProblem.chipGap * 2, mainBounds, rowBounds, neighbor, diff --git a/tests/PlaceNetOnlyDecouplingRowsSolver/PlaceNetOnlyDecouplingRowsSolver01.test.ts b/tests/PlaceNetOnlyDecouplingRowsSolver/PlaceNetOnlyDecouplingRowsSolver01.test.ts index 4545bbab..06d04dc9 100644 --- a/tests/PlaceNetOnlyDecouplingRowsSolver/PlaceNetOnlyDecouplingRowsSolver01.test.ts +++ b/tests/PlaceNetOnlyDecouplingRowsSolver/PlaceNetOnlyDecouplingRowsSolver01.test.ts @@ -122,7 +122,7 @@ test("places a net-only decoupling row after a direct connection", () => { ) expect(weakPartitionLeft - mainPartitionRight).toBeCloseTo( - inputProblem.chipGap, + inputProblem.chipGap * 2, ) expect(layout.chipPlacements.C2!.y).toBeCloseTo(layout.chipPlacements.C1!.y) expect(layout.chipPlacements.C2!.x).toBeGreaterThan( 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..e028a4c6 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..a7728ac0 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..f7a78c86 100644 --- a/tests/repros/repro-power-section.test.ts +++ b/tests/repros/repro-power-section.test.ts @@ -1,11 +1,36 @@ import { expect, test } from "bun:test" import { LayoutPipelineSolver } from "lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver" import type { InputProblem } from "lib/types/InputProblem" +import { getRotatedSize } from "lib/utils/rotatePinOffset" import inputProblem from "../assets/repro-power-section.input.json" test("power section schematic auto-layout", async () => { const solver = new LayoutPipelineSolver(inputProblem as InputProblem) solver.solve() + const placements = solver.getOutputLayout().chipPlacements + const c1Size = getRotatedSize( + inputProblem.chipMap.C1!.size, + placements.C1!.ccwRotationDegrees, + ) + const u3Size = getRotatedSize( + inputProblem.chipMap.U3!.size, + placements.U3!.ccwRotationDegrees, + ) + const horizontalGap = Math.max( + placements.U3!.x - u3Size.x / 2 - (placements.C1!.x + c1Size.x / 2), + placements.C1!.x - c1Size.x / 2 - (placements.U3!.x + u3Size.x / 2), + 0, + ) + const verticalGap = Math.max( + placements.U3!.y - u3Size.y / 2 - (placements.C1!.y + c1Size.y / 2), + placements.C1!.y - c1Size.y / 2 - (placements.U3!.y + u3Size.y / 2), + 0, + ) + expect(Math.hypot(horizontalGap, verticalGap)).toBeGreaterThanOrEqual( + inputProblem.chipGap * 2, + ) + expect(solver.checkForOverlaps(solver.getOutputLayout())).toHaveLength(0) + await expect(solver).toMatchSolverSnapshot(import.meta.path) })