Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export { checkSourceTracesHavePcbTraces } from "./lib/check-source-traces-have-p
export { checkPcbTracesOutOfBoard } from "./lib/check-trace-out-of-board/checkTraceOutOfBoard"
export { checkPcbComponentOverlap } from "./lib/check-pcb-components-overlap/checkPcbComponentOverlap"
export { checkPinMustBeConnected } from "./lib/check-pin-must-be-connected"
export { checkI2cSdaConnectedToSclMisconfigured } from "./lib/check-i2c-sda-connected-to-scl"
export { runAllChecks } from "./lib/run-all-checks"
87 changes: 87 additions & 0 deletions lib/check-i2c-sda-connected-to-scl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type {
AnyCircuitElement,
SourceI2cMisconfiguredError,
} from "circuit-json"
import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"

type SourceComponent = Extract<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

AnyCircuitElement,
{ source_component_id: string; name: string }
>

export function checkI2cSdaConnectedToSclMisconfigured(
circuitJson: AnyCircuitElement[],
Copy link
Contributor

@seveibar seveibar Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
circuitJson: AnyCircuitElement[],
circuitJson: AnyCircuitElement[],
ctx?: DrcContext
ctx ??= getDrcContext(circuitJson)  
type DrcContext = {
  connMap: ConnectivityMap,
  db: CircuitJsonUtil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctx.db.source_port.list({ is_i2c_sda: true })

): SourceI2cMisconfiguredError[] {
const errors: SourceI2cMisconfiguredError[] = []

const sourceComponents = circuitJson.filter(
(el): el is SourceComponent =>
"source_component_id" in el &&
(el.type === "source_component" || el.type.startsWith("source_simple_")),
)

// Get all source ports to easily look up their attributes
const sourcePorts = circuitJson.filter(
(el): el is Extract<AnyCircuitElement, { type: "source_port" }> =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import SourcePort

el.type === "source_port",
)
const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p]))

const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson)

for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inefficient mechanism. Find the ports first

let hasSda = false
let hasScl = false

const conflictingPortIds: string[] = []

for (const portId of connectedPortIds) {
const port = portMap.get(portId)
if (!port) continue

let shouldAddPort = false
if (port.is_configured_for_i2c_sda) {
hasSda = true
shouldAddPort = true
}
if (port.is_configured_for_i2c_scl) {
hasScl = true
shouldAddPort = true
}
if (shouldAddPort && !conflictingPortIds.includes(portId)) {
conflictingPortIds.push(portId)
}
}

if (hasSda && hasScl) {
// Sort conflicting port IDs to ensure deterministic ID generation
const sortedConflicts = [...conflictingPortIds].sort()
const conflictIdStr = sortedConflicts.join("_")

const portDetails = sortedConflicts.map((portId) => {
const port = portMap.get(portId)
const component = sourceComponents.find(
(c) => c.source_component_id === port?.source_component_id,
)
const componentName = component?.name ?? "Unknown"
const portName = port?.name ?? "Unknown"
const i2cRole = port?.is_configured_for_i2c_sda
? "I2C SDA"
: port?.is_configured_for_i2c_scl
? "I2C SCL"
: "Unknown"
return `${componentName}.${portName} (${i2cRole})`
})

errors.push({
type: "source_i2c_misconfigured_error",
source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${conflictIdStr}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are normally generated or something- you should never create ids like this

error_type: "source_i2c_misconfigured_error",
message: `${portDetails.join(" is connected to ")} on the same net. To fix this, ensure SDA and SCL are routed to separate nets.`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad. Give the user exact instructions for how to fix and use toMatchInlineSnapshot for the error in the test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just not a good error but i can't even see it until you have toMatchInlineSnapshot

source_port_ids: conflictingPortIds,
})
}
}

