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
29 changes: 26 additions & 3 deletions lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
type PackedPartition,
} from "lib/solvers/PackInnerPartitionsSolver/PackInnerPartitionsSolver"
import { PartitionPackingSolver } from "lib/solvers/PartitionPackingSolver/PartitionPackingSolver"
import { PartitionFlipOptimizationSolver } from "lib/solvers/PartitionFlipOptimizationSolver/PartitionFlipOptimizationSolver"
import type { ChipPin, InputProblem, PinId } from "lib/types/InputProblem"
import type { OutputLayout } from "lib/types/OutputLayout"
import { doBasicInputProblemLayout } from "./doBasicInputProblemLayout"
Expand Down Expand Up @@ -53,6 +54,7 @@ export class LayoutPipelineSolver extends BaseSolver {
chipPartitionsSolver?: ChipPartitionsSolver
packInnerPartitionsSolver?: PackInnerPartitionsSolver
partitionPackingSolver?: PartitionPackingSolver
partitionFlipOptimizationSolver?: PartitionFlipOptimizationSolver

startTimeOfPhase: Record<string, number>
endTimeOfPhase: Record<string, number>
Expand Down Expand Up @@ -124,6 +126,17 @@ export class LayoutPipelineSolver extends BaseSolver {
},
},
),
definePipelineStep(
"partitionFlipOptimizationSolver",
PartitionFlipOptimizationSolver,
() => [
{
currentLayout: this.partitionPackingSolver!.finalLayout!,
packedPartitions: this.packedPartitions || [],
inputProblem: this.inputProblem,
},
],
),
]

