Clock Domain Crossing for Tear-Free FPGA LED Updates

An LED scanner often runs from a deterministic display clock while pixels arrive from a CPU bus, DMA engine, USB bridge, or network clock. Passing a “new frame” pulse or a multibit control word directly between those domains can cause lost events, metastability, and torn frames. This tutorial builds a safe frame-swap handshake.

Synchronizers solve only single-bit sampling

VHDL-2008 reference implementation

This article uses synthesizable VHDL-2008. The complete tested VHDL bundle is linked below; adapt clock constraints, I/O standards, and timing parameters to the target board and panel.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity hub75_row_scanner is
  generic (COLUMNS : positive := 64; ROW_GROUPS : positive := 16);
  port (
    clk       : in  std_logic;
    rst       : in  std_logic;
    dwell_done: in  std_logic;
    pixel_bits: in  std_logic_vector(5 downto 0);
    hub_clk   : out std_logic;
    lat       : out std_logic;
    oe_n      : out std_logic;
    row_addr  : out natural range 0 to ROW_GROUPS - 1;
    rgb       : out std_logic_vector(5 downto 0)
  );
end entity;

architecture rtl of hub75_row_scanner is
  type state_t is (blank, shift_low, shift_high, latch, set_row, display);
  signal state : state_t := blank;
  signal col   : natural range 0 to COLUMNS - 1 := 0;
  signal row   : natural range 0 to ROW_GROUPS - 1 := 0;
begin
  process (clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        state <= blank; col <= 0; row <= 0;
      else
        case state is
          when blank      => col <= 0; state <= shift_low;
          when shift_low  => state <= shift_high;
          when shift_high => if col = COLUMNS - 1 then state <= latch; else col <= col + 1; state <= shift_low; end if;
          when latch      => state <= set_row;
          when set_row    => state <= display;
          when display    => if dwell_done = '1' then if row = ROW_GROUPS - 1 then row <= 0; else row <= row + 1; end if; state <= blank; end if;
        end case;
      end if;
    end if;
  end process;

  hub_clk  <= '1' when state = shift_high else '0';
  lat      <= '1' when state = latch else '0';
  oe_n     <= '0' when state = display else '1';
  row_addr <= row;
  rgb      <= pixel_bits when state = shift_low else (others => '0');
end architecture;

Download the VHDL-2008 FPGA LED controller examples

A two-register chain in the destination domain reduces the probability that metastability escapes into user logic. It does not make a changing multibit bus coherent, and a narrow source pulse can disappear entirely between destination clock edges.

Use a toggle for an event

The producer flips a bit after finishing the back buffer. The scanner synchronizes that bit and compares it with the last value it serviced. The event persists until observed, regardless of the relative clock rates.

Synchronize ack_toggle back into the producer domain. The producer must not overwrite the completed back buffer until it observes the acknowledgement.

Move multibit data safely

  • Use a dual-clock FIFO for a stream of pixels or commands.
  • Use dual-port memory plus ownership handshakes for framebuffers.
  • For a stable configuration bus, hold the bus unchanged from request through acknowledgement and capture it only after the synchronized request.
  • Never synchronize each bit of a bus independently; different bits can settle on different cycles.

Constraints and structural checks

Mark recognized synchronizer registers with the vendor-supported attribute, declare asynchronous clock relationships correctly, and run the tool’s CDC report. A false-path constraint hides timing analysis; it does not create safe hardware. Review every reported crossing and confirm the RTL structure matches the protocol.

Verification strategy

  • Run source and destination clocks at non-integer ratios.
  • Randomize request timing around destination edges.
  • Assert every accepted request receives exactly one acknowledgement.
  • Assert bank ownership never overlaps.
  • Assert front_bank changes only on frame_done.

Official references


FPGA LED controller tutorial series

This article is part of the FPGA LED Controller Tutorials learning path. Continue with PWM vs. BCM, the HUB75 timing calculator, or VHDL-2008 verification.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *