From 689a41b4decf5c91aee8be90565f315e3e08f2e5 Mon Sep 17 00:00:00 2001
From: Eric Kooistra <kooistra@astron.nl>
Date: Wed, 6 Sep 2023 10:53:49 +0200
Subject: [PATCH] Add dp_packet_unmerge.vhd with tb and tb_tb.

---
 libraries/base/dp/hdllib.cfg                  |  33 +-
 .../base/dp/src/vhdl/dp_packet_unmerge.vhd    | 135 +++++++-
 .../dp/tb/vhdl/tb_dp_packet_merge_unmerge.vhd | 299 ++++++++++++++++++
 .../tb/vhdl/tb_tb_dp_packet_merge_unmerge.vhd |  74 +++++
 4 files changed, 517 insertions(+), 24 deletions(-)
 create mode 100644 libraries/base/dp/tb/vhdl/tb_dp_packet_merge_unmerge.vhd
 create mode 100644 libraries/base/dp/tb/vhdl/tb_tb_dp_packet_merge_unmerge.vhd

diff --git a/libraries/base/dp/hdllib.cfg b/libraries/base/dp/hdllib.cfg
index 8d50013850..f205872d5f 100644
--- a/libraries/base/dp/hdllib.cfg
+++ b/libraries/base/dp/hdllib.cfg
@@ -1,8 +1,8 @@
 hdl_lib_name = dp
 hdl_library_clause_name = dp_lib
-hdl_lib_uses_synth = mm common common_mult easics  
-hdl_lib_uses_sim = 
-hdl_lib_technology = 
+hdl_lib_uses_synth = mm common common_mult easics
+hdl_lib_uses_sim =
+hdl_lib_technology =
 
 synth_files =
     src/vhdl/dp_stream_pkg.vhd
@@ -10,7 +10,7 @@ synth_files =
     src/vhdl/dp_example_dut.vhd
     src/vhdl/dp_packetizing_pkg.vhd
     src/vhdl/dp_packet_pkg.vhd
-    
+
     src/vhdl/dp_eop_extend.vhd
     src/vhdl/dp_validate.vhd
     src/vhdl/dp_ready.vhd
@@ -147,7 +147,7 @@ synth_files =
     src/vhdl/dp_packet_unmerge.vhd
 
     src/vhdl/dp_offload_tx_legacy.vhd
-    src/vhdl/dp_offload_tx_len_calc.vhd  
+    src/vhdl/dp_offload_tx_len_calc.vhd
     src/vhdl/dp_sync_insert.vhd
     src/vhdl/dp_sync_insert_v2.vhd
     src/vhdl/dp_sync_recover.vhd
@@ -192,20 +192,20 @@ synth_files =
     src/vhdl/dp_selector_arr.vhd
     src/vhdl/dp_selector.vhd
     src/vhdl/mms_dp_scale.vhd
-    
+
     tb/vhdl/dp_stream_player.vhd
     tb/vhdl/dp_sosi_recorder.vhd
     tb/vhdl/dp_stream_rec_play.vhd
     tb/vhdl/dp_statistics.vhd
-     
+
     tb/vhdl/tb_dp_pkg.vhd
-    
-test_bench_files = 
-    
+
+test_bench_files =
+
     tb/vhdl/dp_phy_link.vhd
     tb/vhdl/dp_stream_stimuli.vhd
     tb/vhdl/dp_stream_verify.vhd
-    
+
     tb/vhdl/tb_dp_strobe_total_count.vhd
     tb/vhdl/tb_dp_block_select.vhd
     tb/vhdl/tb_dp_block_validate_length.vhd
@@ -266,6 +266,7 @@ test_bench_files =
     tb/vhdl/tb_dp_concat_field_blk.vhd
     tb/vhdl/tb_dp_packet.vhd
     tb/vhdl/tb_dp_packet_merge.vhd
+    tb/vhdl/tb_dp_packet_merge_unmerge.vhd
     tb/vhdl/tb_dp_packetizing.vhd
     tb/vhdl/tb_dp_pad_insert_remove.vhd
     tb/vhdl/tb_dp_pipeline.vhd
@@ -344,6 +345,7 @@ test_bench_files =
     tb/vhdl/tb_tb_dp_packetizing.vhd
     tb/vhdl/tb_tb_dp_packet.vhd
     tb/vhdl/tb_tb_dp_packet_merge.vhd
