@vessel-dsp/compiler 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/bbd-clock.d.ts.map +1 -1
- package/dist/bbd-clock.js +5 -0
- package/dist/bbd-clock.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +261 -12
- package/dist/compile.js.map +1 -1
- package/dist/device-laws.d.ts.map +1 -1
- package/dist/device-laws.js +541 -97
- package/dist/device-laws.js.map +1 -1
- package/dist/fixtures/circuits.d.ts +23 -2
- package/dist/fixtures/circuits.d.ts.map +1 -1
- package/dist/fixtures/circuits.js +205 -2
- package/dist/fixtures/circuits.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/netlist.d.ts +30 -0
- package/dist/netlist.d.ts.map +1 -1
- package/dist/netlist.js +488 -8
- package/dist/netlist.js.map +1 -1
- package/dist/part-catalog.d.ts +35 -18
- package/dist/part-catalog.d.ts.map +1 -1
- package/dist/part-catalog.js +2474 -592
- package/dist/part-catalog.js.map +1 -1
- package/dist/registry-data/bjt.d.ts +3 -0
- package/dist/registry-data/bjt.d.ts.map +1 -0
- package/dist/registry-data/bjt.js +1350 -0
- package/dist/registry-data/bjt.js.map +1 -0
- package/dist/registry-data/charge-pump.d.ts +3 -0
- package/dist/registry-data/charge-pump.d.ts.map +1 -0
- package/dist/registry-data/charge-pump.js +204 -0
- package/dist/registry-data/charge-pump.js.map +1 -0
- package/dist/registry-data/diode.d.ts +3 -0
- package/dist/registry-data/diode.d.ts.map +1 -0
- package/dist/registry-data/diode.js +683 -0
- package/dist/registry-data/diode.js.map +1 -0
- package/dist/registry-data/index.d.ts +20 -0
- package/dist/registry-data/index.d.ts.map +1 -0
- package/dist/registry-data/index.js +100 -0
- package/dist/registry-data/index.js.map +1 -0
- package/dist/registry-data/jfet.d.ts +3 -0
- package/dist/registry-data/jfet.d.ts.map +1 -0
- package/dist/registry-data/jfet.js +605 -0
- package/dist/registry-data/jfet.js.map +1 -0
- package/dist/registry-data/loader.d.ts +137 -0
- package/dist/registry-data/loader.d.ts.map +1 -0
- package/dist/registry-data/loader.js +350 -0
- package/dist/registry-data/loader.js.map +1 -0
- package/dist/registry-data/mosfet.d.ts +3 -0
- package/dist/registry-data/mosfet.d.ts.map +1 -0
- package/dist/registry-data/mosfet.js +63 -0
- package/dist/registry-data/mosfet.js.map +1 -0
- package/dist/registry-data/opamp.d.ts +37 -0
- package/dist/registry-data/opamp.d.ts.map +1 -0
- package/dist/registry-data/opamp.js +669 -0
- package/dist/registry-data/opamp.js.map +1 -0
- package/dist/registry-data/regulator.d.ts +3 -0
- package/dist/registry-data/regulator.d.ts.map +1 -0
- package/dist/registry-data/regulator.js +189 -0
- package/dist/registry-data/regulator.js.map +1 -0
- package/dist/registry-data/schema.d.ts +56 -0
- package/dist/registry-data/schema.d.ts.map +1 -0
- package/dist/registry-data/schema.js +142 -0
- package/dist/registry-data/schema.js.map +1 -0
- package/dist/registry-data/zener.d.ts +3 -0
- package/dist/registry-data/zener.d.ts.map +1 -0
- package/dist/registry-data/zener.js +547 -0
- package/dist/registry-data/zener.js.map +1 -0
- package/dist/registry.d.ts +61 -6
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +35 -7
- package/dist/registry.js.map +1 -1
- package/dist/types.d.ts +209 -19
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/device-laws.js
CHANGED
|
@@ -19,7 +19,7 @@ import { checkDeclaredProgram } from "./declared-program.js";
|
|
|
19
19
|
import { bjtPartRegisteredAsSections, identify, mayCarryRegistrySections, opampPartRegisteredAsOtherLaw, requiresIdentification, } from "./identify.js";
|
|
20
20
|
import { isMultiDeviceTransistorShell } from "./unreadable-terminal-role.js";
|
|
21
21
|
import { deriveBbdDelayFromNetlist, resolveClockModulationSource, deriveOpenOx2ClockLaw, derivePt2399DelayFromNetlist, deriveM50195DelayFromNetlist, OPEN_OX2_VDD_VOLTS, OPEN_OX2_VTH_VOLTS, OPEN_OX2_VF_VOLTS, OPEN_OX2_FLOOR_VOLTS, } from "./bbd-clock.js";
|
|
22
|
-
import { foldPartId, foldToken, pinoutMatches, registryEntryFor, registryLawFor, registryModelFor, registryOpampGainBandwidth,
|
|
22
|
+
import { foldPartId, foldToken, pinoutMatches, registryEntryFor, registryLawFor, registryModelFor, registryOpampGainBandwidth, registryOpampSwing, opampOutputSwingOf, terminalWithRole, withRecordedReads, } from "./registry.js";
|
|
23
23
|
/** Room-temperature thermal voltage; the diode law needs a reference somewhere. */
|
|
24
24
|
export const THERMAL_VOLTAGE = 0.025_852;
|
|
25
25
|
/**
|
|
@@ -333,17 +333,33 @@ function opampRails(device, rails, nodeToVoltage) {
|
|
|
333
333
|
return { high, low };
|
|
334
334
|
}
|
|
335
335
|
/**
|
|
336
|
-
* The rails an op-amp's output can reach: each supply rail pulled in by the part's
|
|
337
|
-
*
|
|
338
|
-
*
|
|
336
|
+
* The rails an op-amp's output can reach: each supply rail pulled in by the part's printed drop for
|
|
337
|
+
* that side, carried to this op-amp's own supply by the row's stated `translation`.
|
|
338
|
+
*
|
|
339
|
+
* `"constant"` applies the printed drops as printed. `"proportional"` multiplies both by the
|
|
340
|
+
* op-amp's supply span (`high - low`, so 9 for a 9 V single supply and 18 for a +-9 V pair) over the
|
|
341
|
+
* span the row was printed at. An unstated swing, an unknown rail, a span that is not positive, or a
|
|
342
|
+
* pair that would close the output interval (`high - dropHigh <= low + dropLow`) leaves the rails as
|
|
343
|
+
* they are, so a part with no entry renders bit-for-bit as before. `carried` is set only when the rails
|
|
344
|
+
* moved; `opamp-law-default-parameters` reads it to tell a pair used at its printed supply from one
|
|
345
|
+
* carried across a supply.
|
|
339
346
|
*/
|
|
340
|
-
function
|
|
341
|
-
if (
|
|
342
|
-
return rails;
|
|
347
|
+
function railsAfterSwing(rails, swing) {
|
|
348
|
+
if (swing === null || rails.high === null || rails.low === null) {
|
|
349
|
+
return { rails, carried: null };
|
|
343
350
|
}
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
351
|
+
const span = rails.high - rails.low;
|
|
352
|
+
if (!(span > 0)) {
|
|
353
|
+
return { rails, carried: null };
|
|
354
|
+
}
|
|
355
|
+
const scale = swing.printed.translation === "proportional" ? span / swing.printed.supplyVolts : 1;
|
|
356
|
+
const highVolts = swing.highVolts * scale;
|
|
357
|
+
const lowVolts = swing.lowVolts * scale;
|
|
358
|
+
const high = rails.high - highVolts;
|
|
359
|
+
const low = rails.low + lowVolts;
|
|
360
|
+
return high > low
|
|
361
|
+
? { rails: { high, low }, carried: { highVolts, lowVolts, circuitSupplyVolts: span } }
|
|
362
|
+
: { rails, carried: null };
|
|
347
363
|
}
|
|
348
364
|
/**
|
|
349
365
|
* The one high-side and one low-side supply pin a device declares, read by the same role tokens
|
|
@@ -436,39 +452,32 @@ function quiescentSupplyDraw(device, registry, expanded, rails, nodeToVoltage) {
|
|
|
436
452
|
{ outcome: "law", device: id, law: { kind: "conductance", siemens: amps / span } },
|
|
437
453
|
];
|
|
438
454
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
455
|
+
export function attachDeviceLaws(netlist, registry) {
|
|
456
|
+
const regulatedBase = registrySuppliedVoltages(netlist, registry);
|
|
457
|
+
const nodeToVoltage = buildNodeToVoltageMap(netlist, regulatedBase);
|
|
458
|
+
const devicesTouchingNode = deviceCountByNode(netlist);
|
|
459
|
+
// A pump's rail is derived from the declared input, so every derivation runs against
|
|
460
|
+
// the document's own voltages -- before any pump output joins the map -- and the
|
|
461
|
+
// derived supplies merge into the rail set after. A pump can never back itself.
|
|
462
|
+
const pumpDerivations = new Map();
|
|
446
463
|
for (const device of netlist.devices) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
device.kind !== "resistor" &&
|
|
451
|
-
device.kind !== "inductor")) {
|
|
452
|
-
continue;
|
|
464
|
+
const derivation = pumpDerivation(device, registry, nodeToVoltage, devicesTouchingNode);
|
|
465
|
+
if (derivation !== null) {
|
|
466
|
+
pumpDerivations.set(device.id, derivation);
|
|
453
467
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (
|
|
458
|
-
|
|
468
|
+
}
|
|
469
|
+
const regulated = [...regulatedBase];
|
|
470
|
+
for (const derivation of pumpDerivations.values()) {
|
|
471
|
+
if (derivation.kind !== "model") {
|
|
472
|
+
continue;
|
|
459
473
|
}
|
|
460
|
-
|
|
461
|
-
|
|
474
|
+
regulated.push({ node: derivation.outputNode, volts: derivation.volts });
|
|
475
|
+
if (!nodeToVoltage.has(derivation.outputNode)) {
|
|
476
|
+
nodeToVoltage.set(derivation.outputNode, derivation.volts);
|
|
462
477
|
}
|
|
463
478
|
}
|
|
464
|
-
return map;
|
|
465
|
-
}
|
|
466
|
-
export function attachDeviceLaws(netlist, registry) {
|
|
467
|
-
const regulated = registrySuppliedVoltages(netlist, registry);
|
|
468
479
|
const rails = supplyRails(netlist, regulated);
|
|
469
480
|
const supply = supplyNodes(netlist);
|
|
470
|
-
const nodeToVoltage = buildNodeToVoltageMap(netlist, regulated);
|
|
471
|
-
const oneHopVoltage = buildOneHopVoltageMap(netlist, nodeToVoltage);
|
|
472
481
|
const mainsFrequencyHz = mainsFrequency(netlist);
|
|
473
482
|
// Stage 1 (`netlist.ts`) already resolved each control's real taper -- from the panel
|
|
474
483
|
// control's own declaration, or failing that, from the first device bound to it -- onto
|
|
@@ -481,7 +490,6 @@ export function attachDeviceLaws(netlist, registry) {
|
|
|
481
490
|
// is multi-section. Expanding into ordinary devices means partitioning, lowering and the runtime
|
|
482
491
|
// see nothing new -- the alternative, a fourth `DeviceResolution` outcome, would have to be
|
|
483
492
|
// understood by every consumer of one.
|
|
484
|
-
const devicesTouchingNode = deviceCountByNode(netlist);
|
|
485
493
|
// Nodes inside a registered package, numbered past every node the document uses.
|
|
486
494
|
const internalNodes = new Map();
|
|
487
495
|
const firstInternal = Math.max(0, ...netlist.nodes) + 1;
|
|
@@ -497,7 +505,7 @@ export function attachDeviceLaws(netlist, registry) {
|
|
|
497
505
|
const devices = [];
|
|
498
506
|
const resolutions = [];
|
|
499
507
|
for (const device of netlist.devices) {
|
|
500
|
-
const expanded = sectionDevices(device, registry, rails, supply, nodeToVoltage, internalNode, netlist);
|
|
508
|
+
const expanded = sectionDevices(device, registry, rails, supply, nodeToVoltage, internalNode, netlist, pumpDerivations.get(device.id) ?? null);
|
|
501
509
|
const draw = quiescentSupplyDraw(device, registry, expanded !== null, rails, nodeToVoltage);
|
|
502
510
|
if (draw !== null) {
|
|
503
511
|
devices.push(draw[0]);
|
|
@@ -505,7 +513,7 @@ export function attachDeviceLaws(netlist, registry) {
|
|
|
505
513
|
}
|
|
506
514
|
if (expanded === null) {
|
|
507
515
|
devices.push(device);
|
|
508
|
-
resolutions.push(resolveDevice(device, registry, rails, supply, taperByControl, mainsFrequencyHz, devicesTouchingNode, nodeToVoltage,
|
|
516
|
+
resolutions.push(resolveDevice(device, registry, rails, supply, taperByControl, mainsFrequencyHz, devicesTouchingNode, nodeToVoltage, netlist));
|
|
509
517
|
continue;
|
|
510
518
|
}
|
|
511
519
|
for (const [section, resolution] of expanded) {
|
|
@@ -550,6 +558,12 @@ export function declaredRegulatorTerminals(device, model) {
|
|
|
550
558
|
// kinds whose vocabulary carries them (`regulator`, `power-converter`, `voltage-source`,
|
|
551
559
|
// `rail`, `battery`), and an `ic` may declare nothing but `pin`. A device that reaches here
|
|
552
560
|
// carrying a supply electrode was therefore declared as a kind entitled to state one.
|
|
561
|
+
// Charge-pump parts never take this path: their volts are derived per circuit from the
|
|
562
|
+
// input node, and the placeholder volts in their entries must not be stamped. They are
|
|
563
|
+
// bound by `pumpDerivation` instead, which runs before this is consulted.
|
|
564
|
+
if (isChargePumpPart(device)) {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
553
567
|
if (model.sections.length !== 1) {
|
|
554
568
|
return null;
|
|
555
569
|
}
|
|
@@ -604,7 +618,16 @@ export function declaredRegulatorTerminals(device, model) {
|
|
|
604
618
|
}
|
|
605
619
|
function sectionDevices(device, registry, rails, supply, nodeToVoltage, internalNode = () => {
|
|
606
620
|
throw new Error("no internal-node allocator");
|
|
607
|
-
}, netlist) {
|
|
621
|
+
}, netlist, pump = null) {
|
|
622
|
+
// A decided pump never reaches generic expansion: the placeholder volts in the pump
|
|
623
|
+
// entries must not be stamped. An isolated pump defers to `resolveDevice`, which
|
|
624
|
+
// names the isolation before any pump question.
|
|
625
|
+
if (pump !== null) {
|
|
626
|
+
if (pump.kind === "isolated") {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
return pumpSectionDevices(device, pump);
|
|
630
|
+
}
|
|
608
631
|
// **`mayCarryRegistrySections`, not `requiresIdentification`.** An OTA reaches here so a
|
|
609
632
|
// registered multi-section part (the BA662A's OTA plus its Darlington buffer) can be expanded;
|
|
610
633
|
// when the registry has no matching entry this returns null and the caller falls through to
|
|
@@ -631,6 +654,25 @@ function sectionDevices(device, registry, rails, supply, nodeToVoltage, internal
|
|
|
631
654
|
if (model.kind !== "sections" && model.kind !== "sections-core") {
|
|
632
655
|
return null;
|
|
633
656
|
}
|
|
657
|
+
// An explicit override cannot target one section of a package the registry expands: the
|
|
658
|
+
// section laws are the entry's numbers (a dual op-amp's two amplifiers share one part's
|
|
659
|
+
// figures), and `device.parameters` -- legacy or override -- never reach them. Accepting
|
|
660
|
+
// the block would be a silent drop, so it refuses by name instead. The way out is a
|
|
661
|
+
// source-declared `devices:` split, whose devices take the device-class law that the
|
|
662
|
+
// override does feed.
|
|
663
|
+
if (device.deviceOverride !== undefined) {
|
|
664
|
+
return [
|
|
665
|
+
[
|
|
666
|
+
device,
|
|
667
|
+
{
|
|
668
|
+
outcome: "unsupported",
|
|
669
|
+
device: device.id,
|
|
670
|
+
reason: `part ${identity.partId} is expanded from a registry sections entry, whose numbers the override cannot replace: ` +
|
|
671
|
+
`declare the package's devices explicitly (a "devices:" split) to take the device-class law an override feeds, or remove the DeviceOverride block`,
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
];
|
|
675
|
+
}
|
|
634
676
|
// A `sections` entry's `terminals` index into the entry's canonical pin order, not the
|
|
635
677
|
// declaration order the document happened to use. When the entry names its pins by role and
|
|
636
678
|
// that naming is a clean bijection onto the device's declared terminals, resolve each
|
|
@@ -740,10 +782,12 @@ function sectionDevices(device, registry, rails, supply, nodeToVoltage, internal
|
|
|
740
782
|
}
|
|
741
783
|
// Read from the section's *parent* device, which is where the package's supply pins
|
|
742
784
|
// are declared -- a section's own terminal list is only its three signal pins.
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const
|
|
785
|
+
const sectionSwing = section.law.kind === "ideal-opamp"
|
|
786
|
+
? opampOutputSwingOf(section.law, identity.partId)
|
|
787
|
+
: null;
|
|
788
|
+
const sectionSwung = railsAfterSwing(opampRails(device, rails, nodeToVoltage), sectionSwing);
|
|
789
|
+
const railHigh = sectionSwung.rails.high;
|
|
790
|
+
const railLow = sectionSwung.rails.low;
|
|
747
791
|
// An op-amp's rails come from the op-amp's own supply terminals or the circuit's supply set,
|
|
748
792
|
// not from the registry -- the same substitution a declared `opamp` gets. A registry cannot know them:
|
|
749
793
|
// it describes a part, and the rails are a property of the pedal the part is fitted to.
|
|
@@ -760,8 +804,16 @@ function sectionDevices(device, registry, rails, supply, nodeToVoltage, internal
|
|
|
760
804
|
// The registry states the part's gain; only the rails are the pedal's.
|
|
761
805
|
openLoopGain: section.law.openLoopGain,
|
|
762
806
|
// Kept on the lowered law so `findOpampLawDefaults` can tell a part that
|
|
763
|
-
// states how close it gets to the rails from one that does not
|
|
764
|
-
|
|
807
|
+
// states how close it gets to the rails from one that does not, and a pair
|
|
808
|
+
// used at its printed supply from one the lowering carried across a supply.
|
|
809
|
+
...(sectionSwing === null
|
|
810
|
+
? {}
|
|
811
|
+
: {
|
|
812
|
+
outputSwingDropHighVolts: sectionSwing.highVolts,
|
|
813
|
+
outputSwingDropLowVolts: sectionSwing.lowVolts,
|
|
814
|
+
outputSwingPrinted: sectionSwing.printed,
|
|
815
|
+
}),
|
|
816
|
+
...(sectionSwung.carried === null ? {} : { outputSwingCarried: sectionSwung.carried }),
|
|
765
817
|
// The part's datasheet product, when the entry states one. Absent stays
|
|
766
818
|
// absent: it lowers to a `null` stamp, which the compile names.
|
|
767
819
|
...(section.law.gainBandwidthHz === undefined
|
|
@@ -1309,6 +1361,12 @@ function registrySuppliedVoltages(netlist, registry) {
|
|
|
1309
1361
|
if (device.parameters.volts !== undefined) {
|
|
1310
1362
|
continue;
|
|
1311
1363
|
}
|
|
1364
|
+
// Pump parts never supply a static rail: their output is derived per circuit from
|
|
1365
|
+
// the input node by `pumpDerivation`, and the placeholder volts in their entries
|
|
1366
|
+
// must not leak into the rail set through a role that happens to read as an output.
|
|
1367
|
+
if (isChargePumpPart(device)) {
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1312
1370
|
const identity = identify(device, registry);
|
|
1313
1371
|
if (identity === null) {
|
|
1314
1372
|
continue;
|
|
@@ -1421,29 +1479,375 @@ export function deviceCountByNode(netlist) {
|
|
|
1421
1479
|
}
|
|
1422
1480
|
return counts;
|
|
1423
1481
|
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Charge-pump inverter parts, by folded part number: every spelling the DIY BOM census
|
|
1484
|
+
* attributes to the family (TC1044S/SCPA, LT1054/CP, 7660 // SCPAZ, TC7662BC, ICL7660SCPA(Z),
|
|
1485
|
+
* LTC1144, MAX1044/S) plus the order codes and package markings the datasheets print.
|
|
1486
|
+
* Compared whole with `foldPartId`, never by substring: membership routes a part to the
|
|
1487
|
+
* pump handling, it does not model it -- modelling needs a registry entry, and a member
|
|
1488
|
+
* with no entry falls through to the honest unknown-chip path.
|
|
1489
|
+
*/
|
|
1424
1490
|
const CHARGE_PUMP_PARTS = new Set([
|
|
1425
|
-
"
|
|
1491
|
+
"tc1044s",
|
|
1426
1492
|
"tc1044scpa",
|
|
1427
|
-
"
|
|
1493
|
+
"tc1044scoa",
|
|
1494
|
+
"tc1044sepa",
|
|
1495
|
+
"tc1044seoa",
|
|
1496
|
+
"tc1044sija",
|
|
1497
|
+
"tc1044smja",
|
|
1498
|
+
"icl7660",
|
|
1499
|
+
"icl7660cpa",
|
|
1500
|
+
"icl7660cpaz",
|
|
1501
|
+
"icl7660cba",
|
|
1502
|
+
"icl7660cbaz",
|
|
1503
|
+
"7660cpa",
|
|
1504
|
+
"7660cpaz",
|
|
1505
|
+
"7660cba",
|
|
1506
|
+
"7660cbaz",
|
|
1507
|
+
"icl7660a",
|
|
1508
|
+
"icl7660acpa",
|
|
1509
|
+
"icl7660acpaz",
|
|
1510
|
+
"icl7660acba",
|
|
1511
|
+
"icl7660acbaz",
|
|
1512
|
+
"icl7660aiba",
|
|
1513
|
+
"icl7660aibaz",
|
|
1514
|
+
"7660acpa",
|
|
1515
|
+
"7660acpaz",
|
|
1516
|
+
"7660acba",
|
|
1517
|
+
"7660acbaz",
|
|
1518
|
+
"7660aiba",
|
|
1519
|
+
"7660aibaz",
|
|
1520
|
+
"icl7660s",
|
|
1521
|
+
"icl7660scpa",
|
|
1522
|
+
"icl7660scpaz",
|
|
1523
|
+
"7660scpa",
|
|
1524
|
+
"7660scpaz",
|
|
1525
|
+
"tc7662b",
|
|
1526
|
+
"tc7662bcpa",
|
|
1527
|
+
"tc7662bepa",
|
|
1528
|
+
"tc7662bcoa",
|
|
1529
|
+
"tc7662beoa",
|
|
1428
1530
|
"7662",
|
|
1429
|
-
"lt1054cp",
|
|
1430
1531
|
"lt1054",
|
|
1532
|
+
"lt1054c",
|
|
1533
|
+
"lt1054cp",
|
|
1534
|
+
"lt1054cpe4",
|
|
1535
|
+
"lt1054i",
|
|
1536
|
+
"ltc1144",
|
|
1537
|
+
"ltc1144cn8",
|
|
1538
|
+
"ltc1144in8",
|
|
1539
|
+
"ltc1144cs8",
|
|
1540
|
+
"ltc1144is8",
|
|
1541
|
+
"max1044",
|
|
1542
|
+
"max1044cpa",
|
|
1543
|
+
"max1044csa",
|
|
1544
|
+
"max1044epa",
|
|
1545
|
+
"max1044esa",
|
|
1546
|
+
// Census spellings with no registry entry: routed here so a pump whose rail the
|
|
1547
|
+
// document declares still opens honestly, and one whose rail is undeclared refuses
|
|
1548
|
+
// as an unknown chip instead of claiming a modelled rail.
|
|
1549
|
+
"tc7662bc",
|
|
1550
|
+
"max1044s",
|
|
1551
|
+
"7660",
|
|
1431
1552
|
]);
|
|
1553
|
+
/** Whether this device names a charge-pump inverter part, compared whole. */
|
|
1554
|
+
function isChargePumpPart(device) {
|
|
1555
|
+
const partNumber = device.identity.partNumber;
|
|
1556
|
+
if (partNumber === null) {
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
return CHARGE_PUMP_PARTS.has(foldPartId(partNumber));
|
|
1560
|
+
}
|
|
1432
1561
|
/**
|
|
1433
|
-
*
|
|
1562
|
+
* The closed role vocabularies a pump's three supply terminals are bound by, compared as
|
|
1563
|
+
* whole folded tokens by `terminalWithRole` -- never as substrings, never against a
|
|
1564
|
+
* description. These query the ENTRY's role groups (which carry the datasheet pinout in
|
|
1565
|
+
* package order), so a document naming its terminals in any order binds, and a document
|
|
1566
|
+
* numbering its pins binds positionally instead.
|
|
1567
|
+
*/
|
|
1568
|
+
const PUMP_INPUT_ROLES = ["vplus", "vin", "vdd", "vcc", "input"];
|
|
1569
|
+
const PUMP_OUTPUT_ROLES = [
|
|
1570
|
+
"negativeoutput",
|
|
1571
|
+
"voutnegative",
|
|
1572
|
+
"vout",
|
|
1573
|
+
"voutput",
|
|
1574
|
+
"out",
|
|
1575
|
+
"output",
|
|
1576
|
+
];
|
|
1577
|
+
const PUMP_GROUND_ROLES = ["gnd", "ground"];
|
|
1578
|
+
/**
|
|
1579
|
+
* A charge-pump IC whose OUTPUT node is already backed by a declared rail is a
|
|
1434
1580
|
* view-only structural anchor: the rail supplies the voltage, the IC adds nothing.
|
|
1435
1581
|
* The Klon's MAX1044 is handled by `isElectricallyIsolated` (all nodes private);
|
|
1436
1582
|
* this covers the wider class where the pump shares nets with the declared rail.
|
|
1583
|
+
*
|
|
1584
|
+
* Tests the pump's OUTPUT node, by role, and nothing else. The previous version asked
|
|
1585
|
+
* whether ANY non-ground node of the pump touched a declared OR one-hop voltage, and a
|
|
1586
|
+
* reservoir capacitor to ground was enough to satisfy it: with both Plumes rails deleted,
|
|
1587
|
+
* nodes 35 and 36 each one-hop to 0 V through C102/C101, and the pump claimed a modelled
|
|
1588
|
+
* rail on ground continuity alone. Ground is not a rail, and an input rail is not the
|
|
1589
|
+
* output rail -- pigtronix's LT1054 sat on the same false positive through its input
|
|
1590
|
+
* capacitor, and moogerfooger's 7662 through its output capacitor.
|
|
1437
1591
|
*/
|
|
1438
|
-
function isChargePumpWithDeclaredRail(device, nodeToVoltage
|
|
1439
|
-
|
|
1440
|
-
if (partNumber === null) {
|
|
1592
|
+
function isChargePumpWithDeclaredRail(device, nodeToVoltage) {
|
|
1593
|
+
if (!isChargePumpPart(device)) {
|
|
1441
1594
|
return false;
|
|
1442
1595
|
}
|
|
1443
|
-
|
|
1596
|
+
const output = chargePumpOutputIndex(device);
|
|
1597
|
+
if (output === null) {
|
|
1444
1598
|
return false;
|
|
1445
1599
|
}
|
|
1446
|
-
|
|
1600
|
+
const node = device.nodes[output];
|
|
1601
|
+
if (node === undefined || node === 0) {
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
return nodeToVoltage.has(node);
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* The index of the pump's inverted-output terminal, or `null` when the document does
|
|
1608
|
+
* not settle it.
|
|
1609
|
+
*
|
|
1610
|
+
* A declared supply electrode decides first: a shell that states `ground` plus
|
|
1611
|
+
* `negative` (or `positive`, the regulator convention deluxe-memory-man uses for its
|
|
1612
|
+
* -15 V output) names its output outright. Otherwise the folded terminal names are read
|
|
1613
|
+
* against the output vocabulary -- whole tokens, unambiguous or nothing.
|
|
1614
|
+
*/
|
|
1615
|
+
function chargePumpOutputIndex(device) {
|
|
1616
|
+
const declared = device.identity.declaredTerminalRoles;
|
|
1617
|
+
const onlyDeclared = (role) => {
|
|
1618
|
+
const found = declared.flatMap((value, index) => value === role ? [index] : []);
|
|
1619
|
+
return found.length === 1 ? found[0] : null;
|
|
1620
|
+
};
|
|
1621
|
+
const fromDeclaration = onlyDeclared("negative") ?? onlyDeclared("positive");
|
|
1622
|
+
if (fromDeclaration !== null) {
|
|
1623
|
+
return fromDeclaration;
|
|
1624
|
+
}
|
|
1625
|
+
return terminalWithRole(device.identity.terminalRoles, PUMP_OUTPUT_ROLES);
|
|
1626
|
+
}
|
|
1627
|
+
function pumpDerivation(device, registry, nodeToVoltage, devicesTouchingNode) {
|
|
1628
|
+
if (!isChargePumpPart(device)) {
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
// Isolation stays the isolation reporter's beat: a pump on private nodes is view-only
|
|
1632
|
+
// however it is part-numbered, and `resolveDevice` names it before any pump question.
|
|
1633
|
+
if (isElectricallyIsolated(device, devicesTouchingNode)) {
|
|
1634
|
+
return { kind: "isolated" };
|
|
1635
|
+
}
|
|
1636
|
+
const identity = identify(device, registry);
|
|
1637
|
+
if (identity === null) {
|
|
1638
|
+
return null;
|
|
1639
|
+
}
|
|
1640
|
+
const entry = registryEntryFor(registry, identity, device.nodes.length);
|
|
1641
|
+
if (entry === null || entry.model.kind !== "sections") {
|
|
1642
|
+
return null;
|
|
1643
|
+
}
|
|
1644
|
+
const section = entry.model.sections[0];
|
|
1645
|
+
if (entry.model.sections.length !== 1 ||
|
|
1646
|
+
section === undefined ||
|
|
1647
|
+
section.law.kind !== "voltage-source" ||
|
|
1648
|
+
section.terminals.length !== 2) {
|
|
1649
|
+
return {
|
|
1650
|
+
kind: "unbound",
|
|
1651
|
+
detail: `part ${identity.partId} is routed to the charge-pump derivation but its registry ` +
|
|
1652
|
+
`entry is not a single two-terminal voltage-source section, so no rail can be ` +
|
|
1653
|
+
`derived from it`,
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
const sourceOhms = section.law.sourceOhms;
|
|
1657
|
+
// Which package positions the supply input, inverted output, and reference sit at,
|
|
1658
|
+
// read off the ENTRY's role groups -- the datasheet pinout in package order -- so one
|
|
1659
|
+
// entry serves every declaration order that names its terminals at all.
|
|
1660
|
+
const groups = entry.terminalRoleGroups;
|
|
1661
|
+
const groupIndex = (vocab) => {
|
|
1662
|
+
const wanted = new Set(vocab.map((alias) => foldToken(alias)));
|
|
1663
|
+
const hits = [];
|
|
1664
|
+
groups.forEach((group, index) => {
|
|
1665
|
+
if (group.some((alias) => wanted.has(foldToken(alias)))) {
|
|
1666
|
+
hits.push(index);
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
return hits.length === 1 ? hits[0] : null;
|
|
1670
|
+
};
|
|
1671
|
+
const inputPos = groupIndex(PUMP_INPUT_ROLES);
|
|
1672
|
+
const outputPos = groupIndex(PUMP_OUTPUT_ROLES);
|
|
1673
|
+
const groundPos = groupIndex(PUMP_GROUND_ROLES);
|
|
1674
|
+
if (inputPos === null || outputPos === null || groundPos === null) {
|
|
1675
|
+
return {
|
|
1676
|
+
kind: "unbound",
|
|
1677
|
+
detail: `part ${identity.partId} is registered as a charge pump but its entry does not ` +
|
|
1678
|
+
`name a distinct supply input, inverted output, and ground reference`,
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
const roles = device.identity.terminalRoles;
|
|
1682
|
+
const declared = device.identity.declaredTerminalRoles;
|
|
1683
|
+
const onlyDeclared = (role) => {
|
|
1684
|
+
const found = declared.flatMap((value, index) => value === role ? [index] : []);
|
|
1685
|
+
return found.length === 1 ? found[0] : null;
|
|
1686
|
+
};
|
|
1687
|
+
// A declared supply electrode outranks the entry's pin numbering, the same rule the
|
|
1688
|
+
// regulator path documents: the reference is `ground`, the inverted output is
|
|
1689
|
+
// `negative` (or `positive`, the convention deluxe-memory-man's -15 V output uses),
|
|
1690
|
+
// and the input is the one remaining terminal sitting on a declared nonzero voltage.
|
|
1691
|
+
const declaredBinding = (() => {
|
|
1692
|
+
const ground = onlyDeclared("ground");
|
|
1693
|
+
const output = onlyDeclared("negative") ?? onlyDeclared("positive");
|
|
1694
|
+
if (ground === null || output === null || ground === output) {
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
const candidates = roles
|
|
1698
|
+
.map((_, index) => index)
|
|
1699
|
+
.filter((index) => index !== ground && index !== output)
|
|
1700
|
+
.filter((index) => {
|
|
1701
|
+
const node = device.nodes[index];
|
|
1702
|
+
if (node === undefined || node === 0) {
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1705
|
+
const volts = nodeToVoltage.get(node);
|
|
1706
|
+
return volts !== undefined && volts !== 0;
|
|
1707
|
+
});
|
|
1708
|
+
if (candidates.length !== 1) {
|
|
1709
|
+
return null;
|
|
1710
|
+
}
|
|
1711
|
+
return { input: candidates[0], output, ground };
|
|
1712
|
+
})();
|
|
1713
|
+
// Otherwise the folded terminal names, bound through the entry's own aliases so any
|
|
1714
|
+
// declaration order lands on the right pins.
|
|
1715
|
+
const roleBinding = (() => {
|
|
1716
|
+
const input = terminalWithRole(roles, [...groups[inputPos]]);
|
|
1717
|
+
const output = terminalWithRole(roles, [...groups[outputPos]]);
|
|
1718
|
+
const ground = terminalWithRole(roles, [...groups[groundPos]]);
|
|
1719
|
+
if (input === null || output === null || ground === null) {
|
|
1720
|
+
return null;
|
|
1721
|
+
}
|
|
1722
|
+
if (new Set([input, output, ground]).size !== 3) {
|
|
1723
|
+
return null;
|
|
1724
|
+
}
|
|
1725
|
+
return { input, output, ground };
|
|
1726
|
+
})();
|
|
1727
|
+
// Otherwise a bare-pin shell in package order: terminals numbered pin1..pinN fold to
|
|
1728
|
+
// no role at all, so declaration order against the entry's package-order pinout is
|
|
1729
|
+
// the only reading, exactly as the regulator entries already assume.
|
|
1730
|
+
const positionalBinding = (() => {
|
|
1731
|
+
if (!roles.every((role) => role === null)) {
|
|
1732
|
+
return null;
|
|
1733
|
+
}
|
|
1734
|
+
if (entry.model.kind !== "sections" ||
|
|
1735
|
+
entry.model.pinout.length !== roles.length) {
|
|
1736
|
+
return null;
|
|
1737
|
+
}
|
|
1738
|
+
return { input: inputPos, output: outputPos, ground: groundPos };
|
|
1739
|
+
})();
|
|
1740
|
+
const binding = declaredBinding ?? roleBinding ?? positionalBinding;
|
|
1741
|
+
if (binding === null) {
|
|
1742
|
+
return {
|
|
1743
|
+
kind: "unbound",
|
|
1744
|
+
detail: `part ${identity.partId} is registered as a charge pump, but neither its terminal ` +
|
|
1745
|
+
`names, its declared supply electrodes, nor its pin order bind a supply input, ` +
|
|
1746
|
+
`inverted output, and ground reference: ${device.nodes.length} terminals against ` +
|
|
1747
|
+
`a ${entry.model.pinout.length}-terminal pinout`,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
const inputNode = device.nodes[binding.input];
|
|
1751
|
+
const outputNode = device.nodes[binding.output];
|
|
1752
|
+
const groundNode = device.nodes[binding.ground];
|
|
1753
|
+
if (inputNode === undefined ||
|
|
1754
|
+
outputNode === undefined ||
|
|
1755
|
+
groundNode === undefined) {
|
|
1756
|
+
return {
|
|
1757
|
+
kind: "unbound",
|
|
1758
|
+
detail: `part ${identity.partId} charge-pump binding names a terminal this component ` +
|
|
1759
|
+
`does not declare`,
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
// The reference is circuit ground. A pump tied to anything else is a shell the
|
|
1763
|
+
// derivation cannot place, not a rail against an unknown reference.
|
|
1764
|
+
if (groundNode !== 0 || outputNode === 0) {
|
|
1765
|
+
return {
|
|
1766
|
+
kind: "unbound",
|
|
1767
|
+
detail: `part ${identity.partId} charge-pump binding puts the reference off circuit ` +
|
|
1768
|
+
`ground, so no rail can be derived from it`,
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
// The document keeps the last word: an output the document already drives --
|
|
1772
|
+
// declared rail or regulator-supplied -- stays the document's, and the pump stays open.
|
|
1773
|
+
if (nodeToVoltage.has(outputNode)) {
|
|
1774
|
+
return { kind: "backed" };
|
|
1775
|
+
}
|
|
1776
|
+
const inputVolts = nodeToVoltage.get(inputNode);
|
|
1777
|
+
if (inputVolts === undefined || inputVolts === 0) {
|
|
1778
|
+
return { kind: "no-input" };
|
|
1779
|
+
}
|
|
1780
|
+
// An output no other device touches cannot move the solve: stamping it would only
|
|
1781
|
+
// widen the circuit-wide rail extremes other devices fall back to.
|
|
1782
|
+
if ((devicesTouchingNode.get(outputNode) ?? 0) < 2) {
|
|
1783
|
+
return { kind: "unloaded" };
|
|
1784
|
+
}
|
|
1785
|
+
const lossVolts = entry.chargePumpLossVolts ?? 0;
|
|
1786
|
+
return {
|
|
1787
|
+
kind: "model",
|
|
1788
|
+
inputNode,
|
|
1789
|
+
outputNode,
|
|
1790
|
+
groundNode,
|
|
1791
|
+
inputVolts,
|
|
1792
|
+
volts: -inputVolts - Math.sign(-inputVolts) * lossVolts,
|
|
1793
|
+
sourceOhms: section.law.sourceOhms,
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* A derived pump rail as one ordinary section device plus its law, or the original device
|
|
1798
|
+
* opened with the reason the derivation recorded. Never `null` for a device the
|
|
1799
|
+
* derivation decided: the placeholder volts in the pump entries must not reach generic
|
|
1800
|
+
* expansion, so every decided device is consumed here.
|
|
1801
|
+
*/
|
|
1802
|
+
function pumpSectionDevices(device, derivation) {
|
|
1803
|
+
switch (derivation.kind) {
|
|
1804
|
+
case "backed":
|
|
1805
|
+
return [[device, openIc(device, "charge-pump-declared-rail")]];
|
|
1806
|
+
case "no-input":
|
|
1807
|
+
return [[device, openIc(device, "charge-pump-no-input")]];
|
|
1808
|
+
case "unloaded":
|
|
1809
|
+
return [[device, openIc(device, "charge-pump-output-unloaded")]];
|
|
1810
|
+
case "unbound":
|
|
1811
|
+
return [
|
|
1812
|
+
[
|
|
1813
|
+
device,
|
|
1814
|
+
{
|
|
1815
|
+
outcome: "law",
|
|
1816
|
+
device: device.id,
|
|
1817
|
+
law: { kind: "open" },
|
|
1818
|
+
openReason: "registry-arity-mismatch",
|
|
1819
|
+
insteadOfRefusal: derivation.detail,
|
|
1820
|
+
},
|
|
1821
|
+
],
|
|
1822
|
+
];
|
|
1823
|
+
case "model": {
|
|
1824
|
+
const sectionId = `${device.id}#0`;
|
|
1825
|
+
const sectionDevice = {
|
|
1826
|
+
...device,
|
|
1827
|
+
id: sectionId,
|
|
1828
|
+
nodes: [derivation.outputNode, derivation.groundNode],
|
|
1829
|
+
identity: {
|
|
1830
|
+
...device.identity,
|
|
1831
|
+
terminalRoles: canonicalRoles("voltage-source", 2),
|
|
1832
|
+
declaredTerminalRoles: canonicalRoles("voltage-source", 2),
|
|
1833
|
+
},
|
|
1834
|
+
};
|
|
1835
|
+
return [
|
|
1836
|
+
[
|
|
1837
|
+
sectionDevice,
|
|
1838
|
+
{
|
|
1839
|
+
outcome: "law",
|
|
1840
|
+
device: sectionId,
|
|
1841
|
+
law: {
|
|
1842
|
+
kind: "voltage-source",
|
|
1843
|
+
volts: derivation.volts,
|
|
1844
|
+
sourceOhms: derivation.sourceOhms,
|
|
1845
|
+
},
|
|
1846
|
+
},
|
|
1847
|
+
],
|
|
1848
|
+
];
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1447
1851
|
}
|
|
1448
1852
|
/**
|
|
1449
1853
|
* An OTA's signal terminals, bound by the names the device declares for them.
|
|
@@ -1652,7 +2056,7 @@ function packageDeviceParameters(device, registry) {
|
|
|
1652
2056
|
}
|
|
1653
2057
|
return parameters;
|
|
1654
2058
|
}
|
|
1655
|
-
function resolveDevice(device, registry, rails, supply, taperByControl, mainsFrequencyHz, devicesTouchingNode, nodeToVoltage,
|
|
2059
|
+
function resolveDevice(device, registry, rails, supply, taperByControl, mainsFrequencyHz, devicesTouchingNode, nodeToVoltage, netlist) {
|
|
1656
2060
|
// A component declared as one transistor that is really several devices in one shell. Opened
|
|
1657
2061
|
// rather than stamped, for the reason `isNonExecutableIcSupportShell` opens an IC shell: there
|
|
1658
2062
|
// is no single device here to describe, and a transistor law would invent one. Reported by
|
|
@@ -1685,7 +2089,7 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1685
2089
|
// `findElectricallyIsolatedIcs`, which compile() runs over the same predicate.
|
|
1686
2090
|
return openIc(device, "electrically-isolated");
|
|
1687
2091
|
}
|
|
1688
|
-
if (isChargePumpWithDeclaredRail(device, nodeToVoltage
|
|
2092
|
+
if (isChargePumpWithDeclaredRail(device, nodeToVoltage)) {
|
|
1689
2093
|
// The rail the pump generates is separately declared as a voltage-carrying
|
|
1690
2094
|
// port; the IC itself is a view-only structural anchor, not a solved model.
|
|
1691
2095
|
return openIc(device, "charge-pump-declared-rail");
|
|
@@ -1903,6 +2307,7 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1903
2307
|
if (declared.breakdownVolts !== undefined ||
|
|
1904
2308
|
declared.saturationCurrent !== undefined ||
|
|
1905
2309
|
declared.seriesResistance !== undefined ||
|
|
2310
|
+
declared.emissionCoefficient !== undefined ||
|
|
1906
2311
|
device.isLed === true) {
|
|
1907
2312
|
return {
|
|
1908
2313
|
outcome: "law",
|
|
@@ -1912,6 +2317,8 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1912
2317
|
breakdownVolts: declared.breakdownVolts ?? catalogued.breakdownVolts,
|
|
1913
2318
|
saturationCurrent: declared.saturationCurrent ?? catalogued.saturationCurrent,
|
|
1914
2319
|
seriesResistance: declared.seriesResistance ?? catalogued.seriesResistance,
|
|
2320
|
+
emissionCoefficient: declared.emissionCoefficient ?? catalogued.emissionCoefficient,
|
|
2321
|
+
thermalVoltage: declared.thermalVoltage ?? catalogued.thermalVoltage,
|
|
1915
2322
|
isLed: device.isLed === true ? true : catalogued.isLed,
|
|
1916
2323
|
},
|
|
1917
2324
|
};
|
|
@@ -1928,7 +2335,8 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1928
2335
|
// If it is an LED, use physical LED defaults (higher forward knee ~1.8V to 2.2V),
|
|
1929
2336
|
// otherwise use class defaults (1N4148-ish silicon signal diode).
|
|
1930
2337
|
saturationCurrent: device.parameters.saturationCurrent ?? (isLed ? 1e-12 : 2.52e-9),
|
|
1931
|
-
|
|
2338
|
+
// The source's own `EmissionCoefficient` when it states one, else the class default.
|
|
2339
|
+
emissionCoefficient: device.parameters.emissionCoefficient ?? 1.752,
|
|
1932
2340
|
thermalVoltage: device.parameters.thermalVoltage ?? (isLed ? 0.05 : THERMAL_VOLTAGE),
|
|
1933
2341
|
// 0 for an ordinary diode. Read from the source, never inferred from a part
|
|
1934
2342
|
// number: `1N4733` is a 5.1 V zener and `1N4148` is not, and telling them apart
|
|
@@ -1984,10 +2392,10 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1984
2392
|
},
|
|
1985
2393
|
};
|
|
1986
2394
|
case "opamp": {
|
|
1987
|
-
const
|
|
1988
|
-
const
|
|
1989
|
-
const railHigh =
|
|
1990
|
-
const railLow =
|
|
2395
|
+
const declaredSwing = registryOpampSwing(registry, device.identity.partNumber);
|
|
2396
|
+
const declaredSwung = railsAfterSwing(opampRails(device, rails, nodeToVoltage), declaredSwing);
|
|
2397
|
+
const railHigh = declaredSwung.rails.high;
|
|
2398
|
+
const railLow = declaredSwung.rails.low;
|
|
1991
2399
|
// The gain-bandwidth product: the packet's own typed `GBP` first, else the datasheet figure
|
|
1992
2400
|
// of the exact part id, else none. Never a class number -- `null` lowers to a stamp the
|
|
1993
2401
|
// consoles run on their default, and `opamp-gain-bandwidth-default` names it.
|
|
@@ -2001,7 +2409,14 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
2001
2409
|
railHigh,
|
|
2002
2410
|
railLow,
|
|
2003
2411
|
openLoopGain: device.parameters.openLoopGain ?? DEFAULT_OPEN_LOOP_GAIN,
|
|
2004
|
-
...(
|
|
2412
|
+
...(declaredSwing === null
|
|
2413
|
+
? {}
|
|
2414
|
+
: {
|
|
2415
|
+
outputSwingDropHighVolts: declaredSwing.highVolts,
|
|
2416
|
+
outputSwingDropLowVolts: declaredSwing.lowVolts,
|
|
2417
|
+
outputSwingPrinted: declaredSwing.printed,
|
|
2418
|
+
}),
|
|
2419
|
+
...(declaredSwung.carried === null ? {} : { outputSwingCarried: declaredSwung.carried }),
|
|
2005
2420
|
...(gainBandwidthHz === null ? {} : { gainBandwidthHz }),
|
|
2006
2421
|
},
|
|
2007
2422
|
};
|
|
@@ -2145,29 +2560,41 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
2145
2560
|
law: { kind: "voltage-source", volts, sourceOhms: SUPPLY_SOURCE_OHMS },
|
|
2146
2561
|
};
|
|
2147
2562
|
}
|
|
2148
|
-
case "bjt":
|
|
2563
|
+
case "bjt": {
|
|
2564
|
+
// A registered part refines the class defaults, exactly as the diode and
|
|
2565
|
+
// JFET-gate cases do. Exact part number only -- see `registryLawFor`.
|
|
2566
|
+
// Declared parameters win over everything, every time: the entries exist
|
|
2567
|
+
// to fill absence, not to overrule a packet that states the value.
|
|
2568
|
+
// Unregistered parts keep the class default bit-identically.
|
|
2569
|
+
const catalogued = registryLawFor(registry, device.identity.partNumber, "bjt");
|
|
2570
|
+
const entry = catalogued !== null && catalogued.kind === "bjt" ? catalogued : null;
|
|
2149
2571
|
return {
|
|
2150
2572
|
outcome: "law",
|
|
2151
2573
|
device: device.id,
|
|
2152
2574
|
law: {
|
|
2153
2575
|
kind: "bjt",
|
|
2154
2576
|
polarity: device.parameters.pnp === 1 ? "pnp" : "npn",
|
|
2155
|
-
saturationCurrent: device.parameters.saturationCurrent ??
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2577
|
+
saturationCurrent: device.parameters.saturationCurrent ??
|
|
2578
|
+
entry?.saturationCurrent ??
|
|
2579
|
+
SILICON_SATURATION_CURRENT,
|
|
2580
|
+
forwardBeta: device.parameters.beta ?? entry?.forwardBeta ?? SILICON_FORWARD_BETA,
|
|
2581
|
+
reverseBeta: device.parameters.reverseBeta ?? entry?.reverseBeta ?? 1,
|
|
2582
|
+
thermalVoltage: device.parameters.thermalVoltage ??
|
|
2583
|
+
entry?.thermalVoltage ??
|
|
2584
|
+
THERMAL_VOLTAGE,
|
|
2159
2585
|
// Absent for every silicon part in the corpus, and for the one germanium
|
|
2160
2586
|
// packet whose `LeakageCurrent` is a sentence rather than a value.
|
|
2161
|
-
leakageAmps: device.parameters.leakageAmps ?? 0,
|
|
2587
|
+
leakageAmps: device.parameters.leakageAmps ?? entry?.leakageAmps ?? 0,
|
|
2162
2588
|
},
|
|
2163
2589
|
};
|
|
2590
|
+
}
|
|
2164
2591
|
case "jfet": {
|
|
2165
|
-
// A registered part refines the class
|
|
2592
|
+
// A registered part refines the class channel, exactly as `GENERIC_TUBE_GRID`'s
|
|
2166
2593
|
// note says a registry entry should. Exact part number only -- see
|
|
2167
|
-
// `registryLawFor`. The
|
|
2168
|
-
//
|
|
2169
|
-
//
|
|
2170
|
-
//
|
|
2594
|
+
// `registryLawFor`. The 2SK30A entry's channel repeats the class default by
|
|
2595
|
+
// construction (rank-dependent, and the database beta disagrees with at
|
|
2596
|
+
// least one packet's record), so a covered part's channel is
|
|
2597
|
+
// indistinguishable from an unregistered one's. Declared
|
|
2171
2598
|
// Vt0/Beta win over everything, every time: the entries exist to fill
|
|
2172
2599
|
// absence, not to overrule a packet that states the value (the same overlay
|
|
2173
2600
|
// the diode case applies). Unregistered parts keep the class default
|
|
@@ -2184,13 +2611,17 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
2184
2611
|
channel: device.parameters.pChannel === 1 ? "p" : "n",
|
|
2185
2612
|
// A JFET is depletion mode: it conducts at zero gate bias and pinches
|
|
2186
2613
|
// off as the gate goes negative, so the threshold is negative.
|
|
2187
|
-
thresholdVolts: device.parameters.thresholdVolts ??
|
|
2188
|
-
|
|
2189
|
-
|
|
2614
|
+
thresholdVolts: device.parameters.thresholdVolts ??
|
|
2615
|
+
entry?.thresholdVolts ??
|
|
2616
|
+
JFET_DEFAULT_THRESHOLD_VOLTS,
|
|
2617
|
+
transconductance: device.parameters.transconductance ??
|
|
2618
|
+
entry?.transconductance ??
|
|
2619
|
+
JFET_DEFAULT_TRANSCONDUCTANCE,
|
|
2620
|
+
channelLengthModulation: entry?.channelLengthModulation ?? 0,
|
|
2190
2621
|
// Subthreshold conduction (see the `fet` law docs in types.ts): the
|
|
2191
2622
|
// gradual region a knee-biased JFET actually sweeps in. MOSFETs
|
|
2192
2623
|
// keep 0 below.
|
|
2193
|
-
subthresholdVolts: 0.07,
|
|
2624
|
+
subthresholdVolts: entry?.subthresholdVolts ?? 0.07,
|
|
2194
2625
|
// A JFET gate is a silicon PN junction, so it conducts once forward-biased.
|
|
2195
2626
|
// Saturation current and scale are **reused from the triode grid**, which
|
|
2196
2627
|
// `compact-mna-dynamic-triode-cell` reports 0 Newton failures with; the onset
|
|
@@ -2199,30 +2630,39 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
2199
2630
|
// part refines the current through its catalog entry, and the
|
|
2200
2631
|
// unregistered default stays exactly what it was.
|
|
2201
2632
|
gateSaturationCurrent: entry?.gateSaturationCurrent ?? 1.0e-5,
|
|
2202
|
-
gateOnsetVolts: 0.5,
|
|
2203
|
-
gateScaleVolts: 0.06,
|
|
2633
|
+
gateOnsetVolts: entry?.gateOnsetVolts ?? 0.5,
|
|
2634
|
+
gateScaleVolts: entry?.gateScaleVolts ?? 0.06,
|
|
2204
2635
|
},
|
|
2205
2636
|
};
|
|
2206
2637
|
}
|
|
2207
|
-
case "mosfet":
|
|
2638
|
+
case "mosfet": {
|
|
2639
|
+
// The same registry refinement as the JFET channel above: a registered
|
|
2640
|
+
// part refines the class defaults, declared parameters win, and
|
|
2641
|
+
// unregistered parts keep the class default bit-identically. Exact part
|
|
2642
|
+
// number only. This shares the `fet` law kind with JFET entries; part
|
|
2643
|
+
// ids are disjoint across the two classes, so a lookup by exact folded
|
|
2644
|
+
// part id cannot cross from one to the other.
|
|
2645
|
+
const catalogued = registryLawFor(registry, device.identity.partNumber, "fet");
|
|
2646
|
+
const entry = catalogued !== null && catalogued.kind === "fet" ? catalogued : null;
|
|
2208
2647
|
return {
|
|
2209
2648
|
outcome: "law",
|
|
2210
2649
|
device: device.id,
|
|
2211
2650
|
law: {
|
|
2212
2651
|
kind: "fet",
|
|
2213
2652
|
channel: device.parameters.pChannel === 1 ? "p" : "n",
|
|
2214
|
-
thresholdVolts: device.parameters.thresholdVolts ?? 2,
|
|
2215
|
-
transconductance: device.parameters.transconductance ?? 1e-3,
|
|
2216
|
-
channelLengthModulation: 0,
|
|
2653
|
+
thresholdVolts: device.parameters.thresholdVolts ?? entry?.thresholdVolts ?? 2,
|
|
2654
|
+
transconductance: device.parameters.transconductance ?? entry?.transconductance ?? 1e-3,
|
|
2655
|
+
channelLengthModulation: entry?.channelLengthModulation ?? 0,
|
|
2217
2656
|
// Insulated gate: no subthreshold region is modelled, behavior unchanged.
|
|
2218
|
-
subthresholdVolts: 0,
|
|
2657
|
+
subthresholdVolts: entry?.subthresholdVolts ?? 0,
|
|
2219
2658
|
// An insulated gate draws no current at any bias. Zero, deliberately: this
|
|
2220
2659
|
// is what the law did for every FET before the JFET junction was added.
|
|
2221
|
-
gateSaturationCurrent: 0,
|
|
2222
|
-
gateOnsetVolts: 0,
|
|
2223
|
-
gateScaleVolts: 1,
|
|
2660
|
+
gateSaturationCurrent: entry?.gateSaturationCurrent ?? 0,
|
|
2661
|
+
gateOnsetVolts: entry?.gateOnsetVolts ?? 0,
|
|
2662
|
+
gateScaleVolts: entry?.gateScaleVolts ?? 1,
|
|
2224
2663
|
},
|
|
2225
2664
|
};
|
|
2665
|
+
}
|
|
2226
2666
|
case "ota":
|
|
2227
2667
|
return otaDeviceClassLaw(device);
|
|
2228
2668
|
case "inverter":
|
|
@@ -3480,15 +3920,19 @@ export function findIcsNotExecuted(lawed, original, registry) {
|
|
|
3480
3920
|
? `${head}: it is registered as a source-boundary shell, drawn for topological continuity rather than execution, so it is compiled as open.`
|
|
3481
3921
|
: reason === "charge-pump-declared-rail"
|
|
3482
3922
|
? `${head}: it is a charge pump whose generated rail the document declares separately, so the rail is modelled and the pump itself is compiled as open.`
|
|
3483
|
-
: reason === "
|
|
3484
|
-
? `${head}:
|
|
3485
|
-
: reason === "
|
|
3486
|
-
? `${head}:
|
|
3487
|
-
: reason === "
|
|
3488
|
-
? `${head}:
|
|
3489
|
-
|
|
3490
|
-
:
|
|
3491
|
-
|
|
3923
|
+
: reason === "charge-pump-no-input"
|
|
3924
|
+
? `${head}: it is a charge pump whose generated rail the document does not declare and whose supply input carries no declared voltage, so no rail can be derived from it and it is compiled as open. Whatever the pump does in the real circuit is absent.`
|
|
3925
|
+
: reason === "charge-pump-output-unloaded"
|
|
3926
|
+
? `${head}: it is a charge pump whose supply input is declared but whose output terminal drives nothing else in the circuit, so stamping its derived rail would change nothing and it is compiled as open.`
|
|
3927
|
+
: reason === "registry-arity-mismatch"
|
|
3928
|
+
? `${head}: ${resolution.insteadOfRefusal ?? "its registry entry could not bind the declared terminals"}. The part is registered, so this is an arity disagreement rather than an unknown chip: either the document declares a shell rather than the part's own pins, or the catalog needs an entry at this terminal count.${wiring} It is compiled as open rather than refusing the pedal.`
|
|
3929
|
+
: reason === "registry-open"
|
|
3930
|
+
? `${head}: the registry states no executable model for it and it is compiled as open. Whatever it does in the real circuit is absent.`
|
|
3931
|
+
: reason === "declared-class-without-model"
|
|
3932
|
+
? `${head}: no registered part model binds it -- ${resolution.insteadOfRefusal ?? "the registry supplied none"} -- and ${device.identity.partNumber === null
|
|
3933
|
+
? `its source names no part number and declares only the generic ${device.identity.declaredType}, so there is nothing to look up`
|
|
3934
|
+
: `its source declares ${device.identity.declaredType}, a class this pipeline opens rather than refusing the pedal for`}. Fixing it means a registry entry, a pinout that fits, or a packet correction.`
|
|
3935
|
+
: `${head}: it was compiled as open by a path that records no reason, so this cannot say which. A new opening branch in \`resolveDevice\` needs to tag itself.`) + gapSuffix,
|
|
3492
3936
|
...(gapClass === undefined ? {} : { gapClass }),
|
|
3493
3937
|
});
|
|
3494
3938
|
}
|