Timing in digital systems was a very challenging subject when I first saw it. Metastability, synchronization, MTBF (mean time between failure), setup & hold times, clock skew, clock jitter are some of the concepts about timing in digital systems. When you work in a single clock domain, where a single clock drives all flip-flops, timing analysis is not that complex in FPGA. The tools are very powerful, for example in Vivado, you can enter timing constraints in a lot of ways. There is “Constraint Wizard” and “Edit Timing Constraints” methods after the synthesis. You can also give timing constraint in xdc file easily just by writing create_clock method. Then in the Implementation stage, Vivado analyze the setup and hold times for all the Flip-Flops with the information of logic delays of LUTs, CARRY and wiring delays. Vivado gives you timing report and critical path information.
In order to understand deeply about timing in digital design systems, I can suggest two books:
* Pong P. Chu – RTL Hardware Design using VHDL, Coding for Efficiency, Portability and Scalability
Section 6.5 Timing Considerations
Section 8.6 Timing analysis of a synchronous sequential circuit
Section 16 Clock and Synchronization: Principle and Practice
* David Money Harris & Sarah L. Harris – Digital Design and Computer Architecture
Section 2.9 Combination Logic Timing
Section 3.5 Timing of Sequential Logic
I also created a video playlist titled “FPGA Programming with VHDL” and the videos from 30 to 39 are about timing analysis, CDC and static timing analysis in Vivado (Videos are in Turkish Language). In CDC part, I showed and designed a synchronization circuit to safe clock domain crossing.
https://www.youtube.com/watch?v=dEtN1D4SLJY&list=PLZyLAHn509339oyv3vi-3Gdyb8bfPx7Ro&index=31&t=15s
You can access the cdc code that I used for synchronization and the example VHDL codes in my github page:
https://github.com/mbaykenar/apis_anatolia/tree/main/VHDL_ile_FPGA_PROGRAMLAMA/ders38
At that time, I was aware of using FIFOs for clock domain crossing synchronizations but couldn’t find enough time to make an example usage of it. Now it is the time!
I want to create an example code, which receives UART data in 50 mbps baud rate and then transmit the received data again in 50 mbps baud rate. In most applications baud rate would not be that high in UART protocol. However, I choose this high rates to utilize FIFOs for CDC synchronization purposes. The design has a uart receiver module. The received byte will be pushed to the recv FIFO. This part must be clocked by a high frequency clock such as 250 MHz to sample 50 mbps uart bit at least 5 times, which is OK in practice. Usually sample rate for a bit is 8 or 16 in most UART systems. Then when the empty flag is not zero, the data will be popped, however this popping part will be clocked at a lower frequency, 100 MHz in our case. The received byte then will be pushed to the transmit FIFO. This time the byte which is wanted to be transmitted is popped and sent to the uart transmit module. This part also will be clocking in 250 MHz. The FIFOs would perform synchronization in domain crossing parts.
Xilinx offers FIFO ip in its IP catalog page in Vivado:

The product guide for the FIFO Generator IP could be achieved in:
This image is from the IP product guide:

I created 2 input ports for two clock signals, and rx & tx for UART:
entity high_speed_uart is
generic (
c_sysclkfreq : integer := 100_000_000;
c_uartclkfreq : integer := 250_000_000;
c_baudrate : integer := 50_000_000;
c_stopbit : integer := 2
);
port (
clk100 : in std_logic;
clk250 : in std_logic;
rx_i : in std_logic;
tx_o : out std_logic
);
end high_speed_uart;
I also used a synchronizer module at the input of the uart receiver module to avoid metastability issues as UART is asynchronous to the system, as the name suggests: Universal Asynchronous Receiver Transmitter. The synchronizer is just cascaded FFs. I instantiated FIFOs using the instantiation template generated by Vivado:

I also created processes, the sensitivity list and rising_edge() function input defines the clock domain. The final code of the top module is given:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity high_speed_uart is
generic (
c_sysclkfreq : integer := 100_000_000;
c_uartclkfreq : integer := 250_000_000;
c_baudrate : integer := 50_000_000;
c_stopbit : integer := 2
);
port (
clk100 : in std_logic;
clk250 : in std_logic;
rx_i : in std_logic;
tx_o : out std_logic
);
end high_speed_uart;
architecture Behavioral of high_speed_uart is
------------------------------------------------------------------------------
-- SIGNAL DEFINITIONS
------------------------------------------------------------------------------
-- UART TRANSCIEVER SIGNALS
signal rx_sync : std_logic := '1';
signal rx_done_tick : std_logic := '0';
signal tx_start : std_logic := '0';
signal tx_done_tick : std_logic := '0';
signal dout : std_logic_vector (7 downto 0) := (others => '0');
signal din : std_logic_vector (7 downto 0) := (others => '0');
-- RECEIVE FIFO SIGNALS
signal rcv_fifo_wr_en : std_logic := '0';
signal rcv_fifo_rd_en : std_logic := '0';
signal rcv_fifo_empty : std_logic := '0';
signal rcv_fifo_dout : std_logic_vector (7 downto 0) := (others => '0');
-- TRANSMIT FIFO SIGNALS
signal xmit_fifo_wr_en : std_logic := '0';
signal xmit_fifo_rd_en : std_logic := '0';
signal xmit_fifo_empty : std_logic := '0';
signal xmit_fifo_din : std_logic_vector (7 downto 0) := (others => '0');
-- CONTROL SIGNALS
signal fetch_rcv_fifo_data : std_logic := '0';
signal fetch_xmit_fifo_data : std_logic := '0';
signal xmit_uart_busy : std_logic := '0';
begin
------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
-- COMPONENT INSTANTIATIONS
------------------------------------------------------------------------------
I_uart_rx : entity work.uart_rx
generic map (
c_clkfreq => c_uartclkfreq ,
c_baudrate => c_baudrate
)
port map (
clk => clk250 ,
rx_i => rx_sync ,
dout_o => dout ,
rx_done_tick_o => rx_done_tick
);
I_uart_tx : entity work.uart_tx
generic map (
c_clkfreq => c_uartclkfreq ,
c_baudrate => c_baudrate ,
c_stopbit => c_stopbit
)
port map (
clk => clk250 ,
din_i => din ,
tx_start_i => tx_start ,
tx_o => tx_o ,
tx_done_tick_o => tx_done_tick
);
I_synchonizer : entity work.synchonizer
generic map (
c_ff_number => 3,
c_init_val => '1'
)
Port map (
clk => clk250 ,
data_i => rx_i ,
data_o => rx_sync
);
I_receive_fifo : entity work.receive_fifo
port map(
wr_clk => clk250,
rd_clk => clk100,
din => dout,
wr_en => rcv_fifo_wr_en,
rd_en => rcv_fifo_rd_en,
dout => rcv_fifo_dout,
full => open,
empty => rcv_fifo_empty
);
I_transmit_fifo : entity work.transmit_fifo
port map(
wr_clk => clk100,
rd_clk => clk250,
din => xmit_fifo_din,
wr_en => xmit_fifo_wr_en,
rd_en => xmit_fifo_rd_en,
dout => din,
full => open,
empty => xmit_fifo_empty
);
P_RECV : process (clk250) begin
if rising_edge(clk250) then
rcv_fifo_wr_en <= '0';
if (rx_done_tick = '1') then
rcv_fifo_wr_en <= '1';
end if;
end if;
end process P_RECV;
P_READ : process (clk100) begin
if rising_edge(clk100) then
rcv_fifo_rd_en <= '0';
if (rcv_fifo_empty = '0') then
rcv_fifo_rd_en <= '1';
end if;
fetch_rcv_fifo_data <= '0';
if (rcv_fifo_rd_en = '1') then
fetch_rcv_fifo_data <= '1';
rcv_fifo_rd_en <= '0';
end if;
end if;
end process P_READ;
P_WRITE : process (clk100) begin
if rising_edge(clk100) then
xmit_fifo_wr_en <= '0';
if (fetch_rcv_fifo_data = '1') then
xmit_fifo_din <= rcv_fifo_dout;
xmit_fifo_wr_en <= '1';
end if;
end if;
end process P_WRITE;
P_XMIT : process (clk250) begin
if rising_edge(clk250) then
xmit_fifo_rd_en <= '0';
if (xmit_fifo_empty = '0' and xmit_uart_busy = '0') then
xmit_fifo_rd_en <= '1';
xmit_uart_busy <= '1';
end if;
fetch_xmit_fifo_data <= '0';
if (xmit_fifo_rd_en = '1') then
fetch_xmit_fifo_data <= '1';
end if;
tx_start <= '0';
if (fetch_xmit_fifo_data = '1') then
tx_start <= '1';
end if;
if (tx_done_tick = '1') then
xmit_uart_busy <= '0';
end if;
end if;
end process P_XMIT;
end Behavioral;
I created timing constraints for these 2 clock signals:
create_clock -period 10.000 -name clk100 -waveform {0.000 5.000} [get_ports clk100]
create_clock -period 4.000 -name clk250 -waveform {0.000 2.000} [get_ports clk250]
You can access all the design, testbench and constraint files in my github page:
https://github.com/mbaykenar/apis_anatolia/tree/main/website/using_fifo_for_cdc_uart_ex
I modified the FIFO parameters in the GUI provided by the Vivado. I chosed 4 as Synchronization Stages option.


I synthesized and implemented the design. The utilization results are:

The timing report showed no failure:

In the next post, I will create a testbench and use UVVM library to verify the module. I will show the FIFO signals inside the module how the synchronization occurs.
Regards,
Mehmet Burak AYKENAR
You can connect me via LinledIn: Just sent me an invitation