A reusable, parameterized Clock Domain Crossing (CDC) synchronizer library in SystemVerilog. Provides 10 hierarchical modules covering the most common CDC patterns — from single-bit synchronization to async and sync FIFOs.
All modules are verified with self-checking testbenches and pass iCE40 synthesis (Yosys). Supports both FPGA (sync reset, initial blocks) and ASIC (async reset, no initial blocks) flows via a single compile-time macro.
cdc_bit single-bit N-stage synchronizer (leaf)
cdc_reset async-assert / sync-deassert reset synchronizer (leaf)
cdc_gray_conv combinational binary <-> Gray code converter (leaf)
cdc_gray_sync bit-parallel Gray-code synchronizer
|--------------------uses cdc_bit x WIDTH
|
cdc_qualifier level-qualified data bus synchronizer
|--------------------uses cdc_bit
|
cdc_counter self-contained CDC-safe binary counter
|--------------------uses cdc_gray_sync
|--------------------uses cdc_gray_conv
|
cdc_handshake handshake-based multi-bit bus synchronizer
|--------------------uses cdc_bit x 2
|
cdc_pulse pulse synchronizer (toggle or counter mode)
|--- MODE=0 -------> uses cdc_bit
|--- MODE=1 -------> uses cdc_counter
|
cdc_fifo small async FIFO (Cummings-style)
|--------------------uses cdc_gray_sync x 2
|
cdc_sync_fifo single-clock synchronous FIFO (standalone)
N-stage single-bit synchronizer. The fundamental CDC building block.
| Parameter | Default | Description |
|---|---|---|
SYNC_STAGES |
2 | Number of synchronizer flip-flops |
RESET_VALUE |
1'b0 | Value held during reset and power-up |
i_clk, i_rst_n, i_async_in --> [FF]-[FF]-..--> o_sync_out
^ ASYNC_REG (FPGA mode only)
Reset synchronizer with async assert, synchronous deassert. Active-low. This is the one module where async reset is always used regardless of the CDC_ASYNC_RESET macro.
| Parameter | Default | Description |
|---|---|---|
SYNC_STAGES |
2 | Deassert latency in clock cycles |
i_async_rst_n --|>-- [FF]-[FF]--> o_sync_rst_n
^
assert: instant (async)
deassert: SYNC_STAGES clocks (sync)
Purely combinational binary-to-Gray and Gray-to-binary converter. Both directions available simultaneously in a single instance.
| Parameter | Default | Description |
|---|---|---|
WIDTH |
4 | Data width |
Bit-parallel synchronizer for a pre-registered Gray-code bus. Instantiates one cdc_bit per bus bit. The caller must ensure i_gray is a registered flip-flop output from the source domain.
| Parameter | Default | Description |
|---|---|---|
WIDTH |
4 | Bus width |
SYNC_STAGES |
2 | Synchronizer depth per bit |
Level-qualified data bus synchronizer. Synchronizes a level-held i_valid into the destination domain via cdc_bit, then uses the synchronized valid as a load-enable to capture i_data directly. No ack path back to the source — the caller must hold i_data and i_valid stable for at least SYNC_STAGES+1 destination clocks.
| Parameter | Default | Description |
|---|---|---|
WIDTH |
8 | Data bus width |
SYNC_STAGES |
2 | Synchronizer depth |
i_data, i_valid (held stable) --[cdc_bit]--> o_valid (destination domain)
|
+--load enable--> o_data
o_data lags o_valid by one extra destination clock (registered load-enable), so total latency from i_valid asserting to o_data updating is SYNC_STAGES+1 cycles. Simpler than cdc_handshake when the source can hold a value for multiple destination clocks and no back-pressure is needed.
Self-contained CDC-safe binary counter. Owns the source-domain counter, registers binary and Gray in the same clock cycle, and synchronizes to the destination domain via cdc_gray_sync.
| Parameter | Default | Description |
|---|---|---|
WIDTH |
4 | Counter width |
SYNC_STAGES |
2 | Synchronizer depth per bit |
| Port | Direction | Description |
|---|---|---|
i_count_up |
in | Increment counter by 1 (src domain) |
i_count_down |
in | Decrement counter by 1 (src domain) |
o_src_count |
out | Binary count in source domain |
o_dst_gray |
out | Synchronized Gray code in destination domain |
o_dst_count |
out | Synchronized binary count in destination domain |
Transfers multi-bit data across clock domains using a four-phase toggle handshake. Provides back-pressure via o_src_ready.
| Parameter | Default | Description |
|---|---|---|
WIDTH |
8 | Data bus width |
SYNC_STAGES |
2 | Synchronizer depth |
src domain dst domain
req (cdc_bit)
i_src_data --|hold|-- ~~~~~~~~~~~~~~~ --> o_dst_data
i_src_valid -> <~~~~~~~~~~~~~~~ -- o_dst_valid
o_src_ready <- ack (cdc_bit)
One transfer at a time. o_src_ready deasserts during transfer and reasserts after the ack round-trip.
Transfers pulses across clock domains. Two compile-time selectable architectures:
| Parameter | Default | Description |
|---|---|---|
SYNC_STAGES |
2 | Synchronizer depth |
MODE |
0 | 0 = toggle, 1 = counter |
CTR_WIDTH |
4 | Counter width (MODE=1 only) |
MODE=0 (toggle) — Lightweight. Toggles a FF on each src pulse, syncs the level, edge-detects in dst. Requires spacing between consecutive pulses (at least 2*SYNC_STAGES + 1 dst clocks).
MODE=1 (counter) — Counts pulses in src domain, syncs the counter via cdc_counter, and generates matching pulses in dst domain. Guarantees N pulses in = N pulses out, even with back-to-back or contiguous bursts. Each output pulse is exactly 1 dst_clk wide, separated by a mandatory 1-cycle gap. More logic than toggle mode, but handles any pulse rate the src can produce.
Small asynchronous FIFO for streaming data across clock domains. Cummings-style Gray-coded pointer design.
| Parameter | Default | Description |
|---|---|---|
WIDTH |
8 | Data width |
DEPTH |
4 | FIFO depth (must be power of 2, minimum 4) |
SYNC_STAGES |
2 | Pointer synchronizer depth |
i_wr_clk domain i_rd_clk domain
i_wr_data -->[ memory ]--> o_rd_data
i_wr_en -->[ wr | rd ]--> i_rd_en
o_full <--[ ptr ptr ]<-- o_empty
Combinational read output. Supports concurrent read/write at full throughput.
Single-clock synchronous FIFO for same-domain buffering. Supports registered read (default) or FWFT mode.
| Parameter | Default | Description |
|---|---|---|
WIDTH |
8 | Data width |
DEPTH |
16 | FIFO depth |
FWFT_MODE |
0 | 0 = registered read (1-cycle latency), 1 = FWFT (zero latency) |
| Port | Direction | Description |
|---|---|---|
o_count |
out | Current occupancy (ADDR_WIDTH+1 bits) |
o_full |
out | Asserted when FIFO is full |
o_empty |
out | Asserted when FIFO is empty |
Standalone module with no CDC dependencies. Useful as a data buffer within a single clock domain.
The library supports two reset styles selected at compile time via the CDC_ASYNC_RESET macro.
| Mode | Macro | always_ff sensitivity |
initial blocks |
ASYNC_REG attribute |
|---|---|---|---|---|
| FPGA (default) | (not defined) | posedge clk only |
present | present |
| ASIC-portable | -DCDC_ASYNC_RESET |
posedge clk or negedge rst_n |
absent | absent |
Setting the project default — edit sources/include/cdc_config.svh and uncomment the define line. This applies to every build without any Make flags.
Per-run override:
make sim TOP_MODULE=cdc_fifo TESTBENCH=cdc_fifo_tb RESET_STYLE=async # force async
make sim TOP_MODULE=cdc_fifo TESTBENCH=cdc_fifo_tb RESET_STYLE=sync # force synccdc_reset is exempt — it always uses async-assert/sync-deassert by design.
# Check tools are installed
make check-tools
# Update file list (required after cloning)
make update_list
# Run all tests
make sim TOP_MODULE=cdc_bit TESTBENCH=cdc_bit_tb
make sim TOP_MODULE=cdc_reset TESTBENCH=cdc_reset_tb
make sim TOP_MODULE=cdc_gray_conv TESTBENCH=cdc_gray_conv_tb
make sim TOP_MODULE=cdc_gray_sync TESTBENCH=cdc_gray_sync_tb
make sim TOP_MODULE=cdc_qualifier TESTBENCH=cdc_qualifier_tb
make sim TOP_MODULE=cdc_counter TESTBENCH=cdc_counter_tb
make sim TOP_MODULE=cdc_handshake TESTBENCH=cdc_handshake_tb
make sim TOP_MODULE=cdc_pulse TESTBENCH=cdc_pulse_tb
make sim TOP_MODULE=cdc_fifo TESTBENCH=cdc_fifo_tb
make sim TOP_MODULE=cdc_sync_fifo TESTBENCH=cdc_sync_fifo_tb
# Run same tests in ASIC async-reset mode
make sim TOP_MODULE=cdc_fifo TESTBENCH=cdc_fifo_tb RESET_STYLE=async
# Synthesize any module for iCE40
make synth-ice40 TOP_MODULE=cdc_fifo
# View waveforms after simulation
make waves TOP_MODULE=cdc_bit TESTBENCH=cdc_bit_tbAll testbenches are self-checking and print *** TEST PASSED *** or *** TEST FAILED ***.
// Synchronize a single-bit signal
cdc_bit #(.SYNC_STAGES(3)) u_sync (
.i_clk (dst_clk),
.i_rst_n (dst_rst_n),
.i_async_in (signal_from_other_domain),
.o_sync_out (synchronized_signal)
);
// Synchronize reset with clean deassert
cdc_reset #(.SYNC_STAGES(3)) u_rst_sync (
.i_clk (sys_clk),
.i_async_rst_n (pll_locked),
.o_sync_rst_n (sys_rst_n)
);
// Transfer pulses (counter mode — burst safe)
cdc_pulse #(.MODE(1), .CTR_WIDTH(8)) u_irq_sync (
.i_src_clk (periph_clk), .i_src_rst_n (periph_rst_n), .i_src_pulse (irq_pulse),
.i_dst_clk (cpu_clk), .i_dst_rst_n (cpu_rst_n), .o_dst_pulse (irq_synced)
);
// Transfer a level-held value with no back-pressure
cdc_qualifier #(.WIDTH(8)) u_status_sync (
.i_clk (core_clk), .i_rst_n (core_rst_n),
.i_data (status_data), .i_valid (status_valid),
.o_data (status_synced), .o_valid (status_synced_valid)
);
// Transfer a register value with handshake
cdc_handshake #(.WIDTH(32)) u_cfg_sync (
.i_src_clk (cfg_clk), .i_src_rst_n (cfg_rst_n),
.i_src_data (cfg_data), .i_src_valid (cfg_valid), .o_src_ready (cfg_ready),
.i_dst_clk (core_clk), .i_dst_rst_n (core_rst_n),
.o_dst_data (cfg_synced), .o_dst_valid (cfg_synced_valid)
);
// Stream data across clock domains
cdc_fifo #(.WIDTH(16), .DEPTH(8)) u_stream_fifo (
.i_wr_clk (adc_clk), .i_wr_rst_n (adc_rst_n),
.i_wr_en (sample_valid), .i_wr_data (sample_data), .o_full (fifo_full),
.i_rd_clk (proc_clk), .i_rd_rst_n (proc_rst_n),
.i_rd_en (read_en), .o_rd_data (proc_data), .o_empty (fifo_empty)
);
// Single-clock buffering (FWFT mode)
cdc_sync_fifo #(.WIDTH(32), .DEPTH(16), .FWFT_MODE(1)) u_cmd_buf (
.i_clk (sys_clk), .i_rst_n (sys_rst_n),
.i_wr_en (cmd_valid), .i_wr_data (cmd_data), .o_full (cmd_full),
.i_rd_en (cmd_read), .o_rd_data (cmd_out), .o_empty (cmd_empty),
.o_count (cmd_count)
);- Dual reset style — sync reset (FPGA default) or async reset (ASIC) selectable via
CDC_ASYNC_RESETmacro; seesources/include/cdc_config.svh (* ASYNC_REG = "TRUE" *)on synchronizer registers in FPGA mode for correct placement (removed in ASIC mode)initialblocks for FPGA power-up values (removed in ASIC mode)always_fffor sequential logic,always_combfor combinational- Parameterized — all widths, depths, and sync stages are configurable
- No vendor primitives — portable across Lattice, Xilinx, Intel, etc.
cdc_lib/
├── sources/
│ ├── rtl/
│ │ ├── cdc_bit.sv
│ │ ├── cdc_reset.sv
│ │ ├── cdc_gray_conv.sv
│ │ ├── cdc_gray_sync.sv
│ │ ├── cdc_qualifier.sv
│ │ ├── cdc_counter.sv
│ │ ├── cdc_handshake.sv
│ │ ├── cdc_pulse.sv
│ │ ├── cdc_fifo.sv
│ │ └── cdc_sync_fifo.sv
│ ├── tb/ # 10 self-checking testbenches
│ ├── include/
│ │ └── cdc_config.svh # Reset style configuration
│ └── constraints/
├── sim/
│ ├── waves/ # VCD waveform dumps
│ └── logs/
├── backend/
│ ├── synth/ # Yosys synthesis outputs
│ ├── pnr/ # Place & route outputs
│ ├── bitstream/
│ └── reports/
├── Makefile # Build system (sim, synth, PnR, bitstream)
└── README.md
| Tool | Purpose | Install |
|---|---|---|
| Icarus Verilog | Simulation | sudo apt install iverilog |
| GTKWave | Waveform viewer | sudo apt install gtkwave |
| Yosys | Synthesis | sudo apt install yosys |
| NextPNR | Place & route (optional) | sudo apt install nextpnr-ice40 |
| Module | Simulation (sync) | Simulation (async) | Synthesis (iCE40) |
|---|---|---|---|
| cdc_bit | PASS | PASS | PASS |
| cdc_reset | PASS | PASS | PASS |
| cdc_gray_conv | PASS | PASS | PASS |
| cdc_gray_sync | PASS | PASS | PASS |
| cdc_qualifier | PASS | PASS | N/A* |
| cdc_counter | PASS | PASS | PASS |
| cdc_handshake | PASS | PASS | PASS |
| cdc_pulse (toggle) | PASS | PASS | PASS |
| cdc_pulse (counter) | PASS | PASS | PASS |
| cdc_fifo | PASS | PASS | PASS |
| cdc_sync_fifo | PASS | PASS | PASS |
* cdc_qualifier synthesis is untested — make synth-ice40 currently fails to locate cdc_config.svh for every module (missing -I$(INCLUDE_DIR) in SYNTH_FLAGS), a pre-existing Makefile issue unrelated to this module.
This project includes a CLAUDE.md file with detailed guidance for AI-assisted development — covering HDL coding standards, naming conventions, reset/clock rules, FSM templates, and project workflow for both Verilog/SystemVerilog and VHDL.
MIT License — Copyright (c) 2026 moaz khaled.
Free to use, modify, and distribute for any purpose. Attribution required — keep the copyright notice in all copies or substantial portions of the code.