From f308e3ef446883fb78c2cb11ce29f08e14649c84 Mon Sep 17 00:00:00 2001 From: seveibar Date: Tue, 14 Apr 2026 01:22:00 -0700 Subject: [PATCH 1/2] two hop finish system --- lib/bus-solver/TinyHyperGraphBusSolver.ts | 350 +++++++++++++++++++++- lib/core.ts | 4 + lib/visualizeTinyGraph.ts | 1 + pages/cm5io/bus1-routing.page.tsx | 5 +- tests/solver/cm5io-bus-routing.test.ts | 31 ++ 5 files changed, 383 insertions(+), 8 deletions(-) diff --git a/lib/bus-solver/TinyHyperGraphBusSolver.ts b/lib/bus-solver/TinyHyperGraphBusSolver.ts index 6ae290a..684297a 100644 --- a/lib/bus-solver/TinyHyperGraphBusSolver.ts +++ b/lib/bus-solver/TinyHyperGraphBusSolver.ts @@ -142,18 +142,27 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { CENTER_PORT_OPTIONS_PER_EDGE = 6 BUS_TRACE_LENGTH_MARGIN = 1 BUS_MAX_TRACE_STEPS = 256 + MANUAL_CENTER_FINISH_MAX_HOPS = 2 + MANUAL_CENTER_FINISH_PORT_OPTIONS_PER_BOUNDARY = 6 + MANUAL_CENTER_FINISH_CANDIDATE_LIMIT = 24 readonly busTraceOrder: BusTraceOrder readonly centerTraceIndex: number readonly centerRouteId: RouteId readonly centerRouteNetId: NetId readonly centerGoalTransitRegionId: RegionId + readonly centerGoalHopDistanceByRegion: Int32Array readonly otherTraceIndices: number[] readonly commitTraceIndices: number[] readonly tracePitch: number readonly regionDistanceToGoalByRegion: Float64Array private readonly sharedZ0PortsByRegionPair = new Map() + private readonly usableCenterlineSharedZ0PortsByRegionPair = new Map< + string, + PortId[] + >() + private readonly centerlineNeighborRegionIdsByRegion: RegionId[][] private readonly regionIndexBySerializedId = new Map() private readonly portIndexBySerializedId = new Map() private readonly boundarySupportCache = new Map() @@ -219,7 +228,11 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { } this.buildSharedZ0PortsByRegionPair() + this.buildUsableCenterlineSharedZ0PortsByRegionPair() + this.centerlineNeighborRegionIdsByRegion = + this.buildCenterlineNeighborRegionIdsByRegion() this.regionDistanceToGoalByRegion = this.computeRegionDistanceToGoal() + this.centerGoalHopDistanceByRegion = this.computeCenterGoalHopDistance() this.updateBusStats() } @@ -394,6 +407,82 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { } } + private buildUsableCenterlineSharedZ0PortsByRegionPair() { + this.usableCenterlineSharedZ0PortsByRegionPair.clear() + + for (const [regionPairKey, sharedPortIds] of this.sharedZ0PortsByRegionPair) { + const usablePortIds = sharedPortIds.filter((portId) => + this.isUsableCenterlineBoundaryPort(portId), + ) + + if (usablePortIds.length === 0) { + continue + } + + this.usableCenterlineSharedZ0PortsByRegionPair.set( + regionPairKey, + usablePortIds, + ) + } + } + + private buildCenterlineNeighborRegionIdsByRegion() { + const neighborRegionIdsByRegion = Array.from( + { length: this.topology.regionCount }, + () => [] as RegionId[], + ) + + for (const regionPairKey of this.usableCenterlineSharedZ0PortsByRegionPair.keys()) { + const separatorIndex = regionPairKey.indexOf(":") + const regionAId = Number(regionPairKey.slice(0, separatorIndex)) + const regionBId = Number(regionPairKey.slice(separatorIndex + 1)) + + neighborRegionIdsByRegion[regionAId]!.push(regionBId) + neighborRegionIdsByRegion[regionBId]!.push(regionAId) + } + + for (const neighborRegionIds of neighborRegionIdsByRegion) { + neighborRegionIds.sort((left, right) => left - right) + } + + return neighborRegionIdsByRegion + } + + private computeCenterGoalHopDistance() { + const hopDistanceByRegion = new Int32Array(this.topology.regionCount).fill( + -1, + ) + + if (this.centerGoalTransitRegionId < 0) { + return hopDistanceByRegion + } + + const queuedRegionIds = [this.centerGoalTransitRegionId] + hopDistanceByRegion[this.centerGoalTransitRegionId] = 0 + + for ( + let queueIndex = 0; + queueIndex < queuedRegionIds.length; + queueIndex++ + ) { + const currentRegionId = queuedRegionIds[queueIndex]! + const nextHopDistance = hopDistanceByRegion[currentRegionId]! + 1 + + for (const neighborRegionId of this.centerlineNeighborRegionIdsByRegion[ + currentRegionId + ] ?? []) { + if (hopDistanceByRegion[neighborRegionId] !== -1) { + continue + } + + hopDistanceByRegion[neighborRegionId] = nextHopDistance + queuedRegionIds.push(neighborRegionId) + } + } + + return hopDistanceByRegion + } + private computeRegionDistanceToGoal() { const regionDistanceToGoalByRegion = new Float64Array( this.topology.regionCount, @@ -423,12 +512,8 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { continue } - for (const [regionPairKey, sharedPortIds] of this - .sharedZ0PortsByRegionPair) { - if (sharedPortIds.length < this.problem.routeCount) { - continue - } - + for (const regionPairKey of this + .usableCenterlineSharedZ0PortsByRegionPair.keys()) { const separatorIndex = regionPairKey.indexOf(":") const regionAId = Number(regionPairKey.slice(0, separatorIndex)) const regionBId = Number(regionPairKey.slice(separatorIndex + 1)) @@ -2106,6 +2191,255 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { return path } + private isUsableCenterlineBoundaryPort(portId: PortId) { + return ( + this.problem.portSectionMask[portId] === 1 && + this.topology.portZ[portId] === 0 && + !this.isPortReservedForDifferentBusNet(this.centerRouteNetId, portId) + ) + } + + private isManualCenterFinishRegion(regionId: RegionId) { + const hopDistance = this.centerGoalHopDistanceByRegion[regionId] + return ( + hopDistance >= 0 && hopDistance <= this.MANUAL_CENTER_FINISH_MAX_HOPS + ) + } + + override getAdditionalRegionLabel(regionId: RegionId) { + if (!this.isManualCenterFinishRegion(regionId)) { + return undefined + } + + return `bus end-manual hop: ${this.centerGoalHopDistanceByRegion[regionId]}` + } + + private getCandidateBoundaryNormal(candidate: BusCenterCandidate) { + if ( + candidate.boundaryNormalX === undefined || + candidate.boundaryNormalY === undefined + ) { + return undefined + } + + return { + x: candidate.boundaryNormalX, + y: candidate.boundaryNormalY, + } + } + + private getOrderedUsableCenterlinePortsForBoundaryStep( + boundaryStep: BoundaryStep, + ) { + const orderedSharedPortIds = + this.getOrderedSharedPortsForBoundaryStep(boundaryStep) + if (!orderedSharedPortIds) { + return undefined + } + + return orderedSharedPortIds.filter((portId) => + this.isUsableCenterlineBoundaryPort(portId), + ) + } + + private getBoundaryCenterMidpointPenalty(boundaryStep: BoundaryStep) { + const orderedSharedPortIds = + this.getOrderedUsableCenterlinePortsForBoundaryStep(boundaryStep) + + if (!orderedSharedPortIds || orderedSharedPortIds.length === 0) { + return Number.POSITIVE_INFINITY + } + + const centerIndex = orderedSharedPortIds.indexOf(boundaryStep.centerPortId) + if (centerIndex === -1) { + return Number.POSITIVE_INFINITY + } + + return Math.abs(centerIndex - (orderedSharedPortIds.length - 1) / 2) + } + + private getManualCenterFinishPortOptions( + currentCandidate: BusCenterCandidate, + nextRegionId: RegionId, + ) { + const currentRegionId = currentCandidate.nextRegionId + if (currentRegionId === undefined) { + return [] as Array<{ + boundaryStep: BoundaryStep + portId: PortId + score: number + }> + } + + const regionPairKey = getRegionPairKey(currentRegionId, nextRegionId) + const sharedPortIds = + this.usableCenterlineSharedZ0PortsByRegionPair.get(regionPairKey) ?? [] + const previousNormal = this.getCandidateBoundaryNormal(currentCandidate) + const candidatePortOptions = sharedPortIds + .filter((portId) => portId !== currentCandidate.portId) + .map((portId) => { + const boundaryStep = this.createBoundaryStep( + currentRegionId, + nextRegionId, + portId, + currentCandidate.portId, + previousNormal, + ) + const midpointPenalty = + this.getBoundaryCenterMidpointPenalty(boundaryStep) + const segmentLength = getPortDistance( + this.topology, + currentCandidate.portId, + portId, + ) + const goalHeuristic = + this.computeCenterHeuristic(portId, nextRegionId) * + this.problem.routeCount + const score = + segmentLength * this.DISTANCE_TO_COST * this.problem.routeCount + + midpointPenalty + + goalHeuristic + + return { + boundaryStep, + portId, + score, + } + }) + .sort( + (left, right) => + left.score - right.score || left.portId - right.portId, + ) + + return candidatePortOptions.slice( + 0, + this.MANUAL_CENTER_FINISH_PORT_OPTIONS_PER_BOUNDARY, + ) + } + + private getCenterCandidatePathKey(candidate: BusCenterCandidate) { + return this.getCenterCandidatePath(candidate) + .map( + (pathCandidate) => + `${pathCandidate.portId}:${pathCandidate.nextRegionId}:${pathCandidate.atGoal ? 1 : 0}`, + ) + .join("|") + } + + private getManualCenterFinishCandidates(currentCandidate: BusCenterCandidate) { + const currentRegionId = currentCandidate.nextRegionId + if ( + currentRegionId === undefined || + !this.isManualCenterFinishRegion(currentRegionId) + ) { + return [] as BusCenterCandidate[] + } + + const goalPortId = this.problem.routeEndPort[this.centerRouteId]! + const candidateKeys = new Set() + const completionCandidates: BusCenterCandidate[] = [] + const currentHopDistance = this.centerGoalHopDistanceByRegion[currentRegionId] + const baseCost = currentCandidate.busCost ?? currentCandidate.g + + const search = ( + candidate: BusCenterCandidate, + regionId: RegionId, + hopDistance: number, + accumulatedCost: number, + ) => { + if (this.isPortIncidentToRegion(goalPortId, regionId)) { + const segmentLength = getPortDistance( + this.topology, + candidate.portId, + goalPortId, + ) + const goalCandidate: BusCenterCandidate = { + portId: goalPortId, + nextRegionId: regionId, + g: + accumulatedCost + + segmentLength * this.DISTANCE_TO_COST * this.problem.routeCount, + h: 0, + f: + accumulatedCost + + segmentLength * this.DISTANCE_TO_COST * this.problem.routeCount, + atGoal: true, + prevRegionId: regionId, + prevCandidate: candidate, + } + const candidateKey = this.getCenterCandidatePathKey(goalCandidate) + if (!candidateKeys.has(candidateKey)) { + candidateKeys.add(candidateKey) + completionCandidates.push(goalCandidate) + } + return + } + + if (hopDistance <= 0) { + return + } + + const nextHopDistance = hopDistance - 1 + for (const nextRegionId of this.centerlineNeighborRegionIdsByRegion[ + regionId + ] ?? []) { + if (this.centerGoalHopDistanceByRegion[nextRegionId] !== nextHopDistance) { + continue + } + + if (this.centerCandidatePathContainsRegion(candidate, nextRegionId)) { + continue + } + + for (const portOption of this.getManualCenterFinishPortOptions( + candidate, + nextRegionId, + )) { + if ( + this.centerCandidatePathContainsHop( + candidate, + portOption.portId, + nextRegionId, + ) + ) { + continue + } + + const segmentLength = getPortDistance( + this.topology, + candidate.portId, + portOption.portId, + ) + const nextCost = + accumulatedCost + + portOption.score + const nextCandidate: BusCenterCandidate = { + portId: portOption.portId, + nextRegionId, + g: nextCost, + h: 0, + f: nextCost, + prevRegionId: regionId, + prevCandidate: candidate, + boundaryNormalX: portOption.boundaryStep.normalX, + boundaryNormalY: portOption.boundaryStep.normalY, + } + + search(nextCandidate, nextRegionId, nextHopDistance, nextCost) + } + } + } + + search(currentCandidate, currentRegionId, currentHopDistance, baseCost) + + return completionCandidates + .sort( + (left, right) => + left.g - right.g || left.portId - right.portId, + ) + .slice(0, this.MANUAL_CENTER_FINISH_CANDIDATE_LIMIT) + } + private getAvailableCenterMoves(currentCandidate: BusCenterCandidate) { const moves: BusCenterCandidate[] = [] const routeId = this.centerRouteId @@ -2124,6 +2458,10 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { return moves } + if (this.isManualCenterFinishRegion(currentCandidate.nextRegionId)) { + return this.getManualCenterFinishCandidates(currentCandidate) + } + const parentCost = currentCandidate.busCost ?? currentCandidate.g const goalPortId = this.problem.routeEndPort[routeId]! diff --git a/lib/core.ts b/lib/core.ts index a6af99c..cc48934 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -747,6 +747,10 @@ export class TinyHyperGraphSolver extends BaseSolver { return typeof connectionId === "string" ? connectionId : `route-${routeId}` } + getAdditionalRegionLabel(_regionId: RegionId): string | undefined { + return undefined + } + getNeverSuccessfullyRoutedRoutes(): NeverSuccessfullyRoutedRouteSummary[] { const neverSuccessfullyRoutedRoutes: NeverSuccessfullyRoutedRouteSummary[] = [] diff --git a/lib/visualizeTinyGraph.ts b/lib/visualizeTinyGraph.ts index 3f0d36d..12611fd 100644 --- a/lib/visualizeTinyGraph.ts +++ b/lib/visualizeTinyGraph.ts @@ -196,6 +196,7 @@ const getRegionCostLabel = ( return formatLabel( `region: region-${regionId}`, `net: ${regionNetLabel}`, + solver.getAdditionalRegionLabel(regionId), `cost: ${regionCost.toFixed(3)}`, `congestion: ${congestionCost.toFixed(3)}`, `same layer X: ${regionCache?.existingSameLayerIntersections ?? 0}`, diff --git a/pages/cm5io/bus1-routing.page.tsx b/pages/cm5io/bus1-routing.page.tsx index 9a340a0..fc1c46d 100644 --- a/pages/cm5io/bus1-routing.page.tsx +++ b/pages/cm5io/bus1-routing.page.tsx @@ -114,8 +114,9 @@ export default function Cm5ioBus1RoutingPage() {
CM5IO bus1 routed by the centerline-only bus solver. One centerline candidate is expanded per step, the rest of the bus is - inferred from nearby z=0 boundary ports, and the end fanout is filled in - once the centerline reaches its destination. + inferred from nearby z=0 boundary ports, and regions marked with + bus end-manual hop switch the centerline over to the + manual two-hop finish logic near the destination.
{ + const solver = await createCm5ioBus1Solver() + const graphics = solver.visualize() + const regionLabels = (graphics.rects ?? []) + .map((rect) => rect.label) + .filter((label): label is string => typeof label === "string") + + expect( + regionLabels.some( + (label) => + label.includes("region: region-228") && + label.includes("bus end-manual hop: 0"), + ), + ).toBe(true) + expect( + regionLabels.some( + (label) => + label.includes("region: region-224") && + label.includes("bus end-manual hop: 1"), + ), + ).toBe(true) + expect( + regionLabels.some( + (label) => + label.includes("region: region-11") && + label.includes("bus end-manual hop: 2"), + ), + ).toBe(true) +}) + test("CM5IO bus1 keeps boundary port ordering stable through centerline direction changes", async () => { const solver = await createCm5ioBus1Solver() const internal = solver as any @@ -181,6 +211,7 @@ test("CM5IO bus1 never accepts an intersecting centerline bus solution", async ( solver.solve() + expect(solver.iterations).toBeLessThanOrEqual(200) expect(solver.stats.busCenterConnectionId).toBe("source_trace_108") expect(solver.solved).toBe(true) expect(solver.failed).toBe(false) From 2ef6fb759e7aed4dd7600a8c9400dd198ce2ed57 Mon Sep 17 00:00:00 2001 From: seveibar Date: Tue, 14 Apr 2026 01:22:26 -0700 Subject: [PATCH 2/2] wip --- lib/bus-solver/TinyHyperGraphBusSolver.ts | 33 +++++++++++------------ pages/cm5io/bus1-routing.page.tsx | 4 +-- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/lib/bus-solver/TinyHyperGraphBusSolver.ts b/lib/bus-solver/TinyHyperGraphBusSolver.ts index 684297a..739585e 100644 --- a/lib/bus-solver/TinyHyperGraphBusSolver.ts +++ b/lib/bus-solver/TinyHyperGraphBusSolver.ts @@ -410,7 +410,8 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { private buildUsableCenterlineSharedZ0PortsByRegionPair() { this.usableCenterlineSharedZ0PortsByRegionPair.clear() - for (const [regionPairKey, sharedPortIds] of this.sharedZ0PortsByRegionPair) { + for (const [regionPairKey, sharedPortIds] of this + .sharedZ0PortsByRegionPair) { const usablePortIds = sharedPortIds.filter((portId) => this.isUsableCenterlineBoundaryPort(portId), ) @@ -512,8 +513,7 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { continue } - for (const regionPairKey of this - .usableCenterlineSharedZ0PortsByRegionPair.keys()) { + for (const regionPairKey of this.usableCenterlineSharedZ0PortsByRegionPair.keys()) { const separatorIndex = regionPairKey.indexOf(":") const regionAId = Number(regionPairKey.slice(0, separatorIndex)) const regionBId = Number(regionPairKey.slice(separatorIndex + 1)) @@ -2201,9 +2201,7 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { private isManualCenterFinishRegion(regionId: RegionId) { const hopDistance = this.centerGoalHopDistanceByRegion[regionId] - return ( - hopDistance >= 0 && hopDistance <= this.MANUAL_CENTER_FINISH_MAX_HOPS - ) + return hopDistance >= 0 && hopDistance <= this.MANUAL_CENTER_FINISH_MAX_HOPS } override getAdditionalRegionLabel(regionId: RegionId) { @@ -2307,8 +2305,7 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { } }) .sort( - (left, right) => - left.score - right.score || left.portId - right.portId, + (left, right) => left.score - right.score || left.portId - right.portId, ) return candidatePortOptions.slice( @@ -2326,7 +2323,9 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { .join("|") } - private getManualCenterFinishCandidates(currentCandidate: BusCenterCandidate) { + private getManualCenterFinishCandidates( + currentCandidate: BusCenterCandidate, + ) { const currentRegionId = currentCandidate.nextRegionId if ( currentRegionId === undefined || @@ -2338,7 +2337,8 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { const goalPortId = this.problem.routeEndPort[this.centerRouteId]! const candidateKeys = new Set() const completionCandidates: BusCenterCandidate[] = [] - const currentHopDistance = this.centerGoalHopDistanceByRegion[currentRegionId] + const currentHopDistance = + this.centerGoalHopDistanceByRegion[currentRegionId] const baseCost = currentCandidate.busCost ?? currentCandidate.g const search = ( @@ -2383,7 +2383,9 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { for (const nextRegionId of this.centerlineNeighborRegionIdsByRegion[ regionId ] ?? []) { - if (this.centerGoalHopDistanceByRegion[nextRegionId] !== nextHopDistance) { + if ( + this.centerGoalHopDistanceByRegion[nextRegionId] !== nextHopDistance + ) { continue } @@ -2410,9 +2412,7 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { candidate.portId, portOption.portId, ) - const nextCost = - accumulatedCost + - portOption.score + const nextCost = accumulatedCost + portOption.score const nextCandidate: BusCenterCandidate = { portId: portOption.portId, nextRegionId, @@ -2433,10 +2433,7 @@ export class TinyHyperGraphBusSolver extends TinyHyperGraphSolver { search(currentCandidate, currentRegionId, currentHopDistance, baseCost) return completionCandidates - .sort( - (left, right) => - left.g - right.g || left.portId - right.portId, - ) + .sort((left, right) => left.g - right.g || left.portId - right.portId) .slice(0, this.MANUAL_CENTER_FINISH_CANDIDATE_LIMIT) } diff --git a/pages/cm5io/bus1-routing.page.tsx b/pages/cm5io/bus1-routing.page.tsx index fc1c46d..83ffd7e 100644 --- a/pages/cm5io/bus1-routing.page.tsx +++ b/pages/cm5io/bus1-routing.page.tsx @@ -115,8 +115,8 @@ export default function Cm5ioBus1RoutingPage() { CM5IO bus1 routed by the centerline-only bus solver. One centerline candidate is expanded per step, the rest of the bus is inferred from nearby z=0 boundary ports, and regions marked with - bus end-manual hop switch the centerline over to the - manual two-hop finish logic near the destination. + bus end-manual hop switch the centerline over to the manual + two-hop finish logic near the destination.