tscircuit/ti
ti tscircuit library
- Version
- 1.0.192
- License
- unset
- Stars
- 0
tests/INA350.test.tsx
/// <reference types="node" />
import assert from "node:assert/strict";
import { test } from "node:test";
import { Circuit } from "@tscircuit/core";
import {
INA350,
INA350ABSIDSGR,
INA350CDSIDSGR,
InstrumentationAmplifier_INA350,
TiChipComponents,
TiSubcircuitComponents,
} from "../index.ts";
type TestCircuit = InstanceType<typeof Circuit>;
function getPort(
circuit: TestCircuit,
name: string,
pin: string,
group?: string,
) {
const sourceGroup = group
? circuit.db.source_group.getWhere({ name: group })
: undefined;
if (group) assert.ok(sourceGroup, group);
const component = circuit.db.source_component
.list()
.find(
(candidate) =>
candidate.name === name &&
(!sourceGroup ||
candidate.source_group_id === sourceGroup.source_group_id),
);
assert.ok(component, name);
const port = circuit.db.source_port
.list({ source_component_id: component.source_component_id })
.find((candidate) => candidate.port_hints?.includes(pin));
assert.ok(port, name + "." + pin);
return port;
}
function connectedIds(circuit: TestCircuit, start: string) {
const connected = new Set([start]);
let size = 0;
while (size !== connected.size) {
size = connected.size;
for (const trace of circuit.db.source_trace.list()) {
const ids = [
...trace.connected_source_port_ids,
...trace.connected_source_net_ids,
];
if (ids.some((id) => connected.has(id))) {
for (const id of ids) connected.add(id);
}
}
}
return connected;
}
function assertNoErrors(circuit: TestCircuit) {
assert.deepEqual(
circuit
.getCircuitJson()
.filter((element) => element.type.endsWith("_error")),
[],
);
}
test("INA350 package exports use the datasheet DSG pinout and land pattern", async () => {
assert.equal(TiChipComponents.INA350, INA350);
assert.equal(
TiSubcircuitComponents.InstrumentationAmplifier_INA350,
InstrumentationAmplifier_INA350,
);
for (const [Chip, mpn] of [
[INA350, "INA350CDSIDSGR"],
[INA350CDSIDSGR, "INA350CDSIDSGR"],
[INA350ABSIDSGR, "INA350ABSIDSGR"],
] as const) {
const circuit = new Circuit();
circuit.add(
<board width={12} height={12} routingDisabled>
<Chip name="U1" pcbX={0} pcbY={0} />
</board>,
);
await circuit.renderUntilSettled();
assertNoErrors(circuit);
const component = circuit.db.source_component.getWhere({ name: "U1" });
assert.equal(component?.manufacturer_part_number, mpn);
assert.equal(circuit.db.source_port.list().length, 9);
assert.equal(circuit.db.pcb_smtpad.list().length, 9);
// TI Table 6-1, Figure 6-2, and DSG0008A land-pattern drawing.
const expected = [
["GS", -0.95, 0.75],
["IN_NEG", -0.95, 0.25],
["IN_POS", -0.95, -0.25],
["V_NEG", -0.95, -0.75],
["REF", 0.95, -0.75],
["OUT", 0.95, -0.25],
["V_POS", 0.95, 0.25],
["SHDN", 0.95, 0.75],
["EP", 0, 0],
] as const;
expected.forEach(([pin, x, y], index) => {
const port = getPort(circuit, "U1", pin);
assert.equal(port.pin_number, index + 1);
const pcbPorts = circuit.db.pcb_port.list({
source_port_id: port.source_port_id,
});
assert.equal(pcbPorts.length, 1);
const pads = circuit.db.pcb_smtpad.list({
pcb_port_id: pcbPorts[0].pcb_port_id,
});
assert.equal(pads.length, 1);
const pad = pads[0];
assert.ok(pad.shape === "rect");
assert.ok(
Math.abs(pad.x - x) < 1e-6 && Math.abs(pad.y - y) < 1e-6,
pin + " position",
);
assert.equal(pad.width, pin === "EP" ? 0.9 : 0.5);
assert.equal(pad.height, pin === "EP" ? 1.6 : 0.25);
});
assert.equal(getPort(circuit, "U1", "VS").pin_number, 7);
assert.equal(getPort(circuit, "U1", "PAD").pin_number, 9);
assert.equal(getPort(circuit, "U1", "N_SHDN").pin_number, 8);
}
assert.throws(
() => INA350({ name: "U1", footprintVariant: "sot_23_8" as never }),
/Unsupported INA350 footprint/,
);
});
for (const settings of [
{},
{ gain: 30 },
{ gain: 50 },
{ gain: 50, shutdown: "enabled" },
] as const) {
const { gain = "external", shutdown = "external" } = settings as {
gain?: 30 | 50 | "external";
shutdown?: "external" | "enabled";
};
test(`INA350CDS module connects gain ${gain} and shutdown ${shutdown} without headers or grounding REF`, async () => {
const circuit = new Circuit();
circuit.add(
<board width={12} height={10} routingDisabled>
<InstrumentationAmplifier_INA350 name="Amp" {...settings} />
</board>,
);
await circuit.renderUntilSettled();
assertNoErrors(circuit);
for (const [name, pin, netName] of [
["U1", "IN_NEG", "IN_NEG"],
["U1", "IN_POS", "IN_POS"],
["U1", "OUT", "OUT"],
["U1", "V_POS", "VS"],
["U1", "SHDN", shutdown === "external" ? "SHDN" : "VS"],
["U1", "GS", gain === "external" ? "GS" : gain === 30 ? "GND" : "VS"],
["U1", "V_NEG", "GND"],
["U1", "REF", "REF"],
["U1", "EP", "GND"],
["C1", "pin1", "VS"],
["C1", "pin2", "GND"],
]) {
const port = getPort(circuit, name, pin);
const net = circuit.db.source_net.getWhere({ name: netName });
assert.ok(net);
assert.ok(
connectedIds(circuit, port.source_port_id).has(net.source_net_id),
name + "." + pin + " -> " + netName,
);
}
const nets = circuit.db.source_net.list();
assert.equal(
nets.length,
6 + Number(gain === "external") + Number(shutdown === "external"),
);
for (const net of nets) {
const connected = connectedIds(circuit, net.source_net_id);
assert.equal(
nets.filter((other) => connected.has(other.source_net_id)).length,
1,
net.name + " is shorted",
);
}
const capacitor = circuit.db.source_component.getWhere({ name: "C1" });
assert.ok(capacitor?.ftype === "simple_capacitor");
assert.equal(capacitor.capacitance, 1e-7);
assert.equal(circuit.db.pcb_component.list().length, 2);
assert.deepEqual(
circuit.db.source_component
.list()
.map((c) => c.name)
.sort(),
["C1", "U1"],
);
// Custom-symbol ports must be the physical pins, not disconnected drawing-only duplicates.
const chip = circuit.db.source_component.getWhere({ name: "U1" });
assert.ok(chip);
assert.equal(chip.manufacturer_part_number, "INA350CDSIDSGR");
const ports = circuit.db.source_port.list({
source_component_id: chip.source_component_id,
});
assert.equal(ports.length, 9);
for (const port of ports) {
const schematicPorts = circuit.db.schematic_port.list({
source_port_id: port.source_port_id,
});
assert.equal(schematicPorts.length, 1);
const schematicPort = schematicPorts[0];
const hasNativeTraceLabel = circuit.db.schematic_net_label
.list()
.some(
(label) =>
Boolean(label.source_trace_id) &&
label.anchor_position !== undefined &&
Math.abs(label.anchor_position.x - schematicPort.center.x) < 1e-6 &&
Math.abs(label.anchor_position.y - schematicPort.center.y) < 1e-6,
);
assert.ok(
schematicPort.is_connected || hasNativeTraceLabel,
`${port.port_hints?.join("/")} schematic port is disconnected`,
);
assert.equal(
circuit.db.pcb_port.list({ source_port_id: port.source_port_id })
.length,
1,
);
}
for (const [pin, x, y] of [
["IN_NEG", -2.5, 1],
["IN_POS", -2.5, -1],
["OUT", 2.5, 0],
] as const) {
const port = circuit.db.schematic_port.getWhere({
source_port_id: getPort(circuit, "U1", pin).source_port_id,
});
assert.ok(port);
assert.deepEqual(port.center, { x, y });
}
});
}
test("two imported INA350 modules accept parent connections without shorting their nets", async () => {
const circuit = new Circuit();
const signals = [
"IN_NEG",
"IN_POS",
"OUT",
"VS",
"V_NEG",
"REF",
"GS",
"SHDN",
] as const;
circuit.add(
<board width={36} height={26} routingDisabled>
<InstrumentationAmplifier_INA350 name="AmpA" pcbX={-6} schX={-6} />
<InstrumentationAmplifier_INA350 name="AmpB" pcbX={6} schX={6} />
<chip
name="JA"
footprint="pinrow8"
pcbRotation={90}
pcbX={-13}
pcbY={0}
schX={-6}
schY={6}
pinLabels={Object.fromEntries(
signals.map((signal, index) => [`pin${index + 1}`, signal]),
)}
connections={Object.fromEntries(
signals.map((signal) => [signal, `.AmpA .U1 > .${signal}`]),
)}
/>
<chip
name="JB"
footprint="pinrow8"
pcbRotation={90}
pcbX={13}
pcbY={0}
schX={6}
schY={6}
pinLabels={Object.fromEntries(
signals.map((signal, index) => [`pin${index + 1}`, signal]),
)}
connections={Object.fromEntries(
signals.map((signal) => [signal, `.AmpB .U1 > .${signal}`]),
)}
/>
</board>,
);
await circuit.renderUntilSettled();
assertNoErrors(circuit);
for (const [group, header] of [
["AmpA", "JA"],
["AmpB", "JB"],
]) {
for (const signal of signals) {
const connected = connectedIds(
circuit,
getPort(circuit, "U1", signal, group).source_port_id,
);
assert.ok(connected.has(getPort(circuit, header, signal).source_port_id));
for (const other of signals) {
assert.ok(
!connected.has(
getPort(circuit, group === "AmpA" ? "JB" : "JA", other)
.source_port_id,
),
"module isolation",
);
}
}
}
});
test("INA350CDS rejects ABS gains instead of silently choosing an incorrect gain", () => {
assert.throws(
() => InstrumentationAmplifier_INA350({ name: "Amp", gain: 20 as never }),
/INA350CDS supports gains 30\/50/,
);
});
for (const gain of ["external", 30, 50] as const) {
test(`INA350 module routes all parent interface connections at gain ${gain}`, async () => {
const circuit = new Circuit();
const interfaces = [
{
name: "ParentInput",
x: -6,
signals: ["V_NEG", "IN_POS", "IN_NEG", "GS"],
},
{ name: "ParentOutput", x: 8, signals: ["REF", "OUT", "VS", "SHDN"] },
];
circuit.add(
<board width={26} height={18} minTraceWidth={0.1} nominalTraceWidth={0.1}>
<InstrumentationAmplifier_INA350 name="Amp" gain={gain} />
{/* Test-only physical endpoints stand in for the parent application's parts. */}
{interfaces.map(({ name, x, signals }) => (
<chip
key={name}
name={name}
footprint="pinrow4"
pcbRotation={90}
pcbX={x}
pinLabels={Object.fromEntries(
signals.map((s, i) => [`pin${i + 1}`, s]),
)}
connections={Object.fromEntries(
signals
.filter((s) => s !== "GS" || gain === "external")
.map((s) => [s, `.Amp .U1 > .${s}`]),
)}
/>
))}
</board>,
);
await circuit.renderUntilSettled();
assertNoErrors(circuit);
assert.ok(circuit.db.pcb_trace.list().length > 0);
for (const { name, signals } of interfaces) {
for (const signal of signals.filter(
(s) => s !== "GS" || gain === "external",
)) {
const connected = connectedIds(
circuit,
getPort(circuit, name, signal).source_port_id,
);
assert.ok(connected.has(getPort(circuit, "U1", signal).source_port_id));
}
}
});
}