diff --git a/applications/lofar2/libraries/sdp/src/vhdl/sdp_pkg.vhd b/applications/lofar2/libraries/sdp/src/vhdl/sdp_pkg.vhd
index 5512ee4aac84bb1d3d44c52ca0b82674514ed22c..0f8a3a8ef3292b6dc867f29ea8e3c4eb558b2bc3 100644
--- a/applications/lofar2/libraries/sdp/src/vhdl/sdp_pkg.vhd
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_pkg.vhd
@@ -20,7 +20,7 @@
 
 -------------------------------------------------------------------------------
 --
--- Author: R. van der Walle
+-- Author: R. van der Walle, E. Kooistra
 -- Purpose: 
 -- . This package contains sdp specific constants.
 -- Description:
@@ -161,6 +161,7 @@ PACKAGE sdp_pkg is
   -- and g_base_ip = x"0A63" in:
   --   https://git.astron.nl/desp/hdl/-/blob/master/boards/uniboard2b/libraries/unb2b_board/src/vhdl/ctrl_unb2b_board.vhd
 
+  -- Can use same offload time for all statistics, because 1GbE mux will combine them
   --CONSTANT c_sdp_offload_time : NATURAL := 13000;  -- from wave window 62855nS / 5nS = 12571 cycles.
   CONSTANT c_sdp_offload_time : NATURAL := 600000;  -- see L2SDP-452
 
@@ -169,22 +170,6 @@ PACKAGE sdp_pkg is
 
   CONSTANT c_sdp_stat_app_header_len    : NATURAL := 32;
 
-  FUNCTION func_sdp_get_stat_marker(g_statistics_type : STRING) RETURN NATURAL;
-  FUNCTION func_sdp_get_stat_nof_signal_inputs(g_statistics_type : STRING) RETURN NATURAL;
-  FUNCTION func_sdp_get_stat_from_mm_data_size(g_statistics_type : STRING) RETURN NATURAL;
-  FUNCTION func_sdp_get_stat_from_mm_step_size(g_statistics_type : STRING) RETURN NATURAL;
-  FUNCTION func_sdp_get_stat_from_mm_nof_data(g_statistics_type : STRING) RETURN NATURAL;
-  FUNCTION func_sdp_get_stat_nof_statistics_per_packet(g_statistics_type : STRING) RETURN NATURAL;
-  FUNCTION func_sdp_get_stat_app_total_length(g_statistics_type : STRING) RETURN NATURAL;
-  FUNCTION func_sdp_get_stat_udp_total_length(g_statistics_type : STRING) RETURN NATURAL;
-  FUNCTION func_sdp_get_stat_ip_total_length(g_statistics_type : STRING) RETURN NATURAL;
-  -- Note:
-  -- . For XST func_sdp_get_stat_nof_packets returns the maximum nof_packets.
-  --   The actual nof_packets for XST will depend on the MM programmable
-  --   nof_crosslets <= c_sdp_N_crosslets_max in sdp_statistics_offload.
-  FUNCTION func_sdp_get_stat_nof_packets(g_statistics_type : STRING; S_pn, P_sq, nof_crosslets : NATURAL) RETURN NATURAL;
-  FUNCTION func_sdp_get_stat_nof_packets(g_statistics_type : STRING) RETURN NATURAL;
-
   CONSTANT c_sdp_stat_eth_dst_mac       : STD_LOGIC_VECTOR(47 DOWNTO 0) := x"001B217176B9";  -- 001B217176B9 = DOP36-enp2s0
   CONSTANT c_sdp_stat_eth_src_mac_47_16 : STD_LOGIC_VECTOR(31 DOWNTO 0) := x"00228608";  -- 00:22:86:08:pp:qq
   CONSTANT c_sdp_stat_ip_dst_addr       : STD_LOGIC_VECTOR(31 DOWNTO 0) := x"0A6300FE";  -- 0A6300FE = '10.99.0.254' = DOP36-enp2s0
@@ -283,6 +268,14 @@ PACKAGE sdp_pkg is
     dp_bsn                                  : STD_LOGIC_VECTOR(63 DOWNTO 0);
   END RECORD;
 
