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-power-routing.mjs

/** Append critical copper to a fresh placement JSON; no schematic rebuild.
 * node scripts/add-power-routing.mjs INPUT.json OUTPUT.json
 * Editable route data: scripts/power-routing-specs.json.
 */
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)
  throw Error(
    "Usage: node scripts/add-power-routing.mjs INPUT.json OUTPUT.json",
  )
if (!output.endsWith(".json")) throw Error("Output must use a .json extension")
const j = JSON.parse(fs.readFileSync(input, "utf8"))
if (j.some((e) => (e.pcb_trace_id ?? "").startsWith("pcb_trace_power_")))
  throw Error("Power copper already present")
const specs = JSON.parse(
  fs.readFileSync(
    new URL("./power-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 scByName = new Map(of("source_component").map((c) => [c.name, c]))
const sources = of("source_port"),
  ports = of("pcb_port"),
  components = of("pcb_component")
const groundNet = of("source_net").find((n) => n.name === "GND")
const groundVias = new Map(
  [...specs.groundVias, ...(specs.powerVias ?? [])].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 (groundVias.has(name)) {
    const v = groundVias.get(name),
      origin = resolve(v.from)
    return {
      ...v,
      net: origin.net,
      source_port_id: origin.source_port_id,
      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 }
}
// Fail if the matching Power.tsx placement was changed: manual paths are global.
for (const [name, expected] of Object.entries(specs.extraPositions ?? {})) {
  const sc = scByName.get(name),
    pc = components.find(
      (c) => c.source_component_id === sc?.source_component_id,
    )
  if (
    !pc ||
    Math.hypot(pc.center.x - expected.x, pc.center.y - expected.y) > 1e-5 ||
    pc.rotation !== expected.rotation
  )
    throw Error("Placement differs for " + name)
}
for (const [name, cx, cy, rotation] of [
  ...[
    [101, 4],
    [102, -12],
    [103, -28],
  ].flatMap(([id, y]) => {
    const c = 101 + (id - 101) * 2
    return [
      [`U${id}`, -44, y, 0],
      [`L${id}`, -38.5, y - 2.7, 0],
      [`C${c}`, -41.5, y + 4.5, 90],
      [`C${c + 1}`, -33.5, y - 2.5, 90],
      [`R${c}`, -48, y + 2, 90],
      [`R${c + 1}`, -48, y - 1, 90],
      ...(id === 103 ? [] : [[`R${id === 101 ? 107 : 108}`, -34, y + 3, 0]]),
    ]
  }),
]) {
  const sc = scByName.get(name),
    pc = components.find(
      (c) => c.source_component_id === sc?.source_component_id,
    )
  if (
    !pc ||
    Math.hypot(pc.center.x - cx, pc.center.y - cy) > 1e-5 ||
    pc.rotation !== rotation
  )
    throw Error("Placement differs for " + name)
}
const subcircuit = groundNet.subcircuit_id
function via(v, net) {
  const record = {
    type: "pcb_via",
    pcb_via_id: `pcb_via_power_${newRecords.filter((r) => r.type === "pcb_via").length}`,
    x: v.x,
    y: v.y,
    hole_diameter: 0.3,
    outer_diameter: 0.6,
    layers: ["top", "inner1", "inner2", "bottom"],
    from_layer: "top",
    to_layer: "bottom",
    subcircuit_id: subcircuit,
    source_net_id: net.source_net_id,
    subcircuit_connectivity_map_key: net.subcircuit_connectivity_map_key,
    net_is_assignable: false,
  }
  newRecords.push(record)
  return record
}
for (const v of [...specs.groundVias, ...(specs.powerVias ?? [])].filter(
  (v) => !v.implicit,
)) {
  const n = resolve(v.from).net,
    r = via(v, n)
  manifest.push({ name: v.name, type: r.type, id: r.pcb_via_id, net: n.name })
}
for (const spec of specs.routes) {
  const a = resolve(spec.from),
    b = resolve(spec.to)
  if (a.net.name !== b.net.name)
    throw Error(
      "Manual route would short " +
        a.net.name +
        " and " +
        b.net.name +
        ": " +
        spec.name,
    )
  const n = a.net,
    route = [],
    wire = (p, layer, extra = {}) => ({
      route_type: "wire",
      x: p.x,
      y: p.y,
      width: spec.width,
      layer,
      ...extra,
    })
  let layer = a.layer ?? a.layers?.[0] ?? "top"
  route.push(
    wire(a, layer, a.pcb_port_id ? { start_pcb_port_id: a.pcb_port_id } : {}),
  )
  for (const p of spec.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
      const v = via(p, n)
      manifest.push({
        name: spec.name + "_VIA",
        type: v.type,
        id: v.pcb_via_id,
        net: n.name,
      })
    } else route.push(wire(p, layer))
  }
  route.push(
    wire(b, layer, b.pcb_port_id ? { end_pcb_port_id: b.pcb_port_id } : {}),
  )
  const index = manifest.filter((m) => m.type === "pcb_trace").length
  const sourceTrace = {
    type: "source_trace",
    source_trace_id: `source_trace_power_${index}`,
    connected_source_port_ids: [
      ...new Set([a.source_port_id, b.source_port_id].filter(Boolean)),
    ],
    connected_source_net_ids: [n.source_net_id],
    subcircuit_id: subcircuit,
    display_name: spec.name,
    min_trace_thickness: spec.width,
    subcircuit_connectivity_map_key: n.subcircuit_connectivity_map_key,
  }
  newRecords.push(sourceTrace)
  const trace = {
    type: "pcb_trace",
    pcb_trace_id: `pcb_trace_power_${index}`,
    source_trace_id: sourceTrace.source_trace_id,
    subcircuit_id: subcircuit,
    route,
    trace_length: route
      .slice(1)
      .reduce(
        (sum, p, i) => sum + Math.hypot(p.x - route[i].x, p.y - route[i].y),
        0,
      ),
  }
  newRecords.push(trace)
  manifest.push({
    name: spec.name,
    type: trace.type,
    id: trace.pcb_trace_id,
    net: n.name,
    source_net_id: n.source_net_id,
    source_trace_id: trace.source_trace_id,
    from: spec.from,
    to: spec.to,
  })
}
const result = [...j, ...newRecords],
  report = {}
for (const name of [
  "checkEachPcbTraceNonOverlapping",
  "checkPadTraceClearance",
  "checkDifferentNetViaSpacing",
  "checkViaPadClearance",
  "checkViaTraceClearance",
  "checkPcbTracesOutOfBoard",
  "checkTracesAreContiguous",
  "checkViasInPads",
])
  report[name] = checks[name](result, { minClearance: 0.2 })
fs.mkdirSync(path.dirname(output), { recursive: true })
fs.writeFileSync(output, JSON.stringify(result, null, 2))
fs.writeFileSync(
  output.replace(/\.json$/, ".power-records.json"),
  JSON.stringify(newRecords, null, 2),
)
fs.writeFileSync(
  output.replace(/\.json$/, ".power-manifest.json"),
  JSON.stringify(manifest, null, 2),
)
fs.writeFileSync(
  output.replace(/\.json$/, ".power-checks.json"),
  JSON.stringify(report, null, 2),
)
console.log(
  JSON.stringify(
    {
      input,
      output,
      traces: newRecords.filter((e) => e.type === "pcb_trace").length,
      vias: newRecords.filter((e) => e.type === "pcb_via").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