diff --git a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts index 88db103..8d95abe 100644 --- a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts @@ -22,6 +22,8 @@ import { doBasicInputProblemLayout } from "../LayoutPipelineSolver/doBasicInputP const PIN_SIZE = 0.1 +const naturalChipIdCollator = new Intl.Collator(undefined, { numeric: true }) + export class SingleInnerPartitionPackingSolver extends BaseSolver { partitionInputProblem: PartitionInputProblem layout: OutputLayout | null = null @@ -38,6 +40,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 +73,47 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } } + private createDecouplingCapsRowLayout(): OutputLayout { + const chips = Object.values(this.partitionInputProblem.chipMap).sort( + (a, b) => naturalChipIdCollator.compare(a.chipId, b.chipId), + ) + const gap = + this.partitionInputProblem.decouplingCapsGap ?? + this.partitionInputProblem.chipGap + + const rowItems = chips.map((chip) => { + const rotation = chip.availableRotations?.includes(0) + ? 0 + : (chip.availableRotations?.[0] ?? 0) + const rotationSwapsSize = rotation === 90 || rotation === 270 + return { + chip, + rotation, + width: rotationSwapsSize ? chip.size.y : chip.size.x, + } + }) + + 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.chip.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/package.json b/package.json index 1fd506e..d876316 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@types/bun": "latest", "bpc-graph": "^0.0.66", "calculate-packing": "^0.0.31", + "circuit-to-svg": "^0.0.345", "circuit-json": "^0.0.226", "graphics-debug": "^0.0.64", "react-cosmos": "^7.0.0", diff --git a/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts new file mode 100644 index 0000000..0d7b4bb --- /dev/null +++ b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts @@ -0,0 +1,72 @@ +import { expect, test } from "bun:test" +import { SingleInnerPartitionPackingSolver } from "../../lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver" +import type { PartitionInputProblem } from "../../lib/types/InputProblem" + +const makeDecouplingPartition = ( + overrides: Partial = {}, +): PartitionInputProblem => ({ + chipMap: { + C10: { + chipId: "C10", + pins: [], + size: { x: 1, y: 0.5 }, + availableRotations: [0, 180], + }, + C2: { + chipId: "C2", + pins: [], + size: { x: 2, y: 0.5 }, + availableRotations: [0, 180], + }, + C1: { + chipId: "C1", + pins: [], + size: { x: 1, y: 0.5 }, + availableRotations: [0, 180], + }, + }, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 0.5, + partitionGap: 2, + decouplingCapsGap: 0.25, + isPartition: true, + partitionType: "decoupling_caps", + ...overrides, +}) + +test("SingleInnerPartitionPackingSolver places decoupling caps in a centered natural-id row", () => { + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem: makeDecouplingPartition(), + pinIdToStronglyConnectedPins: {}, + }) + + solver.step() + + expect(solver.solved).toBe(true) + expect(solver.failed).toBe(false) + expect(solver.activeSubSolver).toBeNull() + expect(solver.layout?.chipPlacements).toEqual({ + C1: { x: -1.75, y: 0, ccwRotationDegrees: 0 }, + C2: { x: 0, y: 0, ccwRotationDegrees: 0 }, + C10: { x: 1.75, y: 0, ccwRotationDegrees: 0 }, + }) +}) + +test("SingleInnerPartitionPackingSolver falls back to chipGap for decoupling cap row spacing", () => { + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem: makeDecouplingPartition({ + decouplingCapsGap: undefined, + }), + pinIdToStronglyConnectedPins: {}, + }) + + solver.step() + + expect(solver.solved).toBe(true) + expect(solver.layout?.chipPlacements.C1?.x).toBe(-2) + expect(solver.layout?.chipPlacements.C2?.x).toBe(0) + expect(solver.layout?.chipPlacements.C10?.x).toBe(2) +})