diff --git a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts index 88db103..0f0e7d4 100644 --- a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts @@ -11,9 +11,9 @@ import type { InputProblem, PinId, ChipId, - NetId, ChipPin, PartitionInputProblem, + Chip, } from "../../types/InputProblem" import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem" import { createFilteredNetworkMapping } from "../../utils/networkFiltering" @@ -38,6 +38,12 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } override _step() { + if (this.partitionInputProblem.partitionType === "decoupling_caps") { + this.layout = this.createDecouplingCapsRowLayout() + this.solved = true + return + } + // Initialize PackSolver2 if not already created if (!this.activeSubSolver) { const packInput = this.createPackInput() @@ -64,6 +70,62 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } } + private getPreferredRotation(chip: Chip): 0 | 90 | 180 | 270 { + const rotations = chip.availableRotations + if (!rotations || rotations.length === 0) return 0 + if (rotations.includes(0)) return 0 + return rotations[0]! + } + + private getChipWidthForRotation(chip: Chip, rotation: 0 | 90 | 180 | 270) { + return rotation === 90 || rotation === 270 ? chip.size.y : chip.size.x + } + + private createDecouplingCapsRowLayout(): OutputLayout { + const sortedChips = Object.entries(this.partitionInputProblem.chipMap).sort( + ([chipIdA], [chipIdB]) => + chipIdA.localeCompare(chipIdB, undefined, { + numeric: true, + sensitivity: "base", + }), + ) + + const gap = + this.partitionInputProblem.decouplingCapsGap ?? + this.partitionInputProblem.chipGap + + const rowItems = sortedChips.map(([chipId, chip]) => { + const rotation = this.getPreferredRotation(chip) + return { + chipId, + chip, + rotation, + width: this.getChipWidthForRotation(chip, rotation), + } + }) + + const totalWidth = + rowItems.reduce((sum, item) => sum + item.width, 0) + + Math.max(0, rowItems.length - 1) * gap + + let cursorX = -totalWidth / 2 + const chipPlacements: Record = {} + + for (const item of rowItems) { + chipPlacements[item.chipId] = { + x: cursorX + item.width / 2, + y: 0, + ccwRotationDegrees: item.rotation, + } + cursorX += item.width + gap + } + + return { + chipPlacements, + groupPlacements: {}, + } + } + private createPackInput(): PackInput { // Fall back to filtered mapping (weak + strong) const pinToNetworkMap = createFilteredNetworkMapping({ diff --git a/pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx b/pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx index c0767bb..adc2e0c 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 { lazy, Suspense } 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 ( + + + + ) } diff --git a/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts new file mode 100644 index 0000000..3f5fdd0 --- /dev/null +++ b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts @@ -0,0 +1,82 @@ +import { expect, test } from "bun:test" +import { SingleInnerPartitionPackingSolver } from "lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver" +import type { + Chip, + ChipId, + PartitionInputProblem, +} from "lib/types/InputProblem" + +const makeChip = ( + chipId: ChipId, + size: Chip["size"], + availableRotations: Chip["availableRotations"] = [0, 180], +): Chip => ({ + chipId, + pins: [], + size, + availableRotations, +}) + +test("SingleInnerPartitionPackingSolver lays decoupling caps out in a centered natural-order row", () => { + const partitionInputProblem: PartitionInputProblem = { + chipMap: { + C10: makeChip("C10", { x: 1, y: 0.5 }), + C2: makeChip("C2", { x: 2, y: 0.5 }), + C1: makeChip("C1", { x: 1, y: 0.5 }), + }, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 1, + partitionGap: 1, + decouplingCapsGap: 0.25, + isPartition: true, + partitionType: "decoupling_caps", + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: {}, + }) + + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout?.chipPlacements.C1?.x).toBeCloseTo(-1.75) + expect(solver.layout?.chipPlacements.C2?.x).toBeCloseTo(0) + expect(solver.layout?.chipPlacements.C10?.x).toBeCloseTo(1.75) + expect(solver.layout?.chipPlacements.C1?.y).toBe(0) + expect(solver.layout?.chipPlacements.C2?.y).toBe(0) + expect(solver.layout?.chipPlacements.C10?.y).toBe(0) +}) + +test("SingleInnerPartitionPackingSolver uses rotated width for fixed rotated decoupling caps", () => { + const partitionInputProblem: PartitionInputProblem = { + chipMap: { + C1: makeChip("C1", { x: 1, y: 3 }, [90]), + C2: makeChip("C2", { x: 1, y: 3 }, [90]), + }, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 0.5, + partitionGap: 1, + isPartition: true, + partitionType: "decoupling_caps", + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: {}, + }) + + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout?.chipPlacements.C1?.ccwRotationDegrees).toBe(90) + expect(solver.layout?.chipPlacements.C2?.ccwRotationDegrees).toBe(90) + expect(solver.layout?.chipPlacements.C1?.x).toBeCloseTo(-1.75) + expect(solver.layout?.chipPlacements.C2?.x).toBeCloseTo(1.75) +})