+    tb/vhdl/tb_tb_dp_packet_merge_unmerge.vhd
     tb/vhdl/tb_tb_dp_concat_field_blk.vhd
     tb/vhdl/tb_tb_dp_pipeline.vhd
     tb/vhdl/tb_tb_dp_pipeline_ready.vhd
@@ -360,7 +362,7 @@ test_bench_files =
     tb/vhdl/tb_tb_dp_throttle_xon.vhd
     tb/vhdl/tb_tb_dp_counter.vhd
     tb/vhdl/tb_tb_dp_xonoff.vhd
-    
+
     tb/vhdl/tb_tb_tb_dp_backpressure.vhd
     tb/vhdl/tb_dp_offload_tx_v3.vhd
     tb/vhdl/tb_tb_dp_offload_tx_v3.vhd
@@ -368,7 +370,7 @@ test_bench_files =
     tb/vhdl/tb_dp_selector_arr.vhd
     tb/vhdl/tb_mms_dp_scale.vhd
 
-regression_test_vhdl = 
+regression_test_vhdl =
     tb/vhdl/tb_dp_fifo_to_mm.vhd
     tb/vhdl/tb_dp_fifo_xonoff.vhd
     tb/vhdl/tb_dp_latency_adapter.vhd
@@ -403,8 +405,8 @@ regression_test_vhdl =
     tb/vhdl/tb_tb_dp_reverse_n_data.vhd
     tb/vhdl/tb_tb_dp_reverse_n_data_fc.vhd
     tb/vhdl/tb_tb_dp_example_dut.vhd
-    tb/vhdl/tb_tb_dp_fifo_dc.vhd  
-    tb/vhdl/tb_tb_dp_fifo_dc_mixed_widths.vhd  
+    tb/vhdl/tb_tb_dp_fifo_dc.vhd
+    tb/vhdl/tb_tb_dp_fifo_dc_mixed_widths.vhd
     tb/vhdl/tb_tb_dp_fifo_fill.vhd
     tb/vhdl/tb_tb_dp_fifo_fill_sc.vhd
     tb/vhdl/tb_tb_dp_fifo_fill_eop.vhd
@@ -417,6 +419,7 @@ regression_test_vhdl =
     tb/vhdl/tb_tb3_dp_mux.vhd
     tb/vhdl/tb_tb_dp_packet.vhd
     tb/vhdl/tb_tb_dp_packet_merge.vhd
+    tb/vhdl/tb_tb_dp_packet_merge_unmerge.vhd
     tb/vhdl/tb_tb_dp_concat_field_blk.vhd
     tb/vhdl/tb_tb_dp_pad_insert_remove.vhd
     tb/vhdl/tb_tb_dp_pipeline.vhd
diff --git a/libraries/base/dp/src/vhdl/dp_packet_unmerge.vhd b/libraries/base/dp/src/vhdl/dp_packet_unmerge.vhd
index 1fe7ebd08f..d7f294f821 100644
--- a/libraries/base/dp/src/vhdl/dp_packet_unmerge.vhd
+++ b/libraries/base/dp/src/vhdl/dp_packet_unmerge.vhd
@@ -19,13 +19,31 @@
 --
 --------------------------------------------------------------------------------
 
--- Purpose: Unmerge each input packet into g_nof_pkt output packets. Below is the
---          waveform when g_nof_pkt = 3. The numbers in snk_in.sop and snk_in.eop
---          match pkt_cnt.
+-- 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 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
+--   g_bsn_increment = 0, then all unmerged output packets get the same BSN as
+--   the input packet.
+-- . The input snk_in.err and snk_in.empty are valid at the snk_in.eop, but
+--   that is too late to apply them to the unmerged packets. Therefor assume
+--   that the snk_in.err and snk_in_eop are already valid at the snk_in.sop
+--   and remain valid until the snk_in.eop. Hence these signals are then valid
+--   when snk_in.valid = '1'. Use same snk_in.err and snk_in.empty value for
+--   all unmerged packets.
+--
 --                    _                             _                             _
--- snk_in.sop   _____|0|___________________________|1|___________________________|2|_
+-- snk_in.sop   _____| |___________________________| |___________________________| |_
 --                                                _                             _
