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
76 changes: 73 additions & 3 deletions lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ export class ChipPartitionsSolver extends BaseSolver {

// 1) Build decoupling-cap-only partitions (exclude the main chip for each group)
const decapChipIdSet = new Set<ChipId>()
const decapGroupPartitions: ChipId[][] = []
const decapGroupPartitions: Array<{
chipIds: ChipId[]
netPair: [NetId, NetId]
}> = []

if (this.decouplingCapGroups && this.decouplingCapGroups.length > 0) {
for (const group of this.decouplingCapGroups) {
Expand All @@ -61,7 +64,10 @@ export class ChipPartitionsSolver extends BaseSolver {
}
// Only add a partition if there are at least two caps present in the inputProblem
if (capsOnly.length >= 2) {
decapGroupPartitions.push(capsOnly)
decapGroupPartitions.push({
chipIds: capsOnly,
netPair: group.netPair,
})
// Mark these caps as handled by decoupling-cap partitions
for (const capId of capsOnly) {
decapChipIdSet.add(capId)
Expand Down Expand Up @@ -119,8 +125,9 @@ export class ChipPartitionsSolver extends BaseSolver {

return [
...decapGroupPartitions.map((partition) =>
this.createInputProblemFromPartition(partition, inputProblem, {
this.createInputProblemFromPartition(partition.chipIds, inputProblem, {
partitionType: "decoupling_caps",
decouplingCapNetPair: partition.netPair,
}),
),
...nonDecapPartitions.map((partition) =>
Expand Down Expand Up @@ -188,6 +195,7 @@ export class ChipPartitionsSolver extends BaseSolver {
originalProblem: InputProblem,
opts?: {
partitionType?: "default" | "decoupling_caps"
decouplingCapNetPair?: [NetId, NetId]
},
): PartitionInputProblem {
const chipIds = partition
Expand Down Expand Up @@ -242,6 +250,19 @@ export class ChipPartitionsSolver extends BaseSolver {
}
}

if (
opts?.partitionType === "decoupling_caps" &&
opts.decouplingCapNetPair
) {
this.addExternalDecouplingNetConnections({
originalProblem,
relevantPinIds,
allowedNetIds: new Set(opts.decouplingCapNetPair),
relevantNetIds,
netConnMap,
})
}

for (const netId of relevantNetIds) {
if (originalProblem.netMap[netId]) {
netMap[netId] = originalProblem.netMap[netId]
Expand All @@ -260,6 +281,55 @@ export class ChipPartitionsSolver extends BaseSolver {
}
}

private addExternalDecouplingNetConnections({
originalProblem,
relevantPinIds,
allowedNetIds,
relevantNetIds,
netConnMap,
}: {
originalProblem: InputProblem
relevantPinIds: Set<PinId>
allowedNetIds: Set<NetId>
relevantNetIds: Set<NetId>
netConnMap: Record<string, boolean>
}) {
const copyAllowedExternalNets = (
targetPinId: PinId,
externalPinId: PinId,
) => {
for (const [connKey, isConnected] of Object.entries(
originalProblem.netConnMap,
)) {
if (!isConnected) continue

const [pinId, netId] = connKey.split("-") as [PinId, NetId]
if (pinId !== externalPinId || !allowedNetIds.has(netId)) continue

relevantNetIds.add(netId)
netConnMap[`${targetPinId}-${netId}`] = true
}
}

for (const [connKey, isConnected] of Object.entries(
originalProblem.pinStrongConnMap,
)) {
if (!isConnected) continue

const [pin1Id, pin2Id] = connKey.split("-") as [PinId, PinId]
const pin1IsRelevant = relevantPinIds.has(pin1Id)
const pin2IsRelevant = relevantPinIds.has(pin2Id)

if (pin1IsRelevant === pin2IsRelevant) continue

if (pin1IsRelevant) {
copyAllowedExternalNets(pin1Id, pin2Id)
} else {
copyAllowedExternalNets(pin2Id, pin1Id)
}
}
}

override visualize(): GraphicsObject {
if (this.partitions.length === 0) {
return super.visualize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
PinId,
ChipId,
NetId,
Chip,
ChipPin,
PartitionInputProblem,
} from "../../types/InputProblem"
Expand All @@ -22,6 +23,56 @@ import { doBasicInputProblemLayout } from "../LayoutPipelineSolver/doBasicInputP

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 +89,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 +122,175 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver {
}
}

private createDecouplingCapsLayout(): OutputLayout {
const entries = Object.entries(this.partitionInputProblem.chipMap).map(
([chipId, chip]) => {
const connectedMainPin = this.getPreferredExternalMainPin(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"

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 getPreferredExternalMainPin(
chipId: ChipId,
chip: Chip,
): ChipPin | null {
const candidates: Array<{
capPinId: PinId
externalPin: ChipPin
isPositiveVoltage: boolean
isGround: boolean
}> = []

for (const pinId of chip.pins) {
for (const connectedPin of this.pinIdToStronglyConnectedPins[pinId] ??
[]) {
if (getChipIdFromPinId(connectedPin.pinId) !== chipId) {
const netRole = this.getPinNetRole(pinId)
candidates.push({
capPinId: pinId,
externalPin: connectedPin,
isPositiveVoltage: netRole.isPositiveVoltage,
isGround: netRole.isGround,
})
}
}
}

candidates.sort((a, b) => {
if (a.isPositiveVoltage !== b.isPositiveVoltage) {
return a.isPositiveVoltage ? -1 : 1
}
if (a.isGround !== b.isGround) {
return a.isGround ? -1 : 1
}
const capPinOrder = a.capPinId.localeCompare(b.capPinId)
if (capPinOrder !== 0) return capPinOrder
return a.externalPin.pinId.localeCompare(b.externalPin.pinId)
})

return candidates[0]?.externalPin ?? null
}

private getPinNetRole(pinId: PinId) {
const role = {
isPositiveVoltage: false,
isGround: false,
}

for (const [connKey, isConnected] of Object.entries(
this.partitionInputProblem.netConnMap,
)) {
if (!isConnected) continue

const [connectedPinId, netId] = connKey.split("-") as [PinId, NetId]
if (connectedPinId !== pinId) continue

const net = this.partitionInputProblem.netMap[netId]
role.isPositiveVoltage ||= Boolean(net?.isPositiveVoltageSource)
role.isGround ||= Boolean(net?.isGround)
}

return role
}

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 @@ -23,6 +23,7 @@
"bpc-graph": "^0.0.66",
"calculate-packing": "^0.0.31",
"circuit-json": "^0.0.226",
"circuit-to-svg": "0.0.345",
"graphics-debug": "^0.0.64",
"react-cosmos": "^7.0.0",
"react-cosmos-plugin-vite": "^7.0.0",
Expand Down
Loading
Loading