Skip to content
Merged
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
24 changes: 23 additions & 1 deletion lib/computeRegionCost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const computeRegionCost = (
traceCount: number,
regionAvailableZMask = 0,
minViaPadDiameter = DEFAULT_MIN_VIA_PAD_DIAMETER,
traceDensityCostFactor = 0,
) => {
const area = regionWidth * regionHeight

Expand All @@ -27,6 +28,7 @@ export const computeRegionCost = (
traceCount,
regionAvailableZMask,
minViaPadDiameter,
traceDensityCostFactor,
)
}

Expand All @@ -38,6 +40,7 @@ export const computeRegionCostForArea = (
traceCount: number,
regionAvailableZMask = 0,
minViaPadDiameter = DEFAULT_MIN_VIA_PAD_DIAMETER,
traceDensityCostFactor = 0,
) => {
const estViasRequired =
numSameLayerIntersections * 2 +
Expand All @@ -52,9 +55,28 @@ export const computeRegionCostForArea = (
)
? numSameLayerIntersections * IMPOSSIBLE_SINGLE_LAYER_INTERSECTION_COST
: 0
const layerCount = countAvailableLayers(regionAvailableZMask)
const traceDensityCost =
(traceDensityCostFactor *
(traceCount / layerCount) ** 2 *
traceWidth ** 2) /
area

return (
(estViasRequired * viaSizeWithMarginSq * traceCountMult) / area +
impossibleSingleLayerIntersectionCost
impossibleSingleLayerIntersectionCost +
traceDensityCost
)
}

