Putting a synchroniser on each bit feels thorough: every bit is settled, so the word should be fine. It is not, because synchronising is a statement about one bit's timing, while a word needs all bits from the same moment.
A synchroniser guarantees each bit eventually delivers a clean old or new value. It says nothing about which bits deliver old and which deliver new on any given cycle. With several bits changing, the destination reads a combination the source never produced, exactly as the tearing lesson showed.
Gray code helps only where it holds: a value that advances one step at a time, so exactly one bit changes per step. A Gray-coded counter is safe to synchronise bit by bit because there is never a second changing bit to mix with. But a counter that can reload, skip, or be written arbitrarily breaks the one-bit rule, and Gray-coded addresses or data that jump are torn just like binary.
Qualifying the bus with an enable (“read the bus when enable says valid”) fixes nothing if the enable itself crosses domains naively: the destination may sample the enable and the data at inconsistent moments. A safe enable scheme is really a handshake or a load pulse in disguise, with the data held stable around the synchronised control, which is one of the safe patterns in the next lessons.
Attempted fix | What it actually guarantees | Where it still tears |
|---|---|---|
More stages per bit | Cleaner individual bits | Everywhere: mixing is unaffected. |
Gray code | One changing bit per step | Reloads, skips, arbitrary writes. |
A crossing enable | A control signal arrives | Whenever data and enable are not held consistent. |
The address can jump many steps at once, changing several Gray bits together, so the destination can capture a torn address. Arbitrary values need a handshake or a load pulse, not Gray code.
Extra stages settle each bit more without coordinating the bits, so the word can still mix old and new values.