diff --git a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts index 88db103..9406bba 100644 --- a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts @@ -3,24 +3,25 @@ * Uses a packing algorithm to arrange chips and their connections within the partition. */ -import type { GraphicsObject } from "graphics-debug" import { type PackInput, PackSolver2 } from "calculate-packing" -import { BaseSolver } from "../BaseSolver" -import type { OutputLayout, Placement } from "../../types/OutputLayout" +import type { GraphicsObject } from "graphics-debug" import type { - InputProblem, - PinId, + Chip, ChipId, - NetId, ChipPin, + InputProblem, PartitionInputProblem, + PinId, } from "../../types/InputProblem" -import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem" +import type { OutputLayout, Placement } from "../../types/OutputLayout" import { createFilteredNetworkMapping } from "../../utils/networkFiltering" -import { getPadsBoundingBox } from "./getPadsBoundingBox" +import { BaseSolver } from "../BaseSolver" import { doBasicInputProblemLayout } from "../LayoutPipelineSolver/doBasicInputProblemLayout" +import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem" +import { getPadsBoundingBox } from "./getPadsBoundingBox" const PIN_SIZE = 0.1 +type RotationDegrees = 0 | 90 | 180 | 270 export class SingleInnerPartitionPackingSolver extends BaseSolver { partitionInputProblem: PartitionInputProblem @@ -38,6 +39,13 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } override _step() { + if (this.partitionInputProblem.partitionType === "decoupling_caps") { + this.layout = this.createDecouplingCapsRowLayout() + this.solved = true + this.activeSubSolver = null + return + } + // Initialize PackSolver2 if not already created if (!this.activeSubSolver) { const packInput = this.createPackInput() @@ -64,6 +72,64 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } } + private getPreferredRotation(chip: Chip): RotationDegrees { + const rotations = chip.availableRotations + if (!rotations || rotations.length === 0) { + return 0 + } + if (rotations.includes(0)) { + return 0 + } + return rotations[0]! + } + + private getWidthForRotation(chip: Chip, rotation: RotationDegrees): number { + return rotation === 90 || rotation === 270 ? chip.size.y : chip.size.x + } + + private createDecouplingCapsRowLayout(): OutputLayout { + const gap = + this.partitionInputProblem.decouplingCapsGap ?? + this.partitionInputProblem.chipGap + + const rowItems = Object.entries(this.partitionInputProblem.chipMap) + .sort(([chipIdA], [chipIdB]) => + chipIdA.localeCompare(chipIdB, undefined, { + numeric: true, + sensitivity: "base", + }), + ) + .map(([chipId, chip]) => { + const rotation = this.getPreferredRotation(chip) + return { + chipId, + rotation, + width: this.getWidthForRotation(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..c070723 100644 --- a/pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx +++ b/pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx @@ -1,6 +1,11 @@ -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((mod) => ({ + default: mod.LayoutPipelineDebugger, + })), +) export const problem: InputProblem = { chipMap: { @@ -877,5 +882,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..2a2903c --- /dev/null +++ b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts @@ -0,0 +1,95 @@ +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, +}) + +const createDecapPartition = ( + chipMap: PartitionInputProblem["chipMap"], + extra: Partial = {}, +): PartitionInputProblem => ({ + chipMap, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 1, + partitionGap: 1, + isPartition: true, + partitionType: "decoupling_caps", + ...extra, +}) + +const solvePartition = (partitionInputProblem: PartitionInputProblem) => { + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: {}, + }) + + solver.solve() + expect(solver.solved).toBe(true) + expect(solver.failed).toBe(false) + return solver.layout! +} + +test("SingleInnerPartitionPackingSolver lays decoupling caps out in a centered natural-order row", () => { + const layout = solvePartition( + createDecapPartition( + { + C10: makeChip("C10", { x: 1, y: 0.5 }), + C2: makeChip("C2", { x: 2, y: 0.5 }), + C1: makeChip("C1", { x: 1, y: 0.5 }), + }, + { decouplingCapsGap: 0.25 }, + ), + ) + + expect(layout.chipPlacements.C1?.x).toBeCloseTo(-1.75) + expect(layout.chipPlacements.C2?.x).toBeCloseTo(0) + expect(layout.chipPlacements.C10?.x).toBeCloseTo(1.75) + expect(layout.chipPlacements.C1?.y).toBe(0) + expect(layout.chipPlacements.C2?.y).toBe(0) + expect(layout.chipPlacements.C10?.y).toBe(0) +}) + +test("SingleInnerPartitionPackingSolver falls back to chipGap for decoupling cap row spacing", () => { + const layout = solvePartition( + createDecapPartition({ + C1: makeChip("C1", { x: 1, y: 0.5 }), + C2: makeChip("C2", { x: 1, y: 0.5 }), + }), + ) + + expect(layout.chipPlacements.C1?.x).toBeCloseTo(-1) + expect(layout.chipPlacements.C2?.x).toBeCloseTo(1) +}) + +test("SingleInnerPartitionPackingSolver uses rotated width for fixed rotated decoupling caps", () => { + const layout = solvePartition( + createDecapPartition( + { + C1: makeChip("C1", { x: 1, y: 3 }, [90]), + C2: makeChip("C2", { x: 1, y: 3 }, [90]), + }, + { chipGap: 0.5 }, + ), + ) + + expect(layout.chipPlacements.C1?.ccwRotationDegrees).toBe(90) + expect(layout.chipPlacements.C2?.ccwRotationDegrees).toBe(90) + expect(layout.chipPlacements.C1?.x).toBeCloseTo(-1.75) + expect(layout.chipPlacements.C2?.x).toBeCloseTo(1.75) +})