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
132 changes: 127 additions & 5 deletions lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,114 @@
import { ConnectivityMap } from "connectivity-map"
import type { InputProblem, PinId } from "lib/types/InputProblem"
import type {
InputChip,
InputPin,
InputProblem,
PinId,
} from "lib/types/InputProblem"
import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"

// Keep nearby ground connections, but do not extend the usual 1 mm local
// routing range vertically just because a sheet allows long signal traces.
const MAX_LOCAL_GROUND_BRANCH_OFFSET = 1
export const MAX_LOCAL_GROUND_BRANCH_OFFSET = 1
const SAME_GROUND_PIN_BANK_AXIS_TOLERANCE = 1e-6

interface PinWithChip {
pin: InputPin
chip: InputChip
}

const getLocalGroundPinBanks = ({
inputProblem,
groundNetId,
netConnMap,
}: {
inputProblem: InputProblem
groundNetId?: string
netConnMap: ConnectivityMap
}): PinId[][] => {
if (!groundNetId) return []

return inputProblem.chips.flatMap((chip) => {
const groundPins = chip.pins.filter(
(pin) => netConnMap.getNetConnectedToId(pin.pinId) === groundNetId,
)
if (groundPins.length < 2) return []
// A connector with half or more of its contacts tied to ground is a
// deliberate ground bus. Localize only the smaller ground-pin banks found
// on multi-function ICs.
if (groundPins.length * 2 >= chip.pins.length) return []

const firstPin = groundPins[0]!
const facingDirection =
firstPin._facingDirection ?? getPinDirection(firstPin, chip)
const shareFacingDirection = groundPins.every(
(pin) =>
(pin._facingDirection ?? getPinDirection(pin, chip)) ===
facingDirection,
)
if (!shareFacingDirection) return []

let parallelCoordinates = groundPins.map((pin) => pin.y)
let perpendicularCoordinates = groundPins.map((pin) => pin.x)
if (facingDirection === "x-" || facingDirection === "x+") {
parallelCoordinates = groundPins.map((pin) => pin.x)
perpendicularCoordinates = groundPins.map((pin) => pin.y)
}
const parallelSpan =
Math.max(...parallelCoordinates) - Math.min(...parallelCoordinates)
const perpendicularSpan =
Math.max(...perpendicularCoordinates) -
Math.min(...perpendicularCoordinates)
if (parallelSpan > SAME_GROUND_PIN_BANK_AXIS_TOLERANCE) return []
if (perpendicularSpan > MAX_LOCAL_GROUND_BRANCH_OFFSET) return []

return [groundPins.map((pin) => pin.pinId)]
})
}

const isLongGroundPinBankConnection = ({
firstPinId,
secondPinId,
groundPinBanks,
pins,
}: {
firstPinId: PinId
secondPinId: PinId
groundPinBanks: PinId[][]
pins: Map<PinId, PinWithChip>
}) => {
const firstPinBank = groundPinBanks.find((pinBank) =>
pinBank.includes(firstPinId),
)
const secondPinBank = groundPinBanks.find((pinBank) =>
pinBank.includes(secondPinId),
)
if (!firstPinBank && !secondPinBank) return false
if (firstPinBank === secondPinBank) return false

const firstPin = pins.get(firstPinId)?.pin
const secondPin = pins.get(secondPinId)?.pin
if (!firstPin || !secondPin) return false
const orthogonalDistance =
Math.abs(firstPin.x - secondPin.x) + Math.abs(firstPin.y - secondPin.y)
return orthogonalDistance > MAX_LOCAL_GROUND_BRANCH_OFFSET
}

