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;
}