diff --git a/applications/lofar2/designs/lofar2_unb2b_sdp_station/revisions/lofar2_unb2b_sdp_station_bf/tb_lofar2_unb2b_sdp_station_bf.vhd b/applications/lofar2/designs/lofar2_unb2b_sdp_station/revisions/lofar2_unb2b_sdp_station_bf/tb_lofar2_unb2b_sdp_station_bf.vhd
index b941058df291306216f661588e8990cc4970ff50..c8614845fb78c196b93ba64c9d0654c6e5567946 100644
--- a/applications/lofar2/designs/lofar2_unb2b_sdp_station/revisions/lofar2_unb2b_sdp_station_bf/tb_lofar2_unb2b_sdp_station_bf.vhd
+++ b/applications/lofar2/designs/lofar2_unb2b_sdp_station/revisions/lofar2_unb2b_sdp_station_bf/tb_lofar2_unb2b_sdp_station_bf.vhd
@@ -140,6 +140,7 @@ architecture tb of tb_lofar2_unb2b_sdp_station_bf is
   constant c_exp_ip_header_checksum : natural := 16#5BDE#;  -- value obtained from rx_sdp_cep_header.ip.header_checksum in wave window
 
   constant c_exp_beamlet_scale   : natural := natural(g_beamlet_scale * real(c_sdp_unit_beamlet_scale));  -- c_sdp_unit_beamlet_scale = 2**15;
+  constant c_exp_beamlet_index   : natural := 0;  -- depends on beamset bset * c_sdp_S_sub_bf
 
   constant c_exp_sdp_info        : t_sdp_info := (
                                      TO_UVEC(3, 6),  -- antenna_field_index
@@ -182,7 +183,7 @@ architecture tb of tb_lofar2_unb2b_sdp_station_bf is
 
   -- BF
   -- . select
-  constant c_exp_beamlet_index        : natural := g_beamlet * c_sdp_N_pol_bf;  -- in beamset 0
+  constant c_exp_g_beamlet_index        : natural := g_beamlet * c_sdp_N_pol_bf;  -- in beamset 0
   -- . Beamlet weights for selected g_sp
   constant c_bf_weight_re             : integer := integer(g_bf_gain * real(c_sdp_unit_bf_weight) * COS(g_bf_phase * MATH_2_PI / 360.0));
   constant c_bf_weight_im             : integer := integer(g_bf_gain * real(c_sdp_unit_bf_weight) * SIN(g_bf_phase * MATH_2_PI / 360.0));
@@ -293,14 +294,6 @@ architecture tb of tb_lofar2_unb2b_sdp_station_bf is
   signal pol_beamlet_bst_Y_arr : t_real_arr(0 to c_sdp_N_beamsets - 1) := (others => 0.0);  -- [bset]
 
   -- 10GbE
-  signal rx_beamlet_arr_re       : t_slv_8_arr(c_sdp_cep_nof_blocks_per_packet - 1 downto 0);  -- [3:0]
-  signal rx_beamlet_arr_im       : t_slv_8_arr(c_sdp_cep_nof_blocks_per_packet - 1 downto 0);  -- [3:0]
-  signal rx_beamlet_cnt          : natural;
-  signal rx_beamlet_valid        : std_logic;
-
-  signal rx_beamlet_list_re      : t_slv_8_arr(c_sdp_cep_nof_beamlets_per_block * c_sdp_N_pol_bf - 1 downto 0);  -- [488 * 2-1:0] = [975:0]
-  signal rx_beamlet_list_im      : t_slv_8_arr(c_sdp_cep_nof_beamlets_per_block * c_sdp_N_pol_bf - 1 downto 0);  -- [488 * 2-1:0] = [975:0]
-
   signal tr_10GbE_src_out        : t_dp_sosi;
   signal tr_10GbE_src_in         : t_dp_siso;
   signal tr_ref_clk_312          : std_logic := '0';
@@ -311,17 +304,27 @@ architecture tb of tb_lofar2_unb2b_sdp_station_bf is
   signal offload_rx_hdr_dat_mosi : t_mem_mosi := c_mem_mosi_rst;
   signal offload_rx_hdr_dat_miso : t_mem_miso;
 
-  signal test_offload_en         : std_logic := '0';
-  signal test_offload_data       : std_logic_vector(c_longword_w - 1 downto 0);  -- 64 bit
-  signal test_offload_sosi       : t_dp_sosi := c_dp_sosi_rst;
-  signal test_offload_sop_cnt    : natural := 0;
-  signal test_offload_eop_cnt    : natural := 0;
-
   signal rx_hdr_fields_out       : std_logic_vector(1023 downto 0);
   signal rx_hdr_fields_raw       : std_logic_vector(1023 downto 0) := (others => '0');
   signal rx_sdp_cep_header       : t_sdp_cep_header;
   signal exp_sdp_cep_header      : t_sdp_cep_header;
   signal exp_dp_bsn              : natural;
+  signal exp_payload_error       : std_logic := '0';
+
+  signal rx_beamlet_en           : std_logic := '0';
+  signal rx_beamlet_data         : std_logic_vector(c_longword_w - 1 downto 0);  -- 64 bit
+  signal rx_beamlet_sosi         : t_dp_sosi := c_dp_sosi_rst;
+  signal rx_beamlet_sop_cnt      : natural := 0;
+  signal rx_beamlet_eop_cnt      : natural := 0;
+
+  -- [0 : 3] =  X, Y, X, Y
+  signal rx_beamlet_arr_re       : t_sdp_beamlet_part_arr;
+  signal rx_beamlet_arr_im       : t_sdp_beamlet_part_arr;
+  signal rx_beamlet_cnt          : natural;
+  signal rx_beamlet_valid        : std_logic;
+  -- [0 : 4 * 488 * 2 - 1] = [0 : 3903]
+  signal rx_packet_list_re       : t_sdp_beamlet_packet_list;
+  signal rx_packet_list_im       : t_sdp_beamlet_packet_list;
 
   -- DUT
   signal ext_clk              : std_logic := '0';
@@ -493,7 +496,7 @@ begin
     snk_in_arr(0)         => tr_10GbE_src_out,
     snk_out_arr(0)        => tr_10GbE_src_in,
 
-    src_out_arr(0)        => test_offload_sosi,
+    src_out_arr(0)        => rx_beamlet_sosi,
 
     hdr_fields_out_arr(0) => rx_hdr_fields_out,
     hdr_fields_raw_arr(0) => rx_hdr_fields_raw
@@ -998,8 +1001,8 @@ begin
     ---------------------------------------------------------------------------
     -- Verify beamlet output in 10GbE UDP offload
     ---------------------------------------------------------------------------
-    v_re := TO_SINT(rx_beamlet_list_re(c_exp_beamlet_index)); v_re_exp := c_exp_beamlet_output_re;
-    v_im := TO_SINT(rx_beamlet_list_im(c_exp_beamlet_index)); v_im_exp := c_exp_beamlet_output_im;
+    v_re := TO_SINT(rx_packet_list_re(c_exp_g_beamlet_index)); v_re_exp := c_exp_beamlet_output_re;
+    v_im := TO_SINT(rx_packet_list_im(c_exp_g_beamlet_index)); v_im_exp := c_exp_beamlet_output_im;
     assert v_re > integer(v_re_exp) - c_beamlet_output_delta report "Wrong 10GbE output (re) " & integer'image(v_re) & " != " & real'image(v_re_exp) severity ERROR;
     assert v_re < integer(v_re_exp) + c_beamlet_output_delta report "Wrong 10GbE output (re) " & integer'image(v_re) & " != " & real'image(v_re_exp) severity ERROR;
     assert v_im > integer(v_im_exp) - c_beamlet_output_delta report "Wrong 10GbE output (im) " & integer'image(v_im) & " != " & real'image(v_im_exp) severity ERROR;
@@ -1022,87 +1025,66 @@ begin
   p_test_counters : process(ext_clk)
   begin
     if rising_edge(ext_clk) then
-      -- Count test_offload_sosi packets
-      if test_offload_sosi.sop = '1' then
-        test_offload_sop_cnt <= test_offload_sop_cnt + 1;  -- early count
+      -- Count rx_beamlet_sosi packets
+      if rx_beamlet_sosi.sop = '1' then
+        rx_beamlet_sop_cnt <= rx_beamlet_sop_cnt + 1;  -- early count
       end if;
-      if test_offload_sosi.eop = '1' then
-        test_offload_eop_cnt <= test_offload_eop_cnt + 1;  -- after count
+      if rx_beamlet_sosi.eop = '1' then
+        rx_beamlet_eop_cnt <= rx_beamlet_eop_cnt + 1;  -- after count
       end if;
     end if;
   end process;
 
-  -- Count sync intervals using in_sosi.sync, because there is no test_offload_sosi.sync
+  -- Count sync intervals using in_sosi.sync, because there is no rx_beamlet_sosi.sync
   in_sync_cnt <= in_sync_cnt + 1 when rising_edge(ext_clk) and in_sync = '1';
-  test_sync_cnt <= in_sync_cnt - 1;  -- optionally adjust to fit test_offload_sosi
-
-  -- Prepare exp_sdp_cep_header before test_offload_sosi.eop, so that
-  -- p_exp_sdp_cep_header can verify it at test_offload_sosi.eop.
-
-  p_exp_sdp_cep_header : process(exp_dp_bsn)
-  begin
-    -- eth header
-    exp_sdp_cep_header.eth.dst_mac        <= c_sdp_cep_eth_dst_mac;
-    exp_sdp_cep_header.eth.src_mac        <= c_cep_eth_src_mac;
-    exp_sdp_cep_header.eth.eth_type       <= x"0800";
-
-    -- ip header
-    exp_sdp_cep_header.ip.version         <= TO_UVEC(                        4, c_network_ip_version_w);
-    exp_sdp_cep_header.ip.header_length   <= TO_UVEC(                        5, c_network_ip_header_length_w);
-    exp_sdp_cep_header.ip.services        <= TO_UVEC(                        0, c_network_ip_services_w);
-    exp_sdp_cep_header.ip.total_length    <=         c_sdp_cep_ip_total_length;  -- 7868, see ICD STAT-CEP
-    exp_sdp_cep_header.ip.identification  <= TO_UVEC(                        0, c_network_ip_identification_w);
-    exp_sdp_cep_header.ip.flags           <= TO_UVEC(                        2, c_network_ip_flags_w);
-    exp_sdp_cep_header.ip.fragment_offset <= TO_UVEC(                        0, c_network_ip_fragment_offset_w);
-    exp_sdp_cep_header.ip.time_to_live    <= TO_UVEC(                      127, c_network_ip_time_to_live_w);
-    exp_sdp_cep_header.ip.protocol        <= TO_UVEC(                       17, c_network_ip_protocol_w);
-    exp_sdp_cep_header.ip.header_checksum <= TO_UVEC( c_exp_ip_header_checksum, c_network_ip_header_checksum_w);
-    exp_sdp_cep_header.ip.src_ip_addr     <=                 c_cep_ip_src_addr;  -- c_network_ip_addr_w
-    exp_sdp_cep_header.ip.dst_ip_addr     <=             c_sdp_cep_ip_dst_addr;  -- c_network_ip_addr_w
-
-    -- udp header
-    exp_sdp_cep_header.udp.src_port       <=                 c_cep_udp_src_port;
-    exp_sdp_cep_header.udp.dst_port       <=             c_sdp_cep_udp_dst_port;
-    exp_sdp_cep_header.udp.total_length   <=         c_sdp_cep_udp_total_length;  -- 7848, see ICD STAT-CEP
-    exp_sdp_cep_header.udp.checksum       <= TO_UVEC(                         0, c_network_udp_checksum_w);
-
-    -- app header
-    exp_sdp_cep_header.app.sdp_marker         <= TO_UVEC(c_sdp_marker_beamlets, 8);  -- 98 = x"62" = 'b'
-    exp_sdp_cep_header.app.sdp_version_id     <= TO_UVEC(c_sdp_cep_version_id, 8);  -- 5
-    exp_sdp_cep_header.app.sdp_observation_id <= c_exp_sdp_info.observation_id;
-    exp_sdp_cep_header.app.sdp_station_info   <= c_exp_sdp_info.antenna_field_index & c_exp_sdp_info.station_id;
-
-    exp_sdp_cep_header.app.sdp_source_info_antenna_band_id         <= slv(c_exp_sdp_info.antenna_band_index);
-    exp_sdp_cep_header.app.sdp_source_info_nyquist_zone_id         <=     c_exp_sdp_info.nyquist_zone_index;
-    exp_sdp_cep_header.app.sdp_source_info_f_adc                   <= slv(c_exp_sdp_info.f_adc);
-    exp_sdp_cep_header.app.sdp_source_info_fsub_type               <= slv(c_exp_sdp_info.fsub_type);
-    exp_sdp_cep_header.app.sdp_source_info_payload_error           <= TO_UVEC(0, 1);
-    exp_sdp_cep_header.app.sdp_source_info_beam_repositioning_flag <= slv(c_exp_sdp_info.beam_repositioning_flag);
-    exp_sdp_cep_header.app.sdp_source_info_beamlet_width           <= TO_UVEC(c_sdp_W_beamlet, 4);
-    exp_sdp_cep_header.app.sdp_source_info_gn_id                   <= TO_UVEC(c_gn_index, 5);
-
-    exp_sdp_cep_header.app.sdp_reserved                       <= TO_UVEC(                               0, 40);
-    exp_sdp_cep_header.app.sdp_beamlet_scale                  <= TO_UVEC(             c_exp_beamlet_scale, 16);
-    exp_sdp_cep_header.app.sdp_beamlet_index                  <= TO_UVEC(                               0, 16);  -- depends on bset
-    exp_sdp_cep_header.app.sdp_nof_blocks_per_packet          <= TO_UVEC( c_sdp_cep_nof_blocks_per_packet,  8);
-    exp_sdp_cep_header.app.sdp_nof_beamlets_per_block         <= TO_UVEC(c_sdp_cep_nof_beamlets_per_block, 16);
-    exp_sdp_cep_header.app.sdp_block_period                   <= c_exp_sdp_info.block_period;
-
-    exp_sdp_cep_header.app.dp_bsn <= TO_UVEC(exp_dp_bsn, 64);  -- depends on bset and time
-  end process;
+  test_sync_cnt <= in_sync_cnt - 1;  -- optionally adjust to fit rx_beamlet_sosi
+
+  -- Prepare exp_sdp_cep_header before rx_beamlet_sosi.eop, so that
+  -- p_exp_sdp_cep_header can verify it at rx_beamlet_sosi.eop.
+  exp_sdp_cep_header <= func_sdp_compose_cep_header(c_exp_ip_header_checksum,
+                                                    c_exp_sdp_info,
+                                                    c_gn_index,
+                                                    exp_payload_error,
+                                                    c_exp_beamlet_scale,
+                                                    c_exp_beamlet_index,
+                                                    exp_dp_bsn);
 
   rx_sdp_cep_header <= func_sdp_map_cep_header(rx_hdr_fields_raw);
 
   p_verify_cep_header : process
-    variable v_bool : boolean;
+    variable v_pkt_cnt : natural;
+    variable v_new_pkt : boolean;
+    variable v_error   : std_logic := '0';
+    variable v_bsn     : natural := 0;
+    variable v_bool    : boolean;
   begin
     wait until rising_edge(ext_clk);
+    -- Count packets per beamset
+    v_pkt_cnt := rx_beamlet_sop_cnt / c_sdp_N_beamsets;
+    v_new_pkt := rx_beamlet_sop_cnt mod c_sdp_N_beamsets = 0;
 
     -- Prepare exp_sdp_cep_header at sop, so that it can be verified at eop
-    if test_offload_sosi.sop = '1' then
-      -- Expected BSN increments by c_sdp_cep_nof_blocks_per_packet = 4 blocks per packet
-      if test_offload_sop_cnt mod c_sdp_N_beamsets = 0 then
-        exp_dp_bsn <= c_init_bsn + (test_offload_sop_cnt / c_sdp_N_beamsets) * c_sdp_cep_nof_blocks_per_packet;
+    if rx_beamlet_sosi.sop = '1' then
+      -- Expected BSN increments by c_sdp_cep_nof_blocks_per_packet = 4 blocks per packet,
+      -- both beamsets are outputting packets.
+      if v_new_pkt then
+        -- Default expected
+        v_error := '0';
+        v_bsn := c_init_bsn + v_pkt_cnt * c_sdp_cep_nof_blocks_per_packet;
+
+        -- Expected due to bsn and payload_error stimuli in sdp_beamformer_output.vhd.
+        if v_pkt_cnt = 1 then
+          v_error := '1';
+        elsif v_pkt_cnt = 2 or v_pkt_cnt = 3 then
+          v_bsn := v_bsn + 1;
+        elsif v_pkt_cnt = 4 then
+          v_bsn := v_bsn + 1;
+          v_error := '1';
+        end if;
+
+        -- Apply expected values
+        exp_payload_error <= v_error;
+        exp_dp_bsn <= v_bsn;
       end if;
     end if;
 
@@ -1111,7 +1093,7 @@ begin
     --   or 1, but the order in which the packets arrive is undetermined.
     --   Therefore accept any beamlet_index MOD c_sdp_S_sub_bf = 0 as correct
     --   in func_sdp_verify_cep_header().
-    if test_offload_sosi.eop = '1' then
+    if rx_beamlet_sosi.eop = '1' then
       v_bool := func_sdp_verify_cep_header(rx_sdp_cep_header, exp_sdp_cep_header);
     end if;
   end process;
@@ -1127,47 +1109,16 @@ begin
   -- . expect c_sdp_cep_nof_beamlets_per_block = c_sdp_S_sub_bf = 488 dual pol
   --   and complex beamlets per packet, so 2 dual pol beamlets/64b data word.
   -- . Beamlets array is stored big endian in the data, so X.real index 0 first
-  --   in MSByte of test_offload_sosi.data.
-  p_rx_cep_beamlets : process
-  begin
-    rx_beamlet_cnt <= 0;
-    rx_beamlet_valid <= '0';
-    -- Wait until start of a beamlet packet, capture only first block in packet
-    proc_common_wait_until_high(ext_clk, test_offload_sosi.sop);
-    -- 2 dual pol beamlets (= XY, XY) per 64b data word
-    for I in 0 to (c_sdp_cep_nof_blocks_per_packet * c_sdp_cep_nof_beamlets_per_block / 2) - 1 loop
-      proc_common_wait_until_high(ext_clk, test_offload_sosi.valid);
-      rx_beamlet_valid <= '1';
-      -- Capture rx beamlets per longword in rx_beamlet_arr, for time series view in Wave window
-      rx_beamlet_arr_re(0) <= test_offload_sosi.data(63 downto 56);  -- X
-      rx_beamlet_arr_im(0) <= test_offload_sosi.data(55 downto 48);
-      rx_beamlet_arr_re(1) <= test_offload_sosi.data(47 downto 40);  -- Y
-      rx_beamlet_arr_im(1) <= test_offload_sosi.data(39 downto 32);
-      rx_beamlet_arr_re(2) <= test_offload_sosi.data(31 downto 24);  -- X
-      rx_beamlet_arr_im(2) <= test_offload_sosi.data(23 downto 16);
-      rx_beamlet_arr_re(3) <= test_offload_sosi.data(15 downto 8);  -- Y
-      rx_beamlet_arr_im(3) <= test_offload_sosi.data( 7 downto 0);
-      if I < c_sdp_cep_nof_beamlets_per_block / 2 then
-        -- Only capture the first beamlets block of each packet in rx_beamlet_list
-        rx_beamlet_list_re(I * 4 + 0) <= test_offload_sosi.data(63 downto 56);  -- X
-        rx_beamlet_list_im(I * 4 + 0) <= test_offload_sosi.data(55 downto 48);
-        rx_beamlet_list_re(I * 4 + 1) <= test_offload_sosi.data(47 downto 40);  -- Y
-        rx_beamlet_list_im(I * 4 + 1) <= test_offload_sosi.data(39 downto 32);
-        rx_beamlet_list_re(I * 4 + 2) <= test_offload_sosi.data(31 downto 24);  -- X
-        rx_beamlet_list_im(I * 4 + 2) <= test_offload_sosi.data(23 downto 16);
-        rx_beamlet_list_re(I * 4 + 3) <= test_offload_sosi.data(15 downto 8);  -- Y
-        rx_beamlet_list_im(I * 4 + 3) <= test_offload_sosi.data( 7 downto 0);
-      end if;
-      proc_common_wait_until_high(ext_clk, test_offload_sosi.valid);
-      -- Use at least one WAIT instead of proc_common_wait_some_cycles() to
-      -- avoid Modelsim warning: (vcom-1090) Possible infinite loop: Process
-      -- contains no WAIT statement.
-      wait until rising_edge(ext_clk);
-      rx_beamlet_valid <= '0';
-      rx_beamlet_cnt   <= (rx_beamlet_cnt + 4) mod c_sdp_cep_nof_beamlets_per_block;  -- 4 blocks/packet
-    end loop;
-  end process;
+  --   in MSByte of rx_beamlet_sosi.data.
+  proc_sdp_rx_beamlet_octets(ext_clk,
+                             rx_beamlet_sosi,
+                             rx_beamlet_cnt,
+                             rx_beamlet_valid,
+                             rx_beamlet_arr_re,
+                             rx_beamlet_arr_im,
+                             rx_packet_list_re,
+                             rx_packet_list_im);
 
   -- To view the 64 bit 10GbE offload data more easily in the Wave window
-  test_offload_data <= test_offload_sosi.data(c_longword_w - 1 downto 0);
+  rx_beamlet_data <= rx_beamlet_sosi.data(c_longword_w - 1 downto 0);
 end tb;
diff --git a/applications/lofar2/designs/lofar2_unb2b_sdp_station/src/vhdl/lofar2_unb2b_sdp_station.vhd b/applications/lofar2/designs/lofar2_unb2b_sdp_station/src/vhdl/lofar2_unb2b_sdp_station.vhd
index 76c4d3cc12316b8d3dd0356e57dba9223e0220ca..d1081da37d29c20e329ab796f227ceff30d126bb 100644
--- a/applications/lofar2/designs/lofar2_unb2b_sdp_station/src/vhdl/lofar2_unb2b_sdp_station.vhd
+++ b/applications/lofar2/designs/lofar2_unb2b_sdp_station/src/vhdl/lofar2_unb2b_sdp_station.vhd
@@ -804,19 +804,20 @@ begin
   -----------------------------------------------------------------------------
   u_sdp_station : entity lofar2_sdp_lib.sdp_station
   generic map (
-    g_sim                    => g_sim,
-    g_wpfb                   => g_wpfb,
-    g_wpfb_complex           => g_wpfb_complex,
-    g_bsn_nof_clk_per_sync   => g_bsn_nof_clk_per_sync,
-    g_scope_selected_subband => g_scope_selected_subband,
-    g_no_jesd                => c_revision_select.no_jesd,
-    g_use_fsub               => c_revision_select.use_fsub,
-    g_use_oversample         => c_revision_select.use_oversample,
-    g_use_xsub               => c_revision_select.use_xsub,
-    g_use_bf                 => c_revision_select.use_bf,
-    g_use_bdo_transpose      => c_revision_select.use_bdo_transpose,
-    g_use_ring               => c_revision_select.use_ring,
-    g_P_sq                   => c_revision_select.P_sq
+    g_sim                           => g_sim,
+    g_wpfb                          => g_wpfb,
+    g_wpfb_complex                  => g_wpfb_complex,
+    g_bsn_nof_clk_per_sync          => g_bsn_nof_clk_per_sync,
+    g_scope_selected_subband        => g_scope_selected_subband,
+    g_no_jesd                       => c_revision_select.no_jesd,
+    g_use_fsub                      => c_revision_select.use_fsub,
+    g_use_oversample                => c_revision_select.use_oversample,
+    g_use_xsub                      => c_revision_select.use_xsub,
+    g_use_bf                        => c_revision_select.use_bf,
+    g_use_bdo_transpose             => c_revision_select.use_bdo_transpose,
+    g_use_bdo_multiple_destinations => c_revision_select.use_bdo_multiple_destinations,
+    g_use_ring                      => c_revision_select.use_ring,
+    g_P_sq                          => c_revision_select.P_sq
   )
   port map (
 
diff --git a/applications/lofar2/designs/lofar2_unb2b_sdp_station/src/vhdl/lofar2_unb2b_sdp_station_pkg.vhd b/applications/lofar2/designs/lofar2_unb2b_sdp_station/src/vhdl/lofar2_unb2b_sdp_station_pkg.vhd
index 0c5ca0f1ac61dfa1058416139c47b218473628ff..e65f35ec02f1047a436b00813f96f2f1ada738ec 100644
--- a/applications/lofar2/designs/lofar2_unb2b_sdp_station/src/vhdl/lofar2_unb2b_sdp_station_pkg.vhd
+++ b/applications/lofar2/designs/lofar2_unb2b_sdp_station/src/vhdl/lofar2_unb2b_sdp_station_pkg.vhd
@@ -31,30 +31,31 @@ package lofar2_unb2b_sdp_station_pkg is
   -----------------------------------------------------------------------------
 
   type t_lofar2_unb2b_sdp_station_config is record
-    no_jesd           : boolean;
-    use_fsub          : boolean;
-    use_oversample    : boolean;
-    use_bf            : boolean;
-    use_bdo_transpose : boolean;
-    use_xsub          : boolean;
-    use_ring          : boolean;
-    P_sq              : natural;
+    no_jesd                       : boolean;
+    use_fsub                      : boolean;
+    use_oversample                : boolean;
+    use_bf                        : boolean;
+    use_bdo_transpose             : boolean;
+    use_bdo_multiple_destinations : boolean;
+    use_xsub                      : boolean;
+    use_ring                      : boolean;
+    P_sq                          : natural;
   end record;
 
-  constant c_ait        : t_lofar2_unb2b_sdp_station_config := (false, false, false, false, false, false, false, 0);
-  constant c_fsub       : t_lofar2_unb2b_sdp_station_config := (false, true,  false, false, false, false, false, 0);
+  constant c_ait        : t_lofar2_unb2b_sdp_station_config := (false, false, false, false, false, false, false, false, 0);
+  constant c_fsub       : t_lofar2_unb2b_sdp_station_config := (false, true,  false, false, false, false, false, false, 0);
   -- use c_bf on one node also to simulate bdo transpose
   -- use c_bf_ring with ring also to simulate bdo identity
-  constant c_bf         : t_lofar2_unb2b_sdp_station_config := (false, true,  false, true,  false, false, false, 0);
-  constant c_bf_ring    : t_lofar2_unb2b_sdp_station_config := (false, true,  false, true,  false, false, true,  0);
-  constant c_xsub_one   : t_lofar2_unb2b_sdp_station_config := (false, true,  false, false, false, true,  false, 1);
-  constant c_xsub_ring  : t_lofar2_unb2b_sdp_station_config := (false, true,  false, false, false, true,  true,  9);
+  constant c_bf         : t_lofar2_unb2b_sdp_station_config := (false, true,  false, true,  false, false, false, false, 0);
+  constant c_bf_ring    : t_lofar2_unb2b_sdp_station_config := (false, true,  false, true,  false, false, false, true,  0);
+  constant c_xsub_one   : t_lofar2_unb2b_sdp_station_config := (false, true,  false, false, false, false, true,  false, 1);
+  constant c_xsub_ring  : t_lofar2_unb2b_sdp_station_config := (false, true,  false, false, false, false, true,  true,  9);
   -- use c_full_wg for SDP regression test on Arts-unb2b
-  constant c_full_wg    : t_lofar2_unb2b_sdp_station_config := (true,  true,  false, true,  true,  true,  true,  9);
-  constant c_full       : t_lofar2_unb2b_sdp_station_config := (false, true,  false, true,  false, true,  true,  9);
-  constant c_full_wg_os : t_lofar2_unb2b_sdp_station_config := (true,  true,  true,  true,  false, true,  true,  9);
+  constant c_full_wg    : t_lofar2_unb2b_sdp_station_config := (true,  true,  false, true,  true,  false, true,  true,  9);
+  constant c_full       : t_lofar2_unb2b_sdp_station_config := (false, true,  false, true,  false, false, true,  true,  9);
+  constant c_full_wg_os : t_lofar2_unb2b_sdp_station_config := (true,  true,  true,  true,  false, false, true,  true,  9);
   -- use c_full_os for SDP on LTS-unb2b of Disturb2
-  constant c_full_os    : t_lofar2_unb2b_sdp_station_config := (false, true,  true,  true,  false, true,  true,  9);
+  constant c_full_os    : t_lofar2_unb2b_sdp_station_config := (false, true,  true,  true,  false, false, true,  true,  9);
 
   -- Function to select the revision configuration.
   function func_sel_revision_rec(g_design_name : string) return t_lofar2_unb2b_sdp_station_config;
diff --git a/applications/lofar2/designs/lofar2_unb2c_sdp_station/revisions/lofar2_unb2c_sdp_station_bf/tb_lofar2_unb2c_sdp_station_bf.vhd b/applications/lofar2/designs/lofar2_unb2c_sdp_station/revisions/lofar2_unb2c_sdp_station_bf/tb_lofar2_unb2c_sdp_station_bf.vhd
index b1d36877af2397afbdd34f62447c0b6d8560f1bb..0e6167477ddb76d0a8d3921ad585908e4aa30fce 100644
--- a/applications/lofar2/designs/lofar2_unb2c_sdp_station/revisions/lofar2_unb2c_sdp_station_bf/tb_lofar2_unb2c_sdp_station_bf.vhd
+++ b/applications/lofar2/designs/lofar2_unb2c_sdp_station/revisions/lofar2_unb2c_sdp_station_bf/tb_lofar2_unb2c_sdp_station_bf.vhd
@@ -246,9 +246,9 @@ architecture tb of tb_lofar2_unb2c_sdp_station_bf is
                                      x"1400"  -- block_period = 5120
                                    );
 
-  -- Expected transposed indices order by func_sdp_bdo_transpose_packet().
+  -- Expected transposed indices order by func_sdp_undo_transpose_beamlet_packet().
   -- Yields same c_transpose_indices order as func_reorder_transpose_packet():
-  -- > python applications/lofar2/libraries/sdp/src/python/test_func_sdp_bdo_transpose_packet.py
+  -- > python applications/lofar2/libraries/sdp/src/python/test_func_sdp_transpose_packet.py
   constant c_nof_ch                : natural := c_sdp_cep_nof_beamlets_per_packet * c_sdp_N_pol_bf;
   -- Use c_transpose_indices and c_transpose_indices_inv for view in Objects window.
   -- The c_transpose_indices is used in sdp_beamformer_output, therefore use
@@ -490,8 +490,9 @@ architecture tb of tb_lofar2_unb2c_sdp_station_bf is
   signal rx_reordered_list_re   : t_sdp_beamlet_packet_list;
   signal rx_reordered_list_im   : t_sdp_beamlet_packet_list;
 
-  -- Recover original beamlet order per block, either by using c_use_bdo_transpose
-  -- = false or by using c_use_bdo_transpose and func_sdp_bdo_transpose_packet().
+  -- Recover original beamlet order per block, either by using
+  -- c_use_bdo_transpose = false or by using c_use_bdo_transpose = true
+  -- and func_sdp_undo_transpose_beamlet_packet().
   -- List: [0 : 488 * 2 - 1] = [0 : 975]
   -- . X part at even indices
   -- . Y part at odd indices
@@ -1361,49 +1362,20 @@ begin
   -- Show received beamlets from 10GbE stream in Wave Window
   -- . The packet header is 9.25 longwords wide. The dp_offload_rx has stripped
   --   the header and has realigned the payload at a longword boundary.
-  -- . expect c_nof_block_per_sync / c_sdp_cep_nof_blocks_per_packet *
-  --   c_sdp_N_beamsets = 16 / 4 * 2 = 4 * 2 = 8 packets per sync interval
-  -- . expect c_sdp_cep_nof_beamlets_per_block = c_sdp_S_sub_bf = 488 dual pol
+  -- . Expect c_sdp_cep_nof_beamlets_per_block = c_sdp_S_sub_bf = 488 dual pol
   --   and complex beamlets per packet, so 2 dual pol beamlets/64b data word.
   -- . Beamlets array is stored big endian in the data, so X.real index 0 first
   --   in MSByte of rx_beamlet_sosi.data.
-  p_rx_cep_beamlets : process
-  begin
-    rx_beamlet_cnt <= 0;
-    rx_beamlet_valid <= '0';
-    -- Wait until start of a beamlet packet
-    proc_common_wait_until_high(ext_clk, rx_beamlet_sosi.sop);
-    -- c_sdp_nof_beamlets_per_longword = 2 dual pol beamlets (= XY, XY) per 64b data word
-    for I in 0 to (c_sdp_cep_nof_beamlets_per_packet / c_sdp_nof_beamlets_per_longword) - 1 loop
-      proc_common_wait_until_high(ext_clk, rx_beamlet_sosi.valid);
-      rx_beamlet_valid <= '1';
-      -- Capture rx beamlets per longword in rx_beamlet_arr, for time series view in Wave window
-      rx_beamlet_arr_re(0) <= rx_beamlet_sosi.data(63 downto 56);  -- X
-      rx_beamlet_arr_im(0) <= rx_beamlet_sosi.data(55 downto 48);
-      rx_beamlet_arr_re(1) <= rx_beamlet_sosi.data(47 downto 40);  -- Y
-      rx_beamlet_arr_im(1) <= rx_beamlet_sosi.data(39 downto 32);
-      rx_beamlet_arr_re(2) <= rx_beamlet_sosi.data(31 downto 24);  -- X
-      rx_beamlet_arr_im(2) <= rx_beamlet_sosi.data(23 downto 16);
-      rx_beamlet_arr_re(3) <= rx_beamlet_sosi.data(15 downto 8);  -- Y
-      rx_beamlet_arr_im(3) <= rx_beamlet_sosi.data( 7 downto 0);
-      -- Capture the beamlets block of each packet in rx_packet_list
-      rx_packet_list_re(I * 4 + 0) <= rx_beamlet_sosi.data(63 downto 56);  -- X
-      rx_packet_list_im(I * 4 + 0) <= rx_beamlet_sosi.data(55 downto 48);
-      rx_packet_list_re(I * 4 + 1) <= rx_beamlet_sosi.data(47 downto 40);  -- Y
-      rx_packet_list_im(I * 4 + 1) <= rx_beamlet_sosi.data(39 downto 32);
-      rx_packet_list_re(I * 4 + 2) <= rx_beamlet_sosi.data(31 downto 24);  -- X
-      rx_packet_list_im(I * 4 + 2) <= rx_beamlet_sosi.data(23 downto 16);
-      rx_packet_list_re(I * 4 + 3) <= rx_beamlet_sosi.data(15 downto 8);  -- Y
-      rx_packet_list_im(I * 4 + 3) <= rx_beamlet_sosi.data( 7 downto 0);
-      proc_common_wait_until_high(ext_clk, rx_beamlet_sosi.valid);
-      -- Use at least one WAIT instead of proc_common_wait_some_cycles() to
-      -- avoid Modelsim warning: (vcom-1090) Possible infinite loop: Process
-      -- contains no WAIT statement.
-      wait until rising_edge(ext_clk);
-      rx_beamlet_valid <= '0';
-      rx_beamlet_cnt   <= (rx_beamlet_cnt + c_sdp_nof_beamlets_per_longword) mod c_sdp_cep_nof_beamlets_per_block;  -- 4 blocks/packet
-    end loop;
-  end process;
+  -- . Expect c_nof_block_per_sync / c_sdp_cep_nof_blocks_per_packet *
+  --   c_sdp_N_beamsets = 16 / 4 * 2 = 4 * 2 = 8 packets per sync interval
+  proc_sdp_rx_beamlet_octets(ext_clk,
+                             rx_beamlet_sosi,
+                             rx_beamlet_cnt,
+                             rx_beamlet_valid,
+                             rx_beamlet_arr_re,
+                             rx_beamlet_arr_im,
+                             rx_packet_list_re,
+                             rx_packet_list_im);
 
   -- Undo the beamlet output transpose, to have original beamlet order
   p_rx_reordered_list : process
@@ -1412,12 +1384,8 @@ begin
     wait until rising_edge(ext_clk);  -- to avoid Modelsim warning: (vcom-1090)
     proc_common_wait_until_hi_lo(ext_clk, rx_beamlet_sosi.eop);  -- to reduce simulation effort
     -- Inverse tranpose
-    rx_reordered_list_re <= func_sdp_bdo_transpose_packet(c_sdp_cep_nof_beamlets_per_block,
-                                                          c_sdp_cep_nof_blocks_per_packet,
-                                                          rx_packet_list_re);
-    rx_reordered_list_im <= func_sdp_bdo_transpose_packet(c_sdp_cep_nof_beamlets_per_block,
-                                                          c_sdp_cep_nof_blocks_per_packet,
-                                                          rx_packet_list_im);
+    rx_reordered_list_re <= func_sdp_undo_transpose_beamlet_packet(rx_packet_list_re);
+    rx_reordered_list_im <= func_sdp_undo_transpose_beamlet_packet(rx_packet_list_im);
   end process;
 
   p_rx_beamlet_list : process
diff --git a/applications/lofar2/designs/lofar2_unb2c_sdp_station/revisions/lofar2_unb2c_sdp_station_bf_ring/tb_lofar2_unb2c_sdp_station_bf_ring.vhd b/applications/lofar2/designs/lofar2_unb2c_sdp_station/revisions/lofar2_unb2c_sdp_station_bf_ring/tb_lofar2_unb2c_sdp_station_bf_ring.vhd
index 46ff829e23b2a2377901e8cb07177b7bf700da80..f4ada61682a5443c7be855ec9c9ad6a42fd86828 100644
--- a/applications/lofar2/designs/lofar2_unb2c_sdp_station/revisions/lofar2_unb2c_sdp_station_bf_ring/tb_lofar2_unb2c_sdp_station_bf_ring.vhd
+++ b/applications/lofar2/designs/lofar2_unb2c_sdp_station/revisions/lofar2_unb2c_sdp_station_bf_ring/tb_lofar2_unb2c_sdp_station_bf_ring.vhd
@@ -260,7 +260,7 @@ architecture tb of tb_lofar2_unb2c_sdp_station_bf_ring is
 
   -- Expected transposed indices order by func_reorder_transpose_packet().
   -- Yields same c_reorder_transpose_indices order as:
-  -- > python applications/lofar2/libraries/sdp/src/python/test_func_sdp_bdo_transpose_packet.py
+  -- > python applications/lofar2/libraries/sdp/src/python/test_func_sdp_transpose_packet.py
   constant c_nof_ch                    : natural := c_sdp_cep_nof_beamlets_per_packet * c_sdp_N_pol_bf;
   constant c_reorder_transpose_indices : t_natural_arr(0 to c_nof_ch - 1) :=
     func_reorder_transpose_indices(c_sdp_cep_nof_blocks_per_packet,
@@ -493,8 +493,9 @@ architecture tb of tb_lofar2_unb2c_sdp_station_bf_ring is
   signal rx_reordered_list_re   : t_sdp_beamlet_packet_list;
   signal rx_reordered_list_im   : t_sdp_beamlet_packet_list;
 
-  -- Recover original beamlet order per block, either by using c_use_bdo_transpose
-  -- = false or by using c_use_bdo_transpose and func_sdp_bdo_transpose_packet().
+  -- Recover original beamlet order per block, either by using
+  -- c_use_bdo_transpose = false or by using c_use_bdo_transpose = true
+  -- and func_sdp_undo_transpose_beamlet_packet().
   -- List: [0 : 488 * 2 - 1] = [0 : 975]
   -- . X part at even indices
   -- . Y part at odd indices
@@ -1502,12 +1503,8 @@ begin
     -- Wait until end of a beamlet packet
     wait until rising_edge(ext_clk);  -- to avoid Modelsim warning: (vcom-1090)
     proc_common_wait_until_hi_lo(ext_clk, rx_beamlet_sosi.eop);  -- to reduce simulation effort
-    rx_reordered_list_re <= func_sdp_bdo_transpose_packet(c_sdp_cep_nof_blocks_per_packet,
-                                                          c_sdp_cep_nof_beamlets_per_block,
-                                                          rx_packet_list_re);
-    rx_reordered_list_im <= func_sdp_bdo_transpose_packet(c_sdp_cep_nof_blocks_per_packet,
-                                                          c_sdp_cep_nof_beamlets_per_block,
-                                                          rx_packet_list_im);
+    rx_reordered_list_re <= func_sdp_undo_transpose_beamlet_packet(rx_packet_list_re);
+    rx_reordered_list_im <= func_sdp_undo_transpose_beamlet_packet(rx_packet_list_im);
   end process;
 
   p_rx_beamlet_list : process
diff --git a/applications/lofar2/designs/lofar2_unb2c_sdp_station/src/vhdl/lofar2_unb2c_sdp_station.vhd b/applications/lofar2/designs/lofar2_unb2c_sdp_station/src/vhdl/lofar2_unb2c_sdp_station.vhd
index 8635117451988ddf5114ad7ece2c26191757fbf3..6862b4620fc00a9e665f8fe07edd96d13fd9737a 100644
--- a/applications/lofar2/designs/lofar2_unb2c_sdp_station/src/vhdl/lofar2_unb2c_sdp_station.vhd
+++ b/applications/lofar2/designs/lofar2_unb2c_sdp_station/src/vhdl/lofar2_unb2c_sdp_station.vhd
@@ -770,18 +770,19 @@ begin
   -----------------------------------------------------------------------------
   u_sdp_station : entity lofar2_sdp_lib.sdp_station
   generic map (
-    g_sim                    => g_sim,
-    g_wpfb                   => g_wpfb,
-    g_bsn_nof_clk_per_sync   => g_bsn_nof_clk_per_sync,
-    g_scope_selected_subband => g_scope_selected_subband,
-    g_no_jesd                => c_revision_select.no_jesd,
-    g_use_fsub               => c_revision_select.use_fsub,
-    g_use_oversample         => c_revision_select.use_oversample,
-    g_use_xsub               => c_revision_select.use_xsub,
-    g_use_bf                 => c_revision_select.use_bf,
-    g_use_bdo_transpose      => c_revision_select.use_bdo_transpose,
-    g_use_ring               => c_revision_select.use_ring,
-    g_P_sq                   => c_revision_select.P_sq
+    g_sim                           => g_sim,
+    g_wpfb                          => g_wpfb,
+    g_bsn_nof_clk_per_sync          => g_bsn_nof_clk_per_sync,
+    g_scope_selected_subband        => g_scope_selected_subband,
+    g_no_jesd                       => c_revision_select.no_jesd,
+    g_use_fsub                      => c_revision_select.use_fsub,
+    g_use_oversample                => c_revision_select.use_oversample,
+    g_use_xsub                      => c_revision_select.use_xsub,
+    g_use_bf                        => c_revision_select.use_bf,
+    g_use_bdo_transpose             => c_revision_select.use_bdo_transpose,
+    g_use_bdo_multiple_destinations => c_revision_select.use_bdo_multiple_destinations,
+    g_use_ring                      => c_revision_select.use_ring,
+    g_P_sq                          => c_revision_select.P_sq
   )
   port map (
 
diff --git a/applications/lofar2/designs/lofar2_unb2c_sdp_station/src/vhdl/lofar2_unb2c_sdp_station_pkg.vhd b/applications/lofar2/designs/lofar2_unb2c_sdp_station/src/vhdl/lofar2_unb2c_sdp_station_pkg.vhd
index fead337700c0f560cf68040dbc958598b1ada954..89fdb57b349264aec4d9125bb723b0abc2746532 100644
--- a/applications/lofar2/designs/lofar2_unb2c_sdp_station/src/vhdl/lofar2_unb2c_sdp_station_pkg.vhd
+++ b/applications/lofar2/designs/lofar2_unb2c_sdp_station/src/vhdl/lofar2_unb2c_sdp_station_pkg.vhd
@@ -31,29 +31,30 @@ package lofar2_unb2c_sdp_station_pkg is
   -----------------------------------------------------------------------------
 
   type t_lofar2_unb2c_sdp_station_config is record
-    no_jesd           : boolean;
-    use_fsub          : boolean;
-    use_oversample    : boolean;
-    use_bf            : boolean;
-    use_bdo_transpose : boolean;
-    use_xsub          : boolean;
-    use_ring          : boolean;
-    P_sq              : natural;
+    no_jesd                       : boolean;
+    use_fsub                      : boolean;
+    use_oversample                : boolean;
+    use_bf                        : boolean;
+    use_bdo_transpose             : boolean;
+    use_bdo_multiple_destinations : boolean;
+    use_xsub                      : boolean;
+    use_ring                      : boolean;
+    P_sq                          : natural;
   end record;
 
-  constant c_ait        : t_lofar2_unb2c_sdp_station_config := (false, false, false, false, false, false, false, 0);
-  constant c_fsub       : t_lofar2_unb2c_sdp_station_config := (false, true,  false, false, false, false, false, 0);
+  constant c_ait        : t_lofar2_unb2c_sdp_station_config := (false, false, false, false, false, false, false, false, 0);
+  constant c_fsub       : t_lofar2_unb2c_sdp_station_config := (false, true,  false, false, false, false, false, false, 0);
   -- use c_bf on one node also to simulate bdo transpose
   -- use c_bf_ring with ring also to simulate bdo identity
-  constant c_bf         : t_lofar2_unb2c_sdp_station_config := (false, true,  false, true,  true,  false, false, 0);
-  constant c_bf_ring    : t_lofar2_unb2c_sdp_station_config := (false, true,  false, true,  false, false, true,  0);
-  constant c_xsub_one   : t_lofar2_unb2c_sdp_station_config := (false, true,  false, false, false, true,  false, 1);
-  constant c_xsub_ring  : t_lofar2_unb2c_sdp_station_config := (false, true,  false, false, false, true,  true,  9);
-  constant c_full_wg    : t_lofar2_unb2c_sdp_station_config := (true,  true,  false, true,  true,  true,  true,  9);
+  constant c_bf         : t_lofar2_unb2c_sdp_station_config := (false, true,  false, true,  true,  false, false, false, 0);
+  constant c_bf_ring    : t_lofar2_unb2c_sdp_station_config := (false, true,  false, true,  false, false, false, true,  0);
+  constant c_xsub_one   : t_lofar2_unb2c_sdp_station_config := (false, true,  false, false, false, false, true,  false, 1);
+  constant c_xsub_ring  : t_lofar2_unb2c_sdp_station_config := (false, true,  false, false, false, false, true,  true,  9);
+  constant c_full_wg    : t_lofar2_unb2c_sdp_station_config := (true,  true,  false, true,  true,  false, true,  true,  9);
   -- Use c_full for LOFAR2 Station SDP operations
-  constant c_full       : t_lofar2_unb2c_sdp_station_config := (false, true,  false, true,  true,  true,  true,  9);
-  constant c_full_wg_os : t_lofar2_unb2c_sdp_station_config := (true,  true,  true,  true,  true,  true,  true,  9);
-  constant c_full_os    : t_lofar2_unb2c_sdp_station_config := (false, true,  true,  true,  true,  true,  true,  9);
+  constant c_full       : t_lofar2_unb2c_sdp_station_config := (false, true,  false, true,  true,  false, true,  true,  9);
+  constant c_full_wg_os : t_lofar2_unb2c_sdp_station_config := (true,  true,  true,  true,  true,  false, true,  true,  9);
+  constant c_full_os    : t_lofar2_unb2c_sdp_station_config := (false, true,  true,  true,  true,  false, true,  true,  9);
 
   -- Function to select the revision configuration.
   function func_sel_revision_rec(g_design_name : string) return t_lofar2_unb2c_sdp_station_config;
diff --git a/applications/lofar2/libraries/sdp/hdllib.cfg b/applications/lofar2/libraries/sdp/hdllib.cfg
index bc0e30b24863c2e0ddcb8ae42b731be1fde5d120..24415a8291ea288a2f86dcf333670bffd4153ab7 100644
--- a/applications/lofar2/libraries/sdp/hdllib.cfg
+++ b/applications/lofar2/libraries/sdp/hdllib.cfg
@@ -1,22 +1,26 @@
 hdl_lib_name = lofar2_sdp
 hdl_library_clause_name = lofar2_sdp_lib
 hdl_lib_uses_synth = common dp wpfb rTwoSDF filter si st reorder technology tech_pll mm dp diag aduh tech_jesd204b nw_10GbE eth ring
-hdl_lib_uses_sim = 
-hdl_lib_technology = 
+hdl_lib_uses_sim =
+hdl_lib_technology =
 
-synth_files = 
-    src/vhdl/sdp_pkg.vhd 
+synth_files =
+    src/vhdl/sdp_pkg.vhd
     src/vhdl/sdp_scope.vhd
     src/vhdl/sdp_subband_weights.vhd
     src/vhdl/sdp_subband_equalizer.vhd
-    src/vhdl/sdp_bf_weights.vhd 
-    src/vhdl/sdp_beamformer_local.vhd 
-    src/vhdl/sdp_beamformer_remote.vhd 
-    src/vhdl/sdp_info_reg.vhd 
-    src/vhdl/sdp_info.vhd 
-    src/vhdl/sdp_beamformer_output.vhd 
-    src/vhdl/sdp_statistics_offload.vhd 
-    src/vhdl/sdp_crosslets_subband_select.vhd 
+    src/vhdl/sdp_bf_weights.vhd
+    src/vhdl/sdp_beamformer_local.vhd
+    src/vhdl/sdp_beamformer_remote.vhd
+    src/vhdl/sdp_info_reg.vhd
+    src/vhdl/sdp_info.vhd
+    src/vhdl/sdp_bdo_pkg.vhd
+    src/vhdl/sdp_bdo_destinations_reg.vhd
+    src/vhdl/sdp_bdo_one_destination.vhd
+    src/vhdl/sdp_bdo_multiple_destinations.vhd
+    src/vhdl/sdp_beamformer_output.vhd
+    src/vhdl/sdp_statistics_offload.vhd
+    src/vhdl/sdp_crosslets_subband_select.vhd
     src/vhdl/node_sdp_adc_input_and_timing.vhd
     src/vhdl/node_sdp_filterbank.vhd
     src/vhdl/node_sdp_oversampled_filterbank.vhd
@@ -29,13 +33,16 @@ test_bench_files =
     tb/vhdl/tb_sdp_info.vhd
     tb/vhdl/tb_sdp_statistics_offload.vhd
     tb/vhdl/tb_tb_sdp_statistics_offload.vhd
-    tb/vhdl/tb_sdp_crosslets_subband_select.vhd 
+    tb/vhdl/tb_sdp_crosslets_subband_select.vhd
+    tb/vhdl/tb_sdp_beamformer_output.vhd
+    tb/vhdl/tb_tb_sdp_beamformer_output.vhd
 
-regression_test_vhdl = 
+regression_test_vhdl =
     tb/vhdl/tb_sdp_info.vhd
-    tb/vhdl/tb_sdp_statistics_offload.vhd 
+    tb/vhdl/tb_sdp_statistics_offload.vhd
     tb/vhdl/tb_tb_sdp_statistics_offload.vhd
-    tb/vhdl/tb_sdp_crosslets_subband_select.vhd 
+    tb/vhdl/tb_sdp_crosslets_subband_select.vhd
+    tb/vhdl/tb_tb_sdp_beamformer_output.vhd
 
 [modelsim_project_file]
 
diff --git a/applications/lofar2/libraries/sdp/sdp.peripheral.yaml b/applications/lofar2/libraries/sdp/sdp.peripheral.yaml
index 73acf210efb3dbed285dc6417e3f2e7748f82d5e..17b64c399c61a6e6aa3646a6b5497c1f86501d8f 100644
--- a/applications/lofar2/libraries/sdp/sdp.peripheral.yaml
+++ b/applications/lofar2/libraries/sdp/sdp.peripheral.yaml
@@ -27,7 +27,7 @@ peripherals:
           - - { field_name: block_period,            mm_width: 16, access_mode: RO, address_offset: 0x0  }
 
 
-  - peripheral_name: sdp_crosslets_subband_select    # pi_sdp_crosslets_info.py 
+  - peripheral_name: sdp_crosslets_subband_select    # pi_sdp_crosslets_info.py
     peripheral_description: "SDP crosslets info."
     mm_ports:
       # MM port for sdp_info.vhd
@@ -35,15 +35,15 @@ peripherals:
         mm_port_type: REG
         mm_port_span: 16 * MM_BUS_SIZE
         mm_port_description: |
-          "The SDP crosslets info contains the step size and 15 offsets, that are used to select a new 
+          "The SDP crosslets info contains the step size and 15 offsets, that are used to select a new
            crosslet subband for every integration interval"
         fields:
           - - { field_name: step, access_mode: RW, address_offset: 0x3C }
           - - field_name: offset
-              number_of_fields: 15  
+              number_of_fields: 15
               address_offset: 0x0
 
-  - peripheral_name: sdp_nof_crosslets    # pi_sdp_nof_crosslets.py 
+  - peripheral_name: sdp_nof_crosslets    # pi_sdp_nof_crosslets.py
     peripheral_description: "SDP nof crosslets."
     mm_ports:
       - mm_port_name: REG_NOF_CROSSLETS
@@ -104,7 +104,7 @@ peripherals:
 
 
   - peripheral_name: sdp_bf_weights    # pi_sdp_bf_weights.py
-    peripheral_description: "SDP Beamformer weights (= beamlet weights)."
+    peripheral_description: "SDP Beamformer (BF) weights (= beamlet weights)."
     parameters:
       # Parameters of pi_sdp_bf_weights.py, fixed in sdp_bf_weights.vhd / sdp_pkg.vhd
       - { name: N_pol_bf, value: 2 }
@@ -155,7 +155,7 @@ peripherals:
 
 
   - peripheral_name: sdp_bf_scale    # pi_sdp_bf_scale.py
-    peripheral_description: "SDP BF beamlet data output scaling and requantization."
+    peripheral_description: "SDP beamlet data output (BDO) scaling and requantization."
     parameters:
       # Parameters fixed in node_sdp_beamformer.vhd / mms_dp_scale.vhd / sdp_pkg.vhd
       - { name: g_gain_w, value: 16 }
@@ -190,8 +190,36 @@ peripherals:
               address_offset: 0x4
 
 
+  - peripheral_name: sdp_bdo_destinations
+    peripheral_description: "SDP beamlet data output (BDO) destinations."
+    parameters:
+      # Parameters fixed in sdp_beamformer_output.vhd / sdp_pkg.vhd
+      - { name: N_destinations_max, value: 16 }
+      - { name: N_reorder_blocks_max, value: 16 }
+    mm_ports:
+      # MM port for sdp_beamformer_output.vhd / mm_fields.vhd
+      - mm_port_name: REG_BDO_DESTINATIONS
+        mm_port_type: REG
+        mm_port_span: 16 * MM_BUS_SIZE
+        mm_port_description: |
+          "The SDP beamlets in a beamset can be send to 1 or multiple destinations, each identified
+           by destination MAC addess, IP address and UDP port.
+           The number of destinations is 1 <= nof_destinations <= N_destinations_max. The actual
+           nof_destinations is reported via nof_destinations_act.
+           The actual number of blocks per packet depends on nof_destinations_act, and is reported
+           via nof_blocks_per_packet_act."
+        fields:
+          - - { field_name: eth_destination_mac,    number_of_fields: N_destinations_max, mm_width: 32, user_width: 48, radix: uint64, access_mode: RW, address_offset: 0x0 }
+          - - { field_name: ip_destination_address, number_of_fields: N_destinations_max, mm_width: 32,                                access_mode: RW, address_offset: 0x80 }
+          - - { field_name: udp_destination_port,   number_of_fields: N_destinations_max, mm_width: 16,                                access_mode: RW, address_offset: 0xC0 }
+          - - { field_name: nof_destinations,          mm_width: 8, access_mode: RW, address_offset: 0x100 }
+          - - { field_name: nof_destinations_act,      mm_width: 8, access_mode: RO, address_offset: 0x104 }
+          - - { field_name: nof_destinations_max,      mm_width: 8, access_mode: RO, address_offset: 0x108 }
+          - - { field_name: nof_blocks_per_packet_act, mm_width: 8, access_mode: RO, address_offset: 0x10C }
+
+
   - peripheral_name: sdp_beamformer_output_hdr_dat  #  pi_dp_offload_tx_hdr_dat_lofar2_beamformer_output.py
-    peripheral_description: "SDP BF beamlet data output header."
+    peripheral_description: "SDP beamlet data output (BDO) header."
     mm_ports:
       # MM port for sdp_beamformer_output.vhd / dp_offload_tx_v3.vhd
       - mm_port_name: REG_DP_OFFLOAD_TX_HDR_DAT
diff --git a/applications/lofar2/libraries/sdp/src/python/test_func_sdp_transpose_packet.py b/applications/lofar2/libraries/sdp/src/python/test_func_sdp_transpose_packet.py
index f1063a7473a9f5c5d8787fed11282d6698937ead..a659504a4201fa5cc10509113231b80c698e9438 100644
--- a/applications/lofar2/libraries/sdp/src/python/test_func_sdp_transpose_packet.py
+++ b/applications/lofar2/libraries/sdp/src/python/test_func_sdp_transpose_packet.py
@@ -21,16 +21,18 @@
 # Author: Eric Kooistra
 # Date: Aug 2023
 # Purpose:
-#   Use Python to verify equivalent VHDL function func_sdp_bdo_transpose_packet()
+#   Use Python to verify equivalent VHDL functions
+#   func_sdp_transpose_beamlet_packet() and
+#   func_sdp_undo_transpose_beamlet_packet() and
 #   in tb_sdp_pkg.vhd
 # Usage:
-# > python test_func_sdp_bdo_transpose_packet.py > x
+# > python test_func_sdp_transpose_packet.py > x
 # > more x
 # > tail -n 50 x
 
 c_sdp_N_pol_bf = 2
 
-def func_sdp_bdo_transpose_packet(nof_blocks_per_packet, nof_beamlets_per_block, packet_list):
+def func_sdp_transpose_packet(nof_blocks_per_packet, nof_beamlets_per_block, packet_list):
     v_list = [0] * len(packet_list)
     for blk in range(nof_blocks_per_packet):
         for blet in range(nof_beamlets_per_block):
@@ -43,6 +45,6 @@ def func_sdp_bdo_transpose_packet(nof_blocks_per_packet, nof_beamlets_per_block,
 nof_blocks_per_packet = 4
 nof_beamlets_per_block = 488
 packet_list = list(range(0, nof_beamlets_per_block * nof_blocks_per_packet * c_sdp_N_pol_bf))
-out_list = func_sdp_bdo_transpose_packet(nof_blocks_per_packet, nof_beamlets_per_block, packet_list)
+out_list = func_sdp_transpose_packet(nof_blocks_per_packet, nof_beamlets_per_block, packet_list)
 for d in out_list:
     print('%d' % d)
diff --git a/applications/lofar2/libraries/sdp/src/vhdl/node_sdp_beamformer.vhd b/applications/lofar2/libraries/sdp/src/vhdl/node_sdp_beamformer.vhd
index d278d1ab57802420adceec141afdf925a7036cd7..6a5a17961acb3510a95d385887ca5858ecead7e2 100644
--- a/applications/lofar2/libraries/sdp/src/vhdl/node_sdp_beamformer.vhd
+++ b/applications/lofar2/libraries/sdp/src/vhdl/node_sdp_beamformer.vhd
@@ -40,14 +40,15 @@ use work.sdp_pkg.all;
 
 entity node_sdp_beamformer is
   generic (
-    g_sim                    : boolean := false;
-    g_sim_sdp                : t_sdp_sim := c_sdp_sim;
-    g_beamset_id             : natural := 0;
-    g_use_bdo_transpose      : boolean := false;
-    g_scope_selected_beamlet : natural := 0;
+    g_sim                           : boolean := false;
+    g_sim_sdp                       : t_sdp_sim := c_sdp_sim;
+    g_beamset_id                    : natural := 0;
+    g_use_bdo_transpose             : boolean := false;
+    g_use_bdo_multiple_destinations : boolean := false;
+    g_scope_selected_beamlet        : natural := 0;
     -- Use no default raw width, to force instance to set it
-    g_subband_raw_dat_w      : natural;  -- default: c_sdp_W_subband;
-    g_subband_raw_fraction_w : natural  -- default: 0
+    g_subband_raw_dat_w             : natural;  -- default: c_sdp_W_subband;
+    g_subband_raw_fraction_w        : natural  -- default: 0
   );
   port (
     dp_clk        : in  std_logic;
@@ -255,8 +256,9 @@ begin
   ---------------------------------------------------------------
   u_sdp_beamformer_output : entity work.sdp_beamformer_output
   generic map(
-    g_beamset_id    => g_beamset_id,
-    g_use_transpose => g_use_bdo_transpose
+    g_beamset_id                => g_beamset_id,
+    g_use_transpose             => g_use_bdo_transpose,
+    g_use_multiple_destinations => g_use_bdo_multiple_destinations
   )
   port map (
     mm_rst => mm_rst,
diff --git a/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_destinations_reg.vhd b/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_destinations_reg.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..89a9aa97d18a6133b9a7016dccac5be15253a145
--- /dev/null
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_destinations_reg.vhd
@@ -0,0 +1,240 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2023
+-- 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:
+-- . SDP beamlet data output (BDO) destinations register
+-- Description:
+--              _________
+--             |mm_fields|
+--             |         |
+-- reg_copi -->|  slv_out|------- _wr ----
+-- reg_cipo <--|         |               |
+--             |         |               v
+--             |   slv_in|<--+--- _rd <--+<---- _ro
+--             |_________|   |
+--                           -----------------> output value
+--
+-- where:
+-- . _ro = actual nof_destinations, actual nof_blocks_per_packet
+-- . output value = destinations_info
+--
+-- References:
+-- 1 https://plm.astron.nl/polarion/#/project/LOFAR2System/wiki/L2%20Interface%20Control%20Documents/SC%20to%20SDP%20ICD
+-- 2 https://support.astron.nl/confluence/display/L2M/L4+SDPFW+Decision%3A+Multiple+beamlet+output+destinations
+--
+
+library IEEE, common_lib, mm_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 work.sdp_pkg.all;
+  use work.sdp_bdo_pkg.all;
+
+entity sdp_bdo_destinations_reg is
+  port (
+    -- Clocks and reset
+    mm_clk   : in  std_logic;
+    mm_rst   : in  std_logic;
+
+    dp_clk   : in  std_logic;
+    dp_rst   : in  std_logic;
+
+    reg_copi : in  t_mem_copi;
+    reg_cipo : out t_mem_cipo;
+
+    destinations_info : out t_sdp_bdo_destinations_info
+  );
+end sdp_bdo_destinations_reg;
+
+architecture str of sdp_bdo_destinations_reg is
+  constant c_field_arr : t_common_field_arr(c_sdp_bdo_destinations_info_nof_hdr_fields - 1 downto 0) :=
+    ( (field_name_pad("nof_blocks_per_packet_act"), "RO",  8, field_default(c_sdp_cep_nof_blocks_per_packet)),
+      (field_name_pad("nof_destinations_max"),      "RO",  8, field_default(1)),
+      (field_name_pad("nof_destinations_act"),      "RO",  8, field_default(1)),
+      (field_name_pad("nof_destinations"),          "RW",  8, field_default(1)),
+
+      (field_name_pad("udp_destination_port_15"),   "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_14"),   "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_13"),   "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_12"),   "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_11"),   "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_10"),   "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_9"),    "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_8"),    "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_7"),    "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_6"),    "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_5"),    "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_4"),    "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_3"),    "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_2"),    "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_1"),    "RW", 16, field_default(0)),
+      (field_name_pad("udp_destination_port_0"),    "RW", 16, field_default(0)),
+
+      (field_name_pad("ip_destination_address_15"), "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_14"), "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_13"), "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_12"), "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_11"), "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_10"), "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_9"),  "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_8"),  "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_7"),  "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_6"),  "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_5"),  "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_4"),  "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_3"),  "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_2"),  "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_1"),  "RW", 32, field_default(0)),
+      (field_name_pad("ip_destination_address_0"),  "RW", 32, field_default(0)),
+
+      (field_name_pad("eth_destination_mac_15"),    "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_14"),    "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_13"),    "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_12"),    "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_11"),    "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_10"),    "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_9"),     "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_8"),     "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_7"),     "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_6"),     "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_5"),     "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_4"),     "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_3"),     "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_2"),     "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_1"),     "RW", 48, field_default(0)),
+      (field_name_pad("eth_destination_mac_0"),     "RW", 48, field_default(0)) );
+
+  signal mm_fields_in  : std_logic_vector(field_slv_in_len(c_field_arr) - 1 downto 0);
+  signal mm_fields_out : std_logic_vector(field_slv_out_len(c_field_arr) - 1 downto 0);
+
+  signal destinations_info_rd : t_sdp_bdo_destinations_info;
+  signal destinations_info_wr : t_sdp_bdo_destinations_info;
+
+  signal nof_destinations_act      : natural := 1;
+  signal nof_blocks_per_packet_act : natural := c_sdp_cep_nof_blocks_per_packet;
+begin
+  destinations_info <= destinations_info_rd;
+
+  p_destinations_info_rd : process(destinations_info_wr,
+                                   nof_destinations_act,
+                                   nof_blocks_per_packet_act)
+  begin
+    -- default write assign all fields
+    destinations_info_rd <= destinations_info_wr;
+
+    -- overrule the read only fields
+    destinations_info_rd.nof_destinations_act      <= nof_destinations_act;
+    destinations_info_rd.nof_destinations_max      <= c_sdp_bdo_nof_destinations_max;
+    destinations_info_rd.nof_blocks_per_packet_act <= nof_blocks_per_packet_act;
+  end process;
+
+  u_mm_fields: entity mm_lib.mm_fields
+  generic map(
+    g_use_slv_in_val  => false,  -- use FALSE to save logic when always slv_in_val='1'
+    g_field_arr       => c_field_arr
+  )
+  port map (
+    mm_clk     => mm_clk,
+    mm_rst     => mm_rst,
+
+    mm_mosi    => reg_copi,
+    mm_miso    => reg_cipo,
+
+    slv_clk    => dp_clk,
+    slv_rst    => dp_rst,
+
+    slv_in     => mm_fields_in,
+    slv_in_val => '1',
+
+    slv_out    => mm_fields_out
+  );
+
+  -- add "RO" fields to mm_fields
+  mm_fields_in(field_hi(c_field_arr, "nof_destinations_act") downto field_lo(c_field_arr, "nof_destinations_act")) <= to_uvec(destinations_info_rd.nof_destinations_act, 8);
+  mm_fields_in(field_hi(c_field_arr, "nof_destinations_max") downto field_lo(c_field_arr, "nof_destinations_max")) <= to_uvec(destinations_info_rd.nof_destinations_max, 8);
+  mm_fields_in(field_hi(c_field_arr, "nof_blocks_per_packet_act") downto field_lo(c_field_arr, "nof_blocks_per_packet_act")) <= to_uvec(destinations_info_rd.nof_blocks_per_packet_act, 8);
+
+  -- get "RW" fields from mm_fields
+  destinations_info_wr.eth_destination_mac_arr(0)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_0") downto field_lo(c_field_arr, "eth_destination_mac_0"));
+  destinations_info_wr.eth_destination_mac_arr(1)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_1") downto field_lo(c_field_arr, "eth_destination_mac_1"));
+  destinations_info_wr.eth_destination_mac_arr(2)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_2") downto field_lo(c_field_arr, "eth_destination_mac_2"));
+  destinations_info_wr.eth_destination_mac_arr(3)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_3") downto field_lo(c_field_arr, "eth_destination_mac_3"));
+  destinations_info_wr.eth_destination_mac_arr(4)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_4") downto field_lo(c_field_arr, "eth_destination_mac_4"));
+  destinations_info_wr.eth_destination_mac_arr(5)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_5") downto field_lo(c_field_arr, "eth_destination_mac_5"));
+  destinations_info_wr.eth_destination_mac_arr(6)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_6") downto field_lo(c_field_arr, "eth_destination_mac_6"));
+  destinations_info_wr.eth_destination_mac_arr(7)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_7") downto field_lo(c_field_arr, "eth_destination_mac_7"));
+  destinations_info_wr.eth_destination_mac_arr(8)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_8") downto field_lo(c_field_arr, "eth_destination_mac_8"));
+  destinations_info_wr.eth_destination_mac_arr(9)  <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_9") downto field_lo(c_field_arr, "eth_destination_mac_9"));
+  destinations_info_wr.eth_destination_mac_arr(10) <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_10") downto field_lo(c_field_arr, "eth_destination_mac_10"));
+  destinations_info_wr.eth_destination_mac_arr(11) <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_11") downto field_lo(c_field_arr, "eth_destination_mac_11"));
+  destinations_info_wr.eth_destination_mac_arr(12) <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_12") downto field_lo(c_field_arr, "eth_destination_mac_12"));
+  destinations_info_wr.eth_destination_mac_arr(13) <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_13") downto field_lo(c_field_arr, "eth_destination_mac_13"));
+  destinations_info_wr.eth_destination_mac_arr(14) <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_14") downto field_lo(c_field_arr, "eth_destination_mac_14"));
+  destinations_info_wr.eth_destination_mac_arr(15) <= mm_fields_out(field_hi(c_field_arr, "eth_destination_mac_15") downto field_lo(c_field_arr, "eth_destination_mac_15"));
+
+  destinations_info_wr.ip_destination_address_arr(0)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_0") downto field_lo(c_field_arr, "ip_destination_address_0"));
+  destinations_info_wr.ip_destination_address_arr(1)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_1") downto field_lo(c_field_arr, "ip_destination_address_1"));
+  destinations_info_wr.ip_destination_address_arr(2)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_2") downto field_lo(c_field_arr, "ip_destination_address_2"));
+  destinations_info_wr.ip_destination_address_arr(3)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_3") downto field_lo(c_field_arr, "ip_destination_address_3"));
+  destinations_info_wr.ip_destination_address_arr(4)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_4") downto field_lo(c_field_arr, "ip_destination_address_4"));
+  destinations_info_wr.ip_destination_address_arr(5)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_5") downto field_lo(c_field_arr, "ip_destination_address_5"));
+  destinations_info_wr.ip_destination_address_arr(6)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_6") downto field_lo(c_field_arr, "ip_destination_address_6"));
+  destinations_info_wr.ip_destination_address_arr(7)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_7") downto field_lo(c_field_arr, "ip_destination_address_7"));
+  destinations_info_wr.ip_destination_address_arr(8)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_8") downto field_lo(c_field_arr, "ip_destination_address_8"));
+  destinations_info_wr.ip_destination_address_arr(9)  <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_9") downto field_lo(c_field_arr, "ip_destination_address_9"));
+  destinations_info_wr.ip_destination_address_arr(10) <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_10") downto field_lo(c_field_arr, "ip_destination_address_10"));
+  destinations_info_wr.ip_destination_address_arr(11) <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_11") downto field_lo(c_field_arr, "ip_destination_address_11"));
+  destinations_info_wr.ip_destination_address_arr(12) <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_12") downto field_lo(c_field_arr, "ip_destination_address_12"));
+  destinations_info_wr.ip_destination_address_arr(13) <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_13") downto field_lo(c_field_arr, "ip_destination_address_13"));
+  destinations_info_wr.ip_destination_address_arr(14) <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_14") downto field_lo(c_field_arr, "ip_destination_address_14"));
+  destinations_info_wr.ip_destination_address_arr(15) <= mm_fields_out(field_hi(c_field_arr, "ip_destination_address_15") downto field_lo(c_field_arr, "ip_destination_address_15"));
+
+  destinations_info_wr.udp_destination_port_arr(0)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_0") downto field_lo(c_field_arr, "udp_destination_port_0"));
+  destinations_info_wr.udp_destination_port_arr(1)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_1") downto field_lo(c_field_arr, "udp_destination_port_1"));
+  destinations_info_wr.udp_destination_port_arr(2)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_2") downto field_lo(c_field_arr, "udp_destination_port_2"));
+  destinations_info_wr.udp_destination_port_arr(3)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_3") downto field_lo(c_field_arr, "udp_destination_port_3"));
+  destinations_info_wr.udp_destination_port_arr(4)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_4") downto field_lo(c_field_arr, "udp_destination_port_4"));
+  destinations_info_wr.udp_destination_port_arr(5)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_5") downto field_lo(c_field_arr, "udp_destination_port_5"));
+  destinations_info_wr.udp_destination_port_arr(6)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_6") downto field_lo(c_field_arr, "udp_destination_port_6"));
+  destinations_info_wr.udp_destination_port_arr(7)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_7") downto field_lo(c_field_arr, "udp_destination_port_7"));
+  destinations_info_wr.udp_destination_port_arr(8)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_8") downto field_lo(c_field_arr, "udp_destination_port_8"));
+  destinations_info_wr.udp_destination_port_arr(9)  <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_9") downto field_lo(c_field_arr, "udp_destination_port_9"));
+  destinations_info_wr.udp_destination_port_arr(10) <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_10") downto field_lo(c_field_arr, "udp_destination_port_10"));
+  destinations_info_wr.udp_destination_port_arr(11) <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_11") downto field_lo(c_field_arr, "udp_destination_port_11"));
+  destinations_info_wr.udp_destination_port_arr(12) <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_12") downto field_lo(c_field_arr, "udp_destination_port_12"));
+  destinations_info_wr.udp_destination_port_arr(13) <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_13") downto field_lo(c_field_arr, "udp_destination_port_13"));
+  destinations_info_wr.udp_destination_port_arr(14) <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_14") downto field_lo(c_field_arr, "udp_destination_port_14"));
+  destinations_info_wr.udp_destination_port_arr(15) <= mm_fields_out(field_hi(c_field_arr, "udp_destination_port_15") downto field_lo(c_field_arr, "udp_destination_port_15"));
+
+  destinations_info_wr.nof_destinations <= to_uint(mm_fields_out(field_hi(c_field_arr, "nof_destinations") downto field_lo(c_field_arr, "nof_destinations")));
+
+  -- Register the read only actual values, to ease timing closure
+  p_dp_clk : process(dp_clk)
+    constant c_nof_blocks_per_packet_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max) :=
+      func_sdp_bdo_nof_blocks_per_packet_look_up_table;
+  begin
+    if rising_edge(dp_clk) then
+      nof_destinations_act <= func_sdp_bdo_parse_nof_destinations(destinations_info_wr.nof_destinations);
+      nof_blocks_per_packet_act <= c_nof_blocks_per_packet_arr(nof_destinations_act);
+    end if;
+  end process;
+end str;
diff --git a/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_multiple_destinations.vhd b/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_multiple_destinations.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..33e2fc12e0ec47c425ba4980fb1c91ce752ca95c
--- /dev/null
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_multiple_destinations.vhd
@@ -0,0 +1,315 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2023
+-- 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:
+--   Construct beamformer data output (BDO) payloads for multiple destinations.
+-- Description:
+-- * Get N_destinations from sdp_bdo_destinations_reg.
+-- * Merge, reorder and unmerge beamlet data for N_destinations > 1 from:
+--       (int8) [t] [N_blocks_per_packet][S_sub_bf / N_destinations] [N_pol_bf][N_complex]
+--     to:
+--       (int8) [t] [S_sub_bf / N_destinations][N_blocks_per_packet] [N_pol_bf][N_complex]
+--
+--   . where (int8) [N_pol_bf][N_complex] = c_sdp_W_dual_pol_beamlet = 32b
+--     dual polarization beamlet word
+--   . where N_destinations packets together transport the S_sub_bf beamlets.
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L4+SDPFW+Decision%3A+Multiple+beamlet+output+destinations
+--
+-------------------------------------------------------------------------------
+
+library IEEE, common_lib, dp_lib, reorder_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 dp_lib.dp_stream_pkg.all;
+  use reorder_lib.reorder_pkg.all;
+  use work.sdp_pkg.all;
+  use work.sdp_bdo_pkg.all;
+
+entity sdp_bdo_multiple_destinations is
+  generic (
+    g_beamset_id    : natural := 0;
+    g_use_transpose : boolean := false
+  );
+  port (
+    mm_clk   : in  std_logic;
+    mm_rst   : in  std_logic;
+
+    dp_clk   : in  std_logic;
+    dp_rst   : in  std_logic;
+
+    reg_destinations_copi : in  t_mem_copi;
+    reg_destinations_cipo : out t_mem_cipo;
+
+    destinations_info     : out t_sdp_bdo_destinations_info;
+
+    snk_in   : in  t_dp_sosi;
+    src_out  : out t_dp_sosi
+  );
+end sdp_bdo_multiple_destinations;
+
+architecture str of sdp_bdo_multiple_destinations is
+  constant c_beamlet_index  : natural := g_beamset_id * c_sdp_S_sub_bf;
+
+  -- Reorder c_nof_ch = c_nof_ch_sel = c_nof_ch_in
+  constant c_reorder_nof_blocks_max : natural := c_sdp_bdo_reorder_nof_blocks_max;  -- = 16
+  constant c_reorder_nof_blocks_w   : natural := ceil_log2(c_reorder_nof_blocks_max + 1);
+  constant c_reorder_nof_ch_max     : natural := c_reorder_nof_blocks_max *
+                                                 c_sdp_nof_beamlets_per_block *
+                                                 c_sdp_nof_words_per_beamlet;  -- = 7808
+
+  -- Look up table constants as function of N_destinations
+  constant c_m                                            : natural := c_sdp_bdo_nof_destinations_max;  -- 16
+  constant c_nof_blocks_per_packet_arr                    : t_natural_arr(1 to c_m) := func_sdp_bdo_nof_blocks_per_packet_look_up_table;
+  constant c_reorder_nof_blocks_arr                       : t_natural_arr(1 to c_m) := func_sdp_bdo_reorder_nof_blocks_look_up_table;
+  constant c_reorder_nof_ch_arr                           : t_natural_arr(1 to c_m) := func_sdp_bdo_reorder_nof_ch_look_up_table;
+  constant c_nof_beamlets_per_block_first_destination_arr : t_natural_arr(1 to c_m) := func_sdp_sdo_nof_beamlets_per_block_first_destination_look_up_table;
+  constant c_nof_beamlets_per_block_last_destination_arr  : t_natural_arr(1 to c_m) := func_sdp_sdo_nof_beamlets_per_block_last_destination_look_up_table;
+  constant c_nof_ch_per_packet_first_destination_arr      : t_natural_arr(1 to c_m) := func_sdp_sdo_nof_ch_per_packet_first_destination_look_up_table;
+  constant c_nof_ch_per_packet_last_destination_arr       : t_natural_arr(1 to c_m) := func_sdp_sdo_nof_ch_per_packet_last_destination_look_up_table;
+
+  constant c_beamlet_index_per_destination_mat : t_natural_matrix(1 to c_m, 0 to c_m - 1) :=
+                                                   func_sdp_sdo_beamlet_index_per_destination_look_up_matrix;
+
+  constant c_nof_ch_per_packet_max  : natural := largest(c_nof_ch_per_packet_first_destination_arr);
+  constant c_nof_ch_per_packet_w    : natural := ceil_log2(c_nof_ch_per_packet_max + 1);
+
+  signal i_destinations_info     : t_sdp_bdo_destinations_info;
+
+  -- Dynamic merge, reorder, unmerge packet sizes
+  -- . default use values for N_destinations = 1
+  signal nof_blocks_per_packet                    : natural := c_nof_blocks_per_packet_arr(1);
+  signal reorder_nof_blocks                       : natural := c_reorder_nof_blocks_arr(1);
+  signal reorder_nof_blocks_slv                   : std_logic_vector(c_reorder_nof_blocks_w - 1 downto 0);
+  signal reorder_nof_ch                           : natural := c_reorder_nof_ch_arr(1);
+  signal nof_beamlets_per_block_first_destination : natural := c_nof_beamlets_per_block_first_destination_arr(1);
+  signal nof_beamlets_per_block_last_destination  : natural := c_nof_beamlets_per_block_last_destination_arr(1);
+  signal nof_beamlets_per_block                   : natural;
+  signal nof_ch_per_packet_first_destination      : natural := c_nof_ch_per_packet_first_destination_arr(1);
+  signal nof_ch_per_packet_last_destination       : natural := c_nof_ch_per_packet_last_destination_arr(1);
+  signal nof_ch_per_packet                        : natural;
+  signal nof_ch_per_packet_slv                    : std_logic_vector(c_nof_ch_per_packet_w - 1 downto 0);
+
+  -- . default use values for N_destinations = 1 and destination index = 0
+  signal beamlet_index_per_destination_bset_0     : natural := c_beamlet_index_per_destination_mat(1, 0);
+  signal beamlet_index_per_destination            : natural := c_beamlet_index + c_beamlet_index_per_destination_mat(1, 0);
+
+  signal select_copi            : t_mem_copi := c_mem_copi_rst;
+  signal select_cipo            : t_mem_cipo := c_mem_cipo_rst;
+  signal r_identity             : t_reorder_identity;
+  signal d_identity             : t_reorder_identity;
+  signal r_transpose            : t_reorder_transpose;
+  signal d_transpose            : t_reorder_transpose;
+
+  signal merge_src_out          : t_dp_sosi;
+  signal merge_word             : t_sdp_dual_pol_beamlet_in_word;
+  signal reorder_src_out        : t_dp_sosi;
+  signal reorder_word           : t_sdp_dual_pol_beamlet_in_word;
+  signal reorder_busy           : std_logic;
+  signal unmerge_src_out        : t_dp_sosi;
+  signal unmerge_word           : t_sdp_dual_pol_beamlet_in_word;
+begin
+  src_out <= unmerge_src_out;
+  destinations_info <= i_destinations_info;
+
+  -----------------------------------------------------------------------------
+  -- Multiple destinations info register
+  -----------------------------------------------------------------------------
+  -- Use dynamic sizes for beamlet data output to multiple destination.
+  u_sdp_bdo_destinations_reg : entity work.sdp_bdo_destinations_reg
+    port map (
+      -- Clocks and reset
+      mm_clk   => mm_clk,
+      mm_rst   => mm_rst,
+
+      dp_clk   => dp_clk,
+      dp_rst   => dp_rst,
+
+      reg_copi => reg_destinations_copi,
+      reg_cipo => reg_destinations_cipo,
+
+      -- sdp info
+      destinations_info => i_destinations_info
+    );
+
+  -----------------------------------------------------------------------------
+  -- Multiple destinations info look up values
+  -----------------------------------------------------------------------------
+  -- Pipeline values from look up tables to ease timing closure
+  p_pipeline : process(dp_clk)
+    variable v_DN : natural;  -- number of destinations
+    variable v_DI : natural;  -- destination index
+  begin
+    if rising_edge(dp_clk) then
+      v_DN := i_destinations_info.nof_destinations_act;
+      nof_blocks_per_packet                    <= i_destinations_info.nof_blocks_per_packet_act;
+      reorder_nof_blocks                       <= c_reorder_nof_blocks_arr(v_DN);
+      reorder_nof_ch                           <= c_reorder_nof_ch_arr(v_DN);
+      nof_beamlets_per_block_first_destination <= c_nof_beamlets_per_block_first_destination_arr(v_DN);
+      nof_beamlets_per_block_last_destination  <= c_nof_beamlets_per_block_last_destination_arr(v_DN);
+      v_DI := 0;
+      beamlet_index_per_destination_bset_0     <= c_beamlet_index_per_destination_mat(v_DN, v_DI);
+      beamlet_index_per_destination            <= c_beamlet_index + beamlet_index_per_destination_bset_0;
+    end if;
+  end process;
+
+  nof_beamlets_per_block <= nof_beamlets_per_block_first_destination;
+  nof_ch_per_packet <= nof_ch_per_packet_first_destination;
+
+  -----------------------------------------------------------------------------
+  -- dp_packet_merge
+  -----------------------------------------------------------------------------
+  -- Use slv in port map to avoid vcom-1436: Actual expression (function call
+  -- "TO_UVEC") of formal "nof_pkt" is not globally static.
+  reorder_nof_blocks_slv <= to_uvec(reorder_nof_blocks, c_reorder_nof_blocks_w);
+
+  u_dp_packet_merge : entity dp_lib.dp_packet_merge
+    generic map(
+      g_use_ready     => false,  -- no flow control
+      g_nof_pkt       => c_reorder_nof_blocks_max,
+      g_bsn_increment => 1
+    )
+    port map(
+      rst     => dp_rst,
+      clk     => dp_clk,
+
+      nof_pkt     => reorder_nof_blocks_slv,
+      nof_pkt_out => open,  -- Valid at src_out.sop
+
+      snk_in  => snk_in,
+      src_out => merge_src_out
+    );
+
+  -- Debug signals for view in Wave window
+  merge_word <= unpack_data(merge_src_out.data(c_sdp_W_dual_pol_beamlet - 1 downto 0));
+
+  -----------------------------------------------------------------------------
+  -- reorder_col_select
+  -- . See tb_reorder_col_select_all.vhd for how to control col_select_copi /
+  --   cipo with p_reorder_identity or p_reorder_transpose.
+  -----------------------------------------------------------------------------
+  u_reorder_col_select : entity reorder_lib.reorder_col_select
+    generic map (
+      g_dsp_data_w  => c_sdp_W_dual_pol_beamlet / c_nof_complex,  -- = 32b / 2
+      g_nof_ch_in   => c_reorder_nof_ch_max,
+      g_nof_ch_sel  => c_reorder_nof_ch_max,
+      g_use_complex => false
+    )
+    port map (
+      dp_rst          => dp_rst,
+      dp_clk          => dp_clk,
+
+      reorder_busy    => reorder_busy,
+
+      -- Dynamic reorder block size control input
+      nof_ch_in       => reorder_nof_ch,
+      nof_ch_sel      => reorder_nof_ch,
+
+      -- Captured reorder block size control used for output_sosi
+      output_nof_ch_in  => open,
+      output_nof_ch_sel => open,
+
+      -- Memory Mapped
+      col_select_mosi => select_copi,
+      col_select_miso => select_cipo,
+
+      -- Streaming
+      input_sosi      => merge_src_out,
+      output_sosi     => reorder_src_out
+    );
+
+  -- Debug signals for view in Wave window
+  reorder_word <= unpack_data(reorder_src_out.data(c_sdp_W_dual_pol_beamlet - 1 downto 0));
+
+  -- Use synchronous reset in d signals
+  p_dp_clk : process(dp_clk)
+  begin
+    if rising_edge(dp_clk) then
+      r_identity  <= d_identity;
+      r_transpose <= d_transpose;
+    end if;
+  end process;
+
+  -- Pass on beamlet data in original order or in transposed order
+  select_copi <= r_transpose.select_copi when g_use_transpose else r_identity.select_copi;
+
+  p_reorder_identity : process(dp_rst, select_cipo, reorder_nof_ch, r_identity)
+    variable v : t_reorder_identity;
+  begin
+    if select_cipo.waitrequest = '0' then
+      -- Read from reorder_col_select page
+      v := func_reorder_identity(reorder_nof_ch, r_identity);
+    else
+      -- No read, new reorder_col_select page not available yet
+      v := c_reorder_identity_rst;
+    end if;
+    -- Synchronous reset
+    if dp_rst = '1' then
+      v := c_reorder_identity_rst;
+    end if;
+    d_identity <= v;
+  end process;
+
+  p_reorder_transpose : process(dp_rst, select_cipo, nof_blocks_per_packet, nof_beamlets_per_block, r_transpose)
+    variable v : t_reorder_transpose;
+  begin
+    if select_cipo.waitrequest = '0' then
+      -- Read from reorder_col_select page
+      v := func_reorder_transpose(nof_blocks_per_packet, nof_beamlets_per_block, r_transpose);
+    else
+      -- No read, new reorder_col_select page not available yet
+      v := c_reorder_transpose_rst;
+    end if;
+    -- Synchronous reset
+    if dp_rst = '1' then
+      v := c_reorder_transpose_rst;
+    end if;
+    d_transpose <= v;
+  end process;
+
+  -----------------------------------------------------------------------------
+  -- dp_packet_unmerge for N_destinations
+  -----------------------------------------------------------------------------
+  nof_ch_per_packet_slv <= to_uvec(nof_ch_per_packet, c_nof_ch_per_packet_w);
+
+  u_dp_packet_unmerge : entity dp_lib.dp_packet_unmerge
+    generic map (
+      g_use_ready     => false,  -- no flow control
+      g_nof_pkt       => c_reorder_nof_blocks_max,
+      g_pkt_len       => c_nof_ch_per_packet_max,
+      g_bsn_increment => 1
+    )
+    port map (
+      rst     => dp_rst,
+      clk     => dp_clk,
+
+      pkt_len     => nof_ch_per_packet_slv,
+      pkt_len_out => open,  -- Valid at src_out.sop
+
+      snk_in      => reorder_src_out,
+      src_out     => unmerge_src_out
+    );
+
+  -- Debug signals for view in Wave window
+  unmerge_word <= unpack_data(unmerge_src_out.data(c_sdp_W_dual_pol_beamlet - 1 downto 0));
+end str;
diff --git a/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_one_destination.vhd b/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_one_destination.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..989cfe606500eec2bce111870a7e13067d3a9def
--- /dev/null
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_one_destination.vhd
@@ -0,0 +1,193 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2023
+-- 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:
+--   Construct beamformer data output (BDO) payload for one destination.
+-- Description:
+--   . Merge and reorder beamlet data for one destination from:
+--       (int8) [t] [N_blocks_per_packet][S_sub_bf] [N_pol_bf][N_complex]
+--     to:
+--       (int8) [t] [S_sub_bf][N_blocks_per_packet] [N_pol_bf][N_complex]
+--
+--   . where (int8) [N_pol_bf][N_complex] = c_sdp_W_dual_pol_beamlet = 32b
+--     dual polarization beamlet word
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L4+SDPFW+Decision%3A+Multiple+beamlet+output+destinations
+--
+-------------------------------------------------------------------------------
+
+library IEEE, common_lib, dp_lib, reorder_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 dp_lib.dp_stream_pkg.all;
+  use reorder_lib.reorder_pkg.all;
+  use work.sdp_pkg.all;
+  use work.sdp_bdo_pkg.all;
+
+entity sdp_bdo_one_destination is
+  generic (
+    g_use_transpose : boolean := false
+  );
+  port (
+    dp_clk   : in  std_logic;
+    dp_rst   : in  std_logic;
+
+    snk_in   : in  t_dp_sosi;
+    src_out  : out t_dp_sosi
+  );
+end sdp_bdo_one_destination;
+
+architecture str of sdp_bdo_one_destination is
+  -- Reorder c_nof_ch = c_nof_ch_sel = c_nof_ch_in
+  constant c_nof_blocks_per_packet  : natural := c_sdp_cep_nof_blocks_per_packet;  -- = 4
+  constant c_nof_beamlets_per_block : natural := c_sdp_S_sub_bf;  -- = 488 dual pol beamlets
+  constant c_nof_words_per_beamlet  : natural := 1;  -- 1 dual pol beamlet data per 32b word
+  constant c_nof_ch                 : natural := c_nof_blocks_per_packet * c_nof_beamlets_per_block * c_nof_words_per_beamlet;  -- = 1952
+
+  -- Use c_transpose_indices and c_transpose_indices_inv for debug view in Objects window.
+  -- Use c_transpose_indices for func_reorder_transpose() in this sdp_bdo_one_destination,
+  -- a tb can then use c_transpose_indices_inv to undo the transpose.
+  constant c_transpose_indices     : t_natural_arr(0 to c_nof_ch - 1) :=
+                                       func_reorder_transpose_indices(c_nof_blocks_per_packet,
+                                                                      c_nof_beamlets_per_block,
+                                                                      c_nof_words_per_beamlet);
+  constant c_transpose_indices_inv : t_natural_arr(0 to c_nof_ch - 1) :=
+                                       func_reorder_transpose_indices(c_nof_beamlets_per_block,
+                                                                      c_nof_blocks_per_packet,
+                                                                      c_nof_words_per_beamlet);
+
+  -- Dynamic reorder block size control input
+  -- . The data consists of 1 word = 1 ch, because 1 word contains 1 dual pol beamlet.
+  -- . The input packet has c_nof_ch of data per packet.
+  -- . The transposed output packet will have blocks with c_nof_blocks_per_packet
+  --   data per block and c_nof_beamlets_per_block blocks per packet.
+  signal select_copi           : t_mem_copi := c_mem_copi_rst;
+  signal select_cipo           : t_mem_cipo := c_mem_cipo_rst;
+  signal r_identity            : t_reorder_identity;
+  signal d_identity            : t_reorder_identity;
+  signal r_transpose           : t_reorder_transpose;
+  signal d_transpose           : t_reorder_transpose;
+
+  signal merge_src_out         : t_dp_sosi;
+  signal merge_word            : t_sdp_dual_pol_beamlet_in_word;
+  signal reorder_src_out       : t_dp_sosi;
+  signal reorder_word          : t_sdp_dual_pol_beamlet_in_word;
+  signal reorder_busy          : std_logic;
+begin
+  src_out <= reorder_src_out;
+
+  -----------------------------------------------------------------------------
+  -- dp_packet_merge
+  -----------------------------------------------------------------------------
+  u_dp_packet_merge : entity dp_lib.dp_packet_merge
+    generic map(
+      g_use_ready     => false,  -- no flow control
+      g_nof_pkt       => c_nof_blocks_per_packet,
+      g_bsn_increment => 1
+    )
+    port map(
+      rst     => dp_rst,
+      clk     => dp_clk,
+      snk_in  => snk_in,
+      src_out => merge_src_out
+    );
+
+  -- Debug signals for view in Wave window
+  merge_word <= unpack_data(merge_src_out.data(c_sdp_W_dual_pol_beamlet - 1 downto 0));
+
+  -----------------------------------------------------------------------------
+  -- reorder_col_select
+  -- . See tb_reorder_col_select_all.vhd for how to control col_select_copi /
+  --   cipo with p_reorder_identity or p_reorder_transpose.
+  -----------------------------------------------------------------------------
+  u_reorder_col_select : entity reorder_lib.reorder_col_select
+    generic map (
+      g_dsp_data_w  => c_sdp_W_dual_pol_beamlet / c_nof_complex,  -- = 32b / 2
+      g_nof_ch_in   => c_nof_ch,
+      g_nof_ch_sel  => c_nof_ch,
+      g_use_complex => false
+    )
+    port map (
+      dp_rst          => dp_rst,
+      dp_clk          => dp_clk,
+
+      reorder_busy    => reorder_busy,
+
+      -- Memory Mapped
+      col_select_mosi => select_copi,
+      col_select_miso => select_cipo,
+
+      -- Streaming
+      input_sosi      => merge_src_out,
+      output_sosi     => reorder_src_out
+    );
+
+  -- Debug signals for view in Wave window
+  reorder_word <= unpack_data(reorder_src_out.data(c_sdp_W_dual_pol_beamlet - 1 downto 0));
+
+  -- Use synchronous reset in d signals
+  p_dp_clk : process(dp_clk)
+  begin
+    if rising_edge(dp_clk) then
+      r_identity  <= d_identity;
+      r_transpose <= d_transpose;
+    end if;
+  end process;
+
+  -- Pass on beamlet data in original order or in transposed order
+  select_copi <= r_transpose.select_copi when g_use_transpose else r_identity.select_copi;
+
+  p_reorder_identity : process(dp_rst, select_cipo, r_identity)
+    variable v : t_reorder_identity;
+  begin
+    if select_cipo.waitrequest = '0' then
+      -- Read from reorder_col_select page
+      v := func_reorder_identity(c_nof_ch, r_identity);
+    else
+      -- No read, new reorder_col_select page not available yet
+      v := c_reorder_identity_rst;
+    end if;
+    -- Synchronous reset
+    if dp_rst = '1' then
+      v := c_reorder_identity_rst;
+    end if;
+    d_identity <= v;
+  end process;
+
+  p_reorder_transpose : process(dp_rst, select_cipo, r_transpose)
+    variable v : t_reorder_transpose;
+  begin
+    if select_cipo.waitrequest = '0' then
+      -- Read from reorder_col_select page
+      v := func_reorder_transpose(c_nof_blocks_per_packet, c_nof_beamlets_per_block, r_transpose);
+    else
+      -- No read, new reorder_col_select page not available yet
+      v := c_reorder_transpose_rst;
+    end if;
+    -- Synchronous reset
+    if dp_rst = '1' then
+      v := c_reorder_transpose_rst;
+    end if;
+    d_transpose <= v;
+  end process;
+end str;
diff --git a/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_pkg.vhd b/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_pkg.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..098d853c91e7c3dac21ceae9d02aaecf8c7e9680
--- /dev/null
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_bdo_pkg.vhd
@@ -0,0 +1,295 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2023
+-- 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 sdp beamlet data output (BDO) specific constants.
+-- Description: See [1]
+-- References:
+-- . [1] https://support.astron.nl/confluence/display/L2M/L4+SDPFW+Decision%3A+Multiple+beamlet+output+destinations
+-------------------------------------------------------------------------------
+library IEEE, common_lib;
+use IEEE.std_logic_1164.all;
+use common_lib.common_pkg.all;
+use work.sdp_pkg.all;
+
+package sdp_bdo_pkg is
+  -- Beamlet data output (BDO) for multiple destinations
+  constant c_sdp_bdo_nof_destinations_max   : natural := 16;
+  constant c_sdp_bdo_reorder_nof_blocks_max : natural := largest(16, c_sdp_cep_nof_blocks_per_packet);
+
+  constant c_sdp_bdo_destinations_info_nof_hdr_fields : natural := c_sdp_bdo_nof_destinations_max * 3 + 4;  -- = 52 fields
+
+  type t_sdp_bdo_destinations_info is record
+    eth_destination_mac_arr     : t_slv_48_arr(c_sdp_bdo_nof_destinations_max - 1 downto 0);
+    ip_destination_address_arr  : t_slv_32_arr(c_sdp_bdo_nof_destinations_max - 1 downto 0);
+    udp_destination_port_arr    : t_slv_16_arr(c_sdp_bdo_nof_destinations_max - 1 downto 0);
+    nof_destinations            : natural;
+    nof_destinations_act        : natural;
+    nof_destinations_max        : natural;
+    nof_blocks_per_packet_act   : natural;
+  end record;
+
+  constant t_sdp_bdo_destinations_info_rst : t_sdp_bdo_destinations_info :=
+    ( (others => (others => '0')),
+      (others => (others => '0')),
+      (others => (others => '0')),
+      1,
+      1,
+      c_sdp_bdo_nof_destinations_max,
+      c_sdp_cep_nof_blocks_per_packet);
+
+  -- Parse user input to determine actual nof_destinations
+  function func_sdp_bdo_parse_nof_destinations(nof_destinations : natural) return natural;
+
+  -- Use functions that return look up tables to precalculate the values as
+  -- constant arrays
+  -- . One ch (channel) = one 32b word = one dual polarization beamlet (Xre, Xim, Yre, Yim)
+
+  -- . Look up table arrays for: t_natural_arr(1 to c_sdp_bdo_nof_destinations_max)
+  function func_sdp_bdo_nof_blocks_per_packet_look_up_table return t_natural_arr;
+  function func_sdp_bdo_reorder_nof_blocks_look_up_table return t_natural_arr;
+  function func_sdp_bdo_reorder_nof_ch_look_up_table return t_natural_arr;
+  function func_sdp_sdo_nof_beamlets_per_block_first_destination_look_up_table return t_natural_arr;
+  function func_sdp_sdo_nof_beamlets_per_block_last_destination_look_up_table return t_natural_arr;
+  function func_sdp_sdo_nof_ch_per_packet_first_destination_look_up_table return t_natural_arr;
+  function func_sdp_sdo_nof_ch_per_packet_last_destination_look_up_table return t_natural_arr;
+
+  -- Look up table matrix for:
+  --   t_natural_matrix(1 to c_sdp_bdo_nof_destinations_max,      -- N_destinations
+  --                    0 to c_sdp_bdo_nof_destinations_max - 1)  -- destination index
+  function func_sdp_sdo_beamlet_index_per_destination_look_up_matrix return t_natural_matrix;
+end package sdp_bdo_pkg;
+
+package body sdp_bdo_pkg is
+  function func_sdp_bdo_parse_nof_destinations(nof_destinations : natural) return natural is
+  begin
+    -- Parse input nof_destinations value
+    if nof_destinations = 0 then
+      return 1;
+    elsif nof_destinations > c_sdp_bdo_nof_destinations_max then
+      return c_sdp_bdo_nof_destinations_max;
+    else
+      return nof_destinations;
+    end if;
+  end func_sdp_bdo_parse_nof_destinations;
+
+  function func_sdp_bdo_nof_blocks_per_packet_look_up_table return t_natural_arr is
+    variable v_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max);
+  begin
+    -- Determine nof_blocks_per_packet as function of number of destinations
+    -- DN.
+    -- . With 1 destination c_sdp_cep_nof_blocks_per_packet = 4 can fit in a
+    --   jumbo frame.
+    -- . With DN destinations DN * c_sdp_cep_nof_blocks_per_packet can fit in
+    --   a jumbo frame, because the number of beamlets per destination reduces
+    --   by DN.
+    --     DN = 1:16 --> 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64
+    -- . In total there are maximum c_sdp_bdo_reorder_nof_blocks_max = 16
+    --   blocks to distribute over DN destinations.
+    -- . Taking smallest yields the actual maximum number of blocks per packet
+    --   per destination, as function of number of destinations DN:
+    --     DN = 1:16 --> 4, 8, 12, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16
+    for DN in 1 to c_sdp_bdo_nof_destinations_max loop
+      v_arr(DN) := smallest(c_sdp_bdo_reorder_nof_blocks_max, DN * c_sdp_cep_nof_blocks_per_packet);
+    end loop;
+    return v_arr;
+  end func_sdp_bdo_nof_blocks_per_packet_look_up_table;
+
+  function func_sdp_bdo_reorder_nof_blocks_look_up_table return t_natural_arr is
+    constant c_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max) :=
+                       func_sdp_bdo_nof_blocks_per_packet_look_up_table;
+    variable v_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max);
+  begin
+    -- Determine reorder_nof_blocks as function of number of destinations DN.
+    -- . The number of blocks per destination is given by c_arr, so the number
+    --   of blocks that need to be merged for the reorder is DN * c_arr(DN):
+    --     DN = 1:16 --> 4, 16, 15, 16, 15, 12, 14, 16, 9, 10, 11, 12, 13, 14, 15, 16
+    for DN in 1 to c_sdp_bdo_nof_destinations_max loop
+      v_arr(DN) := DN * c_arr(DN);
+    end loop;
+    return v_arr;
+  end func_sdp_bdo_reorder_nof_blocks_look_up_table;
+
+  function func_sdp_bdo_reorder_nof_ch_look_up_table return t_natural_arr is
+    constant c_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max) :=
+                       func_sdp_bdo_reorder_nof_blocks_look_up_table;
+    variable v_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max);
+  begin
+    -- Determine reorder nof_ch as function of number of destinations DN.
+    -- . The number of blocks to reorder is given by c_arr, so the number
+    --   of ch (channels = words) that need to be reordered is c_sdp_S_sub_bf
+    --   * c_arr(DN):
+    --     DN = 1:16 --> 1952, 7808, 7320, 7808, 7320, 5856, 6832, 7808
+    --                   4392, 4880, 5368, 5856, 6344, 6832, 7320, 7808
+    for DN in 1 to c_sdp_bdo_nof_destinations_max loop
+      v_arr(DN) := c_sdp_S_sub_bf * c_arr(DN);
+    end loop;
+    return v_arr;
+  end func_sdp_bdo_reorder_nof_ch_look_up_table;
+
+  function func_sdp_sdo_nof_beamlets_per_block_first_destination_look_up_table return t_natural_arr is
+    variable v_first_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max);
+  begin
+    -- Determine nof_beamlets_per_block for the first 1:DN-1 destinations, as
+    -- function of number of destinations DN.
+    -- . In total there are c_sdp_S_sub_bf = 488 dual polarization beamlets to
+    --   distribute over DN destinations, so ceil(488 / DN) yields the number of
+    --   blocks for the first 1:DN-1 destinations:
+    --     DN = 1:16 --> v_first_arr = 488, 244, 163, 122, 98, 82, 70, 61, 55, 49, 45, 41, 38, 35, 33, 31
+    for DN in 1 to c_sdp_bdo_nof_destinations_max loop
+      v_first_arr(DN) := ceil_div(c_sdp_S_sub_bf, DN);
+    end loop;
+    return v_first_arr;
+  end func_sdp_sdo_nof_beamlets_per_block_first_destination_look_up_table;
+
+  function func_sdp_sdo_nof_beamlets_per_block_last_destination_look_up_table return t_natural_arr is
+    variable v_first_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max) :=
+                             func_sdp_sdo_nof_beamlets_per_block_first_destination_look_up_table;
+    variable v_last_arr  : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max);
+  begin
+    -- Determine nof_beamlets_per_block for the last destination with index DN,
+    -- as function of number of destinations DN.
+    -- . In total there are c_sdp_S_sub_bf = 488 dual polarization beamlets to
+    --   distribute over DN destinations, so 488 - (DN-1) * ceil(488 / DN)
+    --   beamlets remain for the last destination:
+    --     DN = 1:16 --> v_first_arr = 488, 244, 163, 122, 98, 82, 70, 61, 55, 49, 45, 41, 38, 35, 33, 31
+    --     DN = 1:16 --> v_last_arr  = 488, 244, 162, 122, 96, 78, 68, 61, 48, 47, 38, 37, 32, 33, 26, 23
+    --
+    -- Remark:
+    -- . The v_last_arr may be < v_first_arr - 1, so the last destination may
+    --   contain much less beamlets than the others. In combination with
+    --   dp_packet_unmerge it is not feasible to distribute the beamlets evenly
+    --   over all destinations, using v_hi beamlets for some first destinations
+    --   and v_lo = v_hi - 1 for the remaining destinations. This is because
+    --   dp_packet_unmerge can only unmerge the same packet length for N - 1
+    --   blocks and then unmerge the remaining data in the last block until the
+    --   eop.
+    --
+    for DN in 1 to c_sdp_bdo_nof_destinations_max loop
+      v_last_arr(DN) := c_sdp_S_sub_bf - (DN - 1) * v_first_arr(DN);
+    end loop;
+    return v_last_arr;
+  end func_sdp_sdo_nof_beamlets_per_block_last_destination_look_up_table;
+
+  function func_sdp_sdo_nof_ch_per_packet_first_destination_look_up_table return t_natural_arr is
+    constant c_nof_blocks_arr   : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max) :=
+                                    func_sdp_bdo_nof_blocks_per_packet_look_up_table;
+    constant c_nof_beamlets_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max) :=
+                                    func_sdp_sdo_nof_beamlets_per_block_first_destination_look_up_table;
+    variable v_len_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max);
+  begin
+    -- Determine nof_ch per packet for the first 1:DN-1 destinations, as
+    -- function of number of destinations DN.
+    -- The packet lengths follow from c_nof_blocks_arr * c_nof_beamlets_arr:
+    -- . c_nof_blocks_arr   =    4,   8,  12,  16,  16,  16,  16,  16,  16,  16,  16,  16,  16,  16,  16,  16
+    -- . c_nof_beamlets_arr =  488, 244, 163, 122,  98,  82,  70,  61,  55,  49,  45,  41,  38,  35,  33,  31
+    -- . v_len_arr          = 1952,1952,1956,1952,1568,1312,1120, 976, 880, 784, 720, 656, 608, 560, 528, 496
+    -- . nof octets         = 7808,7808,7824,7808,6272,5248,4480,3904,3520,3136,2880,2624,2432,2240,2112,1984
+    for DN in 1 to c_sdp_bdo_nof_destinations_max loop
+      v_len_arr(DN) := c_nof_blocks_arr(DN) * c_nof_beamlets_arr(DN);
+    end loop;
+    return v_len_arr;
+  end func_sdp_sdo_nof_ch_per_packet_first_destination_look_up_table;
+
+  function func_sdp_sdo_nof_ch_per_packet_last_destination_look_up_table return t_natural_arr is
+    constant c_nof_blocks_arr   : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max) :=
+                                    func_sdp_bdo_nof_blocks_per_packet_look_up_table;
+    constant c_nof_beamlets_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max) :=
+                                    func_sdp_sdo_nof_beamlets_per_block_last_destination_look_up_table;
+    variable v_len_arr : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max);
+  begin
+    -- Determine nof_ch per packet for the first 1:DN-1 destinations, as
+    -- function of number of destinations DN.
+    -- The packet lengths follow from c_nof_blocks_arr * c_nof_beamlets_arr:
+    -- . c_nof_blocks_arr   =    4,   8,  12,  16,  16,  16,  16,  16,  16,  16,  16,  16,  16,  16,  16,  16
+    -- . c_nof_beamlets_arr =  488, 244, 162, 122,  96,  78,  68,  61,  48,  47,  38,  37,  32,  33,  26,  23
+    -- . v_len_arr          = 1952,1952,1944,1952,1536,1248,1088, 976, 768, 752, 608, 592, 512, 528, 416, 368
+    -- . nof octets         = 7808,7808,7776,7808,6144,4992,4352,3904,3072,3008,2432,2368,2048,2112,1664,1472
+    for DN in 1 to c_sdp_bdo_nof_destinations_max loop
+      v_len_arr(DN) := c_nof_blocks_arr(DN) * c_nof_beamlets_arr(DN);
+    end loop;
+    return v_len_arr;
+  end func_sdp_sdo_nof_ch_per_packet_last_destination_look_up_table;
+
+  function func_sdp_sdo_beamlet_index_per_destination_look_up_matrix return t_natural_matrix is
+    constant c_len_arr   : t_natural_arr(1 to c_sdp_bdo_nof_destinations_max) :=
+                             func_sdp_sdo_nof_beamlets_per_block_first_destination_look_up_table;
+    variable v_index_mat : t_natural_matrix(1 to c_sdp_bdo_nof_destinations_max,
+                                            0 to c_sdp_bdo_nof_destinations_max - 1);
+    variable v_beamlet_index : natural;
+    variable v_step          : natural;
+  begin
+    -- Determine beamlet index of first beamlet in packet per destination with
+    -- index DN, as function of number of destinations DN.
+    -- . Beamlet index for first destination starts at 0
+    -- . Beamlet index for the other destinations increments with number of
+    --   beamlets per previous destination given by c_len_arr.
+    --
+    -- * rows: nof_destinations DN
+    -- * columns: destination index DI
+    --
+    -- DI:   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15
+    -- DN:
+    --  1    0,   0.....................................................................0
+    --  2    0, 244,   0                                                                .
+    --  3    0, 163, 326,   0                                                           .
+    --  4    0, 122, 244, 366,   0                                                      .
+    --  5    0,  98, 196, 294, 392,   0                                                 .
+    --  6    0,  82, 164, 246, 328, 410,   0                                            .
+    --  7    0,  70, 140, 210, 280, 350, 420,   0                                       .
+    --  8    0,  61, 122, 183, 244, 305, 366, 427,   0                                  .
+    --  9    0,  55, 110, 165, 220, 275, 330, 385, 440,   0                             .
+    -- 10    0,  49,  98, 147, 196, 245, 294, 343, 392, 441,   0                        .
+    -- 11    0,  45,  90, 135, 180, 225, 270, 315, 360, 405, 450,   0                   .
+    -- 12    0,  41,  82, 123, 164, 205, 246, 287, 328, 369, 410, 451,   0              .
+    -- 13    0,  38,  76, 114, 152, 190, 228, 266, 304, 342, 380, 418, 456,   0         .
+    -- 14    0,  35,  70, 105, 140, 175, 210, 245, 280, 315, 350, 385, 420, 455,   0    .
+    -- 15    0,  33,  66,  99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462,   0
+    -- 16    0,  31,  62,  93, 124, 155, 186, 217, 248, 279, 310, 341, 372, 403, 434, 465
+    --
+    -- Equivalent Python code to produce matrix:
+    --
+    --   c_len_arr = [488, 244, 163, 122, 98, 82, 70, 61, 55, 49, 45, 41, 38, 35, 33, 31]
+    --   for DN in range(16):
+    --       lineStr = '%2d ' % (DN + 1)
+    --       v_beamlet_index = 0
+    --       v_step = c_len_arr[DN]
+    --       for DI in range(16):
+    --           if v_beamlet_index < 488:
+    --               lineStr += '%4d,' % v_beamlet_index
+    --           v_beamlet_index += v_step
+    --       print(lineStr)
+    --
+    for DN in 1 to c_sdp_bdo_nof_destinations_max loop
+      v_beamlet_index := 0;
+      v_step := c_len_arr(DN);
+      for DI in 0 to c_sdp_bdo_nof_destinations_max - 1 loop
+        if v_beamlet_index < c_sdp_S_sub_bf then
+          v_index_mat(DN, DI) := v_beamlet_index;
+        end if;
+        v_beamlet_index := v_beamlet_index + v_step;
+      end loop;
+    end loop;
+    return v_index_mat;
+  end func_sdp_sdo_beamlet_index_per_destination_look_up_matrix;
+end sdp_bdo_pkg;
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 4012eabd8272c77d113e651524ae650d6c441aa3..984d6a34d01effd1039a5b2250a05f9ca35c7bb1 100644
--- a/applications/lofar2/libraries/sdp/src/vhdl/sdp_beamformer_output.vhd
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_beamformer_output.vhd
@@ -42,11 +42,14 @@ use common_lib.common_network_layers_pkg.all;
 use dp_lib.dp_stream_pkg.all;
 use reorder_lib.reorder_pkg.all;
 use work.sdp_pkg.all;
