tscircuit/ti
ti tscircuit library
- Version
- 1.0.186
- License
- unset
- Stars
- 0
tests/Microcontroller_MSP430FR6007.test.tsx
import { getSchematicElementBounds } from "@tscircuit/circuit-json-util";
import { Circuit } from "@tscircuit/core";
import type { AnyCircuitElement } from "circuit-json";
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map";
import { MSP430FR6007IPZ_PIN_LABELS } from "../lib/chips/MSP430FR6007IPZ.circuit.tsx";
import { Microcontroller_MSP430FR6007 } from "../lib/subcircuits/Microcontroller_MSP430FR6007.circuit.tsx";
import { Microcontroller_MSP430FR6007_MultiSheet } from "../lib/subcircuits/Microcontroller_MSP430FR6007_MultiSheet.circuit.tsx";
type LayoutVariant = "single-sheet" | "multi-sheet";
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
const renderMcu = async (layoutVariant: LayoutVariant) => {
const circuit = new Circuit();
const Component =
layoutVariant === "single-sheet"
? Microcontroller_MSP430FR6007
: Microcontroller_MSP430FR6007_MultiSheet;
circuit.add(<Component name="MCU" />);
await circuit.renderUntilSettled();
return circuit.getCircuitJson();
};
const findSourceComponent = (
circuitJson: AnyCircuitElement[],
name: string,
) => {
const component = circuitJson.find(
(element) => "name" in element && element.name === name,
);
assert(component, `Missing source component ${name}`);
assert(
"source_component_id" in component,
`${name} is not a source component`,
);
return component;
};
const findSourcePort = (
circuitJson: AnyCircuitElement[],
componentName: string,
pinNumber: number,
) => {
const component = findSourceComponent(circuitJson, componentName);
const port = circuitJson.find(
(element) =>
element.type === "source_port" &&
element.source_component_id === component.source_component_id &&
element.pin_number === pinNumber,
);
assert(
port?.type === "source_port",
`Missing ${componentName}.pin${pinNumber}`,
);
return port;
};
const findSubcircuitPort = (circuitJson: AnyCircuitElement[], name: string) => {
const port = circuitJson.find(
(element) =>
element.type === "source_port" &&
element.source_component_id === null &&
element.name === name,
);
assert(port?.type === "source_port", `Missing subcircuit port ${name}`);
return port;
};
const findSchematicComponent = (
circuitJson: AnyCircuitElement[],
componentName: string,
) => {
const component = findSourceComponent(circuitJson, componentName);
const schematicComponent = circuitJson.find(
(element) =>
element.type === "schematic_component" &&
element.source_component_id === component.source_component_id,
);
assert(
schematicComponent?.type === "schematic_component",
`Missing schematic component ${componentName}`,
);
return schematicComponent;
};
const findSchematicCenter = (
circuitJson: AnyCircuitElement[],
componentName: string,
) => findSchematicComponent(circuitJson, componentName).center;
const findSchematicPortCenter = (
circuitJson: AnyCircuitElement[],
componentName: string,
pinNumber: number,
) => {
const sourcePort = findSourcePort(circuitJson, componentName, pinNumber);
const schematicPort = circuitJson.find(
(element) =>
element.type === "schematic_port" &&
element.source_port_id === sourcePort.source_port_id,
);
assert(
schematicPort?.type === "schematic_port",
`Missing schematic port ${componentName}.pin${pinNumber}`,
);
return schematicPort.center;
};
const coordinateKey = ({ x, y }: { x: number; y: number }) =>
`${x.toFixed(9)},${y.toFixed(9)}`;
const testPinMap = () => {
assert(
Object.keys(MSP430FR6007IPZ_PIN_LABELS).length === 100,
"The PZ package must define all 100 pins",
);
assert(MSP430FR6007IPZ_PIN_LABELS.pin16.includes("BSLTX"), "pin16 BSLTX");
assert(MSP430FR6007IPZ_PIN_LABELS.pin17.includes("BSLRX"), "pin17 BSLRX");
assert(MSP430FR6007IPZ_PIN_LABELS.pin20.includes("SBWTCK"), "pin20 SBWTCK");
assert(MSP430FR6007IPZ_PIN_LABELS.pin21.includes("SBWTDIO"), "pin21 SBWTDIO");
assert(MSP430FR6007IPZ_PIN_LABELS.pin88 === "PVCC", "pin88 PVCC");
assert(MSP430FR6007IPZ_PIN_LABELS.pin100.includes("AVCC"), "pin100 AVCC");
};
const testConnectivity = async (layoutVariant: LayoutVariant) => {
const circuitJson = await renderMcu(layoutVariant);
const connectivityMap = getFullConnectivityMapFromCircuitJson(circuitJson);
const sourcePortsById = new Map(
circuitJson
.filter((element) => element.type === "source_port")
.map((port) => [port.source_port_id, port]),
);
for (const trace of circuitJson) {
if (trace.type !== "source_trace") continue;
const touchesComponent = trace.connected_source_port_ids.some(
(portId) => sourcePortsById.get(portId)?.source_component_id !== null,
);
assert(
!touchesComponent || Boolean(trace.name),
`${trace.source_trace_id} touches a component but has no native trace name`,
);
assert(
!touchesComponent || Boolean(trace.display_name),
`${trace.name ?? trace.source_trace_id} touches a component but has no schDisplayLabel trace name`,
);
}
const emptyNetLabels = circuitJson.filter(
(element) =>
element.type === "schematic_net_label" && element.text.trim() === "",
);
assert(
emptyNetLabels.length === 0,
`${layoutVariant} renders ${emptyNetLabels.length} empty net-label bodies`,
);
for (const traceName of [
"C3_AVCC",
"R7_DVCC",
"C16_PVCC",
"C13_PVSS",
"C5_RESET_SBWTDIO",
]) {
assert(
circuitJson.some(
(element) =>
element.type === "source_trace" && element.name === traceName,
),
`Missing native on-trace name ${traceName}`,
);
}
for (const traceName of [
"J3_PIN5_AVSS",
"J3_PIN6_LFXIN",
"J3_PIN7_LFXOUT",
"J3_PIN8_AVSS",
"J3_PIN9_HFXIN",
"J3_PIN10_HFXOUT",
"J3_PIN11_AVSS",
"J3_PIN3_P1_0",
"J3_PIN4_P1_1",
"J3_PIN14_BSL_SDA",
"J3_PIN15_BSL_SCL",
"J3_PIN16_BSL_TX",
"J3_PIN17_BSL_RX",
"J3_PIN19_P1_3",
"J3_PIN20_TEST_SBWTCK",
"J3_PIN21_RESET",
"J3_PIN22_TDO",
"J3_PIN23_TDI",
"J3_PIN24_TMS",
"J3_PIN25_TCK",
"J4_PIN1_DVSS",
"J4_PIN2_DVCC",
"J5_PIN1_DVSS",
"J5_PIN2_DVCC",
"J5_PIN25_DVSS",
"J5_PIN24_LCDCAP",
"J6_PIN1_DVCC",
"J6_PIN10_CH1_IN",
"J6_PIN12_PVSS",
"J6_PIN13_PVCC",
"J6_PIN14_PVSS",
"J6_PIN16_CH0_IN",
"J6_PIN21_AVSS",
"J6_PIN22_USSXTIN",
"J6_PIN23_USSXTOUT",
"J6_PIN24_AVSS",
"J6_PIN25_AVCC",
]) {
assert(
circuitJson.some(
(element) =>
element.type === "source_trace" && element.name === traceName,
),
`Missing target-socket on-trace net name ${traceName}`,
);
}
const sheets = circuitJson.filter(
(element) => element.type === "schematic_sheet",
);
const expectedSheetNames =
layoutVariant === "single-sheet"
? (["reference_full"] as const)
: ([
"mcu_socket",
"programming_debug",
"power_user",
"clocks_channels",
] as const);
assert(
sheets.length === expectedSheetNames.length,
`${layoutVariant}: expected ${expectedSheetNames.length} native sheets, got ${sheets.length}`,
);
for (const [sheetIndex, sheetName] of expectedSheetNames.entries()) {
const sheet = sheets.find((candidate) => candidate.name === sheetName);
assert(sheet, `Missing native schematic sheet ${sheetName}`);
assert(
sheet.sheet_index === sheetIndex,
`${sheetName} has sheet index ${sheet.sheet_index}, expected ${sheetIndex}`,
);
}
const sheetIdByName = new Map(
sheets.map((sheet) => [sheet.name, sheet.schematic_sheet_id]),
);
const componentsBySheet = {
mcu_socket: ["IC1", "J3", "J4", "J5", "J6"],
programming_debug: [
"BSL",
"C5",
"JTAG",
"JP5",
"JP6",
"JP7",
"JP8",
"JP9",
"JP10",
"SH-JP5",
"SH-JP6",
"SH-JP7",
"SH-JP8",
"SH-JP9",
"SH-JP10",
"R3",
"R4",
"R7",
"R16",
"R17",
"R19",
"R20",
"R21",
"SW2",
"SW3",
"SW4",
"SW5",
"TP1",
"TP2",
"TP3",
"TP4",
],
power_user: [
"C3",
"C4",
"C6",
"C7",
"C10",
"C11",
"C13",
"C16",
"D1",
"D2",
"J1",
"J2",
"JP1",
"JP2",
"JP3",
"JP4",
"JP11",
"JP12",
"R1",
"R2",
"R10",
"R11",
"R12",
"R13",
"SW1",
"TP5",
"TP6",
"SH-J1",
"SH-JP1",
"SH-JP2",
"SH-JP3",
"SH-JP4",
"SH-JP11",
"SH-JP12",
],
clocks_channels: [
"C1",
"C2",
"C8",
"C9",
"C12",
"C14",
"C15",
"JP13",
"JP14",
"Q1",
"Q2",
"Q3",
"R5",
"R6",
"R8",
"R9",
"R14",
"R15",
"R18",
"R22",
"SH-JP13",
"SH-JP14",
],
} as const;
if (layoutVariant === "multi-sheet") {
for (const [sheetName, componentNames] of Object.entries(
componentsBySheet,
)) {
const expectedSheetId = sheetIdByName.get(sheetName);
assert(expectedSheetId, `Missing sheet id for ${sheetName}`);
for (const componentName of componentNames) {
const component = findSchematicComponent(circuitJson, componentName);
assert(
component.schematic_sheet_id === expectedSheetId,
`${componentName} is not on ${sheetName}`,
);
}
}
} else {
const fullSheetId = sheetIdByName.get("reference_full");
assert(fullSheetId, "Missing full reference sheet id");
for (const componentNames of Object.values(componentsBySheet)) {
for (const componentName of componentNames) {
const component = findSchematicComponent(circuitJson, componentName);
assert(
component.schematic_sheet_id === fullSheetId,
`${componentName} is not on the full reference sheet`,
);
}
}
}
for (const connectorName of ["J3", "J4", "J5", "J6"]) {
const connector = findSourceComponent(circuitJson, connectorName);
assert(
"manufacturer_part_number" in connector &&
connector.manufacturer_part_number === "TSW-125-07-G-S",
`${connectorName} does not preserve the TI source part number`,
);
}
const figureB78ComponentNames = [
"IC1",
"J1",
"J2",
"J3",
"J4",
"J5",
"J6",
"JTAG",
"BSL",
"JP1",
"JP2",
"JP3",
"JP4",
"JP5",
"JP6",
"JP7",
"JP8",
"JP9",
"JP10",
"JP11",
"JP12",
"JP13",
"JP14",
"SW1",
"SW2",
"SW3",
"SW4",
"SW5",
"Q1",
"Q2",
"Q3",
"D1",
"D2",
"TP1",
"TP2",
"TP3",
"TP4",
"TP5",
"TP6",
...Array.from({ length: 16 }, (_, index) => `C${index + 1}`),
...Array.from({ length: 22 }, (_, index) => `R${index + 1}`),
"SH-J1",
...Array.from({ length: 14 }, (_, index) => `SH-JP${index + 1}`),
];
for (const componentName of figureB78ComponentNames) {
findSourceComponent(circuitJson, componentName);
}
const installedShuntRefdesByHeader = {
J1: "SH-J1",
JP1: "SH-JP1",
JP2: "SH-JP2",
JP3: "SH-JP3",
JP4: "SH-JP4",
JP5: "SH-JP5",
JP6: "SH-JP6",
JP7: "SH-JP7",
JP8: "SH-JP8",
JP9: "SH-JP9",
JP10: "SH-JP10",
JP11: "SH-JP11",
JP12: "SH-JP12",
JP13: "SH-JP13",
JP14: "SH-JP14",
} as const;
const installedShuntPinsByHeader = {
J1: [1, 2],
JP1: [1, 2],
JP2: [1, 2],
JP3: [1, 2],
JP4: [1, 2],
JP5: [2, 3],
JP6: [2, 3],
JP7: [2, 3],
JP8: [2, 3],
JP9: [2, 3],
JP10: [2, 3],
JP11: [1, 2],
JP12: [1, 2],
JP13: [1, 2],
JP14: [1, 2],
} as const;
for (const [headerName, shuntRefdes] of Object.entries(
installedShuntRefdesByHeader,
)) {
const header = findSourceComponent(circuitJson, headerName);
const shunt = findSourceComponent(circuitJson, shuntRefdes);
assert(
"manufacturer_part_number" in shunt &&
shunt.manufacturer_part_number === "3M9580-ND",
`${shuntRefdes} does not preserve the TI shunt part number`,
);
assert(
!("display_name" in header) ||
!header.display_name?.includes(shuntRefdes),
`${headerName} must remain separate from ${shuntRefdes}`,
);
const shuntPin1 = findSourcePort(circuitJson, shuntRefdes, 1);
const shuntPin2 = findSourcePort(circuitJson, shuntRefdes, 2);
assert(
connectivityMap.getNetConnectedToId(shuntPin1.source_port_id) ===
connectivityMap.getNetConnectedToId(shuntPin2.source_port_id),
`${shuntRefdes} is not internally bridged`,
);
const selectedHeaderPins =
installedShuntPinsByHeader[
headerName as keyof typeof installedShuntPinsByHeader
];
const selectedHeaderPin1 = findSourcePort(
circuitJson,
headerName,
selectedHeaderPins[0],
);
const selectedHeaderPin2 = findSourcePort(
circuitJson,
headerName,
selectedHeaderPins[1],
);
assert(
connectivityMap.getNetConnectedToId(selectedHeaderPin1.source_port_id) ===
connectivityMap.getNetConnectedToId(selectedHeaderPin2.source_port_id),
`${headerName}.pin${selectedHeaderPins[0]} and ${headerName}.pin${selectedHeaderPins[1]} do not preserve the Figure B-79 installed-shunt state`,
);
assert(
connectivityMap.getNetConnectedToId(selectedHeaderPin1.source_port_id) !==
connectivityMap.getNetConnectedToId(shuntPin1.source_port_id),
`${shuntRefdes} must remain the separate Figure B-78 assembly block rather than creating an artificial schematic wire to ${headerName}`,
);
}
for (const [componentName, pinNumbers] of [
["JTAG", [6, 13]],
["BSL", [5, 10]],
] as const) {
for (const pinNumber of pinNumbers) {
assert(
findSourcePort(circuitJson, componentName, pinNumber).do_not_connect,
`${componentName}.pin${pinNumber} must preserve the Figure B-78 no-connect`,
);
}
}
const physicalSourceComponentNames = circuitJson
.filter((element) => element.type === "source_component")
.map((element) => element.name)
.sort();
assert(
physicalSourceComponentNames.length === 92,
`Expected the 92 Figure B-78 physical components, got ${physicalSourceComponentNames.length}: ${physicalSourceComponentNames.join(", ")}`,
);
assert(
[...figureB78ComponentNames]
.sort()
.every((name, index) => physicalSourceComponentNames[index] === name),
"The rendered component inventory does not exactly match Figure B-78 and Table B-40",
);
const schematicBodiesAndLabels = circuitJson.filter(
(element) =>
element.type === "schematic_component" ||
element.type === "schematic_net_label",
);
const schematicElementName = (element: AnyCircuitElement) => {
if ("source_component_id" in element) {
const sourceComponent = circuitJson.find(
(candidate) =>
candidate.type === "source_component" &&
candidate.source_component_id === element.source_component_id,
);
if (sourceComponent && "name" in sourceComponent) {
return sourceComponent.name;
}
}
if ("text" in element) return element.text;
return element.type;
};
for (
let firstIndex = 0;
firstIndex < schematicBodiesAndLabels.length;
firstIndex += 1
) {
const first = schematicBodiesAndLabels[firstIndex];
const firstBounds = getSchematicElementBounds(first);
if (!firstBounds) continue;
for (
let secondIndex = firstIndex + 1;
secondIndex < schematicBodiesAndLabels.length;
secondIndex += 1
) {
const second = schematicBodiesAndLabels[secondIndex];
if (first.schematic_sheet_id !== second.schematic_sheet_id) continue;
const secondBounds = getSchematicElementBounds(second);
if (!secondBounds) continue;
const overlapX =
Math.min(firstBounds.maxX, secondBounds.maxX) -
Math.max(firstBounds.minX, secondBounds.minX);
const overlapY =
Math.min(firstBounds.maxY, secondBounds.maxY) -
Math.max(firstBounds.minY, secondBounds.minY);
assert(
overlapX <= 0.05 || overlapY <= 0.05,
`${first.type} ${schematicElementName(first)} overlaps ${second.type} ${schematicElementName(second)} on ${first.schematic_sheet_id}`,
);
}
}
const expectedCapacitances = {
C1: 12e-12,
C2: 12e-12,
C3: 1e-6,
C4: 0.1e-6,
C5: 1100e-12,
C6: 0.1e-6,
C7: 1e-6,
C8: 22e-12,
C9: 22e-12,
C10: 0.1e-6,
C11: 0.1e-6,
C12: 4.7e-6,
// Figure B-78 values are authoritative for the replicated schematic;
// Table B-40 conflicts on C13 and C16.
C13: 0.1e-6,
C14: 27e-12,
C15: 27e-12,
C16: 1e-6,
} as const;
for (const [name, capacitance] of Object.entries(expectedCapacitances)) {
const component = findSourceComponent(circuitJson, name);
assert(
"capacitance" in component && component.capacitance === capacitance,
`${name} does not match the Figure B-78 capacitance`,
);
}
const expectedResistances = {
R1: 330,
R2: 200,
R3: 0,
R4: 0,
R5: 0,
R6: 0,
R7: 47_000,
R8: 0,
R9: 0,
R10: 0,
R11: 0,
R12: 0,
R13: 47_000,
R14: 0,
R15: 0,
R16: 4_700,
R17: 4_700,
R18: 0,
R19: 0,
R20: 0,
R21: 0,
R22: 22,
} as const;
for (const [name, resistance] of Object.entries(expectedResistances)) {
const component = findSourceComponent(circuitJson, name);
assert(
"resistance" in component && component.resistance === resistance,
`${name} does not match the Figure B-78 resistance`,
);
}
const jtagBody = findSchematicComponent(circuitJson, "JTAG");
const bslBody = findSchematicComponent(circuitJson, "BSL");
assert(
Math.abs(jtagBody.size.width / jtagBody.size.height - 0.643) < 0.02,
"JTAG body must preserve the shortened Figure B-78 connector shape",
);
assert(
jtagBody.size.height <= 2.8,
"JTAG body is taller than the compact native reference geometry",
);
assert(
Math.abs(bslBody.size.width / bslBody.size.height - 0.64) < 0.02,
"BSL body must preserve the Figure B-78 aspect ratio",
);
const icCenter = findSchematicCenter(circuitJson, "IC1");
assert(findSchematicCenter(circuitJson, "J3").x < icCenter.x, "J3 left");
assert(findSchematicCenter(circuitJson, "J4").y < icCenter.y, "J4 below");
assert(findSchematicCenter(circuitJson, "J5").x > icCenter.x, "J5 right");
assert(findSchematicCenter(circuitJson, "J6").y > icCenter.y, "J6 above");
if (layoutVariant === "single-sheet") {
const jtagCenter = findSchematicCenter(circuitJson, "JTAG");
const bslCenter = findSchematicCenter(circuitJson, "BSL");
const powerCenter = findSchematicCenter(circuitJson, "J1");
const userCenter = findSchematicCenter(circuitJson, "SW1");
const channelCenter = findSchematicCenter(circuitJson, "JP13");
assert(jtagCenter.x < icCenter.x, "Figure B-78 places JTAG left of IC1");
assert(jtagCenter.y > icCenter.y, "Figure B-78 places JTAG above IC1");
assert(bslCenter.y > icCenter.y, "Figure B-78 places BSL above IC1");
assert(powerCenter.x < icCenter.x, "Figure B-78 places power left of IC1");
assert(userCenter.x < icCenter.x, "Figure B-78 places SW1 left of IC1");
assert(userCenter.y < icCenter.y, "Figure B-78 places SW1 below IC1");
assert(
channelCenter.x > icCenter.x,
"Figure B-78 places channel headers right of IC1",
);
for (const [upper, lower] of [
["J1", "JP1"],
["JP1", "JP2"],
["JP2", "JP3"],
["JP3", "JP4"],
] as const) {
assert(
findSchematicCenter(circuitJson, upper).y >
findSchematicCenter(circuitJson, lower).y,
`Figure B-78 places ${upper} above ${lower}`,
);
}
for (const [left, right] of [
["JP9", "JP10"],
["JP10", "JP5"],
["JP5", "JP6"],
["JP6", "JP7"],
["JP7", "JP8"],
] as const) {
assert(
findSchematicCenter(circuitJson, left).x <
findSchematicCenter(circuitJson, right).x,
`Figure B-78 places ${left} left of ${right}`,
);
}
}
const socketCenters = Object.fromEntries(
["J3", "J4", "J5", "J6"].map((name) => [
name,
findSchematicCenter(circuitJson, name),
]),
);
const netKey = (componentName: string, pinNumber: number) =>
connectivityMap.getNetConnectedToId(
findSourcePort(circuitJson, componentName, pinNumber).source_port_id,
);
const portNetKey = (name: string) =>
connectivityMap.getNetConnectedToId(
findSubcircuitPort(circuitJson, name).source_port_id,
);
const assertSameNet = (
first: [string, number],
...rest: Array<[string, number]>
) => {
const expected = netKey(...first);
assert(expected, `${first[0]}.pin${first[1]} has no connected net`);
for (const endpoint of rest) {
assert(
netKey(...endpoint) === expected,
`${endpoint[0]}.pin${endpoint[1]} is not connected to ${first[0]}.pin${first[1]}`,
);
}
};
const assertDifferentNet = (
first: [string, number],
second: [string, number],
) => {
const firstNet = netKey(...first);
const secondNet = netKey(...second);
assert(firstNet, `${first[0]}.pin${first[1]} has no connected net`);
assert(secondNet, `${second[0]}.pin${second[1]} has no connected net`);
assert(
firstNet !== secondNet,
`${first[0]}.pin${first[1]} must not be connected to ${second[0]}.pin${second[1]}`,
);
};
const assertDirectTrace = (
traceName: string,
first: [string, number],
second: [string, number],
) => {
const firstPort = findSourcePort(circuitJson, ...first);
const secondPort = findSourcePort(circuitJson, ...second);
const trace = circuitJson.find(
(element) =>
element.type === "source_trace" && element.name === traceName,
);
assert(trace?.type === "source_trace", `Missing direct trace ${traceName}`);
assert(
trace.connected_source_port_ids.length === 2 &&
trace.connected_source_port_ids.includes(firstPort.source_port_id) &&
trace.connected_source_port_ids.includes(secondPort.source_port_id),
`${traceName} does not directly connect ${first[0]}.pin${first[1]} to ${second[0]}.pin${second[1]}`,
);
assert(
(() => {
const firstCenter = findSchematicPortCenter(circuitJson, ...first);
const secondCenter = findSchematicPortCenter(circuitJson, ...second);
const matchesCenter = (
point: { x: number; y: number },
center: { x: number; y: number },
) =>
Math.abs(point.x - center.x) < 1e-6 &&
Math.abs(point.y - center.y) < 1e-6;
const points = circuitJson
.filter(
(element) =>
element.type === "schematic_trace" &&
element.subcircuit_connectivity_map_key ===
trace.subcircuit_connectivity_map_key,
)
.flatMap((element) =>
element.type === "schematic_trace"
? element.edges.flatMap(({ from, to }) => [from, to])
: [],
);
return (
points.some((point) => matchesCenter(point, firstCenter)) &&
points.some((point) => matchesCenter(point, secondCenter))
);
})(),
`${traceName} has no rendered native schematic route`,
);
};
const connectorNames = ["J3", "J4", "J5", "J6"] as const;
const socketPortEndpointsByPosition = new Map<
string,
Array<{ componentName: string; pinNumber: number }>
>();
for (let mcuPin = 1; mcuPin <= 100; mcuPin += 1) {
const connectorName = connectorNames[Math.floor((mcuPin - 1) / 25)];
const connectorPin = ((mcuPin - 1) % 25) + 1;
assertSameNet(["IC1", mcuPin], [connectorName, connectorPin]);
const mcuPortCenter = findSchematicPortCenter(circuitJson, "IC1", mcuPin);
const socketPortCenter = findSchematicPortCenter(
circuitJson,
connectorName,
connectorPin,
);
const socketCenter = socketCenters[connectorName];
assert(socketCenter, `Missing ${connectorName} center`);
for (const endpoint of [
{ componentName: "IC1", pinNumber: mcuPin, center: mcuPortCenter },
{
componentName: connectorName,
pinNumber: connectorPin,
center: socketPortCenter,
},
]) {
const key = coordinateKey(endpoint.center);
const endpointsAtPosition = socketPortEndpointsByPosition.get(key) ?? [];
endpointsAtPosition.push(endpoint);
socketPortEndpointsByPosition.set(key, endpointsAtPosition);
}
if (connectorName === "J3" || connectorName === "J5") {
assert(
Math.abs(socketPortCenter.y - mcuPortCenter.y) < 1e-9,
`${connectorName}.pin${connectorPin} is not aligned with IC1.pin${mcuPin}`,
);
assert(
Math.abs(socketPortCenter.x - icCenter.x) <
Math.abs(socketCenter.x - icCenter.x),
`${connectorName}.pin${connectorPin} faces away from IC1`,
);
} else {
assert(
Math.abs(socketPortCenter.x - mcuPortCenter.x) < 1e-9,
`${connectorName}.pin${connectorPin} is not aligned with IC1.pin${mcuPin}`,
);
assert(
Math.abs(socketPortCenter.y - icCenter.y) <
Math.abs(socketCenter.y - icCenter.y),
`${connectorName}.pin${connectorPin} faces away from IC1`,
);
}
}
let directSocketTraceCount = 0;
const directSocketMcuPins = new Set<number>();
const mcuSocketSheetId = sheetIdByName.get(
layoutVariant === "single-sheet" ? "reference_full" : "mcu_socket",
);
assert(mcuSocketSheetId, "Missing MCU socket sheet id");
for (const trace of circuitJson) {
if (trace.type !== "schematic_trace") continue;
if (trace.schematic_sheet_id !== mcuSocketSheetId) continue;
const touchedEndpoints = new Map<
string,
{ componentName: string; pinNumber: number }
>();
for (const edge of trace.edges) {
for (const point of [edge.from, edge.to]) {
for (const endpoint of socketPortEndpointsByPosition.get(
coordinateKey(point),
) ?? []) {
touchedEndpoints.set(
`${endpoint.componentName}.pin${endpoint.pinNumber}`,
endpoint,
);
}
}
}
if (touchedEndpoints.size < 2) continue;
assert(
touchedEndpoints.size === 2,
`${trace.schematic_trace_id} joins more than one socket pair`,
);
const endpoints = [...touchedEndpoints.values()];
const mcuEndpoint = endpoints.find(
(endpoint) => endpoint.componentName === "IC1",
);
const connectorEndpoint = endpoints.find((endpoint) =>
connectorNames.includes(
endpoint.componentName as (typeof connectorNames)[number],
),
);
assert(
mcuEndpoint && connectorEndpoint,
`${trace.schematic_trace_id} connects two pins on the same component`,
);
const expectedConnector =
connectorNames[Math.floor((mcuEndpoint.pinNumber - 1) / 25)];
const expectedConnectorPin = ((mcuEndpoint.pinNumber - 1) % 25) + 1;
assert(
connectorEndpoint.componentName === expectedConnector &&
connectorEndpoint.pinNumber === expectedConnectorPin,
`${trace.schematic_trace_id} does not preserve the IC1-to-socket pin map`,
);
directSocketTraceCount += 1;
directSocketMcuPins.add(mcuEndpoint.pinNumber);
}
const missingDirectSocketPins = Array.from(
{ length: 100 },
(_, index) => index + 1,
).filter((pin) => !directSocketMcuPins.has(pin));
assert(
directSocketTraceCount === 100,
`Expected 100 direct IC1-to-socket traces, got ${directSocketTraceCount}; missing IC1 pins ${missingDirectSocketPins.join(", ")}`,
);
assertSameNet(["IC1", 100], ["C3", 1], ["C11", 1]);
assertSameNet(["IC1", 88], ["C16", 1], ["C13", 1]);
assertSameNet(
["IC1", 21],
["R7", 2],
["C5", 1],
["SW2", 1],
["JP10", 2],
["BSL", 4],
);
assertSameNet(["IC1", 20], ["JP9", 2], ["BSL", 7]);
assertSameNet(["IC1", 22], ["JP5", 2]);
assertSameNet(["IC1", 23], ["JP6", 2]);
assertSameNet(["IC1", 24], ["JP7", 2]);
assertSameNet(["IC1", 25], ["JP8", 2]);
assertSameNet(["JTAG", 1], ["JP5", 3], ["JP10", 1]);
assertSameNet(["JTAG", 3], ["JP6", 3]);
assertSameNet(["JTAG", 5], ["JP7", 3]);
assertSameNet(["JTAG", 7], ["JP8", 3], ["JP9", 1]);
assertSameNet(["JTAG", 8], ["JP9", 3]);
assertSameNet(["JTAG", 11], ["JP10", 3]);
assertSameNet(["JTAG", 8], ["JP9", 3], ["JP9", 2], ["IC1", 20]);
assertDifferentNet(["JP9", 1], ["JP9", 2]);
assertSameNet(["JTAG", 11], ["JP10", 3], ["JP10", 2], ["IC1", 21]);
assertSameNet(["JTAG", 1], ["JP10", 1]);
assertDifferentNet(["JP10", 1], ["JP10", 2]);
assertSameNet(["IC1", 16], ["R20", 1]);
assertSameNet(["R20", 2], ["JTAG", 12]);
assertSameNet(["IC1", 17], ["R19", 1]);
assertSameNet(["R19", 2], ["JTAG", 14]);
assertSameNet(["IC1", 15], ["R21", 1]);
assertSameNet(["R21", 2], ["JTAG", 10]);
assertDirectTrace("R19_JTAG_BSL_RX", ["R19", 2], ["JTAG", 14]);
assertDirectTrace("R20_JTAG_BSL_TX", ["R20", 2], ["JTAG", 12]);
assertDirectTrace("R21_JTAG_BSL_SCL", ["R21", 2], ["JTAG", 10]);
assertDirectTrace("BSL_PIN6_R3_PIN1_BSL_TOOL_VCC", ["BSL", 6], ["R3", 1]);
assertDirectTrace("BSL_PIN8_R4_PIN1_BSL_TARGET_VCC", ["BSL", 8], ["R4", 1]);
assertDirectTrace("D2_PIN2_R2_PIN1_LED2_A", ["D2", 2], ["R2", 1]);
assertDirectTrace("JTAG_PIN8_JP9_PIN3_TEST_SBWTCK", ["JTAG", 8], ["JP9", 3]);
for (const [traceName, expectedLabel] of [
["R19_JTAG_BSL_RX", "BSL_RX"],
["R20_JTAG_BSL_TX", "BSL_TX"],
["R21_JTAG_BSL_SCL", "BSL_SCL"],
["BSL_PIN6_R3_PIN1_BSL_TOOL_VCC", "BSL_TOOL_VCC"],
["BSL_PIN8_R4_PIN1_BSL_TARGET_VCC", "BSL_TARGET_VCC"],
["D2_PIN2_R2_PIN1_LED2_A", "LED2_A"],
["JTAG_PIN8_JP9_PIN3_TEST_SBWTCK", "JTAG"],
] as const) {
const sourceTrace = circuitJson.find(
(element) =>
element.type === "source_trace" && element.name === traceName,
);
assert(sourceTrace?.type === "source_trace", `Missing ${traceName}`);
assert(
circuitJson.some(
(element) =>
element.type === "schematic_text" &&
element.source_trace_id === sourceTrace.source_trace_id &&
element.text === expectedLabel &&
element.rotation === 0,
),
`${traceName} must carry ${expectedLabel} above the resistor-to-JTAG trace`,
);
}
for (const traceName of [
"R19_PIN1_BSL_RX",
"R20_PIN1_BSL_TX",
"R21_PIN1_BSL_SCL",
]) {
const sourceTrace = circuitJson.find(
(element) =>
element.type === "source_trace" && element.name === traceName,
);
assert(sourceTrace?.type === "source_trace", `Missing ${traceName}`);
assert(
!circuitJson.some(
(element) =>
element.type === "schematic_net_label" &&
element.source_trace_id === sourceTrace.source_trace_id &&
element.text.trim() !== "",
),
`${traceName} must not use a free-standing net label outside R19-R21`,
);
}
for (const [headerName, firstPin, secondPin] of [
["J1", 1, 2],
["JP1", 1, 2],
["JP2", 1, 2],
["JP3", 1, 2],
["JP4", 1, 2],
["JP5", 2, 3],
["JP6", 2, 3],
["JP7", 2, 3],
["JP8", 2, 3],
["JP9", 2, 3],
["JP10", 2, 3],
["JP11", 1, 2],
["JP12", 1, 2],
["JP13", 1, 2],
["JP14", 1, 2],
] as const) {
assertSameNet([headerName, firstPin], [headerName, secondPin]);
}
assertSameNet(["IC1", 15], ["SW4", 1], ["R16", 2]);
assertSameNet(["IC1", 14], ["SW4", 3], ["R17", 2]);
assertSameNet(["BSL", 9], ["SW4", 2], ["TP1", 1]);
assertSameNet(["BSL", 1], ["SW4", 4], ["SW5", 4], ["TP2", 1], ["TP4", 1]);
assertSameNet(["IC1", 17], ["SW5", 1]);
assertSameNet(["BSL", 3], ["SW5", 2], ["TP3", 1]);
assertSameNet(["IC1", 16], ["SW5", 3]);
assertSameNet(["BSL", 6], ["R3", 1]);
assertSameNet(["BSL", 8], ["R4", 1]);
assertSameNet(["R3", 2], ["R4", 2], ["J1", 2], ["J2", 1]);
assertSameNet(["J1", 3], ["J2", 2]);
assertSameNet(["J2", 3], ["BSL", 2], ["JTAG", 9]);
assertSameNet(["IC1", 3], ["JP11", 2]);
assertSameNet(["IC1", 4], ["JP12", 2]);
assertSameNet(["IC1", 19], ["R13", 2], ["SW1", 1]);
assertSameNet(["IC1", 74], ["R18", 1], ["C12", 1]);
assertSameNet(["IC1", 85], ["JP13", 2]);
assertSameNet(["IC1", 91], ["JP14", 2]);
assertSameNet(["IC1", 97], ["Q3", 1], ["C14", 1], ["R14", 1]);
assertSameNet(["IC1", 98], ["R22", 2], ["R15", 1]);
assertSameNet(["Q3", 3], ["C15", 1], ["R22", 1]);
const criticalPortEndpoints = {
AVCC: ["IC1", 100],
DVCC: ["IC1", 27],
PVCC: ["IC1", 88],
GND: ["R10", 1],
DVSS: ["IC1", 26],
RESET: ["IC1", 21],
BSL_TX: ["IC1", 16],
BSL_RX: ["IC1", 17],
BSL_SDA: ["IC1", 14],
BSL_SCL: ["IC1", 15],
P1_0: ["IC1", 3],
P1_1: ["IC1", 4],
P1_3: ["IC1", 19],
CH0_IN: ["IC1", 91],
CH1_IN: ["IC1", 85],
USSXTIN: ["IC1", 97],
USSXTOUT: ["IC1", 98],
LCDCAP: ["IC1", 74],
TEST: ["IC1", 20],
TDO: ["IC1", 22],
TDI: ["IC1", 23],
TMS: ["IC1", 24],
TCK: ["IC1", 25],
} as const;
for (const [portName, [componentName, pinNumber]] of Object.entries(
criticalPortEndpoints,
)) {
assert(
portNetKey(portName) === netKey(componentName, pinNumber),
`${portName} subcircuit port is not connected to ${componentName}.pin${pinNumber}`,
);
}
for (const groundPin of [5, 8, 11, 96, 99]) {
assert(
netKey("IC1", groundPin) === netKey("C3", 2),
`IC1.pin${groundPin} is not on AVSS`,
);
}
for (const groundPin of [26, 51, 75]) {
assert(
netKey("IC1", groundPin) === netKey("C4", 2),
`IC1.pin${groundPin} is not on DVSS/GND`,
);
}
for (const groundPin of [87, 89]) {
assert(
netKey("IC1", groundPin) === netKey("C16", 2),
`IC1.pin${groundPin} is not on PVSS`,
);
}
assertSameNet(["IC1", 87], ["R11", 2]);
assertSameNet(["R10", 1], ["R11", 1], ["R12", 1], ["TP6", 1]);
assertSameNet(
["IC1", 26],
["R10", 2],
["C4", 2],
["C10", 2],
["C6", 2],
["C7", 2],
["TP5", 1],
);
assertSameNet(["IC1", 5], ["R12", 2]);
assert(
circuitJson.every(
(element) => !element.type.endsWith("_trace_not_connected_error"),
),
"Rendered circuit contains a trace-not-connected error",
);
};
testPinMap();
await testConnectivity("single-sheet");
await testConnectivity("multi-sheet");
console.log(
"MSP430FR6007 single-sheet and multi-sheet connectivity checks passed",
);