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 [];
},
};
}