+use work.sdp_bdo_pkg.all;
 
 entity sdp_beamformer_output is
   generic (
-    g_beamset_id    : natural := 0;
-    g_use_transpose : boolean := false
+    g_beamset_id                : natural := 0;
+    g_use_transpose             : boolean := false;
+    g_use_multiple_destinations : boolean := false;
+    g_sim_force_bsn_error       : boolean := true
   );
   port (
     dp_clk   : in  std_logic;
@@ -55,24 +58,27 @@ entity sdp_beamformer_output is
     mm_clk   : in  std_logic;
     mm_rst   : in  std_logic;
 
-    reg_hdr_dat_mosi   : in  t_mem_mosi := c_mem_mosi_rst;
-    reg_hdr_dat_miso   : out t_mem_miso;
+    reg_hdr_dat_mosi      : in  t_mem_mosi := c_mem_mosi_rst;
+    reg_hdr_dat_miso      : out t_mem_miso;
 
-    reg_dp_xonoff_mosi : in  t_mem_mosi := c_mem_mosi_rst;
-    reg_dp_xonoff_miso : out t_mem_miso;
+    reg_destinations_copi : in  t_mem_copi := c_mem_mosi_rst;
+    reg_destinations_cipo : out t_mem_cipo;
 
-    in_sosi  : in  t_dp_sosi;
-    out_sosi : out t_dp_sosi;
-    out_siso : in t_dp_siso;
+    reg_dp_xonoff_mosi    : in  t_mem_mosi := c_mem_mosi_rst;
+    reg_dp_xonoff_miso    : out t_mem_miso;
 
-    sdp_info      : in t_sdp_info;
-    beamlet_scale : in std_logic_vector(c_sdp_W_beamlet_scale-1 downto 0);
-    gn_id         : in std_logic_vector(c_sdp_W_gn_id - 1 downto 0);
+    in_sosi        : in  t_dp_sosi;
+    out_sosi       : out t_dp_sosi;
+    out_siso       : in t_dp_siso;
+
+    sdp_info       : in t_sdp_info;
+    beamlet_scale  : in std_logic_vector(c_sdp_W_beamlet_scale-1 downto 0);
+    gn_id          : in std_logic_vector(c_sdp_W_gn_id - 1 downto 0);
 
     -- Source MAC/IP/UDP are not used, c_sdp_cep_hdr_field_sel selects MM programmable instead
-    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);
+    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);
 
     hdr_fields_out : out std_logic_vector(1023 downto 0)
   );
