0hmX/am62l-lpddr4-ti-pth-router
This code defines a hardware-oriented React/TypeScript setup for a PCB breakout circuit featuring an AM62L LPDDR4 interface, including detailed PCB component placement, routing algorithms, and validation scripts for DDR connections, via placement, and manufacturing constraints.
- Version
- 1.0.1
- License
- unset
- Stars
- 0
scripts/verify-circuit-json.ts
import { readdir } from "node:fs/promises"
import { DDR_CONNECTIONS } from "../ddr-connections"
type CircuitJsonElement = Record<string, unknown> & { type: string }
const circuitJsonPath = process.argv[2] ?? "dist/index/circuit.json"
const circuitJson = (await Bun.file(
circuitJsonPath,
).json()) as CircuitJsonElement[]
const elementsOfType = (type: string) =>
circuitJson.filter((element) => element.type === type)
const namesOf = (elements: CircuitJsonElement[]) =>
elements
.map((element) => element.name)
.filter((name): name is string => typeof name === "string")
.sort()
const expectedTraceNames = DDR_CONNECTIONS.map(
({ traceName }) => traceName,
).sort()
const sourceTraces = elementsOfType("source_trace")
const sourceTraceNames = namesOf(sourceTraces)
const breakoutNames = ["RAM_BREAKOUT", "SOC_BREAKOUT"]
const sourceBreakoutGroups = elementsOfType("source_group").filter((element) =>
breakoutNames.includes(String(element.name)),
)
const pcbBreakoutGroups = elementsOfType("pcb_group").filter((element) =>
breakoutNames.includes(String(element.name)),
)
const breakoutPoints = elementsOfType("pcb_breakout_point")
const pcbTraces = elementsOfType("pcb_trace")
const pcbVias = elementsOfType("pcb_via")
const sourceComponents = elementsOfType("source_component")
const pcbComponents = elementsOfType("pcb_component")
const pcbPads = elementsOfType("pcb_smtpad")
const assert: (condition: unknown, message: string) => asserts condition = (
condition,
message,
) => {
if (!condition) throw new Error(message)
}
assert(
sourceTraces.length === 33,
`Expected 33 source traces, got ${sourceTraces.length}`,
)
assert(
JSON.stringify(sourceTraceNames) === JSON.stringify(expectedTraceNames),
`Source trace names differ:\nexpected ${expectedTraceNames.join(", ")}\nactual ${sourceTraceNames.join(", ")}`,
)
assert(
sourceBreakoutGroups.length === 2,
`Expected two source breakout groups, got ${sourceBreakoutGroups.length}`,
)
assert(
pcbBreakoutGroups.length === 2,
`Expected two breakout PCB groups, got ${pcbBreakoutGroups.length}`,
)
const source = await Bun.file("index.circuit.tsx").text()
const forbiddenSourcePatterns: Array<[RegExp, string]> = [
[/<via(?:\s|>)/i, "<via>"],
[/<breakoutpoint(?:\s|>)/i, "<breakoutpoint>"],
[/<pcbtrace(?:\s|>)/i, "manual PCB trace primitive"],
[/algorithmFn/, "algorithmFn"],
[/GenericLocalAutorouter/, "GenericLocalAutorouter"],
[/\broutingDisabled\b/, "routingDisabled"],
[/\bpcbRoutingDisabled\b/, "pcbRoutingDisabled"],
[/am62l-classified-autorouter/, "am62l-classified-autorouter"],
[/ti-am62l-evm-ddr-routes/, "ti-am62l-evm-ddr-routes"],
]
const forbiddenMatches = forbiddenSourcePatterns
.filter(([pattern]) => pattern.test(source))
.map(([, label]) => label)
assert(
forbiddenMatches.length === 0,
`Forbidden source-level routing constructs found: ${forbiddenMatches.join(", ")}`,
)
const breakoutPointCounts = Object.fromEntries(
pcbBreakoutGroups.map((group) => [
String(group.name),
breakoutPoints.filter((point) => point.pcb_group_id === group.pcb_group_id)
.length,
]),
)
const errorTypes = [
"pcb_autorouting_error",
"pcb_trace_error",
"pcb_pad_trace_clearance_error",
"pcb_via_clearance_error",
]
const errors = Object.fromEntries(
errorTypes.map((type) => [
type,
elementsOfType(type).map((error) => ({
id: error[`${type}_id`] ?? error.pcb_error_id,
message: error.message,
})),
]),
)
const debugDirectory =
process.env.AUTOROUTER_DEBUG_DIR ?? "dist/autorouter-debug"
let phaseInputs: Array<{
file: string
connectionCount: number
routingPcbGroupIds: string[]
}> = []
try {
const debugFiles = await readdir(debugDirectory)
const phaseInputFiles = debugFiles
.filter((file) => /^phase-\d+\.input\.simple-route\.json$/.test(file))
.sort((left, right) =>
left.localeCompare(right, undefined, { numeric: true }),
)
phaseInputs = await Promise.all(
phaseInputFiles.map(async (file) => {
const input = (await Bun.file(`${debugDirectory}/${file}`).json()) as {
connections?: Array<{ routingPcbGroupId?: string }>
}
const connections = input.connections ?? []
return {
file,
connectionCount: connections.length,
routingPcbGroupIds: [
...new Set(
connections
.map((connection) => connection.routingPcbGroupId)
.filter((id): id is string => Boolean(id)),
),
],
}
}),
)
} catch {
phaseInputs = []
}
const installedCorePackage = (await Bun.file(
"node_modules/@tscircuit/core/package.json",
).json()) as { version: string }
assert(
installedCorePackage.version === "0.0.1717",
`Expected installed @tscircuit/core 0.0.1717, got ${installedCorePackage.version}`,
)
assert(
breakoutPoints.length === 66,
`Expected 66 breakout points, got ${breakoutPoints.length}`,
)
assert(
pcbTraces.length === 99,
`Expected 99 routed PCB traces, got ${pcbTraces.length}`,
)
assert(pcbVias.length === 66, `Expected 66 fanout vias, got ${pcbVias.length}`)
for (const via of pcbVias) {
assert(
Math.abs(Number(via.outer_diameter) - 0.4572) <= 1e-9 &&
Math.abs(Number(via.hole_diameter) - 0.2032) <= 1e-9 &&
via.from_layer === "top" &&
via.to_layer === "bottom" &&
Array.isArray(via.layers) &&
via.layers.length === 8 &&
via.layers[0] === "top" &&
via.layers.at(-1) === "bottom",
`Via ${String(via.pcb_via_id)} is not an exact 0.4572/0.2032 mm top-to-bottom PTH`,
)
}
const componentIdForName = (name: string) => {
const sourceComponent = sourceComponents.find(
(component) => component.name === name,
)
const pcbComponent = pcbComponents.find(
(component) =>
component.source_component_id === sourceComponent?.source_component_id,
)
assert(pcbComponent, `Cannot find PCB component for ${name}`)
return String(pcbComponent.pcb_component_id)
}
const packageLandRules = [
{
name: "U1",
expectedCount: 373,
radius: 0.127,
soldermaskMargin: 0.0254,
},
{
name: "U2",
expectedCount: 200,
radius: 0.16,
soldermaskMargin: 0.04,
},
]
for (const rule of packageLandRules) {
const componentPads = pcbPads.filter(
(pad) => pad.pcb_component_id === componentIdForName(rule.name),
)
assert(
componentPads.length === rule.expectedCount,
`${rule.name} expected ${rule.expectedCount} lands, got ${componentPads.length}`,
)
for (const pad of componentPads) {
assert(
Math.abs(Number(pad.radius) - rule.radius) <= 1e-9 &&
Math.abs(Number(pad.soldermask_margin) - rule.soldermaskMargin) <= 1e-9,
`${rule.name} pad ${String(pad.pcb_smtpad_id)} has incorrect land/mask geometry`,
)
}
}
for (const [errorType, errorList] of Object.entries(errors)) {
assert(
errorList.length === 0,
`${errorType} contains ${errorList.length} errors`,
)
}
let routingTiming: unknown = null
if (await Bun.file("dist/routing-timing.json").exists()) {
routingTiming = await Bun.file("dist/routing-timing.json").json()
}
const report = {
circuitJsonPath,
expectedCore: {
version: "0.0.1717",
pr3304BaseGitHead: "646a2658a3c3a539e05146a0c9fa468513cbce33",
localArchiveSha1: "7453eec0fa03821b649ad271ae72e2f03f97446d",
},
installedCoreVersion: installedCorePackage.version,
sourceTraceCount: sourceTraces.length,
sourceTraceNames,
sourceBreakoutGroupCount: sourceBreakoutGroups.length,
pcbBreakoutGroupCount: pcbBreakoutGroups.length,
breakoutPointCount: breakoutPoints.length,
breakoutPointCounts,
automaticPcbTraceCount: pcbTraces.length,
automaticPcbViaCount: pcbVias.length,
packageLandRules,
forbiddenSourceConstructs: forbiddenMatches,
phaseInputs,
errors,
routingTiming,
}
await Bun.write(
"dist/verification-report.json",
`${JSON.stringify(report, null, 2)}\n`,
)
console.log(JSON.stringify(report, null, 2))