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
277 changes: 274 additions & 3 deletions lib/solvers/PartitionPackingSolver/PartitionPackingSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ 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 { InputProblem, PinId, NetId } from "../../types/InputProblem"
import type {
ChipId,
InputProblem,
PinId,
NetId,
} from "../../types/InputProblem"
import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem"
import type { PackedPartition } from "../PackInnerPartitionsSolver/PackInnerPartitionsSolver"
import type { Side } from "lib/types/Side"

export interface PartitionPackingSolverInput {
packedPartitions: PackedPartition[]
Expand All @@ -27,6 +33,16 @@ type PartitionGroup = {
}
}

type DecouplingCapTarget = {
chipId: ChipId
side: Side
targetAxis: number
fixedAxis: number
tangentSize: number
normalCoordinate: "x" | "y"
tangentCoordinate: "x" | "y"
}

export class PartitionPackingSolver extends BaseSolver {
packedPartitions: PackedPartition[]
inputProblem: InputProblem
Expand Down Expand Up @@ -57,7 +73,9 @@ export class PartitionPackingSolver extends BaseSolver {

if (this.packedPartitions.length === 1) {
// Only one partition, use its layout directly
this.finalLayout = this.packedPartitions[0]!.layout
this.finalLayout = this.applyDecouplingCapSpecializedLayout(
this.packedPartitions[0]!.layout,
)
this.solved = true
return
}
Expand Down Expand Up @@ -87,7 +105,8 @@ export class PartitionPackingSolver extends BaseSolver {
this.packSolver2.packedComponents,
partitionGroups,
)
this.finalLayout = packedLayout
this.finalLayout =
this.applyDecouplingCapSpecializedLayout(packedLayout)
this.solved = true
this.activeSubSolver = null
}
Expand Down Expand Up @@ -314,6 +333,258 @@ export class PartitionPackingSolver extends BaseSolver {
}
}

private applyDecouplingCapSpecializedLayout(layout: OutputLayout) {
const capTargets = this.getDecouplingCapTargets(layout)
if (capTargets.length === 0) return layout

const chipPlacements = { ...layout.chipPlacements }
const targetsByLane = new Map<string, DecouplingCapTarget[]>()

for (const target of capTargets) {
const laneKey = `${target.normalCoordinate}:${target.fixedAxis}:${target.side}`
const targets = targetsByLane.get(laneKey) ?? []
targets.push(target)
targetsByLane.set(laneKey, targets)
}

for (const targets of targetsByLane.values()) {
const adjustedAxisValues = this.spreadTargetsAlongAxis(targets)

targets.forEach((target, index) => {
const originalPlacement = chipPlacements[target.chipId]
if (!originalPlacement) return

chipPlacements[target.chipId] = {
...originalPlacement,
[target.normalCoordinate]: target.fixedAxis,
[target.tangentCoordinate]: adjustedAxisValues[index]!,
}
})
}

return {
...layout,
chipPlacements,
}
}