constructor(inputProblem: InputProblem) {
Expand Down Expand Up @@ -188,8 +201,13 @@ export class LayoutPipelineSolver extends BaseSolver {
if (!this.solved && this.activeSubSolver)
return this.activeSubSolver.visualize()

// If the pipeline is complete and we have a partition packing solver,
// show only the final chip placements
// If the pipeline is complete, show the flip-optimized layout
if (this.solved && this.partitionFlipOptimizationSolver?.solved) {
const finalLayout = this.partitionFlipOptimizationSolver.improvedLayout
if (finalLayout && this.partitionPackingSolver) {
return this.partitionPackingSolver.visualize()
}
}
if (this.solved && this.partitionPackingSolver?.solved) {
return this.partitionPackingSolver.visualize()
}
Expand Down Expand Up @@ -400,8 +418,13 @@ export class LayoutPipelineSolver extends BaseSolver {

let finalLayout: OutputLayout

// Get the final layout from the partition packing solver
// Prefer the flip-optimized layout; fall back to raw partition packing
if (
this.partitionFlipOptimizationSolver?.solved &&
this.partitionFlipOptimizationSolver.improvedLayout
) {
finalLayout = this.partitionFlipOptimizationSolver.improvedLayout
} else if (
this.partitionPackingSolver?.solved &&
this.partitionPackingSolver.finalLayout
) {
Expand Down
112 changes: 112 additions & 0 deletions lib/solvers/PackInnerPartitionsSolver/DecouplingCapBankLayoutSolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Lays out a decoupling-capacitor partition as a uniform bank:
*
* - All caps oriented with their VCC/positive pin facing y+ (top) and their
* GND/negative pin facing y- (bottom), matching datasheet convention.
* - Caps are placed side-by-side with uniform pitch (cap width + gap).
* - When there are more than `maxPerRow` caps the bank wraps into multiple
* balanced rows, keeping the VCC rail continuous.
*
* This is a synchronous, single-step solver because the placement is fully
* deterministic — no search needed.
*/

import { BaseSolver } from "../BaseSolver"
import type { OutputLayout, Placement } from "../../types/OutputLayout"
import type { PartitionInputProblem, ChipId } from "../../types/InputProblem"

const MAX_PER_ROW = 8

export class DecouplingCapBankLayoutSolver extends BaseSolver {
partitionInputProblem: PartitionInputProblem
layout: OutputLayout | null = null

constructor(partitionInputProblem: PartitionInputProblem) {
super()
this.partitionInputProblem = partitionInputProblem
}

override _step() {
const { chipMap, chipPinMap, netMap, netConnMap } =
this.partitionInputProblem
const gap = this.partitionInputProblem.decouplingCapsGap ?? 0.1

const chips = Object.values(chipMap)
if (chips.length === 0) {
this.layout = { chipPlacements: {}, groupPlacements: {} }
this.solved = true
return
}

// Sort caps deterministically
const sortedChips = [...chips].sort((a, b) =>
a.chipId.localeCompare(b.chipId, undefined, { numeric: true }),
)

// Figure out which pin of each cap is the VCC pin (isPositiveVoltageSource)
// and whether the cap needs to be flipped (rotated 180°)
const capRotations = new Map<ChipId, 0 | 180>()

for (const chip of sortedChips) {
const [pin1Id, pin2Id] = chip.pins
if (!pin1Id || !pin2Id) {
capRotations.set(chip.chipId, 0)
continue
}

// Find which net each pin connects to
const getNet = (pinId: string) => {
for (const key of Object.keys(netConnMap)) {
const [p, n] = key.split("-") as [string, string]
if (p === pinId) return netMap[n]
}
return undefined
}

const net1 = getNet(pin1Id)
const net2 = getNet(pin2Id)

// pin1 is typically "top" in default (0°) rotation.
// If pin1 is GND and pin2 is VCC, we need to flip (180°).
const pin1IsGnd = net1?.isGround ?? false
const pin2IsGnd = net2?.isGround ?? false

// Flip if pin1 is GND (meaning VCC is at bottom in default orientation)
capRotations.set(chip.chipId, pin1IsGnd && !pin2IsGnd ? 180 : 0)
}

// Use the first chip's dimensions as the uniform cap size
const refChip = sortedChips[0]!
const capW = refChip.size.x
const capH = refChip.size.y

const pitch = capW + gap
const perRow = Math.min(MAX_PER_ROW, sortedChips.length)
const rowCount = Math.ceil(sortedChips.length / perRow)
const rowPitch = capH + gap

const chipPlacements: Record<ChipId, Placement> = {}

for (let i = 0; i < sortedChips.length; i++) {
const chip = sortedChips[i]!
const col = i % perRow
const row = Math.floor(i / perRow)
const x = col * pitch
const y = -row * rowPitch
chipPlacements[chip.chipId] = {
x,
y,
ccwRotationDegrees: capRotations.get(chip.chipId) ?? 0,
}
}

this.layout = { chipPlacements, groupPlacements: {} }
this.solved = true
}

override getConstructorParams(): ConstructorParameters<
typeof DecouplingCapBankLayoutSolver
> {
return [this.partitionInputProblem]
}
}
34 changes: 26 additions & 8 deletions lib/solvers/PackInnerPartitionsSolver/PackInnerPartitionsSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,22 @@

import type { GraphicsObject } from "graphics-debug"
import { BaseSolver } from "../BaseSolver"
import type { ChipPin, InputProblem, PinId } from "../../types/InputProblem"
import type {
ChipPin,
InputProblem,
PartitionInputProblem,
PinId,
} from "../../types/InputProblem"
import type { OutputLayout } from "../../types/OutputLayout"
import { SingleInnerPartitionPackingSolver } from "./SingleInnerPartitionPackingSolver"
import { DecouplingCapBankLayoutSolver } from "./DecouplingCapBankLayoutSolver"
import { stackGraphicsHorizontally } from "graphics-debug"

type PartitionSolver = (SingleInnerPartitionPackingSolver | DecouplingCapBankLayoutSolver) & {
layout: OutputLayout | null
visualize(): GraphicsObject
}

export type PackedPartition = {
inputProblem: InputProblem
layout: OutputLayout
Expand All @@ -19,11 +30,11 @@ export type PackedPartition = {
export class PackInnerPartitionsSolver extends BaseSolver {
partitions: InputProblem[]
packedPartitions: PackedPartition[] = []
completedSolvers: SingleInnerPartitionPackingSolver[] = []
activeSolver: SingleInnerPartitionPackingSolver | null = null
completedSolvers: PartitionSolver[] = []
activeSolver: PartitionSolver | null = null
currentPartitionIndex = 0

declare activeSubSolver: SingleInnerPartitionPackingSolver | null
declare activeSubSolver: PartitionSolver | null
pinIdToStronglyConnectedPins: Record<PinId, ChipPin[]>

constructor(params: {
Expand All @@ -45,10 +56,17 @@ export class PackInnerPartitionsSolver extends BaseSolver {
// If no active solver, create one for the current partition
if (!this.activeSolver) {
const currentPartition = this.partitions[this.currentPartitionIndex]!
this.activeSolver = new SingleInnerPartitionPackingSolver({
partitionInputProblem: currentPartition,
pinIdToStronglyConnectedPins: this.pinIdToStronglyConnectedPins,
})
const partitionType = (currentPartition as PartitionInputProblem).partitionType
if (partitionType === "decoupling_caps") {
this.activeSolver = new DecouplingCapBankLayoutSolver(
currentPartition as PartitionInputProblem,
)
} else {
this.activeSolver = new SingleInnerPartitionPackingSolver({
partitionInputProblem: currentPartition,
pinIdToStronglyConnectedPins: this.pinIdToStronglyConnectedPins,
})
}
this.activeSubSolver = this.activeSolver
}

Expand Down
Loading