krishnax12/t113-minimal-linux

Allwinner T113-S3 dual-Cortex-A7 SoC with integrated 128MB DDR3, multi-rail decoupling, 24MHz and 32.768kHz oscillators, filtering, and exposed test points.

Version
0.1.1
License
unset
Stars
0

scripts/add-usb-routing.mjs

/** Append checked USB copper to placement/power JSON.
 * node scripts/add-usb-routing.mjs INPUT.json OUTPUT.json
 * USB geometry: .15mm width/.16mm edge gap on top; local bottom crossover branches.
 * Via and source-net identities are preserved for DSN export and subsequent pours.
 */
import fs from "node:fs"
import path from "node:path"
import * as checks from "@tscircuit/checks"
const [input, output] = process.argv.slice(2)
if (!input || !output || !output.endsWith(".json"))
  throw Error("Usage: node scripts/add-usb-routing.mjs INPUT.json OUTPUT.json")
const j = JSON.parse(fs.readFileSync(input, "utf8"))
if (
  j.some(
    (e) =>
      (e.pcb_trace_id ?? "").startsWith("pcb_trace_usb_") ||
      (e.pcb_via_id ?? "").startsWith("pcb_via_usb_"),
  )
)
  throw Error("USB copper already present")
const specs = JSON.parse(
  fs.readFileSync(new URL("./usb-routing-specs.json", import.meta.url), "utf8"),
)
const of = (t) => j.filter((e) => e.type === t)
const parent = new Map(),
  find = (x) => {
    if (!parent.has(x)) parent.set(x, x)
    if (parent.get(x) !== x) parent.set(x, find(parent.get(x)))
    return parent.get(x)
  }
const join = (ids) => {
  for (const id of ids.slice(1)) parent.set(find(id), find(ids[0]))
}
for (const t of of("source_trace"))
  join([
    ...(t.connected_source_port_ids ?? []),
    ...(t.connected_source_net_ids ?? []),
  ])
for (const t of of("source_component_internal_connection"))
  join(t.source_port_ids ?? [])
for (const t of of("source_component"))
  for (const ids of t.internally_connected_source_port_ids ?? []) join(ids)
const netByRoot = new Map(
  of("source_net").map((n) => [find(n.source_net_id), n]),
)
const netByName = new Map(of("source_net").map((n) => [n.name, n]))
const scByName = new Map(of("source_component").map((c) => [c.name, c]))
const sources = of("source_port"),
  ports = of("pcb_port")
const viaByName = new Map(specs.vias.map((v) => [v.name, v]))
const newRecords = [],
  manifest = []