private getDecouplingCapTargets(layout: OutputLayout) {
const pinOwnerMap = this.buildPinOwnerMap()
const targets: DecouplingCapTarget[] = []

for (const [chipId, chip] of Object.entries(this.inputProblem.chipMap)) {
if (chip.pins.length !== 2) continue
if (!layout.chipPlacements[chipId]) continue
if (!this.isDecouplingCapChip(chipId)) continue

const mainConnection = this.findMainChipConnectionForCap(
chipId,
pinOwnerMap,
layout,
)
if (!mainConnection) continue

const mainChip = this.inputProblem.chipMap[mainConnection.mainChipId]
const mainPlacement = layout.chipPlacements[mainConnection.mainChipId]
const mainPin = this.inputProblem.chipPinMap[mainConnection.mainPinId]
if (!mainChip || !mainPlacement || !mainPin) continue

const mainRotation = mainPlacement.ccwRotationDegrees ?? 0
const mainSize = this.getRotatedSize(mainChip.size, mainRotation)
const mainPinOffset = this.rotatePoint(mainPin.offset, mainRotation)
const mainPinSide = this.rotateSide(mainPin.side, mainRotation)
const capPlacement = layout.chipPlacements[chipId]!
const capSize = this.getRotatedSize(
chip.size,
capPlacement.ccwRotationDegrees ?? 0,
)
const gap =
this.inputProblem.decouplingCapsGap ?? this.inputProblem.chipGap

if (mainPinSide === "x-" || mainPinSide === "x+") {
const direction = mainPinSide === "x-" ? -1 : 1
targets.push({
chipId,
side: mainPinSide,
fixedAxis:
mainPlacement.x +
direction * (mainSize.x / 2 + gap + capSize.x / 2),
targetAxis: mainPlacement.y + mainPinOffset.y,
tangentSize: capSize.y,
normalCoordinate: "x",
tangentCoordinate: "y",
})
} else {
const direction = mainPinSide === "y-" ? -1 : 1
targets.push({
chipId,
side: mainPinSide,
fixedAxis:
mainPlacement.y +
direction * (mainSize.y / 2 + gap + capSize.y / 2),
targetAxis: mainPlacement.x + mainPinOffset.x,
tangentSize: capSize.x,
normalCoordinate: "y",
tangentCoordinate: "x",
})
}
}

return targets
}

private buildPinOwnerMap() {
const pinOwnerMap = new Map<PinId, ChipId>()
for (const [chipId, chip] of Object.entries(this.inputProblem.chipMap)) {
for (const pinId of chip.pins) {
pinOwnerMap.set(pinId, chipId)
}
}
return pinOwnerMap
}

private isDecouplingCapChip(chipId: ChipId) {
const chip = this.inputProblem.chipMap[chipId]
if (!chip || chip.pins.length !== 2) return false

const netIds = new Set<NetId>()
for (const pinId of chip.pins) {
for (const [connKey, connected] of Object.entries(
this.inputProblem.netConnMap,
)) {
if (!connected) continue
const [connPinId, netId] = connKey.split("-") as [PinId, NetId]
if (connPinId === pinId) netIds.add(netId)
}
}

let hasGround = false
let hasPositiveVoltage = false
for (const netId of netIds) {
const net = this.inputProblem.netMap[netId]
hasGround ||= Boolean(net?.isGround)
hasPositiveVoltage ||= Boolean(net?.isPositiveVoltageSource)
}

return hasGround && hasPositiveVoltage
}

private findMainChipConnectionForCap(
capChipId: ChipId,
pinOwnerMap: Map<PinId, ChipId>,
layout: OutputLayout,
) {
const capChip = this.inputProblem.chipMap[capChipId]
if (!capChip) return null

for (const [connKey, connected] of Object.entries(
this.inputProblem.pinStrongConnMap,
)) {
if (!connected) continue
const [pinA, pinB] = connKey.split("-") as [PinId, PinId]
const ownerA = pinOwnerMap.get(pinA)
const ownerB = pinOwnerMap.get(pinB)

const capPinId =
ownerA === capChipId ? pinA : ownerB === capChipId ? pinB : null
const mainPinId =
ownerA === capChipId ? pinB : ownerB === capChipId ? pinA : null
const mainChipId = mainPinId ? pinOwnerMap.get(mainPinId) : null

if (!capPinId || !mainPinId || !mainChipId) continue
if (mainChipId === capChipId) continue
if (!layout.chipPlacements[mainChipId]) continue
if ((this.inputProblem.chipMap[mainChipId]?.pins.length ?? 0) <= 2)
continue

return { capPinId, mainPinId, mainChipId }
}

return null
}

