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,13 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver {
}

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

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

private createDecouplingCapLayout(): OutputLayout {
const chipIds = Object.keys(this.partitionInputProblem.chipMap)
const direction = this.getDecouplingCapLayoutDirection(chipIds)
const sortedChipIds = this.sortDecouplingCapChipIds(chipIds, direction)
const gap =
this.partitionInputProblem.decouplingCapsGap ??
this.partitionInputProblem.chipGap
const pitch = this.getDecouplingCapPitch(sortedChipIds, direction, gap)
const midpoint = (sortedChipIds.length - 1) / 2
const chipPlacements: Record<string, Placement> = {}

for (let i = 0; i < sortedChipIds.length; i++) {
const chipId = sortedChipIds[i]!
const chip = this.partitionInputProblem.chipMap[chipId]!
const rotation = this.chooseDecouplingCapRotation(chip.availableRotations)
const centeredIndex = i - midpoint

chipPlacements[chipId] = {
x: direction === "horizontal" ? centeredIndex * pitch : 0,
y: direction === "vertical" ? -centeredIndex * pitch : 0,
ccwRotationDegrees: rotation,
}
}

return {
chipPlacements,
groupPlacements: {},
}
}

private getDecouplingCapLayoutDirection(
chipIds: ChipId[],
): "horizontal" | "vertical" {
let xSideConnections = 0
let ySideConnections = 0

for (const chipId of chipIds) {
for (const pin of this.getExternalStrongPinsForChip(chipId)) {
if (pin.side === "x-" || pin.side === "x+") xSideConnections++
if (pin.side === "y-" || pin.side === "y+") ySideConnections++
}
}

return xSideConnections >= ySideConnections ? "vertical" : "horizontal"
}

private sortDecouplingCapChipIds(
chipIds: ChipId[],
direction: "horizontal" | "vertical",
): ChipId[] {
return [...chipIds].sort((chipIdA, chipIdB) => {
const keyA = this.getDecouplingCapSortKey(chipIdA, direction)
const keyB = this.getDecouplingCapSortKey(chipIdB, direction)

if (keyA !== keyB) return keyA - keyB
return chipIdA.localeCompare(chipIdB, undefined, { numeric: true })
})
}

private getDecouplingCapSortKey(
chipId: ChipId,
direction: "horizontal" | "vertical",
): number {
const externalPins = this.getExternalStrongPinsForChip(chipId)
if (externalPins.length === 0) return 0

const coordinateSum = externalPins.reduce((sum, pin) => {
return sum + (direction === "vertical" ? -pin.offset.y : pin.offset.x)
}, 0)

return coordinateSum / externalPins.length
}

private getExternalStrongPinsForChip(chipId: ChipId): ChipPin[] {
const chip = this.partitionInputProblem.chipMap[chipId]
if (!chip) return []

const partitionPinIds = new Set(
Object.keys(this.partitionInputProblem.chipPinMap),
)
const externalPins: ChipPin[] = []

for (const pinId of chip.pins) {
const stronglyConnectedPins =
this.pinIdToStronglyConnectedPins[pinId] ?? []

for (const connectedPin of stronglyConnectedPins) {
if (!partitionPinIds.has(connectedPin.pinId)) {
externalPins.push(connectedPin)
}
}
}

return externalPins
}

private getDecouplingCapPitch(
chipIds: ChipId[],
direction: "horizontal" | "vertical",
gap: number,
): number {
const maxAxisSize = chipIds.reduce((maxSize, chipId) => {
const chip = this.partitionInputProblem.chipMap[chipId]
if (!chip) return maxSize
const rotation = this.chooseDecouplingCapRotation(chip.availableRotations)
const size = this.getRotatedChipSize(chip.size, rotation)
const axisSize = direction === "horizontal" ? size.x : size.y
return Math.max(maxSize, axisSize)
}, 0)

return maxAxisSize + gap
}

private chooseDecouplingCapRotation(
availableRotations?: Array<0 | 90 | 180 | 270>,
): 0 | 90 | 180 | 270 {
if (!availableRotations || availableRotations.length === 0) return 0
return availableRotations.includes(0) ? 0 : availableRotations[0]!
}

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

