From 709c5d6dd33eabb419de2aa2b60d7f2cd3ffa1fc Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 21:19:41 +0200 Subject: [PATCH 1/6] fix: guide cross-layer routes through legal transition regions --- lib/core.ts | 48 +++++--- lib/layer-aware-route-heuristic.ts | 187 +++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 19 deletions(-) create mode 100644 lib/layer-aware-route-heuristic.ts diff --git a/lib/core.ts b/lib/core.ts index 58e9004..6277087 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -11,6 +11,11 @@ import { applyInitialAssignments, type TinyHyperGraphInitialAssignment, } from "./initialAssignments" +import { + createLayerAwareRouteHeuristic, + getLayerAwarePortHeuristicCost, + type LayerAwareRouteHeuristic, +} from "./layer-aware-route-heuristic" import { MinHeap } from "./MinHeap" import { shuffle } from "./shuffle" import type { StaticallyUnroutableRouteSummary } from "./static-reachability" @@ -167,6 +172,7 @@ export interface TinyHyperGraphProblem { export interface TinyHyperGraphProblemSetup { // portHCostToEndOfRoute[portId * routeCount + routeId] = distance from port to end of route portHCostToEndOfRoute: Float64Array + layerAwareRouteHeuristic: LayerAwareRouteHeuristic portEndpointNetIds: Array> /** -1 for no endpoint, -2 for endpoints from multiple nets, otherwise the sole endpoint net. */ portEndpointReservationNetId: Int32Array @@ -458,13 +464,15 @@ export class TinyHyperGraphSolver extends BaseSolver { computeProblemSetup(): TinyHyperGraphProblemSetup { const { topology, problem } = this + const layerAwareRouteHeuristic = createLayerAwareRouteHeuristic({ + topology, + problem, + distanceToCost: this.DISTANCE_TO_COST, + minViaPadDiameter: this.minViaPadDiameter, + }) const portHCostToEndOfRoute = this.USE_LAZY_ROUTE_HEURISTIC ? undefined : new Float64Array(topology.portCount * problem.routeCount) - const portX = (topology.portRoutingCostX ?? - topology.portX) as unknown as ArrayLike - const portY = (topology.portRoutingCostY ?? - topology.portY) as unknown as ArrayLike const portEndpointNetIds = Array.from( { length: topology.portCount }, () => new Set(), @@ -488,21 +496,23 @@ export class TinyHyperGraphSolver extends BaseSolver { recordEndpointNet(problem.routeEndPort[routeId]!, routeNetId) if (portHCostToEndOfRoute) { - const endPortId = problem.routeEndPort[routeId] - const endX = portX[endPortId] - const endY = portY[endPortId] - for (let portId = 0; portId < topology.portCount; portId++) { - const dx = portX[portId] - endX - const dy = portY[portId] - endY portHCostToEndOfRoute[portId * problem.routeCount + routeId] = - Math.hypot(dx, dy) * this.DISTANCE_TO_COST + getLayerAwarePortHeuristicCost({ + topology, + problem, + layerAwareRouteHeuristic, + distanceToCost: this.DISTANCE_TO_COST, + routeId, + portId, + }) } } } return { portHCostToEndOfRoute: portHCostToEndOfRoute as Float64Array, + layerAwareRouteHeuristic, portEndpointNetIds, portEndpointReservationNetId, } @@ -1537,14 +1547,14 @@ export class TinyHyperGraphSolver extends BaseSolver { ] } - const endPortId = this.problem.routeEndPort[this.state.currentRouteId!] - const dx = - this.getPortRoutingCostX(neighborPortId) - - this.getPortRoutingCostX(endPortId) - const dy = - this.getPortRoutingCostY(neighborPortId) - - this.getPortRoutingCostY(endPortId) - return Math.hypot(dx, dy) * this.DISTANCE_TO_COST + return getLayerAwarePortHeuristicCost({ + topology: this.topology, + problem: this.problem, + layerAwareRouteHeuristic: this.problemSetup.layerAwareRouteHeuristic, + distanceToCost: this.DISTANCE_TO_COST, + routeId: this.state.currentRouteId!, + portId: neighborPortId, + }) } protected getPortRoutingCostX(portId: PortId): number { diff --git a/lib/layer-aware-route-heuristic.ts b/lib/layer-aware-route-heuristic.ts new file mode 100644 index 0000000..a0c8018 --- /dev/null +++ b/lib/layer-aware-route-heuristic.ts @@ -0,0 +1,187 @@ +import type { TinyHyperGraphProblem, TinyHyperGraphTopology } from "./core" +import { computeRegionCost } from "./computeRegionCost" +import type { PortId, RegionId, RouteId } from "./types" + +export interface LayerAwareRouteHeuristic { + layerTransitionRegionIdByRoute: Int32Array + layerTransitionCostByRoute: Float64Array +} + +interface CreateLayerAwareRouteHeuristicContext { + topology: TinyHyperGraphTopology + problem: TinyHyperGraphProblem + distanceToCost: number + minViaPadDiameter: number +} + +interface GetLayerAwarePortHeuristicCostContext { + topology: TinyHyperGraphTopology + problem: TinyHyperGraphProblem + layerAwareRouteHeuristic: LayerAwareRouteHeuristic + distanceToCost: number + routeId: RouteId + portId: PortId +} + +const getPortRoutingCostX = ( + topology: TinyHyperGraphTopology, + portId: PortId, +) => topology.portRoutingCostX?.[portId] ?? topology.portX[portId]! + +const getPortRoutingCostY = ( + topology: TinyHyperGraphTopology, + portId: PortId, +) => topology.portRoutingCostY?.[portId] ?? topology.portY[portId]! + +const getRegionPortLayerMask = ( + topology: TinyHyperGraphTopology, + regionId: RegionId, +) => { + let layerMask = 0 + + for (const portId of topology.regionIncidentPorts[regionId] ?? []) { + layerMask |= 1 << topology.portZ[portId]! + } + + return layerMask +} + +const getTraversableRegionLayerMask = ( + topology: TinyHyperGraphTopology, + regionId: RegionId, +) => { + const portLayerMask = getRegionPortLayerMask(topology, regionId) + const availableLayerMask = topology.regionAvailableZMask?.[regionId] ?? 0 + + return availableLayerMask === 0 + ? portLayerMask + : portLayerMask & availableLayerMask +} + +/** + * Selects one existing ordinary region per cross-layer route. The region must + * expose ports on both endpoint layers, and the score includes its normal via + * cost. This changes search order only; it does not change topology or routing + * legality. + */ +export const createLayerAwareRouteHeuristic = ({ + topology, + problem, + distanceToCost, + minViaPadDiameter, +}: CreateLayerAwareRouteHeuristicContext): LayerAwareRouteHeuristic => { + const layerTransitionRegionIdByRoute = new Int32Array( + problem.routeCount, + ).fill(-1) + const layerTransitionCostByRoute = new Float64Array(problem.routeCount) + const traversableLayerMaskByRegion = new Int32Array(topology.regionCount) + const layerTransitionCostByRegion = new Float64Array(topology.regionCount) + + for (let regionId = 0; regionId < topology.regionCount; regionId++) { + const traversableLayerMask = getTraversableRegionLayerMask( + topology, + regionId, + ) + traversableLayerMaskByRegion[regionId] = traversableLayerMask + layerTransitionCostByRegion[regionId] = computeRegionCost( + topology.regionWidth[regionId]!, + topology.regionHeight[regionId]!, + 0, + 0, + 1, + 1, + traversableLayerMask, + minViaPadDiameter, + ) + } + + for (let routeId = 0; routeId < problem.routeCount; routeId++) { + const startPortId = problem.routeStartPort[routeId]! + const endPortId = problem.routeEndPort[routeId]! + const startLayer = topology.portZ[startPortId]! + const endLayer = topology.portZ[endPortId]! + + if (startLayer === endLayer) continue + + const requiredLayerMask = (1 << startLayer) | (1 << endLayer) + const routeNetId = problem.routeNet[routeId]! + const startX = getPortRoutingCostX(topology, startPortId) + const startY = getPortRoutingCostY(topology, startPortId) + const endX = getPortRoutingCostX(topology, endPortId) + const endY = getPortRoutingCostY(topology, endPortId) + let bestTransitionScore = Number.POSITIVE_INFINITY + + for (let regionId = 0; regionId < topology.regionCount; regionId++) { + if ( + (traversableLayerMaskByRegion[regionId]! & requiredLayerMask) !== + requiredLayerMask + ) { + continue + } + + const reservedNetId = problem.regionNetId[regionId]! + if (reservedNetId !== -1 && reservedNetId !== routeNetId) continue + + const transitionX = topology.regionCenterX[regionId]! + const transitionY = topology.regionCenterY[regionId]! + const layerTransitionCost = layerTransitionCostByRegion[regionId]! + const transitionScore = + (Math.hypot(transitionX - startX, transitionY - startY) + + Math.hypot(endX - transitionX, endY - transitionY)) * + distanceToCost + + layerTransitionCost + + if (transitionScore >= bestTransitionScore) continue + + bestTransitionScore = transitionScore + layerTransitionRegionIdByRoute[routeId] = regionId + layerTransitionCostByRoute[routeId] = layerTransitionCost + } + } + + return { + layerTransitionRegionIdByRoute, + layerTransitionCostByRoute, + } +} + +/** + * Guides ports that remain on the start layer through the selected transition + * region. After the route changes layer, the original direct-distance + * heuristic applies. + */ +export const getLayerAwarePortHeuristicCost = ({ + topology, + problem, + layerAwareRouteHeuristic, + distanceToCost, + routeId, + portId, +}: GetLayerAwarePortHeuristicCostContext): number => { + const endPortId = problem.routeEndPort[routeId]! + const portX = getPortRoutingCostX(topology, portId) + const portY = getPortRoutingCostY(topology, portId) + const endX = getPortRoutingCostX(topology, endPortId) + const endY = getPortRoutingCostY(topology, endPortId) + const directCost = Math.hypot(portX - endX, portY - endY) * distanceToCost + const startPortId = problem.routeStartPort[routeId]! + + if (topology.portZ[portId] !== topology.portZ[startPortId]) { + return directCost + } + + const layerTransitionRegionId = + layerAwareRouteHeuristic.layerTransitionRegionIdByRoute[routeId]! + + if (layerTransitionRegionId === -1) return directCost + + const transitionX = topology.regionCenterX[layerTransitionRegionId]! + const transitionY = topology.regionCenterY[layerTransitionRegionId]! + + return ( + (Math.hypot(transitionX - portX, transitionY - portY) + + Math.hypot(endX - transitionX, endY - transitionY)) * + distanceToCost + + layerAwareRouteHeuristic.layerTransitionCostByRoute[routeId]! + ) +} From 8f80ab53894d6fb5b53319ecb4a428eca9d1013e Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 21:42:40 +0200 Subject: [PATCH 2/6] test: regenerate fixed cross-layer snapshot in CI --- .github/workflows/bun-test.yml | 4 +- .../cross-layer-heuristic-detour.snap.svg | 281 ------------------ 2 files changed, 3 insertions(+), 282 deletions(-) delete mode 100644 tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index ff846d8..ddce5e6 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -36,5 +36,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: test-snapshots - path: tests/**/__snapshots__/*.diff.png + path: | + tests/**/__snapshots__/*.diff.png + tests/**/__snapshots__/*.snap.svg if-no-files-found: ignore diff --git a/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg b/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg deleted file mode 100644 index e58d79a..0000000 --- a/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg +++ /dev/null @@ -1,281 +0,0 @@ -input topologysolver result From 54972cf58ea2ef7e0ee50bb7f1da72360c88d97c Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 21:44:23 +0200 Subject: [PATCH 3/6] test: update cross-layer snapshot for guided route --- .github/workflows/bun-test.yml | 4 +- .../cross-layer-heuristic-detour.snap.svg | 304 ++++++++++++++++++ 2 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index ddce5e6..ff846d8 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -36,7 +36,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: test-snapshots - path: | - tests/**/__snapshots__/*.diff.png - tests/**/__snapshots__/*.snap.svg + path: tests/**/__snapshots__/*.diff.png if-no-files-found: ignore diff --git a/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg b/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg new file mode 100644 index 0000000..49d9059 --- /dev/null +++ b/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg @@ -0,0 +1,304 @@ +input topologysolver result From f3e333872a42120fcf443b7065d2856ff10bb802 Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 21:45:42 +0200 Subject: [PATCH 4/6] ci: verify stacked fix format and types --- .github/workflows/bun-test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index ff846d8..994c01b 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -28,8 +28,11 @@ jobs: - name: Install dependencies run: bun install - - name: Run tests - run: bun test + - name: Run format, type, and test checks + run: | + bun run format:check + bunx tsc --noEmit + bun test - name: Upload snapshot artifacts if: always() From 5fef9da9d6eff84ea1837364c5315dc8c3ab26a3 Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 21:47:09 +0200 Subject: [PATCH 5/6] ci: scope hosted format check to stack --- .github/workflows/bun-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index 994c01b..0a0dc82 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -30,7 +30,7 @@ jobs: - name: Run format, type, and test checks run: | - bun run format:check + bunx biome format lib/core.ts lib/layer-aware-route-heuristic.ts tests/solver/cross-layer-heuristic-detour.test.ts bunx tsc --noEmit bun test From 788d124bc39fa4fdccad2c1e5181d3b7e780bc97 Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 21:48:08 +0200 Subject: [PATCH 6/6] ci: restore standard test workflow --- .github/workflows/bun-test.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index 0a0dc82..ff846d8 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -28,11 +28,8 @@ jobs: - name: Install dependencies run: bun install - - name: Run format, type, and test checks - run: | - bunx biome format lib/core.ts lib/layer-aware-route-heuristic.ts tests/solver/cross-layer-heuristic-detour.test.ts - bunx tsc --noEmit - bun test + - name: Run tests + run: bun test - name: Upload snapshot artifacts if: always()