return errors
}
2 changes: 2 additions & 0 deletions lib/run-all-checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { checkSourceTracesHavePcbTraces } from "./check-source-traces-have-pcb-t
import { checkPcbTracesOutOfBoard } from "./check-trace-out-of-board/checkTraceOutOfBoard"
import { checkPcbComponentOverlap } from "./check-pcb-components-overlap/checkPcbComponentOverlap"
import { checkPinMustBeConnected } from "./check-pin-must-be-connected"
import { checkI2cSdaConnectedToSclMisconfigured } from "./check-i2c-sda-connected-to-scl"
import type { AnyCircuitElement } from "circuit-json"

export async function runAllChecks(circuitJson: AnyCircuitElement[]) {
Expand All @@ -24,5 +25,6 @@ export async function runAllChecks(circuitJson: AnyCircuitElement[]) {
...checkPcbTracesOutOfBoard(circuitJson),
...checkPcbComponentOverlap(circuitJson),
...checkPinMustBeConnected(circuitJson),
...checkI2cSdaConnectedToSclMisconfigured(circuitJson),
]
}
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@
"@types/bun": "^1.2.8",
"@types/debug": "^4.1.12",
"bun-match-svg": "^0.0.11",
"circuit-json": "^0.0.387",
"circuit-to-svg": "^0.0.166",
"circuit-json": "^0.0.380",
"debug": "^4.3.5",
"tscircuit": "^0.0.525",
"zod": "^3.23.8",
"tsup": "^8.2.3"
"tsup": "^8.2.3",
"zod": "^3.23.8"
},
"peerDependencies": {
"@flatten-js/core": "*",
"circuit-json": "*",
"@tscircuit/math-utils": "*",
"circuit-json-to-connectivity-map": "*",
"@flatten-js/core": "*",
"typescript": "^5.5.3"
}
}
}
71 changes: 71 additions & 0 deletions tests/lib/check-i2c-sda-connected-to-scl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { expect, test } from "bun:test"
import { checkI2cSdaConnectedToSclMisconfigured } from "../../lib/check-i2c-sda-connected-to-scl"
import type { AnyCircuitElement } from "circuit-json"

test("checkI2cSdaConnectedToSclMisconfigured detects SDA connected to SCL", () => {
const circuitJson: AnyCircuitElement[] = [
{
type: "source_port",
source_port_id: "port_sda",
name: "SDA",
is_configured_for_i2c_sda: true,
source_component_id: "comp_1",
},
{
type: "source_component",
source_component_id: "comp_1",
name: "U1",
},
{
type: "source_port",
source_port_id: "port_scl",
name: "SCL",
is_configured_for_i2c_scl: true,
source_component_id: "comp_2",
},
{
type: "source_component",
source_component_id: "comp_2",
name: "U2",
},
{
type: "source_trace",
source_trace_id: "trace_1",
connected_source_port_ids: ["port_sda", "port_scl"],
connected_source_net_ids: [],
},
] as AnyCircuitElement[]

const errors = checkI2cSdaConnectedToSclMisconfigured(circuitJson)
expect(errors).toHaveLength(1)
expect(errors[0].message).toEqual(expect.stringContaining("U2.SCL (I2C SCL)"))
expect(errors[0].message).toEqual(expect.stringContaining("U1.SDA (I2C SDA)"))
expect(errors[0].source_port_ids).toContain("port_sda")
expect(errors[0].source_port_ids).toContain("port_scl")
})

test("checkI2cSdaConnectedToSclMisconfigured allows SDA connected to SDA", () => {
const circuitJson: AnyCircuitElement[] = [
{
type: "source_port",
source_port_id: "port_sda1",
name: "SDA1",
is_configured_for_i2c_sda: true,
},
{
type: "source_port",
source_port_id: "port_sda2",
name: "SDA2",
is_configured_for_i2c_sda: true,
},
{
type: "source_trace",
source_trace_id: "trace_1",
connected_source_port_ids: ["port_sda1", "port_sda2"],
connected_source_net_ids: [],
},
] as AnyCircuitElement[]

const errors = checkI2cSdaConnectedToSclMisconfigured(circuitJson)
expect(errors).toHaveLength(0)
})