+  TYPE t_sdp_stat_data_id IS RECORD
+    sst_signal_input_index      : NATURAL RANGE 0 TO 2**8 - 1;   -- < 192 = c_sdp_N_pn_max * c_sdp_S_pn
+    bst_beamlet_index           : NATURAL RANGE 0 TO 2**16 - 1;  -- < 976 = c_sdp_N_beamsets * c_sdp_S_sub_bf
+    xst_subband_index           : NATURAL RANGE 0 TO 2**9 - 1;   -- < 512 = c_sdp_N_sub
+    xst_signal_input_A_index    : NATURAL RANGE 0 TO 2**8 - 1;   -- < 192 = c_sdp_N_pn_max * c_sdp_S_pn
+    xst_signal_input_B_index    : NATURAL RANGE 0 TO 2**8 - 1;   -- < 192 = c_sdp_N_pn_max * c_sdp_S_pn
+  END RECORD;
+
   TYPE t_sdp_stat_header IS RECORD
     eth : t_network_eth_header;
     ip  : t_network_ip_header;
@@ -290,8 +283,6 @@ PACKAGE sdp_pkg is
     app : t_sdp_network_stat_header;
   END RECORD;
 
-  FUNCTION func_sdp_extract_stat_header(hdr_fields_raw : STD_LOGIC_VECTOR(1023 DOWNTO 0)) RETURN t_sdp_stat_header;
-
   -----------------------------------------------------------------------------
   -- Beamlet output via 10GbE to CEP (= central processor)
   -----------------------------------------------------------------------------
@@ -396,8 +387,6 @@ PACKAGE sdp_pkg is
     app : t_sdp_network_cep_header;
   END RECORD;
 
-  FUNCTION sdp_extract_cep_header(hdr_fields_raw : STD_LOGIC_VECTOR(1023 DOWNTO 0)) RETURN t_sdp_cep_header;
-
   -----------------------------------------------------------------------------
   -- MM
   -----------------------------------------------------------------------------
@@ -464,9 +453,6 @@ PACKAGE sdp_pkg is
     step       : NATURAL;
   END RECORD;
 
