Once, while
writing a new program-loader-and-debugger/"bios" for a Z80 clone, I had a particularly nasty heisenbug.
The problem was my new code "usually" worked. About 25% of the time it would simply fail to do anything (it worked perfectly the other 75% - "umm... aren't computers supposed to be deterministic?") Of course, every single time I did anything to try extract any useful information from the device, it would work 100% of the time. Even tossing a quick 2-byte sequence to flip an I/O pin made the problem completely go away.
The problem was, of course, that the hardware wasn't quote done rebooting, and needed to be left alone a tiny bit longer after hitting the RST pin. About 8 cycles longer than when the hardware engineers assured me it would be "completely safe". Any time I added debug code, it simply worked as the needed delay, and was solved by a very carefully documented sequence of NOOPs.
What made this particularly infuriating, though, was it's seemingly arbitrary sensitivity to when the real code was started. Depending on how much you padded with NOOPs, several behaviors were observed.
#NOOP | Behavior
---------------
0 | original 75/25 ok/fail split
1 | always failed
2 | always worked
3 | always worked
4 | worked ~95% of the time
5 | ~80/20 ok/fail split
6 | ~50/50 ok/fail split (not a lot of data)
7 | *almost* always worked (I think I saw it fail once)
8+ | always worked
Of course, the various stuff I would add for debugging tended to line up with the cases when it DID work.
sigh
I miss working with that chip. Z80 assembly is kind of fun. Well, except for when my boss told me my new loader had to work on our slowest 4Mhz part. Receiving a program over a 115kbaud serial line, verifying CRC-8, and writing it to flash is pretty hard when you have to do it less than ~100 clocks/byte....