In my last post, I utilized FIFOs for CDC synchronization for a high speed UART transciever system. To remeber, there were a UART receiver and a UART transmitter, which run at 250 MHz and the internal logic runs at 100 MHz. There are FIFOs between two clock domains, where ona side of the FIFO runs with 100 MHz and other side is with 250 MHz. The synchronization was obtained utilizing FIFOs from Xilinx IP catalog and using 4 as synchronizations stages. You can find the details here:
Now it is time to simulate this system with UVVM library. I showed how to compile UVVM library into Modelsim in this post:
At that post I only verify the transmitter. However, now there is also the receiver, and the synchronization and loop back logic. There are also two clock signals. In order to access data types, functions and procedures about UART you need to include uart_bfm_pkg in the testbench file:
library uvvm_util;
context uvvm_util.uvvm_util_context;
use uvvm_util.uart_bfm_pkg.all;
I named the testbench file as tb_high_speed_uart:
entity tb_high_speed_uart is
end tb_high_speed_uart;
I defined some constants:
constant c_clkperiod100 : time := 10 ns;
constant c_clkperiod250 : time := 4 ns;
constant c_clkfreq100 : integer := 100_000_000;
constant c_clkfreq250 : integer := 250_000_000;
constant c_baudrate : integer := 50_000_000;
constant c_clock_high_percentage : integer := 50;
This signal is also needed to configure the UART interface:
signal uart_bfm_config : t_uart_bfm_config := C_UART_BFM_CONFIG_DEFAULT;
The DUT instantiation part:
DUT : entity work.high_speed_uart
generic map(
c_sysclkfreq => c_clkfreq100,
c_uartclkfreq => c_clkfreq250,
c_baudrate => c_baudrate,
c_stopbit => 2
)
port map(
clk100 => clk100 ,
clk250 => clk250 ,
rx_i => rx_i ,
tx_o => tx_o
);
There are 2 clocks in the design. clock_generator procedure is defined and implemented in methods_pkg package file. There are some overloading on this procedure, so you can select which procedure is better suited for you. This one is just the simplest:
procedure clock_generator(
signal clock_signal : inout std_logic;
constant clock_period : in time;
constant clock_high_percentage : in natural range 1 to 99 := 50
);
And here is the implementation:
--------------------------------------------
-- Clock generators :
-- Include this as a concurrent procedure from your test bench.
-- ( Including this procedure call as a concurrent statement directly in your architecture
-- is in fact identical to a process, where the procedure parameters is the sensitivity list )
-- Set duty cycle by setting clock_high_percentage from 1 to 99. Beware of rounding errors.
--------------------------------------------
procedure clock_generator(
signal clock_signal : inout std_logic;
constant clock_period : in time;
constant clock_high_percentage : in natural range 1 to 99 := 50
) is
-- Making sure any rounding error after calculating period/2 is not accumulated.
constant C_FIRST_HALF_CLK_PERIOD : time := clock_period * clock_high_percentage/100;
begin
loop
clock_signal <= '1';
wait for C_FIRST_HALF_CLK_PERIOD;
clock_signal <= '0';
wait for (clock_period - C_FIRST_HALF_CLK_PERIOD);
end loop;
end;
So I created two clocks, one is 250 MHz and the other is 100 MHz:
-----------------------------------------------------------------------------
-- Clock Generator
-----------------------------------------------------------------------------
clock_generator(clk100, c_clkperiod100, c_clock_high_percentage);
clock_generator(clk250, c_clkperiod250, c_clock_high_percentage);
I made the initializations in the main stimuli process:
-- uart initializations
uart_bfm_config.bit_time <= 20 ns;
uart_bfm_config.num_data_bits <= 8;
uart_bfm_config.idle_state <= '1';
uart_bfm_config.num_stop_bits <= STOP_BITS_ONE;
uart_bfm_config.parity <= PARITY_NONE;
uart_bfm_config.timeout <= 0 ns;
uart_bfm_config.timeout_severity <= error;
wait for 1 ps;
Then I sent 256 bytes, from 0 to 255 in a for loop and used uart_expect procedure to check if the correct byte is transmitted from my design:
for i in 0 to 255 loop
xmit_byte := CONV_STD_LOGIC_VECTOR(i,8);
wait for 1 ps;
uart_transmit(
xmit_byte, -- data_value
"data transmitted", -- msg
rx_i, -- tx
uart_bfm_config, -- config
C_SCOPE, -- scope
shared_msg_id_panel -- msg_id_panel
);
wait for 1 ps;
uart_expect(
xmit_byte,
"data transmitted", -- msg
tx_o, -- rx
terminate_loop, -- terminate_loop
1, -- max_receptions
-1 ns, -- timeout
ERROR, -- alert_level
uart_bfm_config, -- config
C_SCOPE, -- scope
shared_msg_id_panel -- msg_id_panel
);
wait for 1 ps;
end loop;
You can find tb_high_speed_uart.vhd file from my github page:
https://github.com/mbaykenar/apis_anatolia/tree/main/website/using_fifo_for_cdc_uart_ex/sim
I opened the Vivado project for this design and change simulation settings:

Here vhdl_syntax should be 2008 to run successfully UVVM, also the IP libraries must have been compiled to a location. You can read my old post where I showed how to run Modelsim from Vivado and compile Xilinx IPs here:
https://www.mehmetburakaykenar.com/an-introductory-modelsim-tutorial-for-vivado-xilinx-users/116/
When you press run simulation in Vivado, Modelsim is opened and ready for run. You can push run all button from the Modelsim GUI or just write “run -all” in the console. Here is the waveforms for all simulation time:

Waveform analysis is not needed with UVVM report capability. There is no error in the simulation, you can find the transmission and receive process log of the last byte here:
# UVVM: ID_BFM 140450.0 ns TB seq. uart_transmit(x"FF") completed. 'data transmitted'
# UVVM: ID_BFM_WAIT 140450.0 ns TB seq. Expecting data x"FF" within 1 occurrences. data transmitted
# UVVM: ID_POS_ACK 140798.0 ns TB seq. check_value() => OK, for std_logic '1'. 'data transmitted'
# UVVM: ID_POS_ACK 140798.0 ns TB seq. check_value() => OK, for std_logic '1'. 'data transmitted'
# UVVM: ID_POS_ACK 140798.0 ns TB seq. check_value() => OK, for std_logic '1'. 'data transmitted'
# UVVM: ID_POS_ACK 140798.0 ns TB seq. check_value() => OK, for std_logic '1'. 'data transmitted'
# UVVM: ID_POS_ACK 140798.0 ns TB seq. check_value() => OK, for std_logic '1'. 'data transmitted'
# UVVM: ID_POS_ACK 140798.0 ns TB seq. check_value() => OK, for std_logic '1'. 'data transmitted'
# UVVM: ID_POS_ACK 140798.0 ns TB seq. check_value() => OK, for std_logic '1'. 'data transmitted'
# UVVM: ID_POS_ACK 140798.0 ns TB seq. check_value() => OK, for std_logic '1'. 'data transmitted'
# UVVM: ID_BFM 140798.0 ns TB seq. uart_expect(x"FF")=> OK, received data = x"FF" after 1 occurrences and 347.997 ns. 'data
# UVVM: transmitted'
You can access the log file from here:
https://github.com/mbaykenar/apis_anatolia/tree/main/website/using_fifo_for_cdc_uart_ex/sim
I wonder the FIFO synchronization effect on delay so I looked at the waveforms and see what is the latency from rx_done_tick going high to rcv_fifo_empty going low:

In this situation the delay is 68.1 ns. However, this delay is not deterministic because the phase difference between clocks is not constant. You can see in another situation it is 70.1 ns:

You can also expect the latency is much higher from rx_done_tick going high to tx_start going high:

If the system were running by a single clock, there would be no need to synchronization and this latency would be 1 clock cycle.
In this post, I showed how to test my previous design, which is a high speed uart transciever with FIFOs to CDC synchronization between two clock domains.
I will continue to elaborate on UVVM by testing an SPI master module that I designed before in the next post.
Regards,
Mehmet Burak AYKENAR
You can connect me via LinledIn: Just sent me an invitation
Hi Burak,
Everything you shared is very useful for learning FPGA software. Thanks for all the sharing.
I am triying to do the project in this post. But i have problems. When i add the testbench file you shared (tb_high_speed_uart.vhd) to the vivado project, i get syntax error due to the uvm library. So that i can not set the this file as a top file. I set correctly target simulation, compiled library location, vhdl_syntax(2008). Is there any settings to do? Should we fix the syntax error caused by the uvm library in the testbench file or do we need to run it with a syntax error?