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
68 changes: 65 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,16 @@ export class ChipPartitionsSolver extends BaseSolver {
}
}

if (opts?.partitionType === "decoupling_caps") {
this.addInferredDecouplingCapNetConnections({
relevantPinIds,
netPair: opts.decouplingCapNetPair ?? null,
originalProblem,
relevantNetIds,
netConnMap,
})
}

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

private addInferredDecouplingCapNetConnections({
relevantPinIds,
netPair,
originalProblem,
relevantNetIds,
netConnMap,
}: {
relevantPinIds: Set<PinId>
netPair: [NetId, NetId] | null
originalProblem: InputProblem
relevantNetIds: Set<NetId>
netConnMap: Record<string, boolean>
}) {
if (!netPair) return
const decouplingNetIds = new Set<NetId>(netPair)

for (const [connKey, isConnected] of Object.entries(
originalProblem.pinStrongConnMap,
)) {
if (!isConnected) continue
const [pin1Id, pin2Id] = connKey.split("-") as [PinId, PinId]

const partitionPinId = relevantPinIds.has(pin1Id)
? pin1Id
: relevantPinIds.has(pin2Id)
? pin2Id
: null
const externalPinId = partitionPinId === pin1Id ? pin2Id : pin1Id

if (!partitionPinId || relevantPinIds.has(externalPinId)) continue

for (const [netConnKey, isNetConnected] of Object.entries(
originalProblem.netConnMap,
)) {
if (!isNetConnected) continue
const [pinId, netId] = netConnKey.split("-") as [PinId, NetId]
if (pinId !== externalPinId || !decouplingNetIds.has(netId)) continue

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

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 @@ -51,8 +51,11 @@ export class IdentifyDecouplingCapsSolver extends BaseSolver {
private isTwoPinRestrictedRotation(chip: Chip): boolean {
if (chip.pins.length !== 2) return false

// Must be restricted to 0/180 or a single fixed orientation
if (!chip.availableRotations) return false
// Missing rotation metadata means the imported schematic has a fixed orientation.
if (!chip.availableRotations) return true

if (chip.isDecouplingCap) return true

const allowed = new Set<0 | 180>([0, 180])
return (
chip.availableRotations.length > 0 &&
Expand Down Expand Up @@ -113,6 +116,19 @@ export class IdentifyDecouplingCapsSolver extends BaseSolver {
return best ? best.id : null
}

private isGroundNet(netId: NetId): boolean {
const net = this.inputProblem.netMap[netId]
return Boolean(net?.isGround) || /(^|[_-])gnd($|[_-])/i.test(netId)
}

private isPositiveVoltageNet(netId: NetId): boolean {
const net = this.inputProblem.netMap[netId]
return (
Boolean(net?.isPositiveVoltageSource) ||
/(^v\d|vcc|vdd|vbat|vbus|usb_vdd|[_-]v\d)/i.test(netId)
)
}

/** Get all net IDs connected to a pin */
private getNetIdsForPin(pinId: PinId): Set<NetId> {
const nets = new Set<NetId>()
Expand All @@ -123,6 +139,24 @@ export class IdentifyDecouplingCapsSolver extends BaseSolver {
const [p, n] = connKey.split("-") as [PinId, NetId]
if (p === pinId) nets.add(n)
}

for (const [connKey, connected] of Object.entries(
this.inputProblem.pinStrongConnMap,
)) {
if (!connected) continue
const [a, b] = connKey.split("-") as [PinId, PinId]
const connectedPinId = a === pinId ? b : b === pinId ? a : null
if (!connectedPinId) continue

for (const [netConnKey, isNetConnected] of Object.entries(
this.inputProblem.netConnMap,
)) {
if (!isNetConnected) continue
const [p, n] = netConnKey.split("-") as [PinId, NetId]
if (p === connectedPinId) nets.add(n)
Comment on lines +151 to +156
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve inferred one-hop nets in cap partitions

When a cap pin only gets its power net through the newly added one-hop strong connection path, that inferred net exists only inside getNetIdsForPin; ChipPartitionsSolver.createInputProblemFromPartition later copies only direct netConnMap entries for the cap pins and drops the strong edge to the main chip because the main chip is excluded from the decap partition. In those inputs, the cap is identified and moved into a decoupling_caps partition, but its inherited V/GND network is absent from the partition passed to PartitionPackingSolver, so the final partition packing cannot align that decap row to the corresponding power net.

Useful? React with 👍 / 👎.

}
}

return nets
}

Expand Down Expand Up @@ -190,11 +224,9 @@ export class IdentifyDecouplingCapsSolver extends BaseSolver {
// Ensure the net pair corresponds to a true decoupling capacitor:
// one net must be ground and the other a positive voltage source
const [n1, n2] = netPair
const net1 = this.inputProblem.netMap[n1]
const net2 = this.inputProblem.netMap[n2]
const isDecouplingNetPair =
(net1?.isGround && net2?.isPositiveVoltageSource) ||
(net2?.isGround && net1?.isPositiveVoltageSource)
(this.isGroundNet(n1) && this.isPositiveVoltageNet(n2)) ||
(this.isGroundNet(n2) && this.isPositiveVoltageNet(n1))
if (!isDecouplingNetPair) return

this.addToGroup(mainChipId, netPair, currentChip.chipId)
Expand Down
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.createDecouplingCapsLayout()
this.solved = true
return
}

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

private createDecouplingCapsLayout(): OutputLayout {
const chipPlacements: Record<string, Placement> = {}
const gap =
this.partitionInputProblem.decouplingCapsGap ??
this.partitionInputProblem.chipGap

const chips = Object.values(this.partitionInputProblem.chipMap)
.map((chip) => ({
chip,
rotation: this.getPreferredDecouplingCapRotation(
chip.availableRotations,
),
}))
.sort((a, b) =>
a.chip.chipId.localeCompare(b.chip.chipId, undefined, {
numeric: true,
sensitivity: "base",
}),
)

const widths = chips.map(({ chip, rotation }) =>
rotation === 90 || rotation === 270 ? chip.size.y : chip.size.x,
)
const totalWidth =
widths.reduce((sum, width) => sum + width, 0) +
Math.max(0, chips.length - 1) * gap

let cursorX = -totalWidth / 2

for (const [index, { chip, rotation }] of chips.entries()) {
const width = widths[index]!
chipPlacements[chip.chipId] = {
x: cursorX + width / 2,
y: 0,
ccwRotationDegrees: rotation,
}
cursorX += width + gap
}

return {
chipPlacements,
groupPlacements: {},
}
}

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

private createPackInput(): PackInput {
// Fall back to filtered mapping (weak + strong)
const pinToNetworkMap = createFilteredNetworkMapping({
Expand Down
73 changes: 73 additions & 0 deletions tests/ChipPartitionsSolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,76 @@ test("ChipPartitionsSolver visualization contains partition components", () => {
expect(visualization.rects?.length).toBeGreaterThan(0)
expect(visualization.texts?.length).toBeGreaterThan(0)
})

test("ChipPartitionsSolver preserves inferred decoupling cap nets in cap partitions", () => {
const inputProblem: InputProblem = {
chipMap: {
U1: {
chipId: "U1",
pins: ["U1.1", "U1.2"],
size: { x: 2, y: 2 },
},
C1: {
chipId: "C1",
pins: ["C1.1", "C1.2"],
size: { x: 0.5, y: 1 },
},
C2: {
chipId: "C2",
pins: ["C2.1", "C2.2"],
size: { x: 0.5, y: 1 },
},
},
chipPinMap: {
"U1.1": { pinId: "U1.1", offset: { x: -1, y: 0.5 }, side: "x-" },
"U1.2": { pinId: "U1.2", offset: { x: -1, y: -0.5 }, side: "x-" },
"C1.1": { pinId: "C1.1", offset: { x: 0, y: 0.5 }, side: "y+" },
"C1.2": { pinId: "C1.2", offset: { x: 0, y: -0.5 }, side: "y-" },
"C2.1": { pinId: "C2.1", offset: { x: 0, y: 0.5 }, side: "y+" },
"C2.2": { pinId: "C2.2", offset: { x: 0, y: -0.5 }, side: "y-" },
},
netMap: {
GND: { netId: "GND", isGround: true },
VCC: { netId: "VCC", isPositiveVoltageSource: true },
},
pinStrongConnMap: {
"U1.1-C1.1": true,
"C1.1-U1.1": true,
"U1.2-C2.1": true,
"C2.1-U1.2": true,
},
netConnMap: {
"U1.1-VCC": true,
"U1.2-VCC": true,
"C1.2-GND": true,
"C2.2-GND": true,
},
chipGap: 0.2,
partitionGap: 2,
}

const solver = new ChipPartitionsSolver({
inputProblem,
decouplingCapGroups: [
{
decouplingCapGroupId: "decap_group_U1__GND__VCC",
mainChipId: "U1",
netPair: ["GND", "VCC"],
decouplingCapChipIds: ["C1", "C2"],
},
],
})
solver.solve()

const decapPartition = solver.partitions.find(
(partition) => partition.partitionType === "decoupling_caps",
)

expect(decapPartition).toBeDefined()
expect(decapPartition!.netConnMap["C1.1-VCC"]).toBe(true)
expect(decapPartition!.netConnMap["C2.1-VCC"]).toBe(true)
expect(decapPartition!.netConnMap["C1.2-GND"]).toBe(true)
expect(decapPartition!.netConnMap["C2.2-GND"]).toBe(true)
expect(decapPartition!.netMap.VCC).toBeDefined()
expect(decapPartition!.netMap.GND).toBeDefined()
})
Original file line number Diff line number Diff line change
@@ -1,8 +1,57 @@
import { test, expect } from "bun:test"
import { IdentifyDecouplingCapsSolver } from "../../lib/solvers/IdentifyDecouplingCapsSolver/IdentifyDecouplingCapsSolver"
import { problem } from "../../pages/LayoutPipelineSolver/LayoutPipelineSolver06.page.tsx"
import type { InputProblem } from "../../lib/types/InputProblem"

test("IdentifyDecouplingCapsSolver identifies decoupling capacitor groups from LayoutPipelineSolver06", () => {
const problem: InputProblem = {
chipMap: {
U1: {
chipId: "U1",
pins: ["U1.1", "U1.2"],
size: { x: 2, y: 2 },
availableRotations: [0, 90, 180, 270],
},
C1: {
chipId: "C1",
pins: ["C1.1", "C1.2"],
size: { x: 0.5, y: 1 },
availableRotations: [0],
},
C2: {
chipId: "C2",
pins: ["C2.1", "C2.2"],
size: { x: 0.5, y: 1 },
availableRotations: [0],
},
},
chipPinMap: {
"U1.1": { pinId: "U1.1", offset: { x: -1, y: 0.5 }, side: "x-" },
"U1.2": { pinId: "U1.2", offset: { x: -1, y: -0.5 }, side: "x-" },
"C1.1": { pinId: "C1.1", offset: { x: 0, y: 0.5 }, side: "y+" },
"C1.2": { pinId: "C1.2", offset: { x: 0, y: -0.5 }, side: "y-" },
"C2.1": { pinId: "C2.1", offset: { x: 0, y: 0.5 }, side: "y+" },
"C2.2": { pinId: "C2.2", offset: { x: 0, y: -0.5 }, side: "y-" },
},
netMap: {
GND: { netId: "GND", isGround: true },
VCC: { netId: "VCC", isPositiveVoltageSource: true },
},
pinStrongConnMap: {
"U1.1-C1.1": true,
"C1.1-U1.1": true,
"U1.2-C2.1": true,
"C2.1-U1.2": true,
},
netConnMap: {
"U1.1-VCC": true,
"U1.2-VCC": true,
"C1.2-GND": true,
"C2.2-GND": true,
},
chipGap: 0.2,
partitionGap: 1,
}

test("IdentifyDecouplingCapsSolver identifies decoupling capacitor groups", () => {
const solver = new IdentifyDecouplingCapsSolver(problem)
solver.solve()

Expand Down
Loading
Loading