astra-abse/t113-s3
Allwinner T113-S3 / JLCPCB C5197687: native saved fanout, LCD top, storage right, 127 perimeter exits, 30 decoupling capacitors, connected GND planes; clean DRC and shorts, all 29 vias connected.
- Version
- 1.2.0
- License
- MIT
- Stars
- 0
scripts/verify.ts
import { auditRoutingSource } from "./audit-routing-source";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import assert from "node:assert/strict";
import { Resvg } from "@resvg/resvg-js";
import { runAllChecks } from "@tscircuit/checks";
import type { AnyCircuitElement } from "circuit-json";
import { auditFanout } from "./audit";
import { circuitHash } from "./circuit-hash";
import { auditConnectivity } from "./connectivity";
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg";
import { checkCopperClearance } from "./copper-clearance";
const started = new Date().toISOString();
const commands: { command: string; exitCode: number; log: string }[] = [];
const target = "dist/index/circuit.json";
await mkdir("reports", { recursive: true });
async function run(label: string, command: string[]) {
console.log(`Running ${label}...`);
const process = Bun.spawn(command, {
stdout: "pipe",
stderr: "pipe",
env: { ...Bun.env, NO_COLOR: "1" },
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(process.stdout).text(),
new Response(process.stderr).text(),
process.exited,
]);
const log = `reports/${label}.log`;
await writeFile(log, stdout + stderr);
commands.push({ command: command.join(" "), exitCode, log });
if (
exitCode !== 0 ||
/Async effect error|AutorouterError/.test(stdout + stderr)
)
throw new Error(`${label} failed; see ${log}\n${stdout}\n${stderr}`);
if (/(?:Errors|Warnings):\s*[1-9]\d*/.test(stdout))
throw new Error(
`${label} reported diagnostics despite exit code zero; see ${log}`,
);
}
try {
await auditRoutingSource();
await run("bitmap-compatibility", ["bun", "scripts/patch-cli-bitmap.ts"]);
await run("typecheck", ["node_modules/.bin/tsc", "--noEmit"]);
// The imported footprint is local and SHA-256 verified. Disable supplier
// re-fetching only; every DRC remains enabled, and no ignore flag is used.
await run("build", [
"node_modules/.bin/tsci",
"build",
"index.circuit.tsx",
"--disable-parts-engine",
"--pcb-svgs",
"--schematic-svgs",
]);
await run("tests", ["bun", "test"]);
const data = await readFile(target);
const json = JSON.parse(data.toString()) as AnyCircuitElement[];
const coverage = auditFanout(json);
const connectivity = auditConnectivity(json);
const drc = await runAllChecks(structuredClone(json));
assert.deepEqual(drc, [], "All upstream DRC checks must pass");
const clearance = checkCopperClearance(json);
assert.deepEqual(
clearance.violations,
[],
"Independent source-based copper clearance must pass",
);
for (const subcommand of [
[],
["netlist"],
["pin_specification"],
["placement"],
["source"],
]) {
// This CLI's netlist subcommand accepts TSX only; other checks accept the
// exact prebuilt JSON that the independent audit and short checker use.
const input = subcommand[0] === "netlist" ? "index.circuit.tsx" : target;
await run(`check${subcommand.length ? "-" + subcommand[0] : ""}`, [
"node_modules/.bin/tsci",
"check",
...subcommand,
input,
]);
}
await run("bitmap-regression", ["bun", "scripts/test-bitmap-renderer.ts"]);
for (const mode of ["gerber", "pcb"]) {
await run(`shorts-${mode}`, [
"node_modules/.bin/tsci",
"check",
"shorts",
target,
"--mode",
mode,
"--layer",
"all",
"--pixels-per-mm",
"100",
]);
}
const preview = new Resvg(await readFile("dist/index/pcb.svg"), {
fitTo: { mode: "width", value: 1600 },
});
await writeFile("dist/index/pcb.png", preview.render().asPng());
for (const layer of ["bottom", "inner1"] as const) {
const svg = convertCircuitJsonToPcbSvg(json, { layer });
await writeFile(`dist/index/ground-${layer}.svg`, svg);
await writeFile(
`dist/index/ground-${layer}.png`,
new Resvg(svg, { fitTo: { mode: "width", value: 1600 } })
.render()
.asPng(),
);
}
const schematic = new Resvg(await readFile("dist/index/schematic.svg"), {
fitTo: { mode: "width", value: 1800 },
});
await writeFile("dist/index/schematic.png", schematic.render().asPng());
const report = {
status: "PASS",
started,
completed: new Date().toISOString(),
artifact: target,
circuitSha256: circuitHash(json),
sha256: createHash("sha256").update(data).digest("hex"),
runtime: Bun.version,
versions: Object.fromEntries(
await Promise.all(
[
"tscircuit",
"@tscircuit/core",
"@tscircuit/checks",
"@tscircuit/cli",
].map(async (name) => [
name,
JSON.parse(
await readFile(`node_modules/${name}/package.json`, "utf8"),
).version,
]),
),
),
routingSource: await auditRoutingSource(),
coverage,
connectivity,
drc: { errors: 0, warnings: 0, ignoredChecks: [] },
clearance,
shorts: { gerber: 0, pcb: 0, layers: "all", pixelsPerMm: 100 },
commands,
};
await writeFile(
"reports/verification.json",
JSON.stringify(report, null, 2) + "\n",
);
await writeFile(
"reports/verification.md",
`# T113-S3 fanout verification\n\nStatus: **PASS**\n\n- JLCPCB: C5197687; imported exact footprint retained.\n- Pads: 189 (129 SoC + 60 capacitor); perimeter exits: 127; internal joins: 61; pin 106 deliberately open.\n- LCD: 22 signals exit top; storage: 12 signals exit right.\n- Copper traces: 188, including decoupling and the AGND–EPAD connection.\n- Vias: 29 unique, connected through vias; 0.45 mm pads / 0.20 mm drills.\n- GND: bottom and inner1 pours; all 32 ground pads physically connected.\n- Decoupling: 30 bottom-mounted 0402 capacitors.\n- Core/CLI DRC: 0 errors, 0 warnings; no DRC bypasses.\n- Independent copper check: ${clearance.pairsChecked} pairs, 0 violations.\n- Minimum measured different-net clearance: ${clearance.minimumMeasuredMm.toFixed(6)} mm (rule: 0.10 mm).\n- Gerber and PCB bitmap shorts: 0, all layers, 100 pixels/mm.\n- TypeScript and regression/integration tests pass.\n\nArtifact SHA-256: \`${report.sha256}\`\n\nSee verification.json and individual command logs for exact versions and commands.\n\nScope: fanout copper and routing connectivity; this is not a complete powered SoC design or signal-integrity qualification.\n`,
);
console.log(
"PASS: native saved fanout, 127 perimeter exits; 0 DRC errors, 0 warnings, 0 shorts.",
);
} catch (error) {
await writeFile(
"reports/verification.json",
JSON.stringify(
{
status: "FAIL",
started,
completed: new Date().toISOString(),
commands,
error: String(error),
},
null,
2,
) + "\n",
);
throw error;
}