diff --git a/applications/lofar2/libraries/sdp/src/vhdl/sdp_statistics_offload.vhd b/applications/lofar2/libraries/sdp/src/vhdl/sdp_statistics_offload.vhd
index d1d166a6652a87dea6e7ecaf8980432e3f11685f..e3852bd36718fe5e2157207eedfae96682eaaf00 100644
--- a/applications/lofar2/libraries/sdp/src/vhdl/sdp_statistics_offload.vhd
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_statistics_offload.vhd
@@ -20,7 +20,7 @@
 
 -------------------------------------------------------------------------------
 --
--- Author: P. Donker, R van der Walle
+-- Author: P. Donker, R van der Walle, E. Kooistra
 
 -- Purpose:
 -- . SDP statistics offload
@@ -32,21 +32,23 @@
 --
 -------------------------------------------------------------------------------
 
-LIBRARY IEEE, common_lib, mm_lib, dp_lib;
+LIBRARY IEEE, common_lib, mm_lib, dp_lib, ring_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 ring_lib.ring_pkg.ALL;
 USE work.sdp_pkg.ALL;
 
 ENTITY sdp_statistics_offload IS
   GENERIC (
-    g_statistics_type : STRING  := "SST";
-    g_offload_time    : NATURAL := c_sdp_offload_time;
-    g_beamset_id      : NATURAL := 0;
-    g_P_sq            : NATURAL := c_sdp_P_sq -- use generic to support P_sq = 1 for one node and P_sq = c_sdp_P_sq for multiple nodes (with ring)
+    g_statistics_type     : STRING  := "SST";
+    g_offload_time        : NATURAL := c_sdp_offload_time;
+    g_beamset_id          : NATURAL := 0;
+    g_P_sq                : NATURAL := c_sdp_P_sq;  -- use generic to support P_sq = 1 for one node and P_sq = c_sdp_P_sq for multiple nodes (with ring)
+    g_crosslets_direction : NATURAL := 1            -- > 0 for crosslet transport in positive direction (incrementing RN), else 0 for negative direction
   );
   PORT (
     -- Clocks and reset
@@ -80,7 +82,6 @@ ENTITY sdp_statistics_offload IS
     ip_src_addr             : IN STD_LOGIC_VECTOR(c_network_ip_addr_w-1 DOWNTO 0);
 
     gn_index                : IN NATURAL;
-
     sdp_info                : IN t_sdp_info;
     subband_calibrated_flag : IN STD_LOGIC := '0';
     nof_crosslets           : IN STD_LOGIC_VECTOR(c_sdp_nof_crosslets_reg_w-1 DOWNTO 0) := (OTHERS => '0');
@@ -103,65 +104,64 @@ ARCHITECTURE str OF sdp_statistics_offload IS
 
   CONSTANT c_beamlet_id                : NATURAL := g_beamset_id * c_sdp_S_sub_bf;
 
-  -- payload data
-  CONSTANT c_data_size                 : NATURAL := c_sdp_W_statistic_sz;  -- = 2
-  -- Note:
-  -- . c_nof_data_per_step = 2 for all g_statistics_type, but for different
-  --   reasons, because c_sdp_N_pol_bf = c_nof_complex = c_sdp_Q_fft = 2
-  CONSTANT c_nof_data_per_step         : NATURAL := sel_a_b(g_statistics_type="BST", c_sdp_N_pol_bf,
-                                                    sel_a_b(g_statistics_type="XST", c_nof_complex,
-                                                                                     c_sdp_Q_fft));  -- SST
-  CONSTANT c_step_size                 : NATURAL := sel_a_b(g_statistics_type="BST",  c_data_size,
-                                                    sel_a_b(g_statistics_type="XST",  c_data_size,
-                                                                                      c_data_size * c_nof_data_per_step));  -- SST
-  CONSTANT c_nof_data                  : NATURAL := c_nof_statistics_per_packet;
-  CONSTANT c_block_size                : NATURAL := c_nof_data * c_step_size;
+  -- MM access settings per packet for u_dp_block_from_mm_dc
+  CONSTANT c_mm_data_size              : NATURAL := func_sdp_get_stat_from_mm_data_size(g_statistics_type);
+  CONSTANT c_mm_step_size              : NATURAL := func_sdp_get_stat_from_mm_step_size(g_statistics_type);
+  CONSTANT c_mm_nof_data               : NATURAL := func_sdp_get_stat_from_mm_nof_data(g_statistics_type);
 
   -- offload control
   TYPE t_reg IS RECORD
-    block_count          : NATURAL;
+    packet_count         : NATURAL;
     start_address        : NATURAL;
     start_pulse          : STD_LOGIC;
     dp_header_info       : STD_LOGIC_VECTOR(1023 DOWNTO 0);
-    data_id              : STD_LOGIC_VECTOR(31 DOWNTO 0);
-    nof_cycles_dly       : NATURAL;
     payload_err          : STD_LOGIC;
-    interval_cnt         : NATURAL;
-    integration_interval : NATURAL; 
-    crosslet_count       : NATURAL; 
-    nof_crosslets        : NATURAL; 
+    in_sop_cnt           : NATURAL;
+    integration_interval : NATURAL;
+    interleave_count     : NATURAL RANGE 0 TO c_sdp_Q_fft;
+    crosslet_count       : NATURAL RANGE 0 TO c_sdp_N_crosslets_max;
+    instance_count       : NATURAL RANGE 0 TO c_sdp_P_sq;
+    instance_address     : NATURAL;
+    nof_crosslets        : NATURAL RANGE 0 TO c_sdp_N_crosslets_max;
+    crosslets_info_rec   : t_sdp_crosslets_info;
   END RECORD;
 
-  CONSTANT c_reg_rst : t_reg := (0, 0, '0', (OTHERS => '0'), (OTHERS => '0'), 0, '0', 0, 0, 0, 0);
-
-  TYPE t_selected_crosslet_arr IS ARRAY (INTEGER RANGE <>) OF STD_LOGIC_VECTOR(c_sdp_crosslets_index_w-1 DOWNTO 0);
+  CONSTANT c_crosslets_info_rst : t_sdp_crosslets_info := (offset_arr => (OTHERS => 0), step => 0);
+  CONSTANT c_reg_rst            : t_reg := (0, 0, '0', (OTHERS => '0'), '0', 0, 0, 0, 0, 0, 0, 0, c_crosslets_info_rst);
 
   SIGNAL r     : t_reg;
   SIGNAL nxt_r : t_reg;
 
-  SIGNAL gn_index_reg             : NATURAL;
-  SIGNAL rn_index_reg             : NATURAL;
-
-  SIGNAL trigger                  : STD_LOGIC := '0';
-  SIGNAL done                     : STD_LOGIC := '0';
+  SIGNAL gn_index_reg             : NATURAL;  -- index of this global node
+  SIGNAL pn_index                 : NATURAL;  -- index of this node in antenna band
+  SIGNAL rn_index                 : NATURAL;  -- index of this ring node
+  SIGNAL local_si_offset          : NATURAL;  -- index of first signal input on this node
+  SIGNAL remote_rn                : NATURAL;  -- index of remote ring node
+  SIGNAL remote_gn                : NATURAL;  -- index of remote global node
+  SIGNAL remote_pn                : NATURAL;  -- index of remote node in antenna band
+  SIGNAL remote_si_offset         : NATURAL;  -- index of first signal input on remote node
+  SIGNAL nof_cycles_dly           : NATURAL;  -- trigger_offload delay for this node
+  SIGNAL nof_packets              : NATURAL;  -- nof packets per integration interval
+
+  SIGNAL data_id_rec              : t_sdp_stat_data_id;
+  SIGNAL data_id_slv              : STD_LOGIC_VECTOR(31 DOWNTO 0) := (OTHERS => '0');
+
+  SIGNAL trigger_en               : STD_LOGIC := '0';
+  SIGNAL trigger_offload          : STD_LOGIC := '0';
+  SIGNAL mm_done                  : STD_LOGIC := '0';
   SIGNAL dp_block_from_mm_src_out : t_dp_sosi;
   SIGNAL dp_block_from_mm_src_in  : t_dp_siso;
   
   SIGNAL dp_offload_snk_in        : t_dp_sosi;
   SIGNAL dp_offload_snk_out       : t_dp_siso;
 
-  SIGNAL dp_header_info           : STD_LOGIC_VECTOR(1023 DOWNTO 0):= (OTHERS => '0');
   SIGNAL bsn_at_sync              : STD_LOGIC_VECTOR(63 DOWNTO 0) := (OTHERS => '0');
+  SIGNAL dp_header_info           : STD_LOGIC_VECTOR(1023 DOWNTO 0):= (OTHERS => '0');
 
-  SIGNAL selected_crosslet_arr    : t_selected_crosslet_arr(c_sdp_N_crosslets_max-1 DOWNTO 0);
-  
 BEGIN
 
   bsn_at_sync <= RESIZE_UVEC(in_sosi.bsn, 64) WHEN rising_edge(dp_clk) AND in_sosi.sync = '1';
-  gen_sel_crosslets : FOR I IN 0 TO c_sdp_N_crosslets_max-1 GENERATE
-    selected_crosslet_arr(I) <= crosslets_info((I+1)*c_sdp_crosslets_index_w-1 DOWNTO I*c_sdp_crosslets_index_w);
-  END GENERATE;
-    
+
   -------------------------------------------------------------------------------
   -- Assemble offload header info, for data path fields that are selected by:
   --   c_sdp_stat_hdr_field_sel = "1"&"101"&"111011111001"&"0100"&"0100"&"000000010"&"1000000"&"0"
@@ -207,7 +207,7 @@ BEGIN
   dp_header_info(field_hi(c_sdp_stat_hdr_field_arr, "sdp_source_info_subband_calibrated_flag" ) DOWNTO field_lo(c_sdp_stat_hdr_field_arr,  "sdp_source_info_subband_calibrated_flag" )) <= SLV(subband_calibrated_flag);
   dp_header_info(field_hi(c_sdp_stat_hdr_field_arr, "sdp_source_info_gn_id"                   ) DOWNTO field_lo(c_sdp_stat_hdr_field_arr,  "sdp_source_info_gn_id"                   )) <= TO_UVEC(gn_index, 5);
   dp_header_info(field_hi(c_sdp_stat_hdr_field_arr, "sdp_integration_interval"                ) DOWNTO field_lo(c_sdp_stat_hdr_field_arr,  "sdp_integration_interval"                )) <= TO_UVEC(r.integration_interval, 24);
-  dp_header_info(field_hi(c_sdp_stat_hdr_field_arr, "sdp_data_id"                             ) DOWNTO field_lo(c_sdp_stat_hdr_field_arr,  "sdp_data_id"                             )) <= r.data_id;
+  dp_header_info(field_hi(c_sdp_stat_hdr_field_arr, "sdp_data_id"                             ) DOWNTO field_lo(c_sdp_stat_hdr_field_arr,  "sdp_data_id"                             )) <= data_id_slv;
   dp_header_info(field_hi(c_sdp_stat_hdr_field_arr, "sdp_nof_signal_inputs"                   ) DOWNTO field_lo(c_sdp_stat_hdr_field_arr,  "sdp_nof_signal_inputs"                   )) <= TO_UVEC(c_nof_signal_inputs, 8);
   dp_header_info(field_hi(c_sdp_stat_hdr_field_arr, "sdp_nof_bytes_per_statistic"             ) DOWNTO field_lo(c_sdp_stat_hdr_field_arr,  "sdp_nof_bytes_per_statistic"             )) <= TO_UVEC(c_sdp_nof_bytes_per_statistic, 8);
   dp_header_info(field_hi(c_sdp_stat_hdr_field_arr, "sdp_nof_statistics_per_packet"           ) DOWNTO field_lo(c_sdp_stat_hdr_field_arr,  "sdp_nof_statistics_per_packet"           )) <= TO_UVEC(c_nof_statistics_per_packet, 16);
@@ -223,81 +223,127 @@ BEGIN
     END IF;
   END PROCESS;
 
-  gn_index_reg <= gn_index                          WHEN rising_edge(dp_clk);
-  rn_index_reg <= gn_index - TO_UINT(sdp_info.O_rn) WHEN rising_edge(dp_clk);
+  -- Derive and pipeline dynamic parameters
+  gn_index_reg <= gn_index WHEN rising_edge(dp_clk);
+  pn_index <= func_sdp_gn_index_to_pn_index(gn_index) WHEN rising_edge(dp_clk);
+  rn_index <= gn_index - TO_UINT(sdp_info.O_rn) WHEN rising_edge(dp_clk);
+  local_si_offset <= pn_index * c_sdp_S_pn WHEN rising_edge(dp_clk);
+  nof_cycles_dly <= gn_index * g_offload_time WHEN rising_edge(dp_clk);
+  nof_packets <= func_sdp_get_stat_nof_packets(g_statistics_type, c_sdp_S_pn, g_P_sq, r.nof_crosslets) WHEN rising_edge(dp_clk);
+
+  remote_rn <= func_nof_hops_to_source_rn(r.instance_count, rn_index, TO_UINT(sdp_info.N_rn), g_crosslets_direction);
+  remote_gn <= TO_UINT(sdp_info.O_rn) + remote_rn;
+  remote_pn <= func_sdp_gn_index_to_pn_index(remote_gn) WHEN rising_edge(dp_clk);
+  remote_si_offset <= remote_pn * c_sdp_S_pn WHEN rising_edge(dp_clk);
+
+  -- Assign application header data_id for different statistic types, use
+  -- GENERATE to keep unused fields at 0.
+  gen_data_id_sst : IF g_statistics_type = "SST" GENERATE
+    data_id_rec.sst_signal_input_index <= r.packet_count + local_si_offset;
+  END GENERATE;
+  gen_data_id_bst : IF g_statistics_type = "BST" GENERATE
+    data_id_rec.bst_beamlet_index <= c_beamlet_id;
+  END GENERATE;
+  gen_data_id_xst : IF g_statistics_type = "XST" GENERATE
+    data_id_rec.xst_subband_index <= func_sdp_modulo_N_sub(r.crosslets_info_rec.offset_arr(r.crosslet_count));
+    data_id_rec.xst_signal_input_A_index <= local_si_offset;
+    data_id_rec.xst_signal_input_B_index <= remote_si_offset;
+  END GENERATE;
+
+  data_id_slv <= func_sdp_map_stat_data_id(g_statistics_type, data_id_rec);
 
-  p_control_packet_offload : PROCESS(r, gn_index_reg, in_sosi, trigger, done, dp_header_info, selected_crosslet_arr, nof_crosslets)
-    VARIABLE v: t_reg;
+  p_control_packet_offload : PROCESS(r, in_sosi, local_si_offset, trigger_offload, nof_crosslets, crosslets_info, nof_packets, mm_done, dp_header_info)
+    VARIABLE v       : t_reg;
+    VARIABLE v_index : NATURAL;
   BEGIN
     v := r;
-    v.start_pulse    := '0';
-    v.nof_cycles_dly := gn_index_reg * g_offload_time;
+    v.start_pulse := '0';
     
-    -- Count number of sop's in a sync interval and get payload errors and keep them till next sync.
+    -- Count number of sop in a sync interval and get payload errors and keep them till next sync.
     IF in_sosi.sync = '1' THEN
-      v.integration_interval := r.interval_cnt + 1;  -- count = index + 1
-      v.interval_cnt := 0;
-      v.payload_err  := '0';
+      v.integration_interval := r.in_sop_cnt + 1;  -- count = index + 1
+      v.in_sop_cnt := 0;
+      v.payload_err := '0';
     ELSE
       IF in_sosi.eop = '1' THEN
         v.payload_err := r.payload_err OR in_sosi.err(0);
       END IF;
 
       IF in_sosi.sop = '1' THEN
-        v.interval_cnt := r.interval_cnt + 1;
+        v.in_sop_cnt := r.in_sop_cnt + 1;
       END IF;
     END IF;
 
-    -- assign sdp_data_id for different statistic types
-    v.data_id := x"00000000";
-    IF g_statistics_type = "SST" THEN
-      v.data_id(7 DOWNTO 0) := TO_UVEC(r.block_count + c_sdp_S_pn * gn_index_reg, 8);
-    ELSIF g_statistics_type = "BST" THEN
-      v.data_id(15 DOWNTO 0) := TO_UVEC(c_beamlet_id, 16);
-    ELSIF g_statistics_type = "XST" THEN
-      v.data_id(24 DOWNTO 16) := RESIZE_UVEC(selected_crosslet_arr(r.crosslet_count), 9);
-      v.data_id(15 DOWNTO 8) := TO_UVEC(r.block_count * c_sdp_S_pn, 8);
-      v.data_id(7 DOWNTO 0) := TO_UVEC(r.block_count * c_sdp_S_pn, 8); -- RW TODO: define for P_sq > 1
+    -- Capture nof_crosslets and crosslets_info at in_sosi.sync, to make sure
+    -- they do not change during packets offload. The trigger_offload occurs
+    -- after the nof_cycles_dly and the offload will have finished before the
+    -- next in_sosi.sync
+    IF in_sosi.sync = '1' THEN
+      v.nof_crosslets      := TO_UINT(nof_crosslets);
+      v.crosslets_info_rec := func_sdp_map_crosslets_info(crosslets_info, TO_UINT(nof_crosslets));
     END IF;
 
     -- Issue start_pulse per packet offload
-    IF trigger = '1' THEN
-      -- Use trigger to start first packet
-      v.start_pulse    := '1';
-      v.start_address  := 0;
-      v.block_count    := 0;
-      v.crosslet_count := 0;
-      v.nof_crosslets  := TO_UINT(nof_crosslets); -- register nof_crosslets to make sure it does not change during packet output.
-    ELSIF done = '1' THEN
-      -- Use done to start next packets
-      IF r.block_count < c_nof_packets_max-1  THEN
-        IF g_statistics_type /= "XST" OR r.crosslet_count < r.nof_crosslets-1 THEN
-          -- For SST, BST and for XST nof_crosslets do:
-          IF r.block_count MOD c_nof_data_per_step = 0 THEN
-            v.start_address := r.block_count / c_nof_data_per_step * c_block_size;  -- jump to first packet in next block
-          ELSE 
-            v.start_address := r.start_address + c_data_size;  -- step to next packet within block
+    IF trigger_offload = '1' THEN
+      -- Use trigger_offload to start first packet offload, all g_statistics_type start from start address 0
+      v.start_pulse      := '1';
+      v.start_address    := 0;
+      v.packet_count     := 0;
+      v.interleave_count := 0;  -- only used for SST
+      v.crosslet_count   := 0;  -- only used for XST
+      v.instance_count   := 0;  -- only used for XST
+      v.instance_address := 0;  -- only used for XST
+
+    ELSIF mm_done = '1' THEN
+      -- Use mm_done to start next packets offloads.
+      IF r.packet_count < nof_packets - 1 THEN
+        IF g_statistics_type = "SST" THEN
+          --                 step        step        step        step        step        step
+          -- start_address :    0,    2, 2048, 2050, 4096, 4098, 6144, 6146, 8192, 8194, 10240, 10242
+          v.start_address := r.start_address + c_mm_data_size;  -- default step to next packet in this step
+          v.interleave_count := r.interleave_count + 1;
+          IF r.interleave_count = c_sdp_Q_fft - 1 THEN
+            v.start_address := r.packet_count * c_sdp_N_sub * c_sdp_Q_fft * c_sdp_W_statistic_sz;  -- jump to first packet for next step
+            v.interleave_count := 0;
           END IF;
-          v.start_pulse    := '1';
-          v.block_count    := r.block_count + 1;
+          v.start_pulse := '1';
+          v.packet_count := r.packet_count + 1;
+
+        ELSIF g_statistics_type = "BST" THEN
+          NULL; -- there is only one BST packet, so no more packets to offload here.
+
+        ELSIF g_statistics_type = "XST" THEN
+          -- start_address:
+          --   nof_crosslets:     0,     1,     2,     3,     4,     5,     6
+          --   X_sq instance:
+          --           0          0,   576,  1152,  1728,  2304,  2880,  3456
+          --           1       4096,  4672,  5248,  5824,  6400,  6976,  7552
+          --           2       8192,  8768,  9344,  9920, 10496, 11072, 11648
+          --           3      12288, 12864, 13440, 14016, 14592, 15168, 15744
+          --           4      16384, 16960, 17536, 18112, 18688, 19264, 19840
+          --           5      20480, 21056, 21632, 22208, 22784, 23360, 23936
+          --           6      24576, 25152, 25728, 26304, 26880, 27456, 28032
+          --           7      28672, 29248, 29824, 30400, 30976, 31552, 32128
+          --           8      32768, 33344, 33920, 34496, 35072, 35648, 36224
+          v.start_address := r.start_address + c_sdp_X_sq * c_nof_complex * c_sdp_W_statistic_sz;  -- continue with next packet in this instance
           v.crosslet_count := r.crosslet_count + 1;
+          IF r.crosslet_count = TO_UINT(nof_crosslets) - 1 THEN
+            v.start_address := r.instance_address + 2**c_sdp_ram_st_xsq_addr_w;  -- jump to first packet in next instance
+            v.crosslet_count := 0;
+            v.instance_count := r.instance_count + 1;
+            v.instance_address := v.start_address;  -- use v.start_address to avoid multipier needed in (r.instance_count + 1) * 2**c_sdp_ram_st_xsq_addr_w
+          END IF;
+          v.start_pulse := '1';
+          v.packet_count := r.packet_count + 1;
+
         ELSE
-          -- For XST after nof_crosslets do:
-          v.crosslet_count := 0;
-          -- skip block indices for unused XST blocks in this P_sq iteration by setting the block count to the next multiple of N_crosslets_max i.e. 7, 14, 21, etc.
-          v.block_count    := r.block_count + 1 + (c_sdp_N_crosslets_max - r.nof_crosslets); 
+          NULL;  -- do nothing in case of unknown g_statistics_type
         END IF;
-        
-      ELSE
-        -- Prepare for next trigger interval.
-        v.start_address := 0;
-        v.block_count   := 0;
-        v.crosslet_count := 0;
       END IF;
     END IF;
 
-    IF trigger = '1' OR done = '1' THEN
-      -- Release header info per packet offload
+    -- Release dp_header_info per packet offload
+    IF trigger_offload = '1' OR mm_done = '1' THEN
       v.dp_header_info := dp_header_info;
     END IF;
     nxt_r <= v;
@@ -314,16 +360,17 @@ BEGIN
     reg_enable_mosi => reg_enable_mosi,
     reg_enable_miso => reg_enable_miso,
 
-    delay           => r.nof_cycles_dly,
+    delay           => nof_cycles_dly,
     trigger         => in_sosi.sync,
-    trigger_dly     => trigger
+    trigger_en      => trigger_en,
+    trigger_dly     => trigger_offload
   );
   
   u_dp_block_from_mm_dc : ENTITY dp_lib.dp_block_from_mm_dc
   GENERIC MAP (
-    g_data_size          => c_data_size,
-    g_step_size          => c_step_size,
-    g_nof_data           => c_nof_data,
+    g_data_size          => c_mm_data_size,
+    g_step_size          => c_mm_step_size,
+    g_nof_data           => c_mm_nof_data,
     g_reverse_word_order => TRUE -- default word order is MSB after LSB, we need to stream LSB after MSB.
   ) 
   PORT MAP(
@@ -333,7 +380,7 @@ BEGIN
     mm_clk        => mm_clk,
     start_pulse   => r.start_pulse,
     start_address => r.start_address,
-    done          => done,
+    done          => mm_done,
     mm_mosi       => master_mosi,
     mm_miso       => master_miso,
     out_sosi      => dp_block_from_mm_src_out,