diff --git a/applications/lofar2/libraries/sdp/src/vhdl/sdp_beamformer_output.vhd b/applications/lofar2/libraries/sdp/src/vhdl/sdp_beamformer_output.vhd
index b14d63d3cc046861baef44eebfb98dce5ab46a48..a3aae746b9805eaa40045e50701aea0386337e6d 100644
--- a/applications/lofar2/libraries/sdp/src/vhdl/sdp_beamformer_output.vhd
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_beamformer_output.vhd
@@ -292,7 +292,8 @@ BEGIN
     g_data_w        => c_longword_w,
     g_symbol_w      => c_byte_w,
     g_hdr_field_arr => c_sdp_cep_hdr_field_arr,
-    g_hdr_field_sel => c_sdp_cep_hdr_field_sel
+    g_hdr_field_sel => c_sdp_cep_hdr_field_sel,
+    g_pipeline_ready => TRUE
   )
   PORT MAP (
     mm_rst                => mm_rst,
diff --git a/libraries/base/common/src/vhdl/common_network_layers_pkg.vhd b/libraries/base/common/src/vhdl/common_network_layers_pkg.vhd
index 3d69a4f27e1f5a657af5c104111035daff98f6a0..55d5a853ebc8cb532ef9dab1119f8312609290e5 100644
--- a/libraries/base/common/src/vhdl/common_network_layers_pkg.vhd
+++ b/libraries/base/common/src/vhdl/common_network_layers_pkg.vhd
@@ -51,9 +51,13 @@ PACKAGE common_network_layers_pkg IS
   -- |                Frame Check Sequence                                  |
   -- |------------------------------------------------------------ // ------|
   --
+  -- . The word_align field is optional in and can only exist inside the
+  --   firmware, not on the link. It is removed by the TSE before transmit
+  --   and inserted by TSE after receive,
   
   -- field widths in bits '_w' or in bytes '_len', '_min', '_max', '_sz'
   CONSTANT c_network_eth_preamble_len      : NATURAL := 8;
+  CONSTANT c_network_eth_word_align_len    : NATURAL := 2;  -- to align 14 octets eth header at 32b word
   CONSTANT c_network_eth_mac_addr_len      : NATURAL := 6;
   CONSTANT c_network_eth_mac_addr_w        : NATURAL := c_network_eth_mac_addr_len*c_8;
   CONSTANT c_network_eth_type_len          : NATURAL := 2;
diff --git a/libraries/base/common/src/vhdl/common_pkg.vhd b/libraries/base/common/src/vhdl/common_pkg.vhd
index dd8e2b4c9e2b3864e09fddd1ca1780bee457813b..55bf1da32ec2cac8f6a1c035ea219a7c4ade3a75 100644
--- a/libraries/base/common/src/vhdl/common_pkg.vhd
+++ b/libraries/base/common/src/vhdl/common_pkg.vhd
@@ -197,7 +197,9 @@ PACKAGE common_pkg IS
   -- use almost_equal(a/b, 1.0, max_ratio) to verify that a and b differ less than max_ratio/100 percent
   -- use almost_zero(a/b, max_ratio) to verify that a is less than max_ratio/100 percent of b, so almost zero
   FUNCTION almost_equal(a, b, delta : REAL) RETURN BOOLEAN;  -- return TRUE when abs(a - b) < abs(delta), else return FALSE
+  FUNCTION almost_equal(a, b, delta : INTEGER) RETURN BOOLEAN;
   FUNCTION almost_zero(a, delta : REAL) RETURN BOOLEAN;      -- return TRUE when abs(a)     < abs(delta), else return FALSE
+  FUNCTION almost_zero(a, delta : INTEGER) RETURN BOOLEAN;
   
   FUNCTION ceil_div(   n, d : NATURAL)  RETURN NATURAL;   -- ceil_div    = n/d + (n MOD d)/=0
   FUNCTION ceil_value( n, d : NATURAL)  RETURN NATURAL;   -- ceil_value  = ceil_div(n, d) * d
@@ -672,7 +674,16 @@ PACKAGE BODY common_pkg IS
   
   FUNCTION almost_equal(a, b, delta : REAL) RETURN BOOLEAN IS
   BEGIN
-    IF ABS(a - b) < ABS(delta) THEN
+    IF ABS(a - b) <= ABS(delta) THEN
+      RETURN TRUE;
+    ELSE
+      RETURN FALSE;
+    END IF;
+  END;
+
+  FUNCTION almost_equal(a, b, delta : INTEGER) RETURN BOOLEAN IS
+  BEGIN
+    IF ABS(a - b) <= ABS(delta) THEN
       RETURN TRUE;
     ELSE
       RETURN FALSE;
@@ -684,6 +695,11 @@ PACKAGE BODY common_pkg IS
     RETURN almost_equal(a, 0.0, delta);
   END;
   
+  FUNCTION almost_zero(a, delta : INTEGER) RETURN BOOLEAN IS
+  BEGIN
+    RETURN almost_equal(a, 0, delta);
+  END;
+
   FUNCTION ceil_div(n, d : NATURAL) RETURN NATURAL IS
   BEGIN
     RETURN n/d + sel_a_b(n MOD d = 0, 0, 1);
diff --git a/libraries/base/diag/src/vhdl/diag_block_gen.vhd b/libraries/base/diag/src/vhdl/diag_block_gen.vhd
index 1956204af9265c2562b5343127229da3dbc8b9b7..7e48ee30e5199d813937d0747c05a394401df2b9 100644
--- a/libraries/base/diag/src/vhdl/diag_block_gen.vhd
+++ b/libraries/base/diag/src/vhdl/diag_block_gen.vhd
@@ -33,7 +33,10 @@
 --     mem_low_adrs       : slv -- block start address at MM interface
 --     mem_high_adrs      : slv -- end address at MM interface
 --     bsn_init           : slv -- BSN of first output block
---                                          
+--
+--   The samples_per_packet >= 2, because the in the p_comb state machine the
+--   eop cannot occur at the sop.
+--
 --   The MM reading starts at mem_low_adrs when the BG is first enabled. If
 --   the mem_high_adrs-mem_low_adrs+1 < samples_per_packet then the reading
 --   wraps and continues from mem_low_adrs. For every new block the reading
@@ -47,9 +50,10 @@
 --   every blocks_per_sync.
 --
 --   The current block is finished properly after enable gows low, to ensure 
---   that all blocks have the same length. A new ctrl is accepted after a
---   current block has finished, to ensure that no fractional blocks will 
---   enter the stream.
+--   that all blocks have the same length. A new ctrl is accepted only when
+--   the BG is (re)started, to simplify the use of ctrl_hold. The ctrl_hold
+--   holds the active control settings while BG is enabled. Note that
+--   ctrl_hold.enable remains '1' after the BG has been enabled once.
 --
 --   The BG supports block flow control via out_siso.xon. The BG also supports
 --   sample flow control via out_siso.ready.
@@ -81,6 +85,7 @@ entity diag_block_gen is
     buf_rddat    : in  std_logic_vector(g_buf_dat_w-1 downto 0);
     buf_rdval    : in  std_logic;
     ctrl         : in  t_diag_block_gen;
+    ctrl_hold    : out t_diag_block_gen;  -- hold current active ctrl
     en_sync      : in  std_logic := '1';
     out_siso     : in  t_dp_siso := c_dp_siso_rdy;
     out_sosi     : out t_dp_sosi
@@ -93,7 +98,7 @@ architecture rtl of diag_block_gen is
   type state_type is (s_idle, s_block, s_gap);
 
   type reg_type is record
-    ctrl_reg    : t_diag_block_gen;  -- capture ctrl
+    ctrl_hold   : t_diag_block_gen;  -- capture ctrl
     blk_en      : std_logic;  -- enable at block level
     blk_xon     : std_logic;  -- siso.xon at block level, the BG continues but the sosi control depend on xon (the BG does not support siso.ready)
     blk_sync    : std_logic;  -- block sync alternative of the pulse sync
@@ -111,10 +116,14 @@ architecture rtl of diag_block_gen is
 
   signal r, rin     : reg_type;
   signal out_sosi_i : t_dp_sosi := c_dp_sosi_rst;  -- Signal used to assign reset values to output
+  signal xon_reg    : std_logic := '0';
   
 begin 
     
-    p_comb : process(r, rst, ctrl, en_sync, out_siso)
+    -- xon is not clk cycle timing critical, so can use register xon to ease timing closure
+    xon_reg <= out_siso.xon when rising_edge(clk);
+
+    p_comb : process(r, rst, ctrl, en_sync, out_siso, xon_reg)
       variable v                    : reg_type;                              
       variable v_samples_per_packet : natural;   
       variable v_gapsize            : natural;   
@@ -123,11 +132,11 @@ begin
       variable v_mem_high_adrs      : natural;
     begin
     
-      v_samples_per_packet := TO_UINT(r.ctrl_reg.samples_per_packet);
-      v_gapsize            := TO_UINT(r.ctrl_reg.gapsize);
-      v_blocks_per_sync    := TO_UINT(r.ctrl_reg.blocks_per_sync); 
-      v_mem_low_adrs       := TO_UINT(r.ctrl_reg.mem_low_adrs); 
-      v_mem_high_adrs      := TO_UINT(r.ctrl_reg.mem_high_adrs);
+      v_samples_per_packet := TO_UINT(r.ctrl_hold.samples_per_packet);
+      v_gapsize            := TO_UINT(r.ctrl_hold.gapsize);
+      v_blocks_per_sync    := TO_UINT(r.ctrl_hold.blocks_per_sync);
+      v_mem_low_adrs       := TO_UINT(r.ctrl_hold.mem_low_adrs);
+      v_mem_high_adrs      := TO_UINT(r.ctrl_hold.mem_high_adrs);
       
       v                   := r;     -- default hold all r fields
       v.pls_sync          := '0';
@@ -157,15 +166,15 @@ begin
       
       case r.state is
         when s_idle => 
-          v.ctrl_reg    := ctrl;        -- accept new control settings
-          v.blk_xon     := out_siso.xon;
+          v.blk_xon     := xon_reg;
           v.blk_sync    := '0';
           v.samples_cnt := 0;
           v.blocks_cnt  := 0;    
-          v.bsn_cnt     := ctrl.bsn_init;
           v.mem_cnt     := v_mem_low_adrs; 
-          if r.blk_en = '1' then       -- Wait until enabled
-            if out_siso.xon='1' then   -- Wait until XON is 1
+          if r.blk_en = '1' then  -- Wait until enabled
+            if xon_reg = '1' then   -- Wait until XON is 1
+              v.ctrl_hold   := ctrl;  -- hold new control settings while BG is enabled
+              v.bsn_cnt     := ctrl.bsn_init;
               v.rd_ena      := '1';
               v.state       := s_block;
             end if;
@@ -185,12 +194,10 @@ begin
               v.samples_cnt := v.samples_cnt + 1; 
             elsif r.samples_cnt >= v_samples_per_packet-1 and v_gapsize = 0 and r.blocks_cnt >= v_blocks_per_sync-1 then 
               v.eop         := '1'; 
-              v.ctrl_reg    := ctrl;      -- accept new control settings at end of block when gapsize=0
               v.samples_cnt := 0; 
               v.blocks_cnt  := 0;
             elsif r.samples_cnt >= v_samples_per_packet-1 and v_gapsize = 0 then 
               v.eop         := '1'; 
-              v.ctrl_reg    := ctrl;      -- accept new control settings at end of block when gapsize=0
               v.samples_cnt := 0; 
               v.blocks_cnt  := r.blocks_cnt + 1;
             elsif r.samples_cnt >= v_samples_per_packet-1 then 
@@ -212,21 +219,18 @@ begin
             if v.eop = '1' and r.blk_en = '0' then
               v.state := s_idle;          -- accept disable after eop, not during block
             end if;
-            if r.eop = '1' then
-              v.blk_xon := out_siso.xon;  -- accept XOFF after eop, not during block
-            end if;
-          
           end if;  -- out_siso.ready='1'
+          if r.eop = '1' then
+            v.blk_xon := xon_reg;  -- accept XOFF after eop, not during block
+          end if;
 
         when s_gap => 
           if r.samples_cnt >= v_gapsize-1 and r.blocks_cnt >= v_blocks_per_sync-1 then 
-            v.ctrl_reg    := ctrl;      -- accept new control settings at end of gap
             v.samples_cnt := 0; 
             v.blocks_cnt  := 0;
             v.rd_ena      := '1';
             v.state       := s_block;
           elsif r.samples_cnt >= v_gapsize-1 then 
-            v.ctrl_reg    := ctrl;      -- accept new control settings at end of gap
             v.samples_cnt := 0;             
             v.blocks_cnt  := r.blocks_cnt + 1;
             v.rd_ena      := '1';
@@ -238,7 +242,7 @@ begin
           if r.blk_en = '0' then
             v.state := s_idle;
           end if;
-          v.blk_xon := out_siso.xon;
+          v.blk_xon := xon_reg;
                 
         when others =>
           v.state := s_idle;
@@ -246,7 +250,7 @@ begin
       end case;
       
       if rst = '1' then 
-        v.ctrl_reg    := c_diag_block_gen_rst; 
+        v.ctrl_hold    := c_diag_block_gen_rst;
         v.blk_en      := '0'; 
         v.blk_xon     := '0'; 
         v.blk_sync    := '0'; 
@@ -286,5 +290,7 @@ begin
     out_sosi <= out_sosi_i;
     buf_addr <= TO_UVEC(r.mem_cnt, g_buf_addr_w);
     buf_rden <= r.rd_ena;
+
+    ctrl_hold <= r.ctrl_hold;
  
 end rtl;
diff --git a/libraries/base/diag/src/vhdl/diag_pkg.vhd b/libraries/base/diag/src/vhdl/diag_pkg.vhd
index 1d1fd44248944908964c23a728736a66981a34ae..8e3c0eb5e50914ed7a5090ba1d3c5c19fd2dc169 100644
--- a/libraries/base/diag/src/vhdl/diag_pkg.vhd
+++ b/libraries/base/diag/src/vhdl/diag_pkg.vhd
@@ -102,9 +102,10 @@ PACKAGE diag_pkg IS
   -----------------------------------------------------------------------------
   
   -- control register
-  CONSTANT c_diag_bg_reg_nof_dat : NATURAL := 8;
-  CONSTANT c_diag_bg_reg_adr_w   : NATURAL := ceil_log2(c_diag_bg_reg_nof_dat);
-  
+  CONSTANT c_diag_bg_reg_nof_dat  : NATURAL := 8;
+  CONSTANT c_diag_bg_reg_adr_w    : NATURAL := ceil_log2(c_diag_bg_reg_nof_dat);
+  CONSTANT c_diag_bg_reg_adr_span : NATURAL := 2**c_diag_bg_reg_adr_w;
+
   CONSTANT c_diag_bg_mode_w               : NATURAL :=  8;
   CONSTANT c_diag_bg_samples_per_packet_w : NATURAL := 24;   
   CONSTANT c_diag_bg_blocks_per_sync_w    : NATURAL := 24;   
@@ -112,6 +113,7 @@ PACKAGE diag_pkg IS
   CONSTANT c_diag_bg_mem_adrs_w           : NATURAL := 24;  
   CONSTANT c_diag_bg_mem_low_adrs_w       : NATURAL := c_diag_bg_mem_adrs_w;  
   CONSTANT c_diag_bg_mem_high_adrs_w      : NATURAL := c_diag_bg_mem_adrs_w;
+  CONSTANT c_diag_bg_mem_max_adr          : NATURAL := 2**c_diag_bg_mem_adrs_w-1;
   CONSTANT c_diag_bg_bsn_init_w           : NATURAL := 64;
                                                           
   TYPE t_diag_block_gen IS RECORD
@@ -123,9 +125,20 @@ PACKAGE diag_pkg IS
     mem_low_adrs       : STD_LOGIC_VECTOR(c_diag_bg_mem_low_adrs_w       -1 DOWNTO 0);  
     mem_high_adrs      : STD_LOGIC_VECTOR(c_diag_bg_mem_high_adrs_w      -1 DOWNTO 0);  
     bsn_init           : STD_LOGIC_VECTOR(c_diag_bg_bsn_init_w           -1 DOWNTO 0);  
-  END RECORD;   
-  
-  CONSTANT c_diag_block_gen_rst     : t_diag_block_gen := (         '0',      
+  END RECORD;
+
+  TYPE t_diag_block_gen_integer IS RECORD
+    enable             : STD_LOGIC;
+    enable_sync        : STD_LOGIC;
+    samples_per_packet : NATURAL;
+    blocks_per_sync    : NATURAL;
+    gapsize            : NATURAL;
+    mem_low_adrs       : NATURAL;
+    mem_high_adrs      : NATURAL;
+    bsn_init           : NATURAL;
+  END RECORD;
+
+  CONSTANT c_diag_block_gen_rst     : t_diag_block_gen := (         '0',
                                                                     '0',      
                                                            TO_UVEC( 256, c_diag_bg_samples_per_packet_w), 
                                                            TO_UVEC(  10, c_diag_bg_blocks_per_sync_w),      
@@ -144,10 +157,14 @@ PACKAGE diag_pkg IS
                                                            TO_UVEC(   0, c_diag_bg_bsn_init_w));
                                                        
   TYPE t_diag_block_gen_arr IS ARRAY (INTEGER RANGE <>) OF t_diag_block_gen;
- 
+  TYPE t_diag_block_gen_integer_arr IS ARRAY (INTEGER RANGE <>) OF t_diag_block_gen_integer;
+
   -- Overloaded sel_a_b (from common_pkg) for t_diag_block_gen
   FUNCTION sel_a_b(sel : BOOLEAN; a, b : t_diag_block_gen) RETURN t_diag_block_gen; 
 
+  FUNCTION func_diag_bg_ctrl_integer_to_slv(bg_ctrl_int : t_diag_block_gen_integer) RETURN t_diag_block_gen;
+  FUNCTION func_diag_bg_ctrl_slv_to_integer(bg_ctrl_slv : t_diag_block_gen) RETURN t_diag_block_gen_integer;
+
   -----------------------------------------------------------------------------
   -- Data buffer
   -----------------------------------------------------------------------------
@@ -214,4 +231,28 @@ PACKAGE BODY diag_pkg IS
     END IF;
   END;
 
+  FUNCTION func_diag_bg_ctrl_integer_to_slv(bg_ctrl_int : t_diag_block_gen_integer) RETURN t_diag_block_gen IS
+  BEGIN
+    RETURN (        bg_ctrl_int.enable,
+                    bg_ctrl_int.enable_sync,
+            TO_UVEC(bg_ctrl_int.samples_per_packet, c_diag_bg_samples_per_packet_w),
+            TO_UVEC(bg_ctrl_int.blocks_per_sync   , c_diag_bg_blocks_per_sync_w),
+            TO_UVEC(bg_ctrl_int.gapsize           , c_diag_bg_gapsize_w),
+            TO_UVEC(bg_ctrl_int.mem_low_adrs      , c_diag_bg_mem_low_adrs_w),
+            TO_UVEC(bg_ctrl_int.mem_high_adrs     , c_diag_bg_mem_high_adrs_w),
+            TO_UVEC(bg_ctrl_int.bsn_init          , c_diag_bg_bsn_init_w));
+  END;
+
+  FUNCTION func_diag_bg_ctrl_slv_to_integer(bg_ctrl_slv : t_diag_block_gen) RETURN t_diag_block_gen_integer IS
+  BEGIN
+    RETURN (        bg_ctrl_slv.enable,
+                    bg_ctrl_slv.enable_sync,
+            TO_UINT(bg_ctrl_slv.samples_per_packet),
+            TO_UINT(bg_ctrl_slv.blocks_per_sync),
+            TO_UINT(bg_ctrl_slv.gapsize),
+            TO_UINT(bg_ctrl_slv.mem_low_adrs),
+            TO_UINT(bg_ctrl_slv.mem_high_adrs),
+            TO_UINT(bg_ctrl_slv.bsn_init));
+  END;
+
 END diag_pkg;
diff --git a/libraries/base/diag/src/vhdl/mms_diag_block_gen.vhd b/libraries/base/diag/src/vhdl/mms_diag_block_gen.vhd
index 051b6cbe9f0a93e04e7e8f79ecffb236e7227b4c..93839197c820227c38c773b7a93429bef4e087a6 100644
--- a/libraries/base/diag/src/vhdl/mms_diag_block_gen.vhd
+++ b/libraries/base/diag/src/vhdl/mms_diag_block_gen.vhd
@@ -140,6 +140,7 @@ ENTITY mms_diag_block_gen IS
     reg_tx_seq_mosi  : IN  t_mem_mosi := c_mem_mosi_rst;  -- Tx seq control (one per stream because c_reg_tx_seq_broadcast=FALSE)
     reg_tx_seq_miso  : OUT t_mem_miso;
     -- ST interface
+    bg_ctrl_hold_arr : OUT t_diag_block_gen_arr(g_nof_streams-1 DOWNTO 0);
     usr_siso_arr     : OUT t_dp_siso_arr(g_nof_streams-1 DOWNTO 0);  -- connect when g_use_usr_input=TRUE, else leave not connected 
     usr_sosi_arr     : IN  t_dp_sosi_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS=>c_dp_sosi_rst);
     out_siso_arr     : IN  t_dp_siso_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS=>c_dp_siso_rdy);  -- Default xon='1'
@@ -283,7 +284,8 @@ BEGIN
         buf_rden   => st_rd_arr(I),          
         buf_rddat  => st_rddata_arr(I),       
         buf_rdval  => st_rdval_arr(I),          
-        ctrl       => bg_ctrl,
+        ctrl       => bg_ctrl,  -- same BG control for all streams
+        ctrl_hold  => bg_ctrl_hold_arr(I),  -- active BG control can differ in time per stream
         en_sync    => en_sync,
         out_siso   => bg_src_in_arr(I),
         out_sosi   => bg_src_out_arr(I)
