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