From 861305102282172ff692bfef2048fca25d1f4b26 Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 29 Jul 2026 23:30:53 -0700 Subject: [PATCH 1/3] Route fanout buses to internal planes --- lib/utils/autorouting/FanoutAutorouter.ts | 51 +++ lib/utils/autorouting/SimpleRouteJson.ts | 10 + .../autorouting/getBusesForSimpleRouteJson.ts | 85 +++-- .../getSimpleRouteJsonFromCircuitJson.ts | 45 ++- ...uter-fanout-plane-termination-pcb.snap.svg | 1 + ...uter-fanout-plane-termination-srj.snap.svg | 334 ++++++++++++++++++ ...torouter-fanout-plane-termination.test.tsx | 120 +++++++ 7 files changed, 612 insertions(+), 34 deletions(-) create mode 100644 tests/features/__snapshots__/autorouter-fanout-plane-termination-pcb.snap.svg create mode 100644 tests/features/__snapshots__/autorouter-fanout-plane-termination-srj.snap.svg create mode 100644 tests/features/autorouter-fanout-plane-termination.test.tsx diff --git a/lib/utils/autorouting/FanoutAutorouter.ts b/lib/utils/autorouting/FanoutAutorouter.ts index b774ccd76..2bf0f9e7b 100644 --- a/lib/utils/autorouting/FanoutAutorouter.ts +++ b/lib/utils/autorouting/FanoutAutorouter.ts @@ -1,6 +1,7 @@ import { FanoutSolver, type FanoutBorderTarget, + type FanoutDirection, type FanoutSolverOptions, } from "@tscircuit/fanout-solver" import type { @@ -59,6 +60,27 @@ const getFanoutBorderTarget = ( } } +const getPlaneFanoutDirection = ( + anchor: NinePointAnchor, +): FanoutDirection | undefined => { + switch (anchor) { + case "top_left": + case "top_center": + case "top_right": + return "up" + case "center_left": + return "left" + case "center": + return undefined + case "center_right": + return "right" + case "bottom_left": + case "bottom_center": + case "bottom_right": + return "down" + } +} + const createDownstreamSimpleRouteJson = ({ fanoutSimpleRouteJson, sourceComponentIds, @@ -151,6 +173,11 @@ export class FanoutAutorouter implements GenericLocalAutorouter { | undefined { if (!this.options.busFanoutDirections) return undefined const knownBusIds = new Set(this.input.buses?.map((bus) => bus.busId) ?? []) + const planeBusIds = new Set( + this.input.buses + ?.filter((bus) => bus.termination?.type === "plane") + .map((bus) => bus.busId) ?? [], + ) const preferences: Record = {} for (const [busId, fanoutDirection] of Object.entries( this.options.busFanoutDirections, @@ -160,16 +187,40 @@ export class FanoutAutorouter implements GenericLocalAutorouter { `Fanout direction references unknown bus "${busId}" in this autorouting phase`, ) } + if (planeBusIds.has(busId)) continue const target = getFanoutBorderTarget(getNinePointAnchor(fanoutDirection)) if (target) preferences[busId] = target } return Object.keys(preferences).length > 0 ? preferences : undefined } + private getPlaneBusDirections(): + | Readonly> + | undefined { + if (!this.options.busFanoutDirections) return undefined + const planeBusIds = new Set( + this.input.buses + ?.filter((bus) => bus.termination?.type === "plane") + .map((bus) => bus.busId) ?? [], + ) + const directions: Record = {} + for (const [busId, fanoutDirection] of Object.entries( + this.options.busFanoutDirections, + )) { + if (!planeBusIds.has(busId)) continue + const direction = getPlaneFanoutDirection( + getNinePointAnchor(fanoutDirection), + ) + if (direction) directions[busId] = direction + } + return Object.keys(directions).length > 0 ? directions : undefined + } + private getFanoutSolverOptions(): FanoutSolverOptions { const commonOptions: FanoutSolverOptions = { borderDistribution: "even", compactBusTracks: true, + busDirections: this.getPlaneBusDirections(), busExitPreferences: this.getBusExitPreferences(), } if (this.options.mode === "single_layer_fanout") { diff --git a/lib/utils/autorouting/SimpleRouteJson.ts b/lib/utils/autorouting/SimpleRouteJson.ts index 69c505dd7..de0b8353b 100644 --- a/lib/utils/autorouting/SimpleRouteJson.ts +++ b/lib/utils/autorouting/SimpleRouteJson.ts @@ -104,10 +104,20 @@ export type SimpleRouteDifferentialPair = { } /** A group of connections that an autorouter should keep together. */ +export type SimpleRouteBusTermination = + | { + type: "boundary" + } + | { + type: "plane" + layer: string + } + export type SimpleRouteBus = { busId: string name?: string connectionNames: SrjConnectionName[] + termination?: SimpleRouteBusTermination } export type SimpleRouteJson = Omit< diff --git a/lib/utils/autorouting/getBusesForSimpleRouteJson.ts b/lib/utils/autorouting/getBusesForSimpleRouteJson.ts index 52ddeb686..6be72f0f9 100644 --- a/lib/utils/autorouting/getBusesForSimpleRouteJson.ts +++ b/lib/utils/autorouting/getBusesForSimpleRouteJson.ts @@ -9,10 +9,6 @@ import type { type SourcePortId = NonNullable type SubcircuitId = NonNullable -type SubcircuitConnectivityMapKey = NonNullable< - SourceTrace["subcircuit_connectivity_map_key"] -> - type GetBusesParams = { srjConnections: SimpleRouteConnection[] buses: Bus[] @@ -20,7 +16,7 @@ type GetBusesParams = { subcircuitId?: SubcircuitId | null } -const getBusSourceTraceSubcircuitConnectivityMapKeyOrThrow = ({ +const getBusSourceTraceOrThrow = ({ bus, busSourceTraces, traceNameOrPortSelector, @@ -28,7 +24,7 @@ const getBusSourceTraceSubcircuitConnectivityMapKeyOrThrow = ({ bus: Bus busSourceTraces: SourceTrace[] traceNameOrPortSelector: string -}): SubcircuitConnectivityMapKey => { +}): SourceTrace => { const sourceTracesWithMatchingName = busSourceTraces.filter( (sourceTrace) => sourceTrace.name === traceNameOrPortSelector, ) @@ -57,34 +53,48 @@ const getBusSourceTraceSubcircuitConnectivityMapKeyOrThrow = ({ ) } - const sourceTrace = matchingSourceTraces[0] - if (!sourceTrace?.subcircuit_connectivity_map_key) { + const sourceTrace = matchingSourceTraces[0]! + if (!sourceTrace.subcircuit_connectivity_map_key) { throw new Error( `Source trace for "${traceNameOrPortSelector}" does not have a subcircuit connectivity map key in bus "${bus.name}"`, ) } - return sourceTrace.subcircuit_connectivity_map_key + return sourceTrace } const getBusSrjConnectionNameOrThrow = ({ srjConnections, bus, busSourceTraces, - traceSubcircuitConnectivityMapKey, + sourceTrace, traceNameOrPortSelector, }: { srjConnections: SimpleRouteConnection[] bus: Bus busSourceTraces: SourceTrace[] - traceSubcircuitConnectivityMapKey: SubcircuitConnectivityMapKey + sourceTrace: SourceTrace traceNameOrPortSelector: string }): SrjConnectionName => { + const exactSrjConnections = srjConnections.filter( + (srjConnection) => + srjConnection.source_trace_id === sourceTrace.source_trace_id, + ) + if (exactSrjConnections.length === 1) { + return exactSrjConnections[0]!.name + } + if (exactSrjConnections.length > 1) { + throw new Error( + `Trace name or port selector "${traceNameOrPortSelector}" matches multiple SRJ connections in bus "${bus.name}"`, + ) + } + + const connectivityMapKey = sourceTrace.subcircuit_connectivity_map_key const sourceTraceIds = busSourceTraces .filter( - (sourceTrace) => - sourceTrace.subcircuit_connectivity_map_key === - traceSubcircuitConnectivityMapKey, + (candidateSourceTrace) => + candidateSourceTrace.subcircuit_connectivity_map_key === + connectivityMapKey, ) .map((sourceTrace) => sourceTrace.source_trace_id) const matchingSrjConnections = srjConnections.filter( @@ -107,6 +117,37 @@ const getBusSrjConnectionNameOrThrow = ({ return matchingSrjConnections[0]!.name } +export const getPlaneTerminatedBusSourceTraceIds = ({ + buses, + sourceTraces, + subcircuitId, +}: Pick< + GetBusesParams, + "buses" | "sourceTraces" | "subcircuitId" +>): Set => { + const sourceTraceIds = new Set() + for (const bus of buses) { + if (bus._parsedProps.fanoutTermination?.type !== "plane") continue + + const busSubcircuitId = bus.getSubcircuit().subcircuit_id + if (subcircuitId && busSubcircuitId !== subcircuitId) continue + + const busSourceTraces = sourceTraces.filter( + (sourceTrace) => sourceTrace.subcircuit_id === busSubcircuitId, + ) + for (const traceNameOrPortSelector of bus._parsedProps.connections) { + sourceTraceIds.add( + getBusSourceTraceOrThrow({ + bus, + busSourceTraces, + traceNameOrPortSelector, + }).source_trace_id, + ) + } + } + return sourceTraceIds +} + /** Converts bus trace names or port selectors into SRJ constraints. */ export const getBusesForSimpleRouteJson = ({ srjConnections, @@ -124,17 +165,16 @@ export const getBusesForSimpleRouteJson = ({ ) const connectionNames = bus._parsedProps.connections.map( (traceNameOrPortSelector) => { - const traceSubcircuitConnectivityMapKey = - getBusSourceTraceSubcircuitConnectivityMapKeyOrThrow({ - bus, - busSourceTraces, - traceNameOrPortSelector, - }) + const sourceTrace = getBusSourceTraceOrThrow({ + bus, + busSourceTraces, + traceNameOrPortSelector, + }) return getBusSrjConnectionNameOrThrow({ srjConnections, bus, busSourceTraces, - traceSubcircuitConnectivityMapKey, + sourceTrace, traceNameOrPortSelector, }) }, @@ -150,6 +190,9 @@ export const getBusesForSimpleRouteJson = ({ busId: bus.name, name: bus.name, connectionNames, + ...(bus._parsedProps.fanoutTermination + ? { termination: bus._parsedProps.fanoutTermination } + : {}), }) } diff --git a/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts b/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts index b1ed1150c..da8028869 100644 --- a/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts +++ b/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts @@ -15,7 +15,10 @@ import type { SimpleRouteJson, } from "./SimpleRouteJson" import { getDescendantSubcircuitIds } from "./getAncestorSubcircuitIds" -import { getBusesForSimpleRouteJson } from "./getBusesForSimpleRouteJson" +import { + getBusesForSimpleRouteJson, + getPlaneTerminatedBusSourceTraceIds, +} from "./getBusesForSimpleRouteJson" import { getDifferentialPairsForSimpleRouteJson } from "./getDifferentialPairsForSimpleRouteJson" import { getPreservedRoutedSubcircuitTraces } from "./getPreservedRoutedSubcircuitTraces" import { getUnbrokenCopperPourObstacles } from "./getUnbrokenCopperPourObstacles" @@ -341,8 +344,14 @@ export const getSimpleRouteJsonFromCircuitJson = ({ // here so it is preserved as fixed copper instead of re-routed. // For cross-boundary traces, add breakout points as additional // waypoints so the autorouter routes through the boundary. - const directTraceConnections = db.source_trace - .list() + const sourceTraces = db.source_trace.list() + const buses: Bus[] = subcircuitComponent?.selectAll("bus") ?? [] + const planeTerminatedBusSourceTraceIds = getPlaneTerminatedBusSourceTraceIds({ + buses, + sourceTraces, + subcircuitId: subcircuit_id, + }) + const directTraceConnections = sourceTraces .filter( (trace) => !sourceTraceIdsAlreadyPreservedAsSrjTraces.has(trace.source_trace_id), @@ -361,7 +370,15 @@ export const getSimpleRouteJsonFromCircuitJson = ({ } }) - if (connectedPorts.length < 2) return null + const isPlaneTerminatedSourceTrace = planeTerminatedBusSourceTraceIds.has( + trace.source_trace_id, + ) + if ( + connectedPorts.length < 2 && + !(isPlaneTerminatedSourceTrace && connectedPorts.length === 1) + ) { + return null + } // TODO handle trace.connected_source_net_ids for (const connectedPort of connectedPorts) { @@ -464,9 +481,9 @@ export const getSimpleRouteJsonFromCircuitJson = ({ // eligible through an explicit current-scope net reference or exposed-net // contract. const sourceNetIds = new Set(source_nets.map((net) => net.source_net_id)) - const currentSubcircuitSourceTraces = db.source_trace - .list() - .filter((trace) => !subcircuit_id || trace.subcircuit_id === subcircuit_id) + const currentSubcircuitSourceTraces = sourceTraces.filter( + (trace) => !subcircuit_id || trace.subcircuit_id === subcircuit_id, + ) const exposedBridgeSourceTraceIds = new Set( (subcircuitComponent?.selectAll("trace") ?? []).flatMap((trace) => { const candidate = trace as { @@ -505,8 +522,10 @@ export const getSimpleRouteJsonFromCircuitJson = ({ .filter((id): id is string => Boolean(id)) : [], ) - const sourceTracesEligibleForNetConnections = db.source_trace - .list() + const sourceTracesEligibleForNetConnections = sourceTraces + .filter( + (trace) => !planeTerminatedBusSourceTraceIds.has(trace.source_trace_id), + ) .filter( (trace) => // Existing copper must still contribute endpoint connectivity when it @@ -592,6 +611,8 @@ export const getSimpleRouteJsonFromCircuitJson = ({ width: nominalTraceWidthFromConnectedTraces, pointsToConnect, } + if (pointsToConnect.length === 0) continue + connectionsFromNets.push(connection) for (const sourceNetId of connectedSourceNetIds) { connectionFromNetId.set(sourceNetId, connection) @@ -674,20 +695,18 @@ export const getSimpleRouteJsonFromCircuitJson = ({ const differentialPairs: DifferentialPair[] = subcircuitComponent?.selectAll("differentialpair") ?? [] - const buses: Bus[] = subcircuitComponent?.selectAll("bus") ?? [] - const srjDifferentialPairs: SimpleRouteDifferentialPair[] | undefined = getDifferentialPairsForSimpleRouteJson({ srjConnections: allConns, differentialPairs, - sourceTraces: db.source_trace.list(), + sourceTraces, subcircuitId: subcircuit_id, }) const srjBuses = getBusesForSimpleRouteJson({ srjConnections: allConns, buses, - sourceTraces: db.source_trace.list(), + sourceTraces, subcircuitId: subcircuit_id, }) diff --git a/tests/features/__snapshots__/autorouter-fanout-plane-termination-pcb.snap.svg b/tests/features/__snapshots__/autorouter-fanout-plane-termination-pcb.snap.svg new file mode 100644 index 000000000..8db879426 --- /dev/null +++ b/tests/features/__snapshots__/autorouter-fanout-plane-termination-pcb.snap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/features/__snapshots__/autorouter-fanout-plane-termination-srj.snap.svg b/tests/features/__snapshots__/autorouter-fanout-plane-termination-srj.snap.svg new file mode 100644 index 000000000..c2802e339 --- /dev/null +++ b/tests/features/__snapshots__/autorouter-fanout-plane-termination-srj.snap.svg @@ -0,0 +1,334 @@ + + + + + FULL ROUTED CIRCUIT: 0 CONNECTIONS, 2 TRACES + + + + + + + + + + AUTOROUTING PHASE 2 END: 0 CONNECTIONS, 2 TRACES + + + + + + + + + AUTOROUTING PHASE 2 START: 0 CONNECTIONS, 2 TRACES + + + + + + + + + AUTOROUTING PHASE 1 END: 0 CONNECTIONS, 2 TRACES + + + + + + + + + AUTOROUTING PHASE 1 START: 2 CONNECTIONS, 0 TRACES + + + + + + + \ No newline at end of file diff --git a/tests/features/autorouter-fanout-plane-termination.test.tsx b/tests/features/autorouter-fanout-plane-termination.test.tsx new file mode 100644 index 000000000..9d516bed7 --- /dev/null +++ b/tests/features/autorouter-fanout-plane-termination.test.tsx @@ -0,0 +1,120 @@ +import { expect, test } from "bun:test" +import { createAutoroutingPhaseIoStack } from "tests/fixtures/create-autorouting-phase-io-stack" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +const TestPad = ({ + pinNumber, + pcbX, + pcbY, +}: { + pinNumber: number + pcbX: number + pcbY: number +}) => ( + +) + +test("fanout drops source-only power and ground connections to internal planes", async () => { + const { circuit } = getTestFixture() + const autoroutingPhaseIoStack = createAutoroutingPhaseIoStack(circuit) + const bgaPads = Array.from({ length: 16 }, (_, padIndex) => { + const pinNumber = padIndex + 1 + return ( + + ) + }) + + circuit.add( + + + {bgaPads}} /> + + + + + , + ) + + await circuit.renderUntilSettled() + + expect(circuit.db.pcb_autorouting_error.list()).toEqual([]) + expect(circuit.db.pcb_trace_error.list()).toEqual([]) + expect(circuit.db.pcb_pad_trace_clearance_error.list()).toEqual([]) + expect(circuit.db.pcb_via_clearance_error.list()).toEqual([]) + expect(autoroutingPhaseIoStack).toHaveLength(2) + + const fanoutInput = autoroutingPhaseIoStack[0]!.startSimpleRouteJson! + expect(fanoutInput.connections).toHaveLength(2) + expect( + fanoutInput.connections.every( + (connection) => connection.pointsToConnect.length === 1, + ), + ).toBe(true) + expect(fanoutInput.buses).toEqual([ + { + busId: "GND_B2_PLANE", + name: "GND_B2_PLANE", + connectionNames: [fanoutInput.connections[0]!.name], + termination: { type: "plane", layer: "inner1" }, + }, + { + busId: "VCC_C3_PLANE", + name: "VCC_C3_PLANE", + connectionNames: [fanoutInput.connections[1]!.name], + termination: { type: "plane", layer: "inner2" }, + }, + ]) + expect(autoroutingPhaseIoStack[0]!.endSimpleRouteJson!.connections).toEqual( + [], + ) + + const vias = circuit.db.pcb_via.list() + expect(vias).toHaveLength(2) + expect(vias.map((via) => via.to_layer).toSorted()).toEqual([ + "inner1", + "inner2", + ]) + expect(circuit.db.pcb_trace.list()).toHaveLength(2) + + expect(circuit).toMatchPcbSnapshot(import.meta.path) + await expect(autoroutingPhaseIoStack).toMatchAutoroutingPhaseIoStackSnapshot( + import.meta.path, + "autorouter-fanout-plane-termination-srj", + circuit, + ) +}) From 43bd3c4bdd55f69500a006da5c7cb68d2cf49427 Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 29 Jul 2026 23:43:55 -0700 Subject: [PATCH 2/3] Keep fanout signals off plane layers --- .../primitive-components/Group/Group.ts | 1 + .../Group/GroupRoutingPhasePlan.ts | 1 + .../Group/Group_getRoutingPhasePlans.ts | 3 + lib/utils/autorouting/FanoutAutorouter.ts | 2 + .../autorouting/localAutorouterStrategies.ts | 9 +- ...uter-fanout-plane-termination-pcb.snap.svg | 2 +- ...uter-fanout-plane-termination-srj.snap.svg | 169 ++++++++++++++---- ...torouter-fanout-plane-termination.test.tsx | 57 ++++-- 8 files changed, 198 insertions(+), 46 deletions(-) diff --git a/lib/components/primitive-components/Group/Group.ts b/lib/components/primitive-components/Group/Group.ts index f987c75b3..cdd9da4c7 100644 --- a/lib/components/primitive-components/Group/Group.ts +++ b/lib/components/primitive-components/Group/Group.ts @@ -1174,6 +1174,7 @@ export class Group = typeof groupProps> commonAutorouterOptions, busFanoutDirections: routingPhasePlan.busFanoutDirections, fanoutBoundaryPadding: routingPhasePlan.fanoutBoundaryPadding, + fanoutRoutingLayers: routingPhasePlan.fanoutRoutingLayers, }) } diff --git a/lib/components/primitive-components/Group/GroupRoutingPhasePlan.ts b/lib/components/primitive-components/Group/GroupRoutingPhasePlan.ts index 534103c98..60944a84d 100644 --- a/lib/components/primitive-components/Group/GroupRoutingPhasePlan.ts +++ b/lib/components/primitive-components/Group/GroupRoutingPhasePlan.ts @@ -22,6 +22,7 @@ export interface RoutingPhasePlan { connectionSelectors?: string[] busFanoutDirections?: AutoroutingPhaseProps["busFanoutDirections"] fanoutBoundaryPadding?: AutoroutingPhaseProps["fanoutBoundaryPadding"] + fanoutRoutingLayers?: string[] drcTolerances?: RoutingPhaseDrcTolerances nets: Net[] traces: Trace[] diff --git a/lib/components/primitive-components/Group/Group_getRoutingPhasePlans.ts b/lib/components/primitive-components/Group/Group_getRoutingPhasePlans.ts index 466750fea..68e05c59d 100644 --- a/lib/components/primitive-components/Group/Group_getRoutingPhasePlans.ts +++ b/lib/components/primitive-components/Group/Group_getRoutingPhasePlans.ts @@ -277,6 +277,9 @@ export function Group_getRoutingPhasePlans( plan.busFanoutDirections = phaseProps?.busFanoutDirections plan.fanoutBoundaryPadding = phaseProps?.fanoutBoundaryPadding ?? groupFanoutBoundaryPadding + plan.fanoutRoutingLayers = phaseProps?.fanoutRoutingLayers?.map((layer) => + typeof layer === "string" ? layer : layer.name, + ) plan.drcTolerances = phaseProps ? getDrcTolerancesFromAutoroutingPhaseProps(phaseProps) : undefined diff --git a/lib/utils/autorouting/FanoutAutorouter.ts b/lib/utils/autorouting/FanoutAutorouter.ts index 2bf0f9e7b..0ddbe3a68 100644 --- a/lib/utils/autorouting/FanoutAutorouter.ts +++ b/lib/utils/autorouting/FanoutAutorouter.ts @@ -26,6 +26,7 @@ export interface FanoutAutorouterOptions { mode: FanoutAutorouterMode busFanoutDirections?: Readonly> fanoutBoundaryPadding?: FanoutBoundaryPadding + fanoutRoutingLayers?: string[] } const getNinePointAnchor = ( @@ -222,6 +223,7 @@ export class FanoutAutorouter implements GenericLocalAutorouter { compactBusTracks: true, busDirections: this.getPlaneBusDirections(), busExitPreferences: this.getBusExitPreferences(), + escapeLayers: this.options.fanoutRoutingLayers, } if (this.options.mode === "single_layer_fanout") { return { diff --git a/lib/utils/autorouting/localAutorouterStrategies.ts b/lib/utils/autorouting/localAutorouterStrategies.ts index fc688cc6e..960877f84 100644 --- a/lib/utils/autorouting/localAutorouterStrategies.ts +++ b/lib/utils/autorouting/localAutorouterStrategies.ts @@ -20,6 +20,7 @@ export interface LocalAutorouterStrategyContext { commonAutorouterOptions: AutorouterOptions busFanoutDirections?: AutoroutingPhaseProps["busFanoutDirections"] fanoutBoundaryPadding?: AutoroutingPhaseProps["fanoutBoundaryPadding"] + fanoutRoutingLayers?: string[] } export interface LocalAutorouterStrategy { @@ -45,11 +46,17 @@ const createFanoutAutorouterStrategy = ( ): LocalAutorouterStrategy => ({ cacheable: false, followUpAutorouter: "default", - create: ({ simpleRouteJson, busFanoutDirections, fanoutBoundaryPadding }) => + create: ({ + simpleRouteJson, + busFanoutDirections, + fanoutBoundaryPadding, + fanoutRoutingLayers, + }) => new FanoutAutorouter(simpleRouteJson, { mode, busFanoutDirections, fanoutBoundaryPadding, + fanoutRoutingLayers, }), }) diff --git a/tests/features/__snapshots__/autorouter-fanout-plane-termination-pcb.snap.svg b/tests/features/__snapshots__/autorouter-fanout-plane-termination-pcb.snap.svg index 8db879426..a44682fbf 100644 --- a/tests/features/__snapshots__/autorouter-fanout-plane-termination-pcb.snap.svg +++ b/tests/features/__snapshots__/autorouter-fanout-plane-termination-pcb.snap.svg @@ -1 +1 @@ - \ No newline at end of file +R1 \ No newline at end of file diff --git a/tests/features/__snapshots__/autorouter-fanout-plane-termination-srj.snap.svg b/tests/features/__snapshots__/autorouter-fanout-plane-termination-srj.snap.svg index c2802e339..7ec3ccf55 100644 --- a/tests/features/__snapshots__/autorouter-fanout-plane-termination-srj.snap.svg +++ b/tests/features/__snapshots__/autorouter-fanout-plane-termination-srj.snap.svg @@ -10,16 +10,35 @@ font-size="18" font-weight="700" text-anchor="middle" - >FULL ROUTED CIRCUIT: 0 CONNECTIONS, 2 TRACES + >FULL ROUTED CIRCUIT: 0 CONNECTIONS, 6 TRACES - - - - - - - - AUTOROUTING PHASE 2 END: 2 CONNECTIONS, 6 TRACES - - - - - - - - - AUTOROUTING PHASE 2 START: 2 CONNECTIONS, 4 TRACES - - - - - - - - - AUTOROUTING PHASE 1 END: 2 CONNECTIONS, 4 TRACES - - - - - - - - - AUTOROUTING PHASE 1 START: 4 CONNECTIONS, 0 TRACES - - - - - - - \ No newline at end of file diff --git a/tests/features/autorouter-fanout-plane-termination.test.tsx b/tests/features/autorouter-fanout-plane-termination.test.tsx index a836e73aa..2f08efeee 100644 --- a/tests/features/autorouter-fanout-plane-termination.test.tsx +++ b/tests/features/autorouter-fanout-plane-termination.test.tsx @@ -50,24 +50,18 @@ test("fanout drops source-only power and ground connections to internal planes", {bgaPads}} /> - - @@ -93,14 +87,14 @@ test("fanout drops source-only power and ground connections to internal planes", ).toBe(true) expect(fanoutInput.buses).toEqual([ { - busId: "GND_B2_PLANE", - name: "GND_B2_PLANE", + busId: "GND_B2", + name: "GND_B2", connectionNames: [fanoutInput.connections[0]!.name], termination: { type: "plane", layer: "inner1" }, }, { - busId: "VCC_C3_PLANE", - name: "VCC_C3_PLANE", + busId: "VCC_C3", + name: "VCC_C3", connectionNames: [fanoutInput.connections[1]!.name], termination: { type: "plane", layer: "inner2" }, }, @@ -139,11 +133,55 @@ test("fanout drops source-only power and ground connections to internal planes", ) expect(signalLayers).not.toContain("inner1") expect(signalLayers).not.toContain("inner2") +}) - expect(circuit).toMatchPcbSnapshot(import.meta.path) - await expect(autoroutingPhaseIoStack).toMatchAutoroutingPhaseIoStackSnapshot( - import.meta.path, - "autorouter-fanout-plane-termination-srj", - circuit, +test("fanout infers plane nets from copper pours", async () => { + const { circuit } = getTestFixture() + const autoroutingPhaseIoStack = createAutoroutingPhaseIoStack(circuit) + + circuit.add( + + + + + + } + /> + + + , ) + + await circuit.renderUntilSettled() + + expect(circuit.db.pcb_autorouting_error.list()).toEqual([]) + expect(autoroutingPhaseIoStack[0]!.startSimpleRouteJson!.buses).toEqual([ + { + busId: "GND_A1", + name: "GND_A1", + connectionNames: [ + autoroutingPhaseIoStack[0]!.startSimpleRouteJson!.connections[0]!.name, + ], + termination: { type: "plane", layer: "inner1" }, + }, + ]) + expect( + circuit.db.pcb_via.list().some((via) => via.to_layer === "inner1"), + ).toBe(true) })