@@ -91,38 +97,17 @@ architecture str of sdp_beamformer_output is
   constant c_fifo_fill      : natural := c_sdp_cep_payload_nof_longwords;  -- 976
   constant c_fifo_size      : natural := true_log_pow2(c_sdp_cep_payload_nof_longwords) * c_sdp_N_beamsets;  -- 2048
 
-  -- Reorder c_nof_ch = c_nof_ch_sel = c_nof_ch_in
-  constant c_nof_blocks_per_packet : natural := c_sdp_cep_nof_blocks_per_packet;  -- = 4
-  constant c_nof_data_per_block    : natural := c_sdp_S_sub_bf;  -- = 488 dual pol beamlets
-  constant c_nof_words_per_data    : natural := 1;  -- 1 dual pol beamlet data per 32b word
-  constant c_nof_ch                : natural := c_nof_blocks_per_packet * c_nof_data_per_block * c_nof_words_per_data;  -- = 1952
-
-  -- Use c_transpose_indices and c_transpose_indices_inv for debug view in Objects window.
-  -- Use c_transpose_indices for func_reorder_transpose() in this sdp_beamformer_output,
-  -- a tb can then use c_transpose_indices_inv to undo the transpose.
-  constant c_transpose_indices     : t_natural_arr(0 to c_nof_ch - 1) :=
-                                       func_reorder_transpose_indices(c_nof_blocks_per_packet,
-                                                                      c_nof_data_per_block,
-                                                                      c_nof_words_per_data);
-  constant c_transpose_indices_inv : t_natural_arr(0 to c_nof_ch - 1) :=
-                                       func_reorder_transpose_indices(c_nof_data_per_block,
-                                                                      c_nof_blocks_per_packet,
-                                                                      c_nof_words_per_data);
-
-  -- Dynamic reorder block size control input
-  -- . The data consists of 1 word = 1 ch, because 1 word contains 1 dual pol  beamlet.
-  -- . The input packet has nof_ch of data per packet.
-  -- . The transposed output packet will have blocks with nof_blocks_per_packet
-  --   data per block and nof_data_per_block blocks per packet.
-  signal nof_ch                : natural := c_nof_ch;
-  signal nof_blocks_per_packet : natural := c_nof_blocks_per_packet;
-  signal nof_data_per_block    : natural := c_nof_data_per_block;
-  signal select_copi           : t_mem_copi := c_mem_copi_rst;
-  signal select_cipo           : t_mem_cipo := c_mem_cipo_rst;
-  signal r_identity            : t_reorder_identity;
-  signal d_identity            : t_reorder_identity;
-  signal r_transpose           : t_reorder_transpose;
-  signal d_transpose           : t_reorder_transpose;
+  -- field_sel = '0' for DP (dynamic), '1' for MM (fixed or programmable via MM of dp_offload_tx_v3)
+  constant c_cep_hdr_field_sel : std_logic_vector(c_sdp_cep_nof_hdr_fields - 1 downto 0) :=
+                                   sel_a_b(g_use_multiple_destinations, func_sdp_cep_hdr_field_sel_dst('0'),
+                                                                        func_sdp_cep_hdr_field_sel_dst('1'));
+
+  -- BDO packet size control
+  -- . One 32b word contains 1 dual pol beamlet of 4 octets (Xre, Xim, Yre, Yim).
+  -- . The transposed output packet will have nof_blocks_per_packet time slots
+  --   per beamlet and nof_beamlets_per_block dual pol beamlets per time slot.
+  signal nof_blocks_per_packet      : natural;
+  signal nof_beamlets_per_block     : natural;
 
   signal snk_in_concat              : t_dp_sosi;
   signal snk_in_concat_data         : std_logic_vector(c_data_w - 1 downto 0);
@@ -130,11 +115,8 @@ architecture str of sdp_beamformer_output is
   signal snk_in_concat_im           : std_logic_vector(c_sdp_W_beamlet - 1 downto 0);
   signal dp_repack_beamlet_src_out  : t_dp_sosi;
   signal dp_repack_beamlet_word     : t_sdp_dual_pol_beamlet_in_word;
-  signal dp_packet_merge_src_out    : t_dp_sosi;
-  signal dp_packet_merge_word       : t_sdp_dual_pol_beamlet_in_word;
   signal dp_packet_reorder_src_out  : t_dp_sosi;
   signal dp_packet_reorder_word     : t_sdp_dual_pol_beamlet_in_word;
-  signal reorder_busy               : std_logic;
   signal dp_repack_longword_src_out : t_dp_sosi;
   signal dp_repack_longword         : t_sdp_dual_pol_beamlet_in_longword;
   signal dp_fifo_fill_eop_src_out   : t_dp_sosi;
@@ -152,6 +134,12 @@ architecture str of sdp_beamformer_output is
   signal payload_err        : std_logic_vector(0 downto 0);
   signal station_info       : std_logic_vector(15 downto 0) := (others => '0');
 
+  -- Multiple destinations
+  signal destinations_info  : t_sdp_bdo_destinations_info;
+  signal eth_dst_mac        : std_logic_vector(c_network_eth_mac_addr_w - 1 downto 0);
+  signal ip_dst_addr        : std_logic_vector(c_network_ip_addr_w - 1 downto 0);
+  signal udp_dst_port       : std_logic_vector(c_network_udp_port_w - 1 downto 0);
+
   -- Default set all data path driven header fields to 0
   signal dp_offload_tx_hdr_fields : std_logic_vector(1023 downto 0) := (others => '0');
   signal dp_offload_tx_header     : t_sdp_cep_header;  -- to view dp_offload_tx_hdr_fields in Wave window
@@ -180,32 +168,34 @@ begin
     -- Force BSN error in simulation to verify payload error in
     -- tb_lofar2_unb2c_sdp_station_bf.vhd, this will cause two times payload
     -- errors, one when BSN goes wrong and one when BSN goes ok again.
-    dbg_bsn_offset <= '0';
-    if v_ref_time = 0 ns then
-      if in_sosi.sop = '1' then
-        -- Use start of input as reference time, rather than e.g. fixed 50 us,
-        -- to be independent of how long it takes for the tb to deliver the
-        -- first block.
-        v_ref_time := NOW;
-        -- Offset the v_ref_time to the second block of the
-        -- c_nof_blocks_per_packet = 4 blocks that will be merged.
-        v_ref_time := v_ref_time + c_sdp_block_period * 1 ns;
+    if g_sim_force_bsn_error = true then
+      dbg_bsn_offset <= '0';
+      if v_ref_time = 0 ns then
+        if in_sosi.sop = '1' then
+          -- Use start of input as reference time, rather than e.g. fixed 50 us,
+          -- to be independent of how long it takes for the tb to deliver the
+          -- first block.
+          v_ref_time := NOW;
+          -- Offset the v_ref_time to the second block of the
+          -- c_sdp_cep_nof_blocks_per_packet = 4 blocks that will be merged.
+          v_ref_time := v_ref_time + c_sdp_block_period * 1 ns;
+        end if;
+      elsif NOW > v_ref_time + 1 * c_sdp_cep_nof_blocks_per_packet * c_sdp_block_period * 1 ns and
+            NOW < v_ref_time + 4 * c_sdp_cep_nof_blocks_per_packet * c_sdp_block_period * 1 ns then
+        -- Disturb BSN to cause merged payload error. Expected results for the
+        -- merged blocks:
+        -- . index 0 : First merged block bsn ok and payload_error = '0'.
+        -- . index 1 : bsn still ok, but payload error = '1', due to bsn++
+        --             after first block
+        -- . index 2,3 : bsn wrong due to bsn++, but payload error = '0',
+        --               because all 4 merged blocks have incrementing bsn
+        -- . index 4 : bsn still wrong due to bsn++, and payload error = '1',
+        --             because the bsn is restored after first block, so the
+        --             merged blocks do not have incrementing bsn
+        -- . index >= 5 : bsn ok and payload_error = '0'.
+        dbg_bsn_offset <= '1';
+        snk_in_concat.bsn <= INCR_UVEC(in_sosi.bsn, 1);
       end if;
-    elsif NOW > v_ref_time + 1 * c_nof_blocks_per_packet * c_sdp_block_period * 1 ns and
-          NOW < v_ref_time + 4 * c_nof_blocks_per_packet * c_sdp_block_period * 1 ns then
-      -- Disturb BSN to cause merged payload error. Expected results for the
-      -- merged blocks:
-      -- . index 0 : First merged block bsn ok and payload_error = '0'.
-      -- . index 1 : bsn still ok, but payload error = '1', due to bsn++
-      --             after first block
-      -- . index 2,3 : bsn wrong due to bsn++, but payload error = '0',
-      --               because all 4 merged blocks have incrementing bsn
-      -- . index 4 : bsn still wrong due to bsn++, and payload error = '1',
-      --             because the bsn is restored after first block, so the
-      --             merged blocks do not have incrementing bsn
-      -- . index >= 5 : bsn ok and payload_error = '0'.
-      dbg_bsn_offset <= '1';
-      snk_in_concat.bsn <= INCR_UVEC(in_sosi.bsn, 1);
     end if;
     -- synthesis translate_on
     ---------------------------------------------------------------------------
@@ -239,116 +229,65 @@ begin
   -- [0:3] = [Xre, Xim, Yre, Yim]
   dp_repack_beamlet_word <= unpack_data(dp_repack_beamlet_src_out.data(c_sdp_W_dual_pol_beamlet - 1 downto 0));
 
-  -----------------------------------------------------------------------------
-  -- dp_packet_merge
-  -----------------------------------------------------------------------------
-  u_dp_packet_merge : entity dp_lib.dp_packet_merge
-  generic map(
-    g_use_ready     => false,  -- no flow control
-    g_nof_pkt       => c_nof_blocks_per_packet,
-    g_bsn_increment => 1
-  )
-  port map(
-    rst     => dp_rst,
-    clk     => dp_clk,
-    snk_in  => dp_repack_beamlet_src_out,
-    src_out => dp_packet_merge_src_out
-  );
-
-  -- Debug signals for view in Wave window
-  dp_packet_merge_word <= unpack_data(dp_packet_merge_src_out.data(c_sdp_W_dual_pol_beamlet - 1 downto 0));
-
-  -----------------------------------------------------------------------------
-  -- reorder_col_select
-  -- . Reorder beamlet data from:
-  --     (int8) [t] [N_blocks_per_packet][S_sub_bf] [N_pol_bf][N_complex]
-  --   to:
-  --     (int8) [t] [S_sub_bf][N_blocks_per_packet] [N_pol_bf][N_complex]
-  --
-  -- . where (int8) [N_pol_bf][N_complex] = c_sdp_W_dual_pol_beamlet = 32b
-  --   dual polarization beamlet word
-  -- See tb_reorder_col_select_all.vhd for how to control col_select_copi/cipo.
-  -----------------------------------------------------------------------------
-  u_reorder_col_select : entity reorder_lib.reorder_col_select
-  generic map (
-    g_dsp_data_w  => c_sdp_W_dual_pol_beamlet / c_nof_complex,  -- = 32b / 2
-    g_nof_ch_in   => c_nof_ch,
-    g_nof_ch_sel  => c_nof_ch,
-    g_use_complex => false
-  )
-  port map (
-    dp_rst          => dp_rst,
-    dp_clk          => dp_clk,
-
-    reorder_busy    => reorder_busy,
-
-    -- Dynamic reorder block size control input
-    nof_ch_in       => nof_ch,
-    nof_ch_sel      => nof_ch,
-
-    -- Captured reorder block size control used for output_sosi
-    output_nof_ch_in  => open,
-    output_nof_ch_sel => open,
-
-    -- Memory Mapped
-    col_select_mosi => select_copi,
-    col_select_miso => select_cipo,
-
-    -- Streaming
-    input_sosi      => dp_packet_merge_src_out,
-    output_sosi     => dp_packet_reorder_src_out
-  );
+  gen_one_destination : if g_use_multiple_destinations = false generate
+    -----------------------------------------------------------------------------
+    -- Merge and reorder beamlet data for one destination from:
+    --     (int8) [t] [N_blocks_per_packet][S_sub_bf] [N_pol_bf][N_complex]
+    --   to:
+    --     (int8) [t] [S_sub_bf][N_blocks_per_packet] [N_pol_bf][N_complex]
+    --
+    -- . where (int8) [N_pol_bf][N_complex] = c_sdp_W_dual_pol_beamlet = 32b
+    --   dual polarization beamlet word
+    -----------------------------------------------------------------------------
+    u_sdp_bdo_one_destination : entity work.sdp_bdo_one_destination
+      generic map (
+        g_use_transpose => g_use_transpose
+      )
+      port map (
+        dp_clk   => dp_clk,
+        dp_rst   => dp_rst,
+
+        snk_in   => dp_repack_beamlet_src_out,
+        src_out  => dp_packet_reorder_src_out
+      );
+  end generate;
+
+  gen_multiple_destinations : if g_use_multiple_destinations = true generate
+    -----------------------------------------------------------------------------
+    -- Merge, reorder and unmerge beamlet data for N_destinations >= 1 from:
+    --     (int8) [t] [N_blocks_per_packet][S_sub_bf / N_destinations] [N_pol_bf][N_complex]
+    --   to:
+    --     (int8) [t] [S_sub_bf / N_destinations][N_blocks_per_packet] [N_pol_bf][N_complex]
+    --
+    -- . where (int8) [N_pol_bf][N_complex] = c_sdp_W_dual_pol_beamlet = 32b
+    --   dual polarization beamlet word
+    -- . where N_destinations packets together transport the S_sub_bf beamlets.
+    -----------------------------------------------------------------------------
+    u_sdp_bdo_multiple_destinations : entity work.sdp_bdo_multiple_destinations
+      generic map (
+        g_beamset_id    => g_beamset_id,
+        g_use_transpose => g_use_transpose
+      )
+      port map (
+        mm_clk   => mm_clk,
+        mm_rst   => mm_rst,
+
+        dp_clk   => dp_clk,
+        dp_rst   => dp_rst,
+
+        reg_destinations_copi => reg_destinations_copi,
+        reg_destinations_cipo => reg_destinations_cipo,
+
+        destinations_info => destinations_info,
+
+        snk_in   => dp_repack_beamlet_src_out,
+        src_out  => dp_packet_reorder_src_out
+      );
+  end generate;
 
   -- Debug signals for view in Wave window
   dp_packet_reorder_word <= unpack_data(dp_packet_reorder_src_out.data(c_sdp_W_dual_pol_beamlet - 1 downto 0));
 
-  -- Use synchronous reset in d signals
-  p_dp_clk_synchronous : process(dp_clk)
-  begin
-    if rising_edge(dp_clk) then
-      r_identity  <= d_identity;
-      r_transpose <= d_transpose;
-    end if;
-  end process;
-
-  -- Pass on beamlet data in original order or in transposed order
-  select_copi <= r_transpose.select_copi when g_use_transpose else r_identity.select_copi;
-
-  p_reorder_identity : process(dp_rst, select_cipo, nof_ch, r_identity)
-    variable v : t_reorder_identity;
-  begin
-    if select_cipo.waitrequest = '0' then
-      -- Read from reorder_col_select page
-      v := func_reorder_identity(nof_ch, r_identity);
-    else
-      -- No read, new reorder_col_select page not available yet
-      v := c_reorder_identity_rst;
-    end if;
-    -- Synchronous reset
-    if dp_rst = '1' then
-      v := c_reorder_identity_rst;
-    end if;
-    d_identity <= v;
-  end process;
-
-  p_reorder_transpose : process(dp_rst, select_cipo,
-                                nof_blocks_per_packet, nof_data_per_block, r_transpose)
-    variable v : t_reorder_transpose;
-  begin
-    if select_cipo.waitrequest = '0' then
-      -- Read from reorder_col_select page
-      v := func_reorder_transpose(nof_blocks_per_packet, nof_data_per_block, r_transpose);
-    else
-      -- No read, new reorder_col_select page not available yet
-      v := c_reorder_transpose_rst;
-    end if;
-    -- Synchronous reset
-    if dp_rst = '1' then
-      v := c_reorder_transpose_rst;
-    end if;
-    d_transpose <= v;
-  end process;
-
   -----------------------------------------------------------------------------
   -- dp_repack_data
   -- . Repack 32b -> 64b, to get 64b longwords for network packet data
@@ -377,8 +316,8 @@ begin
   -----------------------------------------------------------------------------
   -- FIFO
   -----------------------------------------------------------------------------
-  -- Pass on dp_repack_longword_src_out.err field (from u_dp_packet_merge) via
-  -- separate u_common_fifo_sc_err
+  -- Pass on dp_repack_longword_src_out.err field not here, but via separate
+  -- u_common_fifo_sc_err.
   u_dp_fifo_fill_eop_sc : entity dp_lib.dp_fifo_fill_eop_sc
   generic map (
     g_data_w         => c_longword_w,
@@ -444,7 +383,7 @@ begin
   --   where 0 = data path, 1 = MM controlled. The '0' fields are assigned here
   --   via dp_offload_tx_hdr_fields. In order:
   --     access   field
-  --     MM       eth_dst_mac
+  --     MM,DP    eth_dst_mac
   --     MM       eth_src_mac
   --     MM       eth_type
   --
@@ -459,10 +398,10 @@ begin
   --     MM       ip_protocol
   --        DP    ip_header_checksum --> added by u_tr_10GbE_ip_checksum
   --     MM       ip_src_addr
-  --     MM       ip_dst_addr
+  --     MM,DP    ip_dst_addr
   --
   --     MM       udp_src_port
-  --     MM       udp_dst_port
+  --     MM,DP    udp_dst_port
   --     MM       udp_total_length
   --        DP    udp_checksum --> default fixed 0, so not used, not calculated
   --                               here or in tr_10GbE because would require
@@ -486,38 +425,60 @@ begin
   --     MM       sdp_reserved
   --        DP    sdp_beamlet_scale
   --        DP    sdp_beamlet_index
-  --     MM       sdp_nof_blocks_per_packet
-  --     MM       sdp_nof_beamlets_per_block
+  --        DP    sdp_nof_blocks_per_packet
+  --        DP    sdp_nof_beamlets_per_block
   --        DP    sdp_block_period
   --
   --        DP    dp_bsn
 
+  p_assemble_offload_info : process(destinations_info)
+  begin
+    if g_use_multiple_destinations = false then
+      -- Use constant defaults for beamlet data output to one destination.
+      nof_blocks_per_packet  <= c_sdp_cep_nof_blocks_per_packet;  -- = 4;
+      nof_beamlets_per_block <= c_sdp_S_sub_bf;  -- = 488 dual pol beamlets;
+    else
+      -- Use dynamic sizes for beamlet data output to multiple destination.
+      nof_blocks_per_packet  <= destinations_info.nof_blocks_per_packet_act;
+      nof_beamlets_per_block <= c_sdp_S_sub_bf;  -- = 488 dual pol beamlets;
+
+      -- TODO check channel field to set destination addresses in dp_offload_tx_hdr_fields
+    end if;
+  end process;
+
+  station_info <= sdp_info.antenna_field_index & sdp_info.station_id;
+
   -- Use MM programmable source MAC/IP/UDP instead of source MAC/IP/UDP based
   -- on node ID. This is necessary because beamlet packets from different
   -- stations must have different source MAC/IP/UDP. Hence the eth_src_mac,
   -- udp_src_port and ip_src_addr are ignored, because c_sdp_cep_hdr_field_sel
   -- selects MM control, but keep the code to be able to enable using them by
   -- just changing the selection bit.
-
-  station_info <= sdp_info.antenna_field_index & sdp_info.station_id;
-
   dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "eth_src_mac" ) downto field_lo(c_sdp_cep_hdr_field_arr, "eth_src_mac" )) <= eth_src_mac;
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "udp_src_port") downto field_lo(c_sdp_cep_hdr_field_arr, "udp_src_port")) <= udp_src_port;
   dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "ip_src_addr" ) downto field_lo(c_sdp_cep_hdr_field_arr, "ip_src_addr" )) <= ip_src_addr;
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "udp_src_port") downto field_lo(c_sdp_cep_hdr_field_arr, "udp_src_port")) <= udp_src_port;
 
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_observation_id"                      ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_observation_id"                      )) <= sdp_info.observation_id;
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_station_info"                        ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_station_info"                        )) <= station_info;
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_antenna_band_id"         ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_source_info_antenna_band_id"         )) <= SLV(sdp_info.antenna_band_index);
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_nyquist_zone_id"         ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_source_info_nyquist_zone_id"         )) <= sdp_info.nyquist_zone_index;
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_f_adc"                   ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_source_info_f_adc"                   )) <= SLV(sdp_info.f_adc);
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_fsub_type"               ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_source_info_fsub_type"               )) <= SLV(sdp_info.fsub_type);
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_payload_error"           ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_source_info_payload_error"           )) <= payload_err;
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_beam_repositioning_flag" ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_source_info_beam_repositioning_flag" )) <= SLV(sdp_info.beam_repositioning_flag);
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_gn_id"                   ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_source_info_gn_id"                   )) <= gn_id;
-
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_beamlet_scale" ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_beamlet_scale" )) <= beamlet_scale;
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_beamlet_index" ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_beamlet_index" )) <= TO_UVEC(c_beamlet_index, c_halfword_w);
-  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_block_period"  ) downto field_lo(c_sdp_cep_hdr_field_arr,  "sdp_block_period"  )) <= sdp_info.block_period;
+  -- Use MM programmable destination MAC/IP/UDP from dp_offload_tx_v3 for one destination
+  -- Use DP programmable destination MAC/IP/UDP from sdp_bdo_destinations_reg for multiple destinations
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "eth_dst_mac" ) downto field_lo(c_sdp_cep_hdr_field_arr, "eth_dst_mac" )) <= eth_dst_mac;
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "ip_dst_addr" ) downto field_lo(c_sdp_cep_hdr_field_arr, "ip_dst_addr" )) <= ip_dst_addr;
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "udp_dst_port") downto field_lo(c_sdp_cep_hdr_field_arr, "udp_dst_port")) <= udp_dst_port;
+
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_observation_id"                     ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_observation_id"                     )) <= sdp_info.observation_id;
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_station_info"                       ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_station_info"                       )) <= station_info;
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_antenna_band_id"        ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_source_info_antenna_band_id"        )) <= SLV(sdp_info.antenna_band_index);
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_nyquist_zone_id"        ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_source_info_nyquist_zone_id"        )) <= sdp_info.nyquist_zone_index;
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_f_adc"                  ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_source_info_f_adc"                  )) <= SLV(sdp_info.f_adc);
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_fsub_type"              ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_source_info_fsub_type"              )) <= SLV(sdp_info.fsub_type);
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_payload_error"          ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_source_info_payload_error"          )) <= payload_err;
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_beam_repositioning_flag") downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_source_info_beam_repositioning_flag")) <= SLV(sdp_info.beam_repositioning_flag);
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_source_info_gn_id"                  ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_source_info_gn_id"                  )) <= gn_id;
+
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_beamlet_scale"         ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_beamlet_scale"         )) <= beamlet_scale;
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_beamlet_index"         ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_beamlet_index"         )) <= TO_UVEC(c_beamlet_index, c_halfword_w);
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_nof_blocks_per_packet" ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_nof_blocks_per_packet" )) <= TO_UVEC(nof_blocks_per_packet, c_octet_w);
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_nof_beamlets_per_block") downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_nof_beamlets_per_block")) <= TO_UVEC(nof_beamlets_per_block, c_halfword_w);
+  dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "sdp_block_period"          ) downto field_lo(c_sdp_cep_hdr_field_arr, "sdp_block_period"          )) <= sdp_info.block_period;
 
   dp_offload_tx_hdr_fields(field_hi(c_sdp_cep_hdr_field_arr, "dp_bsn" ) downto field_lo(c_sdp_cep_hdr_field_arr, "dp_bsn" )) <= dp_pipeline_src_out.bsn(63 downto 0);
 
@@ -533,7 +494,7 @@ 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_cep_hdr_field_sel,
     g_pipeline_ready => true
   )
   port map (
diff --git a/applications/lofar2/libraries/sdp/src/vhdl/sdp_pkg.vhd b/applications/lofar2/libraries/sdp/src/vhdl/sdp_pkg.vhd
index da3ee350e94f7478d7f0713fcf9e93deeb3252ce..a3a41fdb21d4451d8bdfd153c7623fb8f52bd654 100644
--- a/applications/lofar2/libraries/sdp/src/vhdl/sdp_pkg.vhd
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_pkg.vhd
@@ -112,7 +112,7 @@ package sdp_pkg is
   constant c_sdp_W_local_oscillator           : natural := 16;  -- = w in s(w, p), s = signed
   constant c_sdp_W_local_oscillator_fraction  : natural := 15;  -- = p in s(w, p)
   constant c_sdp_W_local_oscillator_magnitude : natural := c_sdp_W_local_oscillator - c_sdp_W_local_oscillator_fraction - 1;  -- = 0
-  constant c_sdp_N_ring_nof_mac10g         : natural := 3;  -- for sdp_station_xsub_ring design.
+  constant c_sdp_N_ring_nof_mac10g            : natural := 3;  -- for sdp_station_xsub_ring design.
 
   -- Derived constants
   constant c_sdp_FS_adc                  : natural := 2**(c_sdp_W_adc - 1);  -- full scale FS corresponds to amplitude 1.0, will just cause clipping of +FS to +FS-1
@@ -137,7 +137,9 @@ package sdp_pkg is
   constant c_sdp_N_beamlets_sdp          : natural := c_sdp_N_beamsets * c_sdp_S_sub_bf;  -- = 976
   constant c_sdp_W_dual_pol_beamlet      : natural := c_sdp_N_pol_bf * c_nof_complex * c_sdp_W_beamlet;  -- 2 * 2 * 8 = 32b
 
+  constant c_sdp_nof_words_per_beamlet     : natural := 1;  -- 1 dual pol, complex, 8bit beamlet (Xre, Xim, Yre, Yim) per 32b word
   constant c_sdp_nof_beamlets_per_longword : natural := 2;  -- 2 dual pol, complex, 8bit beamlets fit in 1 64bit longword
+  constant c_sdp_nof_beamlets_per_block    : natural := c_sdp_S_sub_bf;  -- number of dual pol beamlets per block
 
   -- . unit weights
   constant c_sdp_unit_sub_weight      : natural := 2**c_sdp_W_sub_weight_fraction;  -- 2**13, so range +-4.0 for 16 bit signed weight
@@ -401,7 +403,7 @@ package sdp_pkg is
   end record;
 
   -----------------------------------------------------------------------------
-  -- Beamlet output via 10GbE to CEP (= central processor, see ICD STAT-CEP)
+  -- Beamlet data output (BDO) via 10GbE to CEP (= central processor, see ICD STAT-CEP)
   -----------------------------------------------------------------------------
   constant c_sdp_cep_version_id        : natural := 5;
   constant c_sdp_marker_beamlets       : natural := 98;  -- = x"62" = 'b'
@@ -419,11 +421,12 @@ package sdp_pkg is
   constant c_sdp_cep_header_len        : natural := 14 + 20 + 8 + c_sdp_cep_app_header_len;  -- = eth + ip + udp + app = 74 octets, see ICD STAT-CEP
 
   constant c_sdp_cep_nof_blocks_per_packet     : natural := 4;  -- number of time blocks of beamlets per output packet
-  constant c_sdp_cep_nof_beamlets_per_block    : natural := c_sdp_S_sub_bf;  -- number of dual pol beamlets (c_sdp_N_pol_bf = 2)
+  constant c_sdp_cep_nof_beamlets_per_block    : natural := c_sdp_nof_beamlets_per_block;  -- number of dual pol beamlets (c_sdp_N_pol_bf = 2)
   constant c_sdp_cep_nof_beamlets_per_packet   : natural := c_sdp_cep_nof_blocks_per_packet * c_sdp_cep_nof_beamlets_per_block;
   constant c_sdp_cep_payload_nof_longwords     : natural := c_sdp_cep_nof_beamlets_per_packet / c_sdp_nof_beamlets_per_longword;  -- = 976
   constant c_sdp_cep_packet_nof_longwords      : natural := ceil_div(c_sdp_cep_header_len, c_longword_sz) + c_sdp_cep_payload_nof_longwords;  -- without tail CRC, the CRC is applied by 10GbE MAC
 
+  -- CEP packet header
   constant c_sdp_cep_nof_hdr_fields : natural := 3 + 12 + 4 + 4 + 9 + 6 + 1;  -- = 39 fields
   -- c_sdp_cep_header_len / c_longword_sz = 74 / 8 = 9.25 64b words = 592b
   -- hdr_field_sel bit selects where the hdr_field value is set:
@@ -432,9 +435,10 @@ package sdp_pkg is
   --       sdp_beamformer_output.vhd is not used.
   -- Remarks: see remarks at c_sdp_stat_nof_hdr_fields.
   --                                                                                             eth     ip               udp      app
-  constant c_sdp_cep_hdr_field_sel  : std_logic_vector(c_sdp_cep_nof_hdr_fields - 1 downto 0) := "111" & "111111111011" & "1110" & "1100" & "100000010" & "100110" & "0";  -- current
---CONSTANT c_sdp_cep_hdr_field_sel  : STD_LOGIC_VECTOR(c_sdp_cep_nof_hdr_fields-1 DOWNTO 0) := "101"&"111111111001"&"0111"&"1100"&"100000010"&"000110"&"0";  -- previous 27 sep 2022
---CONSTANT c_sdp_cep_hdr_field_sel  : STD_LOGIC_VECTOR(c_sdp_cep_nof_hdr_fields-1 DOWNTO 0) := "100"&"000000010001"&"0100"&"0100"&"100000000"&"101000"&"0";  -- initial
+  constant c_sdp_cep_hdr_field_sel  : std_logic_vector(c_sdp_cep_nof_hdr_fields - 1 downto 0) := "111" & "111111111011" & "1110" & "1100" & "100000010" & "100000" & "0";  -- current
+--constant c_sdp_cep_hdr_field_sel  : std_logic_vector(c_sdp_cep_nof_hdr_fields - 1 downto 0) := "111" & "111111111011" & "1110" & "1100" & "100000010" & "100110" & "0";  -- 18 sep 2023
+--constant c_sdp_cep_hdr_field_sel  : STD_LOGIC_VECTOR(c_sdp_cep_nof_hdr_fields-1 downto 0) := "101"&"111111111001"&"0111"&"1100"&"100000010"&"000110"&"0";  -- previous 27 sep 2022
+--constant c_sdp_cep_hdr_field_sel  : STD_LOGIC_VECTOR(c_sdp_cep_nof_hdr_fields-1 downto 0) := "100"&"000000010001"&"0100"&"0100"&"100000000"&"101000"&"0";  -- initial
 
   -- Default use source MAC/IP/UDP = 0 and destination MAC/IP/UDP = 0, so these have to be MM programmed
   -- before beamlet output packets can be send.
@@ -683,13 +687,15 @@ package sdp_pkg is
   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;
 
+  -- Select header destination MAC, IP, UDP fields from DP (sl = 0) or from MM (sl = '1') in dp_offload_tx_v3
+  function func_sdp_cep_hdr_field_sel_dst(sl : std_logic) return std_logic_vector;
+
   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) 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) return std_logic_vector;  -- map all c_sdp_N_crosslets_max offsets
   function func_sdp_step_crosslets_info(info_rec : t_sdp_crosslets_info) return t_sdp_crosslets_info;  -- step all c_sdp_N_crosslets_max offsets
-
 end package sdp_pkg;
 
 package body sdp_pkg is
@@ -947,6 +953,16 @@ package body sdp_pkg is
     return v;
   end func_sdp_map_cep_header;
 
+  function func_sdp_cep_hdr_field_sel_dst(sl : std_logic) return std_logic_vector is
+    variable v_sel : std_logic_vector(c_sdp_cep_nof_hdr_fields - 1 downto 0) := c_sdp_cep_hdr_field_sel;
+  begin
+    -- Select header destination MAC, IP, UDP field from DP or from MM in dp_offload_tx_v3
+    v_sel(38) := sl;  -- eth_dst_mac
+    v_sel(24) := sl;  -- ip_dst_addr
+    v_sel(22) := sl;  -- udp_dst_port
+    return v_sel;
+  end func_sdp_cep_hdr_field_sel_dst;
+
   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
@@ -1006,5 +1022,4 @@ package body sdp_pkg is
     end loop;
     return v_info;
   end func_sdp_step_crosslets_info;
-
 end sdp_pkg;
diff --git a/applications/lofar2/libraries/sdp/src/vhdl/sdp_station.vhd b/applications/lofar2/libraries/sdp/src/vhdl/sdp_station.vhd
index ebd79dc6d47bbcaa1214e1a8e1f58b93717e6c49..77a525d30f18ad9a4bde74ff5f7873f98187d106 100644
--- a/applications/lofar2/libraries/sdp/src/vhdl/sdp_station.vhd
+++ b/applications/lofar2/libraries/sdp/src/vhdl/sdp_station.vhd
@@ -66,7 +66,8 @@ entity sdp_station is
     g_use_oversample         : boolean := false;
     g_use_xsub               : boolean := true;
     g_use_bf                 : boolean := true;
-    g_use_bdo_transpose      : boolean := false;
+    g_use_bdo_transpose             : boolean := false;
+    g_use_bdo_multiple_destinations : boolean := false;
     g_use_ring               : boolean := true;
     g_P_sq                   : natural := 1
   );
@@ -908,13 +909,14 @@ begin
     gen_bf : for beamset_id in 0 to c_sdp_N_beamsets - 1 generate
       u_bf : entity work.node_sdp_beamformer
       generic map(
-        g_sim                    => g_sim,
-        g_sim_sdp                => g_sim_sdp,
-        g_beamset_id             => beamset_id,
-        g_use_bdo_transpose      => g_use_bdo_transpose,
-        g_scope_selected_beamlet => g_scope_selected_subband,
-        g_subband_raw_dat_w      => c_subband_raw_dat_w,
-        g_subband_raw_fraction_w => c_subband_raw_fraction_w
+        g_sim                           => g_sim,
+        g_sim_sdp                       => g_sim_sdp,
+        g_beamset_id                    => beamset_id,
+        g_use_bdo_transpose             => g_use_bdo_transpose,
+        g_use_bdo_multiple_destinations => g_use_bdo_multiple_destinations,
+        g_scope_selected_beamlet        => g_scope_selected_subband,
+        g_subband_raw_dat_w             => c_subband_raw_dat_w,
+        g_subband_raw_fraction_w        => c_subband_raw_fraction_w
       )
       port map(
         dp_clk                   => dp_clk,
diff --git a/applications/lofar2/libraries/sdp/tb/vhdl/tb_sdp_beamformer_output.vhd b/applications/lofar2/libraries/sdp/tb/vhdl/tb_sdp_beamformer_output.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..69a52466c91568dcba64d3f57a69f03f22648822
--- /dev/null
+++ b/applications/lofar2/libraries/sdp/tb/vhdl/tb_sdp_beamformer_output.vhd
@@ -0,0 +1,375 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2023
+-- 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 sdp_beamformer_output.vhd
+-- Description:
+-- . https://support.astron.nl/confluence/pages/viewpage.action?spaceKey=L2M&title=L4+SDPFW+Decision%3A+Multiple+beamlet+output+destinations
+--
+-- Usage:
+-- > as 8
+-- > run -a
+-------------------------------------------------------------------------------
+
+library IEEE, common_lib, dp_lib;
+use IEEE.std_logic_1164.all;
+use common_lib.common_pkg.all;
+use common_lib.common_mem_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 work.sdp_pkg.all;
+use work.tb_sdp_pkg.all;
+
+entity tb_sdp_beamformer_output is
+  generic (
+    g_nof_repeat                : natural := 50;
+    g_beamset_id                : natural := 0;
+    g_use_transpose             : boolean := true;
+    g_use_multiple_destinations : boolean := false
+  );
+end tb_sdp_beamformer_output;
+
+architecture tb of tb_sdp_beamformer_output is
+  constant c_dp_clk_period : time := 5 ns;  -- 200 MHz
+  constant c_mm_clk_period : time := 1 ns;  -- fast MM clk to speed up simulation
+
+  constant c_beamlet_mod            : natural := 2**c_sdp_W_beamlet;
+  constant c_init_re                : natural := 0;
+  constant c_init_im                : natural := 1;
+  constant c_init_bsn               : natural := 0;
+  constant c_bf_block_len           : natural := c_sdp_N_pol_bf * c_sdp_S_sub_bf;  -- = 2 * 488 = 976
+  constant c_bf_gap_size            : natural := c_sdp_N_fft - c_bf_block_len;  -- = 1024 - 976 = 48
+
+  constant c_exp_beamlet_scale      : natural := natural(1.0 / 2.0**9 * real(c_sdp_unit_beamlet_scale));
+  constant c_exp_beamlet_scale_slv  : std_logic_vector(c_sdp_W_beamlet_scale-1 downto 0) :=
+                                        to_uvec(c_exp_beamlet_scale, c_sdp_W_beamlet_scale);
+  constant c_gn_id            : natural := 3;
+  constant c_gn_id_slv        : std_logic_vector(c_sdp_W_gn_id - 1 downto 0) :=
+                                  to_uvec(c_gn_id, c_sdp_W_gn_id);
+  constant c_id               : std_logic_vector(7 downto 0) := to_uvec(c_gn_id, 8);
+  constant c_cep_eth_src_mac  : std_logic_vector(47 downto 0) := c_sdp_cep_eth_src_mac_47_16 & func_sdp_gn_index_to_mac_15_0(c_gn_id);
+  constant c_cep_ip_src_addr  : std_logic_vector(31 downto 0) := c_sdp_cep_ip_src_addr_31_16 & func_sdp_gn_index_to_ip_15_0(c_gn_id);
+  constant c_cep_udp_src_port : std_logic_vector(15 downto 0) := c_sdp_cep_udp_src_port_15_8 & c_id;
+
+  -- Checksum value obtained from rx_sdp_cep_header.ip.header_checksum in wave window
+  constant c_exp_ip_header_checksum : natural := 16#5BDB#;
+  constant c_exp_payload_error      : std_logic := '0';
+  constant c_exp_beamlet_index      : natural := g_beamset_id * c_sdp_S_sub_bf;
+
+  constant c_exp_sdp_info : t_sdp_info := (to_uvec(7, 6),  -- antenna_field_index
+                                           to_uvec(601, 10),  -- station_id
+                                           '0',  -- antenna_band_index
+                                           x"FFFFFFFF",  -- observation_id
+                                           b"01",  -- nyquist_zone_index, 0 = first, 1 = second, 2 = third
+                                           '1',  -- f_adc, 0 = 160 MHz, 1 = 200 MHz
+                                           '0',  -- fsub_type, 0 = critically sampled, 1 = oversampled
+                                           '0',  -- beam_repositioning_flag
+                                           x"1400"  -- block_period = 5120
+                                          );
+
+  signal tb_end       : std_logic := '0';
+  signal dp_clk       : std_logic := '1';
+  signal dp_rst       : std_logic;
+  signal mm_clk       : std_logic := '1';
+  signal mm_rst       : std_logic;
+
+  -- beamlet data output
+  signal hdr_dat_copi            : t_mem_copi := c_mem_copi_rst;
+  signal hdr_dat_cipo            : t_mem_cipo;
+  signal reg_destinations_copi   : t_mem_copi := c_mem_mosi_rst;
+  signal reg_destinations_cipo   : t_mem_cipo;
+  signal reg_dp_xonoff_copi      : t_mem_copi := c_mem_copi_rst;
+  signal reg_dp_xonoff_cipo      : t_mem_cipo;
+
+  signal bdo_eth_src_mac  : std_logic_vector(c_network_eth_mac_addr_w - 1 downto 0);
+  signal bdo_ip_src_addr  : std_logic_vector(c_network_ip_addr_w - 1 downto 0);
+  signal bdo_udp_src_port : std_logic_vector(c_network_udp_port_w - 1 downto 0);
+
+  signal bf_sosi          : t_dp_sosi;
+  signal bdo_sosi         : t_dp_sosi;
+  signal bdo_siso         : t_dp_siso;
+
+  -- dp_offload_rx
+  signal rx_hdr_dat_copi     : t_mem_copi := c_mem_copi_rst;
+  signal rx_hdr_dat_cipo     : t_mem_cipo;
+  signal rx_hdr_fields_out   : std_logic_vector(1023 downto 0);
+  signal rx_hdr_fields_raw   : std_logic_vector(1023 downto 0) := (others => '0');
+  signal rx_beamlet_header   : t_sdp_cep_header;
+  signal exp_beamlet_header  : t_sdp_cep_header;
+  signal exp_dp_bsn          : natural;
+
+  -- Beamlets packets data
+  signal rx_beamlet_data     : std_logic_vector(c_longword_w - 1 downto 0);  -- 64 bit
+  signal rx_beamlet_sosi     : t_dp_sosi := c_dp_sosi_rst;
+  signal rx_beamlet_sop_cnt  : natural := 0;
+  signal rx_beamlet_eop_cnt  : natural := 0;
+
+  -- [0 : 3] =  X, Y, X, Y
+  signal rx_beamlet_arr_re   : t_sdp_beamlet_part_arr;
+  signal rx_beamlet_arr_im   : t_sdp_beamlet_part_arr;
+  signal rx_beamlet_cnt      : natural;
+  signal rx_beamlet_valid    : std_logic;
+
+  -- [0 : 4 * 488 * 2 - 1] = [0 : 3903]
+  signal rx_packet_list_re    : t_sdp_beamlet_packet_list;
+  signal rx_packet_list_im    : t_sdp_beamlet_packet_list;
+  signal rx_beamlet_list_re   : t_sdp_beamlet_packet_list;
+  signal rx_beamlet_list_im   : t_sdp_beamlet_packet_list;
+  signal rx_beamlet_list_val  : std_logic := '0';
+  -- Use +c_beamlet_mod to ensure >= 0 to fit in natural, use mod c_beamlet_mod
+  -- to fit count in c_sdp_W_beamlet bits
+  signal prev_re              : natural := (c_init_re - 1 + c_beamlet_mod) mod c_beamlet_mod;
+  signal prev_im              : natural := (c_init_im - 1 + c_beamlet_mod) mod c_beamlet_mod;
+begin
+  dp_rst <= '1', '0' after c_dp_clk_period * 7;
+  dp_clk <= (not dp_clk) or tb_end after c_dp_clk_period / 2;
+
+  mm_rst <= '1', '0' after c_mm_clk_period * 7;
+  mm_clk <= (not mm_clk) or tb_end after c_mm_clk_period / 2;
+
+  p_mm : process
+  begin
+    proc_common_wait_until_low(dp_clk, mm_rst);
+    proc_common_wait_some_cycles(mm_clk, 10);
+
+    ----------------------------------------------------------------------------
+    -- BDO header fields
+    ----------------------------------------------------------------------------
+    -- . Use sim default dst and src MAC, IP, UDP port from sdp_pkg.vhd and
+    --   based on c_gn_id
+    -- . use signed to fit 32 b in integer
+    proc_mem_mm_bus_wr(39, to_uint(c_cep_eth_src_mac(47 downto 32)), mm_clk, hdr_dat_cipo, hdr_dat_copi);
+    proc_mem_mm_bus_wr(38, to_sint(c_cep_eth_src_mac(31 downto 0)), mm_clk, hdr_dat_cipo, hdr_dat_copi);
+    proc_mem_mm_bus_wr(26, to_sint(c_cep_ip_src_addr), mm_clk, hdr_dat_cipo, hdr_dat_copi);
+    proc_mem_mm_bus_wr(24, to_uint(c_cep_udp_src_port), mm_clk, hdr_dat_cipo, hdr_dat_copi);
+    proc_mem_mm_bus_wr(41, to_uint(c_sdp_cep_eth_dst_mac(47 downto 32)), mm_clk, hdr_dat_cipo, hdr_dat_copi);
+    proc_mem_mm_bus_wr(40, to_sint(c_sdp_cep_eth_dst_mac(31 downto 0)), mm_clk, hdr_dat_cipo, hdr_dat_copi);
+    proc_mem_mm_bus_wr(25, to_sint(c_sdp_cep_ip_dst_addr), mm_clk, hdr_dat_cipo, hdr_dat_copi);
+    proc_mem_mm_bus_wr(23, to_uint(c_sdp_cep_udp_dst_port), mm_clk, hdr_dat_cipo, hdr_dat_copi);
+
+    ----------------------------------------------------------------------------
+    -- Enable beamlet output (dp_xonoff)
+    ----------------------------------------------------------------------------
+    proc_mem_mm_bus_wr(0, 1, mm_clk, reg_dp_xonoff_cipo, reg_dp_xonoff_copi);
+    wait;
+  end process;
+
+  u_bf_sosi : entity dp_lib.dp_stream_stimuli
+  generic map (
+    -- initializations
+    g_sync_period    => 10,
+    g_sync_offset    => 0,
+    g_use_complex    => true,
+    g_re_init        => c_init_re,
+    g_im_init        => c_init_im,
+    g_bsn_init       => TO_DP_BSN(c_init_bsn),
+    g_err_init       => 0,  -- not used
+    g_err_incr       => 0,  -- not used
+    g_channel_init   => 0,  -- not used
+    g_channel_incr   => 0,  -- not used
+    -- specific
+    g_in_dat_w       => c_sdp_W_beamlet,  -- = 8
+    g_nof_repeat     => g_nof_repeat,
+    g_pkt_len        => c_bf_block_len,
+    g_pkt_gap        => c_bf_gap_size,
+    g_wait_last_evt  => 100
+  )
+  port map (
+    rst               => dp_rst,
+    clk               => dp_clk,
+
+    -- Generate stimuli
+    src_out           => bf_sosi,
+
+    -- End of stimuli
+    last_snk_in       => open,  -- expected verify_snk_in after end of stimuli
+    last_snk_in_evt   => open,  -- trigger verify to verify the last_snk_in
+    tb_end            => tb_end
+  );
+
+  -- Beamlet Data Output (BDO)
+  u_dut: entity work.sdp_beamformer_output
+  generic map (
+    g_beamset_id                => g_beamset_id,
+    g_use_transpose             => g_use_transpose,
+    g_use_multiple_destinations => g_use_multiple_destinations,
+    g_sim_force_bsn_error       => false
+  )
+  port map (
+    mm_clk => mm_clk,
+    mm_rst => mm_rst,
+
+    dp_clk => dp_clk,
+    dp_rst => dp_rst,
+
+    reg_hdr_dat_mosi      => hdr_dat_copi,
+    reg_hdr_dat_miso      => hdr_dat_cipo,
+
+    reg_destinations_copi => reg_destinations_copi,
+    reg_destinations_cipo => reg_destinations_cipo,
+
+    reg_dp_xonoff_mosi    => reg_dp_xonoff_copi,
+    reg_dp_xonoff_miso    => reg_dp_xonoff_cipo,
+
+    in_sosi        => bf_sosi,
+    out_sosi       => bdo_sosi,
+    out_siso       => bdo_siso,
+
+    sdp_info       => c_exp_sdp_info,
+    beamlet_scale  => c_exp_beamlet_scale_slv,
+    gn_id          => c_gn_id_slv,
+
+    -- Source MAC/IP/UDP are not used, c_sdp_cep_hdr_field_sel selects MM programmable instead
+    eth_src_mac    => bdo_eth_src_mac,
+    ip_src_addr    => bdo_ip_src_addr,
+    udp_src_port   => bdo_udp_src_port,
+
+    hdr_fields_out => open
+  );
+
+  u_rx : entity dp_lib.dp_offload_rx
+  generic map (
+    g_nof_streams         => 1,
+    g_data_w              => c_longword_w,
+    g_symbol_w            => c_octet_w,
+    g_hdr_field_arr       => c_sdp_cep_hdr_field_arr,
+    g_remove_crc          => false,
+    g_crc_nof_words       => 0
+  )
+  port map (
+    mm_rst                => mm_rst,
+    mm_clk                => mm_clk,
+
+    dp_rst                => dp_rst,
+    dp_clk                => dp_clk,
+
+    reg_hdr_dat_mosi      => rx_hdr_dat_copi,
+    reg_hdr_dat_miso      => rx_hdr_dat_cipo,
+
+    snk_in_arr(0)         => bdo_sosi,
+    snk_out_arr(0)        => bdo_siso,
+
+    src_out_arr(0)        => rx_beamlet_sosi,
+
+    hdr_fields_out_arr(0) => rx_hdr_fields_out,
+    hdr_fields_raw_arr(0) => rx_hdr_fields_raw
+  );
+
+  -----------------------------------------------------------------------------
+  -- Beamlet offload packet header
+  -----------------------------------------------------------------------------
+
+  -- Counters to time expected cep_header fields per offload packet
+  p_test_counters : process(dp_clk)
+  begin
+    if rising_edge(dp_clk) then
+      -- Count rx_beamlet_sosi packets
+      if rx_beamlet_sosi.sop = '1' then
+        rx_beamlet_sop_cnt <= rx_beamlet_sop_cnt + 1;  -- early count
+      end if;
+      if rx_beamlet_sosi.eop = '1' then
+        rx_beamlet_eop_cnt <= rx_beamlet_eop_cnt + 1;  -- after count
+      end if;
+    end if;
+  end process;
+
+  -- Prepare exp_beamlet_header before rx_beamlet_sosi.eop, so that
+  -- p_verify_beamlet_header can verify it at rx_beamlet_sosi.eop.
+  exp_beamlet_header <= func_sdp_compose_cep_header(c_exp_ip_header_checksum,
+                                                    c_exp_sdp_info,
+                                                    c_gn_id,
+                                                    c_exp_payload_error,
+                                                    c_exp_beamlet_scale,
+                                                    c_exp_beamlet_index,
+                                                    exp_dp_bsn);
+
+  rx_beamlet_header <= func_sdp_map_cep_header(rx_hdr_fields_raw);
+
+  p_verify_beamlet_header : process
+    variable v_bool    : boolean;
+  begin
+    wait until rising_edge(dp_clk);
+    -- Prepare exp_sdp_cep_header at sop, so that it can be verified at eop
+    if rx_beamlet_sosi.sop = '1' then
+      -- Expected BSN increments by c_sdp_cep_nof_blocks_per_packet = 4 blocks
+      -- per packet
+      exp_dp_bsn <= c_init_bsn + rx_beamlet_sop_cnt * c_sdp_cep_nof_blocks_per_packet;
+    end if;
+
+    -- Verify header at eop
+    if rx_beamlet_sosi.eop = '1' then
+      v_bool := func_sdp_verify_cep_header(rx_beamlet_header, exp_beamlet_header);
+    end if;
+  end process;
+
+  -----------------------------------------------------------------------------
+  -- Beamlet offload packet data
+  -----------------------------------------------------------------------------
+  -- To view the 64 bit 10GbE offload data more easily in the Wave window
+  rx_beamlet_data <= rx_beamlet_sosi.data(c_longword_w - 1 downto 0);
+
+  proc_sdp_rx_beamlet_octets(dp_clk,
+                             rx_beamlet_sosi,
+                             rx_beamlet_cnt,
+                             rx_beamlet_valid,
+                             rx_beamlet_arr_re,
+                             rx_beamlet_arr_im,
+                             rx_packet_list_re,
+                             rx_packet_list_im);
+
+  p_verify_rx_beamlet_list : process
+    -- Nof complex (= nof re = nof im = c_N) values in t_sdp_beamlet_packet_list
+    constant c_N : natural := c_sdp_cep_nof_beamlets_per_packet * c_sdp_N_pol_bf;
+    variable v_prev_re : natural := prev_re;
+    variable v_prev_im : natural := prev_im;
+  begin
+    -- Wait until end of a beamlet packet
+    -- . use at least one wait statement in process to avoid Modelsim warning: (vcom-1090)
+    wait until rising_edge(dp_clk);
+    proc_common_wait_until_hi_lo(dp_clk, rx_beamlet_sosi.eop);
+    if g_use_transpose then
+      -- Undo the beamlet output transpose, to have original beamlet order
+      rx_beamlet_list_re <= func_sdp_undo_transpose_beamlet_packet(rx_packet_list_re);
+      rx_beamlet_list_im <= func_sdp_undo_transpose_beamlet_packet(rx_packet_list_im);
+    else
+      -- Copy identity beamlet output order
+      rx_beamlet_list_re <= rx_packet_list_re;
+      rx_beamlet_list_im <= rx_packet_list_im;
+    end if;
+    rx_beamlet_list_val <= '1';
+
+    -- Wait until rx_beamlet_list is valid
+    wait until rising_edge(dp_clk);
+    rx_beamlet_list_val <= '0';
+    -- Verify rx_beamlet_list
+    for vI in 0 to c_N - 1 loop
+      -- Verify incrementing beamlets
+      v_prev_re := (v_prev_re + 1) mod c_beamlet_mod;
+      v_prev_im := (v_prev_im + 1) mod c_beamlet_mod;
+      assert to_uint(rx_beamlet_list_re(vI)) = v_prev_re report "Wrong re_beamlet." severity error;
+      assert to_uint(rx_beamlet_list_im(vI)) = v_prev_im report "Wrong im_beamlet." severity error;
+    end loop;
+    prev_re <= v_prev_re;
+    prev_im <= v_prev_im;
+  end process;
+end tb;
diff --git a/applications/lofar2/libraries/sdp/tb/vhdl/tb_sdp_pkg.vhd b/applications/lofar2/libraries/sdp/tb/vhdl/tb_sdp_pkg.vhd
index e49931e96a21654d8858ac39830149adf432b777..63ca0848374b7becbbf893c94cc36fdd8ba8a019 100644
--- a/applications/lofar2/libraries/sdp/tb/vhdl/tb_sdp_pkg.vhd
+++ b/applications/lofar2/libraries/sdp/tb/vhdl/tb_sdp_pkg.vhd
@@ -25,10 +25,12 @@
 -- . This package contains specific constants, functions for sdp test benches.
 -- Description:
 -------------------------------------------------------------------------------
-library IEEE, common_lib, reorder_lib;
+library IEEE, common_lib, dp_lib, reorder_lib;
 use IEEE.std_logic_1164.all;
 use common_lib.common_pkg.all;
 use common_lib.common_network_layers_pkg.all;
+use common_lib.tb_common_pkg.all;
+use dp_lib.dp_stream_pkg.all;
 use reorder_lib.reorder_pkg.all;
 use work.sdp_pkg.all;
 
@@ -121,9 +123,19 @@ package tb_sdp_pkg is
   -- . use separate list for re and for im
   subtype t_sdp_beamlet_block_list is t_slv_8_arr(0 to c_sdp_cep_nof_beamlets_per_block * c_sdp_N_pol_bf - 1);
 
-  function func_sdp_bdo_transpose_packet(nof_blocks_per_packet : natural;
-                                         nof_beamlets_per_block : natural;
-                                         packet_list : t_sdp_beamlet_packet_list) return t_sdp_beamlet_packet_list;
+  function func_sdp_transpose_beamlet_packet(     packet_list : t_sdp_beamlet_packet_list) return t_sdp_beamlet_packet_list;
+  function func_sdp_undo_transpose_beamlet_packet(packet_list : t_sdp_beamlet_packet_list) return t_sdp_beamlet_packet_list;
+
+  -- Read beamlet packet octets per re and im parts
+  procedure proc_sdp_rx_beamlet_octets(
+      signal clk               : in std_logic;
+      signal rx_beamlet_sosi   : in t_dp_sosi;
+      signal rx_beamlet_cnt    : inout natural;
+      signal rx_beamlet_valid  : out std_logic;
+      signal rx_beamlet_arr_re : out t_sdp_beamlet_part_arr;
+      signal rx_beamlet_arr_im : out t_sdp_beamlet_part_arr;
+      signal rx_packet_list_re : out t_sdp_beamlet_packet_list;
+      signal rx_packet_list_im : out t_sdp_beamlet_packet_list);
 end package tb_sdp_pkg;
 
 package body tb_sdp_pkg is
@@ -492,12 +504,13 @@ package body tb_sdp_pkg is
   end;
 
   -- BDO transpose:
-  -- . See sdp/src/python/test_func_sdp_bdo_transpose_packet.py to verify that
-  --   v_out = func_sdp_bdo_transpose_packet(4, 488, v_in) yields the expected v_out.
+  -- . See sdp/src/python/test_func_sdp_transpose_packet.py to verify that
+  --   v_out = func_sdp_transpose_beamlet_packet(v_in) yields the expected v_out.
   -- . See data repacking section in:
   --   https://support.astron.nl/confluence/pages/viewpage.action?spaceKey=L2M&title=L4+SDPFW+Decision%3A+Multiple+beamlet+output+destinations
-  -- . Use separate packet_list for re and im. The list contain 4 * 488 * 2 = 3904
-  --   beamlet part octet values.
+  -- . Use separate t_sdp_beamlet_packet_list for re and im. The packet_list
+  --   contains nof_blocks_per_packet * nof_beamlets_per_block * c_sdp_N_pol_bf
+  --   = 4 * 488 * 2 = 3904 beamlet part octet values.
   -- input packet_list:
   -- . blk               0,            1,            2,            3,  for nof_blocks_per_packet = 4
   -- . blet   0,   ... 487, 0,   ... 487, 0,   ... 487, 0,   ... 487,  for nof_beamlets_per_block = 488
@@ -512,16 +525,81 @@ package body tb_sdp_pkg is
   --         ...,       ...,       ...,       ...,
   --     972,973, 1948,1949, 2924,2925, 3900,3901,
   --     974,775, 1950,1951, 2926,2927, 3902,3903, output list index
-  function func_sdp_bdo_transpose_packet(nof_blocks_per_packet : natural;
-                                         nof_beamlets_per_block : natural;
-                                         packet_list : t_sdp_beamlet_packet_list) return t_sdp_beamlet_packet_list is
+  function func_sdp_transpose_beamlet_packet(packet_list : t_sdp_beamlet_packet_list) return t_sdp_beamlet_packet_list is
     variable v_list : t_sdp_beamlet_packet_list;
   begin
-    v_list := func_reorder_transpose_packet(nof_blocks_per_packet,
-                                            nof_beamlets_per_block,
+    v_list := func_reorder_transpose_packet(c_sdp_cep_nof_blocks_per_packet,
+                                            c_sdp_cep_nof_beamlets_per_block,
                                             c_sdp_N_pol_bf,
                                             packet_list);
     return v_list;
-  end func_sdp_bdo_transpose_packet;
+  end func_sdp_transpose_beamlet_packet;
+
+  -- Reverse argument order nof_beamlets_per_block and nof_blocks_per_packet to undo transpose
+  function func_sdp_undo_transpose_beamlet_packet(packet_list : t_sdp_beamlet_packet_list) return t_sdp_beamlet_packet_list is
+    variable v_list : t_sdp_beamlet_packet_list;
+  begin
+    v_list := func_reorder_transpose_packet(c_sdp_cep_nof_beamlets_per_block,
+                                            c_sdp_cep_nof_blocks_per_packet,
+                                            c_sdp_N_pol_bf,
+                                            packet_list);
+    return v_list;
+  end func_sdp_undo_transpose_beamlet_packet;
+
+  -----------------------------------------------------------------------------
+  -- CEP Read Rx 10GbE Stream
+  -----------------------------------------------------------------------------
+  -- Show received beamlets from 10GbE stream in Wave Window
+  -- . The packet header is 9.25 longwords wide. The dp_offload_rx has stripped
+  --   the header and has realigned the payload at a longword boundary.
+  -- . Expect c_sdp_cep_nof_beamlets_per_block = c_sdp_S_sub_bf = 488 dual pol
+  --   and complex beamlets per packet, so 2 dual pol beamlets/64b data word.
+  -- . Beamlets array is stored big endian in the data, so X.real index 0 first
+  --   in MSByte of rx_beamlet_sosi.data.
+  procedure proc_sdp_rx_beamlet_octets(
+      signal clk               : in std_logic;
+      signal rx_beamlet_sosi   : in t_dp_sosi;
+      signal rx_beamlet_cnt    : inout natural;
+      signal rx_beamlet_valid  : out std_logic;
+      signal rx_beamlet_arr_re : out t_sdp_beamlet_part_arr;
+      signal rx_beamlet_arr_im : out t_sdp_beamlet_part_arr;
+      signal rx_packet_list_re : out t_sdp_beamlet_packet_list;
+      signal rx_packet_list_im : out t_sdp_beamlet_packet_list) is
+  begin
+    rx_beamlet_cnt <= 0;
+    rx_beamlet_valid <= '0';
+    -- Wait until start of a beamlet packet
+    proc_common_wait_until_high(clk, rx_beamlet_sosi.sop);
+    -- c_sdp_nof_beamlets_per_longword = 2 dual pol beamlets (= XY, XY) per 64b data word
+    for I in 0 to (c_sdp_cep_nof_beamlets_per_packet / c_sdp_nof_beamlets_per_longword) - 1 loop
+      proc_common_wait_until_high(clk, rx_beamlet_sosi.valid);
+      rx_beamlet_valid <= '1';
+      -- Capture rx beamlets per longword in rx_beamlet_arr, for time series view in Wave window
+      rx_beamlet_arr_re(0) <= rx_beamlet_sosi.data(63 downto 56);  -- X
+      rx_beamlet_arr_im(0) <= rx_beamlet_sosi.data(55 downto 48);
+      rx_beamlet_arr_re(1) <= rx_beamlet_sosi.data(47 downto 40);  -- Y
+      rx_beamlet_arr_im(1) <= rx_beamlet_sosi.data(39 downto 32);
+      rx_beamlet_arr_re(2) <= rx_beamlet_sosi.data(31 downto 24);  -- X
+      rx_beamlet_arr_im(2) <= rx_beamlet_sosi.data(23 downto 16);
+      rx_beamlet_arr_re(3) <= rx_beamlet_sosi.data(15 downto 8);  -- Y
+      rx_beamlet_arr_im(3) <= rx_beamlet_sosi.data( 7 downto 0);
+      -- Capture the beamlets block of each packet in rx_packet_list
+      rx_packet_list_re(I * 4 + 0) <= rx_beamlet_sosi.data(63 downto 56);  -- X
+      rx_packet_list_im(I * 4 + 0) <= rx_beamlet_sosi.data(55 downto 48);
+      rx_packet_list_re(I * 4 + 1) <= rx_beamlet_sosi.data(47 downto 40);  -- Y
+      rx_packet_list_im(I * 4 + 1) <= rx_beamlet_sosi.data(39 downto 32);
+      rx_packet_list_re(I * 4 + 2) <= rx_beamlet_sosi.data(31 downto 24);  -- X
+      rx_packet_list_im(I * 4 + 2) <= rx_beamlet_sosi.data(23 downto 16);
+      rx_packet_list_re(I * 4 + 3) <= rx_beamlet_sosi.data(15 downto 8);  -- Y
+      rx_packet_list_im(I * 4 + 3) <= rx_beamlet_sosi.data( 7 downto 0);
+      proc_common_wait_until_high(clk, rx_beamlet_sosi.valid);
+      -- Use at least one WAIT instead of proc_common_wait_some_cycles() to
+      -- avoid Modelsim warning: (vcom-1090) Possible infinite loop: Process
+      -- contains no WAIT statement.
+      wait until rising_edge(clk);
+      rx_beamlet_valid <= '0';
+      rx_beamlet_cnt   <= (rx_beamlet_cnt + c_sdp_nof_beamlets_per_longword) mod c_sdp_cep_nof_beamlets_per_block;  -- 4 blocks/packet
+    end loop;
+  end proc_sdp_rx_beamlet_octets;
 
 end tb_sdp_pkg;
diff --git a/applications/lofar2/libraries/sdp/tb/vhdl/tb_tb_sdp_beamformer_output.vhd b/applications/lofar2/libraries/sdp/tb/vhdl/tb_tb_sdp_beamformer_output.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..881383bb6c521d754b60c6ed98b5761c77d57f1e
--- /dev/null
+++ b/applications/lofar2/libraries/sdp/tb/vhdl/tb_tb_sdp_beamformer_output.vhd
@@ -0,0 +1,46 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2023
+-- 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: Verify multiple variations of tb_sdp_beamformer_output
+-- Description:
+-- Usage:
+-- > as 5
+-- > run -all
+-------------------------------------------------------------------------------
+
+library IEEE;
+use IEEE.std_logic_1164.all;
+
+entity tb_tb_sdp_beamformer_output is
+end tb_tb_sdp_beamformer_output;
+
+architecture tb of tb_tb_sdp_beamformer_output is
+  signal tb_end : std_logic := '0';  -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end'
+begin
+  -- g_nof_repeat                : natural := 10;
+  -- g_beamset_id                : natural := 0;
+  -- g_use_transpose             : boolean := false;
+  -- g_use_multiple_destinations : boolean := false
+
+  u_one_identity   : entity work.tb_sdp_beamformer_output generic map( 50, 0, false, false);
+  u_one_transpose  : entity work.tb_sdp_beamformer_output generic map( 50, 0,  true, false);
+end tb;
diff --git a/libraries/base/common/src/vhdl/common_pkg.vhd b/libraries/base/common/src/vhdl/common_pkg.vhd
index 1814c905705c31c4381d1b2506587dbf325eb7dc..9fdebec8751b6b80700e888d8d71b31456b45b13 100644
--- a/libraries/base/common/src/vhdl/common_pkg.vhd
+++ b/libraries/base/common/src/vhdl/common_pkg.vhd
@@ -131,6 +131,7 @@ package common_pkg is
   constant c_nat_boolean_arr : t_nat_boolean_arr := (true, false);  -- array the two possible boolean values that can be iterated over
 
   type t_integer_matrix is array (integer range <>, integer range <>) of integer;
+  type t_natural_matrix is array (integer range <>, integer range <>) of natural;
   type t_boolean_matrix is array (integer range <>, integer range <>) of boolean;
   type t_sl_matrix      is array (integer range <>, integer range <>) of std_logic;
   type t_slv_8_matrix   is array (integer range <>, integer range <>) of std_logic_vector(7 downto 0);
diff --git a/libraries/base/dp/src/vhdl/dp_packet_merge.vhd b/libraries/base/dp/src/vhdl/dp_packet_merge.vhd
index 14dfd8a984cda38fe936af12e313163de94bf2e8..2de0853cc9f7d0b773b10ae9a3b8dd7e0e7944e0 100644
--- a/libraries/base/dp/src/vhdl/dp_packet_merge.vhd
+++ b/libraries/base/dp/src/vhdl/dp_packet_merge.vhd
@@ -46,6 +46,8 @@
 -- . with g_align_at_sync=true the merge can be forced to restart at a snk_in.sync.
 -- . Optional flow control dependent on g_use_ready and g_pipeline_ready, see
 --   description of dp_add_flow_control.
+-- . The input packets do not have to have equal length, because they are
+--   merged based on counting their eop.
 
 
 library IEEE,common_lib;
@@ -67,7 +69,7 @@ entity dp_packet_merge is
     rst         : in  std_logic;
     clk         : in  std_logic;
 
-    nof_pkt     : in  std_logic_vector(ceil_log2(g_nof_pkt + 1) - 1 downto 0) := TO_UVEC(g_nof_pkt, ceil_log2(g_nof_pkt + 1));
+    nof_pkt     : in  std_logic_vector(ceil_log2(g_nof_pkt + 1) - 1 downto 0) := to_uvec(g_nof_pkt, ceil_log2(g_nof_pkt + 1));
     nof_pkt_out : out std_logic_vector(ceil_log2(g_nof_pkt + 1) - 1 downto 0);  -- Valid at src_out.sop
 
     snk_out     : out t_dp_siso;
@@ -79,10 +81,13 @@ entity dp_packet_merge is
 end dp_packet_merge;
 
 architecture rtl of dp_packet_merge is
+  constant c_nof_pkt_max : natural := g_nof_pkt + 1;
+  constant c_nof_pkt_w   : natural := ceil_log2(c_nof_pkt_max);
+
   type t_reg is record
-    nof_pkt     : natural range 0 to g_nof_pkt + 1;
-    pkt_cnt     : natural range 0 to g_nof_pkt + 1;
-    align_cnt   : natural range 0 to g_nof_pkt + 1;
+    nof_pkt     : natural range 0 to c_nof_pkt_max;
+    pkt_cnt     : natural range 0 to c_nof_pkt_max;
+    align_cnt   : natural range 0 to c_nof_pkt_max;
     busy        : std_logic;
     sync        : std_logic;
     next_bsn    : std_logic_vector(c_dp_stream_bsn_w - 1 downto 0);
@@ -90,10 +95,12 @@ architecture rtl of dp_packet_merge is
     src_out     : t_dp_sosi;
   end record;
 
+  constant c_reg_rst : t_reg := (0, 0, 0, '0', '0', (others =>'0'), '0', c_dp_sosi_rst);
+
   signal r, nxt_r : t_reg;
 begin
   -- Map logic function outputs to entity outputs
-  nof_pkt_out <= TO_UVEC(r.nof_pkt, ceil_log2(g_nof_pkt + 1));
+  nof_pkt_out <= to_uvec(r.nof_pkt, c_nof_pkt_w);
 
   u_dp_add_flow_control : entity work.dp_add_flow_control
     generic map (
@@ -124,7 +131,7 @@ begin
     v := r;
 
     ----------------------------------------------------------------------------
-    -- Function
+    -- Function: merge input packets into one output packet
     --                       _         _         _         _         _         _            _         _         _
     --   snk_in.sop    _____|0|_______|1|_______|2|_______|0|_______|1|_______|2|__________|0|_______|1|_______|2|_______
     --                               _         _         _         _         _         _            _         _         _
@@ -150,29 +157,6 @@ begin
       end if;
     end if;
 
-    -- capture nof_pkt between output packets when there is no merge busy
-    if snk_in.sop = '1' then
-      v.busy := '1';
-    end if;
-    if snk_in.eop = '1' then
-      if r.pkt_cnt = r.nof_pkt - 1 then
-        v.busy := '0';
-      end if;
-    end if;
-
-    if g_align_at_sync then
-      if snk_in.sync = '1' then
-        if r.pkt_cnt > 0 then
-          v.align_cnt := r.nof_pkt - r.pkt_cnt;  -- = 0 when sync occurs at integer number of merged blocks, > 0 when (re)align to sync is needed
-        end if;
-      end if;
-    end if;
-
-    if v.busy = '0' and v.src_out.eop = '1' then
-      v.nof_pkt := TO_UINT(nof_pkt) - r.align_cnt;  -- use shorter next merge to (re)align to sync (when g_align_at_sync=false then r.align_cnt=0)
-      v.align_cnt := 0;
-    end if;
-
     -- output packet bsn, sync, sop and eop
     v.src_out         := snk_in;
     v.src_out.bsn     := r.src_out.bsn;
@@ -222,6 +206,29 @@ begin
       end if;
     end if;
 
+    -- capture nof_pkt between output packets when there is no merge busy
+    -- . For determining busy, using snk_in.sop is functionally equivalent to
+    --   using v.src_out.sop, but using snk_in.sop may ease timing closure.
+    if snk_in.sop = '1' then
+      v.busy := '1';
+    end if;
+    if v.src_out.eop = '1' then
+      v.busy := '0';
+    end if;
+
+    if g_align_at_sync then
+      if snk_in.sync = '1' then
+        if r.pkt_cnt > 0 then
+          v.align_cnt := r.nof_pkt - r.pkt_cnt;  -- = 0 when sync occurs at integer number of merged blocks, > 0 when (re)align to sync is needed
+        end if;
+      end if;
+    end if;
+
+    if v.busy = '0' then
+      v.nof_pkt := TO_UINT(nof_pkt) - r.align_cnt;  -- use shorter next merge to (re)align to sync (when g_align_at_sync=false then r.align_cnt=0)
+      v.align_cnt := 0;
+    end if;
+
     -- force sosi control to inactive for no output when nof_pkt=0
     if r.nof_pkt = 0 then
       v.src_out.valid := '0';
@@ -234,14 +241,7 @@ begin
     ----------------------------------------------------------------------------
     -- Reset and nxt_r
     if rst = '1' then
-      v.nof_pkt     := TO_UINT(nof_pkt);
-      v.pkt_cnt     := 0;
-      v.align_cnt   := 0;
-      v.busy        := '0';
-      v.sync        := '0';
-      v.next_bsn    := (others => '0');
-      v.bsn_err     := '0';
-      v.src_out     := c_dp_sosi_rst;
+      v := c_reg_rst;
     end if;
 
     nxt_r <= v;
diff --git a/libraries/base/dp/src/vhdl/dp_packet_unmerge.vhd b/libraries/base/dp/src/vhdl/dp_packet_unmerge.vhd
index 9a8401456e7753a45023be5912ec77afb4eb0af7..26c9f6656e7eab03f42af23ad26e6ce03b71d75e 100644
--- a/libraries/base/dp/src/vhdl/dp_packet_unmerge.vhd
+++ b/libraries/base/dp/src/vhdl/dp_packet_unmerge.vhd
@@ -22,13 +22,21 @@
 -- Author: E. Kooistra
 -- Purpose: Unmerge each input packet into output packets of length g_pkt_len.
 -- Description:
--- . The merged packet length of the snk_in input packets must be an integer
---   multiple of g_pkt_len. The number of output packets g_nof_pkt_max is only
---   used to determine the maximum supported pkt_cnt range. The actual number
---   of output packets has to be <= g_nof_pkt_max, and is determined by input
---   packet length / g_pkt_len. Hence the dp_packet_unmerge can dynamically
---   handle different sizes of input packets, provided that their length is an
---   integer multiple of g_pkt_len.
+-- . The merged packet length of the snk_in input packets is unmerged into
+--   ceil(input packet length / pkt_len). The unmerged output packets have
+--   length pkt_len, but if input packet length mod pkt_len /= 0, then the
+--   last packet will contain the remaining data of the input packet and have
+--   length < pkt_len. Corner cases:
+--   - If pkt_len > input packet length, then the output = input
+--   - The length of the last output packet is input packet length mod
+--     pkt_len, so it can have minimal lenght 1.
+-- . The dynamic pkt_len value is captured at the input eop, or between input
+--   packets, to have a stable value during the unmerge.
+-- . The number of output packets g_nof_pkt is only used to determine the
+--   maximum supported pkt_cnt range. The actual number of output packets has
+--   to be <= g_nof_pkt, and is determined by input packet length /
+--   pkt_len. Hence the dp_packet_unmerge can dynamically handle different
+--   sizes of input packets.
 -- . The pkt_cnt is passed on as src_out.channel index.
 -- . The g_bsn_increment sets the BSN increment for the unmerged output
 --   packets relative to the BSN of the snk_in input packet. When
@@ -51,6 +59,9 @@
 --
 -- . Optional flow control dependent on g_use_ready and g_pipeline_ready, see
 --   description of dp_add_flow_control.
+-- . g_pkt_len statically sets the length of the unmerged packets in absence of
+--   dynamic pkt_len control. When the pkt_len control input is used, g_pkt_len
+--   sets the maximum number length of the unmerged packets.
 
 library IEEE,common_lib;
 use IEEE.std_logic_1164.all;
@@ -62,7 +73,7 @@ entity dp_packet_unmerge is
   generic (
     g_use_ready       : boolean := true;
     g_pipeline_ready  : boolean := false;
-    g_nof_pkt_max     : natural := 1;  -- Maximum nof packets to unmerge each incoming packet to
+    g_nof_pkt         : natural := 1;  -- Number of packets to unmerge each incoming packet to
     g_pkt_len         : natural := 1;  -- Length of the unmerged packets
     g_bsn_increment   : natural := 0
   );
@@ -70,6 +81,9 @@ entity dp_packet_unmerge is
     rst         : in  std_logic;
     clk         : in  std_logic;
 
+    pkt_len     : in  std_logic_vector(ceil_log2(g_pkt_len + 1) - 1 downto 0) := to_uvec(g_pkt_len, ceil_log2(g_pkt_len + 1));
+    pkt_len_out : out std_logic_vector(ceil_log2(g_pkt_len + 1) - 1 downto 0);  -- Valid at src_out.sop
+
     snk_out     : out t_dp_siso;
     snk_in      : in  t_dp_sosi;
 
@@ -79,20 +93,28 @@ entity dp_packet_unmerge is
 end dp_packet_unmerge;
 
 architecture rtl of dp_packet_unmerge is
+  constant c_nof_pkt_max : natural := g_nof_pkt + 1;
+  constant c_pkt_len_max : natural := g_pkt_len + 1;
+  constant c_pkt_len_w   : natural := ceil_log2(c_pkt_len_max);
+
   -- Internal state of logic function
   type t_reg is record
-    pkt_cnt     : natural range 0 to g_nof_pkt_max + 1;
-    val_cnt     : natural range 0 to g_pkt_len + 1;
+    pkt_cnt     : natural range 0 to c_nof_pkt_max;
+    pkt_len     : natural range 0 to c_pkt_len_max;
+    val_cnt     : natural range 0 to c_pkt_len_max;
+    busy        : std_logic;
     src_out     : t_dp_sosi;
   end record;
 
-  constant c_reg_rst : t_reg := (0, 0, c_dp_sosi_rst);
+  constant c_reg_rst : t_reg := (0, 0, 0, '0', c_dp_sosi_rst);
 
   signal r : t_reg;
   signal d : t_reg;
 
 begin
   -- Map logic function outputs to entity outputs
+  pkt_len_out <= to_uvec(r.pkt_len, c_pkt_len_w);
+
   u_dp_add_flow_control : entity work.dp_add_flow_control
     generic map (
       g_use_ready       => g_use_ready,
@@ -114,19 +136,19 @@ begin
   r <= d when rising_edge(clk);
 
   -- Logic function
-  p_comb : process(rst, r, snk_in)
+  p_comb : process(rst, r, snk_in, pkt_len)
     variable v : t_reg;
   begin
     -- Default
     v := r;
 
-    -- Function
-    --                   _                 _                 _
-    --   snk_in.sop   __| |_______________| |_______________| |_______________|
-    --                                  _                 _                 _
-    --   snk_in.eop   _________________| |_______________| |_______________| |_
-    --                   _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _
-    --   snk_in.valid __|
+    -- Function: unmerge input packet into output packets
+    --                    _                 _                 _
+    --   snk_in.sop    __| |_______________| |_______________| |_______________|
+    --                                   _                 _                 _
+    --   snk_in.eop    _________________| |_______________| |_______________| |_
+    --                    _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _
+    --   snk_in.valid  __|
     --   v.val_cnt        0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1
     --   v.pkt_cnt        0     1     2     0     1     2     0     1     2
     --                    ______________   _______________   _______________   _
@@ -154,6 +176,18 @@ begin
       v.pkt_cnt := r.pkt_cnt + 1;
     end if;
 
+    -- capture pkt_len between input packets when there is no unmerge busy
+    if snk_in.sop = '1' then
+      v.busy := '1';
+    end if;
+    if snk_in.eop = '1' then
+      v.busy := '0';
+    end if;
+
+    if v.busy = '0' then
+      v.pkt_len := to_uint(pkt_len);
+    end if;
+
     -- output packet bsn, sync, sop and eop
     -- . Default passes on snk_in.sync, valid, data, re, im
     -- . The sync, valid can be passed on from snk_in, without checking
@@ -173,7 +207,12 @@ begin
       if v.val_cnt = 0 then
         v.src_out.sop := '1';
       end if;
-      if v.val_cnt = g_pkt_len - 1 then
+      -- Unmerge packets of length r.pkt_len
+      if v.val_cnt = r.pkt_len - 1 then
+        v.src_out.eop := '1';
+      end if;
+      -- Unmerge last packet with length <= r.pkt_len
+      if snk_in.eop = '1' then
         v.src_out.eop := '1';
       end if;
     end if;
diff --git a/libraries/base/dp/src/vhdl/dp_stream_pkg.vhd b/libraries/base/dp/src/vhdl/dp_stream_pkg.vhd
index 22b5cc127b9cc70e12ff83e8c407b8fe20242199..5f121726fe81f2f5338d6368fd25567015649795 100644
--- a/libraries/base/dp/src/vhdl/dp_stream_pkg.vhd
+++ b/libraries/base/dp/src/vhdl/dp_stream_pkg.vhd
@@ -251,14 +251,15 @@ package dp_stream_pkg is
   function TO_DP_EMPTY(   n : natural) return std_logic_vector;
   function TO_DP_CHANNEL( n : natural) return std_logic_vector;
   function TO_DP_ERROR(   n : natural) return std_logic_vector;
-  function RESIZE_DP_BSN(     vec : std_logic_vector) return std_logic_vector;
-  function RESIZE_DP_DATA(    vec : std_logic_vector) return std_logic_vector;  -- set unused MSBits to '0'
-  function RESIZE_DP_SDATA(   vec : std_logic_vector) return std_logic_vector;  -- sign extend unused MSBits
-  function RESIZE_DP_XDATA(   vec : std_logic_vector) return std_logic_vector;  -- set unused MSBits to 'X'
-  function RESIZE_DP_DSP_DATA(vec : std_logic_vector) return std_logic_vector;  -- sign extend unused MSBits of re and im fields
-  function RESIZE_DP_EMPTY(   vec : std_logic_vector) return std_logic_vector;
-  function RESIZE_DP_CHANNEL( vec : std_logic_vector) return std_logic_vector;
-  function RESIZE_DP_ERROR(   vec : std_logic_vector) return std_logic_vector;
+  function RESIZE_DP_BSN(      vec : std_logic_vector) return std_logic_vector;
+  function RESIZE_DP_DATA(     vec : std_logic_vector) return std_logic_vector;  -- set unused MSBits to '0'
+  function RESIZE_DP_SDATA(    vec : std_logic_vector) return std_logic_vector;  -- sign extend unused MSBits
+  function RESIZE_DP_XDATA(    vec : std_logic_vector) return std_logic_vector;  -- set unused MSBits to 'X'
+  function RESIZE_DP_DSP_DATA( vec : std_logic_vector) return std_logic_vector;  -- sign extend unused MSBits of re and im fields
+  function RESIZE_DP_DSP_UDATA(vec : std_logic_vector) return std_logic_vector;  -- set unused MSBits to '0'
+  function RESIZE_DP_EMPTY(    vec : std_logic_vector) return std_logic_vector;
+  function RESIZE_DP_CHANNEL(  vec : std_logic_vector) return std_logic_vector;
+  function RESIZE_DP_ERROR(    vec : std_logic_vector) return std_logic_vector;
 
   function INCR_DP_DATA(    vec : std_logic_vector; dec : integer; w : natural) return std_logic_vector;  -- unsigned vec(w-1:0) + dec
   function INCR_DP_SDATA(   vec : std_logic_vector; dec : integer; w : natural) return std_logic_vector;  -- signed vec(w-1:0) + dec
@@ -553,6 +554,11 @@ package body dp_stream_pkg is
     return RESIZE_SVEC(vec, c_dp_stream_dsp_data_w);
   end RESIZE_DP_DSP_DATA;
 
+  function RESIZE_DP_DSP_UDATA(vec : std_logic_vector) return std_logic_vector is
+  begin
+    return RESIZE_UVEC(vec, c_dp_stream_dsp_data_w);
+  end RESIZE_DP_DSP_UDATA;
+
   function RESIZE_DP_EMPTY(vec : std_logic_vector) return std_logic_vector is
   begin
     return RESIZE_UVEC(vec, c_dp_stream_empty_w);
diff --git a/libraries/base/dp/tb/vhdl/dp_stream_stimuli.vhd b/libraries/base/dp/tb/vhdl/dp_stream_stimuli.vhd
index 6937ac0de018e235f75f84c5e57b0306acc034e2..742233ad294cbf977d35b2f776fa852e960de337 100644
--- a/libraries/base/dp/tb/vhdl/dp_stream_stimuli.vhd
+++ b/libraries/base/dp/tb/vhdl/dp_stream_stimuli.vhd
@@ -143,9 +143,9 @@ begin
         v_sosi.data    := RESIZE_DP_DATA(v_sosi.data(g_in_dat_w - 1 downto 0));  -- wrap when >= 2**g_in_dat_w
       else
         v_sosi.re      := INCR_UVEC(v_sosi.re, g_pkt_len);
-        v_sosi.re      := RESIZE_DP_DSP_DATA(v_sosi.re(g_in_dat_w - 1 downto 0));  -- wrap when >= 2**g_in_dat_w
+        v_sosi.re      := RESIZE_DP_DSP_UDATA(v_sosi.re(g_in_dat_w - 1 downto 0));  -- wrap when >= 2**g_in_dat_w
         v_sosi.im      := INCR_UVEC(v_sosi.im, g_pkt_len);
-        v_sosi.im      := RESIZE_DP_DSP_DATA(v_sosi.im(g_in_dat_w - 1 downto 0));  -- wrap when >= 2**g_in_dat_w
+        v_sosi.im      := RESIZE_DP_DSP_UDATA(v_sosi.im(g_in_dat_w - 1 downto 0));  -- wrap when >= 2**g_in_dat_w
       end if;
       -- Send packet
       if g_use_complex = false then
diff --git a/libraries/base/dp/tb/vhdl/tb_dp_packet_merge_unmerge.vhd b/libraries/base/dp/tb/vhdl/tb_dp_packet_merge_unmerge.vhd
index 24e34cbaf24916cfd18356d48c1a80dfd0351acd..275d85700bd6a50eeeca057f5a52634eff691b37 100644
--- a/libraries/base/dp/tb/vhdl/tb_dp_packet_merge_unmerge.vhd
+++ b/libraries/base/dp/tb/vhdl/tb_dp_packet_merge_unmerge.vhd
@@ -21,11 +21,33 @@
 -- Purpose:
 -- . Test bench for dp_packet_merge and dp_packet_unmerge
 -- Description:
--- . The p_stimuli_st uses proc_dp_gen_block_data to generate g_nof_repeat packets for the DUT. The output of the DUT needs
---   to be similar e.g. by means of an inverse DUT component so that the proc_dp_verify_* procedures can be used to verify
---   that the counter data in the sosi data fields is passed on correctly. Furthermore the proc_dp_verify_* procedures
---   verify that the test bench has yielded output results at all and that the output valid, sop, eop, and ready fits the
---   streaming interface specification.
+-- . The p_stimuli_st uses proc_dp_gen_block_data to generate g_nof_repeat
+--   stimuli_src_out packets of length g_pkt_len_merge.
+-- . The u_dp_packet_merge merges g_nof_pkt_merge packets from stimuli_src_out
+--   into dp_packet_merge_src_out.
+-- . The p_stimuli_unmerge provides stimuli for the .err and .empty fields of
+--   dp_packet_merge_src_out to get dp_packet_merge_sosi
+-- . The u_dp_packet_unmerge unmerges the dp_packet_merge_sosi into packets
+--   of length g_pkt_len_unmerge into verify_snk_in. If g_pkt_len_unmerge /=
+--   g_pkt_len_merge, then:
+--   - the last unmerged packet may be shorter then g_pkt_len_unmerge, to
+--     contain the remaining data.
+--   - the number of unmerged packets c_nof_pkt_unmerge, may differ from
+--     g_nof_pkt_merge.
+--   - use c_bsn_increment_unmerge = 0 for u_dp_packet_unmerge and expect BSN
+--     increment of 0 for unmerged packets from same merged packet or
+--     c_nof_pkt_unmerge for unmerged packets from subsequent merged packets.
+--     Verify this only manually in wave window, because verifying different
+--     increments is not supported by proc_dp_verify_data().
+-- . The p_verify section does the verification of verify_snk_in.
+-- . Flow control is verified via g_flow_control_*.
+-- . Block diagram:
+--
+--   p_stimuli_st --> u_dp_packet_merge --+--> u_dp_packet_unmerge --> p_verify
+--                                        ^
+--                                        |
+--                    p_stimuli_unmerge --/
+--
 -- Usage:
 -- > as 10
 -- > run -all
@@ -49,9 +71,10 @@ entity tb_dp_packet_merge_unmerge is
     -- specific
     g_pipeline_ready         : boolean := true;
     g_data_w                 : natural := 16;
-    g_nof_repeat             : natural := 24;
-    g_nof_pkt                : natural := 3;
-    g_pkt_len                : natural := 29;
+    g_nof_repeat             : natural := 14;
+    g_nof_pkt_merge          : natural := 4;
+    g_pkt_len_merge          : natural := 7;
+    g_pkt_len_unmerge        : natural := 7;
     g_pkt_gap                : natural := 0;
     g_bsn_increment          : natural := 0
   );
@@ -64,15 +87,26 @@ architecture tb of tb_dp_packet_merge_unmerge is
   constant c_pulse_active             : natural := 1;
   constant c_pulse_period             : natural := 7;
 
+  constant c_nof_pkt_unmerge          : natural := ceil_div(g_nof_pkt_merge * g_pkt_len_merge, g_pkt_len_unmerge);
+  constant c_bsn_increment_unmerge    : natural := sel_a_b(g_nof_pkt_merge = c_nof_pkt_unmerge, g_bsn_increment, 0);
+
   constant c_sync_period              : natural := 10;
   constant c_sync_offset              : natural := 7;
 
   constant c_data_max                 : unsigned(g_data_w - 1 downto 0) := (others => '1');
   constant c_data_init                : integer := -1;
-  constant c_bsn_init                 : std_logic_vector(c_dp_stream_bsn_w - 1 downto 0) := X"0000000000000000";  -- X"0877665544332211"
 
-  constant c_nof_pkt_not_zero         : natural := sel_a_b(g_nof_pkt = 0, 1, g_nof_pkt);
-  constant c_nof_merged_sop           : natural := sel_a_b(g_nof_pkt = 0, 0, ceil_div(g_nof_repeat, c_nof_pkt_not_zero));
+  constant c_bsn_init                 : std_logic_vector(c_dp_stream_bsn_w - 1 downto 0) := X"0000000000000000";
+                                          -- X"0877665544332211"
+  constant c_bsn_increment_max        : natural := (g_bsn_increment + 1) * g_nof_pkt_merge;  -- +1 to ensure max > 0
+  constant c_bsn_increment_w          : natural := ceil_log2(c_bsn_increment_max + 1);
+  constant c_unsigned_bsn_increment   : unsigned(c_bsn_increment_w - 1 downto 0) :=
+                                          to_unsigned(g_bsn_increment, c_bsn_increment_w);
+  constant c_unsigned_bsn_pkt_merge : unsigned(c_bsn_increment_w - 1 downto 0) :=
+                                          to_unsigned(g_nof_pkt_merge, c_bsn_increment_w);
+
+  constant c_nof_pkt_not_zero         : natural := sel_a_b(g_nof_pkt_merge = 0, 1, g_nof_pkt_merge);
+  constant c_nof_merged_sop           : natural := sel_a_b(g_nof_pkt_merge = 0, 0, ceil_div(g_nof_repeat, c_nof_pkt_not_zero));
   -- verify that at least some packets have been merged, not exact to allow variation by p_stimuli_mm
   constant c_verify_at_least          : natural := largest(1,c_nof_merged_sop / 2);
 
@@ -106,6 +140,7 @@ architecture tb of tb_dp_packet_merge_unmerge is
 
   signal input_pkt_cnt              : natural := 0;
   signal merged_pkt_cnt             : natural := 0;
+  signal output_pkt_cnt             : natural := 0;
   signal exp_channel                : natural := 0;
   signal exp_error                  : natural := 0;
   signal exp_empty                  : natural := 0;
@@ -115,7 +150,7 @@ architecture tb of tb_dp_packet_merge_unmerge is
   signal verify_en_sop              : std_logic := '0';
   signal verify_en_eop              : std_logic := '0';
   signal verify_done                : std_logic := '0';
-  signal verify_value_en            : std_logic := sel_a_b(g_nof_pkt = 0, '0', '1');
+  signal verify_value_en            : std_logic := sel_a_b(g_nof_pkt_merge = 0, '0', '1');
 
   signal expected_verify_snk_in     : t_dp_sosi;
 begin
@@ -165,11 +200,11 @@ begin
       v_sosi.bsn  := INCR_UVEC(v_sosi.bsn, g_bsn_increment);
       -- insert sync starting at BSN = c_sync_offset and with period c_sync_period
       v_sosi.sync := sel_a_b((unsigned(v_sosi.bsn) mod c_sync_period) = c_sync_offset, '1', '0');
-      v_sosi.data := INCR_UVEC(v_sosi.data, g_pkt_len);
+      v_sosi.data := INCR_UVEC(v_sosi.data, g_pkt_len_merge);
       v_sosi.data := RESIZE_DP_DATA(v_sosi.data(g_data_w - 1 downto 0));  -- wrap when >= 2**g_data_w
 
       -- Send packet
-      proc_dp_gen_block_data(g_data_w, TO_UINT(v_sosi.data), g_pkt_len,
+      proc_dp_gen_block_data(g_data_w, TO_UINT(v_sosi.data), g_pkt_len_merge,
                              c_channel, c_err, v_sosi.sync, v_sosi.bsn,
                              clk, stimuli_en, stimuli_src_in, stimuli_src_out);
 
@@ -178,12 +213,12 @@ begin
     end loop;
 
     -- Determine expected sosi field values after end of stimuli
-    -- . e_qual (account for merge size g_nof_pkt)
+    -- . e_qual (account for merge size g_nof_pkt_merge)
     -- . e_at_least
-    v_sosi.bsn := std_logic_vector(unsigned(c_bsn_init) + c_verify_at_least * g_nof_pkt);
+    v_sosi.bsn := std_logic_vector(unsigned(c_bsn_init) + c_verify_at_least * g_nof_pkt_merge);
 
-    -- . account for g_pkt_len
-    v_sosi.data := INCR_UVEC(v_sosi.data, g_pkt_len - 1);
+    -- . account for g_pkt_len_merge
+    v_sosi.data := INCR_UVEC(v_sosi.data, g_pkt_len_merge - 1);
     v_sosi.data := RESIZE_DP_DATA(v_sosi.data(g_data_w - 1 downto 0));  -- wrap when >= 2**g_data_w
     expected_verify_snk_in <= v_sosi;
 
@@ -201,6 +236,7 @@ begin
 
   ------------------------------------------------------------------------------
   -- DATA VERIFICATION
+  -- . p_verify
   ------------------------------------------------------------------------------
 
   -- Start verify after first valid, sop or eop
@@ -221,9 +257,25 @@ begin
                       verify_snk_in.valid, verify_snk_in.data, prev_verify_snk_in.data);
 
   -- Verify that the output is incrementing BSN, like the input stimuli
-  proc_dp_verify_data("verify_snk_in.bsn", c_rl, c_unsigned_0, c_unsigned_0,
-                       clk, verify_en_sop, verify_snk_out.ready,
-                       verify_snk_in.sop, verify_snk_in.bsn, prev_verify_snk_in.bsn);
+  gen_verify_bsn_increment : if g_nof_pkt_merge = c_nof_pkt_unmerge generate
+    -- If number of packets stays the same, then unmerge bsn will increment
+    -- as stimuli bsn by c_unsigned_bsn_increment = g_bsn_increment
+    proc_dp_verify_data("verify_snk_in.bsn increment", c_rl,
+                         c_unsigned_0, c_unsigned_bsn_increment,
+                         clk, verify_en_sop, verify_snk_out.ready,
+                         verify_snk_in.sop, verify_snk_in.bsn, prev_verify_snk_in.bsn);
+  end generate;
+  gen_verify_bsn_pkt_unmerge : if g_nof_pkt_merge /= c_nof_pkt_unmerge generate
+    -- If number of packets differs after unmerge, then use
+    -- c_bsn_increment_unmerge = 0 to not increment bsn during ummerge of
+    -- packets, this is covered by gap c_unsigned_0. The bsn of the merged
+    -- packets is passed on into each of the unmerged packets, this is
+    -- covered by c_unsigned_bsn_pkt_merge.
+    proc_dp_verify_data("verify_snk_in.bsn pkt_unmerge", c_rl,
+                         c_unsigned_0, c_unsigned_0, c_unsigned_bsn_pkt_merge,
+                         clk, verify_en_sop, verify_snk_out.ready,
+                         verify_snk_in.sop, verify_snk_in.bsn, prev_verify_snk_in.bsn);
+  end generate;
 
   -- Verify output packet ctrl
   proc_dp_verify_sop_and_eop(clk, verify_snk_in.valid, verify_snk_in.sop, verify_snk_in.eop, verify_hold_sop);
@@ -239,17 +291,17 @@ begin
       -- is that the channel (at sop) and err, empty (at eop) are valid during
       -- the entire unmerged packet.
       if verify_snk_in.valid = '1' then
-        -- Verify that output channel yields index of unmerged packet, in range(g_nof_pkt)
+        -- Verify that output channel yields index of unmerged packet, in range(c_nof_pkt_unmerge)
         assert unsigned(verify_snk_in.channel) = exp_channel
           report "Wrong unmerged verify_snk_in.channel"
           severity ERROR;
 
-        -- Verify that output error is same per g_nof_pkt unmerged packets
+        -- Verify that output error is same per c_nof_pkt_unmerge unmerged packets
         assert unsigned(verify_snk_in.err) = exp_error
           report "Wrong unmerged verify_snk_in.err"
           severity ERROR;
 
-        -- Verify that output empty is same per g_nof_pkt unmerged packets
+        -- Verify that output empty is same per c_nof_pkt_unmerge unmerged packets
         assert unsigned(verify_snk_in.empty) = exp_empty
           report "Wrong unmerged verify_snk_in.empty"
           severity ERROR;
@@ -261,10 +313,10 @@ begin
   -- DUT dp_packet_merge
   ------------------------------------------------------------------------------
 
-  -- Merge every g_nof_pkt incomming packets into output packets
+  -- Merge every g_nof_pkt_merge incomming packets into output packets
   u_dp_packet_merge : entity work.dp_packet_merge
   generic map (
-    g_nof_pkt       => g_nof_pkt,
+    g_nof_pkt       => g_nof_pkt_merge,
     g_align_at_sync => false,  -- not used in this tb
     g_bsn_increment => g_bsn_increment,
     g_bsn_err_bi    => 0  -- not used in this tb
@@ -287,7 +339,7 @@ begin
   ------------------------------------------------------------------------------
   p_stimuli_unmerge : process(dp_packet_merge_src_out)
   begin
-    dp_packet_merge_sosi     <= dp_packet_merge_src_out;
+    dp_packet_merge_sosi <= dp_packet_merge_src_out;
     -- Use counter as err field stimulus, per merged packet. Use
     -- offset to distinguish err and empty values
     dp_packet_merge_sosi.err <= TO_DP_ERROR(merged_pkt_cnt);
@@ -299,9 +351,18 @@ begin
   ------------------------------------------------------------------------------
   input_pkt_cnt <= input_pkt_cnt + 1 when rising_edge(clk) and stimuli_src_out.eop = '1';
   merged_pkt_cnt <= merged_pkt_cnt + 1 when rising_edge(clk) and dp_packet_merge_src_out.eop = '1';
+  output_pkt_cnt <= output_pkt_cnt + 1 when rising_edge(clk) and verify_snk_in.eop = '1';
 
   -- get expected value, aligned with output packet boundaries
-  exp_channel <= input_pkt_cnt mod g_nof_pkt when rising_edge(clk) and verify_snk_in.eop = '1';
+  p_exp_channel : process(output_pkt_cnt)
+  begin
+    -- Avoid divide by 0
+    exp_channel <= 0;
+    if c_nof_pkt_unmerge > 0 then
+      exp_channel <= output_pkt_cnt mod c_nof_pkt_unmerge;
+    end if;
+  end process;
+
   exp_error <= merged_pkt_cnt when rising_edge(clk) and verify_snk_in.eop = '1';
   exp_empty <= exp_error + 1;
 
@@ -312,9 +373,9 @@ begin
   generic map (
     g_use_ready       => c_use_ready,
     g_pipeline_ready  => g_pipeline_ready,
-    g_nof_pkt_max     => g_nof_pkt,
-    g_pkt_len         => g_pkt_len,
-    g_bsn_increment   => g_bsn_increment
+    g_nof_pkt         => c_nof_pkt_unmerge,
+    g_pkt_len         => g_pkt_len_unmerge,
+    g_bsn_increment   => c_bsn_increment_unmerge
   )
   port map (
     rst       => rst,
diff --git a/libraries/base/dp/tb/vhdl/tb_dp_pkg.vhd b/libraries/base/dp/tb/vhdl/tb_dp_pkg.vhd
index 3cb638a976fa37e0b8803b2ae627751a143a2cde..6353df4b0888251a65c083f94af093b3eae414c2 100644
--- a/libraries/base/dp/tb/vhdl/tb_dp_pkg.vhd
+++ b/libraries/base/dp/tb/vhdl/tb_dp_pkg.vhd
@@ -339,6 +339,21 @@ package tb_dp_pkg is
   -- Verify incrementing data
   -- . wrap at c_out_data_max when >0, else no wrap when c_out_data_max=0
   -- . default increment by +1, but also allow an increment by +c_out_data_gap
+  --   or +c_out_data_gap2.
+  -- . by using sop or eop for out_val input, the proc_dp_verify_data() can
+  --   also be used to verify other SOSI fields like bsn, error, channel, empty
+  procedure proc_dp_verify_data(constant c_str           : in    string;
+                                constant c_ready_latency : in    natural;
+                                constant c_out_data_max  : in    unsigned;
+                                constant c_out_data_gap  : in    unsigned;
+                                constant c_out_data_gap2 : in    unsigned;
+                                signal   clk             : in    std_logic;
+                                signal   verify_en       : in    std_logic;
+                                signal   out_ready       : in    std_logic;
+                                signal   out_val         : in    std_logic;
+                                signal   out_data        : in    std_logic_vector;
+                                signal   prev_out_data   : inout std_logic_vector);
+
   procedure proc_dp_verify_data(constant c_str           : in    string;
                                 constant c_ready_latency : in    natural;
                                 constant c_out_data_max  : in    unsigned;
@@ -367,11 +382,21 @@ package tb_dp_pkg is
                                 signal   clk             : in    std_logic;
                                 signal   verify_en       : in    std_logic;
                                 signal   out_ready       : in    std_logic;
-                                signal   out_val         : in    std_logic;  -- by using sop or eop proc_dp_verify_data() can also be used to verify other SOSI fields like bsn, error, channel, empty
+                                signal   out_val         : in    std_logic;
                                 signal   out_data        : in    std_logic_vector;
                                 signal   prev_out_data   : inout std_logic_vector);
 
   -- Verify incrementing data with RL > 0 or no flow control, support wrap at maximum and increment gap
+  procedure proc_dp_verify_data(constant c_str           : in    string;
+                                constant c_out_data_max  : in    unsigned;
+                                constant c_out_data_gap  : in    unsigned;
+                                constant c_out_data_gap2 : in    unsigned;
+                                signal   clk             : in    std_logic;
+                                signal   verify_en       : in    std_logic;
+                                signal   out_val         : in    std_logic;
+                                signal   out_data        : in    std_logic_vector;
+                                signal   prev_out_data   : inout std_logic_vector);
+
   procedure proc_dp_verify_data(constant c_str           : in    string;
                                 constant c_out_data_max  : in    unsigned;
                                 constant c_out_data_gap  : in    unsigned;
@@ -381,6 +406,16 @@ package tb_dp_pkg is
                                 signal   out_data        : in    std_logic_vector;
                                 signal   prev_out_data   : inout std_logic_vector);
 
+  procedure proc_dp_verify_data(constant c_str           : in    string;
+                                constant c_out_data_max  : in    natural;
+                                constant c_out_data_gap  : in    natural;
+                                constant c_out_data_gap2 : in    natural;
+                                signal   clk             : in    std_logic;
+                                signal   verify_en       : in    std_logic;
+                                signal   out_val         : in    std_logic;
+                                signal   out_data        : in    std_logic_vector;
+                                signal   prev_out_data   : inout std_logic_vector);
+
   procedure proc_dp_verify_data(constant c_str           : in    string;
                                 constant c_out_data_max  : in    natural;
                                 constant c_out_data_gap  : in    natural;
@@ -721,8 +756,8 @@ package body tb_dp_pkg is
       src_out.channel <= TO_DP_CHANNEL(c_channel);
       src_out.err     <= TO_DP_ERROR(c_error);
       if c_use_data = true  then src_out.data  <= RESIZE_DP_DATA(v_data);   end if;
-      if c_use_data = false then src_out.re    <= RESIZE_DP_DSP_DATA(v_re); end if;
-      if c_use_data = false then src_out.im    <= RESIZE_DP_DSP_DATA(v_im); end if;
+      if c_use_data = false then src_out.re    <= RESIZE_DP_DSP_UDATA(v_re); end if;
+      if c_use_data = false then src_out.im    <= RESIZE_DP_DSP_UDATA(v_im); end if;
       if c_nof_data > 1 then
         -- . sop
         proc_dp_stream_ready_latency(c_ready_latency, clk, src_in.ready, in_en, c_sync, '1', '1', '0', src_out.sync, src_out.valid, src_out.sop, src_out.eop);
@@ -732,8 +767,8 @@ package body tb_dp_pkg is
           v_re   := func_dp_data_incr(c_data_w, c_symbol_w, v_re);
           v_im   := func_dp_data_incr(c_data_w, c_symbol_w, v_im);
           if c_use_data = true  then src_out.data <= RESIZE_DP_DATA(v_data);   end if;
-          if c_use_data = false then src_out.re   <= RESIZE_DP_DSP_DATA(v_re); end if;
-          if c_use_data = false then src_out.im   <= RESIZE_DP_DSP_DATA(v_im); end if;
+          if c_use_data = false then src_out.re   <= RESIZE_DP_DSP_UDATA(v_re); end if;
+          if c_use_data = false then src_out.im   <= RESIZE_DP_DSP_UDATA(v_im); end if;
           proc_dp_stream_ready_latency(c_ready_latency, clk, src_in.ready, in_en, '0', '1', '0', '0', src_out.sync, src_out.valid, src_out.sop, src_out.eop);
         end loop;
 
@@ -742,8 +777,8 @@ package body tb_dp_pkg is
         v_re   := func_dp_data_incr(c_data_w, c_symbol_w, v_re);
         v_im   := func_dp_data_incr(c_data_w, c_symbol_w, v_im);
         if c_use_data = true  then src_out.data <= RESIZE_DP_DATA(v_data);   end if;
-        if c_use_data = false then src_out.re   <= RESIZE_DP_DSP_DATA(v_re); end if;
-        if c_use_data = false then src_out.im   <= RESIZE_DP_DSP_DATA(v_im); end if;
+        if c_use_data = false then src_out.re   <= RESIZE_DP_DSP_UDATA(v_re); end if;
+        if c_use_data = false then src_out.im   <= RESIZE_DP_DSP_UDATA(v_im); end if;
         proc_dp_stream_ready_latency(c_ready_latency, clk, src_in.ready, in_en, '0', '1', '0', '1', src_out.sync, src_out.valid, src_out.sop, src_out.eop);
       else
         -- . sop and eop, frame has only one word
@@ -1878,10 +1913,12 @@ package body tb_dp_pkg is
   -- Verify incrementing data
   -- . wrap at c_out_data_max when >0, else no wrap when c_out_data_max=0
   -- . default increment by 1, but also allow an increment by c_out_data_gap
+  -- . or c_out_data_gap2.
   procedure proc_dp_verify_data(constant c_str           : in    string;
                                 constant c_ready_latency : in    natural;
                                 constant c_out_data_max  : in    unsigned;
                                 constant c_out_data_gap  : in    unsigned;
+                                constant c_out_data_gap2 : in    unsigned;
                                 signal   clk             : in    std_logic;
                                 signal   verify_en       : in    std_logic;
                                 signal   out_ready       : in    std_logic;  -- only needed when c_ready_latency = 0
@@ -1903,10 +1940,17 @@ package body tb_dp_pkg is
             prev_out_data <= TO_SVEC(-1, prev_out_data'length);  -- do wrap
           end if;
           if verify_en = '1' then
-            if unsigned(out_data) /= unsigned(prev_out_data) + 1 and  -- check increment +1
-               unsigned(out_data) /= unsigned(prev_out_data) + c_out_data_gap and  -- increment +c_out_data_gap
-               unsigned(out_data) /= unsigned(prev_out_data) + c_out_data_gap - c_out_data_max then  -- increment +c_out_data_gap wrapped
-              report "DP : Wrong out_data " & c_str & " count" severity ERROR;
+            -- Default check increment +1.
+            -- also allow increment +c_out_data_gap or +c_out_data_gap2.
+            -- also allow increment +c_out_data_gap wrapped by c_out_data_max.
+            if unsigned(out_data) /= unsigned(prev_out_data) + 1 and
+               unsigned(out_data) /= unsigned(prev_out_data) + c_out_data_gap and
+               unsigned(out_data) /= unsigned(prev_out_data) + c_out_data_gap2 and
+               unsigned(out_data) /= unsigned(prev_out_data) + c_out_data_gap - c_out_data_max then
+              report "DP : Wrong out_data " & c_str & " count (" &
+                natural'image(to_uint(out_data)) & ", " &
+                natural'image(to_uint(prev_out_data)) & ")"
+                severity ERROR;
             end if;
           end if;
         end if;
@@ -1914,6 +1958,22 @@ package body tb_dp_pkg is
     end if;
   end proc_dp_verify_data;
 
+  procedure proc_dp_verify_data(constant c_str           : in    string;
+                                constant c_ready_latency : in    natural;
+                                constant c_out_data_max  : in    unsigned;
+                                constant c_out_data_gap  : in    unsigned;
+                                signal   clk             : in    std_logic;
+                                signal   verify_en       : in    std_logic;
+                                signal   out_ready       : in    std_logic;  -- only needed when c_ready_latency = 0
+                                signal   out_val         : in    std_logic;
+                                signal   out_data        : in    std_logic_vector;
+                                signal   prev_out_data   : inout std_logic_vector) is
+  begin
+    proc_dp_verify_data(c_str, c_ready_latency,
+                        c_out_data_max, c_out_data_gap, c_unsigned_1,
+                        clk, verify_en, out_ready, out_val, out_data, prev_out_data);
+  end proc_dp_verify_data;
+
   -- Verify incrementing data that wraps in range 0 ... c_out_data_max
   procedure proc_dp_verify_data(constant c_str           : in    string;
                                 constant c_ready_latency : in    natural;
@@ -1925,7 +1985,9 @@ package body tb_dp_pkg is
                                 signal   out_data        : in    std_logic_vector;
                                 signal   prev_out_data   : inout std_logic_vector) is
   begin
-    proc_dp_verify_data(c_str, c_ready_latency, c_out_data_max, to_unsigned(1,1), clk, verify_en, out_ready, out_val, out_data, prev_out_data);
+    proc_dp_verify_data(c_str, c_ready_latency,
+                        c_out_data_max, c_unsigned_1, c_unsigned_1,
+                        clk, verify_en, out_ready, out_val, out_data, prev_out_data);
   end proc_dp_verify_data;
 
   -- Verify incrementing data
@@ -1938,26 +2000,47 @@ package body tb_dp_pkg is
                                 signal   out_data        : in    std_logic_vector;
                                 signal   prev_out_data   : inout std_logic_vector) is
   begin
-    proc_dp_verify_data(c_str, c_ready_latency, to_unsigned(0,1), to_unsigned(1,1), clk, verify_en, out_ready, out_val, out_data, prev_out_data);
+    proc_dp_verify_data(c_str, c_ready_latency,
+                        c_unsigned_0, c_unsigned_1, c_unsigned_1,
+                        clk, verify_en, out_ready, out_val, out_data, prev_out_data);
   end proc_dp_verify_data;
 
   -- Verify incrementing data with RL > 0 or no flow control
   procedure proc_dp_verify_data(constant c_str           : in    string;
                                 constant c_out_data_max  : in    unsigned;
                                 constant c_out_data_gap  : in    unsigned;
+                                constant c_out_data_gap2 : in    unsigned;
                                 signal   clk             : in    std_logic;
                                 signal   verify_en       : in    std_logic;
                                 signal   out_val         : in    std_logic;
                                 signal   out_data        : in    std_logic_vector;
                                 signal   prev_out_data   : inout std_logic_vector) is
   begin
-    -- Use out_val as void signal to pass on to unused out_ready, because a signal input can not connect a constant or variable
-    proc_dp_verify_data(c_str, 1, c_out_data_max, c_out_data_gap, clk, verify_en, out_val, out_val, out_data, prev_out_data);
+    -- Use out_val as void signal to pass on to unused out_ready, because a
+    -- signal input can not connect a constant or variable.
+    proc_dp_verify_data(c_str, 1,
+                        c_out_data_max, c_out_data_gap, c_out_data_gap2,
+                        clk, verify_en, out_val, out_val, out_data, prev_out_data);
+  end proc_dp_verify_data;
+
+  procedure proc_dp_verify_data(constant c_str           : in    string;
+                                constant c_out_data_max  : in    unsigned;
+                                constant c_out_data_gap  : in    unsigned;
+                                signal   clk             : in    std_logic;
+                                signal   verify_en       : in    std_logic;
+                                signal   out_val         : in    std_logic;
+                                signal   out_data        : in    std_logic_vector;
+                                signal   prev_out_data   : inout std_logic_vector) is
+  begin
+    proc_dp_verify_data(c_str,
+                        c_out_data_max, c_out_data_gap, c_unsigned_1,
+                        clk, verify_en, out_val, out_data, prev_out_data);
   end proc_dp_verify_data;
 
   procedure proc_dp_verify_data(constant c_str           : in    string;
                                 constant c_out_data_max  : in    natural;
                                 constant c_out_data_gap  : in    natural;
+                                constant c_out_data_gap2 : in    natural;
                                 signal   clk             : in    std_logic;
                                 signal   verify_en       : in    std_logic;
                                 signal   out_val         : in    std_logic;
@@ -1965,11 +2048,16 @@ package body tb_dp_pkg is
                                 signal   prev_out_data   : inout std_logic_vector) is
     constant c_data_w : natural := out_data'length;
   begin
-    proc_dp_verify_data(c_str, to_unsigned(c_out_data_max, c_data_w), to_unsigned(c_out_data_gap, c_data_w), clk, verify_en, out_val, out_data, prev_out_data);
+    proc_dp_verify_data(c_str,
+                        to_unsigned(c_out_data_max, c_data_w),
+                        to_unsigned(c_out_data_gap, c_data_w),
+                        to_unsigned(c_out_data_gap2, c_data_w),
+                        clk, verify_en, out_val, out_data, prev_out_data);
   end proc_dp_verify_data;
 
   procedure proc_dp_verify_data(constant c_str           : in    string;
                                 constant c_out_data_max  : in    natural;
+                                constant c_out_data_gap  : in    natural;
                                 signal   clk             : in    std_logic;
                                 signal   verify_en       : in    std_logic;
                                 signal   out_val         : in    std_logic;
@@ -1977,18 +2065,34 @@ package body tb_dp_pkg is
                                 signal   prev_out_data   : inout std_logic_vector) is
     constant c_data_w : natural := out_data'length;
   begin
-    proc_dp_verify_data(c_str, to_unsigned(c_out_data_max, c_data_w), to_unsigned(1, 1), clk, verify_en, out_val, out_data, prev_out_data);
+    proc_dp_verify_data(c_str,
+                        c_out_data_max, c_out_data_gap, 1,
+                        clk, verify_en, out_val, out_data, prev_out_data);
   end proc_dp_verify_data;
 
   procedure proc_dp_verify_data(constant c_str           : in    string;
+                                constant c_out_data_max  : in    natural;
                                 signal   clk             : in    std_logic;
                                 signal   verify_en       : in    std_logic;
                                 signal   out_val         : in    std_logic;
                                 signal   out_data        : in    std_logic_vector;
                                 signal   prev_out_data   : inout std_logic_vector) is
+    constant c_data_w : natural := out_data'length;
   begin
-    -- Use out_val as void signal to pass on to unused out_ready, because a signal input can not connect a constant or variable
-    proc_dp_verify_data(c_str, 1, to_unsigned(0,1), to_unsigned(1,1), clk, verify_en, out_val, out_val, out_data, prev_out_data);
+    proc_dp_verify_data(c_str,
+                        c_out_data_max, 1,
+                        clk, verify_en, out_val, out_data, prev_out_data);
+  end proc_dp_verify_data;
+
+  procedure proc_dp_verify_data(constant c_str           : in    string;
+                                signal   clk             : in    std_logic;
+                                signal   verify_en       : in    std_logic;
+                                signal   out_val         : in    std_logic;
+                                signal   out_data        : in    std_logic_vector;
+                                signal   prev_out_data   : inout std_logic_vector) is
+  begin
+    proc_dp_verify_data(c_str, 0,
+                        clk, verify_en, out_val, out_data, prev_out_data);
   end proc_dp_verify_data;
 
   ------------------------------------------------------------------------------
diff --git a/libraries/base/dp/tb/vhdl/tb_tb_dp_packet_merge_unmerge.vhd b/libraries/base/dp/tb/vhdl/tb_tb_dp_packet_merge_unmerge.vhd
index 7fd9034f6fb81169d4c7b189a370006c2f78b9d7..e8ec4b5f98c8009df0ac4f12a7d5d197f83f4219 100644
--- a/libraries/base/dp/tb/vhdl/tb_tb_dp_packet_merge_unmerge.vhd
+++ b/libraries/base/dp/tb/vhdl/tb_tb_dp_packet_merge_unmerge.vhd
@@ -42,29 +42,42 @@ begin
   --     g_pipeline_ready         : boolean := true;
   --     g_data_w                 : natural := 4;
   --     g_nof_repeat             : natural := 20;
-  --     g_nof_pkt                : natural := 3;
-  --     g_pkt_len                : natural := 29;
+  --     g_nof_pkt_merge          : natural := 3;
+  --     g_pkt_len_merge          : natural := 29;
+  --     g_pkt_len_unmerge        : natural := 29;
   --     g_pkt_gap                : natural := 0;
   --     g_bsn_increment          : natural := 0;
 
-  u_act_act_8_nof_0       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 0, 29, 0, 1);
-  u_act_act_8_nof_1       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 1, 29, 0, 1);
-  u_act_act_8_nof_2       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 2, 29, 0, 1);
-  u_act_act_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 3, 29, 0, 1);
-  u_act_act_8_nof_4       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 4, 29, 0, 1);
-  u_act_act_8_nof_4_bsn_0 : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 4, 29, 0, 0);
-  --u_act_act_8_nof_4_bsn_2 : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 4, 29, 0, 2);
-  u_act_act_8_nof_5       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 5, 29, 0, 1);
-  u_act_act_8_nof_6       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 6, 29, 0, 1);
-  u_act_act_8_nof_7       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 7, 29, 0, 1);
+  -- No flow control
+  u_act_act_8_nof_0       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 0, 29, 29, 0, 1);
+  u_act_act_8_nof_1       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 1, 29, 29, 0, 1);
+  u_act_act_8_nof_2       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 2, 29, 29, 0, 1);
+  u_act_act_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 3, 29, 29, 0, 1);
+  u_act_act_8_nof_4       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 4, 29, 29, 0, 1);
+  u_act_act_8_nof_4_bsn_0 : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 4, 29, 29, 0, 0);
+  u_act_act_8_nof_4_bsn_2 : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 4, 29, 29, 0, 2);
+  u_act_act_8_nof_5       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 5, 29, 29, 0, 1);
+  u_act_act_8_nof_6       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 6, 29, 29, 0, 1);
+  u_act_act_8_nof_7       : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 7, 29, 29, 0, 1);
 
