From 220fdb30cdd1a90d744dbc561108308bd82abe2c Mon Sep 17 00:00:00 2001 From: mohan-bee Date: Fri, 28 Aug 2026 15:49:45 +0530 Subject: [PATCH 1/5] up --- .../alignSameNetJunctions.ts | 64 +++++++++++++++++++ .../repro-wireless-mcu-cc3235sf-full.snap.svg | 4 +- .../repro-wireless-mcu-cc3235sf-full.test.ts | 57 +++++++++++++++++ 3 files changed, 123 insertions(+), 2 deletions(-) diff --git a/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts b/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts index 2bfef5389..41bda96c0 100644 --- a/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +++ b/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts @@ -6,6 +6,7 @@ import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSol import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect" import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement" import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry" +import { getTraceConnectedPinComponents } from "lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents" import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath" import { getVisibleTraceLength, @@ -40,6 +41,7 @@ const MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET = 0.2 // symbol-stem correction away from the pin itself. const MAX_SHARED_PIN_RAIL_OFFSET = 0.05 const MIN_RETURN_STEM_LENGTH = 0.05 +const MIN_ESTABLISHED_LEVEL_RAIL_TRACE_COUNT = 2 export const getSharedPin = ({ donorTrace, @@ -371,6 +373,55 @@ const candidateIsClear = ({ ) } +const establishedLevelRailHasBlockedMember = ({ + donorTrace, + branchTrace, + traces, + inputProblem, +}: { + donorTrace: SolvedTracePath + branchTrace: SolvedTracePath + traces: SolvedTracePath[] + inputProblem: InputProblem +}) => { + const donorRail = getLongestHorizontalSegment(donorTrace) + const branchRail = getLongestHorizontalSegment(branchTrace) + if (!donorRail || !branchRail) return false + + const sameLevelTraces = traces.filter((trace) => { + if (trace.globalConnNetId !== branchTrace.globalConnNetId) return false + const rail = getLongestHorizontalSegment(trace) + return rail !== null && nearlyEqual(rail.start.y, branchRail.start.y) + }) + const pinIds = [...new Set(sameLevelTraces.flatMap((trace) => trace.pinIds))] + const chain = getTraceConnectedPinComponents({ + pinIds, + traces: sameLevelTraces, + }).find((component) => component.traces.includes(branchTrace))?.traces + if (!chain) return false + if (chain.length < MIN_ESTABLISHED_LEVEL_RAIL_TRACE_COUNT) return false + + const obstacles = getObstacleRects(inputProblem) + return chain.some((trace) => { + const [firstPin, secondPin] = trace.pins + if (!firstPin || !secondPin || !nearlyEqual(firstPin.y, secondPin.y)) { + return false + } + const candidatePath = simplifyPath([ + { x: firstPin.x, y: firstPin.y }, + { x: firstPin.x, y: donorRail.start.y }, + { x: secondPin.x, y: donorRail.start.y }, + { x: secondPin.x, y: secondPin.y }, + ]) + const endpointChipIds = new Set(trace.pins.map((pin) => pin.chipId)) + const unrelatedObstacles = obstacles.filter( + (obstacle) => + obstacle.kind !== "chip" || !endpointChipIds.has(obstacle.chipId), + ) + return isPathCollidingWithObstacles(candidatePath, unrelatedObstacles) + }) +} + /** Extend the outer load's stem instead of adding a return trunk between loads. */ const getAlignedReturnBranchPath = ({ donorTrace, @@ -495,6 +546,19 @@ export const alignSameNetJunctions = ({ for (const candidatePath of candidatePaths) { if (!candidatePath) continue const candidateTrace = { ...branchTrace, tracePath: candidatePath } + // Keep an already-level chain intact when one load cannot follow the + // proposed alignment because of an unrelated obstacle. + if ( + !alignReturnBranches && + establishedLevelRailHasBlockedMember({ + donorTrace, + branchTrace, + traces: outputTraces, + inputProblem, + }) + ) { + continue + } const originalPair = [donorTrace, branchTrace] const candidatePair = [donorTrace, candidateTrace] const removesVisibleSegment = diff --git a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg index 8bf5145ef..5a756b902 100644 --- a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg +++ b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg @@ -1,6 +1,6 @@ - U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51 { @@ -13,5 +42,33 @@ test("repro WirelessMCU CC3235SF full schematic trace routing", () => { const solver = new SchematicTracePipelineSolver(inputProblem) solver.solve() + + const alignedTraces = solver.sameNetJunctionAlignmentSolver!.outputTraces + const bottomRailYs = alignedTraces + .filter((trace) => + trace.pinIds.every((pinId) => BOTTOM_CAPACITOR_RAIL_PIN_IDS.has(pinId)), + ) + .flatMap(getHorizontalRailYs) + expect(bottomRailYs).toHaveLength(4) + for (const railY of bottomRailYs) { + expect(railY).toBeCloseTo(bottomRailYs[0]!) + } + + const topRailYs = alignedTraces + .filter((trace) => + trace.pinIds.every((pinId) => TOP_CAPACITOR_RAIL_PIN_IDS.has(pinId)), + ) + .flatMap(getHorizontalRailYs) + const topRailFeeder = alignedTraces.find( + (trace) => + trace.pinIds.includes("schematic_port_81") && + trace.pinIds.includes("schematic_port_65"), + )! + const feederRailY = getHorizontalRailYs(topRailFeeder)[0]! + expect(topRailYs).toHaveLength(6) + for (const railY of topRailYs) { + expect(railY).toBeCloseTo(feederRailY) + } + expect(solver).toMatchSolverSnapshot(import.meta.path) }) From 1dacfde09d4d563b056db5217741212ac713ca20 Mon Sep 17 00:00:00 2001 From: mohan-bee Date: Fri, 28 Aug 2026 16:00:35 +0530 Subject: [PATCH 2/5] up --- .../shouldSeparateGroundNetRows.ts | 4 +- .../repro-wireless-mcu-cc3235sf-full.snap.svg | 68 ++++++++++++------- ...ection-pair-solver-ground-net-rows.test.ts | 46 +++++++++++-- 3 files changed, 87 insertions(+), 31 deletions(-) diff --git a/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts b/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts index 1bd59109c..50f98ec6d 100644 --- a/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts +++ b/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts @@ -1,7 +1,7 @@ import type { InputNetConnection, InputPin } from "lib/types/InputProblem" const SAME_RAIL_Y_TOLERANCE = 1e-6 -const MIN_GROUPED_RAIL_PIN_COUNT = 3 +const MIN_GROUPED_RAIL_PIN_COUNT = 2 function isGroupedHorizontalRail({ pin, @@ -34,7 +34,7 @@ export function shouldSeparateGroundNetRows({ // Net labels preserve ground connectivity without a cross-row MSP edge. return ( - isGroupedHorizontalRail({ pin: pin1, netPins }) && + isGroupedHorizontalRail({ pin: pin1, netPins }) || isGroupedHorizontalRail({ pin: pin2, netPins }) ) } diff --git a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg index 5a756b902..830950d83 100644 --- a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg +++ b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg @@ -1,6 +1,6 @@ - U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXCC3235SF12RGKRU2MX25R3235FM1IL0U1J1J2J3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + " fill="rgba(255, 255, 255, 0.6)" stroke="rgb(132, 0, 0)" stroke-width="0.769827879316px"/>XXCC3235SF12RGKRU2MX25R3235FM1IL0U1J1J2J3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ No newline at end of file diff --git a/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts b/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts index 62e9d76ae..0cb005e7e 100644 --- a/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts +++ b/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts @@ -1,8 +1,16 @@ import { expect, test } from "bun:test" import { MspConnectionPairSolver } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver" -import type { InputNetConnection, InputProblem } from "lib/types/InputProblem" +import type { + InputNetConnection, + InputProblem, + PinId, +} from "lib/types/InputProblem" import inputProblemJson from "../../repros/assets/repro-bluetooth-controller-ground-decoupling-groups.input.json" +function cloneInputProblem(): InputProblem { + return JSON.parse(JSON.stringify(inputProblemJson)) +} + function solveNetPairs({ inputProblem, netConnection, @@ -20,11 +28,21 @@ function solveNetPairs({ ) } -test("keeps grouped ground rails in separate rows", () => { - const inputProblem = inputProblemJson as InputProblem +function expectSeparatedGroundRows({ + removedPinIds, + expectedPairCount, +}: { + removedPinIds: PinId[] + expectedPairCount: number +}) { + const inputProblem = cloneInputProblem() const groundConnection = inputProblem.netConnections.find( (connection) => connection.isGround, )! + const removedPinIdSet = new Set(removedPinIds) + groundConnection.pinIds = groundConnection.pinIds.filter( + (pinId) => !removedPinIdSet.has(pinId), + ) const groundPairs = solveNetPairs({ inputProblem, netConnection: groundConnection, @@ -33,12 +51,30 @@ test("keeps grouped ground rails in separate rows", () => { (pair) => pair.pins[0].y !== pair.pins[1].y, ) - expect(groundPairs).toHaveLength(5) + expect(groundPairs).toHaveLength(expectedPairCount) expect(crossRowPairs).toHaveLength(0) +} + +test("keeps grouped ground rails in separate rows", () => { + expectSeparatedGroundRows({ removedPinIds: [], expectedPairCount: 5 }) +}) + +test("keeps a two-component ground rail separate from another row", () => { + expectSeparatedGroundRows({ + removedPinIds: ["schematic_port_13"], + expectedPairCount: 4, + }) +}) + +test("does not bridge a grouped ground rail through a singleton", () => { + expectSeparatedGroundRows({ + removedPinIds: ["schematic_port_11", "schematic_port_13"], + expectedPairCount: 3, + }) }) test("does not separate rows without ground metadata", () => { - const inputProblem = structuredClone(inputProblemJson) as InputProblem + const inputProblem = cloneInputProblem() const groundConnection = inputProblem.netConnections.find( (connection) => connection.isGround, )! From 546c1ce9b1d739a2d4e997dc4f6624bfb8c5000d Mon Sep 17 00:00:00 2001 From: mohan-bee Date: Tue, 1 Sep 2026 12:50:41 +0530 Subject: [PATCH 3/5] up --- .../shouldSeparateGroundNetRows.ts | 4 +- .../alignSameNetJunctions.ts | 8 +-- .../repro-wireless-mcu-cc3235sf-full.snap.svg | 68 +++++++------------ .../repro-wireless-mcu-cc3235sf-full.test.ts | 18 ++++- ...ection-pair-solver-ground-net-rows.test.ts | 46 ++----------- 5 files changed, 52 insertions(+), 92 deletions(-) diff --git a/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts b/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts index 50f98ec6d..1bd59109c 100644 --- a/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts +++ b/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts @@ -1,7 +1,7 @@ import type { InputNetConnection, InputPin } from "lib/types/InputProblem" const SAME_RAIL_Y_TOLERANCE = 1e-6 -const MIN_GROUPED_RAIL_PIN_COUNT = 2 +const MIN_GROUPED_RAIL_PIN_COUNT = 3 function isGroupedHorizontalRail({ pin, @@ -34,7 +34,7 @@ export function shouldSeparateGroundNetRows({ // Net labels preserve ground connectivity without a cross-row MSP edge. return ( - isGroupedHorizontalRail({ pin: pin1, netPins }) || + isGroupedHorizontalRail({ pin: pin1, netPins }) && isGroupedHorizontalRail({ pin: pin2, netPins }) ) } diff --git a/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts b/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts index 41bda96c0..fbf0661e1 100644 --- a/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +++ b/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts @@ -1,13 +1,12 @@ import type { Point } from "@tscircuit/math-utils" import { getSegmentIntersection } from "@tscircuit/math-utils/line-intersections" +import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement" import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver" +import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry" +import { getTraceConnectedPinComponents } from "lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents" import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions" import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect" -import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement" -import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry" -import { getTraceConnectedPinComponents } from "lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents" -import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath" import { getVisibleTraceLength, getVisibleTraceSegmentCount, @@ -15,6 +14,7 @@ import { isVertical, nearlyEqual, } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry" +import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath" import type { InputPin, InputProblem } from "lib/types/InputProblem" import { doesPathCoincideWithTraces } from "lib/utils/doesPathCoincideWithTraces" import { diff --git a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg index 830950d83..5a756b902 100644 --- a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg +++ b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg @@ -1,6 +1,6 @@ - U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXCC3235SF12RGKRU2MX25R3235FM1IL0U1J1J2J3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + " fill="rgba(255, 255, 255, 0.6)" stroke="rgb(132, 0, 0)" stroke-width="0.769827879316px"/>XXCC3235SF12RGKRU2MX25R3235FM1IL0U1J1J2J3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ No newline at end of file diff --git a/tests/repros/repro-wireless-mcu-cc3235sf-full.test.ts b/tests/repros/repro-wireless-mcu-cc3235sf-full.test.ts index f7ad118d0..672d726f0 100644 --- a/tests/repros/repro-wireless-mcu-cc3235sf-full.test.ts +++ b/tests/repros/repro-wireless-mcu-cc3235sf-full.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test" -import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver" import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" +import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver" import type { InputProblem } from "lib/types/InputProblem" import "tests/fixtures/matcher" import inputProblemJson from "./assets/repro-wireless-mcu-cc3235sf-full.input.json" @@ -70,5 +70,21 @@ test("repro WirelessMCU CC3235SF full schematic trace routing", () => { expect(railY).toBeCloseTo(feederRailY) } + const c1C2GroundTrace = alignedTraces.find( + (trace) => + trace.pinIds.includes("schematic_port_66") && + trace.pinIds.includes("schematic_port_68"), + ) + const nearbyGroundFeeder = alignedTraces.find( + (trace) => + trace.pinIds.includes("schematic_port_68") && + trace.pinIds.includes("schematic_port_126"), + ) + expect(c1C2GroundTrace).toBeDefined() + expect(nearbyGroundFeeder).toBeDefined() + const c1C2GroundRailY = getHorizontalRailYs(c1C2GroundTrace!)[0]! + const nearbyGroundFeederRailY = getHorizontalRailYs(nearbyGroundFeeder!)[0]! + expect(c1C2GroundRailY).toBeCloseTo(nearbyGroundFeederRailY) + expect(solver).toMatchSolverSnapshot(import.meta.path) }) diff --git a/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts b/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts index 0cb005e7e..62e9d76ae 100644 --- a/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts +++ b/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts @@ -1,16 +1,8 @@ import { expect, test } from "bun:test" import { MspConnectionPairSolver } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver" -import type { - InputNetConnection, - InputProblem, - PinId, -} from "lib/types/InputProblem" +import type { InputNetConnection, InputProblem } from "lib/types/InputProblem" import inputProblemJson from "../../repros/assets/repro-bluetooth-controller-ground-decoupling-groups.input.json" -function cloneInputProblem(): InputProblem { - return JSON.parse(JSON.stringify(inputProblemJson)) -} - function solveNetPairs({ inputProblem, netConnection, @@ -28,21 +20,11 @@ function solveNetPairs({ ) } -function expectSeparatedGroundRows({ - removedPinIds, - expectedPairCount, -}: { - removedPinIds: PinId[] - expectedPairCount: number -}) { - const inputProblem = cloneInputProblem() +test("keeps grouped ground rails in separate rows", () => { + const inputProblem = inputProblemJson as InputProblem const groundConnection = inputProblem.netConnections.find( (connection) => connection.isGround, )! - const removedPinIdSet = new Set(removedPinIds) - groundConnection.pinIds = groundConnection.pinIds.filter( - (pinId) => !removedPinIdSet.has(pinId), - ) const groundPairs = solveNetPairs({ inputProblem, netConnection: groundConnection, @@ -51,30 +33,12 @@ function expectSeparatedGroundRows({ (pair) => pair.pins[0].y !== pair.pins[1].y, ) - expect(groundPairs).toHaveLength(expectedPairCount) + expect(groundPairs).toHaveLength(5) expect(crossRowPairs).toHaveLength(0) -} - -test("keeps grouped ground rails in separate rows", () => { - expectSeparatedGroundRows({ removedPinIds: [], expectedPairCount: 5 }) -}) - -test("keeps a two-component ground rail separate from another row", () => { - expectSeparatedGroundRows({ - removedPinIds: ["schematic_port_13"], - expectedPairCount: 4, - }) -}) - -test("does not bridge a grouped ground rail through a singleton", () => { - expectSeparatedGroundRows({ - removedPinIds: ["schematic_port_11", "schematic_port_13"], - expectedPairCount: 3, - }) }) test("does not separate rows without ground metadata", () => { - const inputProblem = cloneInputProblem() + const inputProblem = structuredClone(inputProblemJson) as InputProblem const groundConnection = inputProblem.netConnections.find( (connection) => connection.isGround, )! From 2894e748cb9458fcb5fb26e737054e93f6ef4306 Mon Sep 17 00:00:00 2001 From: mohan-bee Date: Tue, 1 Sep 2026 13:50:26 +0530 Subject: [PATCH 4/5] up --- .../getGroundConnectionPolicy.ts | 2 +- .../shouldSeparateGroundNetRows.ts | 20 ++++-- .../repro-wireless-mcu-cc3235sf-full.snap.svg | 48 +++++++------- ...ection-pair-solver-ground-net-rows.test.ts | 65 +++++++++++++++++-- 4 files changed, 98 insertions(+), 37 deletions(-) diff --git a/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts b/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts index 785830b2f..032b2e729 100644 --- a/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts +++ b/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts @@ -5,7 +5,7 @@ import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInp // Keep nearby ground connections, but do not extend the usual 1 mm local // routing range vertically just because a sheet allows long signal traces. -const MAX_LOCAL_GROUND_BRANCH_OFFSET = 1 +export const MAX_LOCAL_GROUND_BRANCH_OFFSET = 1 /** Avoid return wires between staggered, net-only ground-facing branches. */ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => { diff --git a/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts b/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts index 1bd59109c..668474191 100644 --- a/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts +++ b/lib/solvers/MspConnectionPairSolver/shouldSeparateGroundNetRows.ts @@ -1,9 +1,10 @@ import type { InputNetConnection, InputPin } from "lib/types/InputProblem" +import { MAX_LOCAL_GROUND_BRANCH_OFFSET } from "./getGroundConnectionPolicy" const SAME_RAIL_Y_TOLERANCE = 1e-6 -const MIN_GROUPED_RAIL_PIN_COUNT = 3 +const MIN_GROUPED_RAIL_CHIP_COUNT = 2 -function isGroupedHorizontalRail({ +function getHorizontalRailChipCount({ pin, netPins, }: { @@ -15,7 +16,7 @@ function isGroupedHorizontalRail({ (otherPin) => Math.abs(otherPin.y - pin.y) <= SAME_RAIL_Y_TOLERANCE, ) const railChipIds = new Set(sameRailPins.map((railPin) => railPin.chipId)) - return railChipIds.size >= MIN_GROUPED_RAIL_PIN_COUNT + return railChipIds.size } export function shouldSeparateGroundNetRows({ @@ -30,11 +31,18 @@ export function shouldSeparateGroundNetRows({ pin2: InputPin & { chipId: string } }) { if (!netConnection?.isGround || pin1.chipId === pin2.chipId) return false - if (Math.abs(pin1.y - pin2.y) <= SAME_RAIL_Y_TOLERANCE) return false + if ( + Math.abs(pin1.y - pin2.y) <= + MAX_LOCAL_GROUND_BRANCH_OFFSET + SAME_RAIL_Y_TOLERANCE + ) { + return false + } + const pin1RailChipCount = getHorizontalRailChipCount({ pin: pin1, netPins }) + const pin2RailChipCount = getHorizontalRailChipCount({ pin: pin2, netPins }) // Net labels preserve ground connectivity without a cross-row MSP edge. return ( - isGroupedHorizontalRail({ pin: pin1, netPins }) && - isGroupedHorizontalRail({ pin: pin2, netPins }) + pin1RailChipCount >= MIN_GROUPED_RAIL_CHIP_COUNT && + pin2RailChipCount >= MIN_GROUPED_RAIL_CHIP_COUNT ) } diff --git a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg index 5a756b902..0d9f448e7 100644 --- a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg +++ b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg @@ -1,6 +1,6 @@ - U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXCC3235SF12RGKRU2MX25R3235FM1IL0U1J1J2J3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + " fill="rgba(255, 255, 255, 0.6)" stroke="rgb(132, 0, 0)" stroke-width="0.769827879316px"/>XXCC3235SF12RGKRU2MX25R3235FM1IL0U1J1J2J3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ No newline at end of file diff --git a/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts b/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts index 62e9d76ae..58e83b0c5 100644 --- a/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts +++ b/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-ground-net-rows.test.ts @@ -1,8 +1,17 @@ import { expect, test } from "bun:test" import { MspConnectionPairSolver } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver" -import type { InputNetConnection, InputProblem } from "lib/types/InputProblem" +import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver" +import type { + InputNetConnection, + InputProblem, + PinId, +} from "lib/types/InputProblem" import inputProblemJson from "../../repros/assets/repro-bluetooth-controller-ground-decoupling-groups.input.json" +function cloneInputProblem(): InputProblem { + return structuredClone(inputProblemJson) as InputProblem +} + function solveNetPairs({ inputProblem, netConnection, @@ -20,11 +29,21 @@ function solveNetPairs({ ) } -test("keeps grouped ground rails in separate rows", () => { - const inputProblem = inputProblemJson as InputProblem +function expectSeparatedGroundRows({ + removedPinIds, + expectedPairCount, +}: { + removedPinIds: PinId[] + expectedPairCount: number +}) { + const inputProblem = cloneInputProblem() const groundConnection = inputProblem.netConnections.find( (connection) => connection.isGround, )! + const removedPinIdSet = new Set(removedPinIds) + groundConnection.pinIds = groundConnection.pinIds.filter( + (pinId) => !removedPinIdSet.has(pinId), + ) const groundPairs = solveNetPairs({ inputProblem, netConnection: groundConnection, @@ -33,12 +52,48 @@ test("keeps grouped ground rails in separate rows", () => { (pair) => pair.pins[0].y !== pair.pins[1].y, ) - expect(groundPairs).toHaveLength(5) + expect(groundPairs).toHaveLength(expectedPairCount) expect(crossRowPairs).toHaveLength(0) +} + +test("keeps grouped ground rails in separate rows", () => { + expectSeparatedGroundRows({ removedPinIds: [], expectedPairCount: 5 }) +}) + +test("uses labels between distant two-component ground rails", () => { + expectSeparatedGroundRows({ + removedPinIds: ["schematic_port_13"], + expectedPairCount: 4, + }) +}) + +test("places one GND label on each distant grouped rail", () => { + const inputProblem = cloneInputProblem() + const groundConnection = inputProblem.netConnections.find( + (connection) => connection.isGround, + )! + groundConnection.pinIds = groundConnection.pinIds.filter( + (pinId) => pinId !== "schematic_port_13", + ) + const solver = new SchematicTracePipelineSolver(inputProblem) + + solver.solve() + + const groundLabels = solver + .netLabelToTraceSolver!.getOutput() + .netLabelPlacements.filter( + (label) => label.netId === groundConnection.netId, + ) + expect(groundLabels).toHaveLength(2) + const groundLabelYs = groundLabels + .map((label) => label.anchorPoint.y) + .sort((a, b) => a - b) + expect(groundLabelYs[0]).toBeCloseTo(-0.13) + expect(groundLabelYs[1]).toBeCloseTo(3.52) }) test("does not separate rows without ground metadata", () => { - const inputProblem = structuredClone(inputProblemJson) as InputProblem + const inputProblem = cloneInputProblem() const groundConnection = inputProblem.netConnections.find( (connection) => connection.isGround, )! From 03aa56bb9851471389e6f0eb0a4c111f9070a6da Mon Sep 17 00:00:00 2001 From: mohan-bee Date: Tue, 1 Sep 2026 14:04:31 +0530 Subject: [PATCH 5/5] up --- .../getGroundConnectionPolicy.ts | 130 +++++++++++++++++- .../repro-powerbank-3v-system-power.snap.svg | 53 ++++--- .../repro-wireless-mcu-cc3235sf-full.snap.svg | 46 ++++--- .../repro-wireless-mcu-cc3235sf-full.test.ts | 32 +++++ .../local-ground-branches.test.ts | 30 ++++ 5 files changed, 244 insertions(+), 47 deletions(-) diff --git a/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts b/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts index 032b2e729..483103ab6 100644 --- a/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts +++ b/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts @@ -1,16 +1,114 @@ import { ConnectivityMap } from "connectivity-map" -import type { InputProblem, PinId } from "lib/types/InputProblem" +import type { + InputChip, + InputPin, + InputProblem, + PinId, +} from "lib/types/InputProblem" import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection" import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem" // Keep nearby ground connections, but do not extend the usual 1 mm local // routing range vertically just because a sheet allows long signal traces. export const MAX_LOCAL_GROUND_BRANCH_OFFSET = 1 +const SAME_GROUND_PIN_BANK_AXIS_TOLERANCE = 1e-6 + +interface PinWithChip { + pin: InputPin + chip: InputChip +} + +const getLocalGroundPinBanks = ({ + inputProblem, + groundNetId, + netConnMap, +}: { + inputProblem: InputProblem + groundNetId?: string + netConnMap: ConnectivityMap +}): PinId[][] => { + if (!groundNetId) return [] + + return inputProblem.chips.flatMap((chip) => { + const groundPins = chip.pins.filter( + (pin) => netConnMap.getNetConnectedToId(pin.pinId) === groundNetId, + ) + if (groundPins.length < 2) return [] + // A connector with half or more of its contacts tied to ground is a + // deliberate ground bus. Localize only the smaller ground-pin banks found + // on multi-function ICs. + if (groundPins.length * 2 >= chip.pins.length) return [] + + const firstPin = groundPins[0]! + const facingDirection = + firstPin._facingDirection ?? getPinDirection(firstPin, chip) + const shareFacingDirection = groundPins.every( + (pin) => + (pin._facingDirection ?? getPinDirection(pin, chip)) === + facingDirection, + ) + if (!shareFacingDirection) return [] + + let parallelCoordinates = groundPins.map((pin) => pin.y) + let perpendicularCoordinates = groundPins.map((pin) => pin.x) + if (facingDirection === "x-" || facingDirection === "x+") { + parallelCoordinates = groundPins.map((pin) => pin.x) + perpendicularCoordinates = groundPins.map((pin) => pin.y) + } + const parallelSpan = + Math.max(...parallelCoordinates) - Math.min(...parallelCoordinates) + const perpendicularSpan = + Math.max(...perpendicularCoordinates) - + Math.min(...perpendicularCoordinates) + if (parallelSpan > SAME_GROUND_PIN_BANK_AXIS_TOLERANCE) return [] + if (perpendicularSpan > MAX_LOCAL_GROUND_BRANCH_OFFSET) return [] + + return [groundPins.map((pin) => pin.pinId)] + }) +} + +const isLongGroundPinBankConnection = ({ + firstPinId, + secondPinId, + groundPinBanks, + pins, +}: { + firstPinId: PinId + secondPinId: PinId + groundPinBanks: PinId[][] + pins: Map +}) => { + const firstPinBank = groundPinBanks.find((pinBank) => + pinBank.includes(firstPinId), + ) + const secondPinBank = groundPinBanks.find((pinBank) => + pinBank.includes(secondPinId), + ) + if (!firstPinBank && !secondPinBank) return false + if (firstPinBank === secondPinBank) return false + + const firstPin = pins.get(firstPinId)?.pin + const secondPin = pins.get(secondPinId)?.pin + if (!firstPin || !secondPin) return false + const orthogonalDistance = + Math.abs(firstPin.x - secondPin.x) + Math.abs(firstPin.y - secondPin.y) + return orthogonalDistance > MAX_LOCAL_GROUND_BRANCH_OFFSET +} /** Avoid return wires between staggered, net-only ground-facing branches. */ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => { const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem) - const groundNetId = netConnMap.getNetConnectedToId("GND") + const groundConnection = inputProblem.netConnections.find( + (connection) => connection.isGround, + ) + let groundNetId: string | undefined + if (groundConnection) { + groundNetId = netConnMap.getNetConnectedToId(groundConnection.netId) + } else { + // Older captured inputs predate `isGround`; preserve their routing while + // current inputs use the semantic marker above. + groundNetId = netConnMap.getNetConnectedToId("GND") + } // Net identifiers do not constitute physical edges: two separate direct // connections may have the same netId without requesting a wire between them. const physicalConnMap = new ConnectivityMap({}) @@ -38,6 +136,11 @@ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => { netConnMap.getNetConnectedToId(pin.pinId) === groundNetId && (pin._facingDirection ?? getPinDirection(pin, chip)) === "y-", ) + const groundPinBanks = getLocalGroundPinBanks({ + inputProblem, + groundNetId, + netConnMap, + }) // A deliberately wired, level bank of two-pin loads already has its own // shared return rail. Do not extend that rail to independent lower branches // just to reduce the number of GND symbols. Other ground topologies retain @@ -75,8 +178,17 @@ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => { return firstPhysicalGroup !== secondPhysicalGroup } if (!hasExplicitParallelGroundRail) { - return (firstPinId: PinId, secondPinId: PinId) => - !areSeparateExplicitGroundIslands(firstPinId, secondPinId) + return (firstPinId: PinId, secondPinId: PinId) => { + if (areSeparateExplicitGroundIslands(firstPinId, secondPinId)) { + return false + } + return !isLongGroundPinBankConnection({ + firstPinId, + secondPinId, + groundPinBanks, + pins, + }) + } } const isGroundFacingTerminal = (pinId: PinId) => { const entry = pins.get(pinId) @@ -92,6 +204,16 @@ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => { const firstGlobalNetId = netConnMap.getNetConnectedToId(firstPinId) const secondGlobalNetId = netConnMap.getNetConnectedToId(secondPinId) if (areSeparateExplicitGroundIslands(firstPinId, secondPinId)) return false + if ( + isLongGroundPinBankConnection({ + firstPinId, + secondPinId, + groundPinBanks, + pins, + }) + ) { + return false + } if ( !groundNetId || firstGlobalNetId !== groundNetId || diff --git a/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg b/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg index 3737bbb06..3009a6543 100644 --- a/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg +++ b/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg @@ -1,11 +1,12 @@ - XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXTPS61236RWLRU1J1J3J2J4 + " fill="rgba(255, 255, 255, 0.6)" stroke="rgb(132, 0, 0)" stroke-width="1.349507429788px"/>XXXXXXXTPS61236RWLRU1J1J3J2J4 \ No newline at end of file diff --git a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg index 0d9f448e7..10a249037 100644 --- a/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg +++ b/tests/repros/__snapshots__/repro-wireless-mcu-cc3235sf-full.snap.svg @@ -1,6 +1,6 @@ - U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51U2GPIO10GPIO11GPIO12GPIO13GPIO14GPIO15GPIO16GPIO17VDD_DIG1VIN_IO1FLASH_SPI_CLKFLASH_SPI_DOUTFLASH_SPI_DINFLASH_SPI_CSGPIO22JTAG_TDIJTAG_TDOGPIO28JTAG_TCKJTAG_TMSBOOT_SOP2WLAN_XTAL_NWLAN_XTAL_PVDD_PLLLDO_IN2NCRF_A_RXRF_A_TXGND1GND2RF_BGRESETVDD_PA_INBOOT_SOP1BOOT_SOP0LDO_IN1VIN_DCDC_ANADCDC_ANA_SWVIN_DCDC_PADCDC_PA_SW_PDCDC_PA_SW_NDCDC_PA_OUTDCDC_DIG_SWVIN_DCDC_DIGDCDC_ANA2_SW_PDCDC_ANA2_SW_NVDD_ANA2VDD_ANA1VDD_RAMGPIO0RTC_XTAL_PRTC_XTAL_NGPIO30VIN_IO2GPIO1VDD_DIG2GPIO2GPIO3GPIO4GPIO5GPIO6GPIO7GPIO8GPIO9GND_TABC1C2C3C4C5C6C7C8C9L1C12C13C14C15C16L2C17C18C19L3C20C21C22C29R5C23C24L4C25R1C10U1CSSOWPGNDSISCLKRESETVCCC11R2R3R4Y1C27C26Y2gnd1gnd2X1X2C28C30J1123R6R9J2123R7R10J3123R8R11TP11TP21TP31TP41TP51XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXCC3235SF12RGKRU2MX25R3235FM1IL0U1J1J2J3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + " fill="rgba(255, 255, 255, 0.6)" stroke="rgb(132, 0, 0)" stroke-width="0.769827879316px"/>XXCC3235SF12RGKRU2MX25R3235FM1IL0U1J1J2J3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ No newline at end of file diff --git a/tests/repros/repro-wireless-mcu-cc3235sf-full.test.ts b/tests/repros/repro-wireless-mcu-cc3235sf-full.test.ts index 672d726f0..cdf4cfffd 100644 --- a/tests/repros/repro-wireless-mcu-cc3235sf-full.test.ts +++ b/tests/repros/repro-wireless-mcu-cc3235sf-full.test.ts @@ -23,6 +23,13 @@ const TOP_CAPACITOR_RAIL_PIN_IDS = new Set([ "schematic_port_81", ]) +const U2_GROUND_PIN_IDS = new Set([ + "schematic_port_28", + "schematic_port_29", + "schematic_port_64", +]) +const MAX_LOCAL_GROUND_LABEL_SPAN = 0.5 + function getHorizontalRailYs(trace: SolvedTracePath): number[] { const railYs: number[] = [] for (let index = 1; index < trace.tracePath.length; index++) { @@ -86,5 +93,30 @@ test("repro WirelessMCU CC3235SF full schematic trace routing", () => { const nearbyGroundFeederRailY = getHorizontalRailYs(nearbyGroundFeeder!)[0]! expect(c1C2GroundRailY).toBeCloseTo(nearbyGroundFeederRailY) + const finalOutput = solver.netLabelToTraceSolver!.getOutput() + const groundNetId = inputProblem.netConnections.find( + (connection) => connection.isGround, + )!.netId + const u2GroundTraces = finalOutput.traces.filter((trace) => + trace.pinIds?.some((pinId) => U2_GROUND_PIN_IDS.has(pinId)), + ) + expect( + u2GroundTraces.every((trace) => + trace.pinIds?.every((pinId) => U2_GROUND_PIN_IDS.has(pinId)), + ), + ).toBe(true) + const u2GroundTraceXs = u2GroundTraces.flatMap((trace) => + trace.tracePath.map((point) => point.x), + ) + const u2GroundTraceSpan = + Math.max(...u2GroundTraceXs) - Math.min(...u2GroundTraceXs) + expect(u2GroundTraceSpan).toBeLessThanOrEqual(MAX_LOCAL_GROUND_LABEL_SPAN) + const u2GroundLabels = finalOutput.netLabelPlacements.filter( + (label) => + label.netId === groundNetId && + label.pinIds.some((pinId) => U2_GROUND_PIN_IDS.has(pinId)), + ) + expect(u2GroundLabels).toHaveLength(1) + expect(solver).toMatchSolverSnapshot(import.meta.path) }) diff --git a/tests/solvers/MspConnectionPairSolver/local-ground-branches.test.ts b/tests/solvers/MspConnectionPairSolver/local-ground-branches.test.ts index 48de03467..d5563804c 100644 --- a/tests/solvers/MspConnectionPairSolver/local-ground-branches.test.ts +++ b/tests/solvers/MspConnectionPairSolver/local-ground-branches.test.ts @@ -103,3 +103,33 @@ test("does not isolate side-facing or multi-pin components", () => { inputProblem.chips[2]!.pins.push({ pinId: "C.3", x: 2, y: -1.6 }) expect(getGroundConnectionPolicy(inputProblem)("B.2", "C.2")).toBe(true) }) + +test("keeps an IC ground pin bank local", () => { + const inputProblem = createParallelGroundRailProblem() + inputProblem.directConnections = [] + const chip = inputProblem.chips.find((chip) => chip.chipId === "U")! + chip.pins.push( + { pinId: "U.GND2", x: 2, y: 0.4, _facingDirection: "x-" }, + { pinId: "U.IO2", x: 2, y: 2.6, _facingDirection: "x-" }, + ) + inputProblem.netConnections[0]!.pinIds.push("U.GND2") + + const canRoute = getGroundConnectionPolicy(inputProblem) + expect(canRoute("U.GND", "U.GND2")).toBe(true) + expect(canRoute("U.GND", "C.2")).toBe(false) +}) + +test("preserves a connector-style ground bus", () => { + const inputProblem = createParallelGroundRailProblem() + inputProblem.directConnections = [] + const chip = inputProblem.chips.find((chip) => chip.chipId === "U")! + chip.pins.push({ + pinId: "U.GND2", + x: 2, + y: 0.4, + _facingDirection: "x-", + }) + inputProblem.netConnections[0]!.pinIds.push("U.GND2") + + expect(getGroundConnectionPolicy(inputProblem)("U.GND", "C.2")).toBe(true) +})