Skip to content

v0.6.4

Choose a tag to compare

@github-actions github-actions released this 06 Oct 16:47
· 5 commits to main since this release
e5d10b5

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 next digitalRead() or Pin.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-back digitalWrite() 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, so shiftOut(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 as shiftIn() 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.