diff --git a/libraries/base/diag/tb/vhdl/tb_diag_block_gen.vhd b/libraries/base/diag/tb/vhdl/tb_diag_block_gen.vhd
index af2edf040228ba8271222dd550e94407c7b3bb5f..c54f89ca2b88ddfb950a84562bf02a26d84009a3 100644
--- a/libraries/base/diag/tb/vhdl/tb_diag_block_gen.vhd
+++ b/libraries/base/diag/tb/vhdl/tb_diag_block_gen.vhd
@@ -134,7 +134,8 @@ ARCHITECTURE tb OF tb_diag_block_gen IS
   SIGNAL bg_buf_miso    : t_mem_miso;
   
   SIGNAL bg_ctrl        : t_diag_block_gen;
-                                                                            
+  SIGNAL bg_ctrl_hold   : t_diag_block_gen;
+
   SIGNAL random         : STD_LOGIC_VECTOR(14 DOWNTO 0) := (OTHERS=>'0');  -- use different lengths to have different random sequences
   SIGNAL toggle         : STD_LOGIC := '0';
   SIGNAL out_siso_bg    : t_dp_siso := c_dp_siso_rdy;
@@ -187,42 +188,7 @@ BEGIN
     bg_ctrl.mem_high_adrs <= TO_UVEC(c_alternative_mem_high_adrs, c_diag_bg_mem_high_adrs_w);
     bg_ctrl.enable <= '1';
     proc_dp_verify_run_some_cycles(5, c_runtime, 0, clk, verify_en);
-    
-    -- Run and change bg_ctrl dynamically
-    bg_ctrl.enable <= '0';
-    proc_common_wait_some_cycles(clk, c_offtime); 
-    bg_ctrl <= c_bg_ctrl;
-    bg_ctrl.enable <= '1';
-    proc_dp_verify_run_some_cycles(5, c_runtime, 0, clk, verify_en);
-    
-    -- . change gapsize dynamically
-    bg_ctrl.gapsize <= TO_UVEC(0, c_diag_bg_gapsize_w);
-    proc_dp_verify_run_some_cycles(0, c_runtime, 0, clk, verify_en);
 
-    bg_ctrl.gapsize <= c_bg_ctrl.gapsize;
-    proc_dp_verify_run_some_cycles(0, c_runtime, 0, clk, verify_en);
-    
-    -- . change mem_high_adrs dynamically
-    proc_common_wait_until_high(c_timeout, clk, out_sosi.eop);
-    bg_ctrl.mem_low_adrs <= TO_UVEC(c_alternative_mem_low_adrs, c_diag_bg_mem_low_adrs_w);
-    bg_ctrl.mem_high_adrs <= TO_UVEC(c_alternative_mem_high_adrs, c_diag_bg_mem_high_adrs_w);
-    proc_dp_verify_run_some_cycles(5, c_runtime, 0, clk, verify_en);
-    
-    bg_ctrl.mem_high_adrs <= c_bg_ctrl.mem_high_adrs;
-    proc_dp_verify_run_some_cycles(0, c_runtime, 0, clk, verify_en);
-        
-    -- . change samples_per_packet dynamically
-    proc_common_wait_until_high(c_timeout, clk, out_sosi.sop);
-    proc_common_wait_some_cycles(clk, 10);
-    bg_ctrl.samples_per_packet <= TO_UVEC(c_more_samples_per_packet, c_diag_bg_samples_per_packet_w);   -- increase
-    proc_dp_verify_run_some_cycles(0, c_runtime, 0, clk, verify_en);
-    
-    -- . change samples_per_packet dynamically
-    proc_common_wait_until_high(c_timeout, clk, out_sosi.sop);
-    proc_common_wait_some_cycles(clk, c_less_samples_per_packet*2);
-    bg_ctrl.samples_per_packet <= TO_UVEC(c_less_samples_per_packet, c_diag_bg_samples_per_packet_w);   -- decrease, could yield proc_dp_verify_block_size() error if it occurs during a packet
-    proc_dp_verify_run_some_cycles(0, c_runtime, 0, clk, verify_en);
-    
     -- Run with XON flow control (verified by proc_dp_verify_block_size)
     bg_ctrl.enable <= '0';
     proc_common_wait_some_cycles(clk, c_offtime); 
@@ -300,6 +266,7 @@ BEGIN
     buf_rddat   => bg_buf_miso.rddata(c_buf.dat_w-1 DOWNTO 0),   
     buf_rdval   => bg_buf_miso.rdval,   
     ctrl        => bg_ctrl,
+    ctrl_hold   => bg_ctrl_hold,
     out_siso    => out_siso_bg,
     out_sosi    => out_sosi
   );
diff --git a/libraries/base/dp/hdllib.cfg b/libraries/base/dp/hdllib.cfg
index db5cbc473d90fa0409bb0d748cd84ce3f2d2e370..5ec867ca066b2db19741171d7cf07c4b948cccd6 100644
--- a/libraries/base/dp/hdllib.cfg
+++ b/libraries/base/dp/hdllib.cfg
@@ -6,6 +6,7 @@ hdl_lib_technology =
 
 synth_files =
     src/vhdl/dp_stream_pkg.vhd
+    src/vhdl/dp_components_pkg.vhd
     src/vhdl/dp_example_dut.vhd
     src/vhdl/dp_packetizing_pkg.vhd
     src/vhdl/dp_packet_pkg.vhd
diff --git a/libraries/base/dp/src/vhdl/dp_bsn_monitor_v2.vhd b/libraries/base/dp/src/vhdl/dp_bsn_monitor_v2.vhd
index 78249aab7520bb47c4d9cd5349b74071898e3762..fd3981df8e0bf046b3ac5fcf5b806d6da1e99e21 100644
--- a/libraries/base/dp/src/vhdl/dp_bsn_monitor_v2.vhd
+++ b/libraries/base/dp/src/vhdl/dp_bsn_monitor_v2.vhd
@@ -104,6 +104,7 @@ ARCHITECTURE rtl OF dp_bsn_monitor_v2 IS
   SIGNAL nxt_valid                   : STD_LOGIC;
   SIGNAL sop                         : STD_LOGIC;
   SIGNAL nxt_sop                     : STD_LOGIC;
+  SIGNAL sync_reg2                   : STD_LOGIC;
   SIGNAL sync_reg                    : STD_LOGIC;
   SIGNAL sync                        : STD_LOGIC;
   SIGNAL nxt_sync                    : STD_LOGIC;
@@ -163,7 +164,7 @@ BEGIN
   nxt_mon_nof_sop      <= nof_sop      WHEN sync='1' ELSE i_mon_nof_sop;
   nxt_mon_nof_err      <= nof_err      WHEN sync='1' ELSE i_mon_nof_err;
   nxt_mon_nof_valid    <= nof_valid    WHEN sync='1' ELSE i_mon_nof_valid;
-  nxt_mon_latency      <= latency      WHEN sync_reg='1' ELSE i_mon_latency;  -- use sync_reg due to ref_sync_reg
+  nxt_mon_latency      <= latency      WHEN sync_reg2='1' ELSE i_mon_latency;  -- use sync_reg2 due to ref_sync_reg
 
   nof_sop   <= cnt_sop;   
   nof_err   <= cnt_err;
@@ -171,9 +172,9 @@ BEGIN
   latency   <= cnt_latency;
 
   -- Register ref_sync to ease timing closure for ref_sync fanout to (many) dp_bsn_monitor_v2
-  -- instances. The ref_sync_reg is used to restart cnt_latency. Therefore the sync_reg
-  -- (instead of sync) is needed to capture latency (= cnt_latency) not too early, so capture
-  -- cnt_latency at or after the cnt_latency has restarted.
+  -- instances. The ref_sync_reg is used to restart cnt_latency. Therefore the sync_reg2
+  -- (instead of sync) is needed to capture latency (= cnt_latency) not too early, in case
+  -- ref_sync = in_sosi.sync, so capture cnt_latency at or after the cnt_latency has restarted.
   ref_sync_reg <= ref_sync WHEN rising_edge(clk);
 
   u_sync_timeout_cnt : ENTITY common_lib.common_counter
@@ -216,6 +217,7 @@ BEGIN
       err                       <= '0';
       sync                      <= '0';
       sync_reg                  <= '0';
+      sync_reg2                 <= '0';
       bsn                       <= (OTHERS=>'0');
       -- output
       mon_evt                   <= '0';
@@ -237,6 +239,7 @@ BEGIN
       err                       <= nxt_err;
       sync                      <= nxt_sync;
       sync_reg                  <= sync;
+      sync_reg2                 <= sync_reg;
       bsn                       <= nxt_bsn;
       -- output
       mon_evt                   <= nxt_mon_evt;
diff --git a/libraries/base/dp/src/vhdl/dp_components_pkg.vhd b/libraries/base/dp/src/vhdl/dp_components_pkg.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..994b5988181f6921038a36ca7ca57313c2ff9716
--- /dev/null
+++ b/libraries/base/dp/src/vhdl/dp_components_pkg.vhd
@@ -0,0 +1,50 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2022
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+-- Author: E. Kooistra
+-- Purpose: Keep parameters of DP components
+-- Description:
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE IEEE.numeric_std.ALL;
+USE common_lib.common_pkg.ALL;
+
+PACKAGE dp_components_pkg IS
+
+  CONSTANT c_dp_clk_MHz                               : NATURAL := 200;
+  CONSTANT c_dp_sync_timeout                          : NATURAL := c_dp_clk_MHz*10**6 + c_dp_clk_MHz*10**5;  -- 10% margin for nominal 1 s
+
+  CONSTANT c_dp_bsn_monitor_v2_reg_adr_w              : NATURAL := ceil_log2(7);  -- = 3
+  CONSTANT c_dp_bsn_monitor_v2_reg_adr_span           : NATURAL := 2**c_dp_bsn_monitor_v2_reg_adr_w; -- = 8
+
+  CONSTANT c_dp_strobe_total_count_reg_nof_words      : NATURAL := 15*2 + 1; -- = 31
+  CONSTANT c_dp_strobe_total_count_reg_adr_w          : NATURAL := ceil_log2(c_dp_strobe_total_count_reg_nof_words); -- = 5
+  CONSTANT c_dp_strobe_total_count_reg_adr_span       : NATURAL := 2**c_dp_strobe_total_count_reg_adr_w;  -- = 32
+  CONSTANT c_dp_strobe_total_count_reg_nof_counts_max : NATURAL := 2**c_dp_strobe_total_count_reg_adr_w / 2 - 1;  -- = 15
+  CONSTANT c_dp_strobe_total_count_reg_clear_adr      : NATURAL := c_dp_strobe_total_count_reg_nof_counts_max*2;  -- after counters in REGMAP
+
+END dp_components_pkg;
+
+
+PACKAGE BODY dp_components_pkg IS
+END dp_components_pkg;
+
diff --git a/libraries/base/dp/src/vhdl/dp_concat.vhd b/libraries/base/dp/src/vhdl/dp_concat.vhd
index 46361034acb6da8466cb4a7e7262ff70f2a47eac..2cd1dc3b30d7b6e47b430e65824cdf7070203c86 100644
--- a/libraries/base/dp/src/vhdl/dp_concat.vhd
+++ b/libraries/base/dp/src/vhdl/dp_concat.vhd
@@ -259,6 +259,8 @@ BEGIN
                 nxt_state <= s_sop;
               ELSE                                         -- Output frame needs 2 words
                 flush_arr(c_tail) <= c_dp_siso_rdy;        -- also ready for hold input when the tail eop is there
+                v_out_empty := func_dp_empty_concat(head_empty_reg, tail_empty, c_nof_symbols_per_data);
+                nxt_out_empty_reg <= v_out_empty;          -- capture the output empty field (needed in s_eop)
                 nxt_state <= s_eop;
               END IF;
             END IF;
diff --git a/libraries/base/dp/src/vhdl/dp_fifo_fill_eop.vhd b/libraries/base/dp/src/vhdl/dp_fifo_fill_eop.vhd
index 381cd124bba1ff193c3423fc76e24d20af32a686..62ddba22104d5a5ff2ba355a63bbe5150b39a20e 100644
--- a/libraries/base/dp/src/vhdl/dp_fifo_fill_eop.vhd
+++ b/libraries/base/dp/src/vhdl/dp_fifo_fill_eop.vhd
@@ -97,7 +97,7 @@ ENTITY dp_fifo_fill_eop IS
     snk_out     : OUT t_dp_siso;
     snk_in      : IN  t_dp_sosi;
     -- ST source
-    src_in      : IN  t_dp_siso;
+    src_in      : IN  t_dp_siso := c_dp_siso_rdy;
     src_out     : OUT t_dp_sosi
   );
 END dp_fifo_fill_eop;
diff --git a/libraries/base/dp/src/vhdl/dp_fifo_fill_sc.vhd b/libraries/base/dp/src/vhdl/dp_fifo_fill_sc.vhd
index 6c677dc7e33bfe3a05394cd1edbce508aa8a0ced..db85d766fdeb1160ec49358fa622ac9e23bb66c8 100644
--- a/libraries/base/dp/src/vhdl/dp_fifo_fill_sc.vhd
+++ b/libraries/base/dp/src/vhdl/dp_fifo_fill_sc.vhd
@@ -68,7 +68,7 @@ ENTITY dp_fifo_fill_sc IS
     snk_out     : OUT t_dp_siso;
     snk_in      : IN  t_dp_sosi;
     -- ST source
-    src_in      : IN  t_dp_siso;
+    src_in      : IN  t_dp_siso := c_dp_siso_rdy;
     src_out     : OUT t_dp_sosi
   );
 END dp_fifo_fill_sc;
diff --git a/libraries/base/dp/src/vhdl/dp_fifo_sc.vhd b/libraries/base/dp/src/vhdl/dp_fifo_sc.vhd
index 6421ae0d6ab3d7e4f2d61501a6be75bc1170026c..fb9570775d09d98e39f905748c21ad9ea61c5feb 100644
--- a/libraries/base/dp/src/vhdl/dp_fifo_sc.vhd
+++ b/libraries/base/dp/src/vhdl/dp_fifo_sc.vhd
@@ -62,7 +62,7 @@ ENTITY dp_fifo_sc IS
     snk_out     : OUT t_dp_siso;
     snk_in      : IN  t_dp_sosi;
     -- ST source
-    src_in      : IN  t_dp_siso;
+    src_in      : IN  t_dp_siso := c_dp_siso_rdy;
     src_out     : OUT t_dp_sosi
   );
 END dp_fifo_sc;
diff --git a/libraries/base/dp/src/vhdl/dp_offload_rx.vhd b/libraries/base/dp/src/vhdl/dp_offload_rx.vhd
index ff1a52c71575982bedcdecf3eaa95edac477d7e2..6ce2e23d4cda4d15e7d7ad80eabee1b0a9729282 100644
--- a/libraries/base/dp/src/vhdl/dp_offload_rx.vhd
+++ b/libraries/base/dp/src/vhdl/dp_offload_rx.vhd
@@ -93,13 +93,13 @@ ARCHITECTURE str OF dp_offload_rx IS
   SIGNAL reg_hdr_dat_mosi_arr         : t_mem_mosi_arr(g_nof_streams-1 DOWNTO 0);
   SIGNAL reg_hdr_dat_miso_arr         : t_mem_miso_arr(g_nof_streams-1 DOWNTO 0); 
 
-  SIGNAL dp_field_blk_slv_out         : t_slv_1024_arr(g_nof_streams-1 DOWNTO 0);
+  SIGNAL dp_field_blk_slv_out         : t_slv_1024_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS=>(OTHERS=>'0'));
   SIGNAL dp_field_blk_slv_out_val     : STD_LOGIC_VECTOR(g_nof_streams-1 DOWNTO 0);
 
   SIGNAL dp_field_blk_src_out_arr     : t_dp_sosi_arr(g_nof_streams-1 DOWNTO 0); 
 
   SIGNAL reg_dp_field_blk_slv_out     : t_slv_1024_arr(g_nof_streams-1 DOWNTO 0); 
-  SIGNAL nxt_reg_dp_field_blk_slv_out : t_slv_1024_arr(g_nof_streams-1 DOWNTO 0); 
+  SIGNAL nxt_reg_dp_field_blk_slv_out : t_slv_1024_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS=>(OTHERS=>'0'));
 
   SIGNAL dp_tail_remove_src_out_arr   : t_dp_sosi_arr(g_nof_streams-1 DOWNTO 0); 
   SIGNAL dp_tail_remove_src_in_arr    : t_dp_siso_arr(g_nof_streams-1 DOWNTO 0);
diff --git a/libraries/base/dp/src/vhdl/dp_offload_tx_v3.vhd b/libraries/base/dp/src/vhdl/dp_offload_tx_v3.vhd
index f72352cd6925c80356551b0c2fa3b6761b1f9877..66551c1bc892ba343176675715242c43d81916b7 100644
--- a/libraries/base/dp/src/vhdl/dp_offload_tx_v3.vhd
+++ b/libraries/base/dp/src/vhdl/dp_offload_tx_v3.vhd
@@ -135,12 +135,24 @@ BEGIN
     snk_out_arr(i).xon <= src_in_arr(i).xon;
 
     -- Wire hdr_fields_out_arr 
-    -- MM override bits determine source for each field
-    gen_field_wires: FOR j IN g_hdr_field_arr'RANGE GENERATE
-      hdr_fields_out_arr(i)(field_hi(g_hdr_field_arr, j) DOWNTO field_lo(g_hdr_field_arr, j)) <= mm_fields_slv_out_arr(i)(field_hi(g_hdr_field_arr, j) DOWNTO field_lo(g_hdr_field_arr, j)) 
-                                                                                              WHEN field_override_arr(j) = '1' ELSE 
-                                                                                              hdr_fields_in_arr(i)(field_hi(g_hdr_field_arr, j) DOWNTO field_lo(g_hdr_field_arr, j));
-    END GENERATE;
+    p_hdr_fields_out_arr : PROCESS(mm_fields_slv_out_arr, hdr_fields_in_arr)
+      VARIABLE v_hi : NATURAL;
+      VARIABLE v_lo : NATURAL;
+    BEGIN
+      -- Default drive unused slv bit to 0 to avoid Warning: (vsim-8684) No drivers exist on out port
+      -- hdr_fields_out_arr(0)(1023 downto ...), and its initial value is not used.
+      hdr_fields_out_arr(i) <= (OTHERS => '0');
+      FOR j IN g_hdr_field_arr'RANGE LOOP
+        v_hi := field_hi(g_hdr_field_arr, j);
+        v_lo := field_lo(g_hdr_field_arr, j);
+        -- MM override bits determine source for each field
+        IF field_override_arr(j) = '1' THEN
+          hdr_fields_out_arr(i)(v_hi DOWNTO v_lo) <= mm_fields_slv_out_arr(i)(v_hi DOWNTO v_lo);
+        ELSE
+          hdr_fields_out_arr(i)(v_hi DOWNTO v_lo) <= hdr_fields_in_arr(i)(v_hi DOWNTO v_lo);
+        END IF;
+      END LOOP;
+    END PROCESS;
 
     ---------------------------------------------------------------------------------------
     -- mm_fields for MM access to each field
diff --git a/libraries/base/dp/src/vhdl/dp_strobe_total_count.vhd b/libraries/base/dp/src/vhdl/dp_strobe_total_count.vhd
index 19c033c715a2f338236f57fe02356754f8ac4aa5..2a08ade6252bd31a5064cfbbe7c6f557f7c565e7 100644
--- a/libraries/base/dp/src/vhdl/dp_strobe_total_count.vhd
+++ b/libraries/base/dp/src/vhdl/dp_strobe_total_count.vhd
@@ -23,7 +23,7 @@
 -- Purpose:
 --   Count strobes
 -- Description:
--- * g_nof_counts >= 1, maximum g_nof_counts_max = 15
+-- * g_nof_counts >= 1, maximum c_nof_counts_max = 15
 --   There are g_nof_counts in parallel, one clear that clears them all
 --   . count any strobe like e.g. sync, sop, valid, error flag, ...
 --   . MM read or write of clear registers clears all counter immediately,
@@ -43,7 +43,8 @@
 --
 -------------------------------------------------------------------------------
 -- REGMAP
--- . address span is (g_nof_counts_max + 1)*2 = 32
+-- . address span is (c_nof_counts_max + 1)*2 = 32
+-- . address width is c_dp_strobe_total_count_reg_adr_w - ceil_log2(32) = 5
 -------------------------------------------------------------------------------
 --  wi                          Bits    R/W Name                        Default
 --  ===========================================================================
@@ -57,23 +58,27 @@
 --  (g_nof_counts-1)*2 + 1      [63.32]
 --  .                            .      .   .                           .
 --  .                            .
---  (g_nof_counts_max-1)*2      [31..0] RO  count[g_nof_counts_max-1]   0x0
---  (g_nof_counts_max-1)*2 + 1  [63.32]
---  g_nof_counts_max*2          [31..0] RW  clear                       0x0
---  g_nof_counts_max*2 + 1      [31..0] RO  rsvd                        0x0
+--  (c_nof_counts_max-1)*2      [31..0] RO  count[c_nof_counts_max-1]   0x0
+--  (c_nof_counts_max-1)*2 + 1  [63.32]
+--  c_nof_counts_max*2          [31..0] RW  clear                       0x0
+--  c_nof_counts_max*2 + 1      [31..0] RO  rsvd                        0x0
 --  ===========================================================================
 --
+-- Remark:
+-- * This dp_strobe_total_count could have been a common_strobe_total_count
+--   component, because it does not use sosi/siso signals. However it is fine
+--   to keep it in dp_lib, to avoid extra work of moving and renaming.
 LIBRARY IEEE, common_lib;
 USE IEEE.std_logic_1164.all;
 USE IEEE.numeric_std.all;
 USE common_lib.common_pkg.ALL;
 USE common_lib.common_mem_pkg.ALL;
+USE work.dp_components_pkg.ALL;
 
 ENTITY dp_strobe_total_count IS
   GENERIC (
     g_mm_w           : NATURAL := c_word_w;
-    g_nof_counts_max : NATURAL := 15;  -- fixed by REGMAP
-    g_nof_counts     : NATURAL := 1;  -- actual nof counts, <= g_nof_counts_max
+    g_nof_counts     : NATURAL := 1;  -- actual nof counts, <= c_nof_counts_max
     g_count_w        : NATURAL := c_longword_w;  -- actual count width, max is c_longword_w due to two mm word width
     g_clip           : BOOLEAN := TRUE
   );
