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