astra/f1c100s

This code defines and renders a four-layer, top-mounted F1C100S module with detailed pin assignments, schematic annotations, and layout profiles, facilitating design, validation, and routing of the hardware including support for decoupling capacitors, crystal oscillators, and external connectors.

Version
0.11.0
License
unset
Stars
0

scripts/validate.ts

import type { AnyCircuitElement } from "circuit-json";
import {
	runAllRoutingChecks,
	runAllPlacementChecks,
	checkViasInPads,
	checkEachPcbTraceNonOverlapping,
} from "@tscircuit/checks";
export async function validateCircuit(json: AnyCircuitElement[]) {
	const errors = [
		...(await runAllRoutingChecks(json)),
		...(await runAllPlacementChecks(json)),
		...checkViasInPads(json),
		...checkEachPcbTraceNonOverlapping(json, { minClearance: 0.1 }),
	].filter(
		(e: any) => e.type.endsWith("_error") || e.error_type?.endsWith("_error"),
	);
	const unique = [...new Map(errors.map((e: any) => [e.message, e])).values()];
	return unique;
}
export function circuitMetrics(json: AnyCircuitElement[]) {
	const traces = json.filter((e: any) => e.type === "pcb_trace") as any[];
	const length = (route: any[]) =>
		route.reduce((sum, p, i) => {
			const a = route[i - 1];
			return (
				sum +
				(a?.route_type === "wire" &&
				p.route_type === "wire" &&
				a.layer === p.layer
					? Math.hypot(a.x - p.x, a.y - p.y)
					: 0)
			);
		}, 0);
	const netTrace = (name: string) => {
		const source = json.find(
			(e: any) => e.type === "source_trace" && e.name === `N_${name}`,
		) as any;
		return traces.filter((t) => t.source_trace_id === source?.source_trace_id);
	};
	const usb = { dm: netTrace("USB_DM"), dp: netTrace("USB_DP") };
	const usbLength = (key: "dm" | "dp") =>
		usb[key].reduce((n, t) => n + length(t.route), 0);
	if (
		usb.dm.length !== 1 ||
		usb.dp.length !== 1 ||
		[...usb.dm, ...usb.dp].some((t) =>
			t.route.some((p: any) => p.route_type !== "wire" || p.layer !== "top"),
		)
	)
		throw new Error("USB pair must use two uninterrupted top-layer traces");
	if (Math.abs(usbLength("dm") - usbLength("dp")) > 0.001)
		throw new Error("USB pair exceeds 1um local skew budget");
	const plated = json.filter((e) => e.type === "pcb_plated_hole") as any[];
	const nonTopExits = plated.filter((h) =>
		traces.some((t) =>
			[t.route[0], t.route.at(-1)].some(
				(p) => p.layer !== "top" && Math.hypot(p.x - h.x, p.y - h.y) < 1e-5,
			),
		),
	);
	return {
		platedTestPoints: plated.length,
		nonTopExitContacts: nonTopExits.length,
		traces: traces.length,
		wireSegments: traces.reduce(
			(sum, t) =>
				sum +
				t.route.filter((p: any, i: number, route: any[]) => {
					const a = route[i - 1];
					return (
						a?.route_type === "wire" &&
						p.route_type === "wire" &&
						a.layer === p.layer &&
						Math.hypot(a.x - p.x, a.y - p.y) > 1e-6
					);
				}).length,
			0,
		),
		vias: json.filter((e) => e.type === "pcb_via").length,
		capacitors: json.filter(
			(e: any) =>
				e.type === "source_component" && e.ftype === "simple_capacitor",
		).length,
		localDecouplers: json.filter(
			(e: any) => e.type === "source_component" && /^C_D\d+$/.test(e.name),
		).length,
		routedLengthMm: +traces
			.reduce((sum, t) => sum + length(t.route), 0)
			.toFixed(3),
		usbDmLengthMm: +usbLength("dm").toFixed(6),
		usbDpLengthMm: +usbLength("dp").toFixed(6),
		usbSkewMm: +Math.abs(usbLength("dm") - usbLength("dp")).toFixed(6),
	};
}