@@ -94,14 +99,15 @@ END dp_strobe_total_count;
 
 ARCHITECTURE rtl OF dp_strobe_total_count IS
 
-  CONSTANT c_nof_words      : NATURAL := g_nof_counts_max*2 + 1;  -- +1 for clear
-  CONSTANT c_clear_adr      : NATURAL := g_nof_counts_max*2;  -- after counters in REGMAP
+  -- Fixed by REGMAP
+  CONSTANT c_nof_counts_max : NATURAL := c_dp_strobe_total_count_reg_nof_counts_max;
+  CONSTANT c_clear_adr      : NATURAL := c_dp_strobe_total_count_reg_clear_adr;
 
   -- Define the size of the MM slave register
   CONSTANT c_mm_reg : t_c_mem := (latency  => 1,
-                                  adr_w    => ceil_log2(c_nof_words),
+                                  adr_w    => c_dp_strobe_total_count_reg_adr_w,
                                   dat_w    => g_mm_w,  -- Use MM bus data width = c_word_w = 32 for all MM registers
-                                  nof_dat  => c_nof_words,
+                                  nof_dat  => c_dp_strobe_total_count_reg_nof_words,
                                   init_sl  => '0');
 
   TYPE t_cnt_arr IS ARRAY (INTEGER RANGE <>) OF STD_LOGIC_VECTOR(g_count_w-1 DOWNTO 0);
@@ -119,7 +125,7 @@ ARCHITECTURE rtl OF dp_strobe_total_count IS
   
 BEGIN
  
-  ASSERT g_nof_counts <= g_nof_counts_max REPORT "Too many counters to fit REGMAP." SEVERITY FAILURE;
+  ASSERT g_nof_counts <= c_nof_counts_max REPORT "Too many counters to fit REGMAP." SEVERITY FAILURE;
   ASSERT g_count_w <= g_mm_w*2 REPORT "Too wide counter to fit REGMAP." SEVERITY FAILURE;
 
   mm_cnt_clr <= (reg_mosi.rd OR reg_mosi.wr) WHEN TO_UINT(reg_mosi.address(c_mm_reg.adr_w-1 DOWNTO 0)) = c_clear_adr ELSE '0' ;
diff --git a/libraries/base/dp/src/vhdl/dp_tail_remove.vhd b/libraries/base/dp/src/vhdl/dp_tail_remove.vhd
index 744d6199dd150d4fe2decd0cf69270f9256358c1..b03c31b05c7bd69e39538205f46601172d8964aa 100644
--- a/libraries/base/dp/src/vhdl/dp_tail_remove.vhd
+++ b/libraries/base/dp/src/vhdl/dp_tail_remove.vhd
@@ -104,6 +104,7 @@ BEGIN
       v_wr_sosi_arr(0).eop := '0';
       v_wr_sosi_arr(c_nof_shiftreg_words-1).eop := '1';
       v_wr_sosi_arr(c_nof_shiftreg_words-1).empty := rd_sosi_arr(0).empty;
+      v_wr_sosi_arr(c_nof_shiftreg_words-1).err := rd_sosi_arr(0).err;
     END IF;
 
     wr_sosi_arr <= v_wr_sosi_arr;
diff --git a/libraries/base/dp/src/vhdl/mms_dp_bsn_monitor_v2.vhd b/libraries/base/dp/src/vhdl/mms_dp_bsn_monitor_v2.vhd
index 64bf0567a6ecd37bef46fb2db0a67ed9019d7af9..e94f3f21cc44c4a2f35572ae75c37fa6dd1c6512 100644
--- a/libraries/base/dp/src/vhdl/mms_dp_bsn_monitor_v2.vhd
+++ b/libraries/base/dp/src/vhdl/mms_dp_bsn_monitor_v2.vhd
@@ -29,12 +29,13 @@ USE IEEE.NUMERIC_STD.ALL;
 USE common_lib.common_pkg.ALL;
 USE common_lib.common_mem_pkg.ALL;
 USE work.dp_stream_pkg.ALL;
