0
0
VhdlDebug / FixIntermediate · 4 min read

How to Handle Multiple Clock Domains in VHDL: Best Practices

In VHDL, handling multiple clock domains requires using clock domain crossing (CDC) techniques like synchronizer flip-flops or FIFOs to safely transfer signals between domains. Directly connecting signals across different clocks causes metastability and unreliable behavior, so proper synchronization circuits must be implemented.
🔍

Why This Happens

When signals move between different clock domains without proper synchronization, the receiving domain may sample signals at unstable times. This causes metastability, where flip-flops enter an undefined state, leading to unpredictable outputs and system failures.

Here is an example of broken VHDL code that directly connects signals from one clock domain to another without synchronization:

vhdl
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity broken_cdc is
    Port (
        clk_a : in std_logic;
        clk_b : in std_logic;
        signal_a : in std_logic;
        signal_b : out std_logic
    );
end broken_cdc;

architecture Behavioral of broken_cdc is
begin
    -- Direct assignment across clock domains (WRONG)
    process(clk_b)
    begin
        if rising_edge(clk_b) then
            signal_b <= signal_a;  -- signal_a is from clk_a domain
        end if;
    end process;
end Behavioral;
Output
No compile error, but causes metastability and unreliable output in hardware.
🔧

The Fix

To fix this, use a two-stage synchronizer for single-bit signals crossing clock domains. This reduces metastability risk by giving the signal time to settle before use.

For multi-bit data, use asynchronous FIFOs or handshake protocols to safely transfer data.

vhdl
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity fixed_cdc is
    Port (
        clk_a : in std_logic;
        clk_b : in std_logic;
        signal_a : in std_logic;
        signal_b : out std_logic
    );
end fixed_cdc;

architecture Behavioral of fixed_cdc is
    signal sync_stage1, sync_stage2 : std_logic := '0';
begin
    -- Two-stage synchronizer for crossing clk_a to clk_b domain
    process(clk_b)
    begin
        if rising_edge(clk_b) then
            sync_stage1 <= signal_a;
            sync_stage2 <= sync_stage1;
        end if;
    end process;

    signal_b <= sync_stage2;
end Behavioral;
Output
Signal_b is safely synchronized to clk_b domain, avoiding metastability.
🛡️

Prevention

To avoid clock domain crossing issues in the future:

  • Always identify signals crossing clock domains early in design.
  • Use two-stage synchronizers for single-bit signals.
  • Use asynchronous FIFOs or handshake protocols for multi-bit data.
  • Simulate CDC behavior with specialized tools or assertions.
  • Follow coding guidelines and use lint tools that detect unsafe CDC.
⚠️

Related Errors

Common related errors include:

  • Metastability: Flip-flops fail to settle, causing random outputs.
  • Data corruption: Multi-bit signals sampled partially, causing invalid data.
  • Timing violations: Setup and hold times not met across domains.

Quick fixes involve adding synchronizers, FIFOs, or redesigning clock domain boundaries.

Key Takeaways

Never directly connect signals between different clock domains without synchronization.
Use two-stage flip-flop synchronizers for single-bit signals crossing clock domains.
For multi-bit data, use asynchronous FIFOs or handshake protocols to transfer safely.
Identify and plan clock domain crossings early to avoid metastability and data corruption.
Use simulation and linting tools to detect and fix unsafe clock domain crossings.