Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver {
}

override _step() {
if (this.partitionInputProblem.partitionType === "decoupling_caps") {
this.layout = this.createDecouplingCapLayout()
this.solved = true
return
}

// Initialize PackSolver2 if not already created
if (!this.activeSubSolver) {
const packInput = this.createPackInput()
Expand All @@ -64,6 +70,62 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver {
}
}

private createDecouplingCapLayout(): OutputLayout {
const chipEntries = Object.entries(this.partitionInputProblem.chipMap).sort(
([a], [b]) => a.localeCompare(b, undefined, { numeric: true }),
)
const gap =
this.partitionInputProblem.decouplingCapsGap ??
this.partitionInputProblem.chipGap
const chipWidths = chipEntries.map(([, chip]) =>
this.getChipWidthForRotation(
chip.size,
this.getDecouplingCapRotation(chip),
),
)
const totalWidth =
chipWidths.reduce((sum, width) => sum + width, 0) +
Math.max(0, chipWidths.length - 1) * gap
const chipPlacements: Record<string, Placement> = {}
let nextLeftEdge = -totalWidth / 2

for (let i = 0; i < chipEntries.length; i++) {
const [chipId, chip] = chipEntries[i]!
const rotation = this.getDecouplingCapRotation(chip)
const width = chipWidths[i]!

chipPlacements[chipId] = {
x: nextLeftEdge + width / 2,
y: 0,
ccwRotationDegrees: rotation,
}
nextLeftEdge += width + gap
}

return {
chipPlacements,
groupPlacements: {},
}
}

private getDecouplingCapRotation(
chip: PartitionInputProblem["chipMap"][string],
): 0 | 90 | 180 | 270 {
const availableRotations = chip.availableRotations ?? [0]

if (availableRotations.includes(0)) return 0
if (availableRotations.includes(180)) return 180

return availableRotations[0] ?? 0
}

private getChipWidthForRotation(
size: { x: number; y: number },
rotation: 0 | 90 | 180 | 270,
): number {
return rotation === 90 || rotation === 270 ? size.y : size.x
}

private createPackInput(): PackInput {
// Fall back to filtered mapping (weak + strong)
const pinToNetworkMap = createFilteredNetworkMapping({
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { expect, test } from "bun:test"
import { SingleInnerPartitionPackingSolver } from "../../lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver"
import type { PartitionInputProblem } from "../../lib/types/InputProblem"

const createDecouplingCapProblem = (
overrides: Partial<PartitionInputProblem> = {},
): PartitionInputProblem => ({
chipMap: {
C10: {
chipId: "C10",
pins: ["C10.1", "C10.2"],
size: { x: 1, y: 0.5 },
availableRotations: [0, 180],
},
C2: {
chipId: "C2",
pins: ["C2.1", "C2.2"],
size: { x: 2, y: 0.5 },
availableRotations: [0, 180],
},
C1: {
chipId: "C1",
pins: ["C1.1", "C1.2"],
size: { x: 1, y: 0.5 },
availableRotations: [0, 180],
},
},
chipPinMap: {
"C10.1": { pinId: "C10.1", offset: { x: 0, y: 0.25 }, side: "y+" },
"C10.2": { pinId: "C10.2", offset: { x: 0, y: -0.25 }, side: "y-" },
"C2.1": { pinId: "C2.1", offset: { x: 0, y: 0.25 }, side: "y+" },
"C2.2": { pinId: "C2.2", offset: { x: 0, y: -0.25 }, side: "y-" },
"C1.1": { pinId: "C1.1", offset: { x: 0, y: 0.25 }, side: "y+" },
"C1.2": { pinId: "C1.2", offset: { x: 0, y: -0.25 }, side: "y-" },
},
netMap: {},
pinStrongConnMap: {},
netConnMap: {},
chipGap: 0.2,
partitionGap: 2,
decouplingCapsGap: 0.5,
isPartition: true,
partitionType: "decoupling_caps",
...overrides,
})

const solveSinglePartition = (inputProblem: PartitionInputProblem) => {
const solver = new SingleInnerPartitionPackingSolver({
partitionInputProblem: inputProblem,
pinIdToStronglyConnectedPins: {},
})
solver.solve()
expect(solver.failed).toBe(false)
expect(solver.solved).toBe(true)
expect(solver.layout).toBeDefined()

return solver.layout!
}

test("decoupling cap partitions are placed in natural-id order", () => {
const layout = solveSinglePartition(createDecouplingCapProblem())

expect(layout.chipPlacements.C1!.x).toBeLessThan(layout.chipPlacements.C2!.x)
expect(layout.chipPlacements.C2!.x).toBeLessThan(layout.chipPlacements.C10!.x)
})

test("decoupling cap row uses decouplingCapsGap between chip edges", () => {
const layout = solveSinglePartition(createDecouplingCapProblem())
const c1 = layout.chipPlacements.C1!
const c2 = layout.chipPlacements.C2!
const c10 = layout.chipPlacements.C10!

expect(c2.x - c1.x).toBeCloseTo(1 / 2 + 0.5 + 2 / 2)
expect(c10.x - c2.x).toBeCloseTo(2 / 2 + 0.5 + 1 / 2)
})

test("decoupling cap row falls back to chipGap when no decouplingCapsGap is set", () => {
const layout = solveSinglePartition(
createDecouplingCapProblem({
decouplingCapsGap: undefined,
chipGap: 0.3,
}),
)
const c1 = layout.chipPlacements.C1!
const c2 = layout.chipPlacements.C2!
const c10 = layout.chipPlacements.C10!

expect(c2.x - c1.x).toBeCloseTo(1 / 2 + 0.3 + 2 / 2)
expect(c10.x - c2.x).toBeCloseTo(2 / 2 + 0.3 + 1 / 2)
})

test("decoupling cap row is centered around the partition origin", () => {
const layout = solveSinglePartition(createDecouplingCapProblem())
const leftEdge = layout.chipPlacements.C1!.x - 1 / 2
const rightEdge = layout.chipPlacements.C10!.x + 1 / 2

expect(leftEdge).toBeCloseTo(-rightEdge)
})

test("single decoupling cap is centered at the origin", () => {
const layout = solveSinglePartition(
createDecouplingCapProblem({
chipMap: {
C1: {
chipId: "C1",
pins: ["C1.1", "C1.2"],
size: { x: 1, y: 0.5 },
availableRotations: [0, 180],
},
},
chipPinMap: {
"C1.1": { pinId: "C1.1", offset: { x: 0, y: 0.25 }, side: "y+" },
"C1.2": { pinId: "C1.2", offset: { x: 0, y: -0.25 }, side: "y-" },
},
}),
)

expect(layout.chipPlacements.C1).toEqual({
x: 0,
y: 0,
ccwRotationDegrees: 0,
})
})

test("decoupling cap row uses rotation-aware width", () => {
const layout = solveSinglePartition(
createDecouplingCapProblem({
chipMap: {
C1: {
chipId: "C1",
pins: ["C1.1", "C1.2"],
size: { x: 2, y: 0.5 },
availableRotations: [90],
},
C2: {
chipId: "C2",
pins: ["C2.1", "C2.2"],
size: { x: 2, y: 0.5 },
availableRotations: [90],
},
},
}),
)

expect(layout.chipPlacements.C1!.ccwRotationDegrees).toBe(90)
expect(layout.chipPlacements.C2!.ccwRotationDegrees).toBe(90)
expect(layout.chipPlacements.C2!.x - layout.chipPlacements.C1!.x).toBeCloseTo(
0.5 / 2 + 0.5 + 0.5 / 2,
)
})

test("non-decoupling partitions still use the generic pack solver", () => {
const inputProblem = createDecouplingCapProblem({
partitionType: "default",
})
const solver = new SingleInnerPartitionPackingSolver({
partitionInputProblem: inputProblem,
pinIdToStronglyConnectedPins: {},
})

solver.step()

expect(solver.activeSubSolver).toBeDefined()
})
Loading