Skip to content
Merged
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
60 changes: 58 additions & 2 deletions lib/components/normal-components/Crystal.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { type CrystalPinLabels, crystalProps } from "@tscircuit/props"
import type {
SourcePort,
SourceSimpleCrystalInput,
SourceTrace,
} from "circuit-json"
import { formatSiUnit } from "format-si-unit"
import { type BaseSymbolName, type Ftype } from "lib/utils/constants"
import { NormalComponent } from "../base-components/NormalComponent/NormalComponent"
import type { SourceSimpleCrystalInput } from "circuit-json"
import { formatSiUnit } from "format-si-unit"

type CrystalPorts = CrystalPinLabels
type SourcePortId = SourcePort["source_port_id"]
type SubcircuitConnectivityMapKey = NonNullable<
SourceTrace["subcircuit_connectivity_map_key"]
>

const DEFAULT_CRYSTAL_MAX_TRACE_LENGTH_MM = 10

export class Crystal extends NormalComponent<
typeof crystalProps,
Expand Down Expand Up @@ -69,4 +79,50 @@ export class Crystal extends NormalComponent<

this.source_component_id = source_component.source_component_id
}

override doInitialSourceDesignRuleChecks(): void {
super.doInitialSourceDesignRuleChecks()
if (!this.source_component_id) return

const { db } = this.root!
const maximumTraceLength =
this._parsedProps.maxTraceLength ?? DEFAULT_CRYSTAL_MAX_TRACE_LENGTH_MM
const crystalSourcePortIds = new Set<SourcePortId>()
const crystalConnectivityMapKeys = new Set<SubcircuitConnectivityMapKey>()

for (const sourcePort of db.source_port.list()) {
if (sourcePort.source_component_id !== this.source_component_id) continue

crystalSourcePortIds.add(sourcePort.source_port_id)
if (sourcePort.subcircuit_connectivity_map_key) {
crystalConnectivityMapKeys.add(
sourcePort.subcircuit_connectivity_map_key,
)
}
}

for (const sourceTrace of db.source_trace.list()) {
const isDirectlyConnectedToCrystal =
sourceTrace.connected_source_port_ids.some((sourcePortId) =>
crystalSourcePortIds.has(sourcePortId),
)
const isOnCrystalNet =
sourceTrace.subcircuit_connectivity_map_key !== undefined &&
crystalConnectivityMapKeys.has(
sourceTrace.subcircuit_connectivity_map_key,
)

if (!isDirectlyConnectedToCrystal && !isOnCrystalNet) continue
if (
typeof sourceTrace.max_length === "number" &&
sourceTrace.max_length <= maximumTraceLength
) {
continue
}

db.source_trace.update(sourceTrace.source_trace_id, {
max_length: maximumTraceLength,
})
}
}
}
6 changes: 0 additions & 6 deletions lib/components/primitive-components/Group/Group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ import type { ISubcircuit } from "./Subcircuit/ISubcircuit"
import { addPortIdsToTracesAtJumperPads } from "./add-port-ids-to-traces-at-jumper-pads"
import { getSourceTraceIdForRoutedTrace } from "./get-source-trace-id-for-routed-trace"
import { insertAutoplacedJumpers } from "./insert-autoplaced-jumpers"
import { insertPcbTraceTooLongWarnings } from "./insert-pcb-trace-too-long-warnings"
import {
deleteExistingPcbTracesReplacedBy,
getExistingPcbTracesForReroute,
Expand Down Expand Up @@ -2061,11 +2060,6 @@ export class Group<Props extends z.ZodType<any, any, any> = typeof groupProps>
})
}
}

insertPcbTraceTooLongWarnings({
db,
subcircuitId: this.subcircuit_id!,
})
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ConnectivityMap } from "circuit-json-to-connectivity-map"
import type { Group } from "./Group"
import type { SourceTrace } from "circuit-json"
import { ConnectivityMap } from "circuit-json-to-connectivity-map"
import type { TraceI } from "../Trace/TraceI"
import type { Via } from "../Via"
import type { Group } from "./Group"

export function Group_doInitialSourceAddConnectivityMapKey(group: Group<any>) {
if (!group.isSubcircuit) return
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,9 @@ export const getMaxLengthFromConnectedComponents = (
return sourceComponent.max_decoupling_trace_length
}

if (sourceComponent?.ftype === "simple_crystal") {
const crystalProps = (
port.parent as unknown as {
_parsedProps?: { maxTraceLength?: number }
}
)?._parsedProps

return crystalProps?.maxTraceLength ?? 10
}

return null
})
.filter((length): length is number => length !== null)
.filter((length): length is number => typeof length === "number")

