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

src/saved-fanout.ts

import type {
	GenericLocalAutorouter,
	SimpleRouteJson,
	Subcircuit,
} from "tscircuit";

/** Match the reference's final saved-path phase. Signal exits coincide;
 * GND exits join through the module's pours, checked on emitted copper in CI. */
export function attachSavedFanout(instance: Subcircuit) {
	const original = instance.doInitialPcbDesignRuleChecks.bind(instance);
	instance.doInitialPcbDesignRuleChecks = () => {
		const db = instance.root!.db;
		const seen = new Map<string, string>();
		for (const via of db.pcb_via.list()) {
			if (via.subcircuit_id !== instance.subcircuit_id) continue;
			const trace = db.pcb_trace.get(via.pcb_trace_id!);
			const source = trace?.source_trace_id
				? db.source_trace.get(trace.source_trace_id)
				: undefined;
			const net = source?.subcircuit_connectivity_map_key;
			if (!net) throw new Error("Saved via must have a source net identity");
			const key = [
				via.x.toFixed(6),
				via.y.toFixed(6),
				via.hole_diameter,
				via.outer_diameter,
				[...via.layers].sort().join(","),
			].join(":");
			if (seen.has(key)) {
				if (seen.get(key) !== net)
					throw new Error("Different nets share a saved fanout via");
				db.pcb_via.delete(via.pcb_via_id);
			} else seen.set(key, net);
		}
		original();
	};
}

export async function joinSavedFanoutExits(
	input: SimpleRouteJson,
): Promise<GenericLocalAutorouter> {
	for (const c of input.connections) {
		const first = c.pointsToConnect[0];
		if (!first) continue;
		const coincident = c.pointsToConnect.every(
			(p) =>
				Math.hypot(p.x - first.x, p.y - first.y) < 1e-4 &&
				p.layer === first.layer,
		);
		if (coincident) continue;
		// Only GND has distributed plane contacts. Match this by the actual
		// source-net membership of the imported EPAD, not by a generated numeric ID.
		const epad = input.obstacles.find(
			(o) => o.circuitJsonMetadata?.source_port_name === "EPAD",
		);
		if (!epad?.connectedTo.includes(c.name))
			throw new Error(`Saved fanout paths do not meet for ${c.name}`);
	}
	const listeners: Record<string, ((event: any) => void)[]> = {
		complete: [],
		error: [],
		progress: [],
	};
	return {
		input,
		isRouting: false,
		on(event, listener) {
			listeners[event]!.push(listener);
		},
		start() {
			queueMicrotask(() =>
				listeners.complete!.forEach((f) => f({ type: "complete", traces: [] })),
			);
		},
		stop() {},
		solveSync() {
			return [];
		},
	};
}