function resolve(selector) {
  const m = selector.match(/^\.([^ ]+) > \.(.+)$/)
  if (!m) throw Error("Invalid selector " + selector)
  const [, name, pin] = m
  if (viaByName.has(name)) {
    const v = viaByName.get(name),
      net = netByName.get(v.net)
    if (!net) throw Error("Missing net " + v.net)
    return { ...v, net, layer: pin === "bottom" ? "bottom" : "top" }
  }
  const sc = scByName.get(name)
  if (!sc) throw Error("Missing component " + name)
  const sp = sources.find(
    (p) =>
      p.source_component_id === sc.source_component_id &&
      (p.pin_number === Number(pin.replace("pin", "")) ||
        p.name === pin ||
        p.port_hints?.includes(pin)),
  )
  if (!sp) throw Error("Missing source port " + selector)
  const pp = ports.find((p) => p.source_port_id === sp.source_port_id)
  if (!pp) throw Error("Missing pcb port " + selector)
  const net = netByRoot.get(find(sp.source_port_id))
  if (!net) throw Error("Unnamed net " + selector)
  return { ...pp, net }
}
for (const [i, v] of specs.vias.entries()) {
  const net = netByName.get(v.net)
  if (!net) throw Error("Missing via net " + v.net)
  const record = {
    type: "pcb_via",
    pcb_via_id: `pcb_via_usb_${i}`,
    x: v.x,
    y: v.y,
    hole_diameter: v.holeDiameter,
    outer_diameter: v.outerDiameter,
    layers: ["top", "inner1", "inner2", "bottom"],
    from_layer: "top",
    to_layer: "bottom",
    subcircuit_id: net.subcircuit_id,
    source_net_id: net.source_net_id,
    subcircuit_connectivity_map_key: net.subcircuit_connectivity_map_key,
    net_is_assignable: false,
  }
  newRecords.push(record)
  manifest.push({
    name: v.name,
    type: "pcb_via",
    id: record.pcb_via_id,
    net: net.name,
    source_net_id: net.source_net_id,
  })
}
for (const [i, s] of specs.routes.entries()) {
  const a = resolve(s.from),
    b = resolve(s.to)
  if (a.net.name !== s.net || b.net.name !== s.net)
    throw Error(
      `Route ${s.name} expects ${s.net}, endpoints are ${a.net.name}/${b.net.name}; confirm FEL ESD channel swap`,
    )
  for (const [p, e, label] of [
    [a, s.start, s.from],
    [b, s.end, s.to],
  ])
    if (Math.hypot(p.x - e.x, p.y - e.y) > 1e-5)
      throw Error(`USB placement changed at ${label}`)
  const net = a.net
  let layer = s.layer
  const wire = (p, extra = {}) => ({
    route_type: "wire",
    x: p.x,
    y: p.y,
    width: s.width,
    layer,
    ...extra,
  })
  const route = [
    wire(a, a.pcb_port_id ? { start_pcb_port_id: a.pcb_port_id } : {}),
  ]
  for (const p of s.path) {
    if (p.via) {
      route.push({
        route_type: "via",
        x: p.x,
        y: p.y,
        from_layer: layer,
        to_layer: p.toLayer,
      })
      layer = p.toLayer
    } else route.push(wire(p))
  }
  route.push(wire(b, b.pcb_port_id ? { end_pcb_port_id: b.pcb_port_id } : {}))
  const source = {
    type: "source_trace",
    source_trace_id: `source_trace_usb_${i}`,
    connected_source_port_ids: [
      ...new Set([a.source_port_id, b.source_port_id].filter(Boolean)),
    ],
    connected_source_net_ids: [net.source_net_id],
    subcircuit_id: net.subcircuit_id,
    display_name: s.name,
    min_trace_thickness: s.width,
    subcircuit_connectivity_map_key: net.subcircuit_connectivity_map_key,
  }
  const length = route
    .slice(1)
    .reduce(
      (sum, p, k) => sum + Math.hypot(p.x - route[k].x, p.y - route[k].y),
      0,
    )
  const trace = {
    type: "pcb_trace",
    pcb_trace_id: `pcb_trace_usb_${i}`,
    source_trace_id: source.source_trace_id,
    source_net_id: net.source_net_id,
    subcircuit_id: net.subcircuit_id,
    route,
    trace_length: length,
  }
  newRecords.push(source, trace)
  manifest.push({
    name: s.name,
    type: "pcb_trace",
    id: trace.pcb_trace_id,
    net: net.name,
    source_net_id: net.source_net_id,
    source_trace_id: source.source_trace_id,
    from: s.from,
    to: s.to,
    length,
  })
}
const result = [...j, ...newRecords],
  report = {}
for (const name of [
  "checkEachPcbTraceNonOverlapping",
  "checkPadTraceClearance",
  "checkDifferentNetViaSpacing",
  "checkViaPadClearance",
  "checkViaTraceClearance",
  "checkPcbTracesOutOfBoard",
  "checkTracesAreContiguous",
  "checkViasInPads",
])
  report[name] = checks[name](result, { minClearance: specs.minClearance })
fs.mkdirSync(path.dirname(output), { recursive: true })
fs.writeFileSync(output, JSON.stringify(result, null, 2))
fs.writeFileSync(
  output.replace(/\.json$/, ".usb-records.json"),
  JSON.stringify(newRecords, null, 2),
)
fs.writeFileSync(
  output.replace(/\.json$/, ".usb-manifest.json"),
  JSON.stringify(manifest, null, 2),
)
fs.writeFileSync(
  output.replace(/\.json$/, ".usb-checks.json"),
  JSON.stringify(report, null, 2),
)
console.log(
  JSON.stringify(
    {
      input,
      output,
      traces: specs.routes.length,
      vias: specs.vias.length,
      checks: Object.fromEntries(
        Object.entries(report).map(([k, v]) => [k, v.length]),
      ),
    },
    null,
    2,
  ),
)
if (Object.values(report).some((v) => v.length)) process.exitCode = 1