@vessel-dsp/compiler 0.5.0 → 0.7.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 +42 -0
- package/dist/bbd-clock.d.ts.map +1 -1
- package/dist/bbd-clock.js +383 -1
- package/dist/bbd-clock.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +316 -34
- package/dist/compile.js.map +1 -1
- package/dist/device-laws.d.ts.map +1 -1
- package/dist/device-laws.js +581 -98
- 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/lower.d.ts.map +1 -1
- package/dist/lower.js +13 -0
- package/dist/lower.js.map +1 -1
- package/dist/netlist.d.ts +30 -0
- package/dist/netlist.d.ts.map +1 -1
- package/dist/netlist.js +502 -8
- package/dist/netlist.js.map +1 -1
- package/dist/part-catalog.d.ts +42 -18
- package/dist/part-catalog.d.ts.map +1 -1
- package/dist/part-catalog.js +2518 -576
- package/dist/part-catalog.js.map +1 -1
- package/dist/partition.d.ts.map +1 -1
- package/dist/partition.js +6 -0
- package/dist/partition.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 +786 -0
- package/dist/registry-data/diode.js.map +1 -0
- package/dist/registry-data/index.d.ts +21 -0
- package/dist/registry-data/index.d.ts.map +1 -0
- package/dist/registry-data/index.js +103 -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 +60 -0
- package/dist/registry-data/schema.d.ts.map +1 -0
- package/dist/registry-data/schema.js +151 -0
- package/dist/registry-data/schema.js.map +1 -0
- package/dist/registry-data/ujt.d.ts +3 -0
- package/dist/registry-data/ujt.d.ts.map +1 -0
- package/dist/registry-data/ujt.js +48 -0
- package/dist/registry-data/ujt.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 +93 -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 +242 -19
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/device-laws.js
CHANGED
|
@@ -19,7 +19,8 @@ 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
|
+
import { GROUND } from "./types.js";
|
|
23
24
|
/** Room-temperature thermal voltage; the diode law needs a reference somewhere. */
|
|
24
25
|
export const THERMAL_VOLTAGE = 0.025_852;
|
|
25
26
|
/**
|
|
@@ -333,17 +334,33 @@ function opampRails(device, rails, nodeToVoltage) {
|
|
|
333
334
|
return { high, low };
|
|
334
335
|
}
|
|
335
336
|
/**
|
|
336
|
-
* The rails an op-amp's output can reach: each supply rail pulled in by the part's
|
|
337
|
-
*
|
|
338
|
-
*
|
|
337
|
+
* The rails an op-amp's output can reach: each supply rail pulled in by the part's printed drop for
|
|
338
|
+
* that side, carried to this op-amp's own supply by the row's stated `translation`.
|
|
339
|
+
*
|
|
340
|
+
* `"constant"` applies the printed drops as printed. `"proportional"` multiplies both by the
|
|
341
|
+
* op-amp's supply span (`high - low`, so 9 for a 9 V single supply and 18 for a +-9 V pair) over the
|
|
342
|
+
* span the row was printed at. An unstated swing, an unknown rail, a span that is not positive, or a
|
|
343
|
+
* pair that would close the output interval (`high - dropHigh <= low + dropLow`) leaves the rails as
|
|
344
|
+
* they are, so a part with no entry renders bit-for-bit as before. `carried` is set only when the rails
|
|
345
|
+
* moved; `opamp-law-default-parameters` reads it to tell a pair used at its printed supply from one
|
|
346
|
+
* carried across a supply.
|
|
339
347
|
*/
|
|
340
|
-
function
|
|
341
|
-
if (
|
|
342
|
-
return rails;
|
|
348
|
+
function railsAfterSwing(rails, swing) {
|
|
349
|
+
if (swing === null || rails.high === null || rails.low === null) {
|
|
350
|
+
return { rails, carried: null };
|
|
343
351
|
}
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
352
|
+
const span = rails.high - rails.low;
|
|
353
|
+
if (!(span > 0)) {
|
|
354
|
+
return { rails, carried: null };
|
|
355
|
+
}
|
|
356
|
+
const scale = swing.printed.translation === "proportional" ? span / swing.printed.supplyVolts : 1;
|
|
357
|
+
const highVolts = swing.highVolts * scale;
|
|
358
|
+
const lowVolts = swing.lowVolts * scale;
|
|
359
|
+
const high = rails.high - highVolts;
|
|
360
|
+
const low = rails.low + lowVolts;
|
|
361
|
+
return high > low
|
|
362
|
+
? { rails: { high, low }, carried: { highVolts, lowVolts, circuitSupplyVolts: span } }
|
|
363
|
+
: { rails, carried: null };
|
|
347
364
|
}
|
|
348
365
|
/**
|
|
349
366
|
* The one high-side and one low-side supply pin a device declares, read by the same role tokens
|
|
@@ -436,39 +453,32 @@ function quiescentSupplyDraw(device, registry, expanded, rails, nodeToVoltage) {
|
|
|
436
453
|
{ outcome: "law", device: id, law: { kind: "conductance", siemens: amps / span } },
|
|
437
454
|
];
|
|
438
455
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
456
|
+
export function attachDeviceLaws(netlist, registry) {
|
|
457
|
+
const regulatedBase = registrySuppliedVoltages(netlist, registry);
|
|
458
|
+
const nodeToVoltage = buildNodeToVoltageMap(netlist, regulatedBase);
|
|
459
|
+
const devicesTouchingNode = deviceCountByNode(netlist);
|
|
460
|
+
// A pump's rail is derived from the declared input, so every derivation runs against
|
|
461
|
+
// the document's own voltages -- before any pump output joins the map -- and the
|
|
462
|
+
// derived supplies merge into the rail set after. A pump can never back itself.
|
|
463
|
+
const pumpDerivations = new Map();
|
|
446
464
|
for (const device of netlist.devices) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
device.kind !== "resistor" &&
|
|
451
|
-
device.kind !== "inductor")) {
|
|
452
|
-
continue;
|
|
465
|
+
const derivation = pumpDerivation(device, registry, nodeToVoltage, devicesTouchingNode);
|
|
466
|
+
if (derivation !== null) {
|
|
467
|
+
pumpDerivations.set(device.id, derivation);
|
|
453
468
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (
|
|
458
|
-
|
|
469
|
+
}
|
|
470
|
+
const regulated = [...regulatedBase];
|
|
471
|
+
for (const derivation of pumpDerivations.values()) {
|
|
472
|
+
if (derivation.kind !== "model") {
|
|
473
|
+
continue;
|
|
459
474
|
}
|
|
460
|
-
|
|
461
|
-
|
|
475
|
+
regulated.push({ node: derivation.outputNode, volts: derivation.volts });
|
|
476
|
+
if (!nodeToVoltage.has(derivation.outputNode)) {
|
|
477
|
+
nodeToVoltage.set(derivation.outputNode, derivation.volts);
|
|
462
478
|
}
|
|
463
479
|
}
|
|
464
|
-
return map;
|
|
465
|
-
}
|
|
466
|
-
export function attachDeviceLaws(netlist, registry) {
|
|
467
|
-
const regulated = registrySuppliedVoltages(netlist, registry);
|
|
468
480
|
const rails = supplyRails(netlist, regulated);
|
|
469
481
|
const supply = supplyNodes(netlist);
|
|
470
|
-
const nodeToVoltage = buildNodeToVoltageMap(netlist, regulated);
|
|
471
|
-
const oneHopVoltage = buildOneHopVoltageMap(netlist, nodeToVoltage);
|
|
472
482
|
const mainsFrequencyHz = mainsFrequency(netlist);
|
|
473
483
|
// Stage 1 (`netlist.ts`) already resolved each control's real taper -- from the panel
|
|
474
484
|
// control's own declaration, or failing that, from the first device bound to it -- onto
|
|
@@ -481,7 +491,6 @@ export function attachDeviceLaws(netlist, registry) {
|
|
|
481
491
|
// is multi-section. Expanding into ordinary devices means partitioning, lowering and the runtime
|
|
482
492
|
// see nothing new -- the alternative, a fourth `DeviceResolution` outcome, would have to be
|
|
483
493
|
// understood by every consumer of one.
|
|
484
|
-
const devicesTouchingNode = deviceCountByNode(netlist);
|
|
485
494
|
// Nodes inside a registered package, numbered past every node the document uses.
|
|
486
495
|
const internalNodes = new Map();
|
|
487
496
|
const firstInternal = Math.max(0, ...netlist.nodes) + 1;
|
|
@@ -497,7 +506,7 @@ export function attachDeviceLaws(netlist, registry) {
|
|
|
497
506
|
const devices = [];
|
|
498
507
|
const resolutions = [];
|
|
499
508
|
for (const device of netlist.devices) {
|
|
500
|
-
const expanded = sectionDevices(device, registry, rails, supply, nodeToVoltage, internalNode, netlist);
|
|
509
|
+
const expanded = sectionDevices(device, registry, rails, supply, nodeToVoltage, internalNode, netlist, pumpDerivations.get(device.id) ?? null);
|
|
501
510
|
const draw = quiescentSupplyDraw(device, registry, expanded !== null, rails, nodeToVoltage);
|
|
502
511
|
if (draw !== null) {
|
|
503
512
|
devices.push(draw[0]);
|
|
@@ -505,7 +514,7 @@ export function attachDeviceLaws(netlist, registry) {
|
|
|
505
514
|
}
|
|
506
515
|
if (expanded === null) {
|
|
507
516
|
devices.push(device);
|
|
508
|
-
resolutions.push(resolveDevice(device, registry, rails, supply, taperByControl, mainsFrequencyHz, devicesTouchingNode, nodeToVoltage,
|
|
517
|
+
resolutions.push(resolveDevice(device, registry, rails, supply, taperByControl, mainsFrequencyHz, devicesTouchingNode, nodeToVoltage, netlist));
|
|
509
518
|
continue;
|
|
510
519
|
}
|
|
511
520
|
for (const [section, resolution] of expanded) {
|
|
@@ -550,6 +559,12 @@ export function declaredRegulatorTerminals(device, model) {
|
|
|
550
559
|
// kinds whose vocabulary carries them (`regulator`, `power-converter`, `voltage-source`,
|
|
551
560
|
// `rail`, `battery`), and an `ic` may declare nothing but `pin`. A device that reaches here
|
|
552
561
|
// carrying a supply electrode was therefore declared as a kind entitled to state one.
|
|
562
|
+
// Charge-pump parts never take this path: their volts are derived per circuit from the
|
|
563
|
+
// input node, and the placeholder volts in their entries must not be stamped. They are
|
|
564
|
+
// bound by `pumpDerivation` instead, which runs before this is consulted.
|
|
565
|
+
if (isChargePumpPart(device)) {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
553
568
|
if (model.sections.length !== 1) {
|
|
554
569
|
return null;
|
|
555
570
|
}
|
|
@@ -604,7 +619,16 @@ export function declaredRegulatorTerminals(device, model) {
|
|
|
604
619
|
}
|
|
605
620
|
function sectionDevices(device, registry, rails, supply, nodeToVoltage, internalNode = () => {
|
|
606
621
|
throw new Error("no internal-node allocator");
|
|
607
|
-
}, netlist) {
|
|
622
|
+
}, netlist, pump = null) {
|
|
623
|
+
// A decided pump never reaches generic expansion: the placeholder volts in the pump
|
|
624
|
+
// entries must not be stamped. An isolated pump defers to `resolveDevice`, which
|
|
625
|
+
// names the isolation before any pump question.
|
|
626
|
+
if (pump !== null) {
|
|
627
|
+
if (pump.kind === "isolated") {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
return pumpSectionDevices(device, pump);
|
|
631
|
+
}
|
|
608
632
|
// **`mayCarryRegistrySections`, not `requiresIdentification`.** An OTA reaches here so a
|
|
609
633
|
// registered multi-section part (the BA662A's OTA plus its Darlington buffer) can be expanded;
|
|
610
634
|
// when the registry has no matching entry this returns null and the caller falls through to
|
|
@@ -631,6 +655,25 @@ function sectionDevices(device, registry, rails, supply, nodeToVoltage, internal
|
|
|
631
655
|
if (model.kind !== "sections" && model.kind !== "sections-core") {
|
|
632
656
|
return null;
|
|
633
657
|
}
|
|
658
|
+
// An explicit override cannot target one section of a package the registry expands: the
|
|
659
|
+
// section laws are the entry's numbers (a dual op-amp's two amplifiers share one part's
|
|
660
|
+
// figures), and `device.parameters` -- legacy or override -- never reach them. Accepting
|
|
661
|
+
// the block would be a silent drop, so it refuses by name instead. The way out is a
|
|
662
|
+
// source-declared `devices:` split, whose devices take the device-class law that the
|
|
663
|
+
// override does feed.
|
|
664
|
+
if (device.deviceOverride !== undefined) {
|
|
665
|
+
return [
|
|
666
|
+
[
|
|
667
|
+
device,
|
|
668
|
+
{
|
|
669
|
+
outcome: "unsupported",
|
|
670
|
+
device: device.id,
|
|
671
|
+
reason: `part ${identity.partId} is expanded from a registry sections entry, whose numbers the override cannot replace: ` +
|
|
672
|
+
`declare the package's devices explicitly (a "devices:" split) to take the device-class law an override feeds, or remove the DeviceOverride block`,
|
|
673
|
+
},
|
|
674
|
+
],
|
|
675
|
+
];
|
|
676
|
+
}
|
|
634
677
|
// A `sections` entry's `terminals` index into the entry's canonical pin order, not the
|
|
635
678
|
// declaration order the document happened to use. When the entry names its pins by role and
|
|
636
679
|
// that naming is a clean bijection onto the device's declared terminals, resolve each
|
|
@@ -740,10 +783,12 @@ function sectionDevices(device, registry, rails, supply, nodeToVoltage, internal
|
|
|
740
783
|
}
|
|
741
784
|
// Read from the section's *parent* device, which is where the package's supply pins
|
|
742
785
|
// are declared -- a section's own terminal list is only its three signal pins.
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const
|
|
786
|
+
const sectionSwing = section.law.kind === "ideal-opamp"
|
|
787
|
+
? opampOutputSwingOf(section.law, identity.partId)
|
|
788
|
+
: null;
|
|
789
|
+
const sectionSwung = railsAfterSwing(opampRails(device, rails, nodeToVoltage), sectionSwing);
|
|
790
|
+
const railHigh = sectionSwung.rails.high;
|
|
791
|
+
const railLow = sectionSwung.rails.low;
|
|
747
792
|
// An op-amp's rails come from the op-amp's own supply terminals or the circuit's supply set,
|
|
748
793
|
// not from the registry -- the same substitution a declared `opamp` gets. A registry cannot know them:
|
|
749
794
|
// it describes a part, and the rails are a property of the pedal the part is fitted to.
|
|
@@ -760,8 +805,16 @@ function sectionDevices(device, registry, rails, supply, nodeToVoltage, internal
|
|
|
760
805
|
// The registry states the part's gain; only the rails are the pedal's.
|
|
761
806
|
openLoopGain: section.law.openLoopGain,
|
|
762
807
|
// 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
|
-
|
|
808
|
+
// states how close it gets to the rails from one that does not, and a pair
|
|
809
|
+
// used at its printed supply from one the lowering carried across a supply.
|
|
810
|
+
...(sectionSwing === null
|
|
811
|
+
? {}
|
|
812
|
+
: {
|
|
813
|
+
outputSwingDropHighVolts: sectionSwing.highVolts,
|
|
814
|
+
outputSwingDropLowVolts: sectionSwing.lowVolts,
|
|
815
|
+
outputSwingPrinted: sectionSwing.printed,
|
|
816
|
+
}),
|
|
817
|
+
...(sectionSwung.carried === null ? {} : { outputSwingCarried: sectionSwung.carried }),
|
|
765
818
|
// The part's datasheet product, when the entry states one. Absent stays
|
|
766
819
|
// absent: it lowers to a `null` stamp, which the compile names.
|
|
767
820
|
...(section.law.gainBandwidthHz === undefined
|
|
@@ -847,6 +900,34 @@ function barePinPosition(macro, roles, port) {
|
|
|
847
900
|
}
|
|
848
901
|
return position;
|
|
849
902
|
}
|
|
903
|
+
/**
|
|
904
|
+
* The terminal the audio leaves a chained two-section part at, or `null` when the part is not
|
|
905
|
+
* chained in this document.
|
|
906
|
+
*
|
|
907
|
+
* Chained means one thing: the first section's output and the second section's input are the same
|
|
908
|
+
* non-ground node, which is connectivity, not a name. All three terminals must resolve to exactly
|
|
909
|
+
* one role each, so a document that names the sections differently, or ties the second section's
|
|
910
|
+
* input to ground (the Deluxe Memory Man uses only section A), falls through to the single-section
|
|
911
|
+
* port unchanged.
|
|
912
|
+
*/
|
|
913
|
+
function cascadedAudioOut(device, macro, sectionAudioOut) {
|
|
914
|
+
const cascade = macro.ports.cascade;
|
|
915
|
+
if (cascade === undefined || sectionAudioOut === null) {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
const roles = device.identity.terminalRoles;
|
|
919
|
+
const sectionOut = terminalWithRole(roles, cascade.sectionOut);
|
|
920
|
+
const nextIn = terminalWithRole(roles, cascade.nextIn);
|
|
921
|
+
const finalOut = terminalWithRole(roles, cascade.finalOut);
|
|
922
|
+
if (sectionOut === null || nextIn === null || finalOut === null || sectionOut !== sectionAudioOut) {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
const joint = device.nodes[sectionOut];
|
|
926
|
+
if (joint === undefined || joint === GROUND || device.nodes[nextIn] !== joint) {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
return finalOut;
|
|
930
|
+
}
|
|
850
931
|
/**
|
|
851
932
|
* Thin wrapper that brackets the real resolution so every registry entry consulted while computing
|
|
852
933
|
* this macro's parameters is recorded against the macro's device id. **No behaviour change** -- the
|
|
@@ -864,8 +945,9 @@ registry, netlist) {
|
|
|
864
945
|
const roles = device.identity.terminalRoles;
|
|
865
946
|
const audioIn = terminalWithRole(roles, macro.ports.audioIn) ??
|
|
866
947
|
barePinPosition(macro, roles, "audioIn");
|
|
867
|
-
const
|
|
948
|
+
const sectionAudioOut = terminalWithRole(roles, macro.ports.audioOut) ??
|
|
868
949
|
barePinPosition(macro, roles, "audioOut");
|
|
950
|
+
const audioOut = cascadedAudioOut(device, macro, sectionAudioOut) ?? sectionAudioOut;
|
|
869
951
|
// A second output, only for a part that declares one and a device that names it. Absent is
|
|
870
952
|
// the ordinary case and stays a one-output macro: unlike the ports above, failing to find
|
|
871
953
|
// this is not a refusal, because most parts genuinely have one output.
|
|
@@ -1309,6 +1391,12 @@ function registrySuppliedVoltages(netlist, registry) {
|
|
|
1309
1391
|
if (device.parameters.volts !== undefined) {
|
|
1310
1392
|
continue;
|
|
1311
1393
|
}
|
|
1394
|
+
// Pump parts never supply a static rail: their output is derived per circuit from
|
|
1395
|
+
// the input node by `pumpDerivation`, and the placeholder volts in their entries
|
|
1396
|
+
// must not leak into the rail set through a role that happens to read as an output.
|
|
1397
|
+
if (isChargePumpPart(device)) {
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1312
1400
|
const identity = identify(device, registry);
|
|
1313
1401
|
if (identity === null) {
|
|
1314
1402
|
continue;
|
|
@@ -1421,29 +1509,375 @@ export function deviceCountByNode(netlist) {
|
|
|
1421
1509
|
}
|
|
1422
1510
|
return counts;
|
|
1423
1511
|
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Charge-pump inverter parts, by folded part number: every spelling the DIY BOM census
|
|
1514
|
+
* attributes to the family (TC1044S/SCPA, LT1054/CP, 7660 // SCPAZ, TC7662BC, ICL7660SCPA(Z),
|
|
1515
|
+
* LTC1144, MAX1044/S) plus the order codes and package markings the datasheets print.
|
|
1516
|
+
* Compared whole with `foldPartId`, never by substring: membership routes a part to the
|
|
1517
|
+
* pump handling, it does not model it -- modelling needs a registry entry, and a member
|
|
1518
|
+
* with no entry falls through to the honest unknown-chip path.
|
|
1519
|
+
*/
|
|
1424
1520
|
const CHARGE_PUMP_PARTS = new Set([
|
|
1425
|
-
"
|
|
1521
|
+
"tc1044s",
|
|
1426
1522
|
"tc1044scpa",
|
|
1427
|
-
"
|
|
1523
|
+
"tc1044scoa",
|
|
1524
|
+
"tc1044sepa",
|
|
1525
|
+
"tc1044seoa",
|
|
1526
|
+
"tc1044sija",
|
|
1527
|
+
"tc1044smja",
|
|
1528
|
+
"icl7660",
|
|
1529
|
+
"icl7660cpa",
|
|
1530
|
+
"icl7660cpaz",
|
|
1531
|
+
"icl7660cba",
|
|
1532
|
+
"icl7660cbaz",
|
|
1533
|
+
"7660cpa",
|
|
1534
|
+
"7660cpaz",
|
|
1535
|
+
"7660cba",
|
|
1536
|
+
"7660cbaz",
|
|
1537
|
+
"icl7660a",
|
|
1538
|
+
"icl7660acpa",
|
|
1539
|
+
"icl7660acpaz",
|
|
1540
|
+
"icl7660acba",
|
|
1541
|
+
"icl7660acbaz",
|
|
1542
|
+
"icl7660aiba",
|
|
1543
|
+
"icl7660aibaz",
|
|
1544
|
+
"7660acpa",
|
|
1545
|
+
"7660acpaz",
|
|
1546
|
+
"7660acba",
|
|
1547
|
+
"7660acbaz",
|
|
1548
|
+
"7660aiba",
|
|
1549
|
+
"7660aibaz",
|
|
1550
|
+
"icl7660s",
|
|
1551
|
+
"icl7660scpa",
|
|
1552
|
+
"icl7660scpaz",
|
|
1553
|
+
"7660scpa",
|
|
1554
|
+
"7660scpaz",
|
|
1555
|
+
"tc7662b",
|
|
1556
|
+
"tc7662bcpa",
|
|
1557
|
+
"tc7662bepa",
|
|
1558
|
+
"tc7662bcoa",
|
|
1559
|
+
"tc7662beoa",
|
|
1428
1560
|
"7662",
|
|
1429
|
-
"lt1054cp",
|
|
1430
1561
|
"lt1054",
|
|
1562
|
+
"lt1054c",
|
|
1563
|
+
"lt1054cp",
|
|
1564
|
+
"lt1054cpe4",
|
|
1565
|
+
"lt1054i",
|
|
1566
|
+
"ltc1144",
|
|
1567
|
+
"ltc1144cn8",
|
|
1568
|
+
"ltc1144in8",
|
|
1569
|
+
"ltc1144cs8",
|
|
1570
|
+
"ltc1144is8",
|
|
1571
|
+
"max1044",
|
|
1572
|
+
"max1044cpa",
|
|
1573
|
+
"max1044csa",
|
|
1574
|
+
"max1044epa",
|
|
1575
|
+
"max1044esa",
|
|
1576
|
+
// Census spellings with no registry entry: routed here so a pump whose rail the
|
|
1577
|
+
// document declares still opens honestly, and one whose rail is undeclared refuses
|
|
1578
|
+
// as an unknown chip instead of claiming a modelled rail.
|
|
1579
|
+
"tc7662bc",
|
|
1580
|
+
"max1044s",
|
|
1581
|
+
"7660",
|
|
1431
1582
|
]);
|
|
1583
|
+
/** Whether this device names a charge-pump inverter part, compared whole. */
|
|
1584
|
+
function isChargePumpPart(device) {
|
|
1585
|
+
const partNumber = device.identity.partNumber;
|
|
1586
|
+
if (partNumber === null) {
|
|
1587
|
+
return false;
|
|
1588
|
+
}
|
|
1589
|
+
return CHARGE_PUMP_PARTS.has(foldPartId(partNumber));
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* The closed role vocabularies a pump's three supply terminals are bound by, compared as
|
|
1593
|
+
* whole folded tokens by `terminalWithRole` -- never as substrings, never against a
|
|
1594
|
+
* description. These query the ENTRY's role groups (which carry the datasheet pinout in
|
|
1595
|
+
* package order), so a document naming its terminals in any order binds, and a document
|
|
1596
|
+
* numbering its pins binds positionally instead.
|
|
1597
|
+
*/
|
|
1598
|
+
const PUMP_INPUT_ROLES = ["vplus", "vin", "vdd", "vcc", "input"];
|
|
1599
|
+
const PUMP_OUTPUT_ROLES = [
|
|
1600
|
+
"negativeoutput",
|
|
1601
|
+
"voutnegative",
|
|
1602
|
+
"vout",
|
|
1603
|
+
"voutput",
|
|
1604
|
+
"out",
|
|
1605
|
+
"output",
|
|
1606
|
+
];
|
|
1607
|
+
const PUMP_GROUND_ROLES = ["gnd", "ground"];
|
|
1432
1608
|
/**
|
|
1433
|
-
* A charge-pump IC whose
|
|
1609
|
+
* A charge-pump IC whose OUTPUT node is already backed by a declared rail is a
|
|
1434
1610
|
* view-only structural anchor: the rail supplies the voltage, the IC adds nothing.
|
|
1435
1611
|
* The Klon's MAX1044 is handled by `isElectricallyIsolated` (all nodes private);
|
|
1436
1612
|
* this covers the wider class where the pump shares nets with the declared rail.
|
|
1613
|
+
*
|
|
1614
|
+
* Tests the pump's OUTPUT node, by role, and nothing else. The previous version asked
|
|
1615
|
+
* whether ANY non-ground node of the pump touched a declared OR one-hop voltage, and a
|
|
1616
|
+
* reservoir capacitor to ground was enough to satisfy it: with both Plumes rails deleted,
|
|
1617
|
+
* nodes 35 and 36 each one-hop to 0 V through C102/C101, and the pump claimed a modelled
|
|
1618
|
+
* rail on ground continuity alone. Ground is not a rail, and an input rail is not the
|
|
1619
|
+
* output rail -- pigtronix's LT1054 sat on the same false positive through its input
|
|
1620
|
+
* capacitor, and moogerfooger's 7662 through its output capacitor.
|
|
1437
1621
|
*/
|
|
1438
|
-
function isChargePumpWithDeclaredRail(device, nodeToVoltage
|
|
1439
|
-
|
|
1440
|
-
if (partNumber === null) {
|
|
1622
|
+
function isChargePumpWithDeclaredRail(device, nodeToVoltage) {
|
|
1623
|
+
if (!isChargePumpPart(device)) {
|
|
1441
1624
|
return false;
|
|
1442
1625
|
}
|
|
1443
|
-
|
|
1626
|
+
const output = chargePumpOutputIndex(device);
|
|
1627
|
+
if (output === null) {
|
|
1444
1628
|
return false;
|
|
1445
1629
|
}
|
|
1446
|
-
|
|
1630
|
+
const node = device.nodes[output];
|
|
1631
|
+
if (node === undefined || node === 0) {
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1634
|
+
return nodeToVoltage.has(node);
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* The index of the pump's inverted-output terminal, or `null` when the document does
|
|
1638
|
+
* not settle it.
|
|
1639
|
+
*
|
|
1640
|
+
* A declared supply electrode decides first: a shell that states `ground` plus
|
|
1641
|
+
* `negative` (or `positive`, the regulator convention deluxe-memory-man uses for its
|
|
1642
|
+
* -15 V output) names its output outright. Otherwise the folded terminal names are read
|
|
1643
|
+
* against the output vocabulary -- whole tokens, unambiguous or nothing.
|
|
1644
|
+
*/
|
|
1645
|
+
function chargePumpOutputIndex(device) {
|
|
1646
|
+
const declared = device.identity.declaredTerminalRoles;
|
|
1647
|
+
const onlyDeclared = (role) => {
|
|
1648
|
+
const found = declared.flatMap((value, index) => value === role ? [index] : []);
|
|
1649
|
+
return found.length === 1 ? found[0] : null;
|
|
1650
|
+
};
|
|
1651
|
+
const fromDeclaration = onlyDeclared("negative") ?? onlyDeclared("positive");
|
|
1652
|
+
if (fromDeclaration !== null) {
|
|
1653
|
+
return fromDeclaration;
|
|
1654
|
+
}
|
|
1655
|
+
return terminalWithRole(device.identity.terminalRoles, PUMP_OUTPUT_ROLES);
|
|
1656
|
+
}
|
|
1657
|
+
function pumpDerivation(device, registry, nodeToVoltage, devicesTouchingNode) {
|
|
1658
|
+
if (!isChargePumpPart(device)) {
|
|
1659
|
+
return null;
|
|
1660
|
+
}
|
|
1661
|
+
// Isolation stays the isolation reporter's beat: a pump on private nodes is view-only
|
|
1662
|
+
// however it is part-numbered, and `resolveDevice` names it before any pump question.
|
|
1663
|
+
if (isElectricallyIsolated(device, devicesTouchingNode)) {
|
|
1664
|
+
return { kind: "isolated" };
|
|
1665
|
+
}
|
|
1666
|
+
const identity = identify(device, registry);
|
|
1667
|
+
if (identity === null) {
|
|
1668
|
+
return null;
|
|
1669
|
+
}
|
|
1670
|
+
const entry = registryEntryFor(registry, identity, device.nodes.length);
|
|
1671
|
+
if (entry === null || entry.model.kind !== "sections") {
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1674
|
+
const section = entry.model.sections[0];
|
|
1675
|
+
if (entry.model.sections.length !== 1 ||
|
|
1676
|
+
section === undefined ||
|
|
1677
|
+
section.law.kind !== "voltage-source" ||
|
|
1678
|
+
section.terminals.length !== 2) {
|
|
1679
|
+
return {
|
|
1680
|
+
kind: "unbound",
|
|
1681
|
+
detail: `part ${identity.partId} is routed to the charge-pump derivation but its registry ` +
|
|
1682
|
+
`entry is not a single two-terminal voltage-source section, so no rail can be ` +
|
|
1683
|
+
`derived from it`,
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
const sourceOhms = section.law.sourceOhms;
|
|
1687
|
+
// Which package positions the supply input, inverted output, and reference sit at,
|
|
1688
|
+
// read off the ENTRY's role groups -- the datasheet pinout in package order -- so one
|
|
1689
|
+
// entry serves every declaration order that names its terminals at all.
|
|
1690
|
+
const groups = entry.terminalRoleGroups;
|
|
1691
|
+
const groupIndex = (vocab) => {
|
|
1692
|
+
const wanted = new Set(vocab.map((alias) => foldToken(alias)));
|
|
1693
|
+
const hits = [];
|
|
1694
|
+
groups.forEach((group, index) => {
|
|
1695
|
+
if (group.some((alias) => wanted.has(foldToken(alias)))) {
|
|
1696
|
+
hits.push(index);
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
return hits.length === 1 ? hits[0] : null;
|
|
1700
|
+
};
|
|
1701
|
+
const inputPos = groupIndex(PUMP_INPUT_ROLES);
|
|
1702
|
+
const outputPos = groupIndex(PUMP_OUTPUT_ROLES);
|
|
1703
|
+
const groundPos = groupIndex(PUMP_GROUND_ROLES);
|
|
1704
|
+
if (inputPos === null || outputPos === null || groundPos === null) {
|
|
1705
|
+
return {
|
|
1706
|
+
kind: "unbound",
|
|
1707
|
+
detail: `part ${identity.partId} is registered as a charge pump but its entry does not ` +
|
|
1708
|
+
`name a distinct supply input, inverted output, and ground reference`,
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
const roles = device.identity.terminalRoles;
|
|
1712
|
+
const declared = device.identity.declaredTerminalRoles;
|
|
1713
|
+
const onlyDeclared = (role) => {
|
|
1714
|
+
const found = declared.flatMap((value, index) => value === role ? [index] : []);
|
|
1715
|
+
return found.length === 1 ? found[0] : null;
|
|
1716
|
+
};
|
|
1717
|
+
// A declared supply electrode outranks the entry's pin numbering, the same rule the
|
|
1718
|
+
// regulator path documents: the reference is `ground`, the inverted output is
|
|
1719
|
+
// `negative` (or `positive`, the convention deluxe-memory-man's -15 V output uses),
|
|
1720
|
+
// and the input is the one remaining terminal sitting on a declared nonzero voltage.
|
|
1721
|
+
const declaredBinding = (() => {
|
|
1722
|
+
const ground = onlyDeclared("ground");
|
|
1723
|
+
const output = onlyDeclared("negative") ?? onlyDeclared("positive");
|
|
1724
|
+
if (ground === null || output === null || ground === output) {
|
|
1725
|
+
return null;
|
|
1726
|
+
}
|
|
1727
|
+
const candidates = roles
|
|
1728
|
+
.map((_, index) => index)
|
|
1729
|
+
.filter((index) => index !== ground && index !== output)
|
|
1730
|
+
.filter((index) => {
|
|
1731
|
+
const node = device.nodes[index];
|
|
1732
|
+
if (node === undefined || node === 0) {
|
|
1733
|
+
return false;
|
|
1734
|
+
}
|
|
1735
|
+
const volts = nodeToVoltage.get(node);
|
|
1736
|
+
return volts !== undefined && volts !== 0;
|
|
1737
|
+
});
|
|
1738
|
+
if (candidates.length !== 1) {
|
|
1739
|
+
return null;
|
|
1740
|
+
}
|
|
1741
|
+
return { input: candidates[0], output, ground };
|
|
1742
|
+
})();
|
|
1743
|
+
// Otherwise the folded terminal names, bound through the entry's own aliases so any
|
|
1744
|
+
// declaration order lands on the right pins.
|
|
1745
|
+
const roleBinding = (() => {
|
|
1746
|
+
const input = terminalWithRole(roles, [...groups[inputPos]]);
|
|
1747
|
+
const output = terminalWithRole(roles, [...groups[outputPos]]);
|
|
1748
|
+
const ground = terminalWithRole(roles, [...groups[groundPos]]);
|
|
1749
|
+
if (input === null || output === null || ground === null) {
|
|
1750
|
+
return null;
|
|
1751
|
+
}
|
|
1752
|
+
if (new Set([input, output, ground]).size !== 3) {
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1755
|
+
return { input, output, ground };
|
|
1756
|
+
})();
|
|
1757
|
+
// Otherwise a bare-pin shell in package order: terminals numbered pin1..pinN fold to
|
|
1758
|
+
// no role at all, so declaration order against the entry's package-order pinout is
|
|
1759
|
+
// the only reading, exactly as the regulator entries already assume.
|
|
1760
|
+
const positionalBinding = (() => {
|
|
1761
|
+
if (!roles.every((role) => role === null)) {
|
|
1762
|
+
return null;
|
|
1763
|
+
}
|
|
1764
|
+
if (entry.model.kind !== "sections" ||
|
|
1765
|
+
entry.model.pinout.length !== roles.length) {
|
|
1766
|
+
return null;
|
|
1767
|
+
}
|
|
1768
|
+
return { input: inputPos, output: outputPos, ground: groundPos };
|
|
1769
|
+
})();
|
|
1770
|
+
const binding = declaredBinding ?? roleBinding ?? positionalBinding;
|
|
1771
|
+
if (binding === null) {
|
|
1772
|
+
return {
|
|
1773
|
+
kind: "unbound",
|
|
1774
|
+
detail: `part ${identity.partId} is registered as a charge pump, but neither its terminal ` +
|
|
1775
|
+
`names, its declared supply electrodes, nor its pin order bind a supply input, ` +
|
|
1776
|
+
`inverted output, and ground reference: ${device.nodes.length} terminals against ` +
|
|
1777
|
+
`a ${entry.model.pinout.length}-terminal pinout`,
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
const inputNode = device.nodes[binding.input];
|
|
1781
|
+
const outputNode = device.nodes[binding.output];
|
|
1782
|
+
const groundNode = device.nodes[binding.ground];
|
|
1783
|
+
if (inputNode === undefined ||
|
|
1784
|
+
outputNode === undefined ||
|
|
1785
|
+
groundNode === undefined) {
|
|
1786
|
+
return {
|
|
1787
|
+
kind: "unbound",
|
|
1788
|
+
detail: `part ${identity.partId} charge-pump binding names a terminal this component ` +
|
|
1789
|
+
`does not declare`,
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
// The reference is circuit ground. A pump tied to anything else is a shell the
|
|
1793
|
+
// derivation cannot place, not a rail against an unknown reference.
|
|
1794
|
+
if (groundNode !== 0 || outputNode === 0) {
|
|
1795
|
+
return {
|
|
1796
|
+
kind: "unbound",
|
|
1797
|
+
detail: `part ${identity.partId} charge-pump binding puts the reference off circuit ` +
|
|
1798
|
+
`ground, so no rail can be derived from it`,
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
// The document keeps the last word: an output the document already drives --
|
|
1802
|
+
// declared rail or regulator-supplied -- stays the document's, and the pump stays open.
|
|
1803
|
+
if (nodeToVoltage.has(outputNode)) {
|
|
1804
|
+
return { kind: "backed" };
|
|
1805
|
+
}
|
|
1806
|
+
const inputVolts = nodeToVoltage.get(inputNode);
|
|
1807
|
+
if (inputVolts === undefined || inputVolts === 0) {
|
|
1808
|
+
return { kind: "no-input" };
|
|
1809
|
+
}
|
|
1810
|
+
// An output no other device touches cannot move the solve: stamping it would only
|
|
1811
|
+
// widen the circuit-wide rail extremes other devices fall back to.
|
|
1812
|
+
if ((devicesTouchingNode.get(outputNode) ?? 0) < 2) {
|
|
1813
|
+
return { kind: "unloaded" };
|
|
1814
|
+
}
|
|
1815
|
+
const lossVolts = entry.chargePumpLossVolts ?? 0;
|
|
1816
|
+
return {
|
|
1817
|
+
kind: "model",
|
|
1818
|
+
inputNode,
|
|
1819
|
+
outputNode,
|
|
1820
|
+
groundNode,
|
|
1821
|
+
inputVolts,
|
|
1822
|
+
volts: -inputVolts - Math.sign(-inputVolts) * lossVolts,
|
|
1823
|
+
sourceOhms: section.law.sourceOhms,
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* A derived pump rail as one ordinary section device plus its law, or the original device
|
|
1828
|
+
* opened with the reason the derivation recorded. Never `null` for a device the
|
|
1829
|
+
* derivation decided: the placeholder volts in the pump entries must not reach generic
|
|
1830
|
+
* expansion, so every decided device is consumed here.
|
|
1831
|
+
*/
|
|
1832
|
+
function pumpSectionDevices(device, derivation) {
|
|
1833
|
+
switch (derivation.kind) {
|
|
1834
|
+
case "backed":
|
|
1835
|
+
return [[device, openIc(device, "charge-pump-declared-rail")]];
|
|
1836
|
+
case "no-input":
|
|
1837
|
+
return [[device, openIc(device, "charge-pump-no-input")]];
|
|
1838
|
+
case "unloaded":
|
|
1839
|
+
return [[device, openIc(device, "charge-pump-output-unloaded")]];
|
|
1840
|
+
case "unbound":
|
|
1841
|
+
return [
|
|
1842
|
+
[
|
|
1843
|
+
device,
|
|
1844
|
+
{
|
|
1845
|
+
outcome: "law",
|
|
1846
|
+
device: device.id,
|
|
1847
|
+
law: { kind: "open" },
|
|
1848
|
+
openReason: "registry-arity-mismatch",
|
|
1849
|
+
insteadOfRefusal: derivation.detail,
|
|
1850
|
+
},
|
|
1851
|
+
],
|
|
1852
|
+
];
|
|
1853
|
+
case "model": {
|
|
1854
|
+
const sectionId = `${device.id}#0`;
|
|
1855
|
+
const sectionDevice = {
|
|
1856
|
+
...device,
|
|
1857
|
+
id: sectionId,
|
|
1858
|
+
nodes: [derivation.outputNode, derivation.groundNode],
|
|
1859
|
+
identity: {
|
|
1860
|
+
...device.identity,
|
|
1861
|
+
terminalRoles: canonicalRoles("voltage-source", 2),
|
|
1862
|
+
declaredTerminalRoles: canonicalRoles("voltage-source", 2),
|
|
1863
|
+
},
|
|
1864
|
+
};
|
|
1865
|
+
return [
|
|
1866
|
+
[
|
|
1867
|
+
sectionDevice,
|
|
1868
|
+
{
|
|
1869
|
+
outcome: "law",
|
|
1870
|
+
device: sectionId,
|
|
1871
|
+
law: {
|
|
1872
|
+
kind: "voltage-source",
|
|
1873
|
+
volts: derivation.volts,
|
|
1874
|
+
sourceOhms: derivation.sourceOhms,
|
|
1875
|
+
},
|
|
1876
|
+
},
|
|
1877
|
+
],
|
|
1878
|
+
];
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1447
1881
|
}
|
|
1448
1882
|
/**
|
|
1449
1883
|
* An OTA's signal terminals, bound by the names the device declares for them.
|
|
@@ -1652,7 +2086,7 @@ function packageDeviceParameters(device, registry) {
|
|
|
1652
2086
|
}
|
|
1653
2087
|
return parameters;
|
|
1654
2088
|
}
|
|
1655
|
-
function resolveDevice(device, registry, rails, supply, taperByControl, mainsFrequencyHz, devicesTouchingNode, nodeToVoltage,
|
|
2089
|
+
function resolveDevice(device, registry, rails, supply, taperByControl, mainsFrequencyHz, devicesTouchingNode, nodeToVoltage, netlist) {
|
|
1656
2090
|
// A component declared as one transistor that is really several devices in one shell. Opened
|
|
1657
2091
|
// rather than stamped, for the reason `isNonExecutableIcSupportShell` opens an IC shell: there
|
|
1658
2092
|
// is no single device here to describe, and a transistor law would invent one. Reported by
|
|
@@ -1685,7 +2119,7 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1685
2119
|
// `findElectricallyIsolatedIcs`, which compile() runs over the same predicate.
|
|
1686
2120
|
return openIc(device, "electrically-isolated");
|
|
1687
2121
|
}
|
|
1688
|
-
if (isChargePumpWithDeclaredRail(device, nodeToVoltage
|
|
2122
|
+
if (isChargePumpWithDeclaredRail(device, nodeToVoltage)) {
|
|
1689
2123
|
// The rail the pump generates is separately declared as a voltage-carrying
|
|
1690
2124
|
// port; the IC itself is a view-only structural anchor, not a solved model.
|
|
1691
2125
|
return openIc(device, "charge-pump-declared-rail");
|
|
@@ -1903,6 +2337,7 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1903
2337
|
if (declared.breakdownVolts !== undefined ||
|
|
1904
2338
|
declared.saturationCurrent !== undefined ||
|
|
1905
2339
|
declared.seriesResistance !== undefined ||
|
|
2340
|
+
declared.emissionCoefficient !== undefined ||
|
|
1906
2341
|
device.isLed === true) {
|
|
1907
2342
|
return {
|
|
1908
2343
|
outcome: "law",
|
|
@@ -1912,6 +2347,8 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1912
2347
|
breakdownVolts: declared.breakdownVolts ?? catalogued.breakdownVolts,
|
|
1913
2348
|
saturationCurrent: declared.saturationCurrent ?? catalogued.saturationCurrent,
|
|
1914
2349
|
seriesResistance: declared.seriesResistance ?? catalogued.seriesResistance,
|
|
2350
|
+
emissionCoefficient: declared.emissionCoefficient ?? catalogued.emissionCoefficient,
|
|
2351
|
+
thermalVoltage: declared.thermalVoltage ?? catalogued.thermalVoltage,
|
|
1915
2352
|
isLed: device.isLed === true ? true : catalogued.isLed,
|
|
1916
2353
|
},
|
|
1917
2354
|
};
|
|
@@ -1928,7 +2365,8 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1928
2365
|
// If it is an LED, use physical LED defaults (higher forward knee ~1.8V to 2.2V),
|
|
1929
2366
|
// otherwise use class defaults (1N4148-ish silicon signal diode).
|
|
1930
2367
|
saturationCurrent: device.parameters.saturationCurrent ?? (isLed ? 1e-12 : 2.52e-9),
|
|
1931
|
-
|
|
2368
|
+
// The source's own `EmissionCoefficient` when it states one, else the class default.
|
|
2369
|
+
emissionCoefficient: device.parameters.emissionCoefficient ?? 1.752,
|
|
1932
2370
|
thermalVoltage: device.parameters.thermalVoltage ?? (isLed ? 0.05 : THERMAL_VOLTAGE),
|
|
1933
2371
|
// 0 for an ordinary diode. Read from the source, never inferred from a part
|
|
1934
2372
|
// number: `1N4733` is a 5.1 V zener and `1N4148` is not, and telling them apart
|
|
@@ -1984,10 +2422,10 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
1984
2422
|
},
|
|
1985
2423
|
};
|
|
1986
2424
|
case "opamp": {
|
|
1987
|
-
const
|
|
1988
|
-
const
|
|
1989
|
-
const railHigh =
|
|
1990
|
-
const railLow =
|
|
2425
|
+
const declaredSwing = registryOpampSwing(registry, device.identity.partNumber);
|
|
2426
|
+
const declaredSwung = railsAfterSwing(opampRails(device, rails, nodeToVoltage), declaredSwing);
|
|
2427
|
+
const railHigh = declaredSwung.rails.high;
|
|
2428
|
+
const railLow = declaredSwung.rails.low;
|
|
1991
2429
|
// The gain-bandwidth product: the packet's own typed `GBP` first, else the datasheet figure
|
|
1992
2430
|
// of the exact part id, else none. Never a class number -- `null` lowers to a stamp the
|
|
1993
2431
|
// consoles run on their default, and `opamp-gain-bandwidth-default` names it.
|
|
@@ -2001,7 +2439,14 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
2001
2439
|
railHigh,
|
|
2002
2440
|
railLow,
|
|
2003
2441
|
openLoopGain: device.parameters.openLoopGain ?? DEFAULT_OPEN_LOOP_GAIN,
|
|
2004
|
-
...(
|
|
2442
|
+
...(declaredSwing === null
|
|
2443
|
+
? {}
|
|
2444
|
+
: {
|
|
2445
|
+
outputSwingDropHighVolts: declaredSwing.highVolts,
|
|
2446
|
+
outputSwingDropLowVolts: declaredSwing.lowVolts,
|
|
2447
|
+
outputSwingPrinted: declaredSwing.printed,
|
|
2448
|
+
}),
|
|
2449
|
+
...(declaredSwung.carried === null ? {} : { outputSwingCarried: declaredSwung.carried }),
|
|
2005
2450
|
...(gainBandwidthHz === null ? {} : { gainBandwidthHz }),
|
|
2006
2451
|
},
|
|
2007
2452
|
};
|
|
@@ -2145,29 +2590,41 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
2145
2590
|
law: { kind: "voltage-source", volts, sourceOhms: SUPPLY_SOURCE_OHMS },
|
|
2146
2591
|
};
|
|
2147
2592
|
}
|
|
2148
|
-
case "bjt":
|
|
2593
|
+
case "bjt": {
|
|
2594
|
+
// A registered part refines the class defaults, exactly as the diode and
|
|
2595
|
+
// JFET-gate cases do. Exact part number only -- see `registryLawFor`.
|
|
2596
|
+
// Declared parameters win over everything, every time: the entries exist
|
|
2597
|
+
// to fill absence, not to overrule a packet that states the value.
|
|
2598
|
+
// Unregistered parts keep the class default bit-identically.
|
|
2599
|
+
const catalogued = registryLawFor(registry, device.identity.partNumber, "bjt");
|
|
2600
|
+
const entry = catalogued !== null && catalogued.kind === "bjt" ? catalogued : null;
|
|
2149
2601
|
return {
|
|
2150
2602
|
outcome: "law",
|
|
2151
2603
|
device: device.id,
|
|
2152
2604
|
law: {
|
|
2153
2605
|
kind: "bjt",
|
|
2154
2606
|
polarity: device.parameters.pnp === 1 ? "pnp" : "npn",
|
|
2155
|
-
saturationCurrent: device.parameters.saturationCurrent ??
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2607
|
+
saturationCurrent: device.parameters.saturationCurrent ??
|
|
2608
|
+
entry?.saturationCurrent ??
|
|
2609
|
+
SILICON_SATURATION_CURRENT,
|
|
2610
|
+
forwardBeta: device.parameters.beta ?? entry?.forwardBeta ?? SILICON_FORWARD_BETA,
|
|
2611
|
+
reverseBeta: device.parameters.reverseBeta ?? entry?.reverseBeta ?? 1,
|
|
2612
|
+
thermalVoltage: device.parameters.thermalVoltage ??
|
|
2613
|
+
entry?.thermalVoltage ??
|
|
2614
|
+
THERMAL_VOLTAGE,
|
|
2159
2615
|
// Absent for every silicon part in the corpus, and for the one germanium
|
|
2160
2616
|
// packet whose `LeakageCurrent` is a sentence rather than a value.
|
|
2161
|
-
leakageAmps: device.parameters.leakageAmps ?? 0,
|
|
2617
|
+
leakageAmps: device.parameters.leakageAmps ?? entry?.leakageAmps ?? 0,
|
|
2162
2618
|
},
|
|
2163
2619
|
};
|
|
2620
|
+
}
|
|
2164
2621
|
case "jfet": {
|
|
2165
|
-
// A registered part refines the class
|
|
2622
|
+
// A registered part refines the class channel, exactly as `GENERIC_TUBE_GRID`'s
|
|
2166
2623
|
// note says a registry entry should. Exact part number only -- see
|
|
2167
|
-
// `registryLawFor`. The
|
|
2168
|
-
//
|
|
2169
|
-
//
|
|
2170
|
-
//
|
|
2624
|
+
// `registryLawFor`. The 2SK30A entry's channel repeats the class default by
|
|
2625
|
+
// construction (rank-dependent, and the database beta disagrees with at
|
|
2626
|
+
// least one packet's record), so a covered part's channel is
|
|
2627
|
+
// indistinguishable from an unregistered one's. Declared
|
|
2171
2628
|
// Vt0/Beta win over everything, every time: the entries exist to fill
|
|
2172
2629
|
// absence, not to overrule a packet that states the value (the same overlay
|
|
2173
2630
|
// the diode case applies). Unregistered parts keep the class default
|
|
@@ -2184,13 +2641,17 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
2184
2641
|
channel: device.parameters.pChannel === 1 ? "p" : "n",
|
|
2185
2642
|
// A JFET is depletion mode: it conducts at zero gate bias and pinches
|
|
2186
2643
|
// off as the gate goes negative, so the threshold is negative.
|
|
2187
|
-
thresholdVolts: device.parameters.thresholdVolts ??
|
|
2188
|
-
|
|
2189
|
-
|
|
2644
|
+
thresholdVolts: device.parameters.thresholdVolts ??
|
|
2645
|
+
entry?.thresholdVolts ??
|
|
2646
|
+
JFET_DEFAULT_THRESHOLD_VOLTS,
|
|
2647
|
+
transconductance: device.parameters.transconductance ??
|
|
2648
|
+
entry?.transconductance ??
|
|
2649
|
+
JFET_DEFAULT_TRANSCONDUCTANCE,
|
|
2650
|
+
channelLengthModulation: entry?.channelLengthModulation ?? 0,
|
|
2190
2651
|
// Subthreshold conduction (see the `fet` law docs in types.ts): the
|
|
2191
2652
|
// gradual region a knee-biased JFET actually sweeps in. MOSFETs
|
|
2192
2653
|
// keep 0 below.
|
|
2193
|
-
subthresholdVolts: 0.07,
|
|
2654
|
+
subthresholdVolts: entry?.subthresholdVolts ?? 0.07,
|
|
2194
2655
|
// A JFET gate is a silicon PN junction, so it conducts once forward-biased.
|
|
2195
2656
|
// Saturation current and scale are **reused from the triode grid**, which
|
|
2196
2657
|
// `compact-mna-dynamic-triode-cell` reports 0 Newton failures with; the onset
|
|
@@ -2199,30 +2660,39 @@ function resolveDevice(device, registry, rails, supply, taperByControl, mainsFre
|
|
|
2199
2660
|
// part refines the current through its catalog entry, and the
|
|
2200
2661
|
// unregistered default stays exactly what it was.
|
|
2201
2662
|
gateSaturationCurrent: entry?.gateSaturationCurrent ?? 1.0e-5,
|
|
2202
|
-
gateOnsetVolts: 0.5,
|
|
2203
|
-
gateScaleVolts: 0.06,
|
|
2663
|
+
gateOnsetVolts: entry?.gateOnsetVolts ?? 0.5,
|
|
2664
|
+
gateScaleVolts: entry?.gateScaleVolts ?? 0.06,
|
|
2204
2665
|
},
|
|
2205
2666
|
};
|
|
2206
2667
|
}
|
|
2207
|
-
case "mosfet":
|
|
2668
|
+
case "mosfet": {
|
|
2669
|
+
// The same registry refinement as the JFET channel above: a registered
|
|
2670
|
+
// part refines the class defaults, declared parameters win, and
|
|
2671
|
+
// unregistered parts keep the class default bit-identically. Exact part
|
|
2672
|
+
// number only. This shares the `fet` law kind with JFET entries; part
|
|
2673
|
+
// ids are disjoint across the two classes, so a lookup by exact folded
|
|
2674
|
+
// part id cannot cross from one to the other.
|
|
2675
|
+
const catalogued = registryLawFor(registry, device.identity.partNumber, "fet");
|
|
2676
|
+
const entry = catalogued !== null && catalogued.kind === "fet" ? catalogued : null;
|
|
2208
2677
|
return {
|
|
2209
2678
|
outcome: "law",
|
|
2210
2679
|
device: device.id,
|
|
2211
2680
|
law: {
|
|
2212
2681
|
kind: "fet",
|
|
2213
2682
|
channel: device.parameters.pChannel === 1 ? "p" : "n",
|
|
2214
|
-
thresholdVolts: device.parameters.thresholdVolts ?? 2,
|
|
2215
|
-
transconductance: device.parameters.transconductance ?? 1e-3,
|
|
2216
|
-
channelLengthModulation: 0,
|
|
2683
|
+
thresholdVolts: device.parameters.thresholdVolts ?? entry?.thresholdVolts ?? 2,
|
|
2684
|
+
transconductance: device.parameters.transconductance ?? entry?.transconductance ?? 1e-3,
|
|
2685
|
+
channelLengthModulation: entry?.channelLengthModulation ?? 0,
|
|
2217
2686
|
// Insulated gate: no subthreshold region is modelled, behavior unchanged.
|
|
2218
|
-
subthresholdVolts: 0,
|
|
2687
|
+
subthresholdVolts: entry?.subthresholdVolts ?? 0,
|
|
2219
2688
|
// An insulated gate draws no current at any bias. Zero, deliberately: this
|
|
2220
2689
|
// is what the law did for every FET before the JFET junction was added.
|
|
2221
|
-
gateSaturationCurrent: 0,
|
|
2222
|
-
gateOnsetVolts: 0,
|
|
2223
|
-
gateScaleVolts: 1,
|
|
2690
|
+
gateSaturationCurrent: entry?.gateSaturationCurrent ?? 0,
|
|
2691
|
+
gateOnsetVolts: entry?.gateOnsetVolts ?? 0,
|
|
2692
|
+
gateScaleVolts: entry?.gateScaleVolts ?? 1,
|
|
2224
2693
|
},
|
|
2225
2694
|
};
|
|
2695
|
+
}
|
|
2226
2696
|
case "ota":
|
|
2227
2697
|
return otaDeviceClassLaw(device);
|
|
2228
2698
|
case "inverter":
|
|
@@ -2403,6 +2873,15 @@ const springReverbTanks = {
|
|
|
2403
2873
|
* documents and deliberately leaves unfiled). Every remaining mismatch sits on `kind: ic`, where
|
|
2404
2874
|
* naming any class is exactly what that kind is for. Adding a class here on speculation would be
|
|
2405
2875
|
* vocabulary against unexercised data.
|
|
2876
|
+
*
|
|
2877
|
+
* A UJT relaxation-oscillator law was attempted on branch `indiejoseph/ujt-law` (2026-10-20):
|
|
2878
|
+
* bar split at eta, emitter diode into the tap, conductivity modulation as a VCCS, plus a
|
|
2879
|
+
* clamp diode for saturation. It oscillates at the right period while it runs, but the
|
|
2880
|
+
* firing-point crossing is a Newton lottery (about one stalled peak in 70 cycles, then the
|
|
2881
|
+
* hold poisons the run permanently) in the reference runtime, and ngspice timestep-collapses
|
|
2882
|
+
* at the same instant -- see the branch report. The minimal addition is a hysteretic switch
|
|
2883
|
+
* stamp (once-per-sample evaluation, no Newton involvement), which no existing stamp is.
|
|
2884
|
+
* Until that exists this entry stays: opening is honest, stamping a BJT is not.
|
|
2406
2885
|
*/
|
|
2407
2886
|
const unimplementedDeviceLaws = new Set([
|
|
2408
2887
|
"circuit.unijunctiontransistor",
|
|
@@ -3480,15 +3959,19 @@ export function findIcsNotExecuted(lawed, original, registry) {
|
|
|
3480
3959
|
? `${head}: it is registered as a source-boundary shell, drawn for topological continuity rather than execution, so it is compiled as open.`
|
|
3481
3960
|
: reason === "charge-pump-declared-rail"
|
|
3482
3961
|
? `${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
|
-
|
|
3962
|
+
: reason === "charge-pump-no-input"
|
|
3963
|
+
? `${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.`
|
|
3964
|
+
: reason === "charge-pump-output-unloaded"
|
|
3965
|
+
? `${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.`
|
|
3966
|
+
: reason === "registry-arity-mismatch"
|
|
3967
|
+
? `${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.`
|
|
3968
|
+
: reason === "registry-open"
|
|
3969
|
+
? `${head}: the registry states no executable model for it and it is compiled as open. Whatever it does in the real circuit is absent.`
|
|
3970
|
+
: reason === "declared-class-without-model"
|
|
3971
|
+
? `${head}: no registered part model binds it -- ${resolution.insteadOfRefusal ?? "the registry supplied none"} -- and ${device.identity.partNumber === null
|
|
3972
|
+
? `its source names no part number and declares only the generic ${device.identity.declaredType}, so there is nothing to look up`
|
|
3973
|
+
: `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.`
|
|
3974
|
+
: `${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
3975
|
...(gapClass === undefined ? {} : { gapClass }),
|
|
3493
3976
|
});
|
|
3494
3977
|
}
|