-  FUNCTION func_extract_crosslets_info(info_slv : STD_LOGIC_VECTOR) RETURN t_sdp_crosslets_info;
-  FUNCTION func_construct_crosslets_info(info_rec : t_sdp_crosslets_info) RETURN STD_LOGIC_VECTOR;
-
   CONSTANT c_sdp_mm_reg_nof_crosslets  : t_c_mem := (latency  => 1,
                                                      adr_w    => 1,
                                                      dat_w    => ceil_log2(c_sdp_N_crosslets_max+1),  
@@ -480,7 +466,8 @@ PACKAGE sdp_pkg is
   CONSTANT c_sdp_reg_crosslets_info_addr_w          : NATURAL := c_sdp_mm_reg_crosslets_info.adr_w;
   CONSTANT c_sdp_reg_nof_crosslets_addr_w           : NATURAL := c_sdp_mm_reg_nof_crosslets.adr_w;
   CONSTANT c_sdp_reg_bsn_sync_scheduler_xsub_addr_w : NATURAL := 4; 
-  CONSTANT c_sdp_ram_st_xsq_addr_w                  : NATURAL := ceil_log2(c_sdp_P_sq) + ceil_log2(c_sdp_N_crosslets_max * c_sdp_X_sq * c_nof_complex * c_sdp_W_statistic_sz );
+  CONSTANT c_sdp_ram_st_xsq_addr_w                  : NATURAL := ceil_log2(c_sdp_N_crosslets_max * c_sdp_X_sq * c_nof_complex * c_sdp_W_statistic_sz);
+  CONSTANT c_sdp_ram_st_xsq_arr_addr_w              : NATURAL := ceil_log2(c_sdp_P_sq) + c_sdp_ram_st_xsq_addr_w;
 
   -- RING MM address widths
   CONSTANT c_sdp_reg_bsn_monitor_v2_ring_rx_addr_w        : NATURAL := ceil_log2(c_sdp_N_ring_lanes_max) + ceil_log2(c_sdp_N_pn_max) + ceil_Log2(7); 
@@ -506,10 +493,71 @@ PACKAGE sdp_pkg is
 
   CONSTANT c_sdp_sim : t_sdp_sim := (1, 10);
 
+  -------------------------------------------------
+  -- SDP functions
+  -------------------------------------------------
+
+  FUNCTION func_sdp_gn_index_to_pn_index(gn_index : NATURAL) RETURN NATURAL;
+  FUNCTION func_sdp_modulo_N_sub(sub_index : NATURAL) RETURN NATURAL;
+
+  FUNCTION func_sdp_get_stat_marker(g_statistics_type : STRING) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_nof_signal_inputs(g_statistics_type : STRING) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_from_mm_data_size(g_statistics_type : STRING) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_from_mm_step_size(g_statistics_type : STRING) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_from_mm_nof_data(g_statistics_type : STRING) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_nof_statistics_per_packet(g_statistics_type : STRING) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_app_total_length(g_statistics_type : STRING) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_udp_total_length(g_statistics_type : STRING) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_ip_total_length(g_statistics_type : STRING) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_nof_packets(g_statistics_type : STRING; S_pn, P_sq, N_crosslets : NATURAL) RETURN NATURAL;
+  FUNCTION func_sdp_get_stat_nof_packets(g_statistics_type : STRING) RETURN NATURAL;  -- use c_sdp_S_pn, c_sdp_P_sq, c_sdp_N_crosslets_max
+
+  FUNCTION func_sdp_map_stat_header(hdr_fields_raw : STD_LOGIC_VECTOR) RETURN t_sdp_stat_header;
+  FUNCTION func_sdp_map_cep_header(hdr_fields_raw : STD_LOGIC_VECTOR) RETURN t_sdp_cep_header;
+
+  FUNCTION func_sdp_map_stat_data_id(g_statistics_type : STRING; data_id_slv : STD_LOGIC_VECTOR) RETURN t_sdp_stat_data_id;
+  FUNCTION func_sdp_map_stat_data_id(g_statistics_type : STRING; data_id_rec : t_sdp_stat_data_id) RETURN STD_LOGIC_VECTOR;
+
+  FUNCTION func_sdp_map_crosslets_info(info_slv : STD_LOGIC_VECTOR; nof_crosslets : NATURAL) RETURN t_sdp_crosslets_info;  -- map only the used offsets
+  FUNCTION func_sdp_map_crosslets_info(info_slv : STD_LOGIC_VECTOR) RETURN t_sdp_crosslets_info;                           -- map all c_sdp_N_crosslets_max offsets
+  FUNCTION func_sdp_map_crosslets_info(info_rec : t_sdp_crosslets_info; nof_crosslets : NATURAL) RETURN STD_LOGIC_VECTOR;  -- map only the used offsets
+  FUNCTION func_sdp_map_crosslets_info(info_rec : t_sdp_crosslets_info) RETURN STD_LOGIC_VECTOR;                           -- map all c_sdp_N_crosslets_max offsets
+  FUNCTION func_sdp_step_crosslets_info(info_rec : t_sdp_crosslets_info; nof_crosslets : NATURAL) RETURN t_sdp_crosslets_info;
+
 END PACKAGE sdp_pkg;
 
 PACKAGE BODY sdp_pkg IS
 
+  FUNCTION func_sdp_gn_index_to_pn_index(gn_index : NATURAL) RETURN NATURAL IS
+    -- Determine PN index that starts at 0 per antenna band.
+    -- For LOFAR2 SDP there are two antenna bands: LB and HB. The LB starts at
+    -- GN index = 0 and has c_sdp_N_pn_lb = c_sdp_N_pn_max = 16 nodes. The HB
+    -- starts at c_sdp_N_pn_max. Assume every antenna_band starts at a GN:
+    --
+    --   pn_index = gn_index MOD c_sdp_N_pn_max
+    --
+    -- The fact that c_sdp_N_pn_max = 16 implies that instead of implementing
+    -- MOD it is possible to do:
+    --
+    --   pn_index = gn_index[3:0], because log2(16) = 4
+    CONSTANT c_pn_w  : NATURAL := ceil_log2(c_sdp_N_pn_max);  -- = 4
+    -- use sufficient bits to fit both PN index and GN index in v_index
+    CONSTANT c_w     : NATURAL := ceil_log2(c_sdp_N_pn_max + gn_index);
+    CONSTANT c_index : STD_LOGIC_VECTOR(c_w-1 DOWNTO 0) := TO_UVEC(gn_index, c_w);
+  BEGIN
+    RETURN TO_UINT(c_index(c_pn_w-1 DOWNTO 0));
+  END func_sdp_gn_index_to_pn_index;
+
+  FUNCTION func_sdp_modulo_N_sub(sub_index : NATURAL) RETURN NATURAL IS
+  BEGIN
+    ASSERT sub_index < 2 * c_sdp_N_sub REPORT "func_sdp_modulo_N_sub: sub_index too large" SEVERITY FAILURE;
+    IF sub_index < c_sdp_N_sub-1 THEN
+      RETURN sub_index;
+    ELSE
+      RETURN sub_index - c_sdp_N_sub;
+    END IF;
+  END func_sdp_modulo_N_sub;
+
   FUNCTION func_sdp_get_stat_marker(g_statistics_type : STRING) RETURN NATURAL IS
     CONSTANT c_marker_sst : NATURAL := 83;  -- = 0x53 = 'S'
     CONSTANT c_marker_bst : NATURAL := 66;  -- = 0x42 = 'B'
@@ -586,10 +634,10 @@ PACKAGE BODY sdp_pkg IS
     RETURN c_sdp_udp_total_length + c_network_ip_header_len;
   END func_sdp_get_stat_ip_total_length;
 
-  FUNCTION func_sdp_get_stat_nof_packets(g_statistics_type : STRING; S_pn, P_sq, nof_crosslets : NATURAL) RETURN NATURAL IS
+  FUNCTION func_sdp_get_stat_nof_packets(g_statistics_type : STRING; S_pn, P_sq, N_crosslets : NATURAL) RETURN NATURAL IS
   BEGIN
     RETURN sel_a_b(g_statistics_type="BST", 1,
-           sel_a_b(g_statistics_type="XST", P_sq * nof_crosslets,
+           sel_a_b(g_statistics_type="XST", P_sq * N_crosslets,
                                             S_pn));  -- SST
   END func_sdp_get_stat_nof_packets;
 
@@ -599,7 +647,7 @@ PACKAGE BODY sdp_pkg IS
   END func_sdp_get_stat_nof_packets;
 
 
-  FUNCTION func_sdp_extract_stat_header(hdr_fields_raw : STD_LOGIC_VECTOR(1023 DOWNTO 0)) RETURN t_sdp_stat_header IS
+  FUNCTION func_sdp_map_stat_header(hdr_fields_raw : STD_LOGIC_VECTOR) RETURN t_sdp_stat_header IS
     VARIABLE v : t_sdp_stat_header;
   BEGIN
     -- eth header
@@ -658,10 +706,10 @@ PACKAGE BODY sdp_pkg IS
 
     v.app.dp_bsn                                  := hdr_fields_raw(field_hi(c_sdp_stat_hdr_field_arr, "dp_bsn") DOWNTO field_lo(c_sdp_stat_hdr_field_arr, "dp_bsn"));
     RETURN v;
-  END func_sdp_extract_stat_header;
+  END func_sdp_map_stat_header;
 
 
-  FUNCTION sdp_extract_cep_header(hdr_fields_raw : STD_LOGIC_VECTOR(1023 DOWNTO 0)) RETURN t_sdp_cep_header IS
+  FUNCTION func_sdp_map_cep_header(hdr_fields_raw : STD_LOGIC_VECTOR) RETURN t_sdp_cep_header IS
     VARIABLE v : t_sdp_cep_header;
   BEGIN
     -- eth header
@@ -713,27 +761,78 @@ PACKAGE BODY sdp_pkg IS
 
     v.app.dp_bsn                             := hdr_fields_raw(field_hi(c_sdp_cep_hdr_field_arr, "dp_bsn") DOWNTO field_lo(c_sdp_cep_hdr_field_arr, "dp_bsn"));
     RETURN v;
-  END sdp_extract_cep_header;
+  END func_sdp_map_cep_header;
 
 
-  FUNCTION func_extract_crosslets_info(info_slv : STD_LOGIC_VECTOR) RETURN t_sdp_crosslets_info IS
+  FUNCTION func_sdp_map_stat_data_id(g_statistics_type : STRING; data_id_slv : STD_LOGIC_VECTOR) RETURN t_sdp_stat_data_id IS
+    VARIABLE v_rec : t_sdp_stat_data_id;
+  BEGIN
+    IF g_statistics_type = "SST" THEN
+      v_rec.sst_signal_input_index := TO_UINT(data_id_slv(7 DOWNTO 0));
+    ELSIF g_statistics_type = "BST" THEN
+      v_rec.bst_beamlet_index := TO_UINT(data_id_slv(15 DOWNTO 0));
+    ELSIF g_statistics_type = "XST" THEN
+      v_rec.xst_subband_index := TO_UINT(data_id_slv(24 DOWNTO 16));
+      v_rec.xst_signal_input_A_index := TO_UINT(data_id_slv(15 DOWNTO 8));
+      v_rec.xst_signal_input_B_index := TO_UINT(data_id_slv(7 DOWNTO 0));
+    END IF;
+    RETURN v_rec;
+  END func_sdp_map_stat_data_id;
+
+  FUNCTION func_sdp_map_stat_data_id(g_statistics_type : STRING; data_id_rec : t_sdp_stat_data_id) RETURN STD_LOGIC_VECTOR IS
+    VARIABLE v_slv : STD_LOGIC_VECTOR(31 DOWNTO 0) := x"00000000";
+  BEGIN
+    IF g_statistics_type = "SST" THEN
+      v_slv(7 DOWNTO 0) := TO_UVEC(data_id_rec.sst_signal_input_index, 8);
+    ELSIF g_statistics_type = "BST" THEN
+      v_slv(15 DOWNTO 0) := TO_UVEC(data_id_rec.bst_beamlet_index, 16);
+    ELSIF g_statistics_type = "XST" THEN
+      v_slv(24 DOWNTO 16) := TO_UVEC(data_id_rec.xst_subband_index, 9);
+      v_slv(15 DOWNTO 8) := TO_UVEC(data_id_rec.xst_signal_input_A_index, 8);
+      v_slv(7 DOWNTO 0) := TO_UVEC(data_id_rec.xst_signal_input_B_index, 8);
+    END IF;
+    RETURN v_slv;
+  END func_sdp_map_stat_data_id;
+
+
+  FUNCTION func_sdp_map_crosslets_info(info_slv : STD_LOGIC_VECTOR; nof_crosslets : NATURAL) RETURN t_sdp_crosslets_info IS
     VARIABLE v_info : t_sdp_crosslets_info;
   BEGIN
-    FOR I IN 0 TO c_sdp_crosslets_info_nof_offsets-1 LOOP
+    FOR I IN 0 TO nof_crosslets-1 LOOP  -- map only used offsets
       v_info.offset_arr(I) := TO_UINT(info_slv((I+1)*c_sdp_crosslets_index_w-1 DOWNTO I*c_sdp_crosslets_index_w));
     END LOOP;
     v_info.step := TO_UINT(info_slv(c_sdp_crosslets_info_reg_w-1 DOWNTO c_sdp_crosslets_info_reg_w - c_sdp_crosslets_index_w));
     RETURN v_info;
-  END func_extract_crosslets_info;
+  END func_sdp_map_crosslets_info;
+
+  FUNCTION func_sdp_map_crosslets_info(info_slv : STD_LOGIC_VECTOR) RETURN t_sdp_crosslets_info IS
+  BEGIN
+    RETURN func_sdp_map_crosslets_info(info_slv, c_sdp_crosslets_info_nof_offsets);  -- map all offsets
+  END func_sdp_map_crosslets_info;
 
-  FUNCTION func_construct_crosslets_info(info_rec : t_sdp_crosslets_info) RETURN STD_LOGIC_VECTOR IS
+  FUNCTION func_sdp_map_crosslets_info(info_rec : t_sdp_crosslets_info; nof_crosslets : NATURAL) RETURN STD_LOGIC_VECTOR IS
     VARIABLE v_info : STD_LOGIC_VECTOR(c_sdp_crosslets_info_reg_w-1 DOWNTO 0);
   BEGIN
-    FOR I IN 0 TO c_sdp_crosslets_info_nof_offsets-1 LOOP
+    FOR I IN 0 TO nof_crosslets-1 LOOP  -- map only used offsets
       v_info((I+1)*c_sdp_crosslets_index_w-1 DOWNTO I*c_sdp_crosslets_index_w) := TO_UVEC(info_rec.offset_arr(I), c_sdp_crosslets_index_w);
     END LOOP;
     v_info(c_sdp_crosslets_info_reg_w-1 DOWNTO c_sdp_crosslets_info_reg_w - c_sdp_crosslets_index_w) := TO_UVEC(info_rec.step, c_sdp_crosslets_index_w);
     RETURN v_info;
-  END func_construct_crosslets_info;
+  END func_sdp_map_crosslets_info;
+
+  FUNCTION func_sdp_map_crosslets_info(info_rec : t_sdp_crosslets_info) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_sdp_map_crosslets_info(info_rec, c_sdp_crosslets_info_nof_offsets);  -- map all offsets
+  END func_sdp_map_crosslets_info;
+
+
+  FUNCTION func_sdp_step_crosslets_info(info_rec : t_sdp_crosslets_info; nof_crosslets : NATURAL) RETURN t_sdp_crosslets_info IS
+    VARIABLE v_info : t_sdp_crosslets_info := info_rec;
+  BEGIN
+    FOR I IN 0 TO nof_crosslets-1 LOOP  -- step only the used offsets
+      v_info.offset_arr(I) := v_info.offset_arr(I) + v_info.step;
+    END LOOP;
+    RETURN v_info;
+  END func_sdp_step_crosslets_info;
 
 END sdp_pkg;