fix(bitstream): drive SysTick off the core cycle counter#58
Merged
Conversation
machine.bitstream() produced degenerate pulses: every high pulse lasted exactly the same time for a bit 0 and a bit 1, so a WS2812 frame left the chip carrying no information and neopixel always painted (0,0,0). The rp2 port times bitstream against SysTick. For each bit it writes CVR, raises the pin, and busy-waits on `(start_ticks - systick_hw->cvr) < t[0]` before lowering it (ports/rp2/machine_bitstream.c, PICO_ARM path). SysTick was only advanced from ITickable.Tick, which RP2040Machine.Run calls once per batch of instructions — 500 K in the test harness. Inside a batch CVR was therefore frozen, so the very first comparison already read a distance that cleared both t[0] and t[1] and each wait fell straight through. Both delays collapsed to the same handful of instructions. SysTick counts off the core clock, so its value is a pure function of the owning core's cycle counter and must be derived from it on demand rather than integrated at the tick quantum. Anchor CVR to CortexM0Plus.Cycles and resolve it lazily on every SysTick register access; Tick() no longer needs the shared delta the router passes, which was also wrong for core 1 (each core's private SysTick advances with its own cycles). The CVR write path now seeds the reload instead of parking the counter at zero: ARMv6-M says a CVR write must not raise a SysTick exception, but a counter left at zero rolls over on the next cycle. Only observable now that the counter resolves per-instruction. Pulse-width histogram for `np.write()` over 3 pixels on GP2 (72 bits, of which 48 zeros and 24 ones), MicroPython 1.28.0: before 15:72x (one cluster — bits unrecoverable) after 51:48x, 99:24x (two clusters, ratio 1.94 ~ T1H/T0H) The new integration test boots real MicroPython, runs the neopixel script and decodes the captured widths back into the GRB bytes the firmware wrote.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
machine.bitstream()— the transport under MicroPython'sneopixelmodule —produced constant-width pulses, so every NeoPixel painted
(0,0,0): a bit-0 and abit-1 left the pin with the same high time.
The rp2 port times each bit against SysTick: write CVR, raise the pin,
busy-wait on
(start - CVR) < width, lower it.PpbPeripheraladvanced SysTickonly from the peripheral tick, so CVR was frozen for the whole busy-wait and both
delays expired at the same instant. This drives SysTick off the core cycle counter
instead, which resolves per instruction.
Includes a regression test over a real 3-pixel WS2812 frame asserting the
pulse-width histogram splits into two clusters (T0H/T1H). Companion to the same fix
in RP2350Sharp.
Tests: 533 unit, integration green (incl. the bitstream regression).