From 84a88274ba4b69719d51e037ffe14ba68e1e2c94 Mon Sep 17 00:00:00 2001
From: Eric Kooistra <kooistra@astron.nl>
Date: Wed, 26 Oct 2022 16:47:11 +0200
Subject: [PATCH] Support g_loopback_eth.

---
 libraries/io/eth/tb/vhdl/tb_eth_tester.vhd    | 201 +++++++++++++-----
 libraries/io/eth/tb/vhdl/tb_tb_eth_tester.vhd |  13 +-
 2 files changed, 159 insertions(+), 55 deletions(-)

diff --git a/libraries/io/eth/tb/vhdl/tb_eth_tester.vhd b/libraries/io/eth/tb/vhdl/tb_eth_tester.vhd
index ade99dfc7b..cc0ba0bfd5 100644
--- a/libraries/io/eth/tb/vhdl/tb_eth_tester.vhd
+++ b/libraries/io/eth/tb/vhdl/tb_eth_tester.vhd
@@ -40,6 +40,7 @@ USE common_lib.common_network_layers_pkg.ALL;
 USE dp_lib.dp_stream_pkg.ALL;
 USE dp_lib.dp_components_pkg.ALL;
 USE diag_lib.diag_pkg.ALL;
+USE work.eth_pkg.ALL;
 USE work.eth_tester_pkg.ALL;
 USE work.tb_eth_tester_pkg.ALL;
 
@@ -47,7 +48,8 @@ ENTITY tb_eth_tester IS
   GENERIC (
     g_tb_index         : NATURAL := 0;  -- use to incremental delay logging from tb instances in tb_tb
     g_tb_str           : STRING := "";  -- use to distinguish logging from tb instances in tb_tb
-    g_nof_streams      : NATURAL := 2;
+    g_nof_streams      : NATURAL := 2;  -- <= c_eth_nof_udp_ports = 4 when g_loopback_tx_rx = 1
+    g_loopback_eth     : BOOLEAN := TRUE;  -- FALSE = sosi loopback, TRUE = eth loopback
 
     -- t_diag_block_gen_integer =
     --   sl:  enable
@@ -66,26 +68,40 @@ END tb_eth_tester;
 
 ARCHITECTURE tb OF tb_eth_tester IS
     
-  CONSTANT mm_clk_period        : TIME := 10 ns;  -- 100 MHz
-  CONSTANT st_clk_period        : TIME :=  5 ns;  -- 200 MHz
+  CONSTANT eth_clk_period          : TIME :=  8 ns;  -- 125 MHz
+  CONSTANT mm_clk_period           : TIME := 10 ns;  -- 100 MHz
+  CONSTANT st_clk_period           : TIME :=  5 ns;  -- 200 MHz
 
-  CONSTANT c_slot_len_first     : NATURAL := g_bg_ctrl_first.samples_per_packet + g_bg_ctrl_first.gapsize;
-  CONSTANT c_slot_len_others    : NATURAL := g_bg_ctrl_others.samples_per_packet + g_bg_ctrl_others.gapsize;
+  CONSTANT c_bg_slot_len_first     : NATURAL := g_bg_ctrl_first.samples_per_packet + g_bg_ctrl_first.gapsize;
+  CONSTANT c_bg_slot_len_others    : NATURAL := g_bg_ctrl_others.samples_per_packet + g_bg_ctrl_others.gapsize;
 
-  CONSTANT c_sync_period_first  : NATURAL := c_slot_len_first * g_bg_ctrl_first.blocks_per_sync;
-  CONSTANT c_sync_period_others : NATURAL := c_slot_len_others * g_bg_ctrl_others.blocks_per_sync;
-  CONSTANT c_sync_period_max    : NATURAL := largest(c_sync_period_first, c_sync_period_others);
-  CONSTANT c_nof_sync           : NATURAL := 3;
-  CONSTANT c_run_time           : NATURAL := c_nof_sync * c_sync_period_max;
-  CONSTANT c_nof_sync_first     : NATURAL := c_run_time / c_sync_period_first;
-  CONSTANT c_nof_sync_others    : NATURAL := c_run_time / c_sync_period_others;
+  CONSTANT c_bg_sync_period_first  : NATURAL := c_bg_slot_len_first * g_bg_ctrl_first.blocks_per_sync;
+  CONSTANT c_bg_sync_period_others : NATURAL := c_bg_slot_len_others * g_bg_ctrl_others.blocks_per_sync;
+  CONSTANT c_bg_sync_period_max    : NATURAL := largest(c_bg_sync_period_first, c_bg_sync_period_others);
+
+  CONSTANT c_nof_sync              : NATURAL := 3;
+  CONSTANT c_run_time              : NATURAL := c_nof_sync * c_bg_sync_period_max;
+  CONSTANT c_nof_sync_first        : NATURAL := c_run_time / c_bg_sync_period_first;
+  CONSTANT c_nof_sync_others       : NATURAL := c_run_time / c_bg_sync_period_others;
+
+  -- Expected Tx --> Rx latency values obtained from a tb run
+  CONSTANT c_tx_exp_latency        : NATURAL := 0;
+  CONSTANT c_rx_exp_latency        : NATURAL := sel_a_b(g_loopback_eth, 450, 27);
+
+  -- CRC is added by ETH IP. Therefore Tx packet has no CRC yet and Rx
+  -- packet length depends on g_loopback_eth = TRUE
+  CONSTANT c_nof_valid_per_packet_first   : NATURAL := g_bg_ctrl_first.samples_per_packet + sel_a_b(g_loopback_eth, 4, 0);
+  CONSTANT c_nof_valid_per_packet_others  : NATURAL := g_bg_ctrl_others.samples_per_packet + sel_a_b(g_loopback_eth, 4, 0);
+
+  CONSTANT c_total_count_nof_valid_per_sync_first  : NATURAL := g_bg_ctrl_first.blocks_per_sync * c_nof_valid_per_packet_first;
+  CONSTANT c_total_count_nof_valid_per_sync_others : NATURAL := g_bg_ctrl_others.blocks_per_sync * c_nof_valid_per_packet_others;
 
   CONSTANT c_mon_nof_sop_first       : NATURAL := g_bg_ctrl_first.blocks_per_sync;
   CONSTANT c_mon_nof_sop_others      : NATURAL := g_bg_ctrl_others.blocks_per_sync;
   CONSTANT c_mon_nof_valid_first_tx  : NATURAL := c_mon_nof_sop_first * ceil_div(g_bg_ctrl_first.samples_per_packet * c_octet_w, c_word_w);
-  CONSTANT c_mon_nof_valid_first_rx  : NATURAL := c_mon_nof_sop_first * g_bg_ctrl_first.samples_per_packet;
-  CONSTANT c_mon_nof_valid_others_tx : NATURAL := c_mon_nof_sop_others * ceil_div(g_bg_ctrl_others.samples_per_packet * c_octet_w, c_word_w);
-  CONSTANT c_mon_nof_valid_others_rx : NATURAL := c_mon_nof_sop_others * g_bg_ctrl_others.samples_per_packet;
+  CONSTANT c_mon_nof_valid_first_rx  : NATURAL := c_mon_nof_sop_first * c_nof_valid_per_packet_first;
+  CONSTANT c_mon_nof_valid_others_tx : NATURAL := c_mon_nof_sop_others * ceil_div(g_bg_ctrl_others.samples_per_packet* c_octet_w, c_word_w);
+  CONSTANT c_mon_nof_valid_others_rx : NATURAL := c_mon_nof_sop_others * c_nof_valid_per_packet_others;
 
   -- Use sim default src MAC, IP, UDP port from eth_tester_pkg.vhd and based on c_gn_index
   CONSTANT c_gn_index           : NATURAL := 17;  -- global node index
@@ -101,6 +117,11 @@ ARCHITECTURE tb OF tb_eth_tester IS
   SIGNAL st_pps              : STD_LOGIC := '0';
   SIGNAL stimuli_end         : STD_LOGIC := '0';
   SIGNAL tb_end              : STD_LOGIC := '0';
+
+  SIGNAL eth_clk             : STD_LOGIC := '1';
+  SIGNAL eth_txp             : STD_LOGIC;
+  SIGNAL eth_rxp             : STD_LOGIC;
+
   -- Use same bg_ctrl for all streams, this provides sufficient test coverage
   SIGNAL bg_ctrl_arr         : t_diag_block_gen_integer_arr(g_nof_streams-1 DOWNTO 0);
 
@@ -126,12 +147,12 @@ ARCHITECTURE tb OF tb_eth_tester IS
   SIGNAL reg_strobe_total_count_rx_cipo  : t_mem_cipo;
 
   -- . reg_strobe_total_count
-  SIGNAL tx_total_count_nof_packets_arr       : t_natural_arr(g_nof_streams-1 DOWNTO 0);
-  SIGNAL rx_total_count_nof_packets_arr       : t_natural_arr(g_nof_streams-1 DOWNTO 0);
-  SIGNAL exp_total_count_nof_packets_arr      : t_natural_arr(g_nof_streams-1 DOWNTO 0);  -- same for both tx and rx
+  SIGNAL tx_total_count_nof_packet_arr        : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  SIGNAL rx_total_count_nof_packet_arr        : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  SIGNAL exp_total_count_nof_packet_arr       : t_natural_arr(g_nof_streams-1 DOWNTO 0);  -- same for both tx and rx
 
-  SIGNAL rx_total_count_nof_valids_arr        : t_natural_arr(g_nof_streams-1 DOWNTO 0);
-  SIGNAL rx_exp_total_count_nof_valids_arr    : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  SIGNAL rx_total_count_nof_valid_arr         : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  SIGNAL rx_exp_total_count_nof_valid_arr     : t_natural_arr(g_nof_streams-1 DOWNTO 0);
 
   SIGNAL rx_total_count_nof_corrupted_arr     : t_natural_arr(g_nof_streams-1 DOWNTO 0);
   SIGNAL rx_exp_total_count_nof_corrupted_arr : t_natural_arr(g_nof_streams-1 DOWNTO 0);
@@ -144,6 +165,15 @@ ARCHITECTURE tb OF tb_eth_tester IS
   SIGNAL rx_mon_nof_valid_arr    : t_natural_arr(g_nof_streams-1 DOWNTO 0);
   SIGNAL rx_mon_latency_arr      : t_natural_arr(g_nof_streams-1 DOWNTO 0);
 
+  -- ETH link, used when g_loopback_eth = TRUE
+  SIGNAL eth_tx_udp_sosi_arr     : t_dp_sosi_arr(c_eth_nof_udp_ports-1 DOWNTO 0) := (OTHERS=> c_dp_sosi_rst);  -- default rst if not used
+  SIGNAL eth_tx_udp_siso_arr     : t_dp_siso_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  SIGNAL eth_rx_udp_sosi_arr     : t_dp_sosi_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  SIGNAL eth_rx_udp_siso_arr     : t_dp_siso_arr(c_eth_nof_udp_ports-1 DOWNTO 0) := (OTHERS=> c_dp_siso_rst);  -- default rst if not used
+
+  SIGNAL reg_eth_copi            : t_mem_copi := c_mem_copi_rst;
+  SIGNAL reg_eth_cipo            : t_mem_cipo;
+
   -- View in Wave window
   SIGNAL dbg_c_mon_nof_sop_first       : NATURAL := c_mon_nof_sop_first;
   SIGNAL dbg_c_mon_nof_sop_others      : NATURAL := c_mon_nof_sop_others;
@@ -154,13 +184,14 @@ ARCHITECTURE tb OF tb_eth_tester IS
 
 BEGIN
 
+  eth_clk <= (NOT eth_clk) OR tb_end AFTER eth_clk_period/2;
   mm_clk <= (NOT mm_clk) OR tb_end AFTER mm_clk_period/2;
   st_clk <= (NOT st_clk) OR tb_end AFTER st_clk_period/2;
   mm_rst <= '1', '0' AFTER mm_clk_period*5;
   st_rst <= '1', '0' AFTER st_clk_period*5;
 
   -- Using
-  --SIGNAL exp_total_count_nof_packets_arr : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  --SIGNAL exp_total_count_nof_packet_arr : t_natural_arr(g_nof_streams-1 DOWNTO 0);
   --                         (g_nof_streams-1 DOWNTO 1 => c_nof_sync * g_bg_ctrl_others.blocks_per_sync,
   --                                                 0 => c_nof_sync * g_bg_ctrl_first.blocks_per_sync);
   -- yields verror 1074, verror 1048, therefor use p_init instead, and
@@ -170,11 +201,11 @@ BEGIN
     bg_ctrl_arr    <= (OTHERS => g_bg_ctrl_others);
     bg_ctrl_arr(0) <= g_bg_ctrl_first;
 
-    exp_total_count_nof_packets_arr    <= (OTHERS => c_nof_sync_others * g_bg_ctrl_others.blocks_per_sync);
-    exp_total_count_nof_packets_arr(0) <= c_nof_sync_first * g_bg_ctrl_first.blocks_per_sync;
+    exp_total_count_nof_packet_arr    <= (OTHERS => c_nof_sync_others * g_bg_ctrl_others.blocks_per_sync);
+    exp_total_count_nof_packet_arr(0) <= c_nof_sync_first * g_bg_ctrl_first.blocks_per_sync;
 
-    rx_exp_total_count_nof_valids_arr    <= (OTHERS => c_nof_sync_others * g_bg_ctrl_others.blocks_per_sync * g_bg_ctrl_others.samples_per_packet);
-    rx_exp_total_count_nof_valids_arr(0) <= c_nof_sync_first * g_bg_ctrl_first.blocks_per_sync * g_bg_ctrl_first.samples_per_packet;
+    rx_exp_total_count_nof_valid_arr    <= (OTHERS => c_nof_sync_others * c_total_count_nof_valid_per_sync_others);
+    rx_exp_total_count_nof_valid_arr(0) <= c_nof_sync_first * c_total_count_nof_valid_per_sync_first;
 
     rx_exp_total_count_nof_corrupted_arr <= (OTHERS => 0);  -- default no Rx errors
     WAIT;
@@ -185,15 +216,40 @@ BEGIN
   -----------------------------------------------------------------------------
   p_mm : PROCESS
     VARIABLE v_offset  : NATURAL;
+    VARIABLE v_port    : NATURAL;
+    VARIABLE v_value   : NATURAL;
   BEGIN
     proc_common_wait_until_low(mm_clk, mm_rst);
     proc_common_wait_some_cycles(mm_clk, 10);
 
+    ---------------------------------------------------------------------------
+    -- Rx UDP offload port
+    ---------------------------------------------------------------------------
+    v_port := TO_UINT(c_eth_tester_udp_dst_port);
+
+    IF g_loopback_eth = TRUE THEN
+      -- Set up demux in eth Rx based on destination UDP port
+      v_value := 2**16 + v_port;   -- enable bit 16, udp port number [15:0]
+      FOR I IN g_nof_streams-1 DOWNTO 0 LOOP
+        proc_mem_mm_bus_wr(0 + I, v_value + I, mm_clk, reg_eth_copi);   -- increment udp_dst_port per stream
+      END LOOP;
+    END IF;
+
+    FOR I IN g_nof_streams-1 DOWNTO 0 LOOP
+      v_offset := I * c_eth_tester_reg_hdr_dat_addr_span;
+      -- Set destination MAC/IP/UDP port in tx header, increment udp_dst_port per stream
+      -- The MM addresses follow from byte address_offset // 4 in eth.peripheral.yaml
+      proc_mem_mm_bus_wr(v_offset + 16#6#, v_port + I, mm_clk, reg_hdr_dat_cipo, reg_hdr_dat_copi);
+      proc_mem_mm_bus_wr(v_offset + 16#9#, TO_SINT(c_eth_tester_ip_dst_addr), mm_clk, reg_hdr_dat_cipo, reg_hdr_dat_copi);  -- use signed to fit 32 b in INTEGER
+      proc_mem_mm_bus_wr(v_offset + 16#17#, TO_SINT(c_eth_tester_eth_dst_mac(31 DOWNTO 0)), mm_clk, reg_hdr_dat_cipo, reg_hdr_dat_copi);  -- use signed to fit 32 b in INTEGER
+      proc_mem_mm_bus_wr(v_offset + 16#18#, TO_UINT(c_eth_tester_eth_dst_mac(47 DOWNTO 32)), mm_clk, reg_hdr_dat_cipo, reg_hdr_dat_copi);
+    END LOOP;
+
     ---------------------------------------------------------------------------
     -- Stimuli
     ---------------------------------------------------------------------------
     FOR I IN g_nof_streams-1 DOWNTO 0 LOOP
-      v_offset := I * c_diag_bg_reg_nof_dat;
+      v_offset := I * c_diag_bg_reg_adr_span;
       -- Prepare the BG
       proc_mem_mm_bus_wr(v_offset + 1, bg_ctrl_arr(I).samples_per_packet, mm_clk, reg_bg_ctrl_copi);
       proc_mem_mm_bus_wr(v_offset + 2, bg_ctrl_arr(I).blocks_per_sync,    mm_clk, reg_bg_ctrl_copi);
@@ -214,11 +270,11 @@ BEGIN
 
     -- Disable the BG
     FOR I IN g_nof_streams-1 DOWNTO 0 LOOP
-      v_offset := I * c_diag_bg_reg_nof_dat;
+      v_offset := I * c_diag_bg_reg_adr_span;
       -- Disable the other BG
       proc_mem_mm_bus_wr(v_offset + 0, 0, mm_clk, reg_bg_ctrl_copi);
     END LOOP;
-    proc_common_wait_some_cycles(st_clk, c_sync_period_max);
+    proc_common_wait_some_cycles(st_clk, sel_a_b(g_loopback_eth, 20, 1) * c_bg_sync_period_max);
     stimuli_end <= '1';
 
     -- Dealy logging between different tb instances
@@ -226,7 +282,7 @@ BEGIN
     print_str("");   -- log empty line between tb results
 
     -------------------------------------------------------------------------
-    -- Verification: Total nof Tx packets = total nof Rx packets
+    -- Verification: Total counts
     -------------------------------------------------------------------------
     FOR I IN g_nof_streams-1 DOWNTO 0 LOOP
       -- . read low part, ignore high part (= 0) of two word total counts
@@ -234,15 +290,15 @@ BEGIN
       -- Tx total nof packets
       proc_mem_mm_bus_rd(v_offset + 0, mm_clk, reg_strobe_total_count_tx_cipo, reg_strobe_total_count_tx_copi);
       proc_mem_mm_bus_rd_latency(1, mm_clk);
-      tx_total_count_nof_packets_arr(I) <= TO_UINT(reg_strobe_total_count_tx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      tx_total_count_nof_packet_arr(I) <= TO_UINT(reg_strobe_total_count_tx_cipo.rddata(c_word_w-1 DOWNTO 0));
       -- Rx total nof packets
       proc_mem_mm_bus_rd(v_offset + 0, mm_clk, reg_strobe_total_count_rx_cipo, reg_strobe_total_count_rx_copi);
       proc_mem_mm_bus_rd_latency(1, mm_clk);
-      rx_total_count_nof_packets_arr(I) <= TO_UINT(reg_strobe_total_count_rx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      rx_total_count_nof_packet_arr(I) <= TO_UINT(reg_strobe_total_count_rx_cipo.rddata(c_word_w-1 DOWNTO 0));
       -- Rx total nof valids
       proc_mem_mm_bus_rd(v_offset + 2, mm_clk, reg_strobe_total_count_rx_cipo, reg_strobe_total_count_rx_copi);
       proc_mem_mm_bus_rd_latency(1, mm_clk);
-      rx_total_count_nof_valids_arr(I) <= TO_UINT(reg_strobe_total_count_rx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      rx_total_count_nof_valid_arr(I) <= TO_UINT(reg_strobe_total_count_rx_cipo.rddata(c_word_w-1 DOWNTO 0));
       -- Rx total nof corrupted
       proc_mem_mm_bus_rd(v_offset + 4, mm_clk, reg_strobe_total_count_rx_cipo, reg_strobe_total_count_rx_copi);
       proc_mem_mm_bus_rd_latency(1, mm_clk);
@@ -252,31 +308,31 @@ BEGIN
       -- Print logging
       print_str(g_tb_str &
           "Tx total counts monitor(" & NATURAL'IMAGE(I) & ") :" &
-          " nof_packets = " & NATURAL'IMAGE(tx_total_count_nof_packets_arr(I)));
+          " nof_packet = " & NATURAL'IMAGE(tx_total_count_nof_packet_arr(I)));
 
       print_str(g_tb_str &
           "Rx total counts monitor(" & NATURAL'IMAGE(I) & ") :" &
-          " nof_packets = " & NATURAL'IMAGE(rx_total_count_nof_packets_arr(I)) &
-          ", nof_valids  = " & NATURAL'IMAGE(rx_total_count_nof_valids_arr(I)) &
+          " nof_packet = " & NATURAL'IMAGE(rx_total_count_nof_packet_arr(I)) &
+          ", nof_valid  = " & NATURAL'IMAGE(rx_total_count_nof_valid_arr(I)) &
           ", nof_corrupted  = " & NATURAL'IMAGE(rx_total_count_nof_corrupted_arr(I)));
 
       -- Verify, only log when wrong
-      ASSERT tx_total_count_nof_packets_arr(I) = exp_total_count_nof_packets_arr(I) REPORT g_tb_str &
+      ASSERT tx_total_count_nof_packet_arr(I) = exp_total_count_nof_packet_arr(I) REPORT g_tb_str &
           "Wrong Tx total nof packets count(" & NATURAL'IMAGE(I) &
-          "), Tx count = " & NATURAL'IMAGE(tx_total_count_nof_packets_arr(I)) &
-          " /= " & NATURAL'IMAGE(exp_total_count_nof_packets_arr(I)) &
+          "), Tx count = " & NATURAL'IMAGE(tx_total_count_nof_packet_arr(I)) &
+          " /= " & NATURAL'IMAGE(exp_total_count_nof_packet_arr(I)) &
           " = Expected count" SEVERITY ERROR;
 
-      ASSERT rx_total_count_nof_packets_arr(I) = exp_total_count_nof_packets_arr(I) REPORT g_tb_str &
+      ASSERT rx_total_count_nof_packet_arr(I) = exp_total_count_nof_packet_arr(I) REPORT g_tb_str &
           "Wrong Rx total nof packets count(" & NATURAL'IMAGE(I) &
-          "), Rx count = " & NATURAL'IMAGE(rx_total_count_nof_packets_arr(I)) &
-          " /= " & NATURAL'IMAGE(exp_total_count_nof_packets_arr(I)) &
+          "), Rx count = " & NATURAL'IMAGE(rx_total_count_nof_packet_arr(I)) &
+          " /= " & NATURAL'IMAGE(exp_total_count_nof_packet_arr(I)) &
           " = Expected count" SEVERITY ERROR;
 
-      ASSERT rx_total_count_nof_valids_arr(I) = rx_exp_total_count_nof_valids_arr(I) REPORT g_tb_str &
+      ASSERT rx_total_count_nof_valid_arr(I) = rx_exp_total_count_nof_valid_arr(I) REPORT g_tb_str &
           "Wrong Rx total nof valids count(" & NATURAL'IMAGE(I) &
-          "), Rx count = " & NATURAL'IMAGE(rx_total_count_nof_valids_arr(I)) &
-          " /= " & NATURAL'IMAGE(rx_exp_total_count_nof_valids_arr(I)) &
+          "), Rx count = " & NATURAL'IMAGE(rx_total_count_nof_valid_arr(I)) &
+          " /= " & NATURAL'IMAGE(rx_exp_total_count_nof_valid_arr(I)) &
           " = Expected count" SEVERITY ERROR;
 
       ASSERT rx_total_count_nof_corrupted_arr(I) = rx_exp_total_count_nof_corrupted_arr(I) REPORT g_tb_str &
@@ -341,8 +397,8 @@ BEGIN
         ASSERT tx_mon_nof_valid_arr(I) = c_mon_nof_valid_others_tx REPORT g_tb_str & "Wrong tx nof_valid for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
         ASSERT rx_mon_nof_valid_arr(I) = c_mon_nof_valid_others_rx REPORT g_tb_str & "Wrong rx nof_valid for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
       END IF;
-      ASSERT tx_mon_latency_arr(I) = 0 REPORT g_tb_str & "Wrong tx latency for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
-      ASSERT rx_mon_latency_arr(I) = 27 REPORT g_tb_str & "Wrong rx latency for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+      ASSERT tx_mon_latency_arr(I) = c_tx_exp_latency REPORT g_tb_str & "Wrong tx latency for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+      ASSERT rx_mon_latency_arr(I) = c_rx_exp_latency REPORT g_tb_str & "Wrong rx latency for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
     END LOOP;
 
     -------------------------------------------------------------------------
@@ -353,9 +409,6 @@ BEGIN
     WAIT;
   END PROCESS;
 
-  -- Wire Tx to Rx
-  rx_udp_sosi_arr <= tx_udp_sosi_arr;
-
   dut : ENTITY work.eth_tester
   GENERIC MAP (
     g_nof_streams      => g_nof_streams
@@ -396,4 +449,54 @@ BEGIN
     reg_strobe_total_count_rx_cipo => reg_strobe_total_count_rx_cipo
   );
   
+  -- Wire Tx to Rx
+  gen_loopback_sosi : IF g_loopback_eth = FALSE GENERATE
+    rx_udp_sosi_arr <= tx_udp_sosi_arr;
+  END GENERATE;
+
+  gen_loopback_eth : IF g_loopback_eth = TRUE GENERATE
+    eth_rxp <= eth_txp;
+
+    eth_tx_udp_sosi_arr(g_nof_streams-1 DOWNTO 0) <= tx_udp_sosi_arr;
+    tx_udp_siso_arr <= eth_tx_udp_siso_arr(g_nof_streams-1 DOWNTO 0);
+
+    rx_udp_sosi_arr <= eth_rx_udp_sosi_arr(g_nof_streams-1 DOWNTO 0);
+    eth_rx_udp_siso_arr(g_nof_streams-1 DOWNTO 0) <= (OTHERS => c_dp_siso_rdy);
+
+    u_eth : ENTITY work.eth
+    GENERIC MAP (
+      g_init_ip_address    => X"0A630000",
+      g_cross_clock_domain => TRUE,
+      g_sim                => TRUE,
+      g_sim_level          => 1  -- when g_sim = TRUE, then 0 = use IP; 1 = use fast serdes model
+    )
+    PORT MAP (
+      -- Clocks and reset
+      mm_rst             => mm_rst,
+      mm_clk             => mm_clk,
+      eth_clk            => eth_clk,  -- ethernet phy reference clock
+      st_rst             => st_rst,
+      st_clk             => st_clk,
+
+      -- UDP transmit interface
+      udp_tx_snk_in_arr  => eth_tx_udp_sosi_arr,
+      udp_tx_snk_out_arr => eth_tx_udp_siso_arr,
+      -- UDP receive interface
+      udp_rx_src_in_arr  => eth_rx_udp_siso_arr,
+      udp_rx_src_out_arr => eth_rx_udp_sosi_arr,
+
+      -- Memory Mapped Slaves
+      tse_sla_in         => c_mem_copi_rst,  -- ETH TSE MAC registers
+      tse_sla_out        => OPEN,
+      reg_sla_in         => reg_eth_copi,  -- ETH control and status registers
+      reg_sla_out        => reg_eth_cipo,
+      reg_sla_interrupt  => OPEN,            -- Interrupt
+      ram_sla_in         => c_mem_copi_rst,  -- ETH rx frame and tx frame memory
+      ram_sla_out        => OPEN,
+
+      -- PHY interface
+      eth_txp            => eth_txp,
+      eth_rxp            => eth_rxp
+    );
+  END GENERATE;
 END tb;
diff --git a/libraries/io/eth/tb/vhdl/tb_tb_eth_tester.vhd b/libraries/io/eth/tb/vhdl/tb_tb_eth_tester.vhd
index 4c004f2ac0..8afc5cbad6 100644
--- a/libraries/io/eth/tb/vhdl/tb_tb_eth_tester.vhd
+++ b/libraries/io/eth/tb/vhdl/tb_tb_eth_tester.vhd
@@ -52,6 +52,7 @@ BEGIN
 --  g_tb_index         : NATURAL := 0;  -- use to incremental delay logging from tb instances in tb_tb
 --  g_tb_str           : STRING := "";  -- use to distinguish logging from tb instances in tb_tb
 --  g_nof_streams      : NATURAL := 2;
+--  g_loopback_eth     : BOOLEAN := FALSE;  -- FALSE = sosi loopback, TRUE = eth loopback
 --
 --  -- t_diag_block_gen_integer =
 --  --   sl:  enable
@@ -65,13 +66,13 @@ BEGIN
 --  g_bg_ctrl_first    : t_diag_block_gen_integer := ('1', '1', 50, 8, 100, 0, 30, 0);  -- for first stream
 --  g_bg_ctrl_others   : t_diag_block_gen_integer := ('1', '1', 30, 8, 10, 0, 30, 0)   -- for other streams
 
-  u_one_stream        : ENTITY work.tb_eth_tester GENERIC MAP (0, "tb_one_stream: ", 1, c_bg_ctrl_first, c_bg_ctrl_rst);
-  u_multiple_streams  : ENTITY work.tb_eth_tester GENERIC MAP (1, "tb_multiple_streams: ", 3, c_bg_ctrl_first, c_bg_ctrl_others);
+  u_one_stream        : ENTITY work.tb_eth_tester GENERIC MAP (0, "tb_one_stream: ",       1, FALSE, c_bg_ctrl_first, c_bg_ctrl_rst);
+  u_multiple_streams  : ENTITY work.tb_eth_tester GENERIC MAP (1, "tb_multiple_streams: ", 3, FALSE, c_bg_ctrl_first, c_bg_ctrl_others);
 
   -- Try different BG block lengths to verify nof octets in last word
-  u_bg_len_0          : ENTITY work.tb_eth_tester GENERIC MAP (10, "tb_bg_len_0: ", 1, c_bg_ctrl_len_0, c_bg_ctrl_rst);
-  u_bg_len_1          : ENTITY work.tb_eth_tester GENERIC MAP (11, "tb_bg_len_1: ", 1, c_bg_ctrl_len_1, c_bg_ctrl_rst);
-  u_bg_len_2          : ENTITY work.tb_eth_tester GENERIC MAP (12, "tb_bg_len_2: ", 1, c_bg_ctrl_len_2, c_bg_ctrl_rst);
-  u_bg_len_3          : ENTITY work.tb_eth_tester GENERIC MAP (13, "tb_bg_len_3: ", 1, c_bg_ctrl_len_3, c_bg_ctrl_rst);
+  u_bg_len_0          : ENTITY work.tb_eth_tester GENERIC MAP (10, "tb_bg_len_0: ", 1, FALSE, c_bg_ctrl_len_0, c_bg_ctrl_rst);
+  u_bg_len_1          : ENTITY work.tb_eth_tester GENERIC MAP (11, "tb_bg_len_1: ", 1, FALSE, c_bg_ctrl_len_1, c_bg_ctrl_rst);
+  u_bg_len_2          : ENTITY work.tb_eth_tester GENERIC MAP (12, "tb_bg_len_2: ", 1, FALSE, c_bg_ctrl_len_2, c_bg_ctrl_rst);
+  u_bg_len_3          : ENTITY work.tb_eth_tester GENERIC MAP (13, "tb_bg_len_3: ", 1, FALSE, c_bg_ctrl_len_3, c_bg_ctrl_rst);
 
 END tb;
-- 
GitLab