private spreadTargetsAlongAxis(targets: DecouplingCapTarget[]) {
const minGap =
this.inputProblem.decouplingCapsGap ?? this.inputProblem.chipGap
const indexedTargets = targets
.map((target, originalIndex) => ({ target, originalIndex }))
.sort(
(a, b) =>
a.target.targetAxis - b.target.targetAxis ||
a.target.chipId.localeCompare(b.target.chipId),
)

const adjustedSortedValues: number[] = []
for (let i = 0; i < indexedTargets.length; i++) {
const current = indexedTargets[i]!.target
if (i === 0) {
adjustedSortedValues.push(current.targetAxis)
continue
}

const previous = indexedTargets[i - 1]!.target
const previousValue = adjustedSortedValues[i - 1]!
const minimumValue =
previousValue +
previous.tangentSize / 2 +
current.tangentSize / 2 +
minGap
adjustedSortedValues.push(Math.max(current.targetAxis, minimumValue))
}

const targetCenter =
indexedTargets.reduce((sum, item) => sum + item.target.targetAxis, 0) /
indexedTargets.length
const adjustedCenter =
adjustedSortedValues.reduce((sum, value) => sum + value, 0) /
adjustedSortedValues.length
const centerOffset = adjustedCenter - targetCenter

const valuesByOriginalIndex: number[] = []
adjustedSortedValues.forEach((value, sortedIndex) => {
valuesByOriginalIndex[indexedTargets[sortedIndex]!.originalIndex] =
value - centerOffset
})

return valuesByOriginalIndex
}

private rotatePoint(
point: { x: number; y: number },
rotationDegrees: number,
) {
const normalizedRotation = ((rotationDegrees % 360) + 360) % 360
if (normalizedRotation === 90) return { x: -point.y, y: point.x }
if (normalizedRotation === 180) return { x: -point.x, y: -point.y }
if (normalizedRotation === 270) return { x: point.y, y: -point.x }
return point
}

private rotateSide(side: Side, rotationDegrees: number): Side {
const sideVectors: Record<Side, { x: number; y: number }> = {
"x+": { x: 1, y: 0 },
"x-": { x: -1, y: 0 },
"y+": { x: 0, y: 1 },
"y-": { x: 0, y: -1 },
}
const rotatedVector = this.rotatePoint(sideVectors[side], rotationDegrees)
if (rotatedVector.x === 1) return "x+"
if (rotatedVector.x === -1) return "x-"
if (rotatedVector.y === 1) return "y+"
return "y-"
}

private getRotatedSize(
size: { x: number; y: number },
rotationDegrees: number,
) {
const normalizedRotation = ((rotationDegrees % 360) + 360) % 360
if (normalizedRotation === 90 || normalizedRotation === 270) {
return { x: size.y, y: size.x }
}
return size
}

private getCombinedPackedPartitionsProblem(): InputProblem {
const combinedProblem: InputProblem = {
chipMap: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect, test } from "bun:test"
import { LayoutPipelineSolver } from "../../lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver"
import { problem } from "../../pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx"

test("LayoutPipelineSolver places decoupling capacitors beside their connected main-chip pins", () => {
const solver = new LayoutPipelineSolver(problem)
solver.solve()

expect(solver.failed).toBe(false)
const layout = solver.partitionPackingSolver!.finalLayout!
const u3Placement = layout.chipPlacements.U3!
const u3LeftEdge = u3Placement.x - problem.chipMap.U3!.size.x / 2

const v33Group =
solver.identifyDecouplingCapsSolver!.outputDecouplingCapGroups.find(
(group) =>
group.mainChipId === "U3" &&
group.netPair.includes("GND") &&
group.netPair.includes("V3_3"),
)!

const capPlacements = v33Group.decouplingCapChipIds.map((chipId) => ({
chipId,
placement: layout.chipPlacements[chipId]!,
}))

expect(capPlacements).toHaveLength(6)
for (const { placement } of capPlacements) {
expect(placement.x).toBeLessThan(u3LeftEdge - 0.1)
}

const capYs = capPlacements.map(({ placement }) => placement.y)
const ySpread = Math.max(...capYs) - Math.min(...capYs)
expect(ySpread).toBeGreaterThan(1)
})
Loading