/** Avoid return wires between staggered, net-only ground-facing branches. */
export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
const groundNetId = netConnMap.getNetConnectedToId("GND")
const groundConnection = inputProblem.netConnections.find(
(connection) => connection.isGround,
)
let groundNetId: string | undefined
if (groundConnection) {
groundNetId = netConnMap.getNetConnectedToId(groundConnection.netId)
} else {
// Older captured inputs predate `isGround`; preserve their routing while
// current inputs use the semantic marker above.
groundNetId = netConnMap.getNetConnectedToId("GND")
}
// Net identifiers do not constitute physical edges: two separate direct
// connections may have the same netId without requesting a wire between them.
const physicalConnMap = new ConnectivityMap({})
Expand Down Expand Up @@ -38,6 +136,11 @@ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
netConnMap.getNetConnectedToId(pin.pinId) === groundNetId &&
(pin._facingDirection ?? getPinDirection(pin, chip)) === "y-",
)
const groundPinBanks = getLocalGroundPinBanks({
inputProblem,
groundNetId,
netConnMap,
})
// A deliberately wired, level bank of two-pin loads already has its own
// shared return rail. Do not extend that rail to independent lower branches
// just to reduce the number of GND symbols. Other ground topologies retain
Expand Down Expand Up @@ -75,8 +178,17 @@ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
return firstPhysicalGroup !== secondPhysicalGroup
}
if (!hasExplicitParallelGroundRail) {
return (firstPinId: PinId, secondPinId: PinId) =>
!areSeparateExplicitGroundIslands(firstPinId, secondPinId)
return (firstPinId: PinId, secondPinId: PinId) => {
if (areSeparateExplicitGroundIslands(firstPinId, secondPinId)) {
return false
}
return !isLongGroundPinBankConnection({
firstPinId,
secondPinId,
groundPinBanks,
pins,
})
}
}
const isGroundFacingTerminal = (pinId: PinId) => {
const entry = pins.get(pinId)
Expand All @@ -92,6 +204,16 @@ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
const firstGlobalNetId = netConnMap.getNetConnectedToId(firstPinId)
const secondGlobalNetId = netConnMap.getNetConnectedToId(secondPinId)
if (areSeparateExplicitGroundIslands(firstPinId, secondPinId)) return false
if (
isLongGroundPinBankConnection({
firstPinId,
secondPinId,
groundPinBanks,
pins,
})
) {
return false
}
if (
!groundNetId ||
firstGlobalNetId !== groundNetId ||
Expand Down
20 changes: 14 additions & 6 deletions lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { InputNetConnection, InputPin } from "lib/types/InputProblem"
import { MAX_LOCAL_GROUND_BRANCH_OFFSET } from "./getGroundConnectionPolicy"

const SAME_RAIL_Y_TOLERANCE = 1e-6
const MIN_GROUPED_RAIL_PIN_COUNT = 3
const MIN_GROUPED_RAIL_CHIP_COUNT = 2

function isGroupedHorizontalRail({
function getHorizontalRailChipCount({
pin,
netPins,
}: {
Expand All @@ -15,7 +16,7 @@ function isGroupedHorizontalRail({
(otherPin) => Math.abs(otherPin.y - pin.y) <= SAME_RAIL_Y_TOLERANCE,
)
const railChipIds = new Set(sameRailPins.map((railPin) => railPin.chipId))
return railChipIds.size >= MIN_GROUPED_RAIL_PIN_COUNT
return railChipIds.size
}

export function shouldSeparateGroundNetRows({
Expand All @@ -30,11 +31,18 @@ export function shouldSeparateGroundNetRows({
pin2: InputPin & { chipId: string }
}) {
if (!netConnection?.isGround || pin1.chipId === pin2.chipId) return false
if (Math.abs(pin1.y - pin2.y) <= SAME_RAIL_Y_TOLERANCE) return false
if (
Math.abs(pin1.y - pin2.y) <=
MAX_LOCAL_GROUND_BRANCH_OFFSET + SAME_RAIL_Y_TOLERANCE
) {
return false
}

const pin1RailChipCount = getHorizontalRailChipCount({ pin: pin1, netPins })
const pin2RailChipCount = getHorizontalRailChipCount({ pin: pin2, netPins })
// Net labels preserve ground connectivity without a cross-row MSP edge.
return (
isGroupedHorizontalRail({ pin: pin1, netPins }) &&
isGroupedHorizontalRail({ pin: pin2, netPins })
pin1RailChipCount >= MIN_GROUPED_RAIL_CHIP_COUNT &&
pin2RailChipCount >= MIN_GROUPED_RAIL_CHIP_COUNT
)
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import type { Point } from "@tscircuit/math-utils"
import { getSegmentIntersection } from "@tscircuit/math-utils/line-intersections"
import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
import { getTraceConnectedPinComponents } from "lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents"
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
import {
getVisibleTraceLength,
getVisibleTraceSegmentCount,
isHorizontal,
isVertical,
nearlyEqual,
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
import type { InputPin, InputProblem } from "lib/types/InputProblem"
import { doesPathCoincideWithTraces } from "lib/utils/doesPathCoincideWithTraces"
import {
Expand All @@ -40,6 +41,7 @@ const MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET = 0.2
// symbol-stem correction away from the pin itself.
const MAX_SHARED_PIN_RAIL_OFFSET = 0.05
const MIN_RETURN_STEM_LENGTH = 0.05
const MIN_ESTABLISHED_LEVEL_RAIL_TRACE_COUNT = 2

export const getSharedPin = ({
donorTrace,
Expand Down Expand Up @@ -371,6 +373,55 @@ const candidateIsClear = ({
)
}

const establishedLevelRailHasBlockedMember = ({
donorTrace,
branchTrace,
traces,
inputProblem,
}: {
donorTrace: SolvedTracePath
branchTrace: SolvedTracePath
traces: SolvedTracePath[]
inputProblem: InputProblem
}) => {
const donorRail = getLongestHorizontalSegment(donorTrace)
const branchRail = getLongestHorizontalSegment(branchTrace)
if (!donorRail || !branchRail) return false

const sameLevelTraces = traces.filter((trace) => {
if (trace.globalConnNetId !== branchTrace.globalConnNetId) return false
const rail = getLongestHorizontalSegment(trace)
return rail !== null && nearlyEqual(rail.start.y, branchRail.start.y)
})
const pinIds = [...new Set(sameLevelTraces.flatMap((trace) => trace.pinIds))]
const chain = getTraceConnectedPinComponents({
pinIds,
traces: sameLevelTraces,
}).find((component) => component.traces.includes(branchTrace))?.traces
if (!chain) return false
if (chain.length < MIN_ESTABLISHED_LEVEL_RAIL_TRACE_COUNT) return false

const obstacles = getObstacleRects(inputProblem)
return chain.some((trace) => {
const [firstPin, secondPin] = trace.pins
if (!firstPin || !secondPin || !nearlyEqual(firstPin.y, secondPin.y)) {
return false
}
const candidatePath = simplifyPath([
{ x: firstPin.x, y: firstPin.y },
{ x: firstPin.x, y: donorRail.start.y },
{ x: secondPin.x, y: donorRail.start.y },
{ x: secondPin.x, y: secondPin.y },
])
const endpointChipIds = new Set(trace.pins.map((pin) => pin.chipId))
const unrelatedObstacles = obstacles.filter(
(obstacle) =>
obstacle.kind !== "chip" || !endpointChipIds.has(obstacle.chipId),
)
return isPathCollidingWithObstacles(candidatePath, unrelatedObstacles)
})
}

/** Extend the outer load's stem instead of adding a return trunk between loads. */
const getAlignedReturnBranchPath = ({
donorTrace,
Expand Down Expand Up @@ -495,6 +546,19 @@ export const alignSameNetJunctions = ({
for (const candidatePath of candidatePaths) {
if (!candidatePath) continue
const candidateTrace = { ...branchTrace, tracePath: candidatePath }
// Keep an already-level chain intact when one load cannot follow the
// proposed alignment because of an unrelated obstacle.
if (
!alignReturnBranches &&
establishedLevelRailHasBlockedMember({
donorTrace,
branchTrace,
traces: outputTraces,
inputProblem,
})
) {
continue
}
const originalPair = [donorTrace, branchTrace]
const candidatePair = [donorTrace, candidateTrace]
const removesVisibleSegment =
Expand Down
53 changes: 31 additions & 22 deletions tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading