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))