--- snk_in.sop   _________________________________|0|___________________________|1|___
+-- snk_in.eop   _________________________________| |___________________________| |___
 --                    _         _         _         _         _         _         _
 -- src_out.sop  _____|0|_______|1|_______|2|_______|0|_______|1|_______|2|_______|0|_
 --                            _         _         _         _         _         _
@@ -39,8 +57,9 @@ use work.dp_stream_pkg.all;
 
 entity dp_packet_unmerge is
   generic (
-    g_nof_pkt   : natural := 1;  -- Nof packets to unmerge each incoming packet to
-    g_pkt_len   : natural := 1  -- Length of the unmerged packets
+    g_nof_pkt_max   : natural := 1;  -- Maximum nof packets to unmerge each incoming packet to
+    g_pkt_len       : natural := 1;  -- Length of the unmerged packets
+    g_bsn_increment : natural := 0
   );
   port (
     rst         : in  std_logic;
@@ -55,8 +74,106 @@ entity dp_packet_unmerge is
 end dp_packet_unmerge;
 
 architecture rtl of dp_packet_unmerge is
+  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;
+    src_out     : t_dp_sosi;
+  end record;
+
+  constant c_reg_rst : t_reg := (0, 0, c_dp_sosi_rst);
+
+  signal r : t_reg;
+  signal d : t_reg;
+
 begin
-  -- Temporary void component that assigns output = input
+  -- Map t_reg outputs to entity outputs
   snk_out <= src_in;
-  src_out <= snk_in;
+  src_out <= r.src_out;
+
+  -- p_reg
+  r <= d when rising_edge(clk);
+
+  p_comb : process(rst, r, snk_in)
+    variable v : t_reg;
+  begin
+    -- Default
+    v := r;
+
+    -- Function
+    --                    _                 _                 _
+    --   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
+    --                    ______________   _______________   _______________   _
+    --   v.busy        __|              |_|               |_|               |_|
+    --                    _     _     _     _     _     _     _     _     _
+    --   v.src_out.sop __|0|___|1|___|2|___|0|___|1|___|2|___|0|___|1|___|2|___|
+    --                       _     _     _     _     _     _     _     _     _
+    --   v.src_out.eop _____|0|___|1|___|2|___|0|___|1|___|2|___|0|___|1|___|2|_
+
+    -- input valid counter
+    if snk_in.sop = '1' then
+      v.val_cnt := 0;
+    elsif snk_in.valid = '1' then
+      if r.val_cnt < g_pkt_len - 1 then
+        v.val_cnt := r.val_cnt + 1;
+      else
+        v.val_cnt := 0;
+      end if;
+    end if;
+
+    -- output packet counter
+    if snk_in.sop = '1' then
+      v.pkt_cnt := 0;
+    elsif snk_in.valid = '1' and v.val_cnt = 0 then
+      v.pkt_cnt := r.pkt_cnt + 1;
+    end if;
+
+    -- output packet bsn, sync, sop and eop
+    v.src_out         := snk_in;  -- passes on snk_in.sync, data, re, im
+    v.src_out.bsn     := r.src_out.bsn;
+    v.src_out.channel := r.src_out.channel;
+    v.src_out.empty   := r.src_out.empty;
+    v.src_out.err     := r.src_out.err;
+    v.src_out.sop     := '0';
+    v.src_out.eop     := '0';
+
+    -- . output sop, eop
+    if snk_in.valid = '1' then
+      if v.val_cnt = 0 then
+        v.src_out.sop := '1';
+      end if;
+      if v.val_cnt = g_pkt_len - 1 then
+        v.src_out.eop := '1';
+      end if;
+    end if;
+
+    -- . output bsn
+    if snk_in.sop = '1' then
+      v.src_out.bsn := snk_in.bsn;
+    elsif v.src_out.sop = '1' then
+      v.src_out.bsn := incr_uvec(r.src_out.bsn, g_bsn_increment);
+    end if;
+
+    -- . output channel
+    v.src_out.channel := TO_DP_CHANNEL(v.pkt_cnt);
+
+    -- . output err, empty. assume they are valid during entire input packet,
+    --   so not only at the snk_in.eop
+    if snk_in.valid = '1' then
+      v.src_out.empty := snk_in.empty;
+      v.src_out.err   := snk_in.err;
+    end if;
+
+    -- Synchronous reset
+    if rst = '1' then
+      v := c_reg_rst;
+    end if;
+
+    d <= v;
+  end process;
 end rtl;
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
new file mode 100644
index 0000000000..6277b534f3
--- /dev/null
+++ b/libraries/base/dp/tb/vhdl/tb_dp_packet_merge_unmerge.vhd
@@ -0,0 +1,299 @@
+-- --------------------------------------------------------------------------
+-- 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 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.
+-- Usage:
+-- > as 10
+-- > run -all
+--
+
+library IEEE, common_lib;
+use IEEE.std_logic_1164.all;
+use IEEE.numeric_std.all;
+use common_lib.common_pkg.all;
+use common_lib.common_lfsr_sequences_pkg.all;
+use common_lib.tb_common_pkg.all;
+use work.dp_stream_pkg.all;
+use work.tb_dp_pkg.all;
+
+entity tb_dp_packet_merge_unmerge is
+  generic (
+    -- general
+    g_flow_control_stimuli   : t_dp_flow_control_enum := e_active;  -- always active, random or pulse flow control
+    g_flow_control_verify    : t_dp_flow_control_enum := e_active;  -- always active, random or pulse flow control
+    -- specific
+    g_data_w                 : natural := 16;
+    g_nof_repeat             : natural := 24;
+    g_nof_pkt                : natural := 3;
+    g_pkt_len                : natural := 10;
+    g_pkt_gap                : natural := 0;
+    g_align_at_sync          : boolean := false;
+    g_verify_bsn_err         : boolean := false;
+    g_bsn_increment          : natural := 1
+  );
+end tb_dp_packet_merge_unmerge;
+
+architecture tb of tb_dp_packet_merge_unmerge is
+  constant c_rl                       : natural := 1;
+
+  constant c_pulse_active             : natural := 1;
+  constant c_pulse_period             : natural := 7;
+
+  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_err_init                 : natural := 247;
+  constant c_bsn_err_bi               : natural := 31;  -- use sufficiently high bsn error bit index, that is outside counter range of c_err_init
+  constant c_channel_init             : integer := 5;  -- fixed
+
+  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_verify_at_least          : natural := largest(1,c_nof_merged_sop / 2);  -- verify that at least some packets have been merged, not exact to allow variation by p_stimuli_mm
+  constant c_verify_data_gap          : natural := g_nof_pkt;
+  constant c_verify_bsn_gap           : natural := g_nof_pkt * g_bsn_increment;
+
+  signal tb_end                     : std_logic := '0';
+  signal clk                        : std_logic := '1';
+  signal rst                        : std_logic := '1';
+  signal sl1                        : std_logic := '1';
+
+  signal random_0                   : std_logic_vector(14 downto 0) := (others => '0');  -- use different lengths to have different random sequences
+  signal random_1                   : std_logic_vector(15 downto 0) := (others => '0');  -- use different lengths to have different random sequences
+  signal pulse_0                    : std_logic;
+  signal pulse_1                    : std_logic;
+  signal pulse_en                   : std_logic := '1';
+
+  signal stimuli_en                 : std_logic := '1';
+  signal stimuli_src_in             : t_dp_siso;
+  signal stimuli_src_out            : t_dp_sosi;
+  signal stimuli_data               : std_logic_vector(g_data_w - 1 downto 0);
+
+  signal dp_packet_merge_snk_in     : t_dp_siso;
+  signal dp_packet_merge_src_out    : t_dp_sosi;
+  signal dp_packet_merge_siso       : t_dp_siso;
+  signal dp_packet_merge_sosi       : t_dp_sosi;
+
+  signal prev_verify_snk_out        : t_dp_siso;
+  signal verify_snk_out             : t_dp_siso := c_dp_siso_rdy;
+  signal verify_snk_in              : t_dp_sosi;
+  signal verify_data                : std_logic_vector(g_data_w - 1 downto 0);
+  signal prev_verify_snk_in         : t_dp_sosi;
+  signal merged_pkt_cnt             : natural := 0;
+  signal exp_channel                : natural := 0;
+
+  signal verify_hold_sop            : std_logic := '0';
+  signal verify_en_valid            : std_logic := '0';
+  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 expected_verify_snk_in     : t_dp_sosi;
+begin
+  clk <= (not clk) or tb_end after clk_period / 2;
+  rst <= '1', '0' after clk_period * 7;
+
+  random_0 <= func_common_random(random_0) when rising_edge(clk);
+  random_1 <= func_common_random(random_1) when rising_edge(clk);
+
+  proc_common_gen_duty_pulse(c_pulse_active, c_pulse_period,     '1', rst, clk, pulse_en, pulse_0);
+  proc_common_gen_duty_pulse(c_pulse_active, c_pulse_period + 1, '1', rst, clk, pulse_en, pulse_1);
+
+  ------------------------------------------------------------------------------
+  -- STREAM CONTROL
+  ------------------------------------------------------------------------------
+
+  stimuli_en           <= '1'                     when g_flow_control_stimuli = e_active else
+                          random_0(random_0'high) when g_flow_control_stimuli = e_random else
+                          pulse_0                 when g_flow_control_stimuli = e_pulse;
+
+  verify_snk_out.ready <= '1'                     when g_flow_control_verify = e_active  else
+                          random_1(random_1'high) when g_flow_control_verify = e_random  else
+                          pulse_1                 when g_flow_control_verify = e_pulse;
+
+  ------------------------------------------------------------------------------
+  -- DATA GENERATION
+  ------------------------------------------------------------------------------
+
+  -- Generate data path input data
+  p_stimuli_st : process
+    variable v_sosi : t_dp_sosi := c_dp_sosi_rst;
+    variable v_bsn  : std_logic_vector(c_dp_stream_bsn_w - 1 downto 0);
+  begin
+    -- Adjust initial sosi field values by -1 to compensate for auto increment
+    v_sosi.bsn  := INCR_UVEC(c_bsn_init, -1);
+    v_sosi.data := INCR_UVEC(TO_DP_DATA(c_data_init), -1);
+    v_sosi.err  := INCR_UVEC(TO_DP_ERROR(c_err_init), -1);
+
+    stimuli_src_out <= c_dp_sosi_rst;
+    proc_common_wait_until_low(clk, rst);
+    proc_common_wait_some_cycles(clk, 5);
+
+    -- Generate c_nof_repeat packets
+    for I in 0 to g_nof_repeat - 1 loop
+      -- Auto increment v_sosi field values for this packet
+      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    := 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, TO_UINT(v_sosi.channel), TO_UINT(v_sosi.err), v_sosi.sync, v_sosi.bsn,
+                             clk, stimuli_en, stimuli_src_in, stimuli_src_out);
+
+      -- Insert optional gap between the packets
+      proc_common_wait_some_cycles(clk, g_pkt_gap);
+    end loop;
+
+    -- Determine expected sosi field values after end of stimuli
+    -- . e_qual (account for merge size g_nof_pkt)
+    -- . e_at_least
+    v_sosi.bsn     := std_logic_vector(unsigned(c_bsn_init) + c_verify_at_least * g_nof_pkt);
+
+    -- . account for g_pkt_len
+    v_sosi.data    := INCR_UVEC(v_sosi.data, g_pkt_len - 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;
+
+    -- Signal end of stimuli
+    -- . latency from stimuli to verify depends on the flow control, so wait
+    --   sufficiently long for last packet to have passed through
+    proc_common_wait_some_cycles(clk, 100);
+    if verify_value_en = '1' then
+      proc_common_gen_pulse(clk, verify_done);
+    end if;
+    proc_common_wait_some_cycles(clk, 50);
+    tb_end <= '1';
+    wait;
+  end process;
+
+  ------------------------------------------------------------------------------
+  -- DATA VERIFICATION
+  ------------------------------------------------------------------------------
+
+  -- Start verify after first valid, sop or eop
+  verify_en_valid <= '1' when verify_snk_in.valid = '1' and rising_edge(clk);
+  verify_en_sop   <= '1' when verify_snk_in.sop = '1'   and rising_edge(clk);
+  verify_en_eop   <= '1' when verify_snk_in.eop = '1'   and rising_edge(clk);
+
+  -- Verify that the stimuli have been applied at all
+  proc_dp_verify_value("verify_snk_in.valid",               clk, verify_done, sl1, verify_en_valid);
+  proc_dp_verify_value("verify_snk_in.sop",                 clk, verify_done, sl1, verify_en_sop);
+  proc_dp_verify_value("verify_snk_in.eop",                 clk, verify_done, sl1, verify_en_eop);
+  proc_dp_verify_value("verify_snk_in.data",    e_equal,    clk, verify_done, expected_verify_snk_in.data,    verify_snk_in.data);
+  proc_dp_verify_value("verify_snk_in.bsn",     e_at_least, clk, verify_done, expected_verify_snk_in.bsn,     verify_snk_in.bsn);
+
+  -- Verify that the output is incrementing data, like the input stimuli
+  proc_dp_verify_data("verify_snk_in.data", c_rl, c_data_max, c_unsigned_1,
+                      clk, verify_en_valid, verify_snk_out.ready,
+                      verify_snk_in.valid, verify_snk_in.data, prev_verify_snk_in.data);
+
+  proc_dp_verify_data("verify_snk_in.bsn", c_rl, c_unsigned_0, to_unsigned(c_verify_bsn_gap, 32),
+                       clk, verify_en_sop, verify_snk_out.ready,
+                       verify_snk_in.sop, verify_snk_in.bsn, prev_verify_snk_in.bsn);
+
+  -- 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);
+
+  -- Verify output ready latency
+  proc_dp_verify_valid(clk, verify_en_valid, verify_snk_out.ready, prev_verify_snk_out.ready, verify_snk_in.valid);
+
+  -- Verify output channel
+  p_verify_channel : process(clk)
+  begin
+    if rising_edge(clk) then
+      if verify_snk_in.sop = '1' then
+        assert unsigned(verify_snk_in.channel) = exp_channel
+          report "wrong unmerged verify_snk_in.channel"
+          severity ERROR;
+      end if;
+    end if;
+  end process;
+
+  ------------------------------------------------------------------------------
+  -- DUT dp_packet_merge
+  ------------------------------------------------------------------------------
+
+  -- Merge every g_nof_pkt incomming packets into output packets
+  u_dp_packet_merge : entity work.dp_packet_merge
+  generic map (
+    g_nof_pkt       => g_nof_pkt,
+    g_align_at_sync => g_align_at_sync,
+    g_bsn_increment => g_bsn_increment,
+    g_bsn_err_bi    => c_bsn_err_bi
+  )
+  port map (
+    rst       => rst,
+    clk       => clk,
+
+    snk_out   => stimuli_src_in,
+    snk_in    => stimuli_src_out,
+
+    src_in    => dp_packet_merge_snk_in,
+    src_out   => dp_packet_merge_src_out
+  );
+
+  merged_pkt_cnt <= merged_pkt_cnt + 1 when rising_edge(clk) and dp_packet_merge_src_out.eop = '1';
+
+  exp_channel <= merged_pkt_cnt mod g_nof_pkt;
+
+  dp_packet_merge_snk_in <= dp_packet_merge_siso;
+
+  p_stimuli_unmerge : process(dp_packet_merge_src_out)
+  begin
+    dp_packet_merge_sosi     <= dp_packet_merge_src_out;
+    dp_packet_merge_sosi.err <= TO_DP_ERROR(merged_pkt_cnt);
+  end process;
+
+  ------------------------------------------------------------------------------
+  -- Optional reverse DUT dp_packet_unmerge
+  ------------------------------------------------------------------------------
+  u_dp_packet_unmerge : entity work.dp_packet_unmerge
+  generic map (
+    g_nof_pkt_max   => g_nof_pkt,
+    g_pkt_len       => g_pkt_len,
+    g_bsn_increment => g_bsn_increment
+  )
+  port map (
+    rst       => rst,
+    clk       => clk,
+
+    snk_out   => dp_packet_merge_siso,
+    snk_in    => dp_packet_merge_sosi,
+
+    src_in    => verify_snk_out,
+    src_out   => verify_snk_in
+  );
+
+  -- Map to slv to ease monitoring in wave window
+  stimuli_data <= stimuli_src_out.data(g_data_w - 1 downto 0);
+  verify_data  <= verify_snk_in.data(g_data_w - 1 downto 0);
+end tb;
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
new file mode 100644
index 0000000000..daa8ab9db6
--- /dev/null
+++ b/libraries/base/dp/tb/vhdl/tb_tb_dp_packet_merge_unmerge.vhd
@@ -0,0 +1,74 @@
+-- --------------------------------------------------------------------------
+-- 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.
+--
+-- ----------------------------------------------------------------------------
+
+library IEEE, dp_lib;
+use IEEE.std_logic_1164.all;
+use dp_lib.tb_dp_pkg.all;
+
+-- Author: E. Kooistra
+-- Purpose: Verify multiple variations of tb_dp_packet_merge_unmerge
+-- Description:
+-- Usage:
+-- > as 3
+-- > run -all
+
+entity tb_tb_dp_packet_merge_unmerge is
+end tb_tb_dp_packet_merge_unmerge;
+
+architecture tb of tb_tb_dp_packet_merge_unmerge is
+  constant c_nof_repeat            : natural := 100;
+  signal tb_end : std_logic := '0';  -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end'
+begin
+  --     -- general
+  --     g_flow_control_stimuli   : t_dp_flow_control_enum := e_active;  -- always active, random or pulse flow control
+  --     g_flow_control_verify    : t_dp_flow_control_enum := e_active;  -- always active, random or pulse flow control
+  --     -- specific
+  --     g_data_w                 : natural := 4;
+  --     g_nof_repeat             : natural := 20;
+  --     g_nof_pkt                : natural := 3;
+  --     g_pkt_len                : natural := 29;
+  --     g_pkt_gap                : natural := 0;
+  --     g_align_at_sync          : boolean := false;
+  --     g_verify_bsn_err         : boolean := false;
+  --     g_bsn_increment          : natural := 0;
+
+  u_act_act_8_nof_0      : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 0, 29, 0, false, false, 1);
+  u_act_act_8_nof_1      : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 1, 29, 0, false, false, 1);
+  u_act_act_8_nof_2      : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 2, 29, 0, false, false, 1);
+  u_act_act_8_nof_3      : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 3, 29, 0, false, false, 1);
+  u_act_act_8_nof_4      : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 4, 29, 0, false, false, 1);
+  u_act_act_8_nof_5      : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 5, 29, 0, false, false, 1);
+  u_act_act_8_nof_6      : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 6, 29, 0, false, false, 1);
+  u_act_act_8_nof_7      : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 7, 29, 0, false, false, 1);
+
+  u_rnd_act_8_nof_3      : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_active, 8, c_nof_repeat, 3, 29, 0, false, false, 2);
+  u_rnd_rnd_8_nof_3      : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_random, 8, c_nof_repeat, 3, 29, 0, false, false, 3);
+  u_pls_act_8_nof_3      : entity work.tb_dp_packet_merge_unmerge generic map ( e_pulse,  e_active, 8, c_nof_repeat, 3, 29, 0, false, false, 4);
+  u_pls_rnd_8_nof_3      : entity work.tb_dp_packet_merge_unmerge generic map ( e_pulse,  e_random, 8, c_nof_repeat, 3, 29, 0, false, false, 5);
+  u_pls_pls_8_nof_3      : entity work.tb_dp_packet_merge_unmerge generic map ( e_pulse,  e_pulse,  8, c_nof_repeat, 3, 29, 0, false, false, 6);
+
+  u_rnd_act_8_nof_1      : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_active, 8, c_nof_repeat, 1, 29,  0, false, false, 1);
+  u_rnd_act_8_nof_3_gap  : entity work.tb_dp_packet_merge_unmerge generic map ( e_random, e_active, 8, c_nof_repeat, 3, 29, 17, false, false, 1);
+
+  u_act_act_8_nof_3_no_err : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 3, 29, 0, false, true, 0);
+  u_act_act_8_nof_3_err_10 : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 3, 29, 0, false, true, 1);
+  u_act_act_8_nof_3_err_11 : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 3, 29, 0, false, true, 1);
+  u_act_act_8_nof_3_err_12 : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 3, 29, 0, false, true, 1);
+  u_act_act_8_nof_3_err_13 : entity work.tb_dp_packet_merge_unmerge generic map ( e_active, e_active, 8, c_nof_repeat, 3, 29, 0, false, true, 1);
+end tb;
-- 
GitLab