A BitSynchronizer is used to move signals that are asynchronous to a clock into that
clock domain using a pair of back-to-back flip-flops. While the first flip flop may
become metastable, the second one is likely to be stable.
A SyncReceiver works together with a SyncSender to transmit data from one clock domain
to another (in one direction). To use a SyncReceiver wire up the [sig_cross], [flag_in]
and [ack_out] signals between the two.
When you need to send many bits between two clock domains, it is risky to use a vector
of BitSynchronizer structs. That is because, you cannot guarantee at any given moment
that all of the bits of your multi-bit signal will cross into the new clock domain at once.
So to synchronize a multi-bit signal, use a SyncSender and SyncReceiver pair. These
widgets will use a set of handshake signals to move a value from one clock domain to another
safely. Note that while the state machine is executing, the synchronizer will indicate it
is busy. Crossing clock domains with greater ease is best done with an [AsynchronousFIFO].
A VectorSynchronizer uses a SyncSender and SyncReceiver in a matched pair to
transmit a vector of bits (or any Synth type from one clock domain to a second
clock domain without metastability or data corruption. You can think of a VectorSynchronizer
as a single-element asynchronous FIFO, and indeed [AsynchronousFIFO] uses the VectorSynchronizer
internally.