From 04ca332320c38ceed4527903a149301c7c442fcf Mon Sep 17 00:00:00 2001 From: ankan roy Date: Wed, 5 Aug 2026 05:37:21 +0530 Subject: [PATCH 01/11] Validate differential pairs are point to point --- .../primitive-components/DifferentialPair.ts | 18 ++ ...ialPair_doInitialSourceDesignRuleChecks.ts | 203 ++++++++++++++++++ .../getDifferentialPairsForSimpleRouteJson.ts | 157 +++++--------- .../getSimpleRouteJsonFromCircuitJson.ts | 2 + .../resolve-differential-pair-connection.ts | 165 ++++++++++++++ .../port-selector-multiple-traces.test.tsx | 29 ++- .../net-fragments-point-to-point.test.tsx | 55 +++++ ...point-to-point-error-circuit-json.test.tsx | 58 +++++ 8 files changed, 576 insertions(+), 111 deletions(-) create mode 100644 lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts create mode 100644 lib/utils/autorouting/resolve-differential-pair-connection.ts create mode 100644 tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx create mode 100644 tests/components/primitive-components/differential-pair/point-to-point-error-circuit-json.test.tsx diff --git a/lib/components/primitive-components/DifferentialPair.ts b/lib/components/primitive-components/DifferentialPair.ts index 72a06edb2..fafc23c1f 100644 --- a/lib/components/primitive-components/DifferentialPair.ts +++ b/lib/components/primitive-components/DifferentialPair.ts @@ -3,6 +3,10 @@ import { type BaseComponentConfig, PrimitiveComponent, } from "../base-components/PrimitiveComponent" +import { + DifferentialPair_doInitialSourceDesignRuleChecks, + DifferentialPair_removeSourceDesignRuleChecks, +} from "./DifferentialPair_doInitialSourceDesignRuleChecks" /** * Declares the routing constraints for a positive and negative trace pair. @@ -10,10 +14,24 @@ import { export class DifferentialPair extends PrimitiveComponent< typeof differentialPairProps > { + _pointToPointErrorIds: string[] = [] + override get config(): BaseComponentConfig { return { componentName: "DifferentialPair", zodProps: differentialPairProps, } } + + doInitialSourceDesignRuleChecks(): void { + DifferentialPair_doInitialSourceDesignRuleChecks(this) + } + + updateSourceDesignRuleChecks(): void { + DifferentialPair_doInitialSourceDesignRuleChecks(this) + } + + removeSourceDesignRuleChecks(): void { + DifferentialPair_removeSourceDesignRuleChecks(this) + } } diff --git a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts new file mode 100644 index 000000000..ebaa877ff --- /dev/null +++ b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts @@ -0,0 +1,203 @@ +import type { BaseCircuitJsonError, SourcePort } from "circuit-json" +import { + type ResolvedDifferentialPairConnection, + resolveDifferentialPairConnectionOrThrow, +} from "lib/utils/autorouting/resolve-differential-pair-connection" +import type { DifferentialPair } from "./DifferentialPair" + +type ConnectionPolarity = + SourceDifferentialPairNotPointToPointError["connection_polarity"] +type SourceComponentId = NonNullable + +/** + * Compatibility boundary for circuit-json#686. This can become an import from + * circuit-json after that PR is released. + */ +export interface SourceDifferentialPairNotPointToPointError + extends BaseCircuitJsonError { + type: "source_differential_pair_not_point_to_point_error" + source_differential_pair_not_point_to_point_error_id: string + error_type: "source_differential_pair_not_point_to_point_error" + subcircuit_id?: string + differential_pair_name?: string + connection_polarity: "positive" | "negative" + connection_selector: string + subcircuit_connectivity_map_key?: string + source_net_id?: string + connected_source_port_ids: string[] +} + +type PointToPointErrorTable = { + delete: (errorId: string) => void + insert: ( + error: Omit< + SourceDifferentialPairNotPointToPointError, + "type" | "source_differential_pair_not_point_to_point_error_id" + >, + ) => SourceDifferentialPairNotPointToPointError +} + +const getPointToPointErrorTable = ( + differentialPair: DifferentialPair, +): PointToPointErrorTable => + ( + differentialPair.root!.db as unknown as { + source_differential_pair_not_point_to_point_error: PointToPointErrorTable + } + ).source_differential_pair_not_point_to_point_error + +const formatTerminalPinList = (terminalPinSelectors: string[]): string => { + if (terminalPinSelectors.length <= 1) return terminalPinSelectors.join("") + if (terminalPinSelectors.length === 2) { + return `${terminalPinSelectors[0]} and ${terminalPinSelectors[1]}` + } + return `${terminalPinSelectors.slice(0, -1).join(", ")}, and ${terminalPinSelectors.at(-1)}` +} + +const getTerminalPinSelector = ( + sourcePort: SourcePort, + sourceComponentsById: Map, +): string => { + const sourceComponent = sourcePort.source_component_id + ? sourceComponentsById.get(sourcePort.source_component_id) + : undefined + if (!sourceComponent?.name) return sourcePort.source_port_id + + const portName = + sourcePort.most_frequently_referenced_by_name ?? sourcePort.name + return `.${sourceComponent.name} > .${portName}` +} + +const getPointToPointErrorMessage = ({ + differentialPairName, + connectionPolarity, + connectionSelector, + sourceNetName, + terminalPinSelectors, +}: { + differentialPairName: string + connectionPolarity: ConnectionPolarity + connectionSelector: string + sourceNetName?: string + terminalPinSelectors: string[] +}): string => { + const resolvedConnectionName = sourceNetName + ? `net.${sourceNetName}` + : `"${connectionSelector}"` + const terminalCount = terminalPinSelectors.length + const pinOrPins = terminalCount === 1 ? "pin" : "pins" + const terminalList = formatTerminalPinList(terminalPinSelectors) + const suggestedSelector = terminalPinSelectors[0] + const correction = + terminalCount > 2 + ? "Remove the extra connection" + : "Connect exactly two terminal pins" + const selectorRecommendation = suggestedSelector + ? ` and prefer a pin selector such as ${connectionPolarity}Connection="${suggestedSelector}"` + : "" + + return ( + `Differential pair "${differentialPairName}" ${connectionPolarity}Connection resolves to ${resolvedConnectionName}, which is not point-to-point. ` + + `It connects to ${terminalCount} ${pinOrPins}${terminalList ? `: ${terminalList}` : ""}. ` + + `${correction}${selectorRecommendation}.` + ) +} + +const removeStoredPointToPointErrors = ( + differentialPair: DifferentialPair, +): void => { + const errorTable = getPointToPointErrorTable(differentialPair) + for (const errorId of differentialPair._pointToPointErrorIds) { + errorTable.delete(errorId) + } + differentialPair._pointToPointErrorIds = [] +} + +export const DifferentialPair_doInitialSourceDesignRuleChecks = ( + differentialPair: DifferentialPair, +): void => { + const { db } = differentialPair.root! + removeStoredPointToPointErrors(differentialPair) + + const sourceTraces = db.source_trace.list() + const sourcePorts = db.source_port.list() + const sourceNets = db.source_net.list() + const pointToPointErrorTable = getPointToPointErrorTable(differentialPair) + const sourceComponentsById = new Map() + for (const sourceComponent of db.source_component.list()) { + sourceComponentsById.set(sourceComponent.source_component_id, { + name: sourceComponent.name, + }) + } + + for (const connectionPolarity of ["positive", "negative"] as const) { + const connectionSelector = + connectionPolarity === "positive" + ? differentialPair._parsedProps.positiveConnection + : differentialPair._parsedProps.negativeConnection + + let resolvedConnection: ResolvedDifferentialPairConnection + try { + resolvedConnection = resolveDifferentialPairConnectionOrThrow({ + differentialPair, + traceNameOrPortSelector: connectionSelector, + sourceTraces, + sourcePorts, + sourceNets, + }) + } catch { + // Missing and unrelated ambiguous references retain their existing SRJ + // diagnostics. This DRC is specifically for resolved connectivity groups. + continue + } + + const terminalSourcePorts = [ + ...new Map( + resolvedConnection.sourcePorts.map((sourcePort) => [ + sourcePort.source_port_id, + sourcePort, + ]), + ).values(), + ] + if (terminalSourcePorts.length === 2) continue + + const terminalPinSelectors = terminalSourcePorts + .map((sourcePort) => + getTerminalPinSelector(sourcePort, sourceComponentsById), + ) + .sort((selectorA, selectorB) => selectorA.localeCompare(selectorB)) + const insertedError = pointToPointErrorTable.insert({ + error_type: "source_differential_pair_not_point_to_point_error", + is_fatal: true, + message: getPointToPointErrorMessage({ + differentialPairName: differentialPair.name, + connectionPolarity, + connectionSelector, + sourceNetName: resolvedConnection.sourceNet?.name, + terminalPinSelectors, + }), + subcircuit_id: + differentialPair.getSubcircuit().subcircuit_id ?? undefined, + differential_pair_name: differentialPair.name, + connection_polarity: connectionPolarity, + connection_selector: connectionSelector, + subcircuit_connectivity_map_key: + resolvedConnection.subcircuitConnectivityMapKey, + source_net_id: resolvedConnection.sourceNet?.source_net_id, + connected_source_port_ids: terminalSourcePorts + .map((sourcePort) => sourcePort.source_port_id) + .sort((sourcePortIdA, sourcePortIdB) => + sourcePortIdA.localeCompare(sourcePortIdB), + ), + }) + differentialPair._pointToPointErrorIds.push( + insertedError.source_differential_pair_not_point_to_point_error_id, + ) + } +} + +export const DifferentialPair_removeSourceDesignRuleChecks = ( + differentialPair: DifferentialPair, +): void => { + removeStoredPointToPointErrors(differentialPair) +} diff --git a/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts b/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts index 72bb359f1..b2b999a40 100644 --- a/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts +++ b/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts @@ -1,32 +1,28 @@ -import type { SourcePort, SourceTrace } from "circuit-json" +import type { SourceNet, SourcePort, SourceTrace } from "circuit-json" import type { DifferentialPair } from "lib/components/primitive-components/DifferentialPair" -import type { Port } from "lib/components/primitive-components/Port/Port" import type { SimpleRouteConnection, SimpleRouteDifferentialPair, SrjConnectionName, } from "./SimpleRouteJson" +import { + type SourceNetId, + type SubcircuitConnectivityMapKey, + type SubcircuitId, + resolveDifferentialPairConnectionOrThrow, +} from "./resolve-differential-pair-connection" type SourceTraceId = SourceTrace["source_trace_id"] -type SourcePortId = NonNullable -type SubcircuitId = NonNullable -type SubcircuitConnectivityMapKey = NonNullable< - SourceTrace["subcircuit_connectivity_map_key"] -> type GetDifferentialPairsParams = { srjConnections: SimpleRouteConnection[] differentialPairs: DifferentialPair[] sourceTraces: SourceTrace[] + sourcePorts: SourcePort[] + sourceNets: SourceNet[] subcircuitId?: SubcircuitId | null } -type GetDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrowParams = { - differentialPair: DifferentialPair - differentialPairSourceTraces: SourceTrace[] - traceNameOrPortSelector: string -} - type GetDifferentialPairSrjConnectionNameOrThrowParams = { srjConnections: SimpleRouteConnection[] differentialPairName: string @@ -35,77 +31,6 @@ type GetDifferentialPairSrjConnectionNameOrThrowParams = { traceNameOrPortSelector: string } -const getDifferentialPairSourceTracesByTraceName = ( - differentialPairSourceTraces: SourceTrace[], - traceName: string, -): SourceTrace[] => - differentialPairSourceTraces.filter( - (sourceTrace) => sourceTrace.name === traceName, - ) - -const getDifferentialPairSourceTracesByPortId = ( - differentialPairSourceTraces: SourceTrace[], - sourcePortId: SourcePortId, -): SourceTrace[] => - differentialPairSourceTraces.filter((sourceTrace) => - sourceTrace.connected_source_port_ids.includes(sourcePortId), - ) - -const getDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrow = ({ - differentialPair, - differentialPairSourceTraces, - traceNameOrPortSelector, -}: GetDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrowParams): SubcircuitConnectivityMapKey => { - const differentialPairSubcircuit = differentialPair.getSubcircuit() - const sourceTracesWithMatchingName = - getDifferentialPairSourceTracesByTraceName( - differentialPairSourceTraces, - traceNameOrPortSelector, - ) - const selectedPort = - sourceTracesWithMatchingName.length === 0 - ? differentialPairSubcircuit.selectOne(traceNameOrPortSelector, { - type: "port", - }) - : null - const selectedSourcePortId: SourcePortId | undefined = - selectedPort?.source_port_id ?? undefined - const matchingSourceTraces = selectedSourcePortId - ? getDifferentialPairSourceTracesByPortId( - differentialPairSourceTraces, - selectedSourcePortId, - ) - : sourceTracesWithMatchingName - - if (matchingSourceTraces.length === 0) { - throw new Error( - `Could not find source trace for trace name or port selector "${traceNameOrPortSelector}" in differential pair "${differentialPair.name}"`, - ) - } - if (matchingSourceTraces.length > 1) { - throw new Error( - `Trace name or port selector "${traceNameOrPortSelector}" matches multiple source traces for differential pair "${differentialPair.name}"`, - ) - } - - const sourceTrace = matchingSourceTraces[0] - if (!sourceTrace) { - throw new Error( - `Expected one source trace for trace name or port selector "${traceNameOrPortSelector}" in differential pair "${differentialPair.name}"`, - ) - } - - const subcircuitConnectivityMapKey = - sourceTrace.subcircuit_connectivity_map_key - if (!subcircuitConnectivityMapKey) { - throw new Error( - `Source trace "${sourceTrace.source_trace_id}" does not have a subcircuit connectivity map key for differential pair "${differentialPair.name}"`, - ) - } - - return subcircuitConnectivityMapKey -} - const getDifferentialPairSrjConnectionNameOrThrow = ({ srjConnections, differentialPairName, @@ -114,24 +39,39 @@ const getDifferentialPairSrjConnectionNameOrThrow = ({ traceNameOrPortSelector, }: GetDifferentialPairSrjConnectionNameOrThrowParams): SrjConnectionName => { const differentialPairSourceTraceIds: SourceTraceId[] = [] + const differentialPairSourceNetIds = new Set() for (const sourceTrace of differentialPairSourceTraces) { if ( sourceTrace.subcircuit_connectivity_map_key === traceSubcircuitConnectivityMapKey ) { differentialPairSourceTraceIds.push(sourceTrace.source_trace_id) + for (const sourceNetId of sourceTrace.connected_source_net_ids) { + differentialPairSourceNetIds.add(sourceNetId) + } } } - const matchingSrjConnections: SimpleRouteConnection[] = [] + const matchingTraceSrjConnections: SimpleRouteConnection[] = [] for (const srjConnection of srjConnections) { if ( - srjConnection.source_trace_id && - differentialPairSourceTraceIds.includes(srjConnection.source_trace_id) + (srjConnection.source_trace_id && + differentialPairSourceTraceIds.includes( + srjConnection.source_trace_id, + )) || + srjConnection.mergedConnectionNames?.some((sourceTraceId) => + differentialPairSourceTraceIds.includes(sourceTraceId), + ) ) { - matchingSrjConnections.push(srjConnection) + matchingTraceSrjConnections.push(srjConnection) } } + const matchingSrjConnections = + matchingTraceSrjConnections.length > 0 + ? matchingTraceSrjConnections + : srjConnections.filter((srjConnection) => + differentialPairSourceNetIds.has(srjConnection.name), + ) if (matchingSrjConnections.length === 0) { throw new Error( @@ -159,6 +99,8 @@ export const getDifferentialPairsForSimpleRouteJson = ({ srjConnections, differentialPairs, sourceTraces, + sourcePorts, + sourceNets, subcircuitId, }: GetDifferentialPairsParams): SimpleRouteDifferentialPair[] | undefined => { const srjDifferentialPairs: SimpleRouteDifferentialPair[] = [] @@ -177,25 +119,35 @@ export const getDifferentialPairsForSimpleRouteJson = ({ differentialPair._parsedProps.positiveConnection const negativeTraceNameOrPortSelector = differentialPair._parsedProps.negativeConnection - const positiveSubcircuitConnectivityMapKey = - getDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrow({ - differentialPair, - differentialPairSourceTraces, - traceNameOrPortSelector: positiveTraceNameOrPortSelector, - }) - const negativeSubcircuitConnectivityMapKey = - getDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrow({ - differentialPair, - differentialPairSourceTraces, - traceNameOrPortSelector: negativeTraceNameOrPortSelector, - }) + const positiveConnection = resolveDifferentialPairConnectionOrThrow({ + differentialPair, + traceNameOrPortSelector: positiveTraceNameOrPortSelector, + sourceTraces, + sourcePorts, + sourceNets, + }) + const negativeConnection = resolveDifferentialPairConnectionOrThrow({ + differentialPair, + traceNameOrPortSelector: negativeTraceNameOrPortSelector, + sourceTraces, + sourcePorts, + sourceNets, + }) + + if ( + positiveConnection.sourcePorts.length !== 2 || + negativeConnection.sourcePorts.length !== 2 + ) { + continue + } const positiveSrjConnectionName = getDifferentialPairSrjConnectionNameOrThrow({ srjConnections, differentialPairName: differentialPair.name, differentialPairSourceTraces, - traceSubcircuitConnectivityMapKey: positiveSubcircuitConnectivityMapKey, + traceSubcircuitConnectivityMapKey: + positiveConnection.subcircuitConnectivityMapKey, traceNameOrPortSelector: positiveTraceNameOrPortSelector, }) const negativeSrjConnectionName = @@ -203,7 +155,8 @@ export const getDifferentialPairsForSimpleRouteJson = ({ srjConnections, differentialPairName: differentialPair.name, differentialPairSourceTraces, - traceSubcircuitConnectivityMapKey: negativeSubcircuitConnectivityMapKey, + traceSubcircuitConnectivityMapKey: + negativeConnection.subcircuitConnectivityMapKey, traceNameOrPortSelector: negativeTraceNameOrPortSelector, }) diff --git a/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts b/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts index 3bf41856a..b220a999a 100644 --- a/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts +++ b/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts @@ -731,6 +731,8 @@ export const getSimpleRouteJsonFromCircuitJson = ({ srjConnections: allConns, differentialPairs, sourceTraces: db.source_trace.list(), + sourcePorts: db.source_port.list(), + sourceNets: db.source_net.list(), subcircuitId: subcircuit_id, }) diff --git a/lib/utils/autorouting/resolve-differential-pair-connection.ts b/lib/utils/autorouting/resolve-differential-pair-connection.ts new file mode 100644 index 000000000..fdaf28d26 --- /dev/null +++ b/lib/utils/autorouting/resolve-differential-pair-connection.ts @@ -0,0 +1,165 @@ +import type { SourceNet, SourcePort, SourceTrace } from "circuit-json" +import type { DifferentialPair } from "lib/components/primitive-components/DifferentialPair" +import type { Port } from "lib/components/primitive-components/Port/Port" + +export type SourcePortId = SourcePort["source_port_id"] +export type SourceNetId = SourceNet["source_net_id"] +export type SubcircuitId = NonNullable +export type SubcircuitConnectivityMapKey = NonNullable< + SourceTrace["subcircuit_connectivity_map_key"] +> + +export type ResolvedDifferentialPairConnection = { + subcircuitConnectivityMapKey: SubcircuitConnectivityMapKey + sourceTraces: SourceTrace[] + sourcePorts: SourcePort[] + sourceNet?: SourceNet +} + +type ResolveDifferentialPairConnectionParams = { + differentialPair: DifferentialPair + traceNameOrPortSelector: string + sourceTraces: SourceTrace[] + sourcePorts: SourcePort[] + sourceNets: SourceNet[] +} + +const getDifferentialPairSourceTracesByTraceName = ( + differentialPairSourceTraces: SourceTrace[], + traceName: string, +): SourceTrace[] => + differentialPairSourceTraces.filter( + (sourceTrace) => sourceTrace.name === traceName, + ) + +const getDifferentialPairSourceTracesByPortId = ( + differentialPairSourceTraces: SourceTrace[], + sourcePortId: SourcePortId, +): SourceTrace[] => + differentialPairSourceTraces.filter((sourceTrace) => + sourceTrace.connected_source_port_ids.includes(sourcePortId), + ) + +/** + * Resolves one differential-pair conductor to its complete source-connectivity + * group. Multiple source-trace fragments are valid when they all belong to the + * same group (for example two traces joined through a source net). + */ +export const resolveDifferentialPairConnectionOrThrow = ({ + differentialPair, + traceNameOrPortSelector, + sourceTraces, + sourcePorts, + sourceNets, +}: ResolveDifferentialPairConnectionParams): ResolvedDifferentialPairConnection => { + const differentialPairSubcircuitId = + differentialPair.getSubcircuit().subcircuit_id + const differentialPairSourceTraces = sourceTraces.filter( + (sourceTrace) => sourceTrace.subcircuit_id === differentialPairSubcircuitId, + ) + const sourceTracesWithMatchingName = + getDifferentialPairSourceTracesByTraceName( + differentialPairSourceTraces, + traceNameOrPortSelector, + ) + const selectedPort = + sourceTracesWithMatchingName.length === 0 + ? differentialPair + .getSubcircuit() + .selectOne(traceNameOrPortSelector, { type: "port" }) + : null + const selectedSourcePortId = selectedPort?.source_port_id ?? undefined + const matchingSourceTraces = selectedSourcePortId + ? getDifferentialPairSourceTracesByPortId( + differentialPairSourceTraces, + selectedSourcePortId, + ) + : sourceTracesWithMatchingName + + if (matchingSourceTraces.length === 0) { + throw new Error( + `Could not find source trace for trace name or port selector "${traceNameOrPortSelector}" in differential pair "${differentialPair.name}"`, + ) + } + + const subcircuitConnectivityMapKeys = new Set( + matchingSourceTraces.flatMap((sourceTrace) => + sourceTrace.subcircuit_connectivity_map_key + ? [sourceTrace.subcircuit_connectivity_map_key] + : [], + ), + ) + if (subcircuitConnectivityMapKeys.size > 1) { + throw new Error( + `Trace name or port selector "${traceNameOrPortSelector}" matches multiple source traces for differential pair "${differentialPair.name}"`, + ) + } + + const sourceTraceWithoutConnectivityMapKey = matchingSourceTraces.find( + (sourceTrace) => !sourceTrace.subcircuit_connectivity_map_key, + ) + if (sourceTraceWithoutConnectivityMapKey) { + throw new Error( + `Source trace "${sourceTraceWithoutConnectivityMapKey.source_trace_id}" does not have a subcircuit connectivity map key for differential pair "${differentialPair.name}"`, + ) + } + + const subcircuitConnectivityMapKey = subcircuitConnectivityMapKeys + .values() + .next().value + if (!subcircuitConnectivityMapKey) { + throw new Error( + `Expected a subcircuit connectivity map key for trace name or port selector "${traceNameOrPortSelector}" in differential pair "${differentialPair.name}"`, + ) + } + + const connectivityGroupSourceTraces = differentialPairSourceTraces.filter( + (sourceTrace) => + sourceTrace.subcircuit_connectivity_map_key === + subcircuitConnectivityMapKey, + ) + const terminalSourcePortIds = new Set( + connectivityGroupSourceTraces.flatMap( + (sourceTrace) => sourceTrace.connected_source_port_ids, + ), + ) + const sourcePortsById = new Map( + sourcePorts.map((sourcePort) => [sourcePort.source_port_id, sourcePort]), + ) + const connectivityGroupSourcePorts = [...terminalSourcePortIds] + .sort((sourcePortIdA, sourcePortIdB) => + sourcePortIdA.localeCompare(sourcePortIdB), + ) + .flatMap((sourcePortId) => { + const sourcePort = sourcePortsById.get(sourcePortId) + return sourcePort ? [sourcePort] : [] + }) + + const directlyReferencedSourceNetIds = matchingSourceTraces.flatMap( + (sourceTrace) => sourceTrace.connected_source_net_ids, + ) + const connectivityGroupSourceNetIds = connectivityGroupSourceTraces.flatMap( + (sourceTrace) => sourceTrace.connected_source_net_ids, + ) + const orderedSourceNetIds = [ + ...new Set([ + ...directlyReferencedSourceNetIds, + ...connectivityGroupSourceNetIds.sort((sourceNetIdA, sourceNetIdB) => + sourceNetIdA.localeCompare(sourceNetIdB), + ), + ]), + ] + const sourceNetsById = new Map( + sourceNets.map((sourceNet) => [sourceNet.source_net_id, sourceNet]), + ) + + return { + subcircuitConnectivityMapKey, + sourceTraces: connectivityGroupSourceTraces, + sourcePorts: connectivityGroupSourcePorts, + sourceNet: orderedSourceNetIds.flatMap((sourceNetId) => { + const sourceNet = sourceNetsById.get(sourceNetId) + return sourceNet ? [sourceNet] : [] + })[0], + } +} diff --git a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx index 2805c5906..27327ae03 100644 --- a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx +++ b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx @@ -1,8 +1,9 @@ import { expect, it } from "bun:test" +import type { SourceDifferentialPairNotPointToPointError } from "lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks" import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" import { getTestFixture } from "tests/fixtures/get-test-fixture" -it("throws when a differential pair port selector matches multiple traces", (): void => { +it("stores an error when a differential pair port selector resolves to a branch", (): void => { const { circuit } = getTestFixture() circuit.add( @@ -36,12 +37,22 @@ it("throws when a differential pair port selector matches multiple traces", (): throw new Error("Expected the circuit to contain a board") } - expect((): void => { - getSimpleRouteJsonFromCircuitJson({ - circuitJson: circuit.getCircuitJson(), - subcircuitComponent: boardSubcircuit, - }) - }).toThrow( - 'Trace name or port selector ".R1 > .pin1" matches multiple source traces for differential pair "USB"', - ) + const pointToPointError = ( + circuit.getCircuitJson() as unknown as Array<{ type: string }> + ).find( + (element) => + element.type === "source_differential_pair_not_point_to_point_error", + ) as SourceDifferentialPairNotPointToPointError | undefined + expect(pointToPointError).toMatchObject({ + differential_pair_name: "USB", + connection_polarity: "positive", + connection_selector: ".R1 > .pin1", + }) + expect(pointToPointError?.connected_source_port_ids).toHaveLength(3) + + const { simpleRouteJson } = getSimpleRouteJsonFromCircuitJson({ + circuitJson: circuit.getCircuitJson(), + subcircuitComponent: boardSubcircuit, + }) + expect(simpleRouteJson.differentialPairs).toBeUndefined() }) diff --git a/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx b/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx new file mode 100644 index 000000000..c813e6c89 --- /dev/null +++ b/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx @@ -0,0 +1,55 @@ +import { expect, test } from "bun:test" +import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("accepts a two-terminal differential conductor split through a source net", (): void => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + + , + ) + + circuit.render() + + expect( + (circuit.getCircuitJson() as unknown as Array<{ type: string }>).filter( + (element) => + element.type === "source_differential_pair_not_point_to_point_error", + ), + ).toEqual([]) + + const boardSubcircuit = circuit.firstChild + if (!boardSubcircuit) throw new Error("Expected a board subcircuit") + + const { simpleRouteJson } = getSimpleRouteJsonFromCircuitJson({ + circuitJson: circuit.getCircuitJson(), + subcircuitComponent: boardSubcircuit, + }) + const dpSourceNet = circuit.db.source_net.getWhere({ name: "DP" }) + const dmSourceTrace = circuit.db.source_trace.getWhere({ name: "DM" }) + if (!dpSourceNet || !dmSourceTrace) { + throw new Error("Expected DP source net and DM source trace") + } + + expect(simpleRouteJson.differentialPairs).toEqual([ + { + connectionNames: [ + dpSourceNet.source_net_id, + dmSourceTrace.source_trace_id, + ], + lengthTolerance: 0.05, + }, + ]) +}) diff --git a/tests/components/primitive-components/differential-pair/point-to-point-error-circuit-json.test.tsx b/tests/components/primitive-components/differential-pair/point-to-point-error-circuit-json.test.tsx new file mode 100644 index 000000000..5964a3565 --- /dev/null +++ b/tests/components/primitive-components/differential-pair/point-to-point-error-circuit-json.test.tsx @@ -0,0 +1,58 @@ +import { expect, test } from "bun:test" +import type { SourceDifferentialPairNotPointToPointError } from "lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks" +import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("stores a Circuit JSON error and omits a branched differential pair from SRJ", (): void => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + + + + , + ) + + circuit.render() + + const pointToPointErrors = ( + circuit.getCircuitJson() as unknown as Array<{ type: string }> + ).filter( + (element) => + element.type === "source_differential_pair_not_point_to_point_error", + ) as SourceDifferentialPairNotPointToPointError[] + expect(pointToPointErrors).toHaveLength(1) + expect(pointToPointErrors[0]).toMatchObject({ + type: "source_differential_pair_not_point_to_point_error", + error_type: "source_differential_pair_not_point_to_point_error", + is_fatal: true, + differential_pair_name: "USB_DATA", + connection_polarity: "positive", + connection_selector: "DP_FROM_J1", + }) + expect(pointToPointErrors[0]?.connected_source_port_ids).toHaveLength(3) + expect(pointToPointErrors[0]?.source_net_id).toBeDefined() + expect(pointToPointErrors[0]?.subcircuit_connectivity_map_key).toBeDefined() + expect(pointToPointErrors[0]?.message).toBe( + 'Differential pair "USB_DATA" positiveConnection resolves to net.DP, which is not point-to-point. It connects to 3 pins: .J1 > .pin1, .TP1 > .pin1, and .U1 > .pin1. Remove the extra connection and prefer a pin selector such as positiveConnection=".J1 > .pin1".', + ) + + const boardSubcircuit = circuit.firstChild + if (!boardSubcircuit) throw new Error("Expected a board subcircuit") + + const { simpleRouteJson } = getSimpleRouteJsonFromCircuitJson({ + circuitJson: circuit.getCircuitJson(), + subcircuitComponent: boardSubcircuit, + }) + expect(simpleRouteJson.differentialPairs).toBeUndefined() +}) From 7527d5896f0a9661ed477db8c73009c1e17db02a Mon Sep 17 00:00:00 2001 From: ankan roy Date: Wed, 5 Aug 2026 06:07:58 +0530 Subject: [PATCH 02/11] Use property warning for branched differential pairs --- .../primitive-components/DifferentialPair.ts | 2 +- ...ialPair_doInitialSourceDesignRuleChecks.ts | 85 +++++-------------- .../port-selector-multiple-traces.test.tsx | 25 +++--- .../net-fragments-point-to-point.test.tsx | 11 ++- ...nt-to-point-warning-circuit-json.test.tsx} | 29 ++----- 5 files changed, 48 insertions(+), 104 deletions(-) rename tests/components/primitive-components/differential-pair/{point-to-point-error-circuit-json.test.tsx => point-to-point-warning-circuit-json.test.tsx} (57%) diff --git a/lib/components/primitive-components/DifferentialPair.ts b/lib/components/primitive-components/DifferentialPair.ts index fafc23c1f..bd41a15d5 100644 --- a/lib/components/primitive-components/DifferentialPair.ts +++ b/lib/components/primitive-components/DifferentialPair.ts @@ -14,7 +14,7 @@ import { export class DifferentialPair extends PrimitiveComponent< typeof differentialPairProps > { - _pointToPointErrorIds: string[] = [] + _pointToPointWarningIds: string[] = [] override get config(): BaseComponentConfig { return { diff --git a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts index ebaa877ff..a9a3afebd 100644 --- a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts +++ b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts @@ -1,51 +1,13 @@ -import type { BaseCircuitJsonError, SourcePort } from "circuit-json" +import type { SourcePort } from "circuit-json" import { type ResolvedDifferentialPairConnection, resolveDifferentialPairConnectionOrThrow, } from "lib/utils/autorouting/resolve-differential-pair-connection" import type { DifferentialPair } from "./DifferentialPair" -type ConnectionPolarity = - SourceDifferentialPairNotPointToPointError["connection_polarity"] +type ConnectionPolarity = "positive" | "negative" type SourceComponentId = NonNullable -/** - * Compatibility boundary for circuit-json#686. This can become an import from - * circuit-json after that PR is released. - */ -export interface SourceDifferentialPairNotPointToPointError - extends BaseCircuitJsonError { - type: "source_differential_pair_not_point_to_point_error" - source_differential_pair_not_point_to_point_error_id: string - error_type: "source_differential_pair_not_point_to_point_error" - subcircuit_id?: string - differential_pair_name?: string - connection_polarity: "positive" | "negative" - connection_selector: string - subcircuit_connectivity_map_key?: string - source_net_id?: string - connected_source_port_ids: string[] -} - -type PointToPointErrorTable = { - delete: (errorId: string) => void - insert: ( - error: Omit< - SourceDifferentialPairNotPointToPointError, - "type" | "source_differential_pair_not_point_to_point_error_id" - >, - ) => SourceDifferentialPairNotPointToPointError -} - -const getPointToPointErrorTable = ( - differentialPair: DifferentialPair, -): PointToPointErrorTable => - ( - differentialPair.root!.db as unknown as { - source_differential_pair_not_point_to_point_error: PointToPointErrorTable - } - ).source_differential_pair_not_point_to_point_error - const formatTerminalPinList = (terminalPinSelectors: string[]): string => { if (terminalPinSelectors.length <= 1) return terminalPinSelectors.join("") if (terminalPinSelectors.length === 2) { @@ -68,7 +30,7 @@ const getTerminalPinSelector = ( return `.${sourceComponent.name} > .${portName}` } -const getPointToPointErrorMessage = ({ +const getPointToPointWarningMessage = ({ differentialPairName, connectionPolarity, connectionSelector, @@ -103,26 +65,25 @@ const getPointToPointErrorMessage = ({ ) } -const removeStoredPointToPointErrors = ( +const removeStoredPointToPointWarnings = ( differentialPair: DifferentialPair, ): void => { - const errorTable = getPointToPointErrorTable(differentialPair) - for (const errorId of differentialPair._pointToPointErrorIds) { - errorTable.delete(errorId) + const warningTable = differentialPair.root!.db.source_property_ignored_warning + for (const warningId of differentialPair._pointToPointWarningIds) { + warningTable.delete(warningId) } - differentialPair._pointToPointErrorIds = [] + differentialPair._pointToPointWarningIds = [] } export const DifferentialPair_doInitialSourceDesignRuleChecks = ( differentialPair: DifferentialPair, ): void => { const { db } = differentialPair.root! - removeStoredPointToPointErrors(differentialPair) + removeStoredPointToPointWarnings(differentialPair) const sourceTraces = db.source_trace.list() const sourcePorts = db.source_port.list() const sourceNets = db.source_net.list() - const pointToPointErrorTable = getPointToPointErrorTable(differentialPair) const sourceComponentsById = new Map() for (const sourceComponent of db.source_component.list()) { sourceComponentsById.set(sourceComponent.source_component_id, { @@ -160,16 +121,19 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( ).values(), ] if (terminalSourcePorts.length === 2) continue + const warningSourceComponentId = terminalSourcePorts[0]?.source_component_id + if (!warningSourceComponentId) continue const terminalPinSelectors = terminalSourcePorts .map((sourcePort) => getTerminalPinSelector(sourcePort, sourceComponentsById), ) .sort((selectorA, selectorB) => selectorA.localeCompare(selectorB)) - const insertedError = pointToPointErrorTable.insert({ - error_type: "source_differential_pair_not_point_to_point_error", - is_fatal: true, - message: getPointToPointErrorMessage({ + const insertedWarning = db.source_property_ignored_warning.insert({ + source_component_id: warningSourceComponentId, + property_name: `${connectionPolarity}Connection`, + error_type: "source_property_ignored_warning", + message: getPointToPointWarningMessage({ differentialPairName: differentialPair.name, connectionPolarity, connectionSelector, @@ -178,20 +142,9 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( }), subcircuit_id: differentialPair.getSubcircuit().subcircuit_id ?? undefined, - differential_pair_name: differentialPair.name, - connection_polarity: connectionPolarity, - connection_selector: connectionSelector, - subcircuit_connectivity_map_key: - resolvedConnection.subcircuitConnectivityMapKey, - source_net_id: resolvedConnection.sourceNet?.source_net_id, - connected_source_port_ids: terminalSourcePorts - .map((sourcePort) => sourcePort.source_port_id) - .sort((sourcePortIdA, sourcePortIdB) => - sourcePortIdA.localeCompare(sourcePortIdB), - ), }) - differentialPair._pointToPointErrorIds.push( - insertedError.source_differential_pair_not_point_to_point_error_id, + differentialPair._pointToPointWarningIds.push( + insertedWarning.source_property_ignored_warning_id, ) } } @@ -199,5 +152,5 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( export const DifferentialPair_removeSourceDesignRuleChecks = ( differentialPair: DifferentialPair, ): void => { - removeStoredPointToPointErrors(differentialPair) + removeStoredPointToPointWarnings(differentialPair) } diff --git a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx index 27327ae03..f9ee245a1 100644 --- a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx +++ b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx @@ -1,9 +1,8 @@ import { expect, it } from "bun:test" -import type { SourceDifferentialPairNotPointToPointError } from "lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks" import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" import { getTestFixture } from "tests/fixtures/get-test-fixture" -it("stores an error when a differential pair port selector resolves to a branch", (): void => { +it("stores a property warning when a differential pair port selector resolves to a branch", (): void => { const { circuit } = getTestFixture() circuit.add( @@ -37,18 +36,18 @@ it("stores an error when a differential pair port selector resolves to a branch" throw new Error("Expected the circuit to contain a board") } - const pointToPointError = ( - circuit.getCircuitJson() as unknown as Array<{ type: string }> - ).find( - (element) => - element.type === "source_differential_pair_not_point_to_point_error", - ) as SourceDifferentialPairNotPointToPointError | undefined - expect(pointToPointError).toMatchObject({ - differential_pair_name: "USB", - connection_polarity: "positive", - connection_selector: ".R1 > .pin1", + const pointToPointWarning = + circuit.db.source_property_ignored_warning.getWhere({ + property_name: "positiveConnection", + }) + expect(pointToPointWarning).toMatchObject({ + type: "source_property_ignored_warning", + error_type: "source_property_ignored_warning", + property_name: "positiveConnection", }) - expect(pointToPointError?.connected_source_port_ids).toHaveLength(3) + expect(pointToPointWarning?.message).toContain( + 'positiveConnection resolves to ".R1 > .pin1", which is not point-to-point', + ) const { simpleRouteJson } = getSimpleRouteJsonFromCircuitJson({ circuitJson: circuit.getCircuitJson(), diff --git a/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx b/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx index c813e6c89..29612ba05 100644 --- a/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx +++ b/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx @@ -24,10 +24,13 @@ test("accepts a two-terminal differential conductor split through a source net", circuit.render() expect( - (circuit.getCircuitJson() as unknown as Array<{ type: string }>).filter( - (element) => - element.type === "source_differential_pair_not_point_to_point_error", - ), + circuit.db.source_property_ignored_warning + .list() + .filter( + (warning) => + warning.property_name === "positiveConnection" || + warning.property_name === "negativeConnection", + ), ).toEqual([]) const boardSubcircuit = circuit.firstChild diff --git a/tests/components/primitive-components/differential-pair/point-to-point-error-circuit-json.test.tsx b/tests/components/primitive-components/differential-pair/point-to-point-warning-circuit-json.test.tsx similarity index 57% rename from tests/components/primitive-components/differential-pair/point-to-point-error-circuit-json.test.tsx rename to tests/components/primitive-components/differential-pair/point-to-point-warning-circuit-json.test.tsx index 5964a3565..5a7e353b1 100644 --- a/tests/components/primitive-components/differential-pair/point-to-point-error-circuit-json.test.tsx +++ b/tests/components/primitive-components/differential-pair/point-to-point-warning-circuit-json.test.tsx @@ -1,9 +1,8 @@ import { expect, test } from "bun:test" -import type { SourceDifferentialPairNotPointToPointError } from "lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks" import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" import { getTestFixture } from "tests/fixtures/get-test-fixture" -test("stores a Circuit JSON error and omits a branched differential pair from SRJ", (): void => { +test("stores a property warning and omits a branched differential pair from SRJ", (): void => { const { circuit } = getTestFixture() circuit.add( @@ -25,25 +24,15 @@ test("stores a Circuit JSON error and omits a branched differential pair from SR circuit.render() - const pointToPointErrors = ( - circuit.getCircuitJson() as unknown as Array<{ type: string }> - ).filter( - (element) => - element.type === "source_differential_pair_not_point_to_point_error", - ) as SourceDifferentialPairNotPointToPointError[] - expect(pointToPointErrors).toHaveLength(1) - expect(pointToPointErrors[0]).toMatchObject({ - type: "source_differential_pair_not_point_to_point_error", - error_type: "source_differential_pair_not_point_to_point_error", - is_fatal: true, - differential_pair_name: "USB_DATA", - connection_polarity: "positive", - connection_selector: "DP_FROM_J1", + const pointToPointWarnings = circuit.db.source_property_ignored_warning.list() + expect(pointToPointWarnings).toHaveLength(1) + expect(pointToPointWarnings[0]).toMatchObject({ + type: "source_property_ignored_warning", + error_type: "source_property_ignored_warning", + property_name: "positiveConnection", }) - expect(pointToPointErrors[0]?.connected_source_port_ids).toHaveLength(3) - expect(pointToPointErrors[0]?.source_net_id).toBeDefined() - expect(pointToPointErrors[0]?.subcircuit_connectivity_map_key).toBeDefined() - expect(pointToPointErrors[0]?.message).toBe( + expect(pointToPointWarnings[0]?.source_component_id).toBeDefined() + expect(pointToPointWarnings[0]?.message).toBe( 'Differential pair "USB_DATA" positiveConnection resolves to net.DP, which is not point-to-point. It connects to 3 pins: .J1 > .pin1, .TP1 > .pin1, and .U1 > .pin1. Remove the extra connection and prefer a pin selector such as positiveConnection=".J1 > .pin1".', ) From a2e162f885cd4885cb612e7db37b663318c25c29 Mon Sep 17 00:00:00 2001 From: ankan roy Date: Wed, 5 Aug 2026 06:19:17 +0530 Subject: [PATCH 03/11] Use direct CAD model asset in snapshot test --- tests/examples/example38-cad-model-placement.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/examples/example38-cad-model-placement.test.tsx b/tests/examples/example38-cad-model-placement.test.tsx index c45ce24c8..d1be3980e 100644 --- a/tests/examples/example38-cad-model-placement.test.tsx +++ b/tests/examples/example38-cad-model-placement.test.tsx @@ -919,7 +919,7 @@ test("example38 cad model placement snapshot", async () => { } cadModel={{ objUrl: - "https://modelcdn.tscircuit.com/easyeda_models/download?uuid=4e90b6d8552a4e058d9ebe9d82e11f3a&pn=C9900017879", + "https://modelcdn.tscircuit.com/easyeda_models/assets/C9900017879.obj?uuid=4e90b6d8552a4e058d9ebe9d82e11f3a", modelOriginPosition: { x: 0, y: 0, z: -2.5 }, pcbRotationOffset: 270, }} From 6e5af72dfe3a87dcb9e29ca4161fe861722255b6 Mon Sep 17 00:00:00 2001 From: ankan roy Date: Wed, 5 Aug 2026 06:29:50 +0530 Subject: [PATCH 04/11] Restore CAD model download URL --- tests/examples/example38-cad-model-placement.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/examples/example38-cad-model-placement.test.tsx b/tests/examples/example38-cad-model-placement.test.tsx index d1be3980e..c45ce24c8 100644 --- a/tests/examples/example38-cad-model-placement.test.tsx +++ b/tests/examples/example38-cad-model-placement.test.tsx @@ -919,7 +919,7 @@ test("example38 cad model placement snapshot", async () => { } cadModel={{ objUrl: - "https://modelcdn.tscircuit.com/easyeda_models/assets/C9900017879.obj?uuid=4e90b6d8552a4e058d9ebe9d82e11f3a", + "https://modelcdn.tscircuit.com/easyeda_models/download?uuid=4e90b6d8552a4e058d9ebe9d82e11f3a&pn=C9900017879", modelOriginPosition: { x: 0, y: 0, z: -2.5 }, pcbRotationOffset: 270, }} From f1d7441dfc7186cb44881b80b658da02edddd69e Mon Sep 17 00:00:00 2001 From: 0hmX <109351887+0hmX@users.noreply.github.com> Date: Wed, 5 Aug 2026 06:36:21 +0530 Subject: [PATCH 05/11] Use explicit differential pair control flow --- ...ialPair_doInitialSourceDesignRuleChecks.ts | 48 ++++++++----- .../getDifferentialPairsForSimpleRouteJson.ts | 12 ++-- .../resolve-differential-pair-connection.ts | 71 +++++++++++-------- 3 files changed, 76 insertions(+), 55 deletions(-) diff --git a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts index a9a3afebd..616b8a918 100644 --- a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts +++ b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts @@ -20,9 +20,10 @@ const getTerminalPinSelector = ( sourcePort: SourcePort, sourceComponentsById: Map, ): string => { - const sourceComponent = sourcePort.source_component_id - ? sourceComponentsById.get(sourcePort.source_component_id) - : undefined + let sourceComponent: { name: string } | undefined + if (sourcePort.source_component_id) { + sourceComponent = sourceComponentsById.get(sourcePort.source_component_id) + } if (!sourceComponent?.name) return sourcePort.source_port_id const portName = @@ -43,24 +44,33 @@ const getPointToPointWarningMessage = ({ sourceNetName?: string terminalPinSelectors: string[] }): string => { - const resolvedConnectionName = sourceNetName - ? `net.${sourceNetName}` - : `"${connectionSelector}"` + let resolvedConnectionName = `"${connectionSelector}"` + if (sourceNetName) { + resolvedConnectionName = `net.${sourceNetName}` + } const terminalCount = terminalPinSelectors.length - const pinOrPins = terminalCount === 1 ? "pin" : "pins" + let pinOrPins = "pins" + if (terminalCount === 1) { + pinOrPins = "pin" + } const terminalList = formatTerminalPinList(terminalPinSelectors) const suggestedSelector = terminalPinSelectors[0] - const correction = - terminalCount > 2 - ? "Remove the extra connection" - : "Connect exactly two terminal pins" - const selectorRecommendation = suggestedSelector - ? ` and prefer a pin selector such as ${connectionPolarity}Connection="${suggestedSelector}"` - : "" + let correction = "Connect exactly two terminal pins" + if (terminalCount > 2) { + correction = "Remove the extra connection" + } + let selectorRecommendation = "" + if (suggestedSelector) { + selectorRecommendation = ` and prefer a pin selector such as ${connectionPolarity}Connection="${suggestedSelector}"` + } + let terminalListDescription = "" + if (terminalList) { + terminalListDescription = `: ${terminalList}` + } return ( `Differential pair "${differentialPairName}" ${connectionPolarity}Connection resolves to ${resolvedConnectionName}, which is not point-to-point. ` + - `It connects to ${terminalCount} ${pinOrPins}${terminalList ? `: ${terminalList}` : ""}. ` + + `It connects to ${terminalCount} ${pinOrPins}${terminalListDescription}. ` + `${correction}${selectorRecommendation}.` ) } @@ -92,10 +102,10 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( } for (const connectionPolarity of ["positive", "negative"] as const) { - const connectionSelector = - connectionPolarity === "positive" - ? differentialPair._parsedProps.positiveConnection - : differentialPair._parsedProps.negativeConnection + let connectionSelector = differentialPair._parsedProps.negativeConnection + if (connectionPolarity === "positive") { + connectionSelector = differentialPair._parsedProps.positiveConnection + } let resolvedConnection: ResolvedDifferentialPairConnection try { diff --git a/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts b/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts index b2b999a40..8cdf7d365 100644 --- a/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts +++ b/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts @@ -66,12 +66,12 @@ const getDifferentialPairSrjConnectionNameOrThrow = ({ matchingTraceSrjConnections.push(srjConnection) } } - const matchingSrjConnections = - matchingTraceSrjConnections.length > 0 - ? matchingTraceSrjConnections - : srjConnections.filter((srjConnection) => - differentialPairSourceNetIds.has(srjConnection.name), - ) + let matchingSrjConnections = matchingTraceSrjConnections + if (matchingSrjConnections.length === 0) { + matchingSrjConnections = srjConnections.filter((srjConnection) => + differentialPairSourceNetIds.has(srjConnection.name), + ) + } if (matchingSrjConnections.length === 0) { throw new Error( diff --git a/lib/utils/autorouting/resolve-differential-pair-connection.ts b/lib/utils/autorouting/resolve-differential-pair-connection.ts index fdaf28d26..0fe890519 100644 --- a/lib/utils/autorouting/resolve-differential-pair-connection.ts +++ b/lib/utils/autorouting/resolve-differential-pair-connection.ts @@ -62,19 +62,20 @@ export const resolveDifferentialPairConnectionOrThrow = ({ differentialPairSourceTraces, traceNameOrPortSelector, ) - const selectedPort = - sourceTracesWithMatchingName.length === 0 - ? differentialPair - .getSubcircuit() - .selectOne(traceNameOrPortSelector, { type: "port" }) - : null + let selectedPort: Port | null = null + if (sourceTracesWithMatchingName.length === 0) { + selectedPort = differentialPair + .getSubcircuit() + .selectOne(traceNameOrPortSelector, { type: "port" }) + } const selectedSourcePortId = selectedPort?.source_port_id ?? undefined - const matchingSourceTraces = selectedSourcePortId - ? getDifferentialPairSourceTracesByPortId( - differentialPairSourceTraces, - selectedSourcePortId, - ) - : sourceTracesWithMatchingName + let matchingSourceTraces = sourceTracesWithMatchingName + if (selectedSourcePortId) { + matchingSourceTraces = getDifferentialPairSourceTracesByPortId( + differentialPairSourceTraces, + selectedSourcePortId, + ) + } if (matchingSourceTraces.length === 0) { throw new Error( @@ -82,13 +83,14 @@ export const resolveDifferentialPairConnectionOrThrow = ({ ) } - const subcircuitConnectivityMapKeys = new Set( - matchingSourceTraces.flatMap((sourceTrace) => - sourceTrace.subcircuit_connectivity_map_key - ? [sourceTrace.subcircuit_connectivity_map_key] - : [], - ), - ) + const subcircuitConnectivityMapKeys = new Set() + for (const sourceTrace of matchingSourceTraces) { + if (sourceTrace.subcircuit_connectivity_map_key) { + subcircuitConnectivityMapKeys.add( + sourceTrace.subcircuit_connectivity_map_key, + ) + } + } if (subcircuitConnectivityMapKeys.size > 1) { throw new Error( `Trace name or port selector "${traceNameOrPortSelector}" matches multiple source traces for differential pair "${differentialPair.name}"`, @@ -126,14 +128,17 @@ export const resolveDifferentialPairConnectionOrThrow = ({ const sourcePortsById = new Map( sourcePorts.map((sourcePort) => [sourcePort.source_port_id, sourcePort]), ) - const connectivityGroupSourcePorts = [...terminalSourcePortIds] - .sort((sourcePortIdA, sourcePortIdB) => + const sortedTerminalSourcePortIds = [...terminalSourcePortIds].sort( + (sourcePortIdA, sourcePortIdB) => sourcePortIdA.localeCompare(sourcePortIdB), - ) - .flatMap((sourcePortId) => { - const sourcePort = sourcePortsById.get(sourcePortId) - return sourcePort ? [sourcePort] : [] - }) + ) + const connectivityGroupSourcePorts: SourcePort[] = [] + for (const sourcePortId of sortedTerminalSourcePortIds) { + const sourcePort = sourcePortsById.get(sourcePortId) + if (sourcePort) { + connectivityGroupSourcePorts.push(sourcePort) + } + } const directlyReferencedSourceNetIds = matchingSourceTraces.flatMap( (sourceTrace) => sourceTrace.connected_source_net_ids, @@ -153,13 +158,19 @@ export const resolveDifferentialPairConnectionOrThrow = ({ sourceNets.map((sourceNet) => [sourceNet.source_net_id, sourceNet]), ) + let sourceNet: SourceNet | undefined + for (const sourceNetId of orderedSourceNetIds) { + const matchingSourceNet = sourceNetsById.get(sourceNetId) + if (matchingSourceNet) { + sourceNet = matchingSourceNet + break + } + } + return { subcircuitConnectivityMapKey, sourceTraces: connectivityGroupSourceTraces, sourcePorts: connectivityGroupSourcePorts, - sourceNet: orderedSourceNetIds.flatMap((sourceNetId) => { - const sourceNet = sourceNetsById.get(sourceNetId) - return sourceNet ? [sourceNet] : [] - })[0], + sourceNet, } } From 7688884b03db4e0779983b5e536656a9ab76570a Mon Sep 17 00:00:00 2001 From: 0hmX <109351887+0hmX@users.noreply.github.com> Date: Wed, 5 Aug 2026 06:52:01 +0530 Subject: [PATCH 06/11] Limit differential pair validation to warnings --- ...ialPair_doInitialSourceDesignRuleChecks.ts | 100 +++++++--- .../getDifferentialPairsForSimpleRouteJson.ts | 157 ++++++++++------ .../getSimpleRouteJsonFromCircuitJson.ts | 2 - .../resolve-differential-pair-connection.ts | 176 ------------------ .../port-selector-multiple-traces.test.tsx | 15 +- .../net-fragments-point-to-point.test.tsx | 27 +-- ...int-to-point-warning-circuit-json.test.tsx | 12 +- 7 files changed, 183 insertions(+), 306 deletions(-) delete mode 100644 lib/utils/autorouting/resolve-differential-pair-connection.ts diff --git a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts index 616b8a918..3c6fb540a 100644 --- a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts +++ b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts @@ -1,13 +1,72 @@ import type { SourcePort } from "circuit-json" -import { - type ResolvedDifferentialPairConnection, - resolveDifferentialPairConnectionOrThrow, -} from "lib/utils/autorouting/resolve-differential-pair-connection" import type { DifferentialPair } from "./DifferentialPair" +import type { Port } from "./Port/Port" type ConnectionPolarity = "positive" | "negative" type SourceComponentId = NonNullable +type ResolvedPointToPointConnection = { + sourcePorts: SourcePort[] + sourceNetName?: string +} + +const resolvePointToPointConnection = ( + differentialPair: DifferentialPair, + connectionSelector: string, +): ResolvedPointToPointConnection | undefined => { + const { db } = differentialPair.root! + const subcircuit = differentialPair.getSubcircuit() + const subcircuitSourceTraces = db.source_trace + .list() + .filter( + (sourceTrace) => sourceTrace.subcircuit_id === subcircuit.subcircuit_id, + ) + const matchingSourceTraces = subcircuitSourceTraces.filter( + (sourceTrace) => sourceTrace.name === connectionSelector, + ) + const connectivityMapKeys = new Set() + + if (matchingSourceTraces.length > 0) { + for (const sourceTrace of matchingSourceTraces) { + if (sourceTrace.subcircuit_connectivity_map_key) { + connectivityMapKeys.add(sourceTrace.subcircuit_connectivity_map_key) + } + } + } else { + const selectedPort = subcircuit.selectOne(connectionSelector, { + type: "port", + }) + if (selectedPort?.source_port_id) { + const sourcePort = db.source_port.get(selectedPort.source_port_id) + if (sourcePort?.subcircuit_connectivity_map_key) { + connectivityMapKeys.add(sourcePort.subcircuit_connectivity_map_key) + } + } + } + + if (connectivityMapKeys.size !== 1) return undefined + const connectivityMapKey = connectivityMapKeys.values().next().value + if (!connectivityMapKey) return undefined + + const sourcePorts = db.source_port + .list() + .filter( + (sourcePort) => + sourcePort.subcircuit_connectivity_map_key === connectivityMapKey, + ) + const sourceNet = db.source_net + .list() + .find( + (sourceNet) => + sourceNet.subcircuit_connectivity_map_key === connectivityMapKey, + ) + + return { + sourcePorts, + sourceNetName: sourceNet?.name, + } +} + const formatTerminalPinList = (terminalPinSelectors: string[]): string => { if (terminalPinSelectors.length <= 1) return terminalPinSelectors.join("") if (terminalPinSelectors.length === 2) { @@ -91,9 +150,6 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( const { db } = differentialPair.root! removeStoredPointToPointWarnings(differentialPair) - const sourceTraces = db.source_trace.list() - const sourcePorts = db.source_port.list() - const sourceNets = db.source_net.list() const sourceComponentsById = new Map() for (const sourceComponent of db.source_component.list()) { sourceComponentsById.set(sourceComponent.source_component_id, { @@ -107,29 +163,13 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( connectionSelector = differentialPair._parsedProps.positiveConnection } - let resolvedConnection: ResolvedDifferentialPairConnection - try { - resolvedConnection = resolveDifferentialPairConnectionOrThrow({ - differentialPair, - traceNameOrPortSelector: connectionSelector, - sourceTraces, - sourcePorts, - sourceNets, - }) - } catch { - // Missing and unrelated ambiguous references retain their existing SRJ - // diagnostics. This DRC is specifically for resolved connectivity groups. - continue - } + const resolvedConnection = resolvePointToPointConnection( + differentialPair, + connectionSelector, + ) + if (!resolvedConnection) continue - const terminalSourcePorts = [ - ...new Map( - resolvedConnection.sourcePorts.map((sourcePort) => [ - sourcePort.source_port_id, - sourcePort, - ]), - ).values(), - ] + const terminalSourcePorts = resolvedConnection.sourcePorts if (terminalSourcePorts.length === 2) continue const warningSourceComponentId = terminalSourcePorts[0]?.source_component_id if (!warningSourceComponentId) continue @@ -147,7 +187,7 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( differentialPairName: differentialPair.name, connectionPolarity, connectionSelector, - sourceNetName: resolvedConnection.sourceNet?.name, + sourceNetName: resolvedConnection.sourceNetName, terminalPinSelectors, }), subcircuit_id: diff --git a/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts b/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts index 8cdf7d365..72bb359f1 100644 --- a/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts +++ b/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts @@ -1,28 +1,32 @@ -import type { SourceNet, SourcePort, SourceTrace } from "circuit-json" +import type { SourcePort, SourceTrace } from "circuit-json" import type { DifferentialPair } from "lib/components/primitive-components/DifferentialPair" +import type { Port } from "lib/components/primitive-components/Port/Port" import type { SimpleRouteConnection, SimpleRouteDifferentialPair, SrjConnectionName, } from "./SimpleRouteJson" -import { - type SourceNetId, - type SubcircuitConnectivityMapKey, - type SubcircuitId, - resolveDifferentialPairConnectionOrThrow, -} from "./resolve-differential-pair-connection" type SourceTraceId = SourceTrace["source_trace_id"] +type SourcePortId = NonNullable +type SubcircuitId = NonNullable +type SubcircuitConnectivityMapKey = NonNullable< + SourceTrace["subcircuit_connectivity_map_key"] +> type GetDifferentialPairsParams = { srjConnections: SimpleRouteConnection[] differentialPairs: DifferentialPair[] sourceTraces: SourceTrace[] - sourcePorts: SourcePort[] - sourceNets: SourceNet[] subcircuitId?: SubcircuitId | null } +type GetDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrowParams = { + differentialPair: DifferentialPair + differentialPairSourceTraces: SourceTrace[] + traceNameOrPortSelector: string +} + type GetDifferentialPairSrjConnectionNameOrThrowParams = { srjConnections: SimpleRouteConnection[] differentialPairName: string @@ -31,6 +35,77 @@ type GetDifferentialPairSrjConnectionNameOrThrowParams = { traceNameOrPortSelector: string } +const getDifferentialPairSourceTracesByTraceName = ( + differentialPairSourceTraces: SourceTrace[], + traceName: string, +): SourceTrace[] => + differentialPairSourceTraces.filter( + (sourceTrace) => sourceTrace.name === traceName, + ) + +const getDifferentialPairSourceTracesByPortId = ( + differentialPairSourceTraces: SourceTrace[], + sourcePortId: SourcePortId, +): SourceTrace[] => + differentialPairSourceTraces.filter((sourceTrace) => + sourceTrace.connected_source_port_ids.includes(sourcePortId), + ) + +const getDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrow = ({ + differentialPair, + differentialPairSourceTraces, + traceNameOrPortSelector, +}: GetDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrowParams): SubcircuitConnectivityMapKey => { + const differentialPairSubcircuit = differentialPair.getSubcircuit() + const sourceTracesWithMatchingName = + getDifferentialPairSourceTracesByTraceName( + differentialPairSourceTraces, + traceNameOrPortSelector, + ) + const selectedPort = + sourceTracesWithMatchingName.length === 0 + ? differentialPairSubcircuit.selectOne(traceNameOrPortSelector, { + type: "port", + }) + : null + const selectedSourcePortId: SourcePortId | undefined = + selectedPort?.source_port_id ?? undefined + const matchingSourceTraces = selectedSourcePortId + ? getDifferentialPairSourceTracesByPortId( + differentialPairSourceTraces, + selectedSourcePortId, + ) + : sourceTracesWithMatchingName + + if (matchingSourceTraces.length === 0) { + throw new Error( + `Could not find source trace for trace name or port selector "${traceNameOrPortSelector}" in differential pair "${differentialPair.name}"`, + ) + } + if (matchingSourceTraces.length > 1) { + throw new Error( + `Trace name or port selector "${traceNameOrPortSelector}" matches multiple source traces for differential pair "${differentialPair.name}"`, + ) + } + + const sourceTrace = matchingSourceTraces[0] + if (!sourceTrace) { + throw new Error( + `Expected one source trace for trace name or port selector "${traceNameOrPortSelector}" in differential pair "${differentialPair.name}"`, + ) + } + + const subcircuitConnectivityMapKey = + sourceTrace.subcircuit_connectivity_map_key + if (!subcircuitConnectivityMapKey) { + throw new Error( + `Source trace "${sourceTrace.source_trace_id}" does not have a subcircuit connectivity map key for differential pair "${differentialPair.name}"`, + ) + } + + return subcircuitConnectivityMapKey +} + const getDifferentialPairSrjConnectionNameOrThrow = ({ srjConnections, differentialPairName, @@ -39,39 +114,24 @@ const getDifferentialPairSrjConnectionNameOrThrow = ({ traceNameOrPortSelector, }: GetDifferentialPairSrjConnectionNameOrThrowParams): SrjConnectionName => { const differentialPairSourceTraceIds: SourceTraceId[] = [] - const differentialPairSourceNetIds = new Set() for (const sourceTrace of differentialPairSourceTraces) { if ( sourceTrace.subcircuit_connectivity_map_key === traceSubcircuitConnectivityMapKey ) { differentialPairSourceTraceIds.push(sourceTrace.source_trace_id) - for (const sourceNetId of sourceTrace.connected_source_net_ids) { - differentialPairSourceNetIds.add(sourceNetId) - } } } - const matchingTraceSrjConnections: SimpleRouteConnection[] = [] + const matchingSrjConnections: SimpleRouteConnection[] = [] for (const srjConnection of srjConnections) { if ( - (srjConnection.source_trace_id && - differentialPairSourceTraceIds.includes( - srjConnection.source_trace_id, - )) || - srjConnection.mergedConnectionNames?.some((sourceTraceId) => - differentialPairSourceTraceIds.includes(sourceTraceId), - ) + srjConnection.source_trace_id && + differentialPairSourceTraceIds.includes(srjConnection.source_trace_id) ) { - matchingTraceSrjConnections.push(srjConnection) + matchingSrjConnections.push(srjConnection) } } - let matchingSrjConnections = matchingTraceSrjConnections - if (matchingSrjConnections.length === 0) { - matchingSrjConnections = srjConnections.filter((srjConnection) => - differentialPairSourceNetIds.has(srjConnection.name), - ) - } if (matchingSrjConnections.length === 0) { throw new Error( @@ -99,8 +159,6 @@ export const getDifferentialPairsForSimpleRouteJson = ({ srjConnections, differentialPairs, sourceTraces, - sourcePorts, - sourceNets, subcircuitId, }: GetDifferentialPairsParams): SimpleRouteDifferentialPair[] | undefined => { const srjDifferentialPairs: SimpleRouteDifferentialPair[] = [] @@ -119,35 +177,25 @@ export const getDifferentialPairsForSimpleRouteJson = ({ differentialPair._parsedProps.positiveConnection const negativeTraceNameOrPortSelector = differentialPair._parsedProps.negativeConnection - const positiveConnection = resolveDifferentialPairConnectionOrThrow({ - differentialPair, - traceNameOrPortSelector: positiveTraceNameOrPortSelector, - sourceTraces, - sourcePorts, - sourceNets, - }) - const negativeConnection = resolveDifferentialPairConnectionOrThrow({ - differentialPair, - traceNameOrPortSelector: negativeTraceNameOrPortSelector, - sourceTraces, - sourcePorts, - sourceNets, - }) - - if ( - positiveConnection.sourcePorts.length !== 2 || - negativeConnection.sourcePorts.length !== 2 - ) { - continue - } + const positiveSubcircuitConnectivityMapKey = + getDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrow({ + differentialPair, + differentialPairSourceTraces, + traceNameOrPortSelector: positiveTraceNameOrPortSelector, + }) + const negativeSubcircuitConnectivityMapKey = + getDifferentialPairTraceSubcircuitConnectivityMapKeyOrThrow({ + differentialPair, + differentialPairSourceTraces, + traceNameOrPortSelector: negativeTraceNameOrPortSelector, + }) const positiveSrjConnectionName = getDifferentialPairSrjConnectionNameOrThrow({ srjConnections, differentialPairName: differentialPair.name, differentialPairSourceTraces, - traceSubcircuitConnectivityMapKey: - positiveConnection.subcircuitConnectivityMapKey, + traceSubcircuitConnectivityMapKey: positiveSubcircuitConnectivityMapKey, traceNameOrPortSelector: positiveTraceNameOrPortSelector, }) const negativeSrjConnectionName = @@ -155,8 +203,7 @@ export const getDifferentialPairsForSimpleRouteJson = ({ srjConnections, differentialPairName: differentialPair.name, differentialPairSourceTraces, - traceSubcircuitConnectivityMapKey: - negativeConnection.subcircuitConnectivityMapKey, + traceSubcircuitConnectivityMapKey: negativeSubcircuitConnectivityMapKey, traceNameOrPortSelector: negativeTraceNameOrPortSelector, }) diff --git a/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts b/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts index b220a999a..3bf41856a 100644 --- a/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts +++ b/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts @@ -731,8 +731,6 @@ export const getSimpleRouteJsonFromCircuitJson = ({ srjConnections: allConns, differentialPairs, sourceTraces: db.source_trace.list(), - sourcePorts: db.source_port.list(), - sourceNets: db.source_net.list(), subcircuitId: subcircuit_id, }) diff --git a/lib/utils/autorouting/resolve-differential-pair-connection.ts b/lib/utils/autorouting/resolve-differential-pair-connection.ts deleted file mode 100644 index 0fe890519..000000000 --- a/lib/utils/autorouting/resolve-differential-pair-connection.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type { SourceNet, SourcePort, SourceTrace } from "circuit-json" -import type { DifferentialPair } from "lib/components/primitive-components/DifferentialPair" -import type { Port } from "lib/components/primitive-components/Port/Port" - -export type SourcePortId = SourcePort["source_port_id"] -export type SourceNetId = SourceNet["source_net_id"] -export type SubcircuitId = NonNullable -export type SubcircuitConnectivityMapKey = NonNullable< - SourceTrace["subcircuit_connectivity_map_key"] -> - -export type ResolvedDifferentialPairConnection = { - subcircuitConnectivityMapKey: SubcircuitConnectivityMapKey - sourceTraces: SourceTrace[] - sourcePorts: SourcePort[] - sourceNet?: SourceNet -} - -type ResolveDifferentialPairConnectionParams = { - differentialPair: DifferentialPair - traceNameOrPortSelector: string - sourceTraces: SourceTrace[] - sourcePorts: SourcePort[] - sourceNets: SourceNet[] -} - -const getDifferentialPairSourceTracesByTraceName = ( - differentialPairSourceTraces: SourceTrace[], - traceName: string, -): SourceTrace[] => - differentialPairSourceTraces.filter( - (sourceTrace) => sourceTrace.name === traceName, - ) - -const getDifferentialPairSourceTracesByPortId = ( - differentialPairSourceTraces: SourceTrace[], - sourcePortId: SourcePortId, -): SourceTrace[] => - differentialPairSourceTraces.filter((sourceTrace) => - sourceTrace.connected_source_port_ids.includes(sourcePortId), - ) - -/** - * Resolves one differential-pair conductor to its complete source-connectivity - * group. Multiple source-trace fragments are valid when they all belong to the - * same group (for example two traces joined through a source net). - */ -export const resolveDifferentialPairConnectionOrThrow = ({ - differentialPair, - traceNameOrPortSelector, - sourceTraces, - sourcePorts, - sourceNets, -}: ResolveDifferentialPairConnectionParams): ResolvedDifferentialPairConnection => { - const differentialPairSubcircuitId = - differentialPair.getSubcircuit().subcircuit_id - const differentialPairSourceTraces = sourceTraces.filter( - (sourceTrace) => sourceTrace.subcircuit_id === differentialPairSubcircuitId, - ) - const sourceTracesWithMatchingName = - getDifferentialPairSourceTracesByTraceName( - differentialPairSourceTraces, - traceNameOrPortSelector, - ) - let selectedPort: Port | null = null - if (sourceTracesWithMatchingName.length === 0) { - selectedPort = differentialPair - .getSubcircuit() - .selectOne(traceNameOrPortSelector, { type: "port" }) - } - const selectedSourcePortId = selectedPort?.source_port_id ?? undefined - let matchingSourceTraces = sourceTracesWithMatchingName - if (selectedSourcePortId) { - matchingSourceTraces = getDifferentialPairSourceTracesByPortId( - differentialPairSourceTraces, - selectedSourcePortId, - ) - } - - if (matchingSourceTraces.length === 0) { - throw new Error( - `Could not find source trace for trace name or port selector "${traceNameOrPortSelector}" in differential pair "${differentialPair.name}"`, - ) - } - - const subcircuitConnectivityMapKeys = new Set() - for (const sourceTrace of matchingSourceTraces) { - if (sourceTrace.subcircuit_connectivity_map_key) { - subcircuitConnectivityMapKeys.add( - sourceTrace.subcircuit_connectivity_map_key, - ) - } - } - if (subcircuitConnectivityMapKeys.size > 1) { - throw new Error( - `Trace name or port selector "${traceNameOrPortSelector}" matches multiple source traces for differential pair "${differentialPair.name}"`, - ) - } - - const sourceTraceWithoutConnectivityMapKey = matchingSourceTraces.find( - (sourceTrace) => !sourceTrace.subcircuit_connectivity_map_key, - ) - if (sourceTraceWithoutConnectivityMapKey) { - throw new Error( - `Source trace "${sourceTraceWithoutConnectivityMapKey.source_trace_id}" does not have a subcircuit connectivity map key for differential pair "${differentialPair.name}"`, - ) - } - - const subcircuitConnectivityMapKey = subcircuitConnectivityMapKeys - .values() - .next().value - if (!subcircuitConnectivityMapKey) { - throw new Error( - `Expected a subcircuit connectivity map key for trace name or port selector "${traceNameOrPortSelector}" in differential pair "${differentialPair.name}"`, - ) - } - - const connectivityGroupSourceTraces = differentialPairSourceTraces.filter( - (sourceTrace) => - sourceTrace.subcircuit_connectivity_map_key === - subcircuitConnectivityMapKey, - ) - const terminalSourcePortIds = new Set( - connectivityGroupSourceTraces.flatMap( - (sourceTrace) => sourceTrace.connected_source_port_ids, - ), - ) - const sourcePortsById = new Map( - sourcePorts.map((sourcePort) => [sourcePort.source_port_id, sourcePort]), - ) - const sortedTerminalSourcePortIds = [...terminalSourcePortIds].sort( - (sourcePortIdA, sourcePortIdB) => - sourcePortIdA.localeCompare(sourcePortIdB), - ) - const connectivityGroupSourcePorts: SourcePort[] = [] - for (const sourcePortId of sortedTerminalSourcePortIds) { - const sourcePort = sourcePortsById.get(sourcePortId) - if (sourcePort) { - connectivityGroupSourcePorts.push(sourcePort) - } - } - - const directlyReferencedSourceNetIds = matchingSourceTraces.flatMap( - (sourceTrace) => sourceTrace.connected_source_net_ids, - ) - const connectivityGroupSourceNetIds = connectivityGroupSourceTraces.flatMap( - (sourceTrace) => sourceTrace.connected_source_net_ids, - ) - const orderedSourceNetIds = [ - ...new Set([ - ...directlyReferencedSourceNetIds, - ...connectivityGroupSourceNetIds.sort((sourceNetIdA, sourceNetIdB) => - sourceNetIdA.localeCompare(sourceNetIdB), - ), - ]), - ] - const sourceNetsById = new Map( - sourceNets.map((sourceNet) => [sourceNet.source_net_id, sourceNet]), - ) - - let sourceNet: SourceNet | undefined - for (const sourceNetId of orderedSourceNetIds) { - const matchingSourceNet = sourceNetsById.get(sourceNetId) - if (matchingSourceNet) { - sourceNet = matchingSourceNet - break - } - } - - return { - subcircuitConnectivityMapKey, - sourceTraces: connectivityGroupSourceTraces, - sourcePorts: connectivityGroupSourcePorts, - sourceNet, - } -} diff --git a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx index f9ee245a1..086704a56 100644 --- a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx +++ b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx @@ -2,7 +2,7 @@ import { expect, it } from "bun:test" import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" import { getTestFixture } from "tests/fixtures/get-test-fixture" -it("stores a property warning when a differential pair port selector resolves to a branch", (): void => { +it("stores a warning without changing the existing autorouter error", (): void => { const { circuit } = getTestFixture() circuit.add( @@ -49,9 +49,12 @@ it("stores a property warning when a differential pair port selector resolves to 'positiveConnection resolves to ".R1 > .pin1", which is not point-to-point', ) - const { simpleRouteJson } = getSimpleRouteJsonFromCircuitJson({ - circuitJson: circuit.getCircuitJson(), - subcircuitComponent: boardSubcircuit, - }) - expect(simpleRouteJson.differentialPairs).toBeUndefined() + expect((): void => { + getSimpleRouteJsonFromCircuitJson({ + circuitJson: circuit.getCircuitJson(), + subcircuitComponent: boardSubcircuit, + }) + }).toThrow( + 'Trace name or port selector ".R1 > .pin1" matches multiple source traces for differential pair "USB"', + ) }) diff --git a/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx b/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx index 29612ba05..c35b9713b 100644 --- a/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx +++ b/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx @@ -1,8 +1,7 @@ import { expect, test } from "bun:test" -import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" import { getTestFixture } from "tests/fixtures/get-test-fixture" -test("accepts a two-terminal differential conductor split through a source net", (): void => { +test("does not warn for a two-terminal conductor split through a source net", (): void => { const { circuit } = getTestFixture() circuit.add( @@ -11,7 +10,6 @@ test("accepts a two-terminal differential conductor split through a source net", name="USB_DATA" positiveConnection=".J1 > .pin1" negativeConnection="DM" - maxLengthSkew={0.05} /> @@ -32,27 +30,4 @@ test("accepts a two-terminal differential conductor split through a source net", warning.property_name === "negativeConnection", ), ).toEqual([]) - - const boardSubcircuit = circuit.firstChild - if (!boardSubcircuit) throw new Error("Expected a board subcircuit") - - const { simpleRouteJson } = getSimpleRouteJsonFromCircuitJson({ - circuitJson: circuit.getCircuitJson(), - subcircuitComponent: boardSubcircuit, - }) - const dpSourceNet = circuit.db.source_net.getWhere({ name: "DP" }) - const dmSourceTrace = circuit.db.source_trace.getWhere({ name: "DM" }) - if (!dpSourceNet || !dmSourceTrace) { - throw new Error("Expected DP source net and DM source trace") - } - - expect(simpleRouteJson.differentialPairs).toEqual([ - { - connectionNames: [ - dpSourceNet.source_net_id, - dmSourceTrace.source_trace_id, - ], - lengthTolerance: 0.05, - }, - ]) }) diff --git a/tests/components/primitive-components/differential-pair/point-to-point-warning-circuit-json.test.tsx b/tests/components/primitive-components/differential-pair/point-to-point-warning-circuit-json.test.tsx index 5a7e353b1..e556a8113 100644 --- a/tests/components/primitive-components/differential-pair/point-to-point-warning-circuit-json.test.tsx +++ b/tests/components/primitive-components/differential-pair/point-to-point-warning-circuit-json.test.tsx @@ -1,8 +1,7 @@ import { expect, test } from "bun:test" -import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" import { getTestFixture } from "tests/fixtures/get-test-fixture" -test("stores a property warning and omits a branched differential pair from SRJ", (): void => { +test("stores a property warning for a branched differential pair", (): void => { const { circuit } = getTestFixture() circuit.add( @@ -35,13 +34,4 @@ test("stores a property warning and omits a branched differential pair from SRJ" expect(pointToPointWarnings[0]?.message).toBe( 'Differential pair "USB_DATA" positiveConnection resolves to net.DP, which is not point-to-point. It connects to 3 pins: .J1 > .pin1, .TP1 > .pin1, and .U1 > .pin1. Remove the extra connection and prefer a pin selector such as positiveConnection=".J1 > .pin1".', ) - - const boardSubcircuit = circuit.firstChild - if (!boardSubcircuit) throw new Error("Expected a board subcircuit") - - const { simpleRouteJson } = getSimpleRouteJsonFromCircuitJson({ - circuitJson: circuit.getCircuitJson(), - subcircuitComponent: boardSubcircuit, - }) - expect(simpleRouteJson.differentialPairs).toBeUndefined() }) From 61230a660e6929e321244b5d52b75e0170b2ac89 Mon Sep 17 00:00:00 2001 From: 0hmX <109351887+0hmX@users.noreply.github.com> Date: Wed, 5 Aug 2026 07:05:45 +0530 Subject: [PATCH 07/11] Split differential pair warning coverage --- ...-selector-multiple-traces-warning.test.tsx | 45 +++++++++++++++++++ .../port-selector-multiple-traces.test.tsx | 15 +------ 2 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces-warning.test.tsx diff --git a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces-warning.test.tsx b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces-warning.test.tsx new file mode 100644 index 000000000..4877814fc --- /dev/null +++ b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces-warning.test.tsx @@ -0,0 +1,45 @@ +import { expect, it } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +it("stores a warning when a differential pair port selector matches multiple traces", (): void => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + + + + + , + ) + + circuit.render() + + const pointToPointWarning = + circuit.db.source_property_ignored_warning.getWhere({ + property_name: "positiveConnection", + }) + expect(pointToPointWarning).toMatchObject({ + type: "source_property_ignored_warning", + error_type: "source_property_ignored_warning", + property_name: "positiveConnection", + }) + expect(pointToPointWarning?.message).toContain( + 'positiveConnection resolves to ".R1 > .pin1", which is not point-to-point', + ) +}) diff --git a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx index 086704a56..2805c5906 100644 --- a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx +++ b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces.test.tsx @@ -2,7 +2,7 @@ import { expect, it } from "bun:test" import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" import { getTestFixture } from "tests/fixtures/get-test-fixture" -it("stores a warning without changing the existing autorouter error", (): void => { +it("throws when a differential pair port selector matches multiple traces", (): void => { const { circuit } = getTestFixture() circuit.add( @@ -36,19 +36,6 @@ it("stores a warning without changing the existing autorouter error", (): void = throw new Error("Expected the circuit to contain a board") } - const pointToPointWarning = - circuit.db.source_property_ignored_warning.getWhere({ - property_name: "positiveConnection", - }) - expect(pointToPointWarning).toMatchObject({ - type: "source_property_ignored_warning", - error_type: "source_property_ignored_warning", - property_name: "positiveConnection", - }) - expect(pointToPointWarning?.message).toContain( - 'positiveConnection resolves to ".R1 > .pin1", which is not point-to-point', - ) - expect((): void => { getSimpleRouteJsonFromCircuitJson({ circuitJson: circuit.getCircuitJson(), From 5b0ba0c591cc72afe005ea42edb78949e8e6be9b Mon Sep 17 00:00:00 2001 From: ankan roy Date: Wed, 5 Aug 2026 15:40:49 +0530 Subject: [PATCH 08/11] remove extra differential pair DRC hooks --- .../primitive-components/DifferentialPair.ts | 15 +------------ ...ialPair_doInitialSourceDesignRuleChecks.ts | 22 +------------------ 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/lib/components/primitive-components/DifferentialPair.ts b/lib/components/primitive-components/DifferentialPair.ts index bd41a15d5..608c3b37c 100644 --- a/lib/components/primitive-components/DifferentialPair.ts +++ b/lib/components/primitive-components/DifferentialPair.ts @@ -3,10 +3,7 @@ import { type BaseComponentConfig, PrimitiveComponent, } from "../base-components/PrimitiveComponent" -import { - DifferentialPair_doInitialSourceDesignRuleChecks, - DifferentialPair_removeSourceDesignRuleChecks, -} from "./DifferentialPair_doInitialSourceDesignRuleChecks" +import { DifferentialPair_doInitialSourceDesignRuleChecks } from "./DifferentialPair_doInitialSourceDesignRuleChecks" /** * Declares the routing constraints for a positive and negative trace pair. @@ -14,8 +11,6 @@ import { export class DifferentialPair extends PrimitiveComponent< typeof differentialPairProps > { - _pointToPointWarningIds: string[] = [] - override get config(): BaseComponentConfig { return { componentName: "DifferentialPair", @@ -26,12 +21,4 @@ export class DifferentialPair extends PrimitiveComponent< doInitialSourceDesignRuleChecks(): void { DifferentialPair_doInitialSourceDesignRuleChecks(this) } - - updateSourceDesignRuleChecks(): void { - DifferentialPair_doInitialSourceDesignRuleChecks(this) - } - - removeSourceDesignRuleChecks(): void { - DifferentialPair_removeSourceDesignRuleChecks(this) - } } diff --git a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts index 3c6fb540a..e8b0d817b 100644 --- a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts +++ b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts @@ -134,21 +134,10 @@ const getPointToPointWarningMessage = ({ ) } -const removeStoredPointToPointWarnings = ( - differentialPair: DifferentialPair, -): void => { - const warningTable = differentialPair.root!.db.source_property_ignored_warning - for (const warningId of differentialPair._pointToPointWarningIds) { - warningTable.delete(warningId) - } - differentialPair._pointToPointWarningIds = [] -} - export const DifferentialPair_doInitialSourceDesignRuleChecks = ( differentialPair: DifferentialPair, ): void => { const { db } = differentialPair.root! - removeStoredPointToPointWarnings(differentialPair) const sourceComponentsById = new Map() for (const sourceComponent of db.source_component.list()) { @@ -179,7 +168,7 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( getTerminalPinSelector(sourcePort, sourceComponentsById), ) .sort((selectorA, selectorB) => selectorA.localeCompare(selectorB)) - const insertedWarning = db.source_property_ignored_warning.insert({ + db.source_property_ignored_warning.insert({ source_component_id: warningSourceComponentId, property_name: `${connectionPolarity}Connection`, error_type: "source_property_ignored_warning", @@ -193,14 +182,5 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( subcircuit_id: differentialPair.getSubcircuit().subcircuit_id ?? undefined, }) - differentialPair._pointToPointWarningIds.push( - insertedWarning.source_property_ignored_warning_id, - ) } } - -export const DifferentialPair_removeSourceDesignRuleChecks = ( - differentialPair: DifferentialPair, -): void => { - removeStoredPointToPointWarnings(differentialPair) -} From 116e63067423636543642b998246bf5af8f51a80 Mon Sep 17 00:00:00 2001 From: ankan roy Date: Wed, 5 Aug 2026 15:44:24 +0530 Subject: [PATCH 09/11] warn for zero-terminal differential pairs --- ...ialPair_doInitialSourceDesignRuleChecks.ts | 6 ++-- ...o-terminal-point-to-point-warning.test.tsx | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 tests/components/primitive-components/differential-pair/zero-terminal-point-to-point-warning.test.tsx diff --git a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts index e8b0d817b..49bfc978b 100644 --- a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts +++ b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts @@ -160,8 +160,10 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( const terminalSourcePorts = resolvedConnection.sourcePorts if (terminalSourcePorts.length === 2) continue - const warningSourceComponentId = terminalSourcePorts[0]?.source_component_id - if (!warningSourceComponentId) continue + const warningSourceComponentId = + terminalSourcePorts[0]?.source_component_id ?? + differentialPair.source_component_id ?? + "" const terminalPinSelectors = terminalSourcePorts .map((sourcePort) => diff --git a/tests/components/primitive-components/differential-pair/zero-terminal-point-to-point-warning.test.tsx b/tests/components/primitive-components/differential-pair/zero-terminal-point-to-point-warning.test.tsx new file mode 100644 index 000000000..b2e260018 --- /dev/null +++ b/tests/components/primitive-components/differential-pair/zero-terminal-point-to-point-warning.test.tsx @@ -0,0 +1,35 @@ +import { expect, test } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("stores a warning for a differential-pair connection with no terminal pins", (): void => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + , + ) + + circuit.render() + + const warning = circuit.db.source_property_ignored_warning.getWhere({ + property_name: "positiveConnection", + }) + expect(warning).toMatchObject({ + type: "source_property_ignored_warning", + source_component_id: "", + error_type: "source_property_ignored_warning", + property_name: "positiveConnection", + }) + expect(warning?.message).toBe( + 'Differential pair "USB_DATA" positiveConnection resolves to net.DP, which is not point-to-point. It connects to 0 pins. Connect exactly two terminal pins.', + ) +}) From f6ae9c017d490f1bb61d997dac7a6f93d949aa1a Mon Sep 17 00:00:00 2001 From: ankan roy Date: Wed, 5 Aug 2026 15:53:01 +0530 Subject: [PATCH 10/11] fail on malformed differential pair terminals --- ...ialPair_doInitialSourceDesignRuleChecks.ts | 14 ++++-- ...sing-terminal-source-component-id.test.tsx | 45 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 tests/components/primitive-components/differential-pair/missing-terminal-source-component-id.test.tsx diff --git a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts index 49bfc978b..fbb896234 100644 --- a/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts +++ b/lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks.ts @@ -160,10 +160,16 @@ export const DifferentialPair_doInitialSourceDesignRuleChecks = ( const terminalSourcePorts = resolvedConnection.sourcePorts if (terminalSourcePorts.length === 2) continue - const warningSourceComponentId = - terminalSourcePorts[0]?.source_component_id ?? - differentialPair.source_component_id ?? - "" + let warningSourceComponentId = "" + const firstTerminalSourcePort = terminalSourcePorts[0] + if (firstTerminalSourcePort) { + if (!firstTerminalSourcePort.source_component_id) { + throw new Error( + `Differential pair "${differentialPair.name}" resolved terminal port "${firstTerminalSourcePort.source_port_id}" without a source_component_id`, + ) + } + warningSourceComponentId = firstTerminalSourcePort.source_component_id + } const terminalPinSelectors = terminalSourcePorts .map((sourcePort) => diff --git a/tests/components/primitive-components/differential-pair/missing-terminal-source-component-id.test.tsx b/tests/components/primitive-components/differential-pair/missing-terminal-source-component-id.test.tsx new file mode 100644 index 000000000..a5e33d9a5 --- /dev/null +++ b/tests/components/primitive-components/differential-pair/missing-terminal-source-component-id.test.tsx @@ -0,0 +1,45 @@ +import { expect, test } from "bun:test" +import type { DifferentialPair } from "lib/components/primitive-components/DifferentialPair" +import { DifferentialPair_doInitialSourceDesignRuleChecks } from "lib/components/primitive-components/DifferentialPair_doInitialSourceDesignRuleChecks" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("throws when a differential-pair terminal has no source component ID", (): void => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + , + ) + + circuit.render() + + const j1 = circuit.db.source_component.getWhere({ name: "J1" })! + const j1Pin1 = circuit.db.source_port + .list() + .find( + (sourcePort) => + sourcePort.source_component_id === j1.source_component_id && + sourcePort.name === "pin1", + )! + circuit.db.source_port.update(j1Pin1.source_port_id, { + source_component_id: null as never, + }) + + const differentialPair = circuit.selectOne( + "differentialpair", + ) as DifferentialPair + expect(() => + DifferentialPair_doInitialSourceDesignRuleChecks(differentialPair), + ).toThrow( + `Differential pair "USB_DATA" resolved terminal port "${j1Pin1.source_port_id}" without a source_component_id`, + ) +}) From b7f8b9d3dc5ea79ad9962c6e9d18d164bf868b78 Mon Sep 17 00:00:00 2001 From: ankan roy Date: Wed, 5 Aug 2026 15:58:47 +0530 Subject: [PATCH 11/11] use consistent differential pair test API --- .../port-selector-multiple-traces-warning.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces-warning.test.tsx b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces-warning.test.tsx index 4877814fc..6bdc92b3b 100644 --- a/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces-warning.test.tsx +++ b/tests/components/primitive-components/differential-pair/failure-tests/port-selector-multiple-traces-warning.test.tsx @@ -1,7 +1,7 @@ -import { expect, it } from "bun:test" +import { expect, test } from "bun:test" import { getTestFixture } from "tests/fixtures/get-test-fixture" -it("stores a warning when a differential pair port selector matches multiple traces", (): void => { +test("stores a warning when a differential pair port selector matches multiple traces", (): void => { const { circuit } = getTestFixture() circuit.add(