From 408e5a0b890fe5672e597b38bd7d6ceb1f602255 Mon Sep 17 00:00:00 2001 From: hieu-lee Date: Mon, 11 May 2026 02:43:46 +0200 Subject: [PATCH 1/2] Add decoupling cap row layout --- .../SingleInnerPartitionPackingSolver.ts | 60 +++++++++ .../SingleInnerPartitionPackingSolver.test.ts | 118 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts diff --git a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts index 88db103..0a74797 100644 --- a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts @@ -14,6 +14,7 @@ import type { NetId, ChipPin, PartitionInputProblem, + Chip, } from "../../types/InputProblem" import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem" import { createFilteredNetworkMapping } from "../../utils/networkFiltering" @@ -38,6 +39,13 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } override _step() { + if (this.partitionInputProblem.partitionType === "decoupling_caps") { + this.layout = this.createDecouplingCapsRowLayout() + this.activeSubSolver = null + this.solved = true + return + } + // Initialize PackSolver2 if not already created if (!this.activeSubSolver) { const packInput = this.createPackInput() @@ -64,6 +72,58 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } } + private createDecouplingCapsRowLayout(): OutputLayout { + const chipPlacements: Record = {} + const gap = + this.partitionInputProblem.decouplingCapsGap ?? + this.partitionInputProblem.chipGap + const orderedChips = Object.values(this.partitionInputProblem.chipMap).sort( + (a, b) => a.chipId.localeCompare(b.chipId, undefined, { numeric: true }), + ) + const orientedChips = orderedChips.map((chip) => { + const rotation = this.getPreferredRotation(chip) + const size = this.getChipSizeForRotation(chip, rotation) + + return { chip, rotation, size } + }) + const totalWidth = + orientedChips.reduce((sum, { size }) => sum + size.x, 0) + + Math.max(0, orientedChips.length - 1) * gap + let cursorX = -totalWidth / 2 + + for (const { chip, rotation, size } of orientedChips) { + cursorX += size.x / 2 + chipPlacements[chip.chipId] = { + x: cursorX, + y: 0, + ccwRotationDegrees: rotation, + } + cursorX += size.x / 2 + gap + } + + return { + chipPlacements, + groupPlacements: {}, + } + } + + private getPreferredRotation(chip: Chip): 0 | 90 | 180 | 270 { + const rotations = chip.availableRotations ?? [0, 90, 180, 270] + if (rotations.includes(0)) return 0 + return rotations[0] ?? 0 + } + + private getChipSizeForRotation( + chip: Chip, + rotation: 0 | 90 | 180 | 270, + ): { x: number; y: number } { + if (rotation === 90 || rotation === 270) { + return { x: chip.size.y, y: chip.size.x } + } + + return chip.size + } + private createPackInput(): PackInput { // Fall back to filtered mapping (weak + strong) const pinToNetworkMap = createFilteredNetworkMapping({ diff --git a/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts new file mode 100644 index 0000000..e2f832b --- /dev/null +++ b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts @@ -0,0 +1,118 @@ +import { expect, test } from "bun:test" +import { SingleInnerPartitionPackingSolver } from "../../lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver" +import type { + Chip, + ChipId, + PartitionInputProblem, +} from "../../lib/types/InputProblem" +import type { OutputLayout } from "../../lib/types/OutputLayout" + +const makeCap = ( + chipId: ChipId, + size: { x: number; y: number }, + availableRotations: Chip["availableRotations"] = [0, 180], +): Chip => ({ + chipId, + pins: [], + size, + isDecouplingCap: true, + availableRotations, +}) + +const makeDecouplingCapsPartition = ( + chipMap: Record, + decouplingCapsGap = 0.4, +): PartitionInputProblem => ({ + chipMap, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 0.2, + partitionGap: 2, + decouplingCapsGap, + isPartition: true, + partitionType: "decoupling_caps", +}) + +const getBounds = ( + inputProblem: PartitionInputProblem, + layout: OutputLayout, + chipId: ChipId, +) => { + const chip = inputProblem.chipMap[chipId]! + const placement = layout.chipPlacements[chipId]! + const rotation = placement.ccwRotationDegrees % 180 + const width = rotation === 90 ? chip.size.y : chip.size.x + const height = rotation === 90 ? chip.size.x : chip.size.y + + return { + left: placement.x - width / 2, + right: placement.x + width / 2, + top: placement.y + height / 2, + bottom: placement.y - height / 2, + } +} + +test("SingleInnerPartitionPackingSolver packs decoupling caps into a deterministic row", () => { + const inputProblem = makeDecouplingCapsPartition({ + C10: makeCap("C10", { x: 0.75, y: 1 }), + C1: makeCap("C1", { x: 0.5, y: 1 }), + C2: makeCap("C2", { x: 1, y: 1 }), + }) + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem: inputProblem, + pinIdToStronglyConnectedPins: {}, + }) + + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.failed).toBe(false) + expect(solver.layout).not.toBeNull() + expect(Object.keys(solver.layout!.chipPlacements)).toEqual([ + "C1", + "C2", + "C10", + ]) + expect(solver.layout!.chipPlacements.C1!.x).toBeCloseTo(-1.275, 6) + expect(solver.layout!.chipPlacements.C2!.x).toBeCloseTo(-0.125, 6) + expect(solver.layout!.chipPlacements.C10!.x).toBeCloseTo(1.15, 6) + expect(solver.layout!.chipPlacements.C1!.y).toBe(0) + expect(solver.layout!.chipPlacements.C2!.y).toBe(0) + expect(solver.layout!.chipPlacements.C10!.y).toBe(0) + + const c1Bounds = getBounds(inputProblem, solver.layout!, "C1") + const c2Bounds = getBounds(inputProblem, solver.layout!, "C2") + const c10Bounds = getBounds(inputProblem, solver.layout!, "C10") + + expect(c2Bounds.left - c1Bounds.right).toBeCloseTo(0.4, 6) + expect(c10Bounds.left - c2Bounds.right).toBeCloseTo(0.4, 6) +}) + +test("SingleInnerPartitionPackingSolver respects fixed rotated cap dimensions", () => { + const inputProblem = makeDecouplingCapsPartition( + { + C1: makeCap("C1", { x: 1, y: 2 }, [90]), + C2: makeCap("C2", { x: 1, y: 1 }, [0]), + }, + 0.25, + ) + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem: inputProblem, + pinIdToStronglyConnectedPins: {}, + }) + + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout!.chipPlacements.C1!.ccwRotationDegrees).toBe(90) + expect(solver.layout!.chipPlacements.C2!.ccwRotationDegrees).toBe(0) + expect(solver.layout!.chipPlacements.C1!.x).toBeCloseTo(-0.625, 6) + expect(solver.layout!.chipPlacements.C2!.x).toBeCloseTo(1.125, 6) + + const c1Bounds = getBounds(inputProblem, solver.layout!, "C1") + const c2Bounds = getBounds(inputProblem, solver.layout!, "C2") + + expect(c2Bounds.left - c1Bounds.right).toBeCloseTo(0.25, 6) +}) From 49be2a08b6f30d35f15d6ce24e9ae426701c3123 Mon Sep 17 00:00:00 2001 From: hieu-lee Date: Mon, 11 May 2026 03:06:37 +0200 Subject: [PATCH 2/2] Avoid eager debugger import in pipeline fixture --- .../LayoutPipelineSolver06.page.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx b/pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx index c0767bb..c20037a 100644 --- a/pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx +++ b/pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx @@ -1,6 +1,12 @@ import type { PackInput } from "calculate-packing" -import { LayoutPipelineDebugger } from "lib/components/LayoutPipelineDebugger" import type { InputProblem } from "lib/index" +import { Suspense, lazy } from "react" + +const LayoutPipelineDebugger = lazy(() => + import("lib/components/LayoutPipelineDebugger").then((module) => ({ + default: module.LayoutPipelineDebugger, + })), +) export const problem: InputProblem = { chipMap: { @@ -877,5 +883,9 @@ export const problem: InputProblem = { } export default function LayoutPipelineSolver06Page() { - return + return ( + + + + ) }