Repository navigation
Releases: devkapa/brownout
Release list
v0.6.5
Patch Changes
- 2292281: A chip still waiting for its supply return no longer joins its island to the battery's section through its own power pins. A floating island it straddles, such as a breadboard rail cut off from the battery, is now an isolated section like any other, with its own 1 S anchor at one of its nets, instead of resting on the matrix's 1e-12 S node shunts alone. That is what stopped the clock: cutting a supply lead or pulling a ground wire while such an island held charge left it with no usable reference, and no step converged at any size. Voltages on such an island now read from its anchor, so they shift by a common offset. The differences across its parts, and so its currents, are as before, to within the shunts' picoamps; an LED on it still lights from the charge it holds. The one exception is the instant a cut strands a current-carrying inductor on the island: its kick is placed relative to the anchor. In the test circuit random-6087, for the first 25 ns one LED carries 402 mA where both carried 54 mA, after which both runs agree.
v0.6.4
Patch Changes
- ecdcede: A 74HC595's QH' pin (pin 9) now carries the shift register's eighth stage, so daisy-chained chips receive what is shifted into them. The model never drove the pin: it sat at 0 V, and a second 595 wired behind it (U1 QH' to U2 SER, shared clocks) shifted in nothing — after eleven clocks with SER held high the first chip's register held 0xFF while the second stayed empty and its LEDs stayed dark. On the real part QH' branches off the eighth shift-register stage before the storage register and the /OE gate (TI SN74HC595 data sheet, SCLS041J, section 5 pin functions and the section 8.2 functional block diagram; the Table 8-1 function table gates only "Outputs QA – QH" with OE), so it follows SRCLK alone: it does not wait for RCLK, it stays driven while /OE blanks the eight outputs, and with SRCLK and RCLK tied it does not pick up the one-clock lag the storage register does (section 8.1). The pin is stamped, published to the digital state and small-signal stamped like the eight outputs, from the shift register the MCU edge replay leaves behind, so an Arduino-driven chain fills as well: with 0xB2 then 0x4C shifted in MSB first, the near chip ends up showing 0x4C and the far chip 0xB2. One boundary stays: a pin driven by another chip is still read once per step, so the clocks must land one MCU step apart for the far chip to see QH' move between them — the replay only paces the microcontroller's own pulses.
- 126e80a: An HD44780 LCD or a MAX7219 matrix driven by an Arduino now counts each of the program's pin changes once. The display decoders replay the microcontroller's ordered pin events from its step, and two holes in that replay are closed. When an NE555 in the circuit makes one step solve three times (a rolled-back trial plus the two halves it splits into), the decoders re-merged the same events on every pass: a program clocking 16 bits into a MAX7219 counted 32, and an LCD's init nibbles latched twice, garbling its message. The merged events now ride the same once-per-step lifecycle as the clocked-IC edge path: the microcontroller's advance marks them owed, the first completed pass consumes them, and the split's remaining passes read the solved pin levels, which stand where the program left them. And a microcontroller that loses power stops stepping, while the only place its event list was ever cleared was inside its own step(), so its last powered step's events re-replayed on every later step and a display's bit count grew without bound after an unplug (32, 48, 64, 80, ...). The engine now clears a skipped core's events at the moment it decides the core did not step, so its stale edges are owed to nobody; the clocks a program made while its board still had power are counted, exactly once. Displays driven by a Raspberry Pi Pico never took the merged-event path (it stays Uno/Nano only), so they decode as before; a Pico that loses power has its stale events cleared the same way. Circuits without a display, and displays driven without a microcontroller, are unchanged.
- 3d1c4c5: A program that reads a shift register between its own clock pulses now sees the bits it shifted. A
shiftIn()from a 74HC165 clocks the chip and reads its Q7 pin inside one microcontroller step, but the register only advanced in the digital pass at the end of the step, so every read returned the level the previous step's solve sampled: a sketch that loaded 0xB2 and read it MSB first got 0xFF at 100 µs steps (every read saw the same stale level), and different garbage at other step sizes. While the same single Arduino or Pico both drives a 74HC165's inputs and reads its outputs, the engine now runs a digital shadow of the chip inside the microcontroller's step: each GPIO write the core captures is fed through the same replay walker the committed pass uses (grouped into writes exactly as the committed pass groups them, by cycle), and the chip's output levels are pushed back into the core's input path, where the nextdigitalRead()orPin.value()finds them — on the AVR through the existing port write listeners and the PIN register path, with no new hooks in avr8js, and on the RP2040 through its GPIO input value. The levels the solve stamps are one replay behind the register, so they are also pushed once at the start of each step: a read before any write in a step sees the register as it stands, not as the last solve stamped it. The committed digital pass remains the state of record; the shadow is thrown away with the step. Only the 74HC165 is shadowed for now — other registered clocked parts (the 74HC595, counters, flip-flops) are write-only from a program's point of view and keep their committed behaviour — and no shadow is installed when the chip's outputs do not reach the microcontroller, when two microcontrollers share any of its nets, or when there is no microcontroller at all: those circuits, including an Arduino with no clocked part, hash byte-identically to before. One measured caveat: a shadow applies the walker's own-output feedback (Q7 wired to DS rotates the register), but its effect cannot be observed mid-step at legal step sizes, because a fed-back bit needs eight shifts of the same register to reach Q7 mid-step; a shadow that spans fewer clocks shows the feedback only through the next step's committed register, which replays it intact. Two boundaries, both inherited from the once-per-step replay and measured: a read AFTER a clock's rising edge but before its fall still sees the pre-rise level (the writes of one instruction are applied together, as on hardware they happen at once — a read between a rise and its fall, which real code can do, is one convention off); and a DAISY-CHAINED pair of 165s (Q7 into the far chip's DS) does not track the near chip's output mid-step — the far chip's serial input is sampled at the step's starting solve, so a chained shiftIn reads the near chip's byte one step late. A single chip's parallel loads and reads are exact. - 5b43ffb: Chips clocked by an Arduino or a Raspberry Pi Pico now see every pulse the program makes. The simulation hands a microcontroller's pins to the rest of the circuit once per step, as they stand at the end of a step of up to 100 µs, so a pulse that started and ended inside one step never reached a chip's clock input. An Arduino
shiftOut()into a 74HC595 makes each clock pulse about 4 µs wide, so its eight clock pulses and the latch pulse after them all fit inside one step: the classic shiftOut example kept its LEDs dark and showed its byte only at steps of 2 µs or less. A MicroPython bit-bang on a Pico lost most of its pulses the same way, and a counter or flip-flop clocked with two back-to-backdigitalWrite()calls never counted. The 74HC595, 74HC165, 74HC74, CD4017, 74LS161 and 74LS173 now replay every edge the microcontroller made on their inputs during the step, in the order the program made them, before they read their pins at the end of the step, soshiftOut(dataPin, clockPin, MSBFIRST, 0xB2)and a latch pulse show 0xB2 at any step size. Two pins changed by one instruction (one port write) change together, as on hardware, and a register clocked by that write samples the levels from before it: RCLK rising in the same write as SRCLK stores the 595's byte from before that shift, as tied clocks do, and RCLK raised by a later instruction stores the shifted byte. An input wired to the chip's own output follows it during the replay, so a CD4017 with RESET on Q5 still wraps at five and a 74HC74 with D on /Q toggles on every pulse. Pins driven by anything else, such as a switch, a clock source or another chip's output, are read at the end of the step as before, so a chip clocked or fed by another chip still sees at most one change per step, and circuits without a microcontroller are unchanged. A program that reads a chip's output in the same step it clocks it, such asshiftIn()from a 74HC165, still reads the output from the end of the previous step, and a micro:bit still reaches these chips only through its pin levels at each step, because its program runs outside the simulation and sends no edge timing. - 574d751: A servo or an HC-SR04 triggered by an Arduino now counts each of the program's pin changes once. Both devices time their input from the microcontroller's ordered pin events, and they read that list once per pass, so when an NE555 in the circuit makes one step solve three times (a rolled-back trial plus the two halves it splits into), every step's events reached them three times. The HC-SR04 showed it: a board's first port write leaves a low-then-high pair of first-seen levels on the TRIG pin, one feed of which merely arms the sensor, but the second feed read the pair's low as the matching fall and scheduled an echo 250 µs after boot — a full phantom ECHO pulse nearly 13 ms before the first real measurement. A servo's decode happened to hold its numbers (a re-fed pulse re-measures the same cycle-timestamped width, and the boot pair's fall lands before its rise, which the width check rejects), but its feed tripled all the same. The raw event read now rides the same once-per-step lifecycle as the clocked-IC and display paths: the microcontroller's advance marks the events owed, the first completed pass hands them out, and the split's remaining passes are told nothing happened. Servos and HC-SR04s driven by a Raspberry Pi Pico take this path too (the display fix's merged events stayed Uno and Nano; this read never did), so they get the same guarantee. A board that loses power was already fenced for these devices and is unchanged, as are circuits without a servo or an HC-SR04.
v0.6.3
Patch Changes
-
14b2a07: A 74HC595 with SRCLK and RCLK wired together now runs its outputs one clock behind its shift register, as the real chip does. With the clocks tied, one rising edge clocks both registers, and the storage register samples the shift register before that edge's shift reaches it. TI's data sheet (SN74HC595, SCLS041J, section 8.1) says that with both clocks connected together "the shift register always is one clock pulse ahead of the storage register." The model stored the bit the edge had just shifted in, so the outputs ran one clock ahead of a real board: with the model's storage register starting empty, the first clock after load lit QA when SER was high, and now the outputs stay dark until the second clock. The two tied pins are now read as one input, so a slow edge that sits between the input thresholds for a step (an RC-debounced button, say) clocks both registers in the same step instead of shifting first and latching later. A /SRCLR clear seen in the same step as a tied edge still stores zeros, as it does with separate clocks. SRCLK and RCLK on separate nets are unchanged, including when both rise in one step: a latch pulse just after the last shift stores the shifted byte, as it does on hardware.
-
4878620: A ferrite bead can now fail from overload. Its catalog entry carries a power rating (
p_max) that the engine never read, so a bead carried any current for as long as the circuit ran: 10 A through the old 0.5 ohm default, 50 W against a 0.25 W rating, never failed it. A bead now dissipates its current squared times the resistance it conducts with (rDc; the catalog's 3.5 mohm for a bead that has none, and never below 1 mohm) and is rated like a resistor: abovep_maxthe damage grows by (power /p_max- 1) per second, it bleeds off at 1 per second inside the rating, and the bead fails open when it reaches 1. A bead dissipating 1.2 times its power rating therefore lasts 5 s. The failure is the existingresistor_overloadwith the resistor's message, so a host that reports a failed resistor reports a failed bead without a change. A failed bead is open in the transient, operating-point and small-signal analyses untilresetFailures(). A bead with its ownrDcinside its rating is unchanged, and ap_maxof 0 or less, or none, still means no limit. Only the DC resistance is rated: the model has no frequency-dependent impedance, so it has no high-frequency heating.The catalog's bead data is corrected as well. The default DC resistance, the catalog's and the engine's for a bead with no
rDc, was 0.5 ohm and is now 3.5 mohm, the resistance of the 22 AWG lead on the Fair-Rite 2743001111 the part is modelled on, so a default bead in series now drops about 1/143 of the voltage it did. Fair-Rite controls this bead for impedance only and gives it no current rating, so the catalog's 2 A is its own assumption, unverified, andp_maxis now 0.056 W, the power at twice that current ((2 x 2 A)^2 x 3.5 mohm). A bead at 2 A never fails and the default bead fails above 4 A DC: 5 A (1.56 times the power rating) takes 1.8 s and 10 A takes 0.19 s. A bead saved with its ownrDc, the old 0.5 ohm for one, keeps it and gets the same 0.056 W rating, so it fails above about 0.335 A. -
685179c:
HeadlessRunnerno longer steps over a source's edges and short pulses. It limited its step size by each source's frequency but did not choose where steps fall, and the engine reads a source at the end of each step, so a step that crossed an edge applied the new level for the whole step, and a feature shorter than a step could be missed entirely. A PWL source has no step limit, so a 0.2 ms pulse at 3 ms never showed in a 20 ms run (22 steps, up to 8.2 ms each), and neither did the 21 us pulse of a one-shotpulse_source(per0). A 20 us pulse every 1 ms, from asignal_genor apulse_gen, showed only in the first period: 11.2 us HIGH over 20 ms instead of 420 us. The runner now places steps the way de:volt's browser simulator does. It lands a step 10 ns before each source event (every edge and corner of aclock,clock_gen,pulse_source,pulse_genorsignal_gen, every PWL point, every 0.1 ms noise sample, and the start of a delayed waveform), then crosses the event in a 10 ns step. Pulses down to about 10 ns wide now show (the 20 us pulse reads 419.6 us HIGH), and a new level arrives at most 15 ns early. Landing on an event costs up to two extra steps (a 1 kHz square or clock into a resistor takes 254 steps over 20 ms instead of 214, a noise generator 613 instead of 413), and a circuit that now sees pulses it used to miss takes the steps its response needs: a 1 kohm, 100 nF RC fed the 20 us pulses takes 2043 instead of 489. Fixed-step runs (adaptive: false) take the same short steps at each event, so their samples are no longer evenly spaced around one: a 1 kHz clock at 10 us steps takes a 9.99 us and a 10 ns step at each edge. A circuit with none of these sources steps exactly as before, as does a sine, triangle or DCsignal_genthat starts at t = 0. -
a1413c1: A logic output that drives only capacitors no longer reports a false
output_sagon its edges. The check confirmed a sag one solve after the output switched, and the simulation's step lags a fast RC edge there: a 74LS04 driving 30 pF is back inside its LOW band 8.6 ns after a falling edge, but a 10 ns step still left the pin at 1.6 V against the 0.8 V limit, so every falling edge raised a sag; driving 100 pF, every rising edge did the same whenever the step was 10 ns. Where the output's net is provably a single RC stage, a sag now counts only if an upper bound of how long it really lasts reaches 10 ns. The bound comes from the output resistance, the capacitance and the capacitor's ESR and leakage, and it starts from the farthest point the edge could have begun at: the capacitor's own state when the solve that first sees the sag began, or the farthest rail or far end. That covers capacitors placed from the catalog with their default ESR and leakage, outputs switched on by an enable, and capacitors still charged from before a reload. A net is a single stage when a 74-series, CD4017, CD4511, CD4060 or 28C-memory output, on supplies from ideal voltage sources, drives nothing but other logic inputs and either one capacitor or several without ESR, each returned to ground, a supply or another fixed voltage, and the simulation uses backward Euler, its default. Every other output keeps the old two-step check, since a series resistor, a pull-up, a second driver or a second capacitor with ESR can make an edge last longer than the bound assumes; a sag shorter than 10 ns can still be reported there, as before. Any sag the bound does not rule out is judged by the old check exactly as before. A sag that ends within a single simulation step is still not seen. One case is not covered: a direct caller that changes a source voltage, or a capacitor's ESR or leakage, between steps withoutload()while a sag is already under way, where that change is what stretches the sag past 10 ns. -
8024715: Switching off a 555 blinker's power no longer freezes the simulation. On a 9 V battery, opening a power switch in either battery lead, or pulling the chip's ground wire, while the timing capacitor held more than about 4 V stalled it: the status showed SETTLING with fail=1/1 and the clock stood still until the power came back. That capacitor and the two timing resistors were all that still fed the 555, and they could not carry its supply current. Switched on, the chip pulled its own supply below its 4 V turn-on threshold and starved itself; switched off, the capacitor lifted the supply back above the threshold, and the solver never settled between the two.
HeadlessRunnermet the same dead end when a 555 ran on a decoupling capacitor after its rail's bridge to the battery was removed, and stopped about 17 ms later as the capacitor drained through the threshold. Now, when a step fails while a chip's power flips like that, the engine checks, with every capacitor holding its present charge, whether the chip could keep its supply switched on and whether the supply comes back with the chip switched off. A chip that starves itself that way counts as off for that step, and the clock keeps running. A chip whose supply is still rising through its turn-on threshold is not starving itself: that step is rejected exactly as before, so the shorter step that follows turns the chip on at the same moment as before. The exception is a step already at the 10 ns minimum step size, where no shorter step is left: there the chip is held off for that one 10 ns step instead of the clock stopping, and turns on as soon as its supply can carry it. Run in 0.5 ms pieces,HeadlessRunnerused to stop 6 ms into powering up a 74LS04 from a 9 V battery through 100 Ω onto a 100 µF rail; it now runs on, and the chip lights its LEDs about 12 µs after its rail reaches the threshold. A step the check cannot settle is rejected exactly as before too, so a smaller step can still find the answer, and circuits that never hit this case are bit-identical. This covers the same chips as the unconnected-rail fix: the 555, 74xx and CD4000 logic, the 28C16 and 28C256 EEPROMs, the HD44780 and MAX7219 displays and the LM393. -
5657a9a: A
signal_genwhosewaveformis not one of the eight names the engine knows now gets a sine's step limit inHeadlessRunner. The engine plays any other value as a sine ("Sine","SQUARE", a typo, a number), but the runner picked the step limit from the raw string, so those sines got none:"Sine"at 1 kHz ran 22 steps over 20 ms, up to 8.2 ms each, and read above mid-scale for 1.1 ms instead of 10 ms. The runner now reads the waveform through the engine's own parse (parseSignalGenParams), so such a generator steps exactly like"sine": 812 steps, 25 us at most. The eight recognised names step exactly as before.
v0.6.2
Patch Changes
-
119fa20: A chip whose ground is wired to a breadboard rail that leads nowhere no longer freezes the simulation. Wiring a 555 blinker grounds-first, with pin 1, the timing capacitor and the LED's cathode on a bottom rail not yet joined to the battery, stalled it: the status showed SETTLING with fail=1/1 and the clock stood still until the rail was bridged. The 555's supply current had no way back to the battery except through the 555 itself, so it could never stay powered. Switched on, it pulled the loose rail up to its own supply and starved itself; switched off, the rail fell back to 0 V and powered it again, and the solver never settled between the two. A chip whose supply has no return path outside it now counts as unpowered for as long as it is wired that way: it and anything it drives stay off, the clock keeps running, and the circuit starts the moment the rail is connected. This covers the 555, 74xx and CD4000 logic, the 28C16 and 28C256 EEPROMs, the HD44780 and MAX7219 displays and the LM393, including several of them sharing the loose rail. It does not yet cover a rail that reaches the battery only through a part that cannot carry the chip's supply current, such as a capacitor or an open switch: switching a running 9 V blinker's power off, or pulling its ground wire, can still freeze it, and a separate fix follows. A logic chip wired that way could also read as powered before, its outputs showing levels while no current could flow; it now reads unpowered, like one whose ground pin is not wired at all. Circuits in which every chip's supply is connected are bit-identical.
-
f1dca71: A circuit whose supply drops to 0 V no longer stops converging for good as it settles. Switch an LM317 driving an LED to a 0 V supply and the voltages it has left shrink about 2e14-fold per step; some 20 steps later they are below 1e-308, where floating-point numbers lose their relative precision. The check that each linear solve is accurate compared the solve's error with the size of its solution, and at that size a single rounding step read as an error of 5e-6, over the 1e-8 a step may have, so the step was rejected. A rejected step changes nothing, so every later step met the same state and was rejected too: the simulation stalled with the solver reporting non-convergence, for 16 of 46 load resistors between 50 and 500 ohm. That count is for the solver small circuits use; the one larger circuits use sums the error in another order and got all 46 right, but stalls the same way at other values. The check now compares the error with a scale of at least 1e-300, so those steps pass and the voltages settle at exactly 0 V. Any solve with larger values, which is any circuit carrying a measurable voltage or current, is checked exactly as before, and singular or ill-conditioned circuits are reported as before.
-
b872148: A current-limited bench supply now holds its limit cleanly in two cases where it did not.
A supply only slightly overloaded crawled. It left constant-current mode as soon as its rail climbed past 98% of the setpoint, so a load drawing just over the limit, whose current-limited rail sits between 98% and 100% of the setpoint (5 V with a 0.1 A limit into 49.9 ohm holds 4.99 V), left CC after every step and re-entered it inside the next one. The readings were right, but the supply's CV/CC state flickered on every step and the adaptive stepper rejected every other step as a regime change, holding the step at 10-15 ns: 50 ms of that circuit took at least 4 million accepted steps, and now takes 25. A load drawing exactly the limit (5 V and 0.1 A into 50 ohm) flickered and crawled the same way.
A supply with a capacitor across its output delivered past its limit. Settled at 5 V with 100 uF on its rail, a supply whose load stepped to 20% over a 0.1 A limit stayed at exactly 5 V and delivered 0.12 A for as long as the simulation ran. Within each step the supply checks whether its current-limited output comes back at the setpoint, so that a supply whose load is removed holds its setpoint instead of pushing the limit current into nothing. That check accepted anything within a few microvolts under the setpoint, and the capacitor lets an overloaded rail fall only a few microvolts per step at fine steps, so the supply never left CV.
The supply now leaves CC exactly when its current-limited output reaches the setpoint, and is back in CV in that same step; for a resistive load that is when its draw at the setpoint is back within the limit. An overloaded supply delivers the limit from the first step, a capacitor-backed rail sags toward the current-limited voltage as the capacitor discharges, and a supply whose load is removed still holds its setpoint from the next step. A supply within its limit, including a cold start whose inrush stays under it, never enters CC. Negative setpoints behave the same way, mirrored.
DeviceContext.useCurrentLimitComplianceClamptakes a new optional fourth argument,toleranceBelow, how far below the compliance voltage a candidate still counts as reaching it; left out, it is the existing band of 1e-9 + 1e-6 * max(1, V), so custom devices that call it are unchanged. -
f198ed9: A current-limited regulator (the DC-DC converter, a fixed linear regulator such as the 7805, or the LM317) now holds its limit cleanly in two cases where it did not.
A regulator only slightly overloaded crawled. The DC-DC converter left its limit as soon as its output climbed past 98% of the voltage it can hold, a fixed linear regulator as soon as its output came within 50 mV of the setpoint, and an LM317 as soon as its output-to-ADJ voltage came within 50 mV of its 1.25 V reference, about 200 mV of output with a 240/720 ohm divider. So a load drawing just over the limit, whose limited output sits in that margin (a 5 V, 1 A 7805 into 4.97 ohm holds 4.97 V), left the limit after every step and re-entered it inside the next one. A fixed linear regulator in dropout did the same at any overload, because every current-limited one short of input headroom was switched to dropout whatever its load. An LM317 measures that headroom from its ADJ pin, which falls as its output sags, so in dropout it did the same only while slightly overloaded: at 6.5 V in with a 240/720 ohm divider, up to about 3.7% over. The readings were right, 4.97 V at 1 A, but the regulator's state flickered on every step and the adaptive stepper rejected one attempted step in three to five as a regime change, so the simulation crawled at about 12 ns steps: 20,000 steps covered at most 0.2 ms, where 50 ms now takes 25 steps.
A regulator with a capacitor on its output delivered past its limit. Settled at 5 V with 100 uF on its output, a 1 A regulator whose load stepped to 0.2% over the limit stayed at exactly 5 V and delivered 1.002 A, and an LM317 at 4.97 V with a 0.1 A limit delivered 0.121 A when its load stepped to 21% over, with the state flickering on every step and the adaptive stepper stuck at its first, finest steps. Within each step the regulator checks whether its current-limited output comes back at the voltage it can hold, so that one whose load is removed holds its setpoint instead of pushing the limit current into nothing. That check accepted anything within a few microvolts under that voltage, and at fine steps the capacitor lets an overloaded output fall only a fraction of a microvolt per step, so the regulator never left regulation. Loads a few parts per million over the limit flickered the same way.
Each regulator now leaves its limit exactly when its output reaches the voltage it can hold, the setpoint or the dropout ceiling, and stays in current limit until then; for a resistive load that is when the load's draw there is back within the limit. An overloaded regulator delivers its limit from the first step, a capacitor on its output discharges toward the current-limited voltage, and a regulator whose load is removed still holds its setpoint from the next step. A regulator within its limit is unchanged, and one charging a capacitor through a resistor still leaves current limit when the charging current falls to the limit, on the same trajectory.
v0.6.1
Patch Changes
- d1dee17: A 74LS47 segment that turns off no longer keeps the
output_sagit raised while it was lit. The segments are open-collector outputs, so only a segment that is sinking can sag; once the decoder releases it the load alone sets its voltage and the sag no longer applies. The scan stopped reporting the released segment, but the clean-up that drops the old record skipped open-collector pins, so a segment overloaded on one digit (a segment driven with too little series resistance, say) kept its sag on every later digit that leaves it dark, through reloads, until failures were cleared. The clean-up now covers open-collector pins, so the sag clears on the next step after the segment is released. An LM393's released output clears the same way. - efceffb: A part's thermal warnings now include its profile's assumptions from the moment the circuit loads. Every step publishes the profile's own caveats (the first-order thermal model, a headline rating that needs a heatsink, an assumed shutdown restart point) together with that step's derating warnings, but a part that starts cold on
load()carried only the derating warnings. The profile's caveats showed up only once the 1 ps operating-point solve at the end ofload()had converged, so on a circuit whose seed solve is rejectedgetThermalStatereported none of them until the first accepted step. The cold start now publishes the same set. - 7b1056d: A digital output no longer reports a false
output_sagwhen a one-step glitch flips it. The debounce counted consecutive sagging steps whatever the commanded direction, so a glitch on a decoder output read "commanded LOW, pin still at 5 V" and the very next step read "commanded HIGH, pin still at the glitch's low level". Those two observations reached the two-step threshold and latched a sag that no real load caused. A breadboard computer's display board did it: a 74HC74 ripple counter feeding a 74HC138 digit select shows the intermediate address for one solve on 01 to 10, and the lit digit's line raised a falseoutput_sagabout once every 70 ms. The count now remembers the commanded direction and restarts at 1 when it flips. A real sag, a HIGH output dragged low or a LOW output dragged high, still latches on its second step. - fe9b38a: A resistor with no
resistanceparam can now overload, and a fuse with noiRatingparam can now trip. The engine conducted it as 1 kohm, its fallback for a missing value, but the overload check read the missing value as 0 ohm and so as 0 W: 50 V across it dissipated 2.5 W through the 0.25 W default rating and it never failed open. It now uses the same 1 kohm, so it latchesresistor_overloadin about 0.1 s at that power. A fuse with noiRatingread as 0 A and never tripped; it now uses the catalog's 1 A default. Parts that set the param are unchanged, and an explicitiRatingof 0 still means no limit.
v0.6.0
Minor Changes
-
2c6ba7d:
current_sourcenow drives its programmed current out of itspospin, through the external circuit and back intoneg, like a battery's+. It used the SPICE I-element's naming, where the current flows from n+ THROUGH the source to n-, so it entered atposand left byneg. A part labelled+(de:volt's Current Source) therefore ran backwards: 1 mA into 220 ohm from+to-read -220 mV across the resistor instead of +220 mV. The transient stamp, the published element current (pin0 -> pin1, so a sourcing current_source now reads-I, plus its parallel shunt's current) and the small-signal input injection all flip together. SPICE decks keep their meaning: the parser attaches an I card's n+ tonegand its n- topos, soI1 0 out 1mstill liftsoutto +1 V across 1k, andrunSpice's.opreports an I card's element current in its own n+ -> n- order, equal to its value, as before. A hand-builtSimCircuitthat wiredcurrent_sourcein the SPICE sense must swap its two pins. -
bd90847: Circuits with an electrically isolated section now solve. A second battery's loop, an optocoupler's LED side, a transformer's secondary or a relay coil on its own supply has no path to ground, so its potential rested on the 1e-12 S node shunt alone and the matrix was singular to rounding. Once a closed switch stamped 1000 S into such a section, LU noise moved the whole section by tenths of a volt between Newton iterates; on a 4N35 board whose LED ran from a second battery, every step was rejected for as long as the button was held. Each isolated section now gets its own reference, a 1 S tie to ground from one node (a source's negative terminal where it has one), which carries only the shunts' ~1e-11 A. The section's voltages read against that terminal instead of averaging 0 V. Device models declare galvanically separate pins through the new optional
DeviceModel.galvanicGroupshook; the optocoupler, coupled inductor and relay do. Circuits in which every net has a path to ground are bit-identical. -
df16d52: A passive buzzer now detects its tone from a 0-5 V drive. The detector counted sign changes of the terminal voltage around 0 V, so the square wave the part's own help recommends (a
clock_genor a PWM pin, which never goes negative) never registered:detectedHzstayed 0 andsoundingstayed 0. Crossings are now taken around the drive's own midpoint, from an envelope that follows each new extreme and relaxes over 0.1 s, with a Schmitt band of 10% of the swing. The frequency is timed over a full period (two crossings in the same direction), so a PWM pin at 25% duty reads its drive frequency rather than alternating between two wrong ones. Sine and bipolar drives still read the same frequency.The readout also follows the drive now. A tone clears two periods after the last crossing, so a buzzer whose drive stops falls silent. Before, the last frequency latched forever. A reading counts only inside the audible band (20 Hz to 20 kHz, previously 1 Hz to 100 kHz), so one press of a button across a passive buzzer is two clicks rather than a 2 Hz tone. Swings under 50 mV peak-to-peak (the speaker's signal-present floor) are silence.
The envelope (
envHi/envLoin the buzzer's IC state) is skipped by the step-error estimate. It is readout-only and moves on every step by an amount that depends on h, so comparing it would let the readout shrink the step. The crossing times are still compared.Physics telemetry reports the state as
modelState:{ kind: "buzzer", buzzerType, sounding, toneHz? }, withtoneHzpresent only while a passive buzzer detects a tone, and{ kind: "speaker", sounding }. Both arebehavioral-estimatequality, read throughengine.getIcState. -
990232e: Potentiometer and trimmer
positionis now shaft rotation from the counter-clockwise stop (0) to the clockwise stop (1): position 1 puts the wiper at thecwterminal, as on a real pot turned fully clockwise. It used to put the wiper atccw, so a knob turned clockwise moved the wiper away from the CW pin. The audio (log) taper now grows the wiper-to-CCW resistance with rotation, so a volume control wired CCW to ground gives a tenth of its input at mid-rotation and rises steeply toward the CW stop. This inverts every saved position other than 0.5 (linear): to keep a circuit's wiper where it was, save1 - position(linear taper). Linear pots at 0.5 are bit-identical.A pot or trimmer no longer fails open when its wiper carries an ordinary load current near or at a stop. Each track segment's share of
p_maxstill scales with its length but is now floored at a quarter of the rating (POT_SEGMENT_MIN_RATING_SHARE), since a short segment shares its heat with the substrate and the track beside it. Before, the share fell to zero at a stop, and in the last few percent of travel the rule amounted to a 4.5 mA wiper limit for a 10 kΩ 0.2 W pot. A 5 V pot driving a red LED through 220 Ω failed at the stop with "dissipated 0.000 W ... above that segment's 0.000 W share", and also at 1-2% of travel (5% for the 0.1 W trimmer). That board now runs at every position. A rheostat overloading a short segment, or a wiper shorted across the supply at a stop, still fails. The failure check also rates the track resistance the simulation uses (rTotal, default 10 kΩ); before, it read aresistanceparam that the stamp ignored.
Patch Changes
- bc1cfd6: A Pico sleeping in MicroPython now costs the host almost nothing. rp2040js's
USBCDCanswered every read the firmware armed on the serial OUT endpoint with an empty packet 10 µs later. TinyUSB re-armed after each one, so a program intime.sleep()took about 35,700 USB interrupts a second and its core was busy about 90% of the time: the default Blink ran at 0.93x real time and every Pico project read SLOW.RP2040Mcunow holds an armed read until it has REPL bytes to send, the way a real host does. Blink now costs about 4 ms of CPU per simulated second instead of 1,078 ms, with its pins toggling at the same times. Busy-loop programs, such as polling a button with no sleep, still keep the core fully busy, as they would on hardware. - b70be41:
RP2040Mcuno longer floods the console. rp2040js defaults to a Debug-levelConsoleLogger, so every USB transfer, SEV and unimplemented peripheral access was printed: a Pico booting MicroPython logged 2,000+ lines in its first seconds, and in de:volt the worker fell behind real time and the page stopped responding. The core now logs errors only, set on every boot includingreset(). Errors still print and throw as before. Arduino (avr8js) never logged, so it is unchanged. - 3497700:
RP2040Mcu.setAnalogVoltsnow converts the pin voltage to 12-bit ADC counts. rp2040js readsadc.channelValuesas raw conversion results (0 to 4095 for 0 V to ADC_VREF), but the core wrote volts there, so 3.3 V on GP26 read as 3 counts and MicroPython'sADC(Pin(26)).read_u16()returned 48 instead of 65535. The voltage is now clamped to 0..vccand scaled toround(volts / vcc * 4095), withvccthe Pico's 3V3 rail that the engine already passes (on the Pico, ADC_VREF is that rail filtered). This covers ADC0 to ADC2 (GP26 to GP28). - fccd8bd:
RP2040Mcunow holds the on-die temperature sensor (ADC4) at 27 °C. The engine never fed that channel and rp2040js starts it at 0 counts, so MicroPython'sADC(4).read_u16()returned 0 and the datasheet formula27 - (V - 0.706) / 0.001721gave 437 °C. Each boot, includingreset(), now seeds 876 counts (the datasheet's 0.706 V at 27 °C against 3.3 V), which reads back as 27.04 °C. - a05089b: A MicroPython program longer than about 510 bytes now runs on the Pico.
RP2040Mcufed the whole program, plus the Ctrl-D that runs it, into rp2040js'sUSBCDCtx FIFO in one go. That FIFO holds 512 bytes and silently drops the rest, so the tail and the Ctrl-D were lost and the program never started. The REPL feed now keeps its own unbounded queue and moves the next chunk into the FIFO each time the firmware arms a read, so programs of any length arrive whole.
v0.5.0
Minor Changes
- e27396e: Faster transient stepping, 1.2-2.9x in de:volt's live worker on real boards (a 50-unknown clock module 2.9x, a mixed-signal lab 2.8x, a logic lab 2.2x), with the same solve counts. Most of the time was bookkeeping around the physics rather than the physics. Pin-to-node lookups no longer build a composite string key per access. Catalog inference caches each kind's discriminating keys and each component's explicit resolution. The floating-input noise is memoised per pin and 10 ms bucket, and delayed-output and failure keys per pin. State snapshots clone their maps without intermediate arrays, and
_hasFailurereturns at once when nothing has failed. These are bit-identical. The sparse linear backend now engages from 16 unknowns instead of 64: stepping real boards, sparse was never slower from 6 unknowns up and 1.1-1.9x faster from 16. Circuits with 16-63 unknowns therefore round differently (1e-14 or better on the fixtures, identical logic-edge times); a circuit whose accept/reject decision tips on roundoff can take a different, equally accurate step sequence. Circuits under 16 unknowns keep dense and their bit-identical trajectories.
v0.4.0
Minor Changes
- 679d00f:
HeadlessRunnercaps error-controlled steps at 2 ms while the circuit is changing, and keeps the 10 msH_MAXwhile it sits still. Backward Euler lags an RC curve by about half a step, and the local error tolerance alone let slow RC timing run at 10 ms steps: a textbook 555 astable (4.7 kohm, 10 kohm, 10 uF) ran 1.5% slow at 1x speed and 2.2% at 4x, and its period wandered by ±0.6 ms from cycle to cycle. With the cap it runs 0.6% slow with a ±0.04 ms spread. "Changing" means the last error estimate, extrapolated to a full 10 ms step (local error scales as h^(order+1)), would spend more than 10% of the tolerance; a rejected step counts as changing. A flat 2 ms ceiling was measured and rejected: it cost static logic and memory boards 3-4x the solves for no accuracy gain. Fixed-step runs and MCU circuits are unaffected. - 679d00f: Add step breakpoints:
SimEngine.captureStepBreakpoints,SimEngine.takeStepBreakpoints()and theStepBreakpointtype. An NE555 switch happens inside astep()(the engine splits the step at the threshold crossing), but a consumer that only sees accepted states interpolates straight across the whole step, so a scope drew the 555 output edge as a ramp centred on the step, up to half a step from where it switched. With capture on, the engine records the state at the switch and, after a 10 ns backward-Euler guard sub-step, the post-switch state; a split clamped short of a crossing in the last 2% of the step carries on to one guard short of the step end so the switch still lands inside it. Breakpoints roll back withrestoreState()(snapshots carrystepBreakpointCount) and clear onload(). Capture is off by default, and with it off every trajectory is unchanged; with it on, the guard sub-step is the only change to the numerics, independent of whether anything reads the breakpoints.
v0.3.0
Minor Changes
- 12be168: Add latched engine warnings beside failures:
SimWarning/SimWarningCode,SimEngine.getWarnings(),SimEngine.takeNewWarnings(), andDeviceContext.hasWarning/recordAccumulatedWarningfor device models. Two conditions ship:transistor_overcurrent(a BJT or MOSFET whose committed collector/drain current exceeds its catalogi_c_maxfor 100 us of accepted simulated time, reporting the measured current against the rating) andmissing_flyback(an inductor, relay coil, or motor winding switched by a transistor or mechanical switch with no diode-like part on its switch node, evaluated from the loaded graph). Warnings never alter a stamp, so every existing trajectory is unchanged; they latch once per condition per component, carry acrossload()for a same-id part, roll back withrestoreState(), and clear withresetFailures(). Thewarnworker message gains an optionalcomponentId, andHeadlessSnapshotcarrieswarnings.
v0.2.0
Minor Changes
-
190865b: Add
runSmallSignalAcSweep/runSmallSignalAcSweepAsync— a worker-hostable
driver for the linearized small-signal AC analysis.runSmallSignalAcis the exact, ngspice-cross-validated transfer function, but
its signature suits an in-process caller: it takes a liveSimEngine, runs the
whole frequency loop atomically, and offers no progress callback or
cancellation. This driver adds those three things for a UI worker — a
structured-cloneable circuit in, oneonPointper frequency, and cooperative
shouldCancel— by composingrunSmallSignalAcunchanged, one chunk of
frequencies at a time with a macrotask yield between chunks. The chunking
re-solves the (deterministic) operating point per chunk and is proven
bit-identical to a single call. Exported frombrownout/analysis.