private createPackInput(): PackInput {
// Fall back to filtered mapping (weak + strong)
const pinToNetworkMap = createFilteredNetworkMapping({
Expand Down
14 changes: 12 additions & 2 deletions pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { PackInput } from "calculate-packing"
import { LayoutPipelineDebugger } from "lib/components/LayoutPipelineDebugger"
import { lazy, Suspense } from "react"
import type { InputProblem } from "lib/index"

const LayoutPipelineDebugger = lazy(() =>
import("lib/components/LayoutPipelineDebugger").then((mod) => ({
default: mod.LayoutPipelineDebugger,
})),
)

export const problem: InputProblem = {
chipMap: {
U3: {
Expand Down Expand Up @@ -877,5 +883,9 @@ export const problem: InputProblem = {
}

export default function LayoutPipelineSolver06Page() {
return <LayoutPipelineDebugger problem={problem} />
return (
<Suspense fallback={null}>
<LayoutPipelineDebugger problem={problem} />
</Suspense>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { expect, test } from "bun:test"
import { SingleInnerPartitionPackingSolver } from "lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver"
import type {
ChipId,
ChipPin,
NetId,
PartitionInputProblem,
PinId,
} from "lib/types/InputProblem"

const makeCap = (chipId: ChipId) => ({
chipId,
pins: [`${chipId}.1`, `${chipId}.2`],
size: { x: 0.53, y: 1.06 },
availableRotations: [0] as Array<0 | 90 | 180 | 270>,
})

const makeCapPinMap = (chipIds: ChipId[]) => {
const chipPinMap: Record<PinId, ChipPin> = {}

for (const chipId of chipIds) {
chipPinMap[`${chipId}.1`] = {
pinId: `${chipId}.1`,
offset: { x: 0, y: 0.53 },
side: "y+",
}
chipPinMap[`${chipId}.2`] = {
pinId: `${chipId}.2`,
offset: { x: 0, y: -0.53 },
side: "y-",
}
}

return chipPinMap
}

const makeDecouplingCapPartition = (
chipIds: ChipId[],
): PartitionInputProblem => {
const chipMap = Object.fromEntries(
chipIds.map((chipId) => [chipId, makeCap(chipId)]),
)
const netConnMap: Record<`${PinId}-${NetId}`, boolean> = {}

for (const chipId of chipIds) {
netConnMap[`${chipId}.1-VCC`] = true
netConnMap[`${chipId}.2-GND`] = true
}

return {
chipMap,
chipPinMap: makeCapPinMap(chipIds),
netMap: {
VCC: { netId: "VCC", isPositiveVoltageSource: true },
GND: { netId: "GND", isGround: true },
},
pinStrongConnMap: {},
netConnMap,
chipGap: 0.6,
decouplingCapsGap: 0.2,
partitionGap: 1.2,
isPartition: true,
partitionType: "decoupling_caps",
}
}

test("SingleInnerPartitionPackingSolver creates a vertical decoupling capacitor column ordered by main-chip pins", () => {
const partitionInputProblem = makeDecouplingCapPartition([
"C_LOW",
"C_TOP",
"C_MID",
])
const solver = new SingleInnerPartitionPackingSolver({
partitionInputProblem,
pinIdToStronglyConnectedPins: {
"C_TOP.1": [{ pinId: "U1.1", offset: { x: -2, y: 2 }, side: "x-" }],
"C_MID.1": [{ pinId: "U1.2", offset: { x: -2, y: 0 }, side: "x-" }],
"C_LOW.1": [{ pinId: "U1.3", offset: { x: -2, y: -2 }, side: "x-" }],
},
})

solver.solve()

expect(solver.failed).toBe(false)
expect(solver.layout).not.toBeNull()

const placements = solver.layout!.chipPlacements
expect(placements.C_TOP!.y).toBeGreaterThan(placements.C_MID!.y)
expect(placements.C_MID!.y).toBeGreaterThan(placements.C_LOW!.y)
expect(placements.C_TOP!.x).toBeCloseTo(0, 6)
expect(placements.C_MID!.x).toBeCloseTo(0, 6)
expect(placements.C_LOW!.x).toBeCloseTo(0, 6)
expect(placements.C_TOP!.y - placements.C_MID!.y).toBeCloseTo(1.26, 6)
expect(placements.C_MID!.y - placements.C_LOW!.y).toBeCloseTo(1.26, 6)
})

test("SingleInnerPartitionPackingSolver creates a horizontal decoupling capacitor row for y-side main-chip pins", () => {
const partitionInputProblem = makeDecouplingCapPartition([
"C_RIGHT",
"C_LEFT",
"C_CENTER",
])
const solver = new SingleInnerPartitionPackingSolver({
partitionInputProblem,
pinIdToStronglyConnectedPins: {
"C_LEFT.1": [{ pinId: "U1.1", offset: { x: -2, y: 2 }, side: "y+" }],
"C_CENTER.1": [{ pinId: "U1.2", offset: { x: 0, y: 2 }, side: "y+" }],
"C_RIGHT.1": [{ pinId: "U1.3", offset: { x: 2, y: 2 }, side: "y+" }],
},
})

solver.solve()

expect(solver.failed).toBe(false)
expect(solver.layout).not.toBeNull()

const placements = solver.layout!.chipPlacements
expect(placements.C_LEFT!.x).toBeLessThan(placements.C_CENTER!.x)
expect(placements.C_CENTER!.x).toBeLessThan(placements.C_RIGHT!.x)
expect(placements.C_LEFT!.y).toBeCloseTo(0, 6)
expect(placements.C_CENTER!.y).toBeCloseTo(0, 6)
expect(placements.C_RIGHT!.y).toBeCloseTo(0, 6)
expect(placements.C_CENTER!.x - placements.C_LEFT!.x).toBeCloseTo(0.73, 6)
expect(placements.C_RIGHT!.x - placements.C_CENTER!.x).toBeCloseTo(0.73, 6)
})
Loading