const countAvailableLayers = (regionAvailableZMask: number) => {
if (regionAvailableZMask === 0) return 2

let mask = regionAvailableZMask >>> 0
let count = 0
while (mask !== 0) {
count += mask & 1
mask >>>= 1
}
return count
}
12 changes: 12 additions & 0 deletions lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ export interface TinyHyperGraphSolverOptions {
RIP_THRESHOLD_END?: number
RIP_THRESHOLD_RAMP_ATTEMPTS?: number
RIP_CONGESTION_REGION_COST_FACTOR?: number
/** Opt-in quadratic penalty for concentrating traces in low-capacity regions. */
TRACE_DENSITY_COST_FACTOR?: number
USE_LAZY_ROUTE_HEURISTIC?: boolean
USE_SPARSE_CANDIDATE_STORAGE?: boolean
MAX_ITERATIONS?: number
Expand Down Expand Up @@ -301,6 +303,7 @@ export interface TinyHyperGraphSolverOptionTarget {
RIP_THRESHOLD_END: number
RIP_THRESHOLD_RAMP_ATTEMPTS: number
RIP_CONGESTION_REGION_COST_FACTOR: number
TRACE_DENSITY_COST_FACTOR?: number
USE_LAZY_ROUTE_HEURISTIC?: boolean
USE_SPARSE_CANDIDATE_STORAGE?: boolean
MAX_ITERATIONS: number
Expand Down Expand Up @@ -351,6 +354,12 @@ export const applyTinyHyperGraphSolverOptions = (
solver.RIP_CONGESTION_REGION_COST_FACTOR =
options.RIP_CONGESTION_REGION_COST_FACTOR
}
if (options.TRACE_DENSITY_COST_FACTOR !== undefined) {
solver.TRACE_DENSITY_COST_FACTOR = Math.max(
0,
options.TRACE_DENSITY_COST_FACTOR,
)
}
if (options.USE_LAZY_ROUTE_HEURISTIC !== undefined) {
solver.USE_LAZY_ROUTE_HEURISTIC = options.USE_LAZY_ROUTE_HEURISTIC
}
Expand Down Expand Up @@ -433,6 +442,7 @@ export const getTinyHyperGraphSolverOptions = (
RIP_THRESHOLD_END: solver.RIP_THRESHOLD_END,
RIP_THRESHOLD_RAMP_ATTEMPTS: solver.RIP_THRESHOLD_RAMP_ATTEMPTS,
RIP_CONGESTION_REGION_COST_FACTOR: solver.RIP_CONGESTION_REGION_COST_FACTOR,
TRACE_DENSITY_COST_FACTOR: solver.TRACE_DENSITY_COST_FACTOR,
USE_LAZY_ROUTE_HEURISTIC: solver.USE_LAZY_ROUTE_HEURISTIC,
USE_SPARSE_CANDIDATE_STORAGE: solver.USE_SPARSE_CANDIDATE_STORAGE,
MAX_ITERATIONS: solver.MAX_ITERATIONS,
Expand Down Expand Up @@ -505,6 +515,7 @@ export class TinyHyperGraphSolver extends BaseSolver {
RIP_THRESHOLD_RAMP_ATTEMPTS = 50

RIP_CONGESTION_REGION_COST_FACTOR = 0.1
TRACE_DENSITY_COST_FACTOR = 0
USE_LAZY_ROUTE_HEURISTIC = false
USE_SPARSE_CANDIDATE_STORAGE = false

Expand Down Expand Up @@ -965,6 +976,7 @@ export class TinyHyperGraphSolver extends BaseSolver {
traceCount,
this.topology.regionAvailableZMask?.[regionId] ?? 0,
this.minViaPadDiameter,
this.TRACE_DENSITY_COST_FACTOR,
)
}

Expand Down
21 changes: 20 additions & 1 deletion lib/outside-in-partial-rip-tiny-hypergraph-solver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ type CompletedRoundSummary = RegionCostSummary & {
squaredRegionSegmentCount: number
}

const NO_PREFERRED_PRESERVED_ROUTE_IDS = new Set<RouteId>()

/**
* Retains the two outside portions of a completed route and only reroutes a
* bounded window around a congested region. The active window is represented
Expand Down Expand Up @@ -234,6 +236,14 @@ export class OutsideInPartialRipTinyHyperGraphSolver extends DistanceAwareTinyHy
}
}

/**
* Quality rerips retain these routes when other routes cross the same hot
* regions. Completion rerips may still use them when they are unavoidable.
*/
protected getRouteIdsPreferredForPreservation(): ReadonlySet<RouteId> {
return NO_PREFERRED_PRESERVED_ROUTE_IDS
}

private getCommittedRouteSegments(
routeId: RouteId,
): CommittedRouteSegment[] | undefined {
Expand Down Expand Up @@ -423,6 +433,11 @@ export class OutsideInPartialRipTinyHyperGraphSolver extends DistanceAwareTinyHy
}
}
if (routeIdsTouchingHotRegions.size === 0) return false
const preferredPreservedRouteIds =
this.getRouteIdsPreferredForPreservation()
const hasNonPreferredRouteTouchingHotRegions = [
...routeIdsTouchingHotRegions,
].some((routeId) => !preferredPreservedRouteIds.has(routeId))

const retainedSegmentsByRegion = Array.from(
{ length: this.topology.regionCount },
Expand All @@ -436,7 +451,11 @@ export class OutsideInPartialRipTinyHyperGraphSolver extends DistanceAwareTinyHy
const orderedSegments = this.getCommittedRouteSegments(routeId)
if (!orderedSegments) return false

if (!routeIdsTouchingHotRegions.has(routeId)) {
if (
!routeIdsTouchingHotRegions.has(routeId) ||
(hasNonPreferredRouteTouchingHotRegions &&
preferredPreservedRouteIds.has(routeId))
) {
for (const segment of orderedSegments) {
this.appendRetainedSegment(retainedSegmentsByRegion, routeId, segment)
retainedSegmentCount += 1
Expand Down
75 changes: 72 additions & 3 deletions lib/selective-rerip-tiny-hyper-graph-solver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ export type SelectiveReripTinyHyperGraphStats = {
selectiveRipCount: number
selectivelyRippedRouteCount: number
globalReripCount: number
globalReripReason?: "no_path" | "expansion_limit" | "no_blocker_path"
globalReripReason?:
| "no_path"
| "expansion_limit"
| "no_blocker_path"
| "failed_owner_cycle"
alternateBlockerSearchCount: number
alternateOwnerCount: number
failedOwnerPairCount: number
Expand Down Expand Up @@ -193,7 +197,7 @@ export class SelectiveReripTinyHyperGraphSolver extends OutsideInPartialRipTinyH
)
}

const directPath = this.findRelaxedBlockerPath()
const directPath = this.findRelaxedBlockerPathPreferringPreservedRoutes()
if (!directPath.found || directPath.owners.size === 0) {
this.selectiveReripStats.globalReripCount += 1
this.selectiveReripStats.globalReripReason = !directPath.found
Expand All @@ -218,6 +222,26 @@ export class SelectiveReripTinyHyperGraphSolver extends OutsideInPartialRipTinyH
const count = this.incrementFailedOwnerPair(failedRouteId, ownerRouteId)
if (count >= 2) repeatedOwnerRouteIds.push(ownerRouteId)
}
if (
directOwnerRouteIds.some((ownerRouteId) =>
this.hasFailedOwnerPath(ownerRouteId, failedRouteId),
)
) {
this.selectiveReripStats.globalReripCount += 1
this.selectiveReripStats.globalReripReason = "failed_owner_cycle"
this.selectiveReripStats.lastFailedRouteId = failedRouteId
this.selectiveReripStats.lastDirectOwnerRouteIds = directOwnerRouteIds
this.selectiveReripStats.lastRepeatedOwnerRouteIds = repeatedOwnerRouteIds
this.selectiveReripStats.lastAlternateOwnerRouteIds = []
this.selectiveReripStats.lastRippedRouteIds = []
this.selectiveReripStats.lastRelaxedSearchExpandedLabelCount =
directPath.expandedLabelCount
this.selectiveReripStats.lastAlternateSearchExpandedLabelCount = 0
this.failedOwnerPairCounts.clear()
super.onOutOfCandidates()
this.publishSelectiveReripStats()
return
}

let alternatePath:
| DistinctOwnerBlockerSearchResult<
Expand All @@ -228,7 +252,7 @@ export class SelectiveReripTinyHyperGraphSolver extends OutsideInPartialRipTinyH
| undefined
if (repeatedOwnerRouteIds.length > 0) {
this.selectiveReripStats.alternateBlockerSearchCount += 1
alternatePath = this.findRelaxedBlockerPath(
alternatePath = this.findRelaxedBlockerPathPreferringPreservedRoutes(
new Set(repeatedOwnerRouteIds),
)
if (!alternatePath.found) {
Expand Down Expand Up @@ -350,6 +374,33 @@ export class SelectiveReripTinyHyperGraphSolver extends OutsideInPartialRipTinyH
})
}

protected findRelaxedBlockerPathPreferringPreservedRoutes(
forbiddenOwnerRouteIds: ReadonlySet<RouteId> = new Set<RouteId>(),
): DistinctOwnerBlockerSearchResult<
RelaxedSearchState,
RouteId,
RelaxedSearchHopData
> {
const preferredPreservedRouteIds =
this.getRouteIdsPreferredForPreservation()
if (preferredPreservedRouteIds.size === 0) {
return this.findRelaxedBlockerPath(forbiddenOwnerRouteIds)
}

const preferredForbiddenOwnerRouteIds = new Set(forbiddenOwnerRouteIds)
for (const routeId of preferredPreservedRouteIds) {
preferredForbiddenOwnerRouteIds.add(routeId)
}
const preferredPath = this.findRelaxedBlockerPath(
preferredForbiddenOwnerRouteIds,
)
if (preferredPath.found && preferredPath.owners.size > 0) {
return preferredPath
}

return this.findRelaxedBlockerPath(forbiddenOwnerRouteIds)
}

protected getRelaxedSearchExpansionLimit(): number {
let incidentHopCount = 0
for (const incidentRegions of this.topology.incidentPortRegion) {
Expand Down Expand Up @@ -620,6 +671,24 @@ export class SelectiveReripTinyHyperGraphSolver extends OutsideInPartialRipTinyH
return count
}

private hasFailedOwnerPath(
fromRouteId: RouteId,
targetRouteId: RouteId,
): boolean {
const pendingRouteIds = [fromRouteId]
const visitedRouteIds = new Set<RouteId>()
while (pendingRouteIds.length > 0) {
const routeId = pendingRouteIds.pop()!
if (routeId === targetRouteId) return true
if (visitedRouteIds.has(routeId)) continue
visitedRouteIds.add(routeId)
pendingRouteIds.push(
...(this.failedOwnerPairCounts.get(routeId)?.keys() ?? []),
)
}
return false
}

private publishSelectiveReripStats(): void {
const failedOwnerPairs: FailedOwnerPairCount[] = []
for (const [failedRouteId, ownerCounts] of this.failedOwnerPairCounts) {
Expand Down
49 changes: 49 additions & 0 deletions tests/outside-in-partial-rip-tiny-hypergraph-solver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import {
import type { PortId, RegionId, RouteId } from "lib/types"

class TestOutsideInPartialRipSolver extends OutsideInPartialRipTinyHyperGraphSolver {
preferredPreservedRouteIds = new Set<RouteId>()

protected override getRouteIdsPreferredForPreservation() {
return this.preferredPreservedRouteIds
}

prepare(hotRegionIds: RegionId[], regionCosts: Float64Array): boolean {
return this.preparePartialRip(hotRegionIds, regionCosts)
}
Expand Down Expand Up @@ -86,6 +92,49 @@ test("partial rip preserves both outside route ends", () => {
expect(solver.stats.retainedPartialRipSegmentCount).toBe(3)
})

test("partial rip leaves preferred routes unchanged when another hot route can move", () => {
const solver = createLinearSolver(24, {}, 2)
solver.preferredPreservedRouteIds.add(0)
for (let regionId = 1; regionId <= 4; regionId++) {
const [routeZeroSegment] = solver.state.regionSegments[regionId]!
solver.state.regionSegments[regionId]!.push([
1,
routeZeroSegment![1],
routeZeroSegment![2],
])
}
const regionCosts = new Float64Array(6)
regionCosts[3] = 1

expect(solver.prepare([3], regionCosts)).toBe(true)
expect(solver.state.unroutedRoutes).toEqual([1])
expect(
solver.state.regionSegments.flat().filter(([routeId]) => routeId === 0),
).toEqual([
[0, 0, 1],
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
])
})

test("partial rip locally rerips a preferred route when every hot route is preferred", () => {
const solver = createLinearSolver()
solver.preferredPreservedRouteIds.add(0)
const regionCosts = new Float64Array(6)
regionCosts[3] = 1

expect(solver.prepare([3], regionCosts)).toBe(true)
expect(solver.getActiveEndpoints(0)).toEqual([2, 3])
expect(solver.state.unroutedRoutes).toEqual([0])
expect(solver.state.regionSegments.flat()).toEqual([
[0, 0, 1],
[0, 1, 2],
[0, 3, 4],
])
expect(solver.stats.partialRipCount).toBe(1)
})

test("a near-target initial solution selects the larger quality window", () => {
const nearTargetSolver = createLinearSolver()
nearTargetSolver.PARTIAL_RIP_QUALITY_MAX_DISTANCE = 8
Expand Down
Loading
Loading