if (componentMaxLengths.length === 0) return undefined
return Math.min(...componentMaxLengths)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@tscircuit/alphabet": "0.0.25",
"@tscircuit/breakout-point-solver": "github:tscircuit/breakout-point-solver#bac9629",
"@tscircuit/capacity-autorouter": "^0.0.710",
"@tscircuit/checks": "0.0.145",
"@tscircuit/checks": "0.0.146",
"@tscircuit/circuit-json-util": "^0.0.101",
"@tscircuit/common": "^0.0.20",
"@tscircuit/copper-pour-solver": "0.0.39",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { expect, test } from "bun:test"
import { getTestFixture } from "tests/fixtures/get-test-fixture"

test("configured crystal maximum propagates without replacing stricter limits", () => {
const { circuit } = getTestFixture()

circuit.add(
<board routingDisabled>
<net name="XTAL_OUT" />
<net name="UNRELATED" />
<crystal
name="Y1"
frequency="12MHz"
loadCapacitance="10pF"
maxTraceLength="5mm"
/>
<chip name="U1" pinLabels={{ "1": "XTAL_OUT", "2": "OTHER" }} />
<capacitor name="C1" capacitance="10pF" maxDecouplingTraceLength={3} />
<capacitor name="C2" capacitance="10pF" />
<trace from=".Y1 > .pin1" to="net.XTAL_OUT" />
<trace from=".U1 > .XTAL_OUT" to="net.XTAL_OUT" />
<trace from=".C1 > .pin1" to="net.XTAL_OUT" />
<trace from=".C2 > .pin1" to="net.UNRELATED" />
</board>,
)

circuit.render()

const sourceTraces = circuit.db.source_trace.list()
expect(
Object.fromEntries(
sourceTraces.map((trace) => [trace.display_name, trace.max_length]),
),
).toEqual({
".Y1 > .pin1 to net.XTAL_OUT": 5,
".U1 > .XTAL_OUT to net.XTAL_OUT": 5,
".C1 > .pin1 to net.XTAL_OUT": 3,
".C2 > .pin1 to net.UNRELATED": undefined,
})
})
Original file line number Diff line number Diff line change
@@ -1,46 +1,55 @@
import { expect, test } from "bun:test"
import { getTestFixture } from "tests/fixtures/get-test-fixture"

test("crystal traces warn above the default or configured maximum length", () => {
test("traces on a crystal net inherit its maximum length and warn when too long", async () => {
const { circuit } = getTestFixture()

circuit.add(
<board width="40mm" height="20mm">
<board width="60mm" height="30mm">
<net name="XTAL_IN" />
<crystal
name="Y1"
frequency="12MHz"
loadCapacitance="10pF"
footprint="0402"
pcbX={-8}
pcbY={4}
pcbX={-20}
pcbY={0}
/>
<resistor name="R1" resistance="1k" footprint="0402" pcbX={8} pcbY={4} />
<trace from=".Y1 > .pin1" to=".R1 > .pin1" pcbStraightLine />

<crystal
name="Y2"
frequency="12MHz"
loadCapacitance="10pF"
<chip
name="U1"
footprint="soic8"
pcbX={20}
pcbY={0}
pinLabels={{ "1": "XTAL_IN", "2": "GND" }}
/>
<capacitor
name="C1"
capacitance="10pF"
footprint="0402"
pcbX={-8}
pcbY={-4}
maxTraceLength="20mm"
pcbX={0}
pcbY={10}
/>
<resistor name="R2" resistance="1k" footprint="0402" pcbX={8} pcbY={-4} />
<trace from=".Y2 > .pin1" to=".R2 > .pin1" pcbStraightLine />
<trace from=".Y1 > .pin1" to="net.XTAL_IN" />
<trace from=".U1 > .XTAL_IN" to="net.XTAL_IN" />
<trace from=".C1 > .pin1" to="net.XTAL_IN" />
</board>,
)

circuit.render()
await circuit.renderUntilSettled()

const sourceTraces = circuit.db.source_trace.list()
expect(sourceTraces.map((trace) => trace.max_length)).toEqual([10, 20])
expect(sourceTraces.map((trace) => trace.max_length)).toEqual([10, 10, 10])

const warnings = circuit.db.pcb_trace_too_long_warning.list()
expect(warnings).toHaveLength(1)
expect(warnings[0]).toMatchObject({
source_trace_id: sourceTraces[0].source_trace_id,
maximum_trace_length: 10,
})
expect(warnings[0].actual_trace_length).toBeGreaterThan(10)
expect(warnings).toHaveLength(2)
expect(warnings.map((warning) => warning.source_trace_id).sort()).toEqual(
[sourceTraces[0].source_trace_id, sourceTraces[1].source_trace_id].sort(),
)
expect(
warnings.every(
(warning) =>
warning.maximum_trace_length === 10 &&
warning.actual_trace_length > warning.maximum_trace_length,
),
).toBe(true)
})
Loading