Clock domain synchronization is required when we have signals crossing logic domains that are running on two different Frequencies that are Asynchronous to each other. The signal from source domain needs to be synchronized to destination domain before it can be used. If the synchronization is not performed then it could result into Metastability of the signal & could result into incorrect sampling of the signal at the receiving flop. The process of synchronization can be broadly categorized into 2 types.
- Open loop solution.
- Closed loop solution.
Open-loop Solution :
2-D flop synchronizer: Here the source signal is flopped twice on destination clock before its used by destination logic. The 2 Flops allow signal to settle down and sample it correctly without getting into Metastable State. We may also use 3 stages of D-Flops if needed but 2 D-Flops are sufficient for most of the cases. The general rule is period of destination clock should be 1.5 times of source clock period. Example below shows the 2 D-Flop Synchronizer .

Binary to Gray Conversion : If we have Multiple bits or a Signal Bus crossing clock domain then it can be converted into Gray code. Conversion to Gray code allows 1-bit difference between signal updates. This allows Synchronizer to be effective as there is less probability of Metastability. The conversion from Gray to Binary is discussed in Async FIFO post.
Synchronized Load Pulse: Another Open loop solution is to pass a synchronized load pulse to destination domain and then use this synchronized pulse to flop in Data bits as shown.

Closed-Loop Solution :
Synchronized Load Pulse with Feedback : In closed loop solution a feedback from destination clock domain is received before sending the next signal from source clock domain. This reduces the chances of Metastability even further as original signal is re-tried if clock synchronization does not happen correctly. The feedback Mechanism is done using a simple FSM and feedback signal from destination domain needs to be clock crossed as well.
Due to Feedback mechanism, this type of solution is slower compared to open-loop but more reliable. It can be used to transfer critical control bits from one clock domain to other. For e.g. FSM States , Mux Selects etc. Here’s an example of closed loop solution using Synchronized load Pulse.

Async FIFO: Async FIFOs are used for clock crossing data bits or large group of signal buses. In this type of implementation read and write pointers are converted into gray code first and then clock crossed into opposite clock domain. The depth of the FIFO is determined using clock domain frequency and data width. Please refer to Async FIFO implementation post for more details.
Alternately, if the frequency difference is small and data widths are same then the FIFO depth can be restricted to 1. This type of implementation is called 1-depth 2 Register FIFO. The 2 Registers are needed for clock crossing however depth of 1 is used to indicate Full and Empty Conditions. This type of implementation is shown below:

In general its recommended to create a single instance of clock crossing module. This common module can be instantiated multiple times if needed throughout the Design.
This post only discusses high-level details of CDC but if you are interested in implementation and more in depth Technical details please refer to this Paper by Clifford E. Cummings.