Skip to content
Closed
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
2 changes: 2 additions & 0 deletions lib/bus-solver/TinyHyperGraphBusSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,8 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver {
regionCache.lesserAngles = EMPTY_PREVIEW_INT32_ARRAY
regionCache.greaterAngles = EMPTY_PREVIEW_INT32_ARRAY
regionCache.layerMasks = EMPTY_PREVIEW_INT32_ARRAY
regionCache.traceLengthByLayer.fill(0)
regionCache.longestTraceLengthByLayer.fill(0)
regionCache.existingCrossingLayerIntersections = 0
regionCache.existingSameLayerIntersections = 0
regionCache.existingEntryExitLayerChanges = 0
Expand Down
8 changes: 8 additions & 0 deletions lib/bus-solver/previewRoutingState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export const snapshotPreviewRoutingState = (
lesserAngles: new Int32Array(cache.lesserAngles),
greaterAngles: new Int32Array(cache.greaterAngles),
layerMasks: new Int32Array(cache.layerMasks),
traceLengthByLayer: new Float64Array(cache.traceLengthByLayer),
longestTraceLengthByLayer: new Float64Array(
cache.longestTraceLengthByLayer,
),
existingCrossingLayerIntersections:
cache.existingCrossingLayerIntersections,
existingSameLayerIntersections: cache.existingSameLayerIntersections,
Expand All @@ -81,6 +85,10 @@ export const restorePreviewRoutingState = (
lesserAngles: new Int32Array(cache.lesserAngles),
greaterAngles: new Int32Array(cache.greaterAngles),
layerMasks: new Int32Array(cache.layerMasks),
traceLengthByLayer: new Float64Array(cache.traceLengthByLayer),
longestTraceLengthByLayer: new Float64Array(
cache.longestTraceLengthByLayer,
),
existingCrossingLayerIntersections:
cache.existingCrossingLayerIntersections,
existingSameLayerIntersections: cache.existingSameLayerIntersections,
Expand Down
30 changes: 30 additions & 0 deletions lib/computeRegionCost.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
export const DEFAULT_MIN_VIA_PAD_DIAMETER = 0.3
export const DEFAULT_MIN_TRACE_WIDTH = 0.1
export const DEFAULT_MIN_TRACE_CLEARANCE = 0.1
export const TRACE_VIA_MARGIN = 0.15
const traceWidth = 0.1
const IMPOSSIBLE_SINGLE_LAYER_INTERSECTION_COST = 10

/**
* Estimates per-layer copper occupancy as swept trace area divided by region
* area. This cost is used only by post-solution optimization, where a complete
* baseline can always be restored, so utilization can remain physically
* meaningful without weakening initial-route completeness.
*/
export const computeTraceOccupancyCost = (
regionArea: number,
traceLengthByLayer: ArrayLike<number>,
longestTraceLengthByLayer: ArrayLike<number>,
minTraceWidth = DEFAULT_MIN_TRACE_WIDTH,
minTraceClearance = DEFAULT_MIN_TRACE_CLEARANCE,
): number => {
let maxSharedTraceLength = 0
for (let layerId = 0; layerId < traceLengthByLayer.length; layerId++) {
const sharedTraceLength = Math.max(
0,
(traceLengthByLayer[layerId] ?? 0) -
(longestTraceLengthByLayer[layerId] ?? 0),
)
maxSharedTraceLength = Math.max(maxSharedTraceLength, sharedTraceLength)
}

const tracePitch = minTraceWidth + minTraceClearance
const sharedTraceArea = maxSharedTraceLength * tracePitch
return (sharedTraceArea / regionArea) ** 2
}

export const isKnownSingleLayerMask = (regionAvailableZMask: number) =>
regionAvailableZMask > 0 &&
(regionAvailableZMask & (regionAvailableZMask - 1)) === 0
Expand Down
187 changes: 179 additions & 8 deletions lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { BaseSolver } from "@tscircuit/solver-utils"
import type { GraphicsObject } from "graphics-debug"
import { convertToSerializedHyperGraph } from "./compat/convertToSerializedHyperGraph"
import {
computeTraceOccupancyCost,
computeRegionCost,
DEFAULT_MIN_TRACE_CLEARANCE,
DEFAULT_MIN_TRACE_WIDTH,
DEFAULT_MIN_VIA_PAD_DIAMETER,
isKnownSingleLayerMask,
} from "./computeRegionCost"
Expand Down Expand Up @@ -30,12 +33,37 @@ export type { StaticallyUnroutableRouteSummary } from "./static-reachability"

const GREEDY_FINAL_ROUTE_MAX_ITERATIONS = 50e3

const addSegmentLengthByLayer = (
traceLengthByLayer: Float64Array,
longestTraceLengthByLayer: Float64Array,
layerMask: number,
segmentLength: number,
) => {
let layerCount = 0
for (let layerId = 0; layerId < 32; layerId++) {
if ((layerMask & (1 << layerId)) !== 0) layerCount += 1
}
if (layerCount === 0) return

const lengthOnLayer = segmentLength / layerCount
for (let layerId = 0; layerId < 32; layerId++) {
if ((layerMask & (1 << layerId)) === 0) continue
traceLengthByLayer[layerId] += lengthOnLayer
longestTraceLengthByLayer[layerId] = Math.max(
longestTraceLengthByLayer[layerId],
lengthOnLayer,
)
}
}

export const createEmptyRegionIntersectionCache =
(): RegionIntersectionCache => ({
netIds: new Int32Array(0),
lesserAngles: new Int32Array(0),
greaterAngles: new Int32Array(0),
layerMasks: new Int32Array(0),
traceLengthByLayer: new Float64Array(32),
longestTraceLengthByLayer: new Float64Array(32),
existingCrossingLayerIntersections: 0,
existingSameLayerIntersections: 0,
existingEntryExitLayerChanges: 0,
Expand All @@ -60,6 +88,12 @@ const cloneRegionIntersectionCache = (
lesserAngles: new Int32Array(regionIntersectionCache.lesserAngles),
greaterAngles: new Int32Array(regionIntersectionCache.greaterAngles),
layerMasks: new Int32Array(regionIntersectionCache.layerMasks),
traceLengthByLayer: new Float64Array(
regionIntersectionCache.traceLengthByLayer,
),
longestTraceLengthByLayer: new Float64Array(
regionIntersectionCache.longestTraceLengthByLayer,
),
existingCrossingLayerIntersections:
regionIntersectionCache.existingCrossingLayerIntersections,
existingSameLayerIntersections:
Expand Down Expand Up @@ -227,6 +261,12 @@ export interface TinyHyperGraphWorkingState {

export interface TinyHyperGraphSolverOptions {
minViaPadDiameter?: number
/** Minimum routed trace width in millimeters. Defaults to 0.1mm. */
minTraceWidth?: number
/** Minimum trace-to-trace clearance in millimeters. Defaults to 0.1mm. */
minTraceClearance?: number
/** Enables physical trace-occupancy scoring during solution optimization. */
USE_TRACE_OCCUPANCY_COST?: boolean
DISTANCE_TO_COST?: number
RIP_THRESHOLD_START?: number
RIP_THRESHOLD_END?: number
Expand All @@ -244,6 +284,9 @@ export interface TinyHyperGraphSolverOptions {

export interface TinyHyperGraphSolverOptionTarget {
minViaPadDiameter: number
minTraceWidth: number
minTraceClearance: number
USE_TRACE_OCCUPANCY_COST: boolean
DISTANCE_TO_COST: number
RIP_THRESHOLD_START: number
RIP_THRESHOLD_END: number
Expand All @@ -270,6 +313,15 @@ export const applyTinyHyperGraphSolverOptions = (
if (options.minViaPadDiameter !== undefined) {
solver.minViaPadDiameter = options.minViaPadDiameter
}
if (options.minTraceWidth !== undefined) {
solver.minTraceWidth = options.minTraceWidth
}
if (options.minTraceClearance !== undefined) {
solver.minTraceClearance = options.minTraceClearance
}
if (options.USE_TRACE_OCCUPANCY_COST !== undefined) {
solver.USE_TRACE_OCCUPANCY_COST = options.USE_TRACE_OCCUPANCY_COST
}
if (options.DISTANCE_TO_COST !== undefined) {
solver.DISTANCE_TO_COST = options.DISTANCE_TO_COST
}
Expand Down Expand Up @@ -318,6 +370,9 @@ export const getTinyHyperGraphSolverOptions = (
solver: TinyHyperGraphSolverOptionTarget,
): TinyHyperGraphSolverOptions => ({
minViaPadDiameter: solver.minViaPadDiameter,
minTraceWidth: solver.minTraceWidth,
minTraceClearance: solver.minTraceClearance,
USE_TRACE_OCCUPANCY_COST: solver.USE_TRACE_OCCUPANCY_COST,
DISTANCE_TO_COST: solver.DISTANCE_TO_COST,
RIP_THRESHOLD_START: solver.RIP_THRESHOLD_START,
RIP_THRESHOLD_END: solver.RIP_THRESHOLD_END,
Expand All @@ -342,6 +397,7 @@ interface SegmentGeometryScratch {
greaterAngle: number
layerMask: number
entryExitLayerChanges: number
length: number
}

export class TinyHyperGraphSolver extends BaseSolver {
Expand All @@ -358,10 +414,17 @@ export class TinyHyperGraphSolver extends BaseSolver {
greaterAngle: 0,
layerMask: 0,
entryExitLayerChanges: 0,
length: 0,
}
private traceLengthByLayerScratch = new Float64Array(32)
private longestTraceLengthByLayerScratch = new Float64Array(32)
private traceOccupancyCostActive = false

DISTANCE_TO_COST = 0.05 // 50mm = 1 cost unit (1 cost unit ~ 100% chance of failure)
minViaPadDiameter = DEFAULT_MIN_VIA_PAD_DIAMETER
minTraceWidth = DEFAULT_MIN_TRACE_WIDTH
minTraceClearance = DEFAULT_MIN_TRACE_CLEARANCE
USE_TRACE_OCCUPANCY_COST = true

RIP_THRESHOLD_START = 0.05
RIP_THRESHOLD_END = 0.8
Expand Down Expand Up @@ -734,6 +797,54 @@ export class TinyHyperGraphSolver extends BaseSolver {
)
}

protected getRegionArea(regionId: RegionId): number {
return (
this.topology.regionWidth[regionId] * this.topology.regionHeight[regionId]
)
}

protected computeTraceOccupancyCostForRegion(
regionId: RegionId,
traceLengthByLayer: ArrayLike<number>,
longestTraceLengthByLayer: ArrayLike<number>,
): number {
if (!this.USE_TRACE_OCCUPANCY_COST || !this.traceOccupancyCostActive) {
return 0
}
return computeTraceOccupancyCost(
this.getRegionArea(regionId),
traceLengthByLayer,
longestTraceLengthByLayer,
this.minTraceWidth,
this.minTraceClearance,
)
}

protected computeProspectiveTraceOccupancyCostForRegion(
regionId: RegionId,
regionCache: RegionIntersectionCache,
segmentGeometry: SegmentGeometryScratch,
): number {
if (!this.USE_TRACE_OCCUPANCY_COST || !this.traceOccupancyCostActive) {
return 0
}
this.traceLengthByLayerScratch.set(regionCache.traceLengthByLayer)
this.longestTraceLengthByLayerScratch.set(
regionCache.longestTraceLengthByLayer,
)
addSegmentLengthByLayer(
this.traceLengthByLayerScratch,
this.longestTraceLengthByLayerScratch,
segmentGeometry.layerMask,
segmentGeometry.length,
)
return this.computeTraceOccupancyCostForRegion(
regionId,
this.traceLengthByLayerScratch,
this.longestTraceLengthByLayerScratch,
)
}

populateSegmentGeometryScratch(
regionId: RegionId,
port1Id: PortId,
Expand Down Expand Up @@ -761,6 +872,10 @@ export class TinyHyperGraphSolver extends BaseSolver {
scratch.greaterAngle = angle1 < angle2 ? angle2 : angle1
scratch.layerMask = (1 << z1) | (1 << z2)
scratch.entryExitLayerChanges = z1 !== z2 ? 1 : 0
scratch.length = Math.hypot(
topology.portX[port2Id] - topology.portX[port1Id],
topology.portY[port2Id] - topology.portY[port1Id],
)

return scratch
}
Expand Down Expand Up @@ -815,23 +930,45 @@ export class TinyHyperGraphSolver extends BaseSolver {
const existingEntryExitLayerChanges =
regionCache.existingEntryExitLayerChanges + newEntryExitLayerChanges
const existingSegmentCount = lesserAngles.length
const traceLengthByLayer = this.USE_TRACE_OCCUPANCY_COST
? new Float64Array(regionCache.traceLengthByLayer)
: regionCache.traceLengthByLayer
const longestTraceLengthByLayer = this.USE_TRACE_OCCUPANCY_COST
? new Float64Array(regionCache.longestTraceLengthByLayer)
: regionCache.longestTraceLengthByLayer
if (this.USE_TRACE_OCCUPANCY_COST) {
addSegmentLengthByLayer(
traceLengthByLayer,
longestTraceLengthByLayer,
segmentGeometry.layerMask,
segmentGeometry.length,
)
}

state.regionIntersectionCaches[regionId] = {
netIds,
lesserAngles,
greaterAngles,
layerMasks,
traceLengthByLayer,
longestTraceLengthByLayer,
existingSameLayerIntersections,
existingCrossingLayerIntersections,
existingEntryExitLayerChanges,
existingSegmentCount,
existingRegionCost: this.computeRegionCostForRegion(
regionId,
existingSameLayerIntersections,
existingCrossingLayerIntersections,
existingEntryExitLayerChanges,
existingSegmentCount,
),
existingRegionCost:
this.computeRegionCostForRegion(
regionId,
existingSameLayerIntersections,
existingCrossingLayerIntersections,
existingEntryExitLayerChanges,
existingSegmentCount,
) +
this.computeTraceOccupancyCostForRegion(
regionId,
traceLengthByLayer,
longestTraceLengthByLayer,
),
}
}

Expand Down Expand Up @@ -1243,6 +1380,7 @@ export class TinyHyperGraphSolver extends BaseSolver {

onAllRoutesRouted() {
const { topology, state } = this
this.activateTraceOccupancyCost()
const ripThresholdProgress =
this.RIP_THRESHOLD_RAMP_ATTEMPTS <= 0
? 1
Expand Down Expand Up @@ -1311,6 +1449,21 @@ export class TinyHyperGraphSolver extends BaseSolver {
})
}

activateTraceOccupancyCost() {
if (!this.USE_TRACE_OCCUPANCY_COST || this.traceOccupancyCostActive) return
this.traceOccupancyCostActive = true

for (let regionId = 0; regionId < this.topology.regionCount; regionId++) {
const regionCache = this.state.regionIntersectionCaches[regionId]
if (!regionCache) continue
regionCache.existingRegionCost += this.computeTraceOccupancyCostForRegion(
regionId,
regionCache.traceLengthByLayer,
regionCache.longestTraceLengthByLayer,
)
}
}

onOutOfCandidates() {
const { topology, state } = this
const currentRouteId = state.currentRouteId
Expand Down Expand Up @@ -1402,6 +1555,12 @@ export class TinyHyperGraphSolver extends BaseSolver {
const neighborPortZ = topology.portZ[neighborPortId]
const layerMask = (1 << currentPortZ) | (1 << neighborPortZ)
const entryExitLayerChanges = currentPortZ !== neighborPortZ ? 1 : 0
const segmentLength = this.traceOccupancyCostActive
? Math.hypot(
topology.portX[neighborPortId] - topology.portX[currentPortId],
topology.portY[neighborPortId] - topology.portY[currentPortId],
)
: 0

const [
newSameLayerIntersections,
Expand Down Expand Up @@ -1431,7 +1590,19 @@ export class TinyHyperGraphSolver extends BaseSolver {
newCrossLayerIntersections,
regionCache.existingEntryExitLayerChanges + newEntryExitLayerChanges,
regionCache.existingSegmentCount + 1,
) - regionCache.existingRegionCost
) +
this.computeProspectiveTraceOccupancyCostForRegion(
nextRegionId,
regionCache,
{
lesserAngle,
greaterAngle,
layerMask,
entryExitLayerChanges,
length: segmentLength,
},
) -
regionCache.existingRegionCost

return (
currentCandidate.g +
Expand Down
3 changes: 3 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export * from "./selective-rerip-tiny-hyper-graph-solver"
export * from "./bus-solver"
export * from "./region-graph"
export {
computeTraceOccupancyCost,
DEFAULT_MIN_TRACE_CLEARANCE,
DEFAULT_MIN_TRACE_WIDTH,
DEFAULT_MIN_VIA_PAD_DIAMETER,
TRACE_VIA_MARGIN,
} from "./computeRegionCost"
Expand Down
Loading
Loading