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

src/schematic.tsx

import { capacitorSpecs } from "./capacitor-specs";
import type { FanoutTracePath } from "@tscircuit/props";
import { BreakoutTestPoint } from "./BreakoutTestPoint";
import { ClockCrystal } from "./ClockCrystal";
import { attachSavedPaths } from "./saved-paths";
import type { LayoutProfile } from "./profiles";
import type { Subcircuit } from "tscircuit";
import { PIN_NETS, RAILS, EXTERNAL_NETS } from "./pin-map";
import { makeLayout } from "./layout";
import {
	SCHEMATIC_BANKS,
	SCHEMATIC_PIN_LABELS,
	createSchematicBox,
} from "./schematic-boxes";
export { SCHEMATIC_BANK_PINS } from "./schematic-boxes";

/** Schematic-only compatibility adapter for the pinned core's JSON inflator.
 * Run immediately after inflation, before source/port/schematic rendering.
 * The imported PCB footprint and positions are retained.
 */
export function attachSchematicLayout(
	instance: Subcircuit | null,
	layout: "default" | "custom" = "default",
	profile: LayoutProfile = "native",
	selected: ReadonlySet<string> = new Set(EXTERNAL_NETS),
	paths?: FanoutTracePath[],
) {
	if (!instance || (instance as any).__f1cSchematicLayout) return;
	(instance as any).__f1cSchematicLayout = true;
	const inflate = instance.doInitialInflateSubcircuitCircuitJson.bind(instance);
	instance.doInitialInflateSubcircuitCircuitJson = () => {
		inflate();
		const module = instance.selectOne(".MODULE") as any;
		const importedCrystal = module.selectOne(".Y1");
		module.children = module.children.filter((c: any) => c !== importedCrystal);
		const placement = makeLayout(profile);
		module.add(<ClockCrystal placement={placement.crystal} />);
		for (const terminal of placement.terminals) {
			if (!selected.has(terminal.name)) continue;
			const imported = module.children.find(
				(c: any) => c.name === terminal.name,
			);
			if (!imported) throw Error(`Missing imported exit ${terminal.name}`);
			module.children = module.children.filter((c: any) => c !== imported);
			module.add(<BreakoutTestPoint terminal={terminal} />);
		}
		configureSchematic(instance, layout);
		attachSavedPaths(instance, profile, paths);
	};
}

function configureSchematic(
	instance: Subcircuit,
	layout: "default" | "custom",
) {
	const patch = (component: any, props: Record<string, unknown>) => {
		if (!component)
			throw new Error("Missing component while arranging F1C100S schematic");
		Object.assign(component.props, props);
		Object.assign(component._parsedProps, props);
	};
	const find = (name: string) =>
		instance.selectOne(`.MODULE > .${name}`) as any;
	const u1 = find("U1");
	patch(u1, { noSchematicRepresentation: true });
	const labelsByPin = SCHEMATIC_PIN_LABELS;
	for (const [p, label] of Object.entries(labelsByPin)) {
		const port = u1.selectOne(`.pin${p}`, { type: "port" });
		patch(port, {
			aliases: [...new Set([...(port._parsedProps.aliases ?? []), label])],
		});
	}
	for (const terminal of EXTERNAL_NETS) {
		const c = find(terminal);
		if (!c) continue;
		for (const child of [...c.children])
			if (child.componentName === "Symbol") c.remove(child);
		patch(c, { noSchematicRepresentation: true });
	}
	patch(instance.selectOne(".MODULE"), {
		schTraceAutoLabelEnabled: true,
		schMaxTraceDistance: 3,
		schLayout: { layoutMode: "relative" },
	});
	// Sections control net-label boundaries; positions are explicitly authored.
	// The pinned core otherwise runs match-pack even for positioned sections.
	instance._doInitialSchematicLayoutSections = () => {};
	(
		instance.selectOne(".MODULE") as Subcircuit
	)._doInitialSchematicLayoutSections = () => {};
	const sectionNames = new Set<string>();
	const section = (key: string, title = key) => {
		const name = `${instance.name}_${key}`;
		if (sectionNames.has(name)) return name;
		sectionNames.add(name);
		instance.add(
			<schematicsection
				name={name}
				displayName={title}
				sectionTitleFontSize={0.3}
			/>,
		);
		return name;
	};
	const supportOffsetY = layout === "custom" ? 0 : -20;
	if (layout === "default") {
		const positions = [
			[-10, 5],
			[-1, 5],
			[8, 5],
			[-10, -4],
			[-1, -4],
			[8, -4],
			[-11, 6.5 + supportOffsetY],
		];
		SCHEMATIC_BANKS.forEach((bank, i) =>
			instance.add(
				createSchematicBox(bank, {
					chipRef: ".MODULE .U1",
					schX: positions[i]![0],
					schY: positions[i]![1],
					schSectionName: section(bank.name, bank.title),
				}),
			),
		);
	}
	const passives = makeLayout("native").passives;
	// Preserve capacitor ratings and the oscillator's specified C0G loads;
	// the pinned Circuit JSON inflator omits these source properties.
	for (const p of passives)
		if (p.kind === "capacitor")
			patch(find(p.name), {
				...(({ footprint, ...spec }) => spec)(capacitorSpecs(p.name, p.value)),
			});
	const place = (name: string, x: number, y: number, sectionName: string) => {
		const c = find(name);
		// Inflated custom symbol geometry omits standard reference/value text.
		for (const child of [...c.children])
			if (child.componentName === "Symbol") c.remove(child);
		patch(c, {
			schX: x,
			schY: y + supportOffsetY,
			schRotation: 270,
			schSectionName: sectionName,
		});
	};
	RAILS.forEach((rail, row) => {
		const caps = passives.filter(
			(p) =>
				p.a === rail && (p.name.startsWith("C_D") || p.name.startsWith("C_B_")),
		);
		const [x, y] = [
			[-6, 8],
			[-2, 8],
			[4, 8],
			[-6, 5],
			[2, 5],
			[6, 5],
			[10, 5],
		][row]!;
		const key = section("DECOUPLING", "Decoupling");
		caps.forEach((c, i) => place(c.name, x + i * 1.1, y, key));
	});
	for (const [i, net] of ["VRA1", "VRA2", "TV_VRN", "TV_VRP"].entries()) {
		const x = -12 + i * 6.5;
		const key = section("ANALOG_REFERENCES", "Analog references");
		place(`C_${net}`, x, -5, key);
		if (net.startsWith("VRA")) place(`R_${net}`, x + 1.5, -5, key);
	}
	const sv = section("SYSTEM_SUPPORT", "Clock / reset / SDRAM"),
		reset = sv;
	place("R_SV_H", -12, 0, sv);
	place("R_SV_L", -12, -2, sv);
	place("C_SV_H", -10, 0, sv);
	place("C_SV_L", -10, -2, sv);
	place("R_RESET", -7, 0, reset);
	place("C_RESET", -7, -2, reset);
	const clock = sv;
	place("Y1", 7, -1, clock);
	patch(find("Y1"), { schRotation: 0 });
	place("C_OSCI", 4.5, -2, clock);
	place("C_OSCO", 9.5, -2, clock);
	place(
		"C_TV_REF",
		10.5,
		-5,
		section("ANALOG_REFERENCES", "Analog references"),
	);
	const pullups = section("PULLUPS", "SDMMC / SPI / I²C pull-ups");
	passives
		.filter((p) => p.name.startsWith("R_PU_"))
		.forEach((p, i) => place(p.name, -11 + i * 3.1, -8, pullups));
	for (const trace of instance.selectAll("trace") as any[]) {
		const net = trace._parsedProps.path
			?.find((p: string) => p.startsWith("net."))
			?.slice(4);
		if (net) patch(trace, { name: `N_${net}`, schDisplayLabel: net });
		else {
			const cap = trace._parsedProps.path
				?.join(" ")
				.match(/\.C_D(\d+)\s*>\s*\.pin1/);
			if (cap) patch(trace, { name: `D_C_D${cap[1]}_U1_pin${cap[1]}` });
		}
	}
}