-  u_rnd_act_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_active,  true, 8, c_nof_repeat, 3, 29, 0, 1);
-  u_rnd_rnd_8_nof_3_comb  : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_random, false, 8, c_nof_repeat, 3, 29, 0, 1);
-  u_rnd_rnd_8_nof_3_reg   : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_random,  true, 8, c_nof_repeat, 3, 29, 0, 1);
-  u_pls_act_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_pulse,  e_active,  true, 8, c_nof_repeat, 3, 29, 0, 1);
-  u_pls_rnd_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_pulse,  e_random,  true, 8, c_nof_repeat, 3, 29, 0, 1);
-  u_pls_pls_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_pulse,  e_pulse,   true, 8, c_nof_repeat, 3, 29, 0, 1);
+  -- Fractional unmerge
+  -- . u_act_act_8_nof_3_more_unmerge  : ceil((3 * 7) / 6) = 4 and (3 * 7) mod 6 = 3, so there are 4 unmerged packets and last has length 3
+  -- . u_act_act_8_nof_3_last_len_1    : (3 * 7) mod 10 = 1, so last unmerged packet has length 1
+  -- . u_act_act_8_nof_3_only_one      : (3 * 7) / 21 = 1, so there is only one unmerged packet with length 21
+  -- . u_act_act_8_nof_3_only_one_frac : (3 * 7) / 22 = 0 and (3 * 7) mod 22 = 21, so there is only one unmerged packet with length 21
+  u_act_act_8_nof_3_more_unmerge  : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 3, 7,  6, 0, 1);
+  u_act_act_8_nof_3_last_len_1    : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 3, 7, 10, 0, 1);
+  u_act_act_8_nof_3_only_one      : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 3, 7, 21, 0, 1);
+  u_act_act_8_nof_3_only_one_frac : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active,  true, 8, c_nof_repeat, 3, 7, 30, 0, 1);
 
