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 @@ -93,7 +93,7 @@ export class IdentifyDecouplingCapsSolver extends BaseSolver {

/** Find the main chip id for a decoupling capacitor candidate */
private findMainChipIdForCap(capChip: Chip): ChipId | null {
// Aggregate strong neighbors from both pins
// Preferred path: a direct (strong) pin-to-pin trace from the cap to a chip.
const strongNeighbors = new Map<ChipId, number>()
for (const pinId of capChip.pins) {
const neighbors = this.getStronglyConnectedNeighborChips(pinId)
Expand All @@ -102,26 +102,94 @@ export class IdentifyDecouplingCapsSolver extends BaseSolver {
strongNeighbors.set(n, (strongNeighbors.get(n) || 0) + 1)
}
}
if (strongNeighbors.size === 0) return null
if (strongNeighbors.size > 0) {
// Choose the neighbor with the most connections (tie-breaker: lexicographic)
let best: { id: ChipId; score: number } | null = null
for (const [id, score] of strongNeighbors.entries()) {
if (
!best ||
score > best.score ||
(score === best.score && id < best.id)
)
best = { id, score }
}
return best ? best.id : null
}

// Fallback: most real schematics wire decoupling caps to power/ground
// *nets* rather than directly to a chip pin, so no strong neighbor exists.
// Identify the chip being decoupled by looking for a non-cap chip that has
// pins connected to *both* of this cap's nets - that's the chip whose
// supply this cap is filtering.
const capNetPair = this.getNormalizedNetPair(capChip)
if (!capNetPair) return null
const [n1, n2] = capNetPair

const candidateScores = new Map<ChipId, number>()
for (const [otherChipId, otherChip] of Object.entries(
this.inputProblem.chipMap,
)) {
if (otherChipId === capChip.chipId) continue
// Skip other 2-pin candidates so caps don't get attributed to each other.
if (otherChip.pins.length === 2) continue

let touchesN1 = false
let touchesN2 = false
let totalPowerPins = 0
for (const pinId of otherChip.pins) {
const nets = this.getNetIdsForPin(pinId)
if (nets.has(n1)) {
touchesN1 = true
totalPowerPins++
}
if (nets.has(n2)) {
touchesN2 = true
totalPowerPins++
}
}
if (touchesN1 && touchesN2) {
candidateScores.set(otherChipId, totalPowerPins)
}
}
if (candidateScores.size === 0) return null

// Choose the neighbor with the most connections (tie-breaker: lexicographic)
// Prefer the chip with the most pins on the supply pair (the chip that
// most clearly "owns" this rail), then lexicographic for stability.
let best: { id: ChipId; score: number } | null = null
for (const [id, score] of strongNeighbors.entries()) {
for (const [id, score] of candidateScores.entries()) {
if (!best || score > best.score || (score === best.score && id < best.id))
best = { id, score }
}
return best ? best.id : null
}

/** Get all net IDs connected to a pin */
/**
* Get all net IDs connected to a pin.
*
* Includes nets reached through strong (direct pin-to-pin) connections so
* that a decoupling cap whose hot pin tees off another chip's pin still
* resolves to the supply net that other pin sits on.
*/
private getNetIdsForPin(pinId: PinId): Set<NetId> {
const nets = new Set<NetId>()

// Build a one-hop reachability set across strong connections.
const reachablePins = new Set<PinId>([pinId])
for (const [connKey, connected] of Object.entries(
this.inputProblem.pinStrongConnMap,
)) {
if (!connected) continue
const [a, b] = connKey.split("-") as [PinId, PinId]
if (a === pinId) reachablePins.add(b)
else if (b === pinId) reachablePins.add(a)
}

for (const [connKey, connected] of Object.entries(
this.inputProblem.netConnMap,
)) {
if (!connected) continue
const [p, n] = connKey.split("-") as [PinId, NetId]
if (p === pinId) nets.add(n)
if (reachablePins.has(p)) nets.add(n)
}
return nets
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver {
}

override _step() {
// Decoupling-cap partitions get a specialized, deterministic layout: the
// caps are placed in a single centered horizontal row sorted by chipId.
// This avoids the overlaps that PackSolver2 can produce on small same-net
// 2-pin components and gives the outer packer a tight, predictable
// bounding box to position next to the main chip.
if (
this.partitionInputProblem.partitionType === "decoupling_caps" &&
Object.keys(this.partitionInputProblem.chipMap).length > 0
) {
this.layout = this.createDecouplingCapsRowLayout()
this.solved = true
return
}

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

/**
* Build a clean, centered horizontal row of decoupling capacitors.
*
* The caps are placed left-to-right in chipId order at y=0 with no rotation,
* separated by `decouplingCapsGap` (falling back to `chipGap`). The final row
* is recentered around the origin so the outer packer can position the
* partition symmetrically beneath/next to its main chip.
*/
private createDecouplingCapsRowLayout(): OutputLayout {
const chips = Object.values(this.partitionInputProblem.chipMap)
const sortedChips = [...chips].sort((a, b) =>
a.chipId.localeCompare(b.chipId),
)

const gap =
this.partitionInputProblem.decouplingCapsGap ??
this.partitionInputProblem.chipGap

const chipPlacements: Record<ChipId, Placement> = {}
let cursorX = 0
for (let i = 0; i < sortedChips.length; i++) {
const chip = sortedChips[i]!
const halfWidth = chip.size.x / 2
const centerX = cursorX + halfWidth
chipPlacements[chip.chipId] = {
x: centerX,
y: 0,
ccwRotationDegrees: 0,
}
cursorX = centerX + halfWidth + gap
}

// Recenter the row horizontally around the origin
const rowWidth = cursorX - gap
const offset = rowWidth / 2
for (const chipId of Object.keys(chipPlacements)) {
chipPlacements[chipId]!.x -= offset
}

return {
chipPlacements,
groupPlacements: {},
}
}

private createPackInput(): PackInput {
// Fall back to filtered mapping (weak + strong)
const pinToNetworkMap = createFilteredNetworkMapping({
Expand Down
34 changes: 34 additions & 0 deletions lib/testing/getInputProblemFromCircuitJsonSchematic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@ import type { InputProblem } from "lib/types/InputProblem"
import type { CircuitJson } from "circuit-json"
import { cju } from "@tscircuit/circuit-json-util"

/** Conventional ground/return net names (GND, AGND, DGND, VSS, ...) */
const isGroundNetName = (name: string): boolean =>
/^(?:[A-Z]?GND\d*|VSS\d*|VEE\d*)$/i.test(name)

/**
* Conventional positive supply rail names (VCC, VDD, V3_3, V1_1, USB_VDD,
* VBAT, VBUS, VIN, VOUT, ...). Anything matched by `isGroundNetName` is
* intentionally excluded.
*/
const isPositiveVoltageNetName = (name: string): boolean => {
if (isGroundNetName(name)) return false
return /^(?:V(?:CC|DD|BAT|BUS|IN|OUT|REF|SYS|AA)\d*|V\d[A-Z0-9_]*|.*_(?:VCC|VDD|VBAT|VBUS|VIN|VOUT|VREF|VSYS|VAA)\d*)$/i.test(
name,
)
}

export const getInputProblemFromCircuitJsonSchematic = (
circuitJson: CircuitJson,
options?: { useReadableIds?: boolean },
Expand Down Expand Up @@ -87,6 +103,20 @@ export const getInputProblemFromCircuitJsonSchematic = (
},
}

// 2-pin polar/passive components (capacitors, resistors, inductors, diodes)
// are conventionally only flipped vertically on a schematic, never rotated
// 90/270. Recording this here lets the decoupling-cap identifier and the
// packing solvers reason about them correctly.
if (
pinIds.length === 2 &&
(source_component.ftype === "simple_capacitor" ||
source_component.ftype === "simple_resistor" ||
source_component.ftype === "simple_inductor" ||
source_component.ftype === "simple_diode")
) {
problem.chipMap[chipId]!.availableRotations = [0, 180]
}

// Create chipPinMap entries for each pin
for (const { schematic_port, source_port } of ports) {
if (source_port) {
Expand Down Expand Up @@ -148,6 +178,10 @@ export const getInputProblemFromCircuitJsonSchematic = (

problem.netMap[netId] = {
netId: netId,
...(isGroundNetName(netId) ? { isGround: true } : {}),
...(isPositiveVoltageNetName(netId)
? { isPositiveVoltageSource: true }
: {}),
}
}

Expand Down
Loading
Loading