astra/f1c100s
The code defines the physical pin layout, labels, and footprint for the F1C100S system-on-chip (SoC) component used in electronic devices.
- Version
- 0.9.2
- License
- unset
- Stars
- 0
src/layout.ts
import { EXTERNAL_NETS, PIN_NETS, RAILS, pinPosition } from "./pin-map";
import {
PROFILE_LAYOUTS,
EXIT_DIAMETER,
TERMINAL_EDGE,
type LayoutProfile,
type Side,
} from "./profiles";
export interface Passive {
name: string;
kind: "capacitor" | "resistor";
value: string;
x: number;
y: number;
rotation: number;
a: string;
b: string;
}
export interface Terminal {
name: string;
x: number;
y: number;
side: Side;
width: number;
height: number;
}
export function makeLayout(profile: LayoutProfile) {
const spec = PROFILE_LAYOUTS[profile];
const source = (net: string) =>
pinPosition(
Number(Object.keys(PIN_NETS).find((p) => PIN_NETS[+p] === net)),
spec.rotation,
);
const sideFor = (p: { x: number; y: number }): Side =>
Math.abs(p.x) > Math.abs(p.y)
? p.x > 0
? "right"
: "left"
: p.y > 0
? "top"
: "bottom";
const groups: Record<Side, string[]> = {
top: [],
right: [],
bottom: [],
left: [],
};
for (const net of EXTERNAL_NETS) {
let side = sideFor(source(net));
if (net.startsWith("LCD_") && spec.lcd) side = spec.lcd;
if ((net.startsWith("SPI0_") || net.startsWith("SDMMC0_")) && spec.storage)
side = spec.storage;
groups[side].push(net);
}
const terminals: Terminal[] = [];
for (const side of Object.keys(groups) as Side[]) {
// Keep each bus contiguous; retain physical pin order within a bus.
const category = (n: string) =>
n.startsWith("LCD_")
? 0
: n.startsWith("SPI0_")
? 1
: n.startsWith("SDMMC0_")
? 2
: n.startsWith("USB_")
? 3
: 4;
const usb: string[] = groups[side].filter(
(n) => n === "USB_DP" || n === "USB_DM",
);
const blocked = usb.map((n) =>
side === "top" || side === "bottom" ? source(n).x : -source(n).y,
);
for (const name of usb) {
const p = { ...source(name) };
const axis = side === "left" || side === "right" ? "y" : "x";
const middle =
usb.reduce((sum, n) => sum + source(n)[axis], 0) / usb.length;
p[axis] += Math.sign(p[axis] - middle) * 0.125;
const horizontal = side === "left" || side === "right";
terminals.push({
name,
side,
x: horizontal
? side === "left"
? -TERMINAL_EDGE
: TERMINAL_EDGE
: p.x,
y: horizontal ? p.y : side === "top" ? TERMINAL_EDGE : -TERMINAL_EDGE,
width: EXIT_DIAMETER,
height: EXIT_DIAMETER,
});
}
const list = groups[side]
.filter((n) => !usb.includes(n))
.sort(
(a, b) =>
category(a) - category(b) ||
(side === "top" || side === "bottom"
? source(a).x - source(b).x
: source(b).y - source(a).y),
);
const slots = Array.from({ length: 37 }, (_, i) => -11.7 + i * 0.65).filter(
(t) => blocked.every((v) => Math.abs(t - v) > 0.6),
);
if (slots.length < list.length)
throw new Error(`Too many terminals on ${side}`);
const start = Math.floor((slots.length - list.length) / 2);
list.forEach((name, i) => {
const t = slots[start + i]!;
terminals.push({
name,
side,
x:
side === "left"
? -TERMINAL_EDGE
: side === "right"
? TERMINAL_EDGE
: t,
y:
side === "top"
? TERMINAL_EDGE
: side === "bottom"
? -TERMINAL_EDGE
: -t,
width: EXIT_DIAMETER,
height: EXIT_DIAMETER,
});
});
}
const angle = (spec.rotation * Math.PI) / 180;
const rotate = (x: number, y: number) => ({
x: +(x * Math.cos(angle) - y * Math.sin(angle)).toFixed(6),
y: +(x * Math.sin(angle) + y * Math.cos(angle)).toFixed(6),
});
const crystal = { ...rotate(1.6, 8.6), rotation: spec.rotation };
const passives: Passive[] = [
{
name: "C_OSCI",
kind: "capacitor",
value: "18pF",
a: "HOSCI",
b: "GND",
...rotate(-1.2, 7.725),
rotation: spec.rotation + 180,
},
{
name: "C_OSCO",
kind: "capacitor",
value: "18pF",
a: "HOSCO",
b: "GND",
...rotate(4.4, 9.475),
rotation: spec.rotation,
},
];
function add(
name: string,
kind: Passive["kind"],
value: string,
a: string,
b: string,
near: { x: number; y: number },
) {
const preferred = sideFor(near);
let chosen: { x: number; y: number } | undefined;
for (const side of [
preferred,
...(["top", "right", "bottom", "left"] as Side[]).filter(
(s) => s !== preferred,
),
]) {
const horizontal = side === "left" || side === "right";
const tangent = horizontal ? near.y : near.x;
for (const r of [8, 10.45]) {
for (const offset of [
0, 1.8, -1.8, 3.6, -3.6, 5.4, -5.4, 7.2, -7.2, 9, -9, 12, -12, 15,
-15, 18, -18,
]) {
const t = Math.max(-10.7, Math.min(10.7, tangent + offset));
const p = {
x: horizontal ? (side === "right" ? r : -r) : t,
y: horizontal ? t : side === "top" ? r : -r,
};
// Reserve the complete crystal body/land pattern and local clock wiring.
const unrot = rotateBack(p.x, p.y);
if (
unrot.x > -2.3 &&
unrot.x < 5.5 &&
unrot.y > 6.9 &&
unrot.y < 10.65
)
continue;
if (
terminals.some((terminal) => {
if (
Math.abs(p.x - terminal.x) < 0.88 + terminal.width / 2 &&
Math.abs(p.y - terminal.y) < 0.42 + terminal.height / 2
)
return true;
if (!terminal.name.startsWith("USB_")) return false;
const start = source(terminal.name);
return (
p.x > Math.min(start.x, terminal.x) - 0.95 &&
p.x < Math.max(start.x, terminal.x) + 0.95 &&
p.y > Math.min(start.y, terminal.y) - 0.5 &&
p.y < Math.max(start.y, terminal.y) + 0.5
);
})
)
continue;
if (
passives.every(
(q) => Math.abs(q.x - p.x) > 2.35 || Math.abs(q.y - p.y) > 1.65,
)
) {
chosen = p;
break;
}
}
if (chosen) break;
}
if (chosen) break;
}
if (!chosen) throw Error(`Cannot place ${name}`);
passives.push({ name, kind, value, ...chosen, rotation: 0, a, b });
}
function rotateBack(x: number, y: number) {
return {
x: x * Math.cos(angle) + y * Math.sin(angle),
y: -x * Math.sin(angle) + y * Math.cos(angle),
};
}
for (const [pin, net] of Object.entries(PIN_NETS)) {
if ((RAILS as readonly string[]).includes(net))
add(
`C_D${pin}`,
"capacitor",
"100nF",
net,
"GND",
pinPosition(+pin, spec.rotation),
);
}
for (const net of RAILS)
add(
`C_B_${net}`,
"capacitor",
net === "VCC_DRAM" ? "1uF" : "10uF",
net,
"GND",
source(net),
);
for (const net of ["VRA1", "VRA2"]) {
add(`C_${net}`, "capacitor", "1uF", net, "GND", source(net));
add(`R_${net}`, "resistor", "200k", net, "GND", source(net));
}
add("R_SV_H", "resistor", "2k", "VCC_DRAM", "SVREF", source("SVREF"));
add("R_SV_L", "resistor", "2k", "SVREF", "GND", source("SVREF"));
add("C_SV_H", "capacitor", "100nF", "VCC_DRAM", "SVREF", source("SVREF"));
add("C_SV_L", "capacitor", "100nF", "SVREF", "GND", source("SVREF"));
for (const net of ["TV_VRN", "TV_VRP"])
add(`C_${net}`, "capacitor", "10uF", net, "GND", source(net));
add("C_TV_REF", "capacitor", "10uF", "TV_VRP", "TV_VRN", source("TV_VRP"));
add("R_RESET", "resistor", "47k", "VCC_IO", "RESET", source("RESET"));
add("C_RESET", "capacitor", "100nF", "RESET", "GND", source("RESET"));
for (const net of [
"SDMMC0_CMD",
"SDMMC0_D0",
"SDMMC0_D1",
"SDMMC0_D2",
"SDMMC0_D3",
])
add(`R_PU_${net}`, "resistor", "47k", "VCC_IO", net, source(net));
add(
"R_PU_SPI0_CS",
"resistor",
"10k",
"VCC_IO",
"SPI0_CS",
source("SPI0_CS"),
);
for (const net of ["TWI0_SDA", "TWI0_SCL"])
add(`R_PU_${net}`, "resistor", "4.7k", "VCC_IO", net, source(net));
// Pin 1 of the 0402 footprint is on the left at 0 degrees. Aim the
// supply/reference side at its actual processor pin, not merely at U1's
// center. Dedicated C_D capacitors retain their individual supply pin.
// The two oscillator loads keep their crystal-oriented placement.
for (const p of passives) {
if (p.kind !== "capacitor" || p.name.startsWith("C_OSC")) continue;
const dedicated = p.name.match(/^C_D(\d+)$/);
const pins = dedicated
? [Number(dedicated[1])]
: Object.entries(PIN_NETS)
.filter(([, net]) => net === p.a)
.map(([pin]) => Number(pin));
const targets = pins.map((pin) => pinPosition(pin, spec.rotation));
const target = targets.sort(
(a, b) =>
Math.hypot(a.x - p.x, a.y - p.y) - Math.hypot(b.x - p.x, b.y - p.y),
)[0];
if (!target) throw Error(`No processor target for ${p.name}`);
const dx = target.x - p.x,
dy = target.y - p.y;
p.rotation =
Math.abs(dx) > Math.abs(dy) ? (dx < 0 ? 0 : 180) : dy < 0 ? 90 : 270;
}
const nets: Record<string, string[]> = {};
const connect = (net: string, port: string) => (nets[net] ??= []).push(port);
for (const [pin, net] of Object.entries(PIN_NETS))
connect(net, `.U1 > .pin${pin}`);
for (const p of passives) {
connect(p.a, `.${p.name} > .pin1`);
connect(p.b, `.${p.name} > .pin2`);
}
connect("HOSCI", ".Y1 > .pin1");
connect("HOSCO", ".Y1 > .pin3");
connect("GND", ".Y1 > .pin2");
connect("GND", ".Y1 > .pin4");
for (const t of terminals) connect(t.name, `.${t.name} > .pin1`);
return {
profile,
rotation: spec.rotation,
crystal,
terminals,
passives,
nets,
};
}