-  u_rnd_act_8_nof_1       : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_active,  true, 8, c_nof_repeat, 1, 29,  0, 1);
-  u_rnd_act_8_nof_3_gap   : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_active,  true, 8, c_nof_repeat, 3, 29, 17, 1);
+  -- Flow control
+  u_rnd_act_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_active,  true, 8, c_nof_repeat, 3, 29, 29, 0, 1);
+  u_rnd_rnd_8_nof_3_comb  : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_random, false, 8, c_nof_repeat, 3, 29, 29, 0, 1);
+  u_rnd_rnd_8_nof_3_reg   : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_random,  true, 8, c_nof_repeat, 3, 29, 29, 0, 1);
+  u_pls_act_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_pulse,  e_active,  true, 8, c_nof_repeat, 3, 29, 29, 0, 1);
+  u_pls_rnd_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_pulse,  e_random,  true, 8, c_nof_repeat, 3, 29, 29, 0, 1);
+  u_pls_pls_8_nof_3       : entity work.tb_dp_packet_merge_unmerge generic map ( e_pulse,  e_pulse,   true, 8, c_nof_repeat, 3, 29, 29, 0, 1);
+
+  u_rnd_act_8_nof_1       : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_active,  true, 8, c_nof_repeat, 1, 29, 29,  0, 1);
+  u_rnd_act_8_nof_3_gap   : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_active,  true, 8, c_nof_repeat, 3, 29, 29, 17, 1);
 end tb;