+USE work.dp_components_pkg.ALL;
 
 ENTITY mms_dp_bsn_monitor_v2 IS
   GENERIC (
     g_nof_streams        : POSITIVE := 1;
     g_cross_clock_domain : BOOLEAN := TRUE;  -- use FALSE when mm_clk and dp_clk are the same, else use TRUE to cross the clock domain
-    g_sync_timeout       : NATURAL := 200*10**6;
+    g_sync_timeout       : NATURAL := c_dp_sync_timeout;
     g_bsn_w              : NATURAL := c_dp_stream_bsn_w;
     g_error_bi           : NATURAL := 0;
     g_cnt_sop_w          : NATURAL := c_word_w;
@@ -61,7 +62,7 @@ END mms_dp_bsn_monitor_v2;
 
 ARCHITECTURE str OF mms_dp_bsn_monitor_v2 IS
 
-  CONSTANT c_reg_adr_w : NATURAL := ceil_log2(7);
+  CONSTANT c_reg_adr_w : NATURAL := c_dp_bsn_monitor_v2_reg_adr_w;
 
   SIGNAL mon_evt_arr           : STD_LOGIC_VECTOR(g_nof_streams-1 DOWNTO 0);
   SIGNAL mon_sync_timeout_arr  : STD_LOGIC_VECTOR(g_nof_streams-1 DOWNTO 0);
diff --git a/libraries/base/dp/tb/vhdl/tb_dp_bsn_monitor_v2.vhd b/libraries/base/dp/tb/vhdl/tb_dp_bsn_monitor_v2.vhd
index 09eeec859d5d0b885a9508a32c565ff0674afb5f..49596bc437a7c520c72e96b7b49e1a2004fcafa6 100644
--- a/libraries/base/dp/tb/vhdl/tb_dp_bsn_monitor_v2.vhd
+++ b/libraries/base/dp/tb/vhdl/tb_dp_bsn_monitor_v2.vhd
@@ -58,7 +58,9 @@ ARCHITECTURE tb OF tb_dp_bsn_monitor_v2 IS
   CONSTANT c_sync_timeout        : NATURAL := c_frame_len*c_sync_period;
   CONSTANT c_nof_repeat          : NATURAL := g_nof_sync * c_sync_period + 1;
   CONSTANT c_ref_sync_latency    : NATURAL := 7;
-  
+  CONSTANT c_mon_sync_latency    : NATURAL := 1;  -- due to sync_reg2 in dp_bsn_monitor_v2
+  CONSTANT c_exp_sync_latency    : NATURAL := c_ref_sync_latency + c_mon_sync_latency;
+
   -- Error control
   CONSTANT c_skip_sync_nr        : INTEGER := -1;  -- use e.g. 5 >= 0 to introduce a sync timeout at that sync interval 5 (causes missing sinc error by proc_dp_verify_sync), use -1 to disable skipping a sync
   CONSTANT c_nof_err             : NATURAL := 2;   -- <= c_sync_period   -- introduce frame errors
@@ -187,7 +189,7 @@ BEGIN
   expected_nof_sop     <= TO_UVEC(            c_sync_period, c_word_w);
   expected_nof_err     <= TO_UVEC(                c_nof_err, c_word_w);
   expected_nof_valid   <= TO_UVEC(c_frame_len*c_sync_period, c_word_w);
-  expected_latency     <= TO_UVEC(       c_ref_sync_latency, c_word_w);
+  expected_latency     <= TO_UVEC(       c_exp_sync_latency, c_word_w);
   
   ------------------------------------------------------------------------------
   -- SISO FLOW CONTROL GENERATION
diff --git a/libraries/base/dp/tb/vhdl/tb_dp_strobe_total_count.vhd b/libraries/base/dp/tb/vhdl/tb_dp_strobe_total_count.vhd
index 24f22387fdcd454352a37f80ca0b964ae7e47553..9a62cd3808a47f746877d7ad58155abfbdef299f 100644
--- a/libraries/base/dp/tb/vhdl/tb_dp_strobe_total_count.vhd
+++ b/libraries/base/dp/tb/vhdl/tb_dp_strobe_total_count.vhd
@@ -40,6 +40,7 @@ USE common_lib.tb_common_mem_pkg.ALL;
 USE common_lib.common_str_pkg.ALL;
 USE common_lib.tb_common_pkg.ALL;
 USE work.dp_stream_pkg.ALL;
+USE work.dp_components_pkg.ALL;
 
 ENTITY tb_dp_strobe_total_count IS
   GENERIC (
@@ -66,10 +67,10 @@ ARCHITECTURE tb OF tb_dp_strobe_total_count IS
   CONSTANT c_clip                  : BOOLEAN := TRUE;
 
   -- dut
-  CONSTANT c_nof_counts_max        : NATURAL := 15;
+  CONSTANT c_nof_counts_max        : NATURAL := c_dp_strobe_total_count_reg_nof_counts_max;  -- fixed by REGMAP
   CONSTANT c_nof_counts            : NATURAL := 3;  -- count stimuli.sync, sop, valid
   CONSTANT c_count_max             : NATURAL := 2**g_count_w - 1;
-  CONSTANT c_mm_addr_clear         : NATURAL := c_nof_counts_max*2;
+  CONSTANT c_mm_addr_clear         : NATURAL := c_dp_strobe_total_count_reg_clear_adr;
 
   -- c_tb_nof_sync - c_skip_nof_sync because first sync intervals are skipped
   -- by using clear, and -1, because stimuli_sosi.sync is used as ref_sync,
@@ -140,7 +141,6 @@ BEGIN
   u_dut : ENTITY work.dp_strobe_total_count
   GENERIC MAP (
     g_mm_w           => g_mm_w,
-    g_nof_counts_max => c_nof_counts_max,
     g_nof_counts     => c_nof_counts,
     g_count_w        => g_count_w,
     g_clip           => c_clip
diff --git a/libraries/base/dp/tb/vhdl/tb_mmp_dp_bsn_align_v2.vhd b/libraries/base/dp/tb/vhdl/tb_mmp_dp_bsn_align_v2.vhd
index c66a1b87fd2f9a1803d06e30dacd2c89a8f2f11a..fc46753c9debdd6578358bdf47a2b7b7f5c20b18 100644
--- a/libraries/base/dp/tb/vhdl/tb_mmp_dp_bsn_align_v2.vhd
+++ b/libraries/base/dp/tb/vhdl/tb_mmp_dp_bsn_align_v2.vhd
@@ -80,6 +80,7 @@ ARCHITECTURE tb OF tb_mmp_dp_bsn_align_v2 IS
   CONSTANT c_nof_clk_per_sync           : NATURAL := c_block_per_sync * c_block_period;
   CONSTANT c_nof_input_bsn_monitors     : NATURAL := c_nof_streams;
   CONSTANT c_use_bsn_output_monitor     : BOOLEAN := TRUE;
+  CONSTANT c_mon_sync_latency           : NATURAL := 1;  -- due to sync_reg2 in dp_bsn_monitor_v2
 
   CONSTANT c_reg_bsn_monitor_adr_w      : NATURAL := ceil_log2(7);
   CONSTANT c_reg_bsn_monitor_span       : NATURAL := 2**c_reg_bsn_monitor_adr_w;
@@ -216,7 +217,8 @@ BEGIN
     FOR I IN 0 TO c_nof_streams-1 LOOP
       proc_mem_mm_bus_rd(2*I, mm_clk, reg_bsn_align_cipo, reg_bsn_align_copi);
       proc_mem_mm_bus_rd_latency(1, mm_clk);
-      ASSERT reg_bsn_align_cipo.rddata(0) = '1' REPORT "Wrong stream disable for output " & int_to_str(I) SEVERITY ERROR;
+      ASSERT reg_bsn_align_cipo.rddata(0) = '1' REPORT
+        "Wrong stream disable for output " & int_to_str(I) SEVERITY ERROR;
     END LOOP;
 
     -- Write stream enable bits for stream_en_arr
@@ -230,7 +232,8 @@ BEGIN
     FOR I IN 0 TO c_nof_streams-1 LOOP
       proc_mem_mm_bus_rd(2*I, mm_clk, reg_bsn_align_cipo, reg_bsn_align_copi);
       proc_mem_mm_bus_rd_latency(1, mm_clk);
-      ASSERT reg_bsn_align_cipo.rddata(0) = '0' REPORT "Wrong BSN align stream enable for output " & int_to_str(I) SEVERITY ERROR;
+      ASSERT reg_bsn_align_cipo.rddata(0) = '0' REPORT
+        "Wrong BSN align stream enable for output " & int_to_str(I) SEVERITY ERROR;
     END LOOP;
 
     -- Write stream enable bits for stream_en_arr
@@ -244,7 +247,8 @@ BEGIN
     FOR I IN 0 TO c_nof_streams-1 LOOP
       proc_mem_mm_bus_rd(2*I, mm_clk, reg_bsn_align_cipo, reg_bsn_align_copi);
       proc_mem_mm_bus_rd_latency(1, mm_clk);
-      ASSERT reg_bsn_align_cipo.rddata(0) = '1' REPORT "Wrong BSN align stream enable for output " & int_to_str(I) SEVERITY ERROR;
+      ASSERT reg_bsn_align_cipo.rddata(0) = '1' REPORT
+        "Wrong BSN align stream enable for output " & int_to_str(I) SEVERITY ERROR;
     END LOOP;
 
     -- End of MM test
@@ -253,6 +257,7 @@ BEGIN
   END PROCESS;
 
   p_mm_verify_bsn_monitors : PROCESS
+    VARIABLE v_exp_latency : INTEGER;
   BEGIN
     proc_common_wait_until_high(mm_clk, verify_done);
 
@@ -263,11 +268,19 @@ BEGIN
       mon_latency_input_arr(I) <= TO_SINT(reg_input_monitor_cipo.rddata(31 DOWNTO 0));
       proc_common_wait_some_cycles(mm_clk, 1);
       IF g_lost_input = TRUE AND I = c_nof_streams-1 THEN
-        ASSERT mon_latency_input_arr(I) = -1 REPORT "Wrong input BSN monitor latency timeout for input " & int_to_str(I) &
-          " (" & int_to_str(mon_latency_input_arr(I)) & " /= -1)" SEVERITY ERROR;
+        v_exp_latency := -1;  -- -1 for BSN monitor timeout due to lost input
+        ASSERT mon_latency_input_arr(I) = v_exp_latency REPORT
+          "Wrong input BSN monitor latency timeout for input " & int_to_str(I) &
+          " (" & int_to_str(mon_latency_input_arr(I)) &
+          " /= " & int_to_str(v_exp_latency) &
+          ")" SEVERITY ERROR;
       ELSE
-        ASSERT mon_latency_input_arr(I) = func_input_delay(I) REPORT "Wrong input BSN monitor latency for input " & int_to_str(I) &
-          " (" & int_to_str(mon_latency_input_arr(I)) & " /= " & int_to_str(func_input_delay(I)) & ")" SEVERITY ERROR;
+        v_exp_latency := c_mon_sync_latency + func_input_delay(I);
+        ASSERT mon_latency_input_arr(I) = v_exp_latency REPORT
+          "Wrong input BSN monitor latency for input " & int_to_str(I) &
+          " (" & int_to_str(mon_latency_input_arr(I)) &
+          " /= " & int_to_str(v_exp_latency) &
+          ")" SEVERITY ERROR;
       END IF;
     END LOOP;
 
@@ -277,7 +290,11 @@ BEGIN
     mon_latency_output <= TO_SINT(reg_output_monitor_cipo.rddata(31 DOWNTO 0));
 
     proc_common_wait_some_cycles(mm_clk, 1);
-    ASSERT mon_latency_output = c_total_latency REPORT "Wrong output BSN monitor latency" SEVERITY ERROR;
+    v_exp_latency := c_mon_sync_latency + c_total_latency;
+    ASSERT mon_latency_output = v_exp_latency REPORT
+          "Wrong output BSN monitor latency (" & int_to_str(mon_latency_output) &
+          " /= " & int_to_str(v_exp_latency) &
+          ")" SEVERITY ERROR;
 
     -- End of MM test
     mm_end <= '1';
diff --git a/libraries/io/eth/eth.peripheral.yaml b/libraries/io/eth/eth.peripheral.yaml
index b887db31986ab67e390213cd6b06844e5147cedb..321706436eb9bc52210d67d1da407f3fb5c71fb5 100644
--- a/libraries/io/eth/eth.peripheral.yaml
+++ b/libraries/io/eth/eth.peripheral.yaml
@@ -233,5 +233,49 @@ peripherals:
               number_of_fields: 1024    # = c_eth_ram_nof_words in eth_pkg.vhd
               access_mode: RO
 
-         
-    
+  - peripheral_name: eth_tester_offload_hdr_dat
+    peripheral_description: "UDP offload header for the eth tester [1]."
+    mm_ports:
+      # MM port for eth_tester.vhd / dp_offload_tx_v3.vhd
+      - mm_port_name: REG_DP_OFFLOAD_TX_HDR_DAT
+        mm_port_type: REG
+        mm_port_span: 32 * MM_BUS_SIZE
+        mm_port_description: |
+          "The ETH/IP/UDP/application header fields for the 1GbE eth_tester offload UDP packets.
+
+           The RO value reads the MM value, not the used data path value. Therefore simply use access
+           mode RW for all fields. Whether the write RW MM value is used in the packet header depends
+           on the c_eth_tester_hdr_field_sel bit selection setting in eth_tester_pkg.vhd.
+
+           [1] https://support.astron.nl/confluence/display/L2M/L6+FWLIB+Design+Document%3A+ETH+tester+unit+for+1GbE
+          "
+        fields:
+          # eth field group
+          - - { field_name: word_align,             mm_width: 16,                                access_mode: RW, address_offset: 0x68 }
+          - - { field_name: eth_destination_mac,    mm_width: 32, user_width: 48, radix: uint64, access_mode: RW, address_offset: 0x60 }
+          - - { field_name: eth_source_mac,         mm_width: 32, user_width: 48, radix: uint64, access_mode: RW, address_offset: 0x58 }
+          - - { field_name: eth_type,               mm_width: 16,                                access_mode: RW, address_offset: 0x54 }
+          # ip field group
+          - - { field_name: ip_version,             mm_width:  4,                                access_mode: RW, address_offset: 0x50 }
+          - - { field_name: ip_header_length,       mm_width:  4,                                access_mode: RW, address_offset: 0x4c }
+          - - { field_name: ip_services,            mm_width:  8,                                access_mode: RW, address_offset: 0x48 }
+          - - { field_name: ip_total_length,        mm_width: 16,                                access_mode: RW, address_offset: 0x44 }
+          - - { field_name: ip_identification,      mm_width: 16,                                access_mode: RW, address_offset: 0x40 }
+          - - { field_name: ip_flags,               mm_width:  3,                                access_mode: RW, address_offset: 0x3c }
+          - - { field_name: ip_fragment_offset,     mm_width: 13,                                access_mode: RW, address_offset: 0x38 }
+          - - { field_name: ip_time_to_live,        mm_width:  8,                                access_mode: RW, address_offset: 0x34 }
+          - - { field_name: ip_protocol,            mm_width:  8,                                access_mode: RW, address_offset: 0x30 }
+          - - { field_name: ip_header_checksum,     mm_width: 16,                                access_mode: RW, address_offset: 0x2c }
+          - - { field_name: ip_source_address,      mm_width: 32,                                access_mode: RW, address_offset: 0x28 }
+          - - { field_name: ip_destination_address, mm_width: 32,                                access_mode: RW, address_offset: 0x24 }
+          # udp field group
+          - - { field_name: udp_source_port,        mm_width: 16,                                access_mode: RW, address_offset: 0x20 }
+          - - { field_name: udp_destination_port,   mm_width: 16,                                access_mode: RW, address_offset: 0x1c }
+          - - { field_name: udp_length,             mm_width: 16,                                access_mode: RW, address_offset: 0x18 }
+          - - { field_name: udp_checksum,           mm_width: 16,                                access_mode: RW, address_offset: 0x14 }
+          # application field group
+          - - { field_name: dp_length,              mm_width: 16,                                access_mode: RW, address_offset: 0x10 }
+          - - { field_name: dp_reserved,            mm_width: 15,                                access_mode: RW, address_offset: 0xC }
+          - - { field_name: dp_sync,                mm_width:  1,                                access_mode: RW, address_offset: 0x8 }
+          - - { field_name: dp_bsn,                 mm_width: 32, user_width: 64, radix: uint64, access_mode: RW, address_offset: 0x0 }
+
diff --git a/libraries/io/eth/hdllib.cfg b/libraries/io/eth/hdllib.cfg
index 5c3fb5fb6901a606b031a2c9f80e7f44f51ffc10..11d97855c2d2c66fc69d0db9120c34e4d56095d3 100644
--- a/libraries/io/eth/hdllib.cfg
+++ b/libraries/io/eth/hdllib.cfg
@@ -1,6 +1,6 @@
 hdl_lib_name = eth
 hdl_library_clause_name = eth_lib
-hdl_lib_uses_synth = dp common tech_tse
+hdl_lib_uses_synth = dp common diag tech_tse
 hdl_lib_uses_sim = 
 hdl_lib_technology = 
 
@@ -22,14 +22,21 @@ synth_files =
     src/vhdl/eth_control.vhd
     src/vhdl/eth_ihl_to_20.vhd
     src/vhdl/eth.vhd
-    
+    src/vhdl/eth_tester_pkg.vhd
+    src/vhdl/eth_tester_tx.vhd
+    src/vhdl/eth_tester_rx.vhd
+    src/vhdl/eth_tester.vhd
+
 test_bench_files = 
     src/vhdl/eth_statistics.vhd
     tb/vhdl/tb_eth_checksum.vhd
     tb/vhdl/tb_eth_crc_ctrl.vhd
     tb/vhdl/tb_eth_hdr.vhd
     tb/vhdl/tb_eth.vhd
+    tb/vhdl/tb_eth_tester_pkg.vhd
+    tb/vhdl/tb_eth_tester.vhd
     tb/vhdl/tb_tb_eth.vhd
+    tb/vhdl/tb_tb_eth_tester.vhd
     tb/vhdl/tb_eth_udp_offload.vhd
     tb/vhdl/tb_eth_ihl_to_20.vhd
     tb/vhdl/tb_tb_tb_eth_regression.vhd
@@ -41,6 +48,7 @@ regression_test_vhdl =
     tb/vhdl/tb_eth_udp_offload.vhd
     tb/vhdl/tb_eth_ihl_to_20.vhd
     tb/vhdl/tb_tb_eth.vhd
+    tb/vhdl/tb_tb_eth_tester.vhd
 
 
 [modelsim_project_file]
@@ -51,4 +59,4 @@ modelsim_copy_files =
 [quartus_project_file]
 quartus_copy_files =
     #src/vhdl/avs2_eth_coe_hw_<buildset_name>.tcl $RADIOHDL_BUILD_DIR/<buildset_name>/avs2_eth_coe_hw.tcl
-    src/vhdl/avs2_eth_coe_hw_<buildset_name>.tcl $RADIOHDL_WORK/libraries/io/eth/src/vhdl/avs2_eth_coe_hw.tcl
\ No newline at end of file
+    src/vhdl/avs2_eth_coe_hw_<buildset_name>.tcl $RADIOHDL_WORK/libraries/io/eth/src/vhdl/avs2_eth_coe_hw.tcl
diff --git a/libraries/io/eth/src/vhdl/eth_pkg.vhd b/libraries/io/eth/src/vhdl/eth_pkg.vhd
index f8df9fb044aca2ab6473089a7bc262edb7d38650..eb4f556c1b29f3bd5bc7cb8f949bda396653bc28 100644
--- a/libraries/io/eth/src/vhdl/eth_pkg.vhd
+++ b/libraries/io/eth/src/vhdl/eth_pkg.vhd
@@ -204,9 +204,10 @@ PACKAGE eth_pkg IS
                                                c_eth_reg_config_nof_words +
                                                c_eth_reg_control_nof_words +
                                                c_eth_reg_frame_nof_words +
-                                               c_eth_reg_status_nof_words;
-  CONSTANT c_eth_reg_addr_w       : NATURAL := ceil_log2(c_eth_reg_nof_words + 1);  -- + 1 for c_eth_continue_wi
-  
+                                               c_eth_reg_status_nof_words;  -- 4 + 4 + 1 + 1 + 1 = 11
+  CONSTANT c_eth_reg_addr_w       : NATURAL := ceil_log2(c_eth_reg_nof_words + 1);  -- 11 + 1 for c_eth_continue_wi = 12
+  CONSTANT c_eth_reg_addr_span    : NATURAL := 2**c_eth_reg_addr_w;  -- = 16
+
   ------------------------------------------------------------------------------
   -- Definitions for ETH Rx packet buffer and Tx packet buffer
   ------------------------------------------------------------------------------
diff --git a/libraries/io/eth/src/vhdl/eth_tester.vhd b/libraries/io/eth/src/vhdl/eth_tester.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..9ff3d07b8fae0fb274410b2b1076e17ac906403b
--- /dev/null
+++ b/libraries/io/eth/src/vhdl/eth_tester.vhd
@@ -0,0 +1,249 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2022
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-------------------------------------------------------------------------------
+-- AUthor: E. Kooistra
+-- Purpose: Test the 1GbE interface by sending and counting received packets.
+-- Description: See detailed design in [1]
+--
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L6+FWLIB+Design+Document%3A+ETH+tester+unit+for+1GbE
+
+LIBRARY IEEE, common_lib, dp_lib, diag_lib;
+USE IEEE.std_logic_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+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;
+
+ENTITY eth_tester IS
+  GENERIC (
+    g_nof_streams      : NATURAL := 1;
+    g_bg_sync_timeout  : NATURAL := 220*10**6;  -- 10% margin for nominal 1 s with st_clk at 200MHz
+    g_remove_crc       : BOOLEAN := TRUE  -- use TRUE when using sim_tse and tech_tse link interface,
+                                          -- use FALSE when streaming link interface
+  );
+  PORT (
+    -- Clocks and reset
+    mm_rst             : IN  STD_LOGIC;
+    mm_clk             : IN  STD_LOGIC;
+    st_rst             : IN  STD_LOGIC;
+    st_clk             : IN  STD_LOGIC;
+    st_pps             : IN  STD_LOGIC;
+
+    -- UDP transmit interface
+    eth_src_mac        : IN  STD_LOGIC_VECTOR(c_network_eth_mac_addr_w-1 DOWNTO 0);
+    ip_src_addr        : IN  STD_LOGIC_VECTOR(c_network_ip_addr_w-1 DOWNTO 0);
+    udp_src_port       : IN  STD_LOGIC_VECTOR(c_network_udp_port_w-1 DOWNTO 0);
+
+    tx_fifo_rd_emp_arr : OUT STD_LOGIC_VECTOR(g_nof_streams-1 DOWNTO 0);
+
+    tx_udp_sosi_arr    : OUT t_dp_sosi_arr(g_nof_streams-1 DOWNTO 0);
+    tx_udp_siso_arr    : IN  t_dp_siso_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS=> c_dp_siso_rdy);
+
+    -- UDP receive interface
+    rx_udp_sosi_arr    : IN  t_dp_sosi_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS=> c_dp_sosi_rst);
+
+    -- Memory Mapped Slaves (one per stream)
+    -- . Tx
+    reg_bg_ctrl_copi               : IN  t_mem_copi := c_mem_copi_rst;
+    reg_bg_ctrl_cipo               : OUT t_mem_cipo;
+    reg_hdr_dat_copi               : IN  t_mem_copi := c_mem_copi_rst;
+    reg_hdr_dat_cipo               : OUT t_mem_cipo;
+    reg_bsn_monitor_v2_tx_copi     : IN  t_mem_copi := c_mem_copi_rst;
+    reg_bsn_monitor_v2_tx_cipo     : OUT t_mem_cipo;
+    reg_strobe_total_count_tx_copi : IN  t_mem_copi := c_mem_copi_rst;
+    reg_strobe_total_count_tx_cipo : OUT t_mem_cipo;
+    -- . Rx
+    reg_bsn_monitor_v2_rx_copi             : IN  t_mem_copi := c_mem_copi_rst;
+    reg_bsn_monitor_v2_rx_cipo             : OUT t_mem_cipo;
+    reg_strobe_total_count_rx_copi         : IN  t_mem_copi := c_mem_copi_rst;
+    reg_strobe_total_count_rx_cipo         : OUT t_mem_cipo
+  );
+END eth_tester;
+
+
+ARCHITECTURE str OF eth_tester IS
+
+  SIGNAL ref_sync_arr  : STD_LOGIC_VECTOR(g_nof_streams-1 DOWNTO 0);
+
+  SIGNAL dp_length_arr : t_natural_arr(g_nof_streams-1 DOWNTO 0);  -- tx block length
+
+  -- MM port multiplexers
+  -- . Tx
+  SIGNAL reg_bg_ctrl_copi_arr               : t_mem_copi_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_copi_rst);
+  SIGNAL reg_bg_ctrl_cipo_arr               : t_mem_cipo_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_cipo_rst);
+  SIGNAL reg_hdr_dat_copi_arr               : t_mem_copi_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_copi_rst);
+  SIGNAL reg_hdr_dat_cipo_arr               : t_mem_cipo_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_cipo_rst);
+  SIGNAL reg_bsn_monitor_v2_tx_copi_arr     : t_mem_copi_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_copi_rst);
+  SIGNAL reg_bsn_monitor_v2_tx_cipo_arr     : t_mem_cipo_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_cipo_rst);
+  SIGNAL reg_strobe_total_count_tx_copi_arr : t_mem_copi_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_copi_rst);
+  SIGNAL reg_strobe_total_count_tx_cipo_arr : t_mem_cipo_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_cipo_rst);
+  -- . Rx
+  SIGNAL reg_bsn_monitor_v2_rx_copi_arr     : t_mem_copi_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_copi_rst);
+  SIGNAL reg_bsn_monitor_v2_rx_cipo_arr     : t_mem_cipo_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_cipo_rst);
+  SIGNAL reg_strobe_total_count_rx_copi_arr : t_mem_copi_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_copi_rst);
+  SIGNAL reg_strobe_total_count_rx_cipo_arr : t_mem_cipo_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_mem_cipo_rst);
+
+BEGIN
+
+  gen_streams : FOR I IN 0 TO g_nof_streams-1 GENERATE
+    u_tx : ENTITY work.eth_tester_tx
+    GENERIC MAP (
+      g_bg_sync_timeout  => g_bg_sync_timeout
+    )
+    PORT MAP (
+      -- Clocks and reset
+      mm_rst             => mm_rst,
+      mm_clk             => mm_clk,
+      st_rst             => st_rst,
+      st_clk             => st_clk,
+      st_pps             => st_pps,
+      ref_sync           => ref_sync_arr(I),
+
+      -- UDP transmit interface
+      eth_src_mac        => eth_src_mac,
+      ip_src_addr        => ip_src_addr,
+      udp_src_port       => udp_src_port,
+
+      tx_length          => dp_length_arr(I),
+      tx_fifo_rd_emp     => tx_fifo_rd_emp_arr(I),
+
+      tx_udp_sosi        => tx_udp_sosi_arr(I),
+      tx_udp_siso        => tx_udp_siso_arr(I),
+
+      -- Memory Mapped Slaves (one per stream)
+      reg_bg_ctrl_copi               => reg_bg_ctrl_copi_arr(I),
+      reg_bg_ctrl_cipo               => reg_bg_ctrl_cipo_arr(I),
+      reg_hdr_dat_copi               => reg_hdr_dat_copi_arr(I),
+      reg_hdr_dat_cipo               => reg_hdr_dat_cipo_arr(I),
+      reg_bsn_monitor_v2_tx_copi     => reg_bsn_monitor_v2_tx_copi_arr(I),
+      reg_bsn_monitor_v2_tx_cipo     => reg_bsn_monitor_v2_tx_cipo_arr(I),
+      reg_strobe_total_count_tx_copi => reg_strobe_total_count_tx_copi_arr(I),
+      reg_strobe_total_count_tx_cipo => reg_strobe_total_count_tx_cipo_arr(I)
+    );
+
+    u_rx : ENTITY work.eth_tester_rx
+    GENERIC MAP (
+      g_bg_sync_timeout  => g_bg_sync_timeout,
+      g_remove_crc       => g_remove_crc
+    )
+    PORT MAP (
+      -- Clocks and reset
+      mm_rst             => mm_rst,
+      mm_clk             => mm_clk,
+      st_rst             => st_rst,
+      st_clk             => st_clk,
+      ref_sync           => ref_sync_arr(I),
+
+      exp_length         => dp_length_arr(I),
+
+      -- UDP transmit interface
+      rx_udp_sosi        => rx_udp_sosi_arr(I),
+
+      -- Memory Mapped Slaves (one per stream)
+      reg_bsn_monitor_v2_rx_copi     => reg_bsn_monitor_v2_rx_copi_arr(I),
+      reg_bsn_monitor_v2_rx_cipo     => reg_bsn_monitor_v2_rx_cipo_arr(I),
+      reg_strobe_total_count_rx_copi => reg_strobe_total_count_rx_copi_arr(I),
+      reg_strobe_total_count_rx_cipo => reg_strobe_total_count_rx_cipo_arr(I)
+    );
+  END GENERATE;
+
+  -----------------------------------------------------------------------------
+  -- MM port multiplexers for g_nof_streams
+  -----------------------------------------------------------------------------
+
+  -- Tx
+  u_common_mem_mux_bg : ENTITY common_lib.common_mem_mux
+  GENERIC MAP (
+    g_nof_mosi    => g_nof_streams,
+    g_mult_addr_w => c_diag_bg_reg_adr_w
+  )
+  PORT MAP (
+    mosi     => reg_bg_ctrl_copi,
+    miso     => reg_bg_ctrl_cipo,
+    mosi_arr => reg_bg_ctrl_copi_arr,
+    miso_arr => reg_bg_ctrl_cipo_arr
+  );
+
+  u_common_mem_mux_hdr_dat : ENTITY common_lib.common_mem_mux
+  GENERIC MAP (
+    g_nof_mosi    => g_nof_streams,
+    g_mult_addr_w => c_eth_tester_reg_hdr_dat_addr_w
+  )
+  PORT MAP (
+    mosi     => reg_hdr_dat_copi,
+    miso     => reg_hdr_dat_cipo,
+    mosi_arr => reg_hdr_dat_copi_arr,
+    miso_arr => reg_hdr_dat_cipo_arr
+  );
+
+  u_common_mem_mux_bsn_monitor_v2_tx : ENTITY common_lib.common_mem_mux
+  GENERIC MAP (
+    g_nof_mosi    => g_nof_streams,
+    g_mult_addr_w => c_dp_bsn_monitor_v2_reg_adr_w
+  )
+  PORT MAP (
+    mosi     => reg_bsn_monitor_v2_tx_copi,
+    miso     => reg_bsn_monitor_v2_tx_cipo,
+    mosi_arr => reg_bsn_monitor_v2_tx_copi_arr,
+    miso_arr => reg_bsn_monitor_v2_tx_cipo_arr
+  );
+
+  u_common_mem_mux_strobe_total_count_tx : ENTITY common_lib.common_mem_mux
+  GENERIC MAP (
+    g_nof_mosi    => g_nof_streams,
+    g_mult_addr_w => c_dp_strobe_total_count_reg_adr_w
+  )
+  PORT MAP (
+    mosi     => reg_strobe_total_count_tx_copi,
+    miso     => reg_strobe_total_count_tx_cipo,
+    mosi_arr => reg_strobe_total_count_tx_copi_arr,
+    miso_arr => reg_strobe_total_count_tx_cipo_arr
+  );
+
+  -- Rx
+  u_common_mem_mux_bsn_monitor_v2_rx : ENTITY common_lib.common_mem_mux
+  GENERIC MAP (
+    g_nof_mosi    => g_nof_streams,
+    g_mult_addr_w => c_dp_bsn_monitor_v2_reg_adr_w
+  )
+  PORT MAP (
+    mosi     => reg_bsn_monitor_v2_rx_copi,
+    miso     => reg_bsn_monitor_v2_rx_cipo,
+    mosi_arr => reg_bsn_monitor_v2_rx_copi_arr,
+    miso_arr => reg_bsn_monitor_v2_rx_cipo_arr
+  );
+
+  u_common_mem_mux_strobe_total_count_rx : ENTITY common_lib.common_mem_mux
+  GENERIC MAP (
+    g_nof_mosi    => g_nof_streams,
+    g_mult_addr_w => c_dp_strobe_total_count_reg_adr_w
+  )
+  PORT MAP (
+    mosi     => reg_strobe_total_count_rx_copi,
+    miso     => reg_strobe_total_count_rx_cipo,
+    mosi_arr => reg_strobe_total_count_rx_copi_arr,
+    miso_arr => reg_strobe_total_count_rx_cipo_arr
+  );
+
+END str;
diff --git a/libraries/io/eth/src/vhdl/eth_tester_pkg.vhd b/libraries/io/eth/src/vhdl/eth_tester_pkg.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..a60dc6c2e381120c811770d11e5100a14d1ac4be
--- /dev/null
+++ b/libraries/io/eth/src/vhdl/eth_tester_pkg.vhd
@@ -0,0 +1,163 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2022
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: This package contains eth_tester specific constants and functions
+-- Description:
+--
+LIBRARY IEEE, common_lib;
+USE IEEE.std_logic_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE common_lib.common_field_pkg.ALL;
+USE common_lib.common_network_layers_pkg.ALL;
+
+PACKAGE eth_tester_pkg is
+
+  CONSTANT c_eth_tester_bg_block_len_max   : NATURAL := c_network_eth_payload_jumbo_max;  -- 9000 octets
+  CONSTANT c_eth_tester_rx_block_len_max   : NATURAL := c_network_eth_payload_jumbo_max + c_network_eth_crc_len;  -- 9004 octets
+  CONSTANT c_eth_tester_eth_packet_len_max : NATURAL := c_network_eth_word_align_len + c_network_eth_frame_jumbo_max;   -- 9020 octets = 2 word align + 14 header + 9000 + 4 crc
+
+  -- hdr_field_sel bit selects where the hdr_field value is set:
+  -- . 0 = data path controlled, value is set in data path, so field_default()
+  --       is not used.
+  -- . 1 = MM controlled, value is set via MM or by the field_default(), so any
+  --       data path setting in eth_tester.vhd is not used.
+  -- Remarks:
+  -- . For constant values it is convenient to use MM controlled, because then
+  --   the field_default() is used that can be set here in
+  --   c_eth_tester_hdr_field_arr.
+  -- . For reserved values it is convenient to use MM controlled, because then
+  --   in future they could still be changed via MM without having to recompile
+  --   the FW.
+  -- . Typically only use data path controlled if the value has to be set
+  --   dynamically, so dependent on the state of the FW.
+  -- . If a data path controlled field is not set in the FW, then it defaults
+  --   to 0 by declaring hdr_fields_in_arr with all 0. Hence e.g. udp_checksum
+  --   = 0 can be achieve via data path and default hdr_fields_in_arr = 0 or
+  --   via MM controlled and field_default(0).
+  CONSTANT c_eth_tester_nof_hdr_fields    : NATURAL := 1+3+12+4+4;
+  CONSTANT c_eth_tester_hdr_field_sel     : STD_LOGIC_VECTOR(c_eth_tester_nof_hdr_fields-1 DOWNTO 0) := "1"&"101"&"111011111001"&"0100"&"0100";
+
+  -- Default use destination MAC/IP/UDP = 0, so these have to be MM programmed
+  -- before eth_tester packets can be send.
+  CONSTANT c_eth_tester_hdr_field_arr : t_common_field_arr(c_eth_tester_nof_hdr_fields-1 DOWNTO 0) := (
+      ( field_name_pad("word_align"                              ), "RW", 16, field_default(0) ),  -- Tx TSE IP will strip these 2 padding bytes
+      ( field_name_pad("eth_dst_mac"                             ), "RW", 48, field_default(0) ),  -- c_eth_tester_eth_dst_mac
+      ( field_name_pad("eth_src_mac"                             ), "RW", 48, field_default(0) ),
+      ( field_name_pad("eth_type"                                ), "RW", 16, field_default(x"0800") ),
+
+      ( field_name_pad("ip_version"                              ), "RW",  4, field_default(4) ),
+      ( field_name_pad("ip_header_length"                        ), "RW",  4, field_default(5) ),
+      ( field_name_pad("ip_services"                             ), "RW",  8, field_default(0) ),
+      ( field_name_pad("ip_total_length"                         ), "RW", 16, field_default(0) ),  -- depends on BG block size, so set by data path
+      ( field_name_pad("ip_identification"                       ), "RW", 16, field_default(0) ),
+      ( field_name_pad("ip_flags"                                ), "RW",  3, field_default(2) ),
+      ( field_name_pad("ip_fragment_offset"                      ), "RW", 13, field_default(0) ),
+      ( field_name_pad("ip_time_to_live"                         ), "RW",  8, field_default(127) ),
+      ( field_name_pad("ip_protocol"                             ), "RW",  8, field_default(17) ),
+      ( field_name_pad("ip_header_checksum"                      ), "RW", 16, field_default(0) ),
+      ( field_name_pad("ip_src_addr"                             ), "RW", 32, field_default(0) ),
+      ( field_name_pad("ip_dst_addr"                             ), "RW", 32, field_default(0) ),  -- c_eth_tester_ip_dst_addr
+
+      ( field_name_pad("udp_src_port"                            ), "RW", 16, field_default(0) ),
+      ( field_name_pad("udp_dst_port"                            ), "RW", 16, field_default(0) ),  -- c_eth_tester_udp_dst_port
+      ( field_name_pad("udp_total_length"                        ), "RW", 16, field_default(0) ),  -- depends on BG block size, so set by data path
+      ( field_name_pad("udp_checksum"                            ), "RW", 16, field_default(0) ),
+
+      ( field_name_pad("dp_length"                               ), "RW", 16, field_default(0) ),
+      ( field_name_pad("dp_reserved"                             ), "RW", 15, field_default(0) ),
+      ( field_name_pad("dp_sync"                                 ), "RW",  1, field_default(0) ),
+      ( field_name_pad("dp_bsn"                                  ), "RW", 64, field_default(0) )
+  );
+  CONSTANT c_eth_tester_reg_hdr_dat_addr_w    : NATURAL := ceil_log2(field_nof_words(c_eth_tester_hdr_field_arr, c_word_w));  -- = 5
+  CONSTANT c_eth_tester_reg_hdr_dat_addr_span : NATURAL := 2**c_eth_tester_reg_hdr_dat_addr_w;  -- = 32
+
+  CONSTANT c_eth_tester_app_hdr_len  : NATURAL :=  12;  -- octets
+
+  -- Destinations:
+  -- . MAC address 00:22:86:08:pp:qq = UNB_ETH_SRC_MAC_BASE in
+  --   libraries/unb_osy/unbos_eth.h, pp = backplane ID, qq = node ID
+  -- . IP address 10.99.xx.yy = g_base_ip in ctrl_unb2#_board.vhd used in
+  --   libraries/unb_osy/unbos_eth.c, xx = backplane ID, yy = node ID + 1
+  -- . UDP port 15:8 = E0, 7:0 = gn_id (= ID[7:0] = backplane[5:0] & node[1:0])
+  CONSTANT c_eth_tester_eth_src_mac_47_16 : STD_LOGIC_VECTOR(31 DOWNTO 0) := x"00228608";
+  CONSTANT c_eth_tester_ip_src_addr_31_16 : STD_LOGIC_VECTOR(15 DOWNTO 0) := x"0A63";
+  CONSTANT c_eth_tester_udp_src_port_15_8 : STD_LOGIC_VECTOR( 7 DOWNTO 0) := x"E0";
+
+  TYPE t_eth_tester_app_header IS RECORD
+    dp_length   : STD_LOGIC_VECTOR(15 DOWNTO 0);
+    dp_reserved : STD_LOGIC_VECTOR(14 DOWNTO 0);
+    dp_sync     : STD_LOGIC;
+    dp_bsn      : STD_LOGIC_VECTOR(63 DOWNTO 0);
+  END RECORD;
+
+  TYPE t_eth_tester_header IS RECORD
+    eth : t_network_eth_header;
+    ip  : t_network_ip_header;
+    udp : t_network_udp_header;
+    app : t_eth_tester_app_header;
+  END RECORD;
+
+  FUNCTION func_eth_tester_map_header(hdr_fields_raw : STD_LOGIC_VECTOR) RETURN t_eth_tester_header;
+
+END eth_tester_pkg;
+
+
+PACKAGE BODY eth_tester_pkg IS
+
+  FUNCTION func_eth_tester_map_header(hdr_fields_raw : STD_LOGIC_VECTOR) RETURN t_eth_tester_header IS
+    VARIABLE v : t_eth_tester_header;
+  BEGIN
+    -- eth header
+    v.eth.dst_mac        := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "eth_dst_mac") DOWNTO field_lo(c_eth_tester_hdr_field_arr, "eth_dst_mac"));
+    v.eth.src_mac        := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "eth_src_mac") DOWNTO field_lo(c_eth_tester_hdr_field_arr, "eth_src_mac"));
+    v.eth.eth_type       := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "eth_type")    DOWNTO field_lo(c_eth_tester_hdr_field_arr, "eth_type"));
+
+    -- ip header
+    v.ip.version         := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_version")         DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_version"));
+    v.ip.header_length   := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_header_length")   DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_header_length"));
+    v.ip.services        := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_services")        DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_services"));
+    v.ip.total_length    := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_total_length")    DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_total_length"));
+    v.ip.identification  := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_identification")  DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_identification"));
+    v.ip.flags           := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_flags")           DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_flags"));
+    v.ip.fragment_offset := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_fragment_offset") DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_fragment_offset"));
+    v.ip.time_to_live    := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_time_to_live")    DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_time_to_live"));
+    v.ip.protocol        := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_protocol")        DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_protocol"));
+    v.ip.header_checksum := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_header_checksum") DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_header_checksum"));
+    v.ip.src_ip_addr     := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_src_addr")        DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_src_addr"));
+    v.ip.dst_ip_addr     := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "ip_dst_addr")        DOWNTO field_lo(c_eth_tester_hdr_field_arr, "ip_dst_addr"));
+
+    -- udp header
+    v.udp.src_port       := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "udp_src_port")     DOWNTO field_lo(c_eth_tester_hdr_field_arr, "udp_src_port"));
+    v.udp.dst_port       := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "udp_dst_port")     DOWNTO field_lo(c_eth_tester_hdr_field_arr, "udp_dst_port"));
+    v.udp.total_length   := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "udp_total_length") DOWNTO field_lo(c_eth_tester_hdr_field_arr, "udp_total_length"));
+    v.udp.checksum       := hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "udp_checksum")     DOWNTO field_lo(c_eth_tester_hdr_field_arr, "udp_checksum"));
+
+    -- app header
+    v.app.dp_length      :=    hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "dp_length")   DOWNTO field_lo(c_eth_tester_hdr_field_arr, "dp_length"));
+    v.app.dp_reserved    :=    hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "dp_reserved") DOWNTO field_lo(c_eth_tester_hdr_field_arr, "dp_reserved"));
+    v.app.dp_sync        := sl(hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "dp_sync")     DOWNTO field_lo(c_eth_tester_hdr_field_arr, "dp_sync")));
+    v.app.dp_bsn         :=    hdr_fields_raw(field_hi(c_eth_tester_hdr_field_arr, "dp_bsn")      DOWNTO field_lo(c_eth_tester_hdr_field_arr, "dp_bsn"));
+    RETURN v;
+  END func_eth_tester_map_header;
+
+END eth_tester_pkg;
+
diff --git a/libraries/io/eth/src/vhdl/eth_tester_rx.vhd b/libraries/io/eth/src/vhdl/eth_tester_rx.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..982054836a1dd884878c733548be96fc41ee054e
--- /dev/null
+++ b/libraries/io/eth/src/vhdl/eth_tester_rx.vhd
@@ -0,0 +1,266 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2022
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-------------------------------------------------------------------------------
+-- AUthor: E. Kooistra
+-- Purpose: Test the 1GbE interface by sending and counting received packets.
+-- Description: Rx part of eth_tester, see detailed design in [1]
+--
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L6+FWLIB+Design+Document%3A+ETH+tester+unit+for+1GbE
+
+LIBRARY IEEE, common_lib, dp_lib, diag_lib, tech_tse_lib;
+USE IEEE.std_logic_1164.ALL;
+USE IEEE.NUMERIC_STD.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE common_lib.common_field_pkg.ALL;
+USE dp_lib.dp_stream_pkg.ALL;
+USE diag_lib.diag_pkg.ALL;
+USE tech_tse_lib.tech_tse_pkg.ALL;
+USE work.eth_tester_pkg.ALL;
+
+ENTITY eth_tester_rx IS
+  GENERIC (
+    g_bg_sync_timeout  : NATURAL := 220*10**6;  -- 10% margin for nominal 1 s with st_clk at 200MHz
+    g_remove_crc       : BOOLEAN := TRUE  -- use TRUE when using sim_tse and tech_tse link interface,
+                                          -- use FALSE when streaming link interface
+  );
+  PORT (
+    -- Clocks and reset
+    mm_rst             : IN  STD_LOGIC;
+    mm_clk             : IN  STD_LOGIC;
+    st_rst             : IN  STD_LOGIC;
+    st_clk             : IN  STD_LOGIC;
+    ref_sync           : IN  STD_LOGIC;
+
+    exp_length         : IN NATURAL RANGE 0 TO 2**c_halfword_w - 1;  -- 16 bit
+
+    -- UDP receive interface
+    rx_udp_sosi        : IN  t_dp_sosi;
+
+    -- Memory Mapped Slaves (one per stream)
+    reg_bsn_monitor_v2_rx_copi     : IN  t_mem_copi := c_mem_copi_rst;
+    reg_bsn_monitor_v2_rx_cipo     : OUT t_mem_cipo;
+    reg_strobe_total_count_rx_copi : IN  t_mem_copi := c_mem_copi_rst;
+    reg_strobe_total_count_rx_cipo : OUT t_mem_cipo
+  );
+END eth_tester_rx;
+
+
+ARCHITECTURE str OF eth_tester_rx IS
+
+  CONSTANT c_nof_total_counts     : NATURAL := 3;  -- 0 = nof_sop, 1 = nof_valid, 2 = nof_crc_corrupt
+
+  CONSTANT c_empty_w              : NATURAL := 2;  -- for 0, 1, 2, 3 empty octets per word
+  CONSTANT c_error_w              : NATURAL := 1;
+
+  -- Rx FIFO size can be much less than rx_block_sz_max, because st_clk >
+  -- eth_clk rate, but with st level tx-rx loopback the Rx FIFO does need
+  -- rx_block_sz_max FIFO size.
+  CONSTANT rx_block_sz_max        : NATURAL := c_eth_tester_rx_block_len_max / c_word_sz;  -- = 9004 / 4 = 2251
+  CONSTANT c_fifo_size            : NATURAL := true_log_pow2(rx_block_sz_max);  -- = 4096 words
+
+  SIGNAL rx_udp_data         : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  SIGNAL rx_offload_sosi     : t_dp_sosi;
+  SIGNAL rx_offload_data     : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  SIGNAL decoded_sosi        : t_dp_sosi;
+  SIGNAL decoded_data        : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  SIGNAL decoded_length      : NATURAL;
+  SIGNAL rx_fifo_siso        : t_dp_siso;
+  SIGNAL rx_fifo_sosi        : t_dp_sosi;
+  SIGNAL rx_fifo_data        : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  SIGNAL rx_fifo_wr_ful      : STD_LOGIC;
+  SIGNAL rx_fifo_usedw       : STD_LOGIC_VECTOR(ceil_log2(c_fifo_size)-1 DOWNTO 0);
+  SIGNAL unpacked_sosi       : t_dp_sosi;
+  SIGNAL unpacked_data       : STD_LOGIC_VECTOR(c_octet_w-1 DOWNTO 0);
+  SIGNAL crc_corrupt         : STD_LOGIC := '0';
+
+  SIGNAL in_strobe_arr       : STD_LOGIC_VECTOR(c_nof_total_counts-1 DOWNTO 0);
+
+  SIGNAL hdr_fields_out_slv  : STD_LOGIC_VECTOR(1023 DOWNTO 0);
+  SIGNAL hdr_fields_raw_slv  : STD_LOGIC_VECTOR(1023 DOWNTO 0);
+  SIGNAL hdr_fields_out_rec  : t_eth_tester_header;
+  SIGNAL hdr_fields_raw_rec  : t_eth_tester_header;
+
+BEGIN
+
+  -- View sosi.data in Wave Window
+  rx_udp_data     <= rx_udp_sosi.data(c_word_w-1 DOWNTO 0);
+  rx_offload_data <= rx_offload_sosi.data(c_word_w-1 DOWNTO 0);
+  decoded_data    <= decoded_sosi.data(c_word_w-1 DOWNTO 0);
+  rx_fifo_data    <= rx_fifo_sosi.data(c_word_w-1 DOWNTO 0);
+  unpacked_data   <= unpacked_sosi.data(c_octet_w-1 DOWNTO 0);
+
+  -------------------------------------------------------------------------------
+  -- Rx ETH/UDP/IP packets with packed BG data
+  -------------------------------------------------------------------------------
+
+  u_dp_offload_rx : ENTITY dp_lib.dp_offload_rx
+  GENERIC MAP (
+    g_nof_streams   => 1,
+    g_data_w        => c_word_w,
+    g_symbol_w      => c_octet_w,
+    g_hdr_field_arr => c_eth_tester_hdr_field_arr,
+    g_remove_crc    => g_remove_crc,
+    g_crc_nof_words => 1
+  )
+  PORT MAP (
+    mm_rst    => mm_rst,
+    mm_clk    => mm_clk,
+
+    dp_rst    => st_rst,
+    dp_clk    => st_clk,
+
+    snk_in_arr(0)         => rx_udp_sosi,
+    src_out_arr(0)        => rx_offload_sosi,
+
+    hdr_fields_out_arr(0) => hdr_fields_out_slv,  -- Valid at src_out_arr(i).sop, use for sosi.sync
+    hdr_fields_raw_arr(0) => hdr_fields_raw_slv   -- Valid at src_out_arr(i).sop and beyond, use for sosi.bsn
+  );
+
+  -- View record in Wave Window
+  hdr_fields_out_rec <= func_eth_tester_map_header(hdr_fields_out_slv);
+  hdr_fields_raw_rec <= func_eth_tester_map_header(hdr_fields_raw_slv);
+
+  p_set_meta: PROCESS(rx_offload_sosi, hdr_fields_out_slv, hdr_fields_raw_slv)
+  BEGIN
+    decoded_sosi      <= rx_offload_sosi;
+    decoded_length    <=       TO_UINT(hdr_fields_raw_slv(field_hi(c_eth_tester_hdr_field_arr, "dp_length") DOWNTO field_lo(c_eth_tester_hdr_field_arr, "dp_length")));
+    decoded_sosi.sync <=            sl(hdr_fields_out_slv(field_hi(c_eth_tester_hdr_field_arr, "dp_sync") DOWNTO field_lo(c_eth_tester_hdr_field_arr, "dp_sync")));
+    decoded_sosi.bsn  <= RESIZE_DP_BSN(hdr_fields_raw_slv(field_hi(c_eth_tester_hdr_field_arr, "dp_bsn" ) DOWNTO field_lo(c_eth_tester_hdr_field_arr, "dp_bsn")));
+    -- Map rx_offload_sosi.err c_tech_tse_error_w = 6 bit value on to c_error_w = 1 bit decoded_sosi.err value
+    decoded_sosi.err <= TO_DP_ERROR(0);
+    IF UNSIGNED(rx_offload_sosi.err(c_tech_tse_error_w-1 DOWNTO 0)) /= 0 THEN
+      decoded_sosi.err <= TO_DP_ERROR(1);
+    END IF;
+  END PROCESS;
+
+  -- synthesis translate_off
+  p_verify_length : PROCESS(st_clk)
+  BEGIN
+    IF rising_edge(st_clk) THEN
+      IF decoded_sosi.sop = '1' THEN
+        ASSERT decoded_length = exp_length REPORT "Unexpected Rx length" & NATURAL'IMAGE(decoded_length) & " /= " & NATURAL'IMAGE(exp_length) & " expected length from Tx" SEVERITY ERROR;
+      END IF;
+    END IF;
+  END PROCESS;
+  -- synthesis translate_on
+
+  u_rx_fifo : ENTITY dp_lib.dp_fifo_sc
+  GENERIC MAP (
+    g_data_w         => c_word_w,
+    g_bsn_w          => c_diag_bg_bsn_init_w,  -- = 64 bit
+    g_empty_w        => c_empty_w,
+    g_error_w        => c_error_w,
+    g_use_bsn        => TRUE,
+    g_use_empty      => TRUE,
+    g_use_sync       => TRUE,
+    g_use_error      => TRUE,
+    g_fifo_size      => c_fifo_size
+  )
+  PORT MAP (
+    rst         => st_rst,
+    clk         => st_clk,
+    -- Monitor FIFO filling
+    wr_ful      => rx_fifo_wr_ful,
+    usedw       => rx_fifo_usedw,
+    -- ST sink
+    snk_in      => decoded_sosi,
+    -- ST source
+    src_in      => rx_fifo_siso,
+    src_out     => rx_fifo_sosi
+  );
+
+  u_unpack : ENTITY dp_lib.dp_repack_data  -- unpack 32b words into 8b octets
+  GENERIC MAP (
+    g_in_dat_w       => c_word_w,  -- = 32
+    g_in_nof_words   => 1,
+    g_in_symbol_w    => c_octet_w,
+    g_out_dat_w      => c_octet_w,  -- = 8
+    g_out_nof_words  => c_word_sz,  -- = 4
+    g_out_symbol_w   => c_octet_w
+  )
+  PORT MAP (
+    rst              => st_rst,
+    clk              => st_clk,
+    snk_out          => rx_fifo_siso,
+    snk_in           => rx_fifo_sosi,
+    src_out          => unpacked_sosi
+  );
+
+  -------------------------------------------------------------------------------
+  -- Rx packet monitors
+  -------------------------------------------------------------------------------
+
+  u_mms_dp_bsn_monitor_v2 : ENTITY dp_lib.mms_dp_bsn_monitor_v2
+  GENERIC MAP (
+    g_nof_streams  => 1,
+    g_sync_timeout => g_bg_sync_timeout
+  )
+  PORT MAP (
+    -- Memory-mapped clock domain
+    mm_rst         => mm_rst,
+    mm_clk         => mm_clk,
+    reg_mosi       => reg_bsn_monitor_v2_rx_copi,
+    reg_miso       => reg_bsn_monitor_v2_rx_cipo,
+
+    -- Streaming clock domain
+    dp_rst         => st_rst,
+    dp_clk         => st_clk,
+    ref_sync       => ref_sync,
+
+    in_sosi_arr(0) => unpacked_sosi
+  );
+
+  -- Rx CRC result is available at last octet
+  p_crc_corrupt : PROCESS(st_clk)
+  BEGIN
+    IF rising_edge(st_clk) THEN
+      crc_corrupt <= '0';
+      IF unpacked_sosi.eop = '1' AND unpacked_sosi.err(0) = '1' THEN
+        crc_corrupt <= '1';
+      END IF;
+    END IF;
+  END PROCESS;
+
+  in_strobe_arr(0) <= unpacked_sosi.sop;    -- count total nof Rx packets
+  in_strobe_arr(1) <= unpacked_sosi.valid;  -- count total nof Rx valid octets
+  in_strobe_arr(2) <= crc_corrupt;          -- count total nof corrupted Rx packets
+
+  u_dp_strobe_total_count : ENTITY dp_lib.dp_strobe_total_count
+  GENERIC MAP (
+    g_nof_counts  => c_nof_total_counts,
+    g_count_w     => c_longword_w,
+    g_clip        => TRUE
+  )
+  PORT MAP (
+    dp_rst        => st_rst,
+    dp_clk        => st_clk,
+
+    ref_sync      => unpacked_sosi.sync,
+    in_strobe_arr => in_strobe_arr,
+
+    mm_rst        => mm_rst,
+    mm_clk        => mm_clk,
+
+    reg_mosi      => reg_strobe_total_count_rx_copi,
+    reg_miso      => reg_strobe_total_count_rx_cipo
+  );
+
+END str;
diff --git a/libraries/io/eth/src/vhdl/eth_tester_tx.vhd b/libraries/io/eth/src/vhdl/eth_tester_tx.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..b4d2201a231e12ae4d740cac6cdc5561904ec466
--- /dev/null
+++ b/libraries/io/eth/src/vhdl/eth_tester_tx.vhd
@@ -0,0 +1,356 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2022
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-------------------------------------------------------------------------------
+-- AUthor: E. Kooistra
+-- Purpose: Test the 1GbE interface by sending and counting received packets.
+-- Description: Tx part of eth_tester, see detailed design in [1]
+--
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L6+FWLIB+Design+Document%3A+ETH+tester+unit+for+1GbE
+
+LIBRARY IEEE, common_lib, dp_lib, diag_lib;
+USE IEEE.std_logic_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE common_lib.common_field_pkg.ALL;
+USE common_lib.common_network_layers_pkg.ALL;
+USE dp_lib.dp_stream_pkg.ALL;
+USE diag_lib.diag_pkg.ALL;
+USE work.eth_tester_pkg.ALL;
+
+ENTITY eth_tester_tx IS
+  GENERIC (
+    g_bg_sync_timeout  : NATURAL := 220*10**6  -- 10% margin for nominal 1 s with st_clk at 200MHz
+  );
+  PORT (
+    -- Clocks and reset
+    mm_rst             : IN  STD_LOGIC;
+    mm_clk             : IN  STD_LOGIC;
+    st_rst             : IN  STD_LOGIC;
+    st_clk             : IN  STD_LOGIC;
+    st_pps             : IN  STD_LOGIC;
+    ref_sync           : OUT STD_LOGIC;
+
+    -- UDP transmit interface
+    eth_src_mac        : IN  STD_LOGIC_VECTOR(c_network_eth_mac_addr_w-1 DOWNTO 0);
+    ip_src_addr        : IN  STD_LOGIC_VECTOR(c_network_ip_addr_w-1 DOWNTO 0);
+    udp_src_port       : IN  STD_LOGIC_VECTOR(c_network_udp_port_w-1 DOWNTO 0);
+
+    tx_length          : OUT NATURAL RANGE 0 TO 2**c_halfword_w - 1;  -- 16 bit
+    tx_fifo_rd_emp     : OUT STD_LOGIC;
+
+    tx_udp_sosi        : OUT t_dp_sosi;
+    tx_udp_siso        : IN  t_dp_siso := c_dp_siso_rdy;
+
+    -- Memory Mapped Slaves (one per stream)
+    reg_bg_ctrl_copi               : IN  t_mem_copi := c_mem_copi_rst;
+    reg_bg_ctrl_cipo               : OUT t_mem_cipo;
+    reg_hdr_dat_copi               : IN  t_mem_copi := c_mem_copi_rst;
+    reg_hdr_dat_cipo               : OUT t_mem_cipo;
+    reg_bsn_monitor_v2_tx_copi     : IN  t_mem_copi := c_mem_copi_rst;
+    reg_bsn_monitor_v2_tx_cipo     : OUT t_mem_cipo;
+    reg_strobe_total_count_tx_copi : IN  t_mem_copi := c_mem_copi_rst;
+    reg_strobe_total_count_tx_cipo : OUT t_mem_cipo
+  );
+END eth_tester_tx;
+
+
+ARCHITECTURE str OF eth_tester_tx IS
+
+  CONSTANT c_empty_w              : NATURAL := 2;  -- for 0, 1, 2, 3 empty octets per word
+
+  -- Choose 10% extra margin for FIFO fill level that will result in BG block
+  -- level flow control via  bg_siso.xon. The input eop will release blocks
+  -- for FIFO output already before the FIFO is fill level is reached.
+  -- Choose FIFO size to fit one more packet on top of FIFO fill level.
+  CONSTANT c_fifo_fill            : NATURAL := c_eth_tester_bg_block_len_max * 11 / c_word_sz / 10;
+  CONSTANT c_fifo_size            : NATURAL := true_log_pow2(c_fifo_fill + c_eth_tester_bg_block_len_max);  -- = 8192
+
+  CONSTANT c_nof_total_counts     : NATURAL := 1;  -- one to count Tx packets
+
+  SIGNAL ip_total_length          : NATURAL;
+  SIGNAL udp_total_length         : NATURAL;
+  SIGNAL app_total_length         : NATURAL;
+
+  SIGNAL bg_siso                  : t_dp_siso := c_dp_siso_rdy;
+  SIGNAL bg_sosi                  : t_dp_sosi;
+  SIGNAL bg_data                  : STD_LOGIC_VECTOR(c_octet_w-1 DOWNTO 0);
+  SIGNAL bg_ctrl_hold             : t_diag_block_gen;
+  SIGNAL bg_block_len             : NATURAL;
+  SIGNAL tx_packed_siso           : t_dp_siso;
+  SIGNAL tx_packed_sosi           : t_dp_sosi;
+  SIGNAL tx_packed_data           : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  SIGNAL tx_fifo_sosi             : t_dp_sosi;
+  SIGNAL tx_fifo_data             : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  SIGNAL tx_fifo_siso             : t_dp_siso;
+  SIGNAL tx_fifo_wr_ful           : STD_LOGIC;
+  SIGNAL tx_fifo_wr_usedw         : STD_LOGIC_VECTOR(ceil_log2(c_fifo_size)-1 DOWNTO 0);
+  SIGNAL i_tx_fifo_rd_emp         : STD_LOGIC;
+
+  SIGNAL i_ref_sync               : STD_LOGIC := '0';
+  SIGNAL in_strobe_arr            : STD_LOGIC_VECTOR(c_nof_total_counts-1 DOWNTO 0);
+  SIGNAL i_tx_udp_sosi            : t_dp_sosi;
+  SIGNAL tx_udp_data              : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+
+  -- Use hdr_fields_slv_in default 0, to have DP driven fields ip_header_checksum = 0 and udp_checksum = 0
+  SIGNAL hdr_fields_slv_in        : STD_LOGIC_VECTOR(1023 DOWNTO 0) := (OTHERS => '0');
+  SIGNAL hdr_fields_slv_tx        : STD_LOGIC_VECTOR(1023 DOWNTO 0);
+  SIGNAL hdr_fields_rec_in        : t_eth_tester_header;
+  SIGNAL hdr_fields_rec_tx        : t_eth_tester_header;
+
+BEGIN
+
+  ref_sync <= i_ref_sync;
+  tx_length <= app_total_length;
+  tx_fifo_rd_emp <= i_tx_fifo_rd_emp;
+  tx_udp_sosi <= i_tx_udp_sosi;
+
+  -- View sosi.data in Wave Window
+  bg_data        <= bg_sosi.data(c_octet_w-1 DOWNTO 0);
+  tx_packed_data <= tx_packed_sosi.data(c_word_w-1 DOWNTO 0);
+  tx_fifo_data   <= tx_fifo_sosi.data(c_word_w-1 DOWNTO 0);
+  tx_udp_data    <= i_tx_udp_sosi.data(c_word_w-1 DOWNTO 0);
+
+  -------------------------------------------------------------------------------
+  -- Generate packed data blocks
+  -------------------------------------------------------------------------------
+  u_bg : ENTITY diag_lib.mms_diag_block_gen
+  GENERIC MAP (
+    g_nof_streams        => 1,
+    g_use_bg_buffer_ram  => FALSE,
+    g_buf_addr_w         => c_diag_bg_mem_adrs_w  -- = 24, use full range 2**24 for BG addr --> data values
+  )
+  PORT MAP (
+    -- System
+    mm_rst           => mm_rst,
+    mm_clk           => mm_clk,
+    dp_rst           => st_rst,
+    dp_clk           => st_clk,
+    en_sync          => st_pps,  -- block generator enable sync pulse in ST clock domain
+    -- MM interface
+    reg_bg_ctrl_mosi      => reg_bg_ctrl_copi,  -- BG control register (one for all streams)
+    reg_bg_ctrl_miso      => reg_bg_ctrl_cipo,
+    -- ST interface
+    bg_ctrl_hold_arr(0)   => bg_ctrl_hold,
+    out_sosi_arr(0)       => bg_sosi,
+    out_siso_arr(0)       => bg_siso
+  );
+
+  -- BG clock level flow control, needed when the dp_repack_data has to insert
+  -- empty octets into the last packed word.
+  bg_siso.ready <= tx_packed_siso.ready;
+
+  -- BG block level flow control, needed in case BG settings result in eth bit
+  -- rate > 1 Gbps, to avoid u_tx_fifo overflow.
+  p_bg_siso_xon : PROCESS(st_clk)
+  BEGIN
+    IF rising_edge(st_clk) THEN
+      bg_siso.xon <= '1';
+      IF TO_UINT(tx_fifo_wr_usedw) > c_fifo_fill THEN
+        bg_siso.xon <= '0';
+      END IF;
+    END IF;
+  END PROCESS;
+
+  u_pack : ENTITY dp_lib.dp_repack_data  -- pack 8b octets into 32b words
+  GENERIC MAP (
+    g_in_dat_w       => c_octet_w,  -- = 8
+    g_in_nof_words   => c_word_sz,  -- = 4
+    g_in_symbol_w    => c_octet_w,
+    g_out_dat_w      => c_word_w,  -- = 32
+    g_out_nof_words  => 1,
+    g_out_symbol_w   => c_octet_w
+  )
+  PORT MAP (
+    rst              => st_rst,
+    clk              => st_clk,
+    snk_out          => tx_packed_siso,
+    snk_in           => bg_sosi,
+    src_out          => tx_packed_sosi
+  );
+
+  u_tx_fifo : ENTITY dp_lib.dp_fifo_fill_eop
+  GENERIC MAP (
+    g_data_w         => c_word_w,
+    g_bsn_w          => c_diag_bg_bsn_init_w,  -- = 64 bit
+    g_empty_w        => c_empty_w,
+    g_use_bsn        => TRUE,
+    g_use_empty      => TRUE,
+    g_use_sync       => TRUE,
+    g_fifo_fill      => c_fifo_fill,
+    g_fifo_size      => c_fifo_size
+  )
+  PORT MAP (
+    wr_rst      => st_rst,
+    wr_clk      => st_clk,
+    rd_rst      => st_rst,
+    rd_clk      => st_clk,
+    -- Monitor FIFO filling
+    wr_ful      => tx_fifo_wr_ful,
+    wr_usedw    => tx_fifo_wr_usedw,
+    rd_emp      => i_tx_fifo_rd_emp,
+    -- ST sink
+    snk_in      => tx_packed_sosi,
+    -- ST source
+    src_in      => tx_fifo_siso,
+    src_out     => tx_fifo_sosi
+  );
+
+  -------------------------------------------------------------------------------
+  -- Assemble header info
+  -------------------------------------------------------------------------------
+  -- Whether the dp_offload_tx_hdr_fields value is actually used in the Tx
+  -- header depends on:
+  --   c_eth_tester_hdr_field_sel = "1"&"101"&"111011111001"&"0100"&"100"
+  --                                     eth   ip             udp    app
+  --   where 0 = data path, 1 = MM controlled. The '0' fields are assigned here
+  --   in hdr_fields_slv_in in order:
+  --     access   field
+  --     MM       word_align
+  --
+  --     MM       eth_dst_mac
+  --        DP    eth_src_mac
+  --     MM       eth_type
+  --
+  --     MM       ip_version
+  --     MM       ip_header_length
+  --     MM       ip_services
+  --        DP    ip_total_length
+  --     MM       ip_identification
+  --     MM       ip_flags
+  --     MM       ip_fragment_offset
+  --     MM       ip_time_to_live
+  --     MM       ip_protocol
+  --        DP    ip_header_checksum --> not here, will be filled in by 1GbE
+  --                                     eth component
+  --        DP    ip_src_addr
+  --     MM       ip_dst_addr
+  --
+  --        DP    udp_src_port
+  --     MM       udp_dst_port
+  --        DP    udp_total_length
+  --        DP    udp_checksum --> default fixed 0, so not used, not calculated
+  --                               here or in 1GbE eth component because would
+  --                               require store and forward
+  --        DP    dp_length
+  --     MM       dp_reserved
+  --        DP    dp_sync
+  --        DP    dp_bsn
+
+  -- The bg_block_len is still valid because bg_ctrl_hold holds the BG settings
+  -- until it restarts, so no need to pass bg_block_len on via e.g. the channel
+  -- field in u_fifo.
+  bg_block_len <= TO_UINT(bg_ctrl_hold.samples_per_packet(15 DOWNTO 0));  -- packet lenghts fit in 16b
+  app_total_length <= c_eth_tester_app_hdr_len + bg_block_len WHEN rising_edge(st_clk);
+  udp_total_length <= c_network_udp_header_len + app_total_length WHEN rising_edge(st_clk);
+  ip_total_length <= c_network_ip_header_len + udp_total_length WHEN rising_edge(st_clk);
+
+  hdr_fields_slv_in(field_hi(c_eth_tester_hdr_field_arr, "eth_src_mac"     ) DOWNTO field_lo(c_eth_tester_hdr_field_arr,  "eth_src_mac"     )) <= eth_src_mac;
+  hdr_fields_slv_in(field_hi(c_eth_tester_hdr_field_arr, "ip_total_length" ) DOWNTO field_lo(c_eth_tester_hdr_field_arr,  "ip_total_length" )) <= TO_UVEC(ip_total_length, 16);
+  hdr_fields_slv_in(field_hi(c_eth_tester_hdr_field_arr, "ip_src_addr"     ) DOWNTO field_lo(c_eth_tester_hdr_field_arr,  "ip_src_addr"     )) <= ip_src_addr;
+  hdr_fields_slv_in(field_hi(c_eth_tester_hdr_field_arr, "udp_src_port"    ) DOWNTO field_lo(c_eth_tester_hdr_field_arr,  "udp_src_port"    )) <= udp_src_port;
+  hdr_fields_slv_in(field_hi(c_eth_tester_hdr_field_arr, "udp_total_length") DOWNTO field_lo(c_eth_tester_hdr_field_arr,  "udp_total_length")) <= TO_UVEC(udp_total_length, 16);
+  hdr_fields_slv_in(field_hi(c_eth_tester_hdr_field_arr, "dp_length"       ) DOWNTO field_lo(c_eth_tester_hdr_field_arr,  "dp_length"       )) <= TO_UVEC(app_total_length, 16);
+  hdr_fields_slv_in(field_hi(c_eth_tester_hdr_field_arr, "dp_sync"         ) DOWNTO field_lo(c_eth_tester_hdr_field_arr,  "dp_sync"         )) <= slv(tx_fifo_sosi.sync);
+  hdr_fields_slv_in(field_hi(c_eth_tester_hdr_field_arr, "dp_bsn"          ) DOWNTO field_lo(c_eth_tester_hdr_field_arr,  "dp_bsn"          )) <= tx_fifo_sosi.bsn;
+
+  -------------------------------------------------------------------------------
+  -- Tx ETH/UDP/IP packets with packed BG data
+  -------------------------------------------------------------------------------
+  u_dp_offload_tx : ENTITY dp_lib.dp_offload_tx_v3
+  GENERIC MAP (
+    g_nof_streams    => 1,
+    g_data_w         => c_word_w,
+    g_symbol_w       => c_octet_w,
+    g_hdr_field_arr  => c_eth_tester_hdr_field_arr,
+    g_hdr_field_sel  => c_eth_tester_hdr_field_sel,
+    g_pipeline_ready => TRUE
+  )
+  PORT MAP (
+    mm_rst                => mm_rst,
+    mm_clk                => mm_clk,
+    dp_rst                => st_rst,
+    dp_clk                => st_clk,
+
+    reg_hdr_dat_mosi      => reg_hdr_dat_copi,
+    reg_hdr_dat_miso      => reg_hdr_dat_cipo,
+
+    snk_in_arr(0)         => tx_fifo_sosi,
+    snk_out_arr(0)        => tx_fifo_siso,
+
+    src_out_arr(0)        => i_tx_udp_sosi,
+    src_in_arr(0)         => tx_udp_siso,
+
+    hdr_fields_in_arr(0)  => hdr_fields_slv_in,  -- hdr_fields_slv_in_arr(i) is considered valid @ snk_in_arr(i).sop
+    hdr_fields_out_arr(0) => hdr_fields_slv_tx
+  );
+
+  -- View record in Wave Window
+  hdr_fields_rec_in <= func_eth_tester_map_header(hdr_fields_slv_in);
+  hdr_fields_rec_tx <= func_eth_tester_map_header(hdr_fields_slv_tx);
+
+  -------------------------------------------------------------------------------
+  -- Tx packet monitors
+  -------------------------------------------------------------------------------
+  i_ref_sync <= tx_fifo_sosi.sync WHEN rising_edge(st_clk);
+
+  u_mms_dp_bsn_monitor_v2 : ENTITY dp_lib.mms_dp_bsn_monitor_v2
+  GENERIC MAP (
+    g_nof_streams  => 1,
+    g_sync_timeout => g_bg_sync_timeout
+  )
+  PORT MAP (
+    -- Memory-mapped clock domain
+    mm_rst         => mm_rst,
+    mm_clk         => mm_clk,
+    reg_mosi       => reg_bsn_monitor_v2_tx_copi,
+    reg_miso       => reg_bsn_monitor_v2_tx_cipo,
+
+    -- Streaming clock domain
+    dp_rst         => st_rst,
+    dp_clk         => st_clk,
+    ref_sync       => i_ref_sync,
+
+    in_siso_arr(0) => tx_fifo_siso,
+    in_sosi_arr(0) => tx_fifo_sosi
+  );
+
+  in_strobe_arr(0) <= tx_fifo_sosi.sop;  -- count total nof Tx packets
+
+  u_dp_strobe_total_count : ENTITY dp_lib.dp_strobe_total_count
+  GENERIC MAP (
+    g_nof_counts  => c_nof_total_counts,
+    g_count_w     => c_longword_w,
+    g_clip        => TRUE
+  )
+  PORT MAP (
+    dp_rst        => st_rst,
+    dp_clk        => st_clk,
+
+    ref_sync      => i_ref_sync,
+    in_strobe_arr => in_strobe_arr,
+
+    mm_rst        => mm_rst,
+    mm_clk        => mm_clk,
+
+    reg_mosi      => reg_strobe_total_count_tx_copi,
+    reg_miso      => reg_strobe_total_count_tx_cipo
+  );
+
+END str;
diff --git a/libraries/io/eth/tb/vhdl/tb_eth_tester.vhd b/libraries/io/eth/tb/vhdl/tb_eth_tester.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..f0f0574905364b3ac22679e2f80e0799a633fc9c
--- /dev/null
+++ b/libraries/io/eth/tb/vhdl/tb_eth_tester.vhd
@@ -0,0 +1,686 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2022
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-------------------------------------------------------------------------------
+-- AUthor: E. Kooistra
+-- Purpose: Test bench for eth_tester
+-- Description: See detailed design in [1]
+--   The g_nof_streams >= 1 are tested independently, using g_bg_ctrl_first for
+--   BG in stream 0 and g_bg_ctrl_others for all other streams.
+--
+-- Usage:
+-- > as 8
+-- # * E.g. view sosi/data signals in dut/gen_streams/u_rx and u_tx
+-- # * Cannot use run -a when g_loopback_eth=TRUE and g_eth_sim_level=0,
+-- #   because the TSE IP model remains active internally after tb_end
+-- > run 100 us  # when g_loopback_eth=TRUE and g_eth_sim_level=0, else:
+-- > run -a
+--
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L6+FWLIB+Design+Document%3A+ETH+tester+unit+for+1GbE
+-- [2] https://support.astron.nl/confluence/display/L2M/L4+SDPFW+Decision%3A+Application+header+size+in+Ethernet+packets
+
+LIBRARY IEEE, common_lib, dp_lib, diag_lib, technology_lib, tech_tse_lib;
+USE IEEE.std_logic_1164.ALL;
+USE IEEE.numeric_std.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE common_lib.common_str_pkg.ALL;
+USE common_lib.tb_common_pkg.ALL;
+USE common_lib.tb_common_mem_pkg.ALL;
+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;
+USE technology_lib.technology_select_pkg.ALL;
+USE tech_tse_lib.tech_tse_pkg.ALL;
+USE tech_tse_lib.tb_tech_tse_pkg.ALL;
+
+ENTITY tb_eth_tester IS
+  GENERIC (
+    g_tb_index         : NATURAL := 0;  -- use to incremental delay logging from tb instances in tb_tb
+    g_nof_sync         : NATURAL := 2;  -- number of BG sync intervals to set c_run_time
+    g_nof_streams      : NATURAL := 1;  -- <= c_eth_nof_udp_ports = 4 when g_loopback_tx_rx = 1
+    g_loopback_eth     : BOOLEAN := TRUE;  -- FALSE = sosi loopback, TRUE = eth loopback (using sim_tse or tech_tse)
+    g_eth_sim_level    : NATURAL := 0;  -- when g_loopback_eth = TRUE, then 0 = use tech_tse IP; 1 = use fast sim_tse model
+    g_corrupted_en     : BOOLEAN := FALSE;  -- when TRUE cause a corrupted Rx packet, when tech_tse is used
+
+    -- t_diag_block_gen_integer =
+    --   sl:  enable
+    --   sl:  enable_sync
+    --   nat: samples_per_packet
+    --   nat: blocks_per_sync
+    --   nat: gapsize
+    --   nat: mem_low_adrs
+    --   nat: mem_high_adrs
+    --   nat: bsn_init
+    --g_bg_ctrl_first    : t_diag_block_gen_integer := ('1', '1', 50, 3, 100, 0, c_diag_bg_mem_max_adr, 0);  -- for first stream
+    --g_bg_ctrl_others   : t_diag_block_gen_integer := ('1', '1', 30, 3, 10, 0, c_diag_bg_mem_max_adr, 0)   -- for other streams
+    g_bg_ctrl_first    : t_diag_block_gen_integer := ('1', '1', 50, 3, 200, 0, c_diag_bg_mem_max_adr, 0);  -- for first stream
+    g_bg_ctrl_others   : t_diag_block_gen_integer := ('1', '1', 30, 3, 10, 0, c_diag_bg_mem_max_adr, 0)   -- for other streams
+  );
+  PORT (
+    tb_end : OUT STD_LOGIC
+  );
+END tb_eth_tester;
+
+
+ARCHITECTURE tb OF tb_eth_tester IS
+
+  CONSTANT c_tb_str                : STRING := "tb-" & NATURAL'IMAGE(g_tb_index) & " : ";  -- use to distinguish logging from tb instances in tb_tb
+  CONSTANT eth_clk_period          : TIME :=  8 ns;  -- 125 MHz
+  CONSTANT mm_clk_period           : TIME := 10 ns;  -- 100 MHz
+  CONSTANT c_nof_st_clk_per_s      : NATURAL := 200 * 10**6;
+  CONSTANT st_clk_period           : TIME :=  (10**9 / c_nof_st_clk_per_s) * 1 ns;  -- 5 ns, 200 MHz
+
+  CONSTANT c_bg_block_len_first    : NATURAL := g_bg_ctrl_first.samples_per_packet;
+  CONSTANT c_bg_block_len_others   : NATURAL := g_bg_ctrl_others.samples_per_packet;
+  CONSTANT c_bg_block_len_max      : NATURAL := largest(c_bg_block_len_first, c_bg_block_len_others);
+
+  CONSTANT c_bg_slot_len_first     : NATURAL := c_bg_block_len_first + g_bg_ctrl_first.gapsize;
+  CONSTANT c_bg_slot_len_others    : NATURAL := c_bg_block_len_others + g_bg_ctrl_others.gapsize;
+  CONSTANT c_bg_slot_len_max       : NATURAL := largest(c_bg_slot_len_first, c_bg_slot_len_others);
+
+  CONSTANT c_eth_packet_len_first  : NATURAL := func_eth_tester_eth_packet_length(c_bg_block_len_first);
+  CONSTANT c_eth_packet_len_others : NATURAL := func_eth_tester_eth_packet_length(c_bg_block_len_others);
+
+  -- Use REAL to avoid NATURAL overflow in bps calculation
+  CONSTANT c_bg_nof_bps_first      : REAL := REAL(c_bg_block_len_first * c_octet_w) * REAL(c_nof_st_clk_per_s) / REAL(c_bg_slot_len_first);
+  CONSTANT c_bg_nof_bps_others     : REAL := REAL(c_bg_block_len_others * c_octet_w) * REAL(c_nof_st_clk_per_s) / REAL(c_bg_slot_len_others);
+  CONSTANT c_bg_nof_bps_total      : REAL := c_bg_nof_bps_first + REAL(g_nof_streams - 1) * c_bg_nof_bps_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_run_time              : NATURAL := g_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_en       : BOOLEAN := c_bg_block_len_max >= 50;
+  CONSTANT c_rx_exp_latency_st       : NATURAL := 27;
+  CONSTANT c_rx_exp_latency_sim_tse  : NATURAL := 165;
+  CONSTANT c_rx_exp_latency_tech_tse : NATURAL := 375;
+
+  -- CRC is added by Tx TSE IP and removed by dp_offload_rx when g_remove_crc =
+  -- g_loopback_eth = TRUE. Therefore internally only application payload
+  -- (= block_len) octets are counted.
+  CONSTANT c_nof_valid_per_packet_first   : NATURAL := c_bg_block_len_first;
+  CONSTANT c_nof_valid_per_packet_others  : NATURAL := c_bg_block_len_others;
+
+  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(c_bg_block_len_first * c_octet_w, c_word_w);
+  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(c_bg_block_len_others* 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
+  CONSTANT c_gn_eth_src_mac     : STD_LOGIC_VECTOR(c_network_eth_mac_addr_w-1 DOWNTO 0) := c_eth_tester_eth_src_mac_47_16 & func_eth_tester_gn_index_to_mac_15_0(c_gn_index);
+  CONSTANT c_gn_ip_src_addr     : STD_LOGIC_VECTOR(c_network_ip_addr_w-1 DOWNTO 0) := c_eth_tester_ip_src_addr_31_16 & func_eth_tester_gn_index_to_ip_15_0(c_gn_index);
+  CONSTANT c_gn_udp_src_port    : STD_LOGIC_VECTOR(c_network_udp_port_w-1 DOWNTO 0) := c_eth_tester_udp_src_port_15_8 & TO_UVEC(c_gn_index, 8);
+
+  -- Clocks and reset
+  SIGNAL mm_rst              : STD_LOGIC := '1';
+  SIGNAL mm_clk              : STD_LOGIC := '1';
+  SIGNAL st_rst              : STD_LOGIC := '1';
+  SIGNAL st_clk              : STD_LOGIC := '1';
+  SIGNAL st_pps              : STD_LOGIC := '0';
+  SIGNAL stimuli_end         : STD_LOGIC := '0';
+  SIGNAL i_tb_end            : STD_LOGIC := '0';
+
+  SIGNAL eth_clk             : STD_LOGIC := '1';
+  SIGNAL eth_txp             : STD_LOGIC;
+  SIGNAL eth_rxp             : STD_LOGIC;
+  SIGNAL eth_corrupt         : STD_LOGIC := '0';
+
+  -- 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);
+
+  SIGNAL tx_fifo_rd_emp_arr  : STD_LOGIC_VECTOR(g_nof_streams-1 DOWNTO 0);
+
+  -- ETH UDP data path interface
+  SIGNAL tx_udp_sosi_arr     : t_dp_sosi_arr(g_nof_streams-1 DOWNTO 0);
+  SIGNAL tx_udp_siso_arr     : t_dp_siso_arr(g_nof_streams-1 DOWNTO 0) := (OTHERS => c_dp_siso_rdy);
+  SIGNAL rx_udp_sosi_arr     : t_dp_sosi_arr(g_nof_streams-1 DOWNTO 0);
+
+  -- MM interface
+  -- . Tx
+  SIGNAL reg_bg_ctrl_copi                : t_mem_copi := c_mem_copi_rst;
+  SIGNAL reg_bg_ctrl_cipo                : t_mem_cipo;
+  SIGNAL reg_hdr_dat_copi                : t_mem_copi := c_mem_copi_rst;
+  SIGNAL reg_hdr_dat_cipo                : t_mem_cipo;
+  SIGNAL reg_bsn_monitor_v2_tx_copi      : t_mem_copi := c_mem_copi_rst;
+  SIGNAL reg_bsn_monitor_v2_tx_cipo      : t_mem_cipo;
+  SIGNAL reg_strobe_total_count_tx_copi  : t_mem_copi := c_mem_copi_rst;
+  SIGNAL reg_strobe_total_count_tx_cipo  : t_mem_cipo;
+  -- . Rx
+  SIGNAL reg_bsn_monitor_v2_rx_copi      : t_mem_copi := c_mem_copi_rst;
+  SIGNAL reg_bsn_monitor_v2_rx_cipo      : t_mem_cipo;
+  SIGNAL reg_strobe_total_count_rx_copi  : t_mem_copi := c_mem_copi_rst;
+  SIGNAL reg_strobe_total_count_rx_cipo  : t_mem_cipo;
+
+  -- . reg_strobe_total_count
+  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_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);
+
+  -- . reg_bsn_monitor_v2
+  SIGNAL tx_mon_nof_sop_arr      : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  SIGNAL tx_mon_nof_valid_arr    : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  SIGNAL tx_mon_latency_arr      : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  SIGNAL rx_mon_nof_sop_arr      : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  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;
+
+  -- . set up eth when g_eth_sim_level = 0
+  SIGNAL tse_init                : STD_LOGIC := '1';
+  SIGNAL tse_copi                : t_mem_copi := c_mem_copi_rst;
+  SIGNAL tse_cipo                : t_mem_cipo;
+  SIGNAL tse_psc_access          : STD_LOGIC;
+
+  -- View in Wave window
+  SIGNAL dbg_c_run_time                : NATURAL := c_run_time;
+  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;
+  SIGNAL dbg_c_mon_nof_valid_first_tx  : NATURAL := c_mon_nof_valid_first_tx;
+  SIGNAL dbg_c_mon_nof_valid_first_rx  : NATURAL := c_mon_nof_valid_first_rx;
+  SIGNAL dbg_c_mon_nof_valid_others_tx : NATURAL := c_mon_nof_valid_others_tx;
+  SIGNAL dbg_c_mon_nof_valid_others_rx : NATURAL := c_mon_nof_valid_others_rx;
+
+BEGIN
+
+  tb_end <= i_tb_end;
+
+  eth_clk <= (NOT eth_clk) OR i_tb_end AFTER eth_clk_period/2;
+  mm_clk <= (NOT mm_clk) OR i_tb_end AFTER mm_clk_period/2;
+  st_clk <= (NOT st_clk) OR i_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_packet_arr : t_natural_arr(g_nof_streams-1 DOWNTO 0);
+  --                         (g_nof_streams-1 DOWNTO 1 => g_nof_sync * g_bg_ctrl_others.blocks_per_sync,
+  --                                                 0 => g_nof_sync * g_bg_ctrl_first.blocks_per_sync);
+  -- yields verror 1074, verror 1048, therefor use p_init instead, and
+  -- therefor use bg_ctrl_arr instead of c_bg_ctrl_arr.
+  p_init : PROCESS
+  BEGIN
+    bg_ctrl_arr    <= (OTHERS => g_bg_ctrl_others);
+    bg_ctrl_arr(0) <= g_bg_ctrl_first;
+
+    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_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
+    IF g_corrupted_en = TRUE THEN
+      rx_exp_total_count_nof_corrupted_arr(0) <= 1;
+    END IF;
+    WAIT;
+  END PROCESS;
+
+  -----------------------------------------------------------------------------
+  -- MM control and monitoring
+  -----------------------------------------------------------------------------
+  p_mm : PROCESS
+    VARIABLE v_value        : NATURAL;
+    VARIABLE v_offset       : NATURAL;
+    VARIABLE v_udp_dst_port : NATURAL;
+  BEGIN
+    i_tb_end <= '0';
+
+    proc_common_wait_until_low(mm_clk, mm_rst);
+    proc_common_wait_some_cycles(mm_clk, 10);
+
+    proc_common_wait_until_low(mm_clk, tse_init);
+    proc_common_wait_some_cycles(mm_clk, 10);
+
+    ---------------------------------------------------------------------------
+    -- Rx UDP offload port
+    ---------------------------------------------------------------------------
+    v_udp_dst_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_udp_dst_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#7#, v_udp_dst_port + I, mm_clk, reg_hdr_dat_cipo, reg_hdr_dat_copi);
+      proc_mem_mm_bus_wr(v_offset + 16#10#, 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#18#, 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#19#, 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_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);
+      proc_mem_mm_bus_wr(v_offset + 3, bg_ctrl_arr(I).gapsize,            mm_clk, reg_bg_ctrl_copi);
+      proc_mem_mm_bus_wr(v_offset + 4, bg_ctrl_arr(I).mem_low_adrs,       mm_clk, reg_bg_ctrl_copi);
+      proc_mem_mm_bus_wr(v_offset + 5, bg_ctrl_arr(I).mem_high_adrs,      mm_clk, reg_bg_ctrl_copi);
+      proc_mem_mm_bus_wr(v_offset + 6, bg_ctrl_arr(I).bsn_init,           mm_clk, reg_bg_ctrl_copi);  -- low part
+      proc_mem_mm_bus_wr(v_offset + 7, 0,                                 mm_clk, reg_bg_ctrl_copi);  -- high part
+      -- Enable the BG at st_pps pulse.
+      proc_mem_mm_bus_wr(v_offset + 0, 3, mm_clk, reg_bg_ctrl_copi);
+    END LOOP;
+    proc_common_wait_some_cycles(mm_clk, 10);
+    -- Issue an st_pps pulse to start the enabled BG
+    proc_common_gen_pulse(st_clk, st_pps);
+
+    -- Run test
+    proc_common_wait_some_cycles(st_clk, c_run_time);
+
+    -- Disable the BG
+    FOR I IN g_nof_streams-1 DOWNTO 0 LOOP
+      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;
+
+    -- Wait until Tx FIFOs have emptied for all streams
+    WHILE UNSIGNED(tx_fifo_rd_emp_arr(g_nof_streams-1 DOWNTO 0)) /= 2**g_nof_streams - 1 LOOP
+      proc_common_wait_some_cycles(st_clk, 1);
+    END LOOP;
+    proc_common_wait_some_cycles(st_clk, c_bg_sync_period_max);
+    stimuli_end <= '1';
+
+    -- Delay logging between different tb instances
+    proc_common_wait_some_cycles(st_clk, g_tb_index * 100);
+
+    -- Print logging
+    print_str("");   -- log empty line between tb results
+    FOR I IN g_nof_streams-1 DOWNTO 0 LOOP
+      IF I = 0 THEN
+        print_str(c_tb_str &
+            "ETH bit rate (" & NATURAL'IMAGE(I) & ") :" &
+            " c_bg_nof_bps_first = " & REAL'IMAGE(c_bg_nof_bps_first) & " bps");
+      ELSE
+        print_str(c_tb_str &
+            "ETH bit rate (" & NATURAL'IMAGE(I) & ") :" &
+            " c_bg_nof_bps_others = " & REAL'IMAGE(c_bg_nof_bps_others) & " bps");
+      END IF;
+    END LOOP;
+    IF g_nof_streams > 1 THEN
+        print_str(c_tb_str &
+            "ETH bit rate total :" &
+            " c_bg_nof_bps_total = " & REAL'IMAGE(c_bg_nof_bps_total) & " bps");
+    END IF;
+    ASSERT c_bg_nof_bps_total < 10.0**9 REPORT "Tx flow control will keep ETH bitrate < 1Gbps." SEVERITY NOTE;
+
+    -------------------------------------------------------------------------
+    -- 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
+      v_offset := I * c_dp_strobe_total_count_reg_adr_span;
+      -- 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_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_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_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);
+      rx_total_count_nof_corrupted_arr(I) <= TO_UINT(reg_strobe_total_count_rx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      proc_common_wait_some_cycles(mm_clk, 1);
+
+      -- Print logging
+      print_str(c_tb_str &
+          "Tx total counts monitor(" & NATURAL'IMAGE(I) & ") :" &
+          " nof_packet = " & NATURAL'IMAGE(tx_total_count_nof_packet_arr(I)));
+
+      print_str(c_tb_str &
+          "Rx total counts monitor(" & NATURAL'IMAGE(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
+      IF g_corrupted_en = FALSE THEN
+        IF c_bg_nof_bps_total < 10.0**9 THEN
+          ASSERT tx_total_count_nof_packet_arr(I) = exp_total_count_nof_packet_arr(I) REPORT c_tb_str &
+              "Wrong Tx total nof packets count(" & NATURAL'IMAGE(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_packet_arr(I) = exp_total_count_nof_packet_arr(I) REPORT c_tb_str &
+              "Wrong Rx total nof packets count(" & NATURAL'IMAGE(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_valid_arr(I) = rx_exp_total_count_nof_valid_arr(I) REPORT c_tb_str &
+              "Wrong Rx total nof valids count(" & NATURAL'IMAGE(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 c_tb_str &
+              "Wrong Rx total nof corrupted count(" & NATURAL'IMAGE(I) &
+              "), Rx count = " & NATURAL'IMAGE(rx_total_count_nof_corrupted_arr(I)) &
+              " /= " & NATURAL'IMAGE(rx_exp_total_count_nof_corrupted_arr(I)) &
+              " = Expected count" SEVERITY ERROR;
+        ELSE
+          -- Verify that Tx total nof packets = Rx total nof packets, also when
+          -- BG experiences siso.xon block level flow control, to stay below
+          -- 1 Gbps of the 1GbE link rate.
+          ASSERT tx_total_count_nof_packet_arr(I) = rx_total_count_nof_packet_arr(I) REPORT c_tb_str &
+              "Wrong Tx-Rx total nof packets count(" & NATURAL'IMAGE(I) &
+              "), Tx count = " & NATURAL'IMAGE(tx_total_count_nof_packet_arr(I)) &
+              " /= " & NATURAL'IMAGE(rx_total_count_nof_packet_arr(I)) &
+              " = Rx count" SEVERITY ERROR;
+         END IF;
+       ELSE
+          -- g_corrupted_en = TRUE
+          ASSERT rx_total_count_nof_corrupted_arr(I) = rx_exp_total_count_nof_corrupted_arr(I) REPORT c_tb_str &
+              "Wrong Rx total nof corrupted count(" & NATURAL'IMAGE(I) &
+              "), Rx count = " & NATURAL'IMAGE(rx_total_count_nof_corrupted_arr(I)) &
+              " /= " & NATURAL'IMAGE(rx_exp_total_count_nof_corrupted_arr(I)) &
+              " = Expected count" SEVERITY ERROR;
+       END IF;
+    END LOOP;
+
+    -------------------------------------------------------------------------
+    -- Verification: BSN monitors (yield same values in every sync interval)
+    -------------------------------------------------------------------------
+    FOR I IN g_nof_streams-1 DOWNTO 0 LOOP
+      v_offset := I * c_dp_bsn_monitor_v2_reg_adr_span;
+      -- 3 = nof_sop
+      -- 4 = nof_valid
+      -- 6 = latency
+      -- . Tx
+      proc_mem_mm_bus_rd(v_offset + 3, mm_clk, reg_bsn_monitor_v2_tx_cipo, reg_bsn_monitor_v2_tx_copi);
+      proc_mem_mm_bus_rd_latency(1, mm_clk);
+      tx_mon_nof_sop_arr(I) <= TO_UINT(reg_bsn_monitor_v2_tx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      proc_mem_mm_bus_rd(v_offset + 4, mm_clk, reg_bsn_monitor_v2_tx_cipo, reg_bsn_monitor_v2_tx_copi);
+      proc_mem_mm_bus_rd_latency(1, mm_clk);
+      tx_mon_nof_valid_arr(I) <= TO_UINT(reg_bsn_monitor_v2_tx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      proc_mem_mm_bus_rd(v_offset + 6, mm_clk, reg_bsn_monitor_v2_tx_cipo, reg_bsn_monitor_v2_tx_copi);
+      proc_mem_mm_bus_rd_latency(1, mm_clk);
+      tx_mon_latency_arr(I) <= TO_UINT(reg_bsn_monitor_v2_tx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      -- . Rx
+      proc_mem_mm_bus_rd(v_offset + 3, mm_clk, reg_bsn_monitor_v2_rx_cipo, reg_bsn_monitor_v2_rx_copi);
+      proc_mem_mm_bus_rd_latency(1, mm_clk);
+      rx_mon_nof_sop_arr(I) <= TO_UINT(reg_bsn_monitor_v2_rx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      proc_mem_mm_bus_rd(v_offset + 4, mm_clk, reg_bsn_monitor_v2_rx_cipo, reg_bsn_monitor_v2_rx_copi);
+      proc_mem_mm_bus_rd_latency(1, mm_clk);
+      rx_mon_nof_valid_arr(I) <= TO_UINT(reg_bsn_monitor_v2_rx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      proc_mem_mm_bus_rd(v_offset + 6, mm_clk, reg_bsn_monitor_v2_rx_cipo, reg_bsn_monitor_v2_rx_copi);
+      proc_mem_mm_bus_rd_latency(1, mm_clk);
+      rx_mon_latency_arr(I) <= TO_UINT(reg_bsn_monitor_v2_rx_cipo.rddata(c_word_w-1 DOWNTO 0));
+      proc_common_wait_some_cycles(mm_clk, 1);
+
+      -- Print logging
+      print_str(c_tb_str &
+          "Tx BSN monitor(" & NATURAL'IMAGE(I) & ") :" &
+          " nof_sop = " & NATURAL'IMAGE(tx_mon_nof_sop_arr(I)) &
+          ", nof_valid = " & NATURAL'IMAGE(tx_mon_nof_valid_arr(I)) &
+          ", latency = " & NATURAL'IMAGE(tx_mon_latency_arr(I)));
+
+      print_str(c_tb_str &
+          "Rx BSN monitor(" & NATURAL'IMAGE(I) & ") :" &
+          " nof_sop = " & NATURAL'IMAGE(rx_mon_nof_sop_arr(I)) &
+          ", nof_valid = " & NATURAL'IMAGE(rx_mon_nof_valid_arr(I)) &
+          ", latency = " & NATURAL'IMAGE(rx_mon_latency_arr(I)));
+
+      IF c_bg_nof_bps_total < 10.0**9 THEN
+        -- Verify BSN monitors only when the BG sync interval is stable, so
+        -- the ETH data rate < 1 Gbps and no BG block flow control.
+        -- Verify, only log when wrong
+        IF I = 0 THEN
+          ASSERT tx_mon_nof_sop_arr(I) = c_mon_nof_sop_first REPORT c_tb_str & "Wrong tx nof_sop for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+          ASSERT rx_mon_nof_sop_arr(I) = c_mon_nof_sop_first REPORT c_tb_str & "Wrong rx nof_sop for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+          ASSERT tx_mon_nof_valid_arr(I) = c_mon_nof_valid_first_tx REPORT c_tb_str & "Wrong tx nof_valid for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+          ASSERT rx_mon_nof_valid_arr(I) = c_mon_nof_valid_first_rx REPORT c_tb_str & "Wrong rx nof_valid for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+        ELSE
+          ASSERT tx_mon_nof_sop_arr(I) = c_mon_nof_sop_others REPORT c_tb_str & "Wrong tx nof_sop for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+          ASSERT rx_mon_nof_sop_arr(I) = c_mon_nof_sop_others REPORT c_tb_str & "Wrong rx nof_sop for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+          ASSERT tx_mon_nof_valid_arr(I) = c_mon_nof_valid_others_tx REPORT c_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 c_tb_str & "Wrong rx nof_valid for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+        END IF;
+        ASSERT tx_mon_latency_arr(I) = c_tx_exp_latency REPORT c_tb_str & "Wrong tx latency for stream (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+
+        -- For short block lengths the Rx latency appears to become less, the
+        -- exact Rx latency is therefore hard to predetermine. The actual
+        -- latency is not critical, therefore it is sufficient to only very
+        -- the latency when it is more or less fixed.
+        IF c_rx_exp_latency_en THEN
+          -- The rx_exp_latency is fixed when:
+          -- . g_loopback_eth = FALSE: the streams operate in parallel.
+          -- . g_loopback_eth = TRUE and g_nof_streams = 1, because for
+          --   g_nof_streams > 1 the streams are multiplexed, so then the Rx
+          --   latency will vary.
+          IF g_loopback_eth = TRUE THEN
+            IF g_nof_streams = 1 THEN
+              IF g_eth_sim_level = 0 THEN
+                ASSERT almost_equal(rx_mon_latency_arr(I), c_rx_exp_latency_tech_tse, 10) REPORT
+                    c_tb_str & "Wrong rx latency using tech_tse interface" SEVERITY ERROR;
+              ELSIF g_eth_sim_level = 1 THEN
+                ASSERT almost_equal(rx_mon_latency_arr(I), c_rx_exp_latency_sim_tse, 10) REPORT
+                    c_tb_str & "Wrong rx latency using sim_tse interface" SEVERITY ERROR;
+              END IF;
+            END IF;
+          ELSE
+            ASSERT almost_equal(rx_mon_latency_arr(I), c_rx_exp_latency_st, 0) REPORT
+                c_tb_str & "Wrong rx latency using st interface (" & NATURAL'IMAGE(I) & ")" SEVERITY ERROR;
+          END IF;
+        END IF;
+      END IF;
+    END LOOP;
+
+    -------------------------------------------------------------------------
+    -- End of test
+    -------------------------------------------------------------------------
+    proc_common_wait_some_cycles(mm_clk, 100);
+    i_tb_end <= '1';
+    WAIT;
+  END PROCESS;
+
+  dut : ENTITY work.eth_tester
+  GENERIC MAP (
+    g_nof_streams      => g_nof_streams,
+    g_remove_crc       => g_loopback_eth  -- remove CRC inserted by TSE (sim or tech)
+  )
+  PORT MAP (
+    -- Clocks and reset
+    mm_rst             => mm_rst,
+    mm_clk             => mm_clk,
+    st_rst             => st_rst,
+    st_clk             => st_clk,
+    st_pps             => st_pps,
+
+    -- UDP transmit interface
+    eth_src_mac        => c_gn_eth_src_mac,
+    ip_src_addr        => c_gn_ip_src_addr,
+    udp_src_port       => c_gn_udp_src_port,
+
+    tx_fifo_rd_emp_arr => tx_fifo_rd_emp_arr,
+
+    tx_udp_sosi_arr    => tx_udp_sosi_arr,
+    tx_udp_siso_arr    => tx_udp_siso_arr,
+
+    -- UDP receive interface
+    rx_udp_sosi_arr    => rx_udp_sosi_arr,
+
+    -- Memory Mapped Slaves (one per stream)
+    -- . Tx
+    reg_bg_ctrl_copi               => reg_bg_ctrl_copi,
+    reg_bg_ctrl_cipo               => reg_bg_ctrl_cipo,
+    reg_hdr_dat_copi               => reg_hdr_dat_copi,
+    reg_hdr_dat_cipo               => reg_hdr_dat_cipo,
+    reg_bsn_monitor_v2_tx_copi     => reg_bsn_monitor_v2_tx_copi,
+    reg_bsn_monitor_v2_tx_cipo     => reg_bsn_monitor_v2_tx_cipo,
+    reg_strobe_total_count_tx_copi => reg_strobe_total_count_tx_copi,
+    reg_strobe_total_count_tx_cipo => reg_strobe_total_count_tx_cipo,
+    -- . Rx
+    reg_bsn_monitor_v2_rx_copi     => reg_bsn_monitor_v2_rx_copi,
+    reg_bsn_monitor_v2_rx_cipo     => reg_bsn_monitor_v2_rx_cipo,
+    reg_strobe_total_count_rx_copi => reg_strobe_total_count_rx_copi,
+    reg_strobe_total_count_rx_cipo => reg_strobe_total_count_rx_cipo
+  );
+  
+  -- Wire Tx to Rx
+  gen_loopback_st : IF g_loopback_eth = FALSE GENERATE
+    -- Loop back at streaming sosi level
+
+    rx_udp_sosi_arr <= tx_udp_sosi_arr;
+    tse_init <= '0';
+  END GENERATE;
+
+  gen_loopback_eth : IF g_loopback_eth = TRUE GENERATE
+    -- Loop back at ethernet 1Gbps line level
+
+    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);
+
+    -- Copied from tb_tech_tse.vhd
+    use_sim_tse : IF g_eth_sim_level > 0 GENERATE
+      eth_rxp <= eth_txp;
+      tse_init <= '0';
+    END GENERATE;
+    use_tech_tse : IF g_eth_sim_level = 0 GENERATE
+      p_eth_link : PROCESS(eth_txp)
+      BEGIN
+        eth_rxp <= TRANSPORT eth_txp AFTER 12 ns;  -- apply cable delay
+        eth_corrupt <= '0';
+
+        -- Optionally force eth_rxp low to cause a CRC error
+        IF g_corrupted_en = TRUE THEN
+          -- Use long BG block_len to easy timing of eth_corrupt during a
+          -- payload. Not during header to avoid lost packet and not
+          -- during idle to have no effect. E.g. g_bg_ctrl_first =
+          -- ('1', '1', 1000, 1, 200, 0, c_diag_bg_mem_max_adr, 0)
+          IF (NOW > 30000 ns) AND (NOW <= 30000 ns + eth_clk_period * 2) THEN
+            eth_corrupt <= '1';
+            eth_rxp <= '0';
+          END IF;
+        END IF;
+      END PROCESS;
+
+      p_tech_tse_setup : PROCESS
+        -- When c_promis_en = FALSE then only accept broadcast and packets with
+        -- dst_mac for this src_mac, else accept packets for any dst MAC
+        -- Therefore when c_promis_en = FALSE the TSE c_src_mac must be equal
+        -- to the Tx packet dst_mac to be able to receive the Tx packets via
+        -- the tx-rx loopback.
+        CONSTANT c_promis_en : BOOLEAN := FALSE;
+        CONSTANT c_src_mac   : STD_LOGIC_VECTOR(47 DOWNTO 0) := sel_a_b(c_promis_en, c_gn_eth_src_mac, c_eth_tester_eth_dst_mac);
+      BEGIN
+        tse_init <= '1';
+        tse_copi.wr <= '0';
+        tse_copi.rd <= '0';
+
+        proc_common_wait_until_low(mm_clk, mm_rst);
+        proc_common_wait_some_cycles(mm_clk, 10);
+
+        proc_tech_tse_setup(c_tech_select_default,
+                            c_promis_en, c_tech_tse_tx_fifo_depth, c_tech_tse_rx_fifo_depth, c_tech_tse_tx_ready_latency,
+                            c_src_mac, tse_psc_access,
+                            mm_clk, tse_cipo, tse_copi);
+        tse_init <= '0';
+        WAIT;
+      END PROCESS;
+    END GENERATE;
+
+    -- ETH module, see [1]
+    u_eth : ENTITY work.eth
+    GENERIC MAP (
+      g_init_ip_address    => X"0A630000",
+      g_cross_clock_domain => TRUE,
+      g_sim                => TRUE,
+      g_sim_level          => g_eth_sim_level
+    )
+    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         => tse_copi,  -- ETH TSE MAC registers
+      tse_sla_out        => tse_cipo,
+      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_eth_tester_pkg.vhd b/libraries/io/eth/tb/vhdl/tb_eth_tester_pkg.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..e0fd29ce23adf74df9ea0ccd9ef1bad4eb36e924
--- /dev/null
+++ b/libraries/io/eth/tb/vhdl/tb_eth_tester_pkg.vhd
@@ -0,0 +1,88 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2022
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: This package contains eth_tester specific constants and functions
+--          for use in a test bench
+-- Description: See [1]
+-- References:
+-- . [1] https://support.astron.nl/confluence/display/L2M/L3+SDP+Decision%3A+SDP+Parameter+definitions
+--
+LIBRARY IEEE, common_lib;
+USE IEEE.std_logic_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE common_lib.common_field_pkg.ALL;
+USE common_lib.common_network_layers_pkg.ALL;
+USE work.eth_tester_pkg.ALL;
+
+PACKAGE tb_eth_tester_pkg is
+
+  CONSTANT c_eth_tester_eth_dst_mac       : STD_LOGIC_VECTOR(47 DOWNTO 0) := x"001B217176B9";  -- 001B217176B9 = DOP36-enp2s0
+  CONSTANT c_eth_tester_ip_dst_addr       : STD_LOGIC_VECTOR(31 DOWNTO 0) := x"0A6300FE";  -- 0A6300FE = '10.99.0.254' = DOP36-enp2s0
+  CONSTANT c_eth_tester_udp_dst_port      : STD_LOGIC_VECTOR(15 DOWNTO 0) := TO_UVEC(6001, 16);  -- 0x1771 = 6001
+
+  -- Map global node index on UniBoard2 to node MAC address and node IP address
+  FUNCTION func_eth_tester_gn_index_to_mac_15_0(gn_index : NATURAL) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_eth_tester_gn_index_to_ip_15_0(gn_index : NATURAL) RETURN STD_LOGIC_VECTOR;
+
+  -- Ethernet packet length in octets inclduing eth header and CRC
+  FUNCTION func_eth_tester_eth_packet_length(block_len : NATURAL) RETURN NATURAL;
+
+  -- Ethernet packet lenght on link including c_network_eth_preamble_len and one idle word
+  FUNCTION func_eth_tester_eth_packet_on_link_length(block_len : NATURAL) RETURN NATURAL;
+
+END tb_eth_tester_pkg;
+
+
+PACKAGE BODY tb_eth_tester_pkg IS
+
+  FUNCTION func_eth_tester_gn_index_to_mac_15_0(gn_index : NATURAL) RETURN STD_LOGIC_VECTOR IS
+    CONSTANT c_unb_nr    : NATURAL := gn_index / 4;  -- 4 PN per Uniboard2
+    CONSTANT c_node_nr   : NATURAL := gn_index MOD 4;
+    CONSTANT c_mac_15_0  : STD_LOGIC_VECTOR(15 DOWNTO 0) := TO_UVEC(c_unb_nr, 8) & TO_UVEC(c_node_nr, 8);
+  BEGIN
+    RETURN c_mac_15_0;
+  END func_eth_tester_gn_index_to_mac_15_0;
+
+  FUNCTION func_eth_tester_gn_index_to_ip_15_0(gn_index : NATURAL) RETURN STD_LOGIC_VECTOR IS
+    CONSTANT c_unb_nr    : NATURAL := gn_index / 4;  -- 4 PN per Uniboard2
+    CONSTANT c_node_nr   : NATURAL := gn_index MOD 4;
+    CONSTANT c_ip_15_0   : STD_LOGIC_VECTOR(15 DOWNTO 0) := TO_UVEC(c_unb_nr, 8) & TO_UVEC(c_node_nr+1, 8);  -- +1 to avoid IP = *.*.*.0
+  BEGIN
+    RETURN c_ip_15_0;
+  END func_eth_tester_gn_index_to_ip_15_0;
+
+  FUNCTION func_eth_tester_eth_packet_length(block_len : NATURAL) RETURN NATURAL IS
+    CONSTANT c_app_len : NATURAL := c_eth_tester_app_hdr_len + block_len;
+    CONSTANT c_udp_len : NATURAL := c_network_udp_header_len + c_app_len;
+    CONSTANT c_ip_len  : NATURAL := c_network_ip_header_len + c_udp_len;
+    CONSTANT c_eth_len : NATURAL := c_network_eth_header_len + c_ip_len + c_network_eth_crc_len;
+  BEGIN
+    RETURN c_eth_len;
+  END func_eth_tester_eth_packet_length;
+
+  FUNCTION func_eth_tester_eth_packet_on_link_length(block_len : NATURAL) RETURN NATURAL IS
+  BEGIN
+    RETURN c_network_eth_preamble_len + func_eth_tester_eth_packet_length(block_len) + c_word_sz;
+  END func_eth_tester_eth_packet_on_link_length;
+
+END tb_eth_tester_pkg;
+
diff --git a/libraries/io/eth/tb/vhdl/tb_tb_eth_tester.vhd b/libraries/io/eth/tb/vhdl/tb_tb_eth_tester.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..f4c8b3b8b12cdfc551dbd6b0a2a2e3db042afab5
--- /dev/null
+++ b/libraries/io/eth/tb/vhdl/tb_tb_eth_tester.vhd
@@ -0,0 +1,231 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2022
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-------------------------------------------------------------------------------
+-- Author: E. Kooistra
+-- Purpose: Multi test bench for eth_tester
+-- Description: See detailed design in [1]
+--
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L6+FWLIB+Design+Document%3A+ETH+tester+unit+for+1GbE
+-- [2] https://support.astron.nl/confluence/display/L2M/L4+SDPFW+Decision%3A+Application+header+size+in+Ethernet+packets
+--
+-- Usage:
+--   > as 8
+--   > run -all
+--   Takes about 25 m
+
+LIBRARY IEEE, diag_lib;
+USE IEEE.std_logic_1164.ALL;
+USE diag_lib.diag_pkg.ALL;
+USE work.tb_eth_tester_pkg.ALL;
+
+ENTITY tb_tb_eth_tester IS
+END tb_tb_eth_tester;
+
+ARCHITECTURE tb OF tb_tb_eth_tester IS
+
+  -- Multi tb
+  CONSTANT c_tb_w       : NATURAL := 100;  -- sufficiently long to fit all tb instances
+  CONSTANT c_tb_end_vec : STD_LOGIC_VECTOR(c_tb_w-1 DOWNTO 0) := (OTHERS=>'1');
+
+  SIGNAL tb_end_vec   : STD_LOGIC_VECTOR(c_tb_w-1 DOWNTO 0) := c_tb_end_vec;  -- best view as hex in Wave Window
+  SIGNAL tb_end       : STD_LOGIC := '0';
+
+  -- Tb
+  CONSTANT c_eth_clk_MHz   : NATURAL := 125;
+  CONSTANT c_st_clk_MHz    : NATURAL := 200;
+  CONSTANT c_nof_sync      : NATURAL := 2;
+  CONSTANT c_nof_sync_many : NATURAL := 50;  -- sufficient to achieve Tx FIFO fill level
+  CONSTANT c_nof_streams   : NATURAL := 3;
+  CONSTANT c_nof_blk       : NATURAL := 3;   -- nof_blk per sync
+
+  -- Tx packet size and gap size in octets
+  CONSTANT c_block_len       : NATURAL := 50;  -- BG block length of first stream [0]
+  CONSTANT c_block_len_odd   : NATURAL := 51;
+  CONSTANT c_block_len_jumbo : NATURAL := 9000;
+  CONSTANT c_link_len        : NATURAL := func_eth_tester_eth_packet_on_link_length(c_block_len);
+
+  -- For near maximum 1Gbps link rate the c_block_len + c_gap_len_min time
+  -- in the st_clk domain equals c_link_len time in eth_clk domain.
+  CONSTANT c_gap_len_min   : NATURAL := c_link_len * c_st_clk_MHz / c_eth_clk_MHz - c_block_len;
+  CONSTANT c_slot_len_min  : NATURAL := c_block_len + c_gap_len_min;
+
+  -- Choose c_gap_len somewhat larger to have packet link rate < 1 Gbps
+  CONSTANT c_gap_len       : NATURAL := c_gap_len_min * 2;   -- for g_nof_streams = 1
+  CONSTANT c_long_gap      : NATURAL := c_gap_len_min * 10;
+  CONSTANT c_short_gap     : NATURAL := 10;   -- to cause BG xon/xoff flow control
+  CONSTANT c_zero_gap      : NATURAL := 0;    -- to verify BG ready flow control
+
+  -- Choose c_others_len > c_block_len, so same c_gap_len is suitable to
+  -- keep Ethernet link rate < 1 Gbps
+  CONSTANT c_others_len    : NATURAL := 65;  -- BG block length of other streams [c_nof_streams-1 : 1]
+
+  -- BG ctrl
+  CONSTANT c_high             : NATURAL := c_diag_bg_mem_max_adr;  -- = 2**24
+
+  CONSTANT c_bg_ctrl_rst      : t_diag_block_gen_integer := ('0', '0', 1, c_nof_blk, c_gap_len, 0, c_high, 0);  -- place holder for unused stream
+
+  CONSTANT c_bg_ctrl_one      : t_diag_block_gen_integer := ('1', '1', c_block_len,  c_nof_blk, c_gap_len, 0, c_high, 0);  -- for first stream
+  CONSTANT c_bg_ctrl_others   : t_diag_block_gen_integer := ('1', '1', c_others_len, c_nof_blk, c_gap_len, 0, c_high, 0);  -- for other streams
+
+  -- . BG with different block lengths and other payload values
+  --   The payload values are only verified manually using the Wave Window
+  CONSTANT c_bg_ctrl_len_0    : t_diag_block_gen_integer := ('1', '1', c_block_len+0, c_nof_blk, c_gap_len,      0,      0, 0);  -- nof octets
+  CONSTANT c_bg_ctrl_len_1    : t_diag_block_gen_integer := ('1', '1', c_block_len+1, c_nof_blk, c_gap_len,      1,      1, 0);  -- nof octets
+  CONSTANT c_bg_ctrl_len_2    : t_diag_block_gen_integer := ('1', '1', c_block_len+2, c_nof_blk, c_gap_len,      1,      7, 0);  -- nof octets
+  CONSTANT c_bg_ctrl_len_3    : t_diag_block_gen_integer := ('1', '1', c_block_len+3, c_nof_blk, c_gap_len, c_high-1, c_high-1, 0);  -- nof octets
+
+  CONSTANT c_bg_ctrl_corrupted : t_diag_block_gen_integer := ('1', '1', 1000, 1, 200, 0, c_high, 0);
+
+  CONSTANT c_bg_ctrl_multiple_first    : t_diag_block_gen_integer := ('1', '1', c_block_len,  c_nof_blk, c_nof_streams * c_gap_len, 0, c_high, 0);  -- for first stream
+  CONSTANT c_bg_ctrl_multiple_others   : t_diag_block_gen_integer := ('1', '1', c_others_len, c_nof_blk, c_nof_streams * c_gap_len, 0, c_high, 0);  -- for other streams
+
+BEGIN
+
+--  g_tb_index         : NATURAL := 0;  -- use to incremental delay logging from tb instances in tb_tb
+--  g_nof_sync         : NATURAL := 3;  -- number of BG sync intervals to set c_run_time
+--  g_nof_streams      : NATURAL := 2;
+--  g_loopback_eth     : BOOLEAN := FALSE;  -- FALSE = sosi loopback, TRUE = eth loopback
+--  g_eth_sim_level    : NATURAL := 0;  -- when g_loopback_eth = TRUE, then 0 = use IP; 1 = use fast serdes model
+--  g_corrupted_en     : BOOLEAN := FALSE;  -- when TRUE cause a corrupted Rx packet
+--
+--  -- t_diag_block_gen_integer =
+--  --   sl:  enable
+--  --   sl:  enable_sync
+--  --   nat: samples_per_packet
+--  --   nat: blocks_per_sync
+--  --   nat: gapsize
+--  --   nat: mem_low_adrs
+--  --   nat: mem_high_adrs
+--  --   nat: bsn_init
+--  g_bg_ctrl_first    : t_diag_block_gen_integer := ('1', '1', 50, c_nof_blk, 100, 0, 30, 0);  -- for first stream
+--  g_bg_ctrl_others   : t_diag_block_gen_integer := ('1', '1', 30, c_nof_blk, 10, 0, 30, 0)   -- for other streams
+
+  -- Tb instance prefix:
+  -- . u_st   : uses streaming Tx-Rx interface
+  -- . u_sim  : uses sim_tse Tx-Rx interface
+  -- . u_tech : uses tech_tse Tx-Rx interface
+
+  -----------------------------------------------------------------------------
+  -- Single stream
+  -----------------------------------------------------------------------------
+  -- Try different loopback interfaces
+  u_st          : ENTITY work.tb_eth_tester GENERIC MAP (0, c_nof_sync, 1, FALSE, 1, FALSE, c_bg_ctrl_one, c_bg_ctrl_rst) PORT MAP (tb_end_vec(0));
+  u_sim_tse     : ENTITY work.tb_eth_tester GENERIC MAP (1, c_nof_sync, 1,  TRUE, 1, FALSE, c_bg_ctrl_one, c_bg_ctrl_rst) PORT MAP (tb_end_vec(1));
+  u_tech_tse    : ENTITY work.tb_eth_tester GENERIC MAP (2, c_nof_sync, 1,  TRUE, 0, FALSE, c_bg_ctrl_one, c_bg_ctrl_rst) PORT MAP (tb_end_vec(2));
+
+  -- Try large block size and nof blocks_per_sync = 1
+  u_st_jumbo1   : ENTITY work.tb_eth_tester GENERIC MAP (10, c_nof_sync, 1, FALSE, 1, FALSE,
+                                                         ('1', '1', c_block_len_jumbo, 1, c_zero_gap, 0, c_high, 0),
+                                                         c_bg_ctrl_rst)
+                                            PORT MAP (tb_end_vec(10));
+
+  -- Try large block sizes
+  u_st_jumbo2   : ENTITY work.tb_eth_tester GENERIC MAP (11, c_nof_sync, 1, FALSE, 1, FALSE,
+                                                         ('1', '1', c_block_len_jumbo, 2, c_zero_gap, 0, c_high, 0),
+                                                         c_bg_ctrl_rst)
+                                            PORT MAP (tb_end_vec(11));
+
+  u_sim_tse_jumbo : ENTITY work.tb_eth_tester GENERIC MAP (12, c_nof_sync, 1, TRUE, 1, FALSE,
+                                                           ('1', '1', c_block_len_jumbo, 2, c_zero_gap, 0, c_high, 0),
+                                                           c_bg_ctrl_rst)
+                                              PORT MAP (tb_end_vec(12));
+
+  u_tech_tse_jumbo : ENTITY work.tb_eth_tester GENERIC MAP (13, c_nof_sync, 1, TRUE, 0, FALSE,
+                                                           ('1', '1', c_block_len_jumbo, 2, c_zero_gap, 0, c_high, 0),
+                                                             c_bg_ctrl_rst)
+                                               PORT MAP (tb_end_vec(13));
+
+  -- Try small block sizes
+  -- . BG supports samples_per_packet >= 2, BG treats samples_per_packet = 1 as 2
+  -- . ETH MAC pads samples_per_packet <= 6 to 6, to have minimum packet length of 64 octets,
+  --   because hdr = 14 + 20 + 8 + 12 and crc = 4 have 58 octets.
+  u_st_len2 : ENTITY work.tb_eth_tester GENERIC MAP (20, c_nof_sync, 1, FALSE, 1, FALSE,
+                                                     ('1', '1', 2, c_nof_blk, c_gap_len, 0, c_high, 0),
+                                                     c_bg_ctrl_rst)
+                                        PORT MAP (tb_end_vec(20));
+  u_sim_tse_len2 : ENTITY work.tb_eth_tester GENERIC MAP (21, c_nof_sync, 1, TRUE, 1, FALSE,
+                                                          ('1', '1', 2, c_nof_blk, c_gap_len, 0, c_high, 0),
+                                                          c_bg_ctrl_rst)
+                                             PORT MAP (tb_end_vec(21));
+  u_tech_tse_len6 : ENTITY work.tb_eth_tester GENERIC MAP (22, c_nof_sync, 1, TRUE, 0, FALSE,
+                                                           ('1', '1', 6, c_nof_blk, c_gap_len, 0, c_high, 0),
+                                                           c_bg_ctrl_rst)
+                                              PORT MAP (tb_end_vec(22));
+
+  -- Try different BG block lengths to verify sosi.empty nof octets in last word
+  u_st_bg_len_0 : ENTITY work.tb_eth_tester GENERIC MAP (30, c_nof_sync, 1, FALSE, 1, FALSE, c_bg_ctrl_len_0, c_bg_ctrl_rst) PORT MAP (tb_end_vec(30));
+  u_st_bg_len_1 : ENTITY work.tb_eth_tester GENERIC MAP (31, c_nof_sync, 1, FALSE, 1, FALSE, c_bg_ctrl_len_1, c_bg_ctrl_rst) PORT MAP (tb_end_vec(31));
+  u_st_bg_len_2 : ENTITY work.tb_eth_tester GENERIC MAP (32, c_nof_sync, 1, FALSE, 1, FALSE, c_bg_ctrl_len_2, c_bg_ctrl_rst) PORT MAP (tb_end_vec(32));
+  u_st_bg_len_3 : ENTITY work.tb_eth_tester GENERIC MAP (33, c_nof_sync, 1, FALSE, 1, FALSE, c_bg_ctrl_len_3, c_bg_ctrl_rst) PORT MAP (tb_end_vec(33));
+
+  -- Try BG xon/xoff block flow control by using smaller gapsize that would
+  -- exceed 1 Gbps. Use c_nof_sync_many to fill Tx FIFO. Use tse because
+  -- tse is limited to 1 Gbps, the st interface can achieve > 1 Gbps.
+  -- Try BG ready clock flow control by using c_block_len_odd to have BG
+  -- sosi.empy /= 0 and use zero gapsize to have BG blocks directly after
+  -- each other.
+  u_sim_tse_bg_flow_control : ENTITY work.tb_eth_tester
+                              GENERIC MAP (40, c_nof_sync_many, 1, TRUE, 1, FALSE,
+                                           ('1', '1', c_block_len_odd, c_nof_blk, c_zero_gap, 0, c_high, 0),
+                                           c_bg_ctrl_rst)
+                              PORT MAP (tb_end_vec(40));
+
+  u_tech_tse_bg_flow_control : ENTITY work.tb_eth_tester
+                               GENERIC MAP (41, c_nof_sync_many, 1, TRUE, 0, FALSE,
+                                            ('1', '1', c_block_len_odd, c_nof_blk, c_zero_gap, 0, c_high, 0),
+                                            c_bg_ctrl_rst)
+                               PORT MAP (tb_end_vec(41));
+
+  -- Try corrupted packet
+  u_tech_tse_corrupted : ENTITY work.tb_eth_tester GENERIC MAP (50, c_nof_sync, 1,  TRUE, 0, TRUE, c_bg_ctrl_corrupted, c_bg_ctrl_rst) PORT MAP (tb_end_vec(50));
+
+  -----------------------------------------------------------------------------
+  -- Multiple streams
+  -----------------------------------------------------------------------------
+  u_st_multiple_streams : ENTITY work.tb_eth_tester
+                          GENERIC MAP (80, c_nof_sync, c_nof_streams, FALSE, 1, FALSE,
+                                       c_bg_ctrl_multiple_first,
+                                       c_bg_ctrl_multiple_others)
+                          PORT MAP (tb_end_vec(80));
+
+  -- Use tse to verify dp_mux and dp_demux in ETH module [1]
+  u_sim_tse_multiple_streams : ENTITY work.tb_eth_tester
+                               GENERIC MAP (81, c_nof_sync, c_nof_streams, TRUE, 1, FALSE,
+                                            c_bg_ctrl_multiple_first,
+                                            c_bg_ctrl_multiple_others)
+                               PORT MAP (tb_end_vec(81));
+
+  u_sim_tse_multiple_bg_flow_control : ENTITY work.tb_eth_tester
+                                       GENERIC MAP (82, c_nof_sync_many, c_nof_streams, TRUE, 1, FALSE,
+                                                    ('1', '1', c_block_len,  c_nof_blk, c_short_gap, 0, c_high, 0),
+                                                    ('1', '1', c_others_len, c_nof_blk, c_short_gap, 0, c_high, 0))
+                                       PORT MAP (tb_end_vec(82));
+
+  tb_end <= '1' WHEN tb_end_vec = c_tb_end_vec ELSE '0';
+
+  p_tb_end : PROCESS
+  BEGIN
+    WAIT UNTIL tb_end='1';
+    WAIT FOR 1 ns;
+    REPORT "Multi tb simulation finished." SEVERITY FAILURE;
+    WAIT;
+  END PROCESS;
+
+END tb;