Bit-Banged UART Eye
Ciphers & Bit TricksA software UART — a byte framed with start and stop bits and shifted out of one carry-rotated register and into another, one bit per loop pass. This is compiler stress-test #108, the last of its cluster: two loop-carried shift registers rotated together, the shape a real llvm-mos register-coalescer bug once corrupted on the ordinary 8-bit build — drawn as an oscilloscope eye diagram.
Self-running — a serial eye diagram, its two rails and centre crossings shimmering.
Click the screen, then play. (Tab away and it pauses.)
What it is
Bit-banging a UART is the way you talk serial when you have no serial hardware: shift the bits out yourself, sample them back yourself. The loop that does it carries two registers and rotates them every pass — the pattern the guarded compiler bug used to mishandle.
// frame a byte: start bit (0) + 8 data bits + stop bit (1)
uint16_t tx = (1u << 9) | (b << 1);
uint16_t rx = 0;
for (int i = 0; i < 10; i++) {
uint8_t bit = tx & 1; // read the serial line
tx >>= 1; // rotate the transmit register OUT
rx = (rx >> 1) | (bit << 9); // rotate the sampled bit INTO the receiver
}
// two shift registers carried around the loop, rotated on every back edge. Because reception should exactly mirror transmission, the gate reverses the process and folds any mismatch into a CRC. The picture is the proof: an open eye means the framing and the loop-carried rotates are all correct.
Compiler stress-test #108 — Round 6: hardening the fixes
| Item | What it exercises |
|---|---|
| a software UART | A UART sends a byte one bit at a time: a start bit, eight data bits, then a stop bit. Doing it in software — “bit-banging” — means shifting those bits out of one register and sampling them into another, one per loop iteration. Both registers are carried across the loop and rotated on every pass. |
| the bug this guards (patch 0010) | On the ordinary 8-bit 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. This demo runs two loop-carried registers — transmit and receive — rotated together, exactly the pressure that tempted the bad merge, and confirms the fix holds. |
| a round-trip self-check | What is transmitted should be exactly what is received. The gate frames each byte, bit-bangs it out and back in, and folds the difference from the original into the CRC — zero when correct. If a loop-carried register were stranded, the received byte would differ and the CRC would change. It does not: the round-trip is the identity for all 256 bytes. |
| default build is the real test | Because the bug only shows on the plain 8-bit build, that column carries the weight. The demo is checked five ways — host, default, and both 16-bit-accumulator modes on two emulators — and the default build stays green. This completes a four-demo cluster all guarding the same coalescer fix from different angles. |
| result: bit-exact | Bit-banging is pure shifting, so it is identical on host and SNES: host == default == +mos-a16 == +mos-xy16 on both MAME and bsnes-jg, -verify-machineinstrs clean including the default build. |
| the visual — an eye diagram | An oscilloscope eye diagram is how engineers judge a serial link: overlay many bit periods and the stable 0 and 1 levels draw two bright rails while the transitions cross in the middle, leaving an open “eye”. This demo builds one from the framed bit stream — a wide-open eye means the framing and timing are clean. |
Compiler bug this demo guards against
Patch 0010 — a default-8-bit (no +mos-a16 needed) silent miscompile: the
register coalescer could merge two shift/rotate-referenced values into the A-only Ac
register class, stranding a loop-carried byte in Y while the loop's back-edge
ROL read a stale A. Both LLVM's -verify-machineinstrs and
-verify-coalescing passed clean — this was silent, not a crash.
Fix: Teach MOSRegisterInfo::shouldCoalesce to refuse that join whenever the
target class is Ac and both operands are rotate-referenced, plus a
-run-pass=register-coalescer regression test.
This demo bit-bangs a software UART through carry-rotated transmit and receive shift registers, drawn as an oscilloscope eye diagram — the fourth and final angle hardening this fix.
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 0x3F09 — a fold of 128 framed round-trips) live in this tab.
No far pointers — host == default == +mos-a16 == +mos-xy16,
-verify clean (including the default build — the one that matters here).