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 @@ -17,6 +17,7 @@ import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputPro
import { createFilteredNetworkMapping } from "../../utils/networkFiltering"
import { getPadsBoundingBox } from "./getPadsBoundingBox"
import { doBasicInputProblemLayout } from "../LayoutPipelineSolver/doBasicInputProblemLayout"
import { isGroundNet, isPositiveVoltageNet } from "../../utils/netBiasUtils"

const PIN_SIZE = 0.1

Expand Down Expand Up @@ -129,13 +130,59 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver {
}
})

// Add static attractor components for power/ground nets
const activeNets = new Set<string>()
for (const netId of pinToNetworkMap.values()) {
activeNets.add(netId)
}

const attractorComponents: any[] = []
for (const netId of activeNets) {
const net = this.partitionInputProblem.netMap[netId]
if (isPositiveVoltageNet(netId, net)) {
attractorComponents.push({
componentId: `attractor_vcc_${netId}`,
pads: [
{
padId: `attractor_vcc_${netId}_pad`,
networkId: netId,
type: "rect" as const,
offset: { x: 0, y: 0 },
size: { x: PIN_SIZE, y: PIN_SIZE },
},
],
availableRotationDegrees: [0] as Array<0 | 90 | 180 | 270>,
isStatic: true as const,
center: { x: 0, y: -100 },
ccwRotationOffset: 0,
})
} else if (isGroundNet(netId, net)) {
attractorComponents.push({
componentId: `attractor_gnd_${netId}`,
pads: [
{
padId: `attractor_gnd_${netId}_pad`,
networkId: netId,
type: "rect" as const,
offset: { x: 0, y: 0 },
size: { x: PIN_SIZE, y: PIN_SIZE },
},
],
availableRotationDegrees: [0] as Array<0 | 90 | 180 | 270>,
isStatic: true as const,
center: { x: 0, y: 100 },
ccwRotationOffset: 0,
})
}
}

let minGap = this.partitionInputProblem.chipGap
if (this.partitionInputProblem.partitionType === "decoupling_caps") {
minGap = this.partitionInputProblem.decouplingCapsGap ?? minGap
}

return {
components: packComponents,
components: [...packComponents, ...attractorComponents],
minGap,
packOrderStrategy: "largest_to_smallest",
packPlacementStrategy: "minimum_closest_sum_squared_distance",
Expand All @@ -149,6 +196,7 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver {

for (const packedComponent of packedComponents) {
const chipId = packedComponent.componentId
if (chipId.startsWith("attractor_")) continue

chipPlacements[chipId] = {
x: packedComponent.center.x,
Expand Down
51 changes: 50 additions & 1 deletion lib/solvers/PartitionPackingSolver/PartitionPackingSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { OutputLayout, Placement } from "../../types/OutputLayout"
import type { InputProblem, PinId, NetId } from "../../types/InputProblem"
import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem"
import type { PackedPartition } from "../PackInnerPartitionsSolver/PackInnerPartitionsSolver"
import { isGroundNet, isPositiveVoltageNet } from "../../utils/netBiasUtils"

export interface PartitionPackingSolverInput {
packedPartitions: PackedPartition[]
Expand Down Expand Up @@ -261,8 +262,54 @@ export class PartitionPackingSolver extends BaseSolver {
}
})

// Add static attractor components for power/ground nets
const activeNets = new Set<string>()
for (const netId of pinToNetworkMap.values()) {
activeNets.add(netId)
}

const attractorComponents: any[] = []
for (const netId of activeNets) {
const net = this.inputProblem.netMap[netId]
if (isPositiveVoltageNet(netId, net)) {
attractorComponents.push({
componentId: `attractor_vcc_${netId}`,
pads: [
{
padId: `attractor_vcc_${netId}_pad`,
networkId: netId,
type: "rect" as const,
offset: { x: 0, y: 0 },
size: { x: 0.1, y: 0.1 },
},
],
availableRotationDegrees: [0] as Array<0 | 90 | 180 | 270>,
isStatic: true as const,
center: { x: 0, y: -100 },
ccwRotationOffset: 0,
})
} else if (isGroundNet(netId, net)) {
attractorComponents.push({
componentId: `attractor_gnd_${netId}`,
pads: [
{
padId: `attractor_gnd_${netId}_pad`,
networkId: netId,
type: "rect" as const,
offset: { x: 0, y: 0 },
size: { x: 0.1, y: 0.1 },
},
],
availableRotationDegrees: [0] as Array<0 | 90 | 180 | 270>,
isStatic: true as const,
center: { x: 0, y: 100 },
ccwRotationOffset: 0,
})
}
}

return {
components: packComponents,
components: [...packComponents, ...attractorComponents],
minGap: this.inputProblem.partitionGap,
packOrderStrategy: "largest_to_smallest",
packPlacementStrategy: "minimum_sum_squared_distance_to_network",
Expand All @@ -277,6 +324,8 @@ export class PartitionPackingSolver extends BaseSolver {
const newChipPlacements: Record<string, Placement> = {}

for (const packedComponent of packedComponents) {
if (packedComponent.componentId.startsWith("attractor_")) continue

const partitionIndex = parseInt(
packedComponent.componentId.replace("partition_", ""),
)
Expand Down
30 changes: 30 additions & 0 deletions lib/utils/netBiasUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Net, NetId } from "../types/InputProblem"

export function isGroundNet(netId: NetId, net?: Net): boolean {
if (net?.isGround) return true
const lower = netId.toLowerCase()
return lower.includes("gnd") || lower.includes("vss")
}

export function isPositiveVoltageNet(netId: NetId, net?: Net): boolean {
if (isGroundNet(netId, net)) return false
if (net?.isPositiveVoltageSource) return true

const lower = netId.toLowerCase()
// Common positive voltage names
if (
lower.includes("vcc") ||
lower.includes("vdd") ||
lower.includes("vsys") ||
lower === "v+" ||
lower.startsWith("+")
) {
return true
}
// Matches like "v3_3", "v1_1", "v5", "3.3v", "5v", "3v3", "12v"
if (/^v\d/.test(lower)) return true
if (/\d+v/.test(lower)) return true
if (/\d+v\d+/.test(lower)) return true

return false
}
36 changes: 16 additions & 20 deletions lib/utils/networkFiltering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import type { ChipPin, InputProblem, PinId } from "../types/InputProblem"
import { isGroundNet, isPositiveVoltageNet } from "./netBiasUtils"

export interface NetworkFilteringResult {
/** Map from pinId to networkId, with filtered networks marked as disconnected */
Expand Down Expand Up @@ -68,27 +69,22 @@ export function createFilteredNetworkMapping(params: {
}

// Process net connections
if (hasStrongConnections) {
// If any strong connections exist anywhere in the problem, filter out all weak (pin-to-net) connections
for (const [connKey, connected] of Object.entries(
inputProblem.netConnMap,
)) {
if (!connected) continue
const [pinId, netId] = connKey.split("-")
if (pinId && netId) {
// Do not assign a network for weak connections when strong connections are present.
// Mark this pin as filtered so callers can inspect what was ignored.
for (const [connKey, connected] of Object.entries(inputProblem.netConnMap)) {
if (!connected) continue
const [pinId, netId] = connKey.split("-")
if (pinId && netId) {
const net = inputProblem.netMap[netId]
const isPowerOrGround =
isGroundNet(netId, net) || isPositiveVoltageNet(netId, net)

if (isPowerOrGround) {
// Always preserve power and ground nets
pinToNetworkMap.set(pinId, netId)
} else if (hasStrongConnections) {
// Filter out other weak (pin-to-net) connections when strong connections exist
filteredPins.add(pinId)
}
}
} else {
// No strong connections exist; include weak connections with basic opposite-side filtering
for (const [connKey, connected] of Object.entries(
inputProblem.netConnMap,
)) {
if (!connected) continue
const [pinId, netId] = connKey.split("-")
if (pinId && netId) {
} else {
// No strong connections exist; include weak connections with basic opposite-side filtering
const pin = inputProblem.chipPinMap[pinId]
if (!pin) continue

Expand Down
73 changes: 73 additions & 0 deletions tests/LayoutPipelineSolver/LayoutPipelineSolverPowerBias.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect, test } from "bun:test"
import { LayoutPipelineSolver } from "lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver"
import { getInputProblemFromCircuitJsonSchematic } from "lib/testing/getInputProblemFromCircuitJsonSchematic"
import { getExampleCircuitJson } from "../assets/ExampleCircuit04"

test("LayoutPipelineSolverPowerBias - verifies VCC and GND sorting", () => {
const circuitJson = getExampleCircuitJson()
const problem = getInputProblemFromCircuitJsonSchematic(circuitJson, {
useReadableIds: true,
})

// Create solver and run the pipeline
const solver = new LayoutPipelineSolver(problem)
solver.solve()
expect(solver.solved).toBe(true)

const finalLayout = solver.getOutputLayout()
expect(finalLayout).toBeDefined()

// Let's verify that decoupling capacitors C1, C2, C5, C6
// are oriented such that their positive pins (Pin 1) are above their ground pins (Pin 2).
// In schematic layouts, negative Y is upward. So absolutePin1Y should be less than absolutePin2Y.
for (const capId of ["C1", "C2", "C5", "C6"]) {
const placement = finalLayout.chipPlacements[capId]
expect(placement).toBeDefined()

const chip = problem.chipMap[capId]
expect(chip).toBeDefined()

// Get pin 1 and pin 2
const pin1Id = chip!.pins.find((p) => p.endsWith(".1"))
const pin2Id = chip!.pins.find((p) => p.endsWith(".2"))

expect(pin1Id).toBeDefined()
expect(pin2Id).toBeDefined()

const pin1 = problem.chipPinMap[pin1Id!]
const pin2 = problem.chipPinMap[pin2Id!]

expect(pin1).toBeDefined()
expect(pin2).toBeDefined()

// Calculate rotated offset for pin 1
let rotatedPin1Offset = { x: pin1!.offset.x, y: pin1!.offset.y }
const rot = placement!.ccwRotationDegrees ?? 0
if (rot === 90) {
rotatedPin1Offset = { x: -pin1!.offset.y, y: pin1!.offset.x }
} else if (rot === 180) {
rotatedPin1Offset = { x: -pin1!.offset.x, y: -pin1!.offset.y }
} else if (rot === 270) {
rotatedPin1Offset = { x: pin1!.offset.y, y: -pin1!.offset.x }
}

// Calculate rotated offset for pin 2
let rotatedPin2Offset = { x: pin2!.offset.x, y: pin2!.offset.y }
if (rot === 90) {
rotatedPin2Offset = { x: -pin2!.offset.y, y: pin2!.offset.x }
} else if (rot === 180) {
rotatedPin2Offset = { x: -pin2!.offset.x, y: -pin2!.offset.y }
} else if (rot === 270) {
rotatedPin2Offset = { x: pin2!.offset.y, y: -pin2!.offset.x }
}

const absolutePin1Y = placement!.y + rotatedPin1Offset.y
const absolutePin2Y = placement!.y + rotatedPin2Offset.y

// Pin 1 (positive net) should be above (more negative Y) than Pin 2 (GND)
console.log(
`${capId} absolute pin Y: Pin 1 (VCC) = ${absolutePin1Y}, Pin 2 (GND) = ${absolutePin2Y}`,
)
expect(absolutePin1Y).toBeLessThan(absolutePin2Y)
}
})
Loading