Bit-Banged UART Eye

Ciphers & Bit Tricks

A 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.

loading core…

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

ItemWhat it exercises
a software UARTA 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-checkWhat 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 testBecause 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-exactBit-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 diagramAn 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).