Skip to content
Closed
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 @@ -3,25 +3,75 @@
* 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 LayoutAxis = "x" | "y"

const compareNaturalChipIds = (a: ChipId, b: ChipId) => {
const aParts = a.match(/\d+|\D+/g) ?? [a]
const bParts = b.match(/\d+|\D+/g) ?? [b]
const partCount = Math.min(aParts.length, bParts.length)

for (let i = 0; i < partCount; i++) {
const aPart = aParts[i]!
const bPart = bParts[i]!
const aNumber = Number(aPart)
const bNumber = Number(bPart)
const aIsNumber = Number.isInteger(aNumber)
const bIsNumber = Number.isInteger(bNumber)

if (aIsNumber && bIsNumber && aNumber !== bNumber) {
return aNumber - bNumber
}

if (aPart !== bPart) {
return aPart.localeCompare(bPart)
}
}

return aParts.length - bParts.length
}

const getChipIdFromPinId = (pinId: PinId): ChipId =>
pinId.split(".")[0] ?? pinId

const getPreferredRotation = (chip: Chip): 0 | 90 | 180 | 270 => {
if (!chip.availableRotations?.length) return 0
return chip.availableRotations.includes(0) ? 0 : chip.availableRotations[0]!
}

const getRotatedSize = (size: { x: number; y: number }, rotation: number) => {
if (rotation === 90 || rotation === 270) {
return { x: size.y, y: size.x }
}
return size
}

const getLayoutAxisForExternalSide = (
side: ChipPin["side"] | undefined,
): LayoutAxis | null => {
if (side?.startsWith("x")) return "y"
if (side?.startsWith("y")) return "x"
return null
}

export class SingleInnerPartitionPackingSolver extends BaseSolver {
partitionInputProblem: PartitionInputProblem
layout: OutputLayout | null = null
Expand All @@ -38,6 +88,13 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver {
}

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

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

private createDecouplingCapsLayout(): OutputLayout {
const entries = Object.entries(this.partitionInputProblem.chipMap).map(
([chipId, chip]) => {
const connectedMainPin = this.getStronglyConnectedExternalPin(
chipId,
chip,
)
const rotation = getPreferredRotation(chip)
const layoutSize = this.getChipLayoutSize(chip)
const rotatedSize = getRotatedSize(layoutSize, rotation)

return {
chipId,
connectedMainPin,
rotation,
rotatedSize,
}
},
)

const externalPinSideCounts = entries.reduce(
(counts, entry) => {
if (entry.connectedMainPin?.side.startsWith("x")) counts.x += 1
if (entry.connectedMainPin?.side.startsWith("y")) counts.y += 1
return counts
},
{ x: 0, y: 0 },
)
const layoutAxis: LayoutAxis =
externalPinSideCounts.x > externalPinSideCounts.y ? "y" : "x"
Comment thread
Gimyoonsoo marked this conversation as resolved.

entries.sort((a, b) => {
const aHasMainPin = a.connectedMainPin ? 1 : 0
const bHasMainPin = b.connectedMainPin ? 1 : 0
if (aHasMainPin !== bHasMainPin) return bHasMainPin - aHasMainPin

const aCoordinate = this.getDecouplingCapSortCoordinate(a, layoutAxis)
const bCoordinate = this.getDecouplingCapSortCoordinate(b, layoutAxis)

if (aCoordinate !== bCoordinate) return aCoordinate - bCoordinate
return compareNaturalChipIds(a.chipId, b.chipId)
})

const gap =
this.partitionInputProblem.decouplingCapsGap ??
this.partitionInputProblem.chipGap
const totalSpan =
entries.reduce((sum, entry) => {
return sum + entry.rotatedSize[layoutAxis]
}, 0) +
Math.max(0, entries.length - 1) * gap
const chipPlacements: Record<string, Placement> = {}
let cursor = -totalSpan / 2

for (const entry of entries) {
const span = entry.rotatedSize[layoutAxis]
const center = cursor + span / 2

chipPlacements[entry.chipId] = {
x: layoutAxis === "x" ? center : 0,
y: layoutAxis === "y" ? center : 0,
ccwRotationDegrees: entry.rotation,
}

cursor += span + gap
}

return {
chipPlacements,
groupPlacements: {},
}
}

private getChipLayoutSize(chip: Chip) {
let minX = -chip.size.x / 2
let maxX = chip.size.x / 2
let minY = -chip.size.y / 2
let maxY = chip.size.y / 2

for (const pinId of chip.pins) {
const pin = this.partitionInputProblem.chipPinMap[pinId]
if (!pin) continue

minX = Math.min(minX, pin.offset.x - PIN_SIZE / 2)
maxX = Math.max(maxX, pin.offset.x + PIN_SIZE / 2)
minY = Math.min(minY, pin.offset.y - PIN_SIZE / 2)
maxY = Math.max(maxY, pin.offset.y + PIN_SIZE / 2)
}

return {
x: maxX - minX,
y: maxY - minY,
}
}

private getDecouplingCapSortCoordinate(
entry: {
connectedMainPin: ChipPin | null
},
fallbackLayoutAxis: LayoutAxis,
) {
const mainPin = entry.connectedMainPin
if (!mainPin) return 0

const sideAwareAxis =
getLayoutAxisForExternalSide(mainPin.side) ?? fallbackLayoutAxis
return mainPin.offset[sideAwareAxis]
}

private getStronglyConnectedExternalPin(
chipId: ChipId,
chip: Chip,
): ChipPin | null {
for (const pinId of chip.pins) {
for (const connectedPin of this.pinIdToStronglyConnectedPins[pinId] ??
[]) {
if (getChipIdFromPinId(connectedPin.pinId) !== chipId) {
return connectedPin
}
}
}

return null
}

private createPackInput(): PackInput {
// Fall back to filtered mapping (weak + strong)
const pinToNetworkMap = createFilteredNetworkMapping({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { test, expect } from "bun:test"
import { test, expect, mock } from "bun:test"
import { IdentifyDecouplingCapsSolver } from "../../lib/solvers/IdentifyDecouplingCapsSolver/IdentifyDecouplingCapsSolver"
import { problem } from "../../pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx"

mock.module("lib/components/LayoutPipelineDebugger", () => ({
LayoutPipelineDebugger: () => null,
}))

const { problem } = await import(
"../../pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx"
)

test("IdentifyDecouplingCapsSolver identifies decoupling capacitor groups from LayoutPipelineSolver06", () => {
const solver = new IdentifyDecouplingCapsSolver(problem)
Expand Down
Loading
Loading