Dual-LFSR Scrambler

Ciphers & Bit Tricks

Two linear-feedback shift registers — a Galois and a Fibonacci LFSR — stepped together, each a loop-carried byte folded through a rotate on the loop’s back edge. This is compiler stress-test #106: it puts two dissimilar loop-carried rotates under pressure at once, re-running the shape a real llvm-mos register-coalescer bug once corrupted on the ordinary 8-bit build.

loading core…

Self-running — two interleaved pseudo-noise fields scroll as the LFSRs advance.

Click the screen to focus, then watch. Tab away and it pauses.

What it is

Linear-feedback shift registers are the workhorse of cheap pseudo-randomness — scramblers, test patterns, checksums. This demo runs two of them, wired two different ways, at the same time, so the compiler juggles two loop-carried registers and their feedback together.

// two linear-feedback shift registers, stepped together
// Galois: shift out a bit; if it was 1, XOR the tap mask back in
out = v & 1;  v >>= 1;  if (out) v ^= 0xB8;

// Fibonacci: XOR the tapped bits, feed the result into the top
fb = (v ^ (v>>2) ^ (v>>3) ^ (v>>4)) & 1;  v = (v>>1) | (fb<<7);

// both are loop-carried through a shift/rotate on the back edge —
// the exact shape a default-build register-coalescer bug once corrupted.

Both are tuned for maximal length (period 255), which only holds if every feedback bit is exactly right. The gate folds both streams into a CRC. The picture is the proof: a corrupted loop-carried byte would collapse the sequence and change the CRC.

Compiler stress-test #106 — Round 6: hardening the fixes

ItemWhat it exercises
two LFSRs, two shapesA linear-feedback shift register makes a long pseudo-random sequence by shifting a byte and folding a few bits back in. The Galois form XORs a tap mask when the shifted-out bit is 1; the Fibonacci form XORs the tapped bits and feeds the result into the top. Two different feedback structures, run at the same time, keep two shift registers and their feedback live at once.
maximal lengthBoth 8-bit registers are wired for maximal length — they cycle through all 255 non-zero states before repeating. That is the proof the feedback is exactly right: one wrong bit and the period collapses. The demo verifies both hit 255.
the bug this guards (patch 0010)On the plain (non-16-bit-accumulator) build, a register-coalescer once merged two shift/rotate values into the accumulator-only class, stranding a loop-carried byte while the loop’s back-edge rotate read a stale accumulator. The fix keeps them apart. Because the bug is invisible to the 16-bit-accumulator modes, the ordinary 8-bit build is the one that matters here — and this demo puts two dissimilar loop-carried rotates under pressure at once.
default build is the real testThis is one of the few demos where the standard 8-bit build carries the weight. It is checked five ways — host, default, and both 16-bit-accumulator modes on two emulators — but the default column is the one that would catch a coalescer regression, and it stays green.
result: bit-exactAn LFSR is pure shifting and XOR, so the sequences are identical on host and SNES. The gate folds both streams (and the 16-bit one) into a CRC: host == default == +mos-a16 == +mos-xy16 on both MAME and bsnes-jg, -verify-machineinstrs clean including the default build. A stranded loop-carried register would streak a field and change the CRC. It does not.
the visualThe two streams paint two interleaved noise fields on a 16×16 grid — Galois cells on one diagonal parity, Fibonacci on the other — scrolling as the live registers advance. It is deliberately noisy: that texture is what a working scrambler looks like.

Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg and MAME. Hit Verify fidelity to reproduce the build gate's WRAM assert (gate CRC 0x6AA3 — a fold of 200 LFSR steps) live in this tab. No far pointers — host == default == +mos-a16 == +mos-xy16, -verify clean (including the default build — the one that matters here).