@vessel-dsp/compiler 0.6.0 → 0.8.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 +1 -1
- package/dist/bbd-clock.d.ts +42 -0
- package/dist/bbd-clock.d.ts.map +1 -1
- package/dist/bbd-clock.js +378 -1
- package/dist/bbd-clock.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +92 -23
- package/dist/compile.js.map +1 -1
- package/dist/device-laws.d.ts.map +1 -1
- package/dist/device-laws.js +77 -11
- package/dist/device-laws.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.map +1 -1
- package/dist/netlist.js +27 -1
- package/dist/netlist.js.map +1 -1
- package/dist/part-catalog.d.ts +19 -0
- package/dist/part-catalog.d.ts.map +1 -1
- package/dist/part-catalog.js +114 -4
- 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/diode.d.ts.map +1 -1
- package/dist/registry-data/diode.js +181 -64
- package/dist/registry-data/diode.js.map +1 -1
- package/dist/registry-data/index.d.ts +2 -0
- package/dist/registry-data/index.d.ts.map +1 -1
- package/dist/registry-data/index.js +15 -3
- package/dist/registry-data/index.js.map +1 -1
- package/dist/registry-data/led.d.ts +20 -0
- package/dist/registry-data/led.d.ts.map +1 -0
- package/dist/registry-data/led.js +415 -0
- package/dist/registry-data/led.js.map +1 -0
- package/dist/registry-data/schema.d.ts +11 -3
- package/dist/registry-data/schema.d.ts.map +1 -1
- package/dist/registry-data/schema.js +20 -4
- package/dist/registry-data/schema.js.map +1 -1
- 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.d.ts +32 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js.map +1 -1
- package/dist/types.d.ts +57 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/bbd-clock.d.ts
CHANGED
|
@@ -138,6 +138,48 @@ export type OpenOx2ClockLawOutcome = {
|
|
|
138
138
|
* form (a different oscillator; no corpus case) all read absent here.
|
|
139
139
|
*/
|
|
140
140
|
export declare function deriveOpenOx2ClockLaw(netlist: Netlist, registry: PartRegistry): OpenOx2ClockLawOutcome;
|
|
141
|
+
/**
|
|
142
|
+
* Derive a BBD delay from a UJT relaxation-oscillator clock network through a
|
|
143
|
+
* divide-by-two flip-flop.
|
|
144
|
+
*
|
|
145
|
+
* The recognised form, every condition a whole-value comparison on typed fields
|
|
146
|
+
* plus connectivity (no names, no prose, no patterns):
|
|
147
|
+
*
|
|
148
|
+
* - one `kind: bjt` device whose declared type is `Circuit.UnijunctionTransistor`
|
|
149
|
+
* and whose part resolves to a `ujt` registry law (the eta the formula reads);
|
|
150
|
+
* its `emitter`/`base1`/`base2` terminal roles must each resolve to exactly one
|
|
151
|
+
* node, with base1 on ground;
|
|
152
|
+
* - the emitter charges from a supply rail through one or more two-terminal
|
|
153
|
+
* resistors, optionally plus one switch-selected branch: a three-terminal
|
|
154
|
+
* `kind: switch` with declared `common`/`throw`/`throw` roles whose common joins
|
|
155
|
+
* the switched resistor, one throw sits on the rail and the other does not;
|
|
156
|
+
* - exactly one timing capacitor (under 1 uF, the MN310x bound) from the emitter
|
|
157
|
+
* to a node that is AC ground: tied to ground through resistors totalling at
|
|
158
|
+
* most 1% of the smallest charging resistance (the corpus form returns through
|
|
159
|
+
* 68 ohms against 25 kohm), with nothing on it but the timing capacitor,
|
|
160
|
+
* ground-return resistors and capacitors;
|
|
161
|
+
* - a 4013-family divider (`UJT_DIVIDER_PART_IDS`) with one half toggle-wired
|
|
162
|
+
* (`data` and `not-q` on one node) and that half's `q`/`not-q` on the BBD's own
|
|
163
|
+
* clock nodes;
|
|
164
|
+
* - exactly one buffer transistor: a `bjt` (not itself a UJT) with its collector
|
|
165
|
+
* on the divider's clock node, its emitter on the rail, its base coupled to the
|
|
166
|
+
* timing capacitor's far end through exactly one capacitor, and no other active
|
|
167
|
+
* device on the clock or base nodes (resistor biasing is allowed).
|
|
168
|
+
*
|
|
169
|
+
* The frequency is the ideal relaxation law f = 1/(R*C*ln(1/(1-eta))) with the
|
|
170
|
+
* netlist's R and C and the registry's eta; the divider halves it, so the BBD sees
|
|
171
|
+
* f/2 and delay = stages/f_UJT. The ideal law charges 0 to Vp rather than valley
|
|
172
|
+
* to Vp (VF is printed, the valley voltage is graph-only), which overstates the
|
|
173
|
+
* period by about 6% -- second order against the printed eta spread, and stated
|
|
174
|
+
* rather than corrected. A switch with a control sweeps both positions through
|
|
175
|
+
* `clockControl` (min/max resistances, the control's own taper, nominal at the
|
|
176
|
+
* default position); a switch without one, or no switch at all, derives the
|
|
177
|
+
* single selected resistance statically. Anything else about the form -- a second
|
|
178
|
+
* UJT, an unregistered UJT part, an eta outside (0, 1), an unknown charging
|
|
179
|
+
* branch, a missing or miswired divider, buffer or timing capacitor -- is
|
|
180
|
+
* `no-clock-driver`, and the caller falls through to the gate diagnosis.
|
|
181
|
+
*/
|
|
182
|
+
export declare function deriveUjtRelaxationClock(bbdDevice: Device, netlist: Netlist, stages: number, registry: PartRegistry): BbdClockDerivationOutcome;
|
|
141
183
|
/**
|
|
142
184
|
* Princeton Technology PT2399 delay time from external VCO resistance R (in Ohms).
|
|
143
185
|
*
|
package/dist/bbd-clock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bbd-clock.d.ts","sourceRoot":"","sources":["../src/bbd-clock.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACX,SAAS,EACT,MAAM,EACN,UAAU,EACV,OAAO,EACP,MAAM,EACN,SAAS,EACT,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE3E,MAAM,MAAM,yBAAyB,GAClC;IACA,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACjC,GACD;IACA,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;CACnC,GACD;IACA,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACvB,CAAC;AA+FL;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,UAAU,CAOtD,CAAC;AAEH;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,YAAY,GACpB,SAAS,MAAM,EAAE,CAkBnB;AAiSD,MAAM,MAAM,qBAAqB,GAC9B;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnF;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAC3C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,YAAY,GACpB,qBAAqB,CAiFvB;AAqBD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,YAAY,GACpB,WAAW,CAAC,MAAM,CAAC,CA6BrB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAMrE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAUrE;AAueD,wBAAgB,yBAAyB,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,YAAY,GACpB,yBAAyB,
|
|
1
|
+
{"version":3,"file":"bbd-clock.d.ts","sourceRoot":"","sources":["../src/bbd-clock.ts"],"names":[],"mappings":"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"}
|
package/dist/bbd-clock.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// - ambiguous multi-path timing networks across different oscillator node pairs
|
|
17
17
|
// - unconnected oscillator pins
|
|
18
18
|
import { GROUND } from "./types.js";
|
|
19
|
-
import { foldToken, registryEntryFor } from "./registry.js";
|
|
19
|
+
import { foldToken, registryEntryFor, registryLawFor } from "./registry.js";
|
|
20
20
|
const CD4047_PART_IDS = new Set([
|
|
21
21
|
"cd4047",
|
|
22
22
|
"cd4047b",
|
|
@@ -993,6 +993,15 @@ function implausibleClock(fClockHz, stages) {
|
|
|
993
993
|
export function deriveBbdDelayFromNetlist(bbdDevice, netlist, stages, registry) {
|
|
994
994
|
const clockDev = findClockDriverDevice(netlist);
|
|
995
995
|
if (!clockDev) {
|
|
996
|
+
// A UJT relaxation oscillator through a divide-by-two into the brigade is a
|
|
997
|
+
// dedicated timing network with a printed formula of its own (section below),
|
|
998
|
+
// so it is tried before the generic gate-oscillator diagnosis. A mere divider
|
|
999
|
+
// spelling on the clock pins is not enough: without the UJT form this falls
|
|
1000
|
+
// through to the gate branch, where the buffer transistor reads as steering.
|
|
1001
|
+
const ujt = deriveUjtRelaxationClock(bbdDevice, netlist, stages, registry);
|
|
1002
|
+
if (ujt.outcome === "derived" || ujt.outcome === "refused") {
|
|
1003
|
+
return ujt;
|
|
1004
|
+
}
|
|
996
1005
|
// No dedicated CD4047/MN310x. Before reporting that nothing clocks this brigade -- which
|
|
997
1006
|
// on two corpus packets is simply false -- look for a logic part on its clock pins.
|
|
998
1007
|
const gate = findGateClockSource(bbdDevice, netlist);
|
|
@@ -1579,6 +1588,374 @@ export function deriveOpenOx2ClockLaw(netlist, registry) {
|
|
|
1579
1588
|
cFarads,
|
|
1580
1589
|
};
|
|
1581
1590
|
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Dual D-type flip-flop parts that divide a clock by two, by exact part id.
|
|
1593
|
+
*
|
|
1594
|
+
* A closed vocabulary matched as whole folded values, like `GATE_CLOCK_PART_IDS`
|
|
1595
|
+
* above, but narrower: only the 4013 family divides, while the 14069/4011/4043
|
|
1596
|
+
* members of that set oscillate or gate and must never reach the toggle check below.
|
|
1597
|
+
* `scl4013a` is the Solid State Scientific spelling the corpus's UJT-clocked
|
|
1598
|
+
* brigade declares; it rides the CD4013AE registry entry on core main, which this
|
|
1599
|
+
* module never consults (it matches folded part ids, not aliases), so it is listed
|
|
1600
|
+
* here literally.
|
|
1601
|
+
*/
|
|
1602
|
+
const UJT_DIVIDER_PART_IDS = new Set([
|
|
1603
|
+
"cd4013",
|
|
1604
|
+
"cd4013a",
|
|
1605
|
+
"cd4013ae",
|
|
1606
|
+
"cd4013b",
|
|
1607
|
+
"cd4013be",
|
|
1608
|
+
"hef4013",
|
|
1609
|
+
"hcf4013",
|
|
1610
|
+
"mc14013",
|
|
1611
|
+
"mc14013b",
|
|
1612
|
+
"scl4013a",
|
|
1613
|
+
]);
|
|
1614
|
+
/** The source's own closed type for a unijunction transistor, folded for comparison. */
|
|
1615
|
+
const UJT_DECLARED_TYPE = "circuit.unijunctiontransistor";
|
|
1616
|
+
/**
|
|
1617
|
+
* Derive a BBD delay from a UJT relaxation-oscillator clock network through a
|
|
1618
|
+
* divide-by-two flip-flop.
|
|
1619
|
+
*
|
|
1620
|
+
* The recognised form, every condition a whole-value comparison on typed fields
|
|
1621
|
+
* plus connectivity (no names, no prose, no patterns):
|
|
1622
|
+
*
|
|
1623
|
+
* - one `kind: bjt` device whose declared type is `Circuit.UnijunctionTransistor`
|
|
1624
|
+
* and whose part resolves to a `ujt` registry law (the eta the formula reads);
|
|
1625
|
+
* its `emitter`/`base1`/`base2` terminal roles must each resolve to exactly one
|
|
1626
|
+
* node, with base1 on ground;
|
|
1627
|
+
* - the emitter charges from a supply rail through one or more two-terminal
|
|
1628
|
+
* resistors, optionally plus one switch-selected branch: a three-terminal
|
|
1629
|
+
* `kind: switch` with declared `common`/`throw`/`throw` roles whose common joins
|
|
1630
|
+
* the switched resistor, one throw sits on the rail and the other does not;
|
|
1631
|
+
* - exactly one timing capacitor (under 1 uF, the MN310x bound) from the emitter
|
|
1632
|
+
* to a node that is AC ground: tied to ground through resistors totalling at
|
|
1633
|
+
* most 1% of the smallest charging resistance (the corpus form returns through
|
|
1634
|
+
* 68 ohms against 25 kohm), with nothing on it but the timing capacitor,
|
|
1635
|
+
* ground-return resistors and capacitors;
|
|
1636
|
+
* - a 4013-family divider (`UJT_DIVIDER_PART_IDS`) with one half toggle-wired
|
|
1637
|
+
* (`data` and `not-q` on one node) and that half's `q`/`not-q` on the BBD's own
|
|
1638
|
+
* clock nodes;
|
|
1639
|
+
* - exactly one buffer transistor: a `bjt` (not itself a UJT) with its collector
|
|
1640
|
+
* on the divider's clock node, its emitter on the rail, its base coupled to the
|
|
1641
|
+
* timing capacitor's far end through exactly one capacitor, and no other active
|
|
1642
|
+
* device on the clock or base nodes (resistor biasing is allowed).
|
|
1643
|
+
*
|
|
1644
|
+
* The frequency is the ideal relaxation law f = 1/(R*C*ln(1/(1-eta))) with the
|
|
1645
|
+
* netlist's R and C and the registry's eta; the divider halves it, so the BBD sees
|
|
1646
|
+
* f/2 and delay = stages/f_UJT. The ideal law charges 0 to Vp rather than valley
|
|
1647
|
+
* to Vp (VF is printed, the valley voltage is graph-only), which overstates the
|
|
1648
|
+
* period by about 6% -- second order against the printed eta spread, and stated
|
|
1649
|
+
* rather than corrected. A switch with a control sweeps both positions through
|
|
1650
|
+
* `clockControl` (min/max resistances, the control's own taper, nominal at the
|
|
1651
|
+
* default position); a switch without one, or no switch at all, derives the
|
|
1652
|
+
* single selected resistance statically. Anything else about the form -- a second
|
|
1653
|
+
* UJT, an unregistered UJT part, an eta outside (0, 1), an unknown charging
|
|
1654
|
+
* branch, a missing or miswired divider, buffer or timing capacitor -- is
|
|
1655
|
+
* `no-clock-driver`, and the caller falls through to the gate diagnosis.
|
|
1656
|
+
*/
|
|
1657
|
+
export function deriveUjtRelaxationClock(bbdDevice, netlist, stages, registry) {
|
|
1658
|
+
const clockNodes = bbdClockNodes(bbdDevice);
|
|
1659
|
+
if (clockNodes.size === 0) {
|
|
1660
|
+
return { outcome: "no-clock-driver" };
|
|
1661
|
+
}
|
|
1662
|
+
const ujts = netlist.devices.filter((device) => device.kind === "bjt" &&
|
|
1663
|
+
foldToken(device.identity.declaredType ?? "") === UJT_DECLARED_TYPE);
|
|
1664
|
+
if (ujts.length !== 1) {
|
|
1665
|
+
return { outcome: "no-clock-driver" };
|
|
1666
|
+
}
|
|
1667
|
+
const ujt = ujts[0];
|
|
1668
|
+
const emitter = terminalNodeByRole(ujt, "emitter");
|
|
1669
|
+
const base1 = terminalNodeByRole(ujt, "base1");
|
|
1670
|
+
const base2 = terminalNodeByRole(ujt, "base2");
|
|
1671
|
+
if (emitter === null ||
|
|
1672
|
+
base1 === null ||
|
|
1673
|
+
base2 === null ||
|
|
1674
|
+
emitter === base1 ||
|
|
1675
|
+
emitter === base2 ||
|
|
1676
|
+
base1 === base2) {
|
|
1677
|
+
return { outcome: "no-clock-driver" };
|
|
1678
|
+
}
|
|
1679
|
+
if (base1 !== GROUND) {
|
|
1680
|
+
return { outcome: "no-clock-driver" };
|
|
1681
|
+
}
|
|
1682
|
+
const ujtLaw = registryLawFor(registry, ujt.identity.partNumber, "ujt");
|
|
1683
|
+
if (ujtLaw === null || ujtLaw.kind !== "ujt") {
|
|
1684
|
+
return { outcome: "no-clock-driver" };
|
|
1685
|
+
}
|
|
1686
|
+
const eta = ujtLaw.intrinsicStandoffRatio;
|
|
1687
|
+
if (!(eta > 0) || !(eta < 1) || !Number.isFinite(eta)) {
|
|
1688
|
+
return {
|
|
1689
|
+
outcome: "refused",
|
|
1690
|
+
reason: `${ujt.id} (${ujt.identity.partNumber ?? "unmarked UJT"}) states ` +
|
|
1691
|
+
`intrinsicStandoffRatio ${eta}, outside (0, 1), so ln(1/(1-eta)) is not a frequency`,
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
// The rail is whatever a supply-kind device asserts: kinds, never names.
|
|
1695
|
+
const railNodes = new Set();
|
|
1696
|
+
for (const device of netlist.devices) {
|
|
1697
|
+
if (device.kind !== "voltage-source" && device.kind !== "rail") {
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
for (const node of device.nodes) {
|
|
1701
|
+
if (node !== GROUND) {
|
|
1702
|
+
railNodes.add(node);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
// Charging branches: two-terminal resistors with exactly one end on the emitter.
|
|
1707
|
+
const charging = netlist.devices.filter((device) => device.kind === "resistor" &&
|
|
1708
|
+
device.nodes.length === 2 &&
|
|
1709
|
+
(device.nodes[0] === emitter) !== (device.nodes[1] === emitter) &&
|
|
1710
|
+
(device.parameters.ohms ?? 0) > 0);
|
|
1711
|
+
if (charging.length === 0) {
|
|
1712
|
+
return { outcome: "no-clock-driver" };
|
|
1713
|
+
}
|
|
1714
|
+
const farEnd = (device) => (device.nodes[0] === emitter ? device.nodes[1] : device.nodes[0]);
|
|
1715
|
+
// Selector candidates: three-terminal switches with exactly one `common` and
|
|
1716
|
+
// two `throw`s by declared role. The footswitch and every other unrelated
|
|
1717
|
+
// switch in the document matches that shape, so the one that counts is the
|
|
1718
|
+
// switch whose common joins a charging branch -- throws in node order (the
|
|
1719
|
+
// same order `switchPoles` and both selector evaluations use: position p
|
|
1720
|
+
// shorts the common to throws[p]).
|
|
1721
|
+
const candidates = netlist.devices.filter((device) => {
|
|
1722
|
+
if (device.kind !== "switch" || device.nodes.length !== 3) {
|
|
1723
|
+
return false;
|
|
1724
|
+
}
|
|
1725
|
+
const declared = device.identity.declaredTerminalRoles;
|
|
1726
|
+
return (declared.length === 3 &&
|
|
1727
|
+
declared.filter((role) => role === "common").length === 1 &&
|
|
1728
|
+
declared.filter((role) => role === "throw").length === 2);
|
|
1729
|
+
});
|
|
1730
|
+
const chargingFarEnds = new Set(charging.map((device) => (device.nodes[0] === emitter ? device.nodes[1] : device.nodes[0])));
|
|
1731
|
+
const linked = candidates.filter((device) => {
|
|
1732
|
+
const commonIndex = device.identity.declaredTerminalRoles.findIndex((role) => role === "common");
|
|
1733
|
+
return chargingFarEnds.has(device.nodes[commonIndex]);
|
|
1734
|
+
});
|
|
1735
|
+
if (linked.length > 1) {
|
|
1736
|
+
return { outcome: "no-clock-driver" };
|
|
1737
|
+
}
|
|
1738
|
+
const selector = linked[0] ?? null;
|
|
1739
|
+
let commonNode = null;
|
|
1740
|
+
let throwNodes = [];
|
|
1741
|
+
if (selector !== null) {
|
|
1742
|
+
const declared = selector.identity.declaredTerminalRoles;
|
|
1743
|
+
const commonIndex = declared.findIndex((role) => role === "common");
|
|
1744
|
+
commonNode = selector.nodes[commonIndex];
|
|
1745
|
+
throwNodes = selector.nodes.filter((_, index) => index !== commonIndex);
|
|
1746
|
+
if (nodeDegree(netlist, commonNode) !== 2) {
|
|
1747
|
+
return { outcome: "no-clock-driver" };
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
const fixed = charging.filter((device) => railNodes.has(farEnd(device)));
|
|
1751
|
+
const switched = charging.filter((device) => commonNode !== null && farEnd(device) === commonNode);
|
|
1752
|
+
const unknown = charging.filter((device) => !fixed.includes(device) && !switched.includes(device));
|
|
1753
|
+
if (fixed.length === 0 || unknown.length > 0) {
|
|
1754
|
+
return { outcome: "no-clock-driver" };
|
|
1755
|
+
}
|
|
1756
|
+
if (switched.length > 1) {
|
|
1757
|
+
return { outcome: "no-clock-driver" };
|
|
1758
|
+
}
|
|
1759
|
+
let railThrow = null;
|
|
1760
|
+
if (selector !== null) {
|
|
1761
|
+
if (switched.length !== 1) {
|
|
1762
|
+
return { outcome: "no-clock-driver" };
|
|
1763
|
+
}
|
|
1764
|
+
const onRail = throwNodes.filter((node) => railNodes.has(node));
|
|
1765
|
+
const offRail = throwNodes.filter((node) => !railNodes.has(node));
|
|
1766
|
+
// Exactly one throw on the rail and the other elsewhere: a selector that
|
|
1767
|
+
// chooses between two feeds is a different circuit.
|
|
1768
|
+
if (onRail.length !== 1 || offRail.length !== 1) {
|
|
1769
|
+
return { outcome: "no-clock-driver" };
|
|
1770
|
+
}
|
|
1771
|
+
if (offRail[0] === emitter || offRail[0] === commonNode) {
|
|
1772
|
+
return { outcome: "no-clock-driver" };
|
|
1773
|
+
}
|
|
1774
|
+
railThrow = onRail[0];
|
|
1775
|
+
}
|
|
1776
|
+
const conductance = (devices) => devices.reduce((sum, device) => sum + 1 / (device.parameters.ohms ?? 0), 0);
|
|
1777
|
+
const fixedConductance = conductance(fixed);
|
|
1778
|
+
const switchedConductance = conductance(switched);
|
|
1779
|
+
const resistanceAt = (position) => {
|
|
1780
|
+
const selected = throwNodes[position];
|
|
1781
|
+
const extra = selected !== undefined && selected === railThrow ? switchedConductance : 0;
|
|
1782
|
+
return 1 / (fixedConductance + extra);
|
|
1783
|
+
};
|
|
1784
|
+
// The timing capacitor: exactly one sub-microfarad capacitor off the emitter.
|
|
1785
|
+
const timingCaps = netlist.devices.filter((device) => device.kind === "capacitor" &&
|
|
1786
|
+
device.nodes.length === 2 &&
|
|
1787
|
+
(device.nodes[0] === emitter) !== (device.nodes[1] === emitter) &&
|
|
1788
|
+
(device.parameters.farads ?? 0) > 0 &&
|
|
1789
|
+
(device.parameters.farads ?? 0) < 1e-6);
|
|
1790
|
+
if (timingCaps.length !== 1) {
|
|
1791
|
+
return { outcome: "no-clock-driver" };
|
|
1792
|
+
}
|
|
1793
|
+
const cFarads = timingCaps[0].parameters.farads ?? 0;
|
|
1794
|
+
const capFar = farEnd(timingCaps[0]);
|
|
1795
|
+
if (capFar === GROUND || capFar === emitter) {
|
|
1796
|
+
// A grounded timing capacitor takes its output off B1 instead: a second
|
|
1797
|
+
// form, not implemented. (The corpus form returns through 68 ohms.)
|
|
1798
|
+
return { outcome: "no-clock-driver" };
|
|
1799
|
+
}
|
|
1800
|
+
// AC ground: the far end returns to ground through resistors totalling at most
|
|
1801
|
+
// 1% of the smallest charging resistance, and carries nothing but the timing
|
|
1802
|
+
// capacitor, those returns and capacitors. The 1% is the negligibility bound:
|
|
1803
|
+
// the charge current divides between R_charge and the return, so the rate
|
|
1804
|
+
// error from ignoring the return is under a percent by construction.
|
|
1805
|
+
const returnConductance = netlist.devices
|
|
1806
|
+
.filter((device) => device.kind === "resistor" && isTwoTerminalBetween(device, capFar, GROUND))
|
|
1807
|
+
.reduce((sum, device) => sum + 1 / (device.parameters.ohms ?? 0), 0);
|
|
1808
|
+
const resistances = throwNodes.length > 0
|
|
1809
|
+
? throwNodes.map((_, index) => resistanceAt(index))
|
|
1810
|
+
: [1 / fixedConductance];
|
|
1811
|
+
const minResistance = Math.min(...resistances);
|
|
1812
|
+
const returnOhms = returnConductance > 0 ? 1 / returnConductance : Infinity;
|
|
1813
|
+
if (!(returnOhms <= 0.01 * minResistance)) {
|
|
1814
|
+
return { outcome: "no-clock-driver" };
|
|
1815
|
+
}
|
|
1816
|
+
const strangers = netlist.devices.filter((device) => device !== timingCaps[0] &&
|
|
1817
|
+
device.nodes.includes(capFar) &&
|
|
1818
|
+
!(device.kind === "resistor" ||
|
|
1819
|
+
device.kind === "capacitor"));
|
|
1820
|
+
if (strangers.length > 0) {
|
|
1821
|
+
return { outcome: "no-clock-driver" };
|
|
1822
|
+
}
|
|
1823
|
+
// The divider: a 4013-family part with one half toggle-wired onto the BBD clocks.
|
|
1824
|
+
let dividerClock = null;
|
|
1825
|
+
for (const divider of netlist.devices) {
|
|
1826
|
+
if (divider.kind !== "ic" ||
|
|
1827
|
+
!UJT_DIVIDER_PART_IDS.has(foldToken(divider.identity.partNumber ?? ""))) {
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
for (const half of [1, 2]) {
|
|
1831
|
+
const clock = terminalNodeByRole(divider, `clock${half}`);
|
|
1832
|
+
const data = terminalNodeByRole(divider, `data${half}`);
|
|
1833
|
+
const notQ = terminalNodeByRole(divider, `notq${half}`);
|
|
1834
|
+
const q = terminalNodeByRole(divider, `q${half}`);
|
|
1835
|
+
if (clock === null || data === null || notQ === null || q === null) {
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
if (clock === GROUND || data === GROUND) {
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
// Toggle wiring: D driven by its own half's not-Q.
|
|
1842
|
+
if (data !== notQ) {
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
if (q === notQ || clock === q || clock === notQ) {
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
if (!clockNodes.has(q) || !clockNodes.has(notQ)) {
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
dividerClock = clock;
|
|
1852
|
+
break;
|
|
1853
|
+
}
|
|
1854
|
+
if (dividerClock !== null) {
|
|
1855
|
+
break;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
if (dividerClock === null) {
|
|
1859
|
+
return { outcome: "no-clock-driver" };
|
|
1860
|
+
}
|
|
1861
|
+
const clockNode = dividerClock;
|
|
1862
|
+
// The buffer: exactly one transistor with its collector on the divider clock,
|
|
1863
|
+
// its emitter on the rail and its base coupled from the timing node. Resistor
|
|
1864
|
+
// biasing on either node is allowed; anything active is a different circuit.
|
|
1865
|
+
const buffers = netlist.devices.filter((device) => device.kind === "bjt" &&
|
|
1866
|
+
device !== ujt &&
|
|
1867
|
+
foldToken(device.identity.declaredType ?? "") !== UJT_DECLARED_TYPE &&
|
|
1868
|
+
terminalNodeByRole(device, "collector") === clockNode);
|
|
1869
|
+
if (buffers.length !== 1) {
|
|
1870
|
+
return { outcome: "no-clock-driver" };
|
|
1871
|
+
}
|
|
1872
|
+
const buffer = buffers[0];
|
|
1873
|
+
const bufferEmitter = terminalNodeByRole(buffer, "emitter");
|
|
1874
|
+
const bufferBase = terminalNodeByRole(buffer, "base");
|
|
1875
|
+
if (bufferEmitter === null || !railNodes.has(bufferEmitter)) {
|
|
1876
|
+
return { outcome: "no-clock-driver" };
|
|
1877
|
+
}
|
|
1878
|
+
if (bufferBase === null || bufferBase === GROUND) {
|
|
1879
|
+
return { outcome: "no-clock-driver" };
|
|
1880
|
+
}
|
|
1881
|
+
const coupling = netlist.devices.filter((device) => device.kind === "capacitor" && isTwoTerminalBetween(device, capFar, bufferBase));
|
|
1882
|
+
if (coupling.length !== 1) {
|
|
1883
|
+
return { outcome: "no-clock-driver" };
|
|
1884
|
+
}
|
|
1885
|
+
const activeOnClock = netlist.devices.filter((device) => device !== buffer &&
|
|
1886
|
+
ACTIVE_TIMING_KINDS.has(device.kind) &&
|
|
1887
|
+
device.nodes.includes(clockNode));
|
|
1888
|
+
const activeOnBase = netlist.devices.filter((device) => device !== buffer &&
|
|
1889
|
+
ACTIVE_TIMING_KINDS.has(device.kind) &&
|
|
1890
|
+
device.nodes.includes(bufferBase));
|
|
1891
|
+
if (activeOnClock.length > 0 || activeOnBase.length > 0) {
|
|
1892
|
+
return { outcome: "no-clock-driver" };
|
|
1893
|
+
}
|
|
1894
|
+
const formulaConstant = Math.log(1 / (1 - eta));
|
|
1895
|
+
if (!Number.isFinite(formulaConstant) || formulaConstant <= 0) {
|
|
1896
|
+
return { outcome: "no-clock-driver" };
|
|
1897
|
+
}
|
|
1898
|
+
let controlId = null;
|
|
1899
|
+
let taper = "linear";
|
|
1900
|
+
let ohmsAtControlMin = resistances[0];
|
|
1901
|
+
let ohmsAtControlMax = resistances[0];
|
|
1902
|
+
let rOhms = resistances[0];
|
|
1903
|
+
// `ohmsAtControlMin/Max` are position-indexed, not sorted: the resistance with
|
|
1904
|
+
// the control at its minimum (position 0, first throw) and at its maximum.
|
|
1905
|
+
// The runtime lerps between them by control position, so a selector whose open
|
|
1906
|
+
// position is the larger resistance keeps Min > Max and still evaluates both
|
|
1907
|
+
// positions exactly. Buffer sizing takes the larger of the two.
|
|
1908
|
+
if (selector !== null) {
|
|
1909
|
+
const control = selector.control !== null
|
|
1910
|
+
? netlist.controls.find((entry) => entry.id === selector.control)
|
|
1911
|
+
: undefined;
|
|
1912
|
+
if (control !== undefined) {
|
|
1913
|
+
controlId = control.id;
|
|
1914
|
+
taper = control.taper;
|
|
1915
|
+
ohmsAtControlMin = resistances[0];
|
|
1916
|
+
ohmsAtControlMax = resistances[throwNodes.length - 1];
|
|
1917
|
+
const position = Math.min(1, Math.max(0, control.defaultPosition));
|
|
1918
|
+
const selected = Math.min(throwNodes.length - 1, Math.max(0, Math.floor(position * throwNodes.length)));
|
|
1919
|
+
rOhms = resistanceAt(selected);
|
|
1920
|
+
}
|
|
1921
|
+
else {
|
|
1922
|
+
// A fixed selector states one position: derive it statically. Same
|
|
1923
|
+
// position rule as `lower.ts` (`parameters.position ?? 1`).
|
|
1924
|
+
const selected = Math.min(throwNodes.length - 1, Math.max(0, Math.floor((selector.parameters.position ?? 1) * throwNodes.length)));
|
|
1925
|
+
rOhms = resistanceAt(selected);
|
|
1926
|
+
ohmsAtControlMin = rOhms;
|
|
1927
|
+
ohmsAtControlMax = rOhms;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
if (!(rOhms > 0) || !Number.isFinite(rOhms) || !(cFarads > 0)) {
|
|
1931
|
+
return { outcome: "no-clock-driver" };
|
|
1932
|
+
}
|
|
1933
|
+
const fUjtHz = 1 / (rOhms * cFarads * formulaConstant);
|
|
1934
|
+
const fClockHz = fUjtHz / 2;
|
|
1935
|
+
const implausible = implausibleClock(fClockHz, stages);
|
|
1936
|
+
if (implausible !== null) {
|
|
1937
|
+
return { outcome: "refused", reason: implausible };
|
|
1938
|
+
}
|
|
1939
|
+
const delaySeconds = stages / fUjtHz;
|
|
1940
|
+
if (!Number.isFinite(delaySeconds) || !(delaySeconds > 0)) {
|
|
1941
|
+
return { outcome: "no-clock-driver" };
|
|
1942
|
+
}
|
|
1943
|
+
return {
|
|
1944
|
+
outcome: "derived",
|
|
1945
|
+
delaySeconds,
|
|
1946
|
+
rOhms,
|
|
1947
|
+
cFarads,
|
|
1948
|
+
fClockHz,
|
|
1949
|
+
family: "unknown",
|
|
1950
|
+
stages,
|
|
1951
|
+
formulaConstant,
|
|
1952
|
+
offsetSeconds: 0,
|
|
1953
|
+
controlId,
|
|
1954
|
+
taper,
|
|
1955
|
+
ohmsAtControlMin,
|
|
1956
|
+
ohmsAtControlMax,
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1582
1959
|
/**
|
|
1583
1960
|
* Princeton Technology PT2399 delay time from external VCO resistance R (in Ohms).
|
|
1584
1961
|
*
|