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/T113S3Module.tsx
import { attachSavedFanout, joinSavedFanoutExits } from "./saved-fanout";
import type { FanoutTracePath } from "@tscircuit/props";
import savedPaths from "./generated/lcd_top_storage_right.trace-paths.json";
import { Fragment } from "react";
import { Decoupling } from "./decoupling";
import type { ChipProps } from "@tscircuit/props";
import { T113_S3 } from "../imports/T113_S3";
import { pins } from "./pins";
import { resolveTerminal, type TerminalName } from "./terminals";
import {
CHIP_ROTATION,
FANOUT_SIZE,
TRACE_WIDTH,
LCD_PINS,
STORAGE_PINS,
type RoutingProfile,
} from "./routing-profile";
export interface T113S3ModuleProps {
name: string;
routingProfile?: RoutingProfile;
/** Profile selector matching the current astra/f1c100s reference. */
layoutProfile?: RoutingProfile;
/** Alias for compatibility with the first release. */
busProfile?: RoutingProfile;
/** Module center in board coordinates, mm. +X right, +Y top. */
pcbX?: number;
pcbY?: number;
/** Rotate the complete module and exits; directions remain module-relative. */
pcbRotation?: 0 | 90 | 180 | 270;
schX?: number;
schY?: number;
connections?: Partial<Record<TerminalName, string>>;
}
const pinAttributes: NonNullable<ChipProps["pinAttributes"]> =
Object.fromEntries(
pins.map((pin) => [
`pin${pin.pin}`,
pin.pin === 106
? { doNotConnect: true }
: {
mustBeConnected: true,
...([91, 129].includes(pin.pin) ? { requiresGround: true } : {}),
...(/^(VCC|VDD|AVCC|HPVCC|LDO_IN)/.test(pin.label)
? { requiresPower: true }
: {}),
},
]),
);
/** Reusable, saved-fanout module for JLCPCB C5197687.
* This is a fanout primitive, not a powered or bootable SoC reference design.
* Public chip selectors: `.SOC .U1 > .PD0`, `.SOC .U1 > .PF0`, etc.
*/
export function T113S3Module({
name,
routingProfile,
layoutProfile,
busProfile,
pcbX = 0,
pcbY = 0,
pcbRotation = 0,
schX = 0,
schY = 0,
connections = {},
}: T113S3ModuleProps) {
const profile =
layoutProfile ?? routingProfile ?? busProfile ?? "lcd_top_storage_right";
if (profile !== "lcd_top_storage_right") {
throw new Error(`Unknown routing profile: ${profile}`);
}
if (
[layoutProfile, routingProfile, busProfile]
.filter(Boolean)
.some((p) => p !== profile)
)
throw new Error("Conflicting layoutProfile, routingProfile or busProfile");
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
throw new Error("Module name must be a selector-safe identifier");
if (![0, 90, 180, 270].includes(pcbRotation))
throw new Error("pcbRotation must be 0, 90, 180 or 270");
const externalConnections = Object.entries(connections).map(
([terminal, target]) => {
const pin = resolveTerminal(terminal);
if (typeof target !== "string" || !target.trim())
throw new Error(`Empty connection for ${terminal}`);
return { terminal, pin, target };
},
);
return (
<>
<subcircuit
name={name}
{...{ ref: attachSavedFanout }}
autorouter={{ local: true, algorithmFn: joinSavedFanoutExits }}
schTraceAutoLabelEnabled
schMaxTraceDistance={3}
pcbX={pcbX}
pcbY={pcbY}
pcbRotation={pcbRotation}
schX={schX}
schY={schY}
pcbStyle={{ viaHoleDiameter: 0.2, viaPadDiameter: 0.45 }}
>
<fanout
name="FANOUT"
width={FANOUT_SIZE}
height={FANOUT_SIZE}
pcbTracePaths={structuredClone(savedPaths) as FanoutTracePath[]}
>
<T113_S3
name="U1"
pcbRotation={CHIP_ROTATION}
pinAttributes={pinAttributes}
schSectionName="soc"
schX={-14}
schY={0}
schWidth={5}
schHeight={20.5}
schPinStyle={Object.fromEntries(
pins.map((p) => [
`pin${p.pin}`,
{ topMargin: 0.05, bottomMargin: 0.05 },
]),
)}
schPinArrangement={{
leftSide: {
pins: Array.from({ length: 65 }, (_, i) => i + 1),
direction: "top-to-bottom",
},
rightSide: {
pins: Array.from({ length: 64 }, (_, i) => i + 66),
direction: "top-to-bottom",
},
}}
/>
<Decoupling />
</fanout>
<bus name="LCD" connections={LCD_PINS.map((p) => `ESC_${p.label}`)} />
<bus
name="STORAGE"
connections={STORAGE_PINS.map((p) => `ESC_${p.label}`)}
/>
{pins
.filter((p) => p.pin !== 106)
.map((pin) => {
const connection = `U1.pin${pin.pin}`;
const net = pin.pin === 91 || pin.pin === 129 ? "GND" : pin.label;
return (
<Fragment key={pin.pin}>
<trace
name={`ESC_${pin.label}`}
schDisplayLabel={net}
from={connection}
to={`net.${net}`}
thickness={TRACE_WIDTH}
/>
</Fragment>
);
})}
<Decoupling connectionsOnly />
{(["bottom", "inner1"] as const).map((layer) => (
<Fragment key={layer}>
<copperpour
name={`GROUND_${layer}`}
layer={layer}
connectsTo="net.GND"
coveredWithSolderMask
clearance={0.15}
useThermalReliefs
outline={[
{ x: -17, y: -17 },
{ x: 17, y: -17 },
{ x: 17, y: 17 },
{ x: -17, y: 17 },
].map((p) => ({ x: p.x + pcbX, y: p.y + pcbY }))}
/>
</Fragment>
))}
</subcircuit>
{externalConnections.map(({ terminal, pin, target }) => (
<Fragment key={terminal}>
<trace
name={`${name}_${terminal}_external`}
from={`.${name} .U1 > .${pin}`}
to={target}
/>
</Fragment>
))}
</>
);
}