Main Content

Clock Domain Crossing Using the HDL Dual Clock FIFO Block

R2026b
Since R2026b

This example shows how to use the HDL Dual Clock FIFO example library block to approximate the functional behavior of a dual-clock first-in first-out (FIFO) and transfer data between two independent clock domains. The block captures data ordering, flag behavior, and backpressure during simulation.

Modeling Requirements

To perform clock domain crossing by using the HDL Dual Clock FIFO block, you must model the design to run at the clock rate. When you use the block in your device under test (DUT):

  • The write domain inputs, WrData and WrEnable, must use the same sample rate. WrData cannot inherit its rate because the block uses this rate to propagate the write domain timing.

  • The read domain sample rate, such as the rate of RdEnable, must be different from the write domain rate for code generation.

  • You must specify the read domain sample rate either by connecting RdEnable at the desired rate or by setting the Read sample time mask parameter.

  • The HDL Dual Clock FIFO block mask parameters control the XPM configuration, including the FIFO depth, the operating mode (classic or first-word fall-through), and the number of cycles of simulated reset-busy latency. The FIFO depth must be a power of two and greater than or equal to sixteen.

The HDL Dual Clock FIFO block does not reproduce cycle-accurate timing or gray-coded pointer details. You must configure the Simulink® sample rates to match the actual hardware clock rates so that the block correctly represents the relationship between the write and read domains.

Limitations

When you target the AMD® platform with Verilog® as the target language, HDL Coder™ instantiates the AMD Xilinx Parameterized Macro (XPM) asynchronous FIFO (xpm_fifo_async) in place of the HDL Dual Clock FIFO block, giving you a device-optimized implementation with vendor timing constraints.

When using the HDL Dual Clock FIFO block:

  • The Language model configuration parameter must be Verilog.

  • The Synthesis Tool model configuration parameter must be Xilinx Vivado.

  • The Clock inputs model configuration parameter must be Multiple.

Transfer Data Across Clock Domains by Using the HDL Dual Clock FIFO Block

In this example, you:

  1. Use the HDL Dual Clock FIFO block to cross clock domains when modeling at clock rate.

  2. Generate a multiple-clock HDL IP core by using HDL Coder™.

  3. Integrate the generated IP core into an AMD Vivado™ project and deploy it to a ZedBoard™.

  4. Use FPGA I/O to test the design on hardware.

Prerequisites

To interact with the deployed IP core, you must:

Examine the Model

When you model multiple-clock designs in Simulink®, you represent each clock domain by a unique sample rate. To model data transfers between clock domains, the sample rates must match the frequencies of the corresponding hardware clocks. The hdlcAsyncFIFO model compares two ways of crossing the sample-rate boundary: the withDualClockFIFO subsystem and the withoutDualClockFIFO subsystem.

Open the HDLDUT subsystem in the example model.

load_system("hdlcAsyncFIFO");
open_system("hdlcAsyncFIFO/HDLDUT");
set_param("hdlcAsyncFIFO","SimulationCommand","Update")

The HDLDUT subsystem contains a producer that drives the same data through two parallel clock-domain-crossing paths into matching consumers. The model uses two clock domains with sampling times and packet count defined in the model initialization callback. Ts1 = 1/100e6 is the fast, 100 MHz domain. Ts2 = 1/77e6 is the slow, 77 MHz domain. num_packets = uint16(100) sets the number of packets per burst.

Examine the Producer_FSM Block

The Producer_FSM subsystem is a finite state machine (FSM) that runs in the fast clock domain, Ts1, and generates framed packets at an effective rate of 50 MHz. It sends one byte every two 100 MHz cycles and num_packets packets per burst. Each packet carries a payload byte from a counter that starts at 2, increments by 1 per packet, and wraps at 170 (0xAA). Each packet is five bytes:

Position

Name

Value

1

SOP

0x01 (Start of Packet)

2

PL

Lower 8 bits of 16-bit counter

3

PH

Upper 8 bits of 16-bit counter

4

Checksum

bitxor(PL, PH)

5

EOP

0xCC (End of Packet)

The Producer_FSM subsystem outputs data to two subsystems, withDualClockFIFO and withoutDualClockFIFO. Each subsystem contains a consumer that decodes the packets.

Examine the withDualClockFIFO Subsystem

The withDualClockFIFO subsystem writes data through the HDL Dual Clock FIFO block to cross from the 100 MHz write domain to the 77 MHz read domain. The HDL Dual Clock FIFO block outputs a value and asserts the valid signal whenever it is not empty. Then the consumer block decodes the data.

Simulink subsystem withDualClockFIFO showing the producer data and valid signals connected to the write side of the HDL Dual Clock FIFO block, with read-side outputs connected to the consumer block in the 77 MHz domain.

The HDL Dual Clock FIFO block configuration is:

  • Write depth: 256

  • Data width: 8 bits

  • Read mode: First-Word Fall-Through (FWFT)

  • Write clock: 100 MHz producer domain (Ts1)

  • Read clock: 77 MHz consumer domain (Ts2)

Because the HDL Dual Clock FIFO block targets AMD devices, its interface matches the AMD xpm_fifo_async primitive. The block has two reset-busy outputs: WrRstBusy and RdRstBusy. Each signal indicates when its clock domain is resetting. The design must wait for these signals to go low before sending or receiving data.

The Producer_FSM subsystem waits for two things before it starts transmitting. The first is the NotWrRstBusy flag, which is the inverted WrRstBusy flag from the FIFO, available at the top of the withDualClockFIFO subsystem. It goes high once the write side finishes resetting. The second is initiate, a startup delay created by taking a constant true and delaying it by 100 fast-clock cycles.

When you use multiple rates as multiple clocks, resets are not modeled explicitly. The delay register inherits the reset of its clock domain, which is the same reset that resets the FIFO write side, and the 100-cycle countdown begins when the write clock domain comes out of reset.

Typically, WrRstBusy goes low within a few write-clock cycles after reset. Because the 100-cycle initiate delay is longer than that, it is always the slower of the two conditions. This delay provides the producer with a safety margin before it drives the data.

Examine the withoutDualClockFIFO Subsystem

The withoutDualClockFIFO subsystem passes data from the Producer_FSM subsystem through two Unit Delay blocks, which act as a dual-register synchronizer, into a consumer subsystem. This path is intentionally unreliable. Without a FIFO, there is no flow control and no guarantee that every byte reaches the consumer. Additionally, because the two clocks originate from independent sources, the multi-bit data bus is subject to metastability when sampled by the destination clock.

Simulink subsystem withoutDualClockFIFO showing the producer data and valid signals passing through two Unit Delay blocks acting as a dual-register synchronizer before reaching the consumer block.

Examine the consumer Subsystems

Both subsystems contain the consumer subsystem. It decodes the five-byte packets, validates the checksum and framing, and produces these outputs:

  • pkt_received — Number of successfully validated packets

  • error_count — Total errors detected

  • framing_errors — Invalid EOP markers

  • checksum_errors — Payload checksum mismatches

  • desync_events — Unexpected bytes received while waiting for SOP

After the consumer block validates a packet, it stores the reconstructed 16-bit payload in a single-clock output FIFO that you can read by using AXI4-Lite registers. The queuePacketPathA and queuePacketPathB signals pop one value from each output FIFO. When the AXI4-Lite register transitions from true to false, it generates a single-cycle pulse. This pulse generation logic is in the design under test (DUT).

Simulate the Model

Verify the functional behavior of the DUT by simulating the model. While behavioral simulation cannot model metastability, it can verify packet framing, checksum logic, and FIFO handshaking.

sim("hdlcAsyncFIFO");

After the simulation completes, open the Logic Analyzer by clicking Logic Analyzer on the Simulation tab. The model logs the data and valid signals at the output of the producer and at the input of each consumer subsystem.

Logic Analyzer display comparing data and valid signals across Path A and Path B, showing that Path A preserves all packets while Path B drops bytes due to clock domain crossing failures.

Both subsystems sample the first packet correctly, but the withoutDualClockFIFO subsystem contains errors in later packets. For example, the withoutDualClockFIFO subsystem samples only the SOP and EOP. Because the consumer subsystem expects four valid elements between SOP and EOP, it misinterprets the EOP as a payload byte and desynchronizes for several subsequent transfers.

The root cause is a data coherency problem. In the withoutDualClockFIFO subsystem, the producer asserts data and valid at an effective rate of 50 MHz on a 100 MHz clock. These signals pass through a register in the 100 MHz domain, then through two registers in the 77 MHz consumer subsystem domain. Because 77 MHz and 100 MHz are unrelated frequencies, the 77 MHz clock can miss a valid pulse entirely. The pulse is only 10 ns wide, which is shorter than the 77 MHz clock period (~13 ns) time period, if no rising edge falls within that window the byte is lost. This causes the consumer to miss bytes and lose packet alignment.

Generate HDL IP Core

Generate a generic HDL IP core from the HDLDUT subsystem. In this example, you generate an IP core for a generic AMD platform. First, set up the Xilinx Vivado synthesis tool path by using the hdlsetuptoolpath function. Use your own Vivado installation path.

hdlsetuptoolpath("ToolName","Xilinx Vivado","ToolPath","C:\Xilinx\Vivado\2025.1\bin\vivado.bat")

Then, configure the model for IP core generation:

  1. In the Apps tab, click HDL Coder. In the HDL Code tab, in the Output section, set the button to IP Core. Set Code for to HDLDUT.

  2. Click Settings to open the Configuration Parameters dialog box. In the HDL Code Generation > Target pane, set Project Folder to hdl_prj, set Target Platform to Generic Xilinx Platform, and set Synthesis Tool to Xilinx Vivado.

  3. Click Settings > HDL Code Generation. In the Global Settings pane, set Clock inputs to Multiple.

Next, map the DUT ports to the IP core interfaces. In this example, map all DUT ports to AXI4-Lite interfaces:

  1. In the IP Core pane, select the Interface Mapping tab. If the mapping table does not appear, click the Reload IP core settings button to compile the model and populate the DUT ports and their data types.

  2. For all ports, set the Interface to AXI4-Lite.

  3. Validate your settings by clicking the Validate IP core settings button.

In the HDL Code tab, click Generate IP Core. After generation completes, the IP core report opens and displays details about the multiple-clock connections, the generated clock ports, and the AXI4-Lite register map. Inspect the generated HDL code for the DUT subsystem. The xpm_fifo_async instantiation with the values configured in the model: depth of 256, data width of 8 bits, and FWFT read mode, the code is instantiated in hdl_prj\hdlsrc\hdlcAsyncFIFO\HDLDUT_ip_src_HDL_Dual_Clock_FIFO.v. To learn how to configure the model for IP core generation and map the IP core interfaces using the HDL Coder Toolstrip, see Get Started with IP Core Generation from Simulink Model.

 xpm_fifo_async #(.CASCADE_HEIGHT(0),
                   .CDC_SYNC_STAGES(2),
                   .DOUT_RESET_VALUE("0"),
                   .ECC_MODE("no_ecc"),
                   .EN_SIM_ASSERT_ERR("warning"),
                   .FIFO_MEMORY_TYPE("auto"),
                   .FIFO_READ_LATENCY(1),
                   .FIFO_WRITE_DEPTH(256),
                   .FULL_RESET_VALUE(0),
                   .PROG_EMPTY_THRESH(10),
                   .PROG_FULL_THRESH(10),
                   .RD_DATA_COUNT_WIDTH(1),
                   .READ_DATA_WIDTH(8),
                   .READ_MODE("fwft"),
                   .RELATED_CLOCKS(0),
                   .SIM_ASSERT_CHK(0),
                   .USE_ADV_FEATURES("0707"),
                   .WAKEUP_TIME(0),
                   .WRITE_DATA_WIDTH(8),
                   .WR_DATA_COUNT_WIDTH(1))

Integrate the Generated HDL IP Core into an AMD Vivado SoC Project

To use the generic IP core as part of a larger system, create a Vivado project, integrate the IP core, and generate a bitstream for the ZedBoard. This image shows the connections in the Vivado block design. The first clock is derived from the Zynq Processing System clock (FCLK_CLK0, 100 MHz) and routed through a Clock Wizard that generates the 77 MHz consumer domain frequency.

The second clock is a fixed 100 MHz clock from the on-board crystal oscillator, brought into the programmable logic (PL) through the Xilinx Design Constraints (XDC) constraints file, and serves as the producer domain clock. For a detailed walkthrough of manual IP core integration in Vivado, see Generate Clock-Domain-Crossing Pulse Synchronizer by Generating a Multiple-Clock IP Core.

Vivado block design showing the Zynq Processing System connected to the generated HDLDUT IP core through an AXI interconnect, with a Clock Wizard generating the 77 MHz clock and a 100 MHz board clock input routed to the IP core.

This example includes Tcl scripts that automate project creation, block design assembly, IP insertion, clock and reset connections, and bitstream generation. The integration uses three files:

  • vivado_custom_design.tcl — Tcl file that creates the base block design containing the Zynq Processing System (PS), AXI interconnect, Clock Wizard, and reset generators. This file was created by using Vivado 2025.1.1.

  • board_clk_100.xdc — Constraints file that maps the 100 MHz oscillator on the ZedBoard to the board_clk_100 port in the block design.

  • build_custom_prj.tcl — Top-level script that creates the Vivado project in a new hdl_fifo folder, sources the block design, adds the generated IP core to the IP catalog, instantiates it in the block design, connects clocks and resets, adds timing constraints, and runs synthesis and implementation.

To create the project and build the bitstream, run:

!vivado -source build_custom_prj.tcl &

Interact with the FPGA Design by Using MATLAB

To compare error statistics between the withDualClockFIFO and withoutDualClockFIFO subsystems on real hardware, use an fpga object to interact with the deployed design.

Program the FPGA hardware with the generated bitstream and configure the processor with the corresponding device tree. The device tree devicetree_axilite_iio.dtb is available in the firmware image when you configure your board using the Hardware Setup app. When you use build_custom_prj.tcl for integration, the bitstream is in the hdl_fifo project folder.

hProcessor = xilinxsoc();
programFPGA(hProcessor, ...
    "hdl_fifo\hdl_fifo.runs\impl_1\system_top_wrapper.bit", ...
    remoteFile("devicetree_axilite_iio.dtb"));

Open and run the custom_interface_visualize.m script. The script creates an fpga object, configures the AXI4-Lite interfaces to interact with the DUT ports of the deployed IP core, reads the error counters from both paths, retrieves the packet payloads, and prints a side-by-side comparison.

run("custom_interface_visualize")

The script prints a formatted summary comparing both paths:

==============================================================
  CDC Path Comparison - Producer/Consumer Results
  Path A: Async FIFO (proper CDC)
  Path B: Dual-Register (no CDC)
==============================================================

                                 Path A     Path B
                                 (FIFO) (Dual-Reg)
  ------------------------------------------------
  Packets Received                  100         20
  Total Errors                        0        151
  Framing Errors                      0         40
  Checksum Errors                     0         30
  Desync Events                       0         81
  FIFO Empty                      false      false

  ------------------------------------------------
  Dequeued Payloads:
                                 Path A     Path B
  Packet 1                            2          4
  Packet 2                            3          9

  ------------------------------------------------
  Verdict:
  Path A (FIFO):      PASS - 100/100 packets, 0 errors
  Path B (Dual-Reg):  FAIL - 20/100 packets, 151 errors
     CDC errors expected - demonstrates why async FIFO is needed.

==============================================================

The withDualClockFIFO subsystem receives all 100 packets with 0 errors, and the retrieved payloads are consecutive, which confirms that the FIFO preserved the data integrity and ordering across the clock domains. The withoutDualClockFIFO subsystem receives fewer than 100 valid packets, which contains framing errors, checksum errors, and desynchronization events. The retrieved payloads are non-consecutive, which demonstrates that the dual-register approach does not reliably transfer multi-byte streaming data across asynchronous clock domains.

On real hardware, the results from the withoutDualClockFIFO subsystem differ from the simulation results for these reasons:

  • In Simulink®, the two clocks always start with a fixed, deterministic phase relationship, so the simulation misses the same bytes on every run. On hardware, the clocks come from independent sources and their relative phase is arbitrary at power-up and can drift over time, so different bytes are missed on each run and the error counts might not be repeatable.

  • Metastability can corrupt the sampled data values when a clock edge falls during the setup of a flip-flop or hold window.

This contrast confirms that for continuous or burst data transfers between asynchronous clock domains, a dual-clock FIFO is essential. Simple register synchronizers are only suitable for single-bit level signals or pulses with adequate spacing.

See Also

Apps

Topics