diff --git a/applications/rdma_demo/libraries/rdma_packetiser/hdllib.cfg b/applications/rdma_demo/libraries/rdma_packetiser/hdllib.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..c449e83d95a91ecdf4244a1e792bbbc7ca5316ae
--- /dev/null
+++ b/applications/rdma_demo/libraries/rdma_packetiser/hdllib.cfg
@@ -0,0 +1,22 @@
+hdl_lib_name = rdma_packetiser
+hdl_library_clause_name = rdma_packetiser_lib
+hdl_lib_uses_synth = common dp eth 
+hdl_lib_uses_sim = diag technology
+hdl_lib_technology = 
+
+synth_files =
+    src/vhdl/rdma_packetiser_pkg.vhd
+    src/vhdl/rdma_packetiser_assemble_header.vhd
+
+test_bench_files = 
+    tb/vhdl/tb_rdma_packetiser_assemble_header.vhd
+    tb/vhdl/tb_tb_rdma_packetiser_assemble_header.vhd
+
+regression_test_vhdl = 
+    tb/vhdl/tb_tb_rdma_packetiser_assemble_header.vhd
+
+
+[modelsim_project_file]
+
+[quartus_project_file]
+
diff --git a/applications/rdma_demo/libraries/rdma_packetiser/src/vhdl/rdma_packetiser_assemble_header.vhd b/applications/rdma_demo/libraries/rdma_packetiser/src/vhdl/rdma_packetiser_assemble_header.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..e51876b7d4d5972fcb4e5f70adbbe29595b0f768
--- /dev/null
+++ b/applications/rdma_demo/libraries/rdma_packetiser/src/vhdl/rdma_packetiser_assemble_header.vhd
@@ -0,0 +1,195 @@
+-------------------------------------------------------------------------------
+--
+-- 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: R. van der Walle
+-- Purpose: Assembles the RDMA header at snk_in.sop
+-- Description:
+-- Generates a RoCEv2 header (ETH + UDP + IP + RDMA). See [1].
+-- Generics:
+-- . g_use_immediate: When true, the immediate data field will be added to the
+--   header.
+-- . g_use_msg_cnt_as_immediate: When true, the message counter going from 0 to
+--   "nof_msg" is used as immediate data. When false, the "immediate_data"
+--   input port is used.
+-- Signal inputs:
+-- . immediate_data: Will be used as immediate data when
+--   g_use_msg_cnt_as_immediate = False.
+-- . block_len: Should be set to the length of the incoming data frame in octets.
+-- . nof_packets_in_msg: Should be set to the desired amount of packets in a message.
+-- . nof_msg: Should be set to the desired amount of messages, this determines the
+--   address space aswell, see remarks.
+-- . dma_len: The amount with which the address should increase every message,
+--   this should be set to >= nof_packets_in_msg * block_len.
+-- . start_address: The start address for the virtual_address field.
+-- Remarks
+-- . The hdr_fields_slv output is set one st_clk cycle after snk_in.sop and will
+--   contain the RoCEv2 (RDMA over Converged Ethernet v2) header information for
+--   the corresponding data frame.
+-- . The virtual_address is set automatically by increasing it with dma_len every
+--   new message. The virtual address is reset to "start_address" when the number
+--   of messages has reached "nof_msg". Then the cycle repeats.
+-- . The PSN field (= Packet Sequence Number) is set to LSBs of the incoming BSN.
+--   This can be used to check the order or detect missing packets at the receiver.
+
+-- References:
+-- . [1] https://support.astron.nl/confluence/x/3pKrB
+
+library IEEE, common_lib, dp_lib, eth_lib;
+  use IEEE.std_logic_1164.all;
+  use IEEE.numeric_std.all;
+  use common_lib.common_pkg.all;
+  use common_lib.common_mem_pkg.all;
+  use common_lib.common_network_layers_pkg.all;
+  use common_lib.common_field_pkg.all;
+  use dp_lib.dp_stream_pkg.all;
+  use dp_lib.dp_components_pkg.all;
+  use eth_lib.eth_pkg.all;
+  use eth_lib.eth_tester_pkg.all;
+  use work.rdma_packetiser_pkg.all;
+
+entity rdma_packetiser_assemble_header is
+  generic (
+    g_use_immediate            : boolean := true;
+    g_use_msg_cnt_as_immediate : boolean := true
+  );
+  port (
+    -- Clocks and reset
+    st_clk             : in  std_logic;
+    st_rst             : in  std_logic;
+
+    snk_in             : in  t_dp_sosi := c_dp_sosi_rst;
+
+    hdr_fields_slv     : out std_logic_vector(1023 downto 0) := (others => '0');
+
+    immediate_data     : in  std_logic_vector(c_rdma_packetiser_roce_imm_len * c_octet_w - 1 downto 0) := (others => '0');
+    block_len          : in  std_logic_vector(c_halfword_w - 1 downto 0); -- in octets
+    nof_packets_in_msg : in  std_logic_vector(c_word_w - 1 downto 0);
+    nof_msg            : in  std_logic_vector(c_word_w - 1 downto 0);
+    dma_len            : in  std_logic_vector(c_word_w - 1 downto 0); -- = block_len * nof_packets_in_msg
+    start_address      : in  std_logic_vector(c_longword_w - 1 downto 0)
+  );
+end rdma_packetiser_assemble_header;
+
+architecture str of rdma_packetiser_assemble_header is
+  constant c_hdr_field_arr         : t_common_field_arr := sel_a_b(g_use_immediate, c_rdma_packetiser_roce_hdr_field_arr, c_rdma_packetiser_roce_no_imm_hdr_field_arr);
+  constant c_app_hdr_length        : natural := sel_a_b(g_use_immediate, c_rdma_packetiser_roce_hdr_len, c_rdma_packetiser_roce_no_imm_hdr_len);
+  constant c_udp_app_hdr_length    : natural := c_network_udp_header_len + c_app_hdr_length;
+  constant c_ip_udp_app_hdr_length : natural := c_network_ip_header_len + c_udp_app_hdr_length;
+
+  type t_state is (s_first, s_middle, s_last);
+  type t_reg is record -- record to keep the registers organized.
+    state              : t_state;
+    opcode             : std_logic_vector(c_byte_w - 1 downto 0);
+    immediate_data     : std_logic_vector(c_rdma_packetiser_roce_imm_len * c_octet_w - 1 downto 0);
+    psn                : std_logic_vector(c_word_w - 1 downto 0);
+    virtual_address    : unsigned(c_longword_w - 1 downto 0);
+    dma_len            : unsigned(c_word_w - 1 downto 0);
+    p_cnt              : natural; -- Packet count (0 to nof_packets_in_msg).
+    msg_cnt            : natural; -- message count (0 to nof_msg).
+    udp_total_length   : natural;
+    ip_total_length    : natural;
+    nof_packets_in_msg : natural;
+  end record;
+
+  constant c_reg_rst : t_reg := (s_first, (others => '1'), (others => '0'), (others => '0'), (others => '0'), (others => '0'), 0, 0, 0, 0, 0);
+  signal d, q : t_reg;
+begin
+  q <= d when rising_edge(st_clk);
+
+  -- State machine to derive RDMA header fields.
+  p_comb : process(st_rst, q, snk_in, nof_packets_in_msg, start_address, nof_msg, immediate_data, dma_len, block_len)
+    variable v : t_reg;
+  begin
+    v := q;
+    if snk_in.sop = '1' then
+      v.psn := resize_uvec(snk_in.bsn, c_word_w);
+      v.p_cnt := q.p_cnt + 1;
+      case q.state is
+        when s_first => -- wait to start a new message and set the first opcode.
+          v.p_cnt := 1;
+          if q.p_cnt >= v.nof_packets_in_msg then -- (re)set message counter and virtual address.
+            if q.msg_cnt >= to_uint(nof_msg) - 1 then
+              v.msg_cnt := 0;
+            else
+              v.msg_cnt := q.msg_cnt + 1;
+              v.virtual_address := q.virtual_address + q.dma_len;
+            end if;
+          end if;
+
+          if v.nof_packets_in_msg = 1 then -- set opcode to write_only.
+            v.opcode := c_rdma_packetiser_opcode_uc_write_only;
+            if g_use_immediate then  -- set opcode to write_only with immediate data.
+              v.opcode := c_rdma_packetiser_opcode_uc_write_only_imm;
+            end if;
+          elsif v.nof_packets_in_msg = 2 then -- set opcode to write_first.
+            v.state := s_last; -- next state is last as there are only 2 packets.
+            v.opcode := c_rdma_packetiser_opcode_uc_write_first;
+          elsif v.nof_packets_in_msg > 2 then
+            v.state := s_middle;
+            v.opcode := c_rdma_packetiser_opcode_uc_write_first;
+          end if;
+
+        when s_middle => -- wait unitl the first packet is done and set next opcode.
+          v.opcode := c_rdma_packetiser_opcode_uc_write_middle;
+          if q.p_cnt >= v.nof_packets_in_msg - 2 then -- wait until last middle packet
+            v.state := s_last;
+          end if;
+
+        when s_last => -- next packet must be last packet, set opcode.
+          v.state := s_first;
+          v.opcode := c_rdma_packetiser_opcode_uc_write_last;
+          if g_use_immediate then -- set opcode to write_last with immediate data
+            v.opcode := c_rdma_packetiser_opcode_uc_write_last_imm;
+          end if;
+      end case;
+    end if;
+
+    if v.msg_cnt = 0 then -- set on new message
+      v.virtual_address    := unsigned(start_address);
+      v.dma_len            := unsigned(dma_len);
+      v.udp_total_length   := c_udp_app_hdr_length    + to_uint(block_len) + c_rdma_packetiser_roce_icrc_len;
+      v.ip_total_length    := c_ip_udp_app_hdr_length + to_uint(block_len) + c_rdma_packetiser_roce_icrc_len;
+      v.nof_packets_in_msg := to_uint(nof_packets_in_msg);
+      v.immediate_data     := immediate_data;
+    end if;
+
+    if st_rst = '1' then
+      v := c_reg_rst;
+    end if;
+
+    d <= v;
+  end process;
+
+  hdr_fields_slv(field_hi(c_hdr_field_arr, "ip_total_length"     ) downto field_lo(c_hdr_field_arr, "ip_total_length"     )) <= TO_UVEC(q.ip_total_length, 16);
+  hdr_fields_slv(field_hi(c_hdr_field_arr, "udp_total_length"    ) downto field_lo(c_hdr_field_arr, "udp_total_length"    )) <= TO_UVEC(q.udp_total_length, 16);
+  hdr_fields_slv(field_hi(c_hdr_field_arr, "bth_opcode"          ) downto field_lo(c_hdr_field_arr, "bth_opcode"          )) <= q.opcode;
+  hdr_fields_slv(field_hi(c_hdr_field_arr, "bth_psn"             ) downto field_lo(c_hdr_field_arr, "bth_psn"             )) <= q.psn;
+  hdr_fields_slv(field_hi(c_hdr_field_arr, "reth_virtual_address") downto field_lo(c_hdr_field_arr, "reth_virtual_address")) <= std_logic_vector(q.virtual_address);
+  hdr_fields_slv(field_hi(c_hdr_field_arr, "reth_dma_length"     ) downto field_lo(c_hdr_field_arr, "reth_dma_length"     )) <= std_logic_vector(q.dma_len);
+
+  gen_use_immediate : if g_use_immediate generate
+    gen_use_msg_cnt : if g_use_msg_cnt_as_immediate generate
+      hdr_fields_slv(field_hi(c_hdr_field_arr, "immediate_data") downto field_lo(c_hdr_field_arr, "immediate_data")) <= TO_UVEC(q.msg_cnt, 32);
+    end generate;
+
+    gen_use_no_msg_cnt : if not g_use_msg_cnt_as_immediate generate
+      hdr_fields_slv(field_hi(c_hdr_field_arr, "immediate_data") downto field_lo(c_hdr_field_arr, "immediate_data")) <= q.immediate_data;
+    end generate;
+  end generate;
+end str;
diff --git a/applications/rdma_demo/libraries/rdma_packetiser/src/vhdl/rdma_packetiser_pkg.vhd b/applications/rdma_demo/libraries/rdma_packetiser/src/vhdl/rdma_packetiser_pkg.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..bebc9205415d93fa2e33336d48b6a880852ae40c
--- /dev/null
+++ b/applications/rdma_demo/libraries/rdma_packetiser/src/vhdl/rdma_packetiser_pkg.vhd
@@ -0,0 +1,272 @@
+-------------------------------------------------------------------------------
+--
+-- 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: R. vd Walle
+-- Purpose: This package contains rdma_packetiser specific constants and/or functions
+-- Description: See [1] for RDMA explanation.
+-- References:
+-- . [1] https://support.astron.nl/confluence/x/3pKrB
+-------------------------------------------------------------------------------
+library IEEE, common_lib;
+  use IEEE.std_logic_1164.all;
+  use common_lib.common_pkg.all;
+  use common_lib.common_mem_pkg.all;
+  use common_lib.common_field_pkg.all;
+  use common_lib.common_network_layers_pkg.all;
+
+package rdma_packetiser_pkg is
+  type t_rdma_packetiser_bth_header is record
+    opcode        : std_logic_vector( 7 downto 0);
+    se            : std_logic_vector( 0 downto 0);
+    m             : std_logic_vector( 0 downto 0);
+    pad           : std_logic_vector( 1 downto 0);
+    tver          : std_logic_vector( 3 downto 0);
+    partition_key : std_logic_vector(15 downto 0);
+    fres          : std_logic_vector( 0 downto 0);
+    bres          : std_logic_vector( 0 downto 0);
+    reserved_a    : std_logic_vector( 5 downto 0);
+    dest_qp       : std_logic_vector(15 downto 0);
+    ack_req       : std_logic_vector( 0 downto 0);
+    reserved_b    : std_logic_vector( 6 downto 0);
+    psn           : std_logic_vector(31 downto 0);
+  end record;
+
+  type t_rdma_packetiser_reth_header is record
+    virtual_address : std_logic_vector(63 downto 0);
+    r_key           : std_logic_vector(31 downto 0);
+    dma_length      : std_logic_vector(31 downto 0);
+  end record;
+
+  type t_rdma_packetiser_roce_header is record
+    eth            : t_network_eth_header;
+    ip             : t_network_ip_header;
+    udp            : t_network_udp_header;
+    bth            : t_rdma_packetiser_bth_header;
+    reth           : t_rdma_packetiser_reth_header;
+    immediate_data : std_logic_vector(31 downto 0);
+  end record;
+
+  constant c_rdma_packetiser_nof_octet_generate_100gbe : natural := 64;
+  constant c_rdma_packetiser_nof_octet_output_100gbe   : natural := 64;
+
+  -- hdr_field_sel bit selects where the hdr_field value is set:
+  -- . 0 = data path controlled, value is set in data path, so field_default()
+  --       is not used.
+  -- . 1 = MM controlled, value is set via MM or by the field_default(), so any
+  --       data path setting in eth_tester.vhd is not used.
+  -- Remarks:
+  -- . For constant values it is convenient to use MM controlled, because then
+  --   the field_default() is used that can be set here in
+  --   c_rdma_packetiser_hdr_field_arr.
+  -- . For reserved values it is convenient to use MM controlled, because then
+  --   in future they could still be changed via MM without having to recompile
+  --   the FW.
+  -- . Typically only use data path controlled if the value has to be set
+  --   dynamically, so dependent on the state of the FW.
+  -- . If a data path controlled field is not set in the FW, then it defaults
+  --   to 0 by declaring hdr_fields_in_arr with all 0. Hence e.g. udp_checksum
+  --   = 0 can be achieve via data path and default hdr_fields_in_arr = 0 or
+  --   via MM controlled and field_default(0).
+
+  -- RoCEv2 header for RDMA operation with immediate data
+  -- ETH + IP + UDP + Base Transport Header (BTH) + RDMA Extended Transport Header (RETH) + Immediate Data
+  constant c_rdma_packetiser_roce_nof_hdr_fields : natural := 3 + 12 + 4 + 13 + 3 + 1;
+  constant c_rdma_packetiser_roce_hdr_field_sel  : std_logic_vector(c_rdma_packetiser_roce_nof_hdr_fields - 1 downto 0) :=  "111" & "111011111001" & "0100" & "1111111111111" & "111" & "1";
+
+  constant c_rdma_packetiser_roce_hdr_field_arr : t_common_field_arr(
+    c_rdma_packetiser_roce_nof_hdr_fields - 1 downto 0) := (
+    ( field_name_pad("eth_dst_mac"         ), "RW", 48, field_default(0) ), -- set by M&C
+    ( field_name_pad("eth_src_mac"         ), "RW", 48, field_default(0) ), -- set by M&C
+    ( field_name_pad("eth_type"            ), "RW", 16, field_default(x"0800") ), -- fixed
+
+    ( field_name_pad("ip_version"          ), "RW",  4, field_default(4) ), -- fixed
+    ( field_name_pad("ip_header_length"    ), "RW",  4, field_default(5) ), -- fixed
+    ( field_name_pad("ip_services"         ), "RW",  8, field_default(0) ), -- fixed
+    ( field_name_pad("ip_total_length"     ), "RW", 16, field_default(0) ), -- set by data path
+    ( field_name_pad("ip_identification"   ), "RW", 16, field_default(0) ), -- fixed
+    ( field_name_pad("ip_flags"            ), "RW",  3, field_default(2) ), -- fixed
+    ( field_name_pad("ip_fragment_offset"  ), "RW", 13, field_default(0) ), -- fixed
+    ( field_name_pad("ip_time_to_live"     ), "RW",  8, field_default(127) ), -- fixed
+    ( field_name_pad("ip_protocol"         ), "RW",  8, field_default(17) ), -- fixed
+    ( field_name_pad("ip_header_checksum"  ), "RW", 16, field_default(0) ), -- set by data path
+    ( field_name_pad("ip_src_addr"         ), "RW", 32, field_default(0) ), -- set by M&C
+    ( field_name_pad("ip_dst_addr"         ), "RW", 32, field_default(0) ), -- set by M&C
+
+    ( field_name_pad("udp_src_port"        ), "RW", 16, field_default(0) ), -- set by M&C
+    ( field_name_pad("udp_dst_port"        ), "RW", 16, field_default(0) ), -- set by M&C
+    ( field_name_pad("udp_total_length"    ), "RW", 16, field_default(0) ), -- set by data path
+    ( field_name_pad("udp_checksum"        ), "RW", 16, field_default(0) ), -- fixed
+
+    ( field_name_pad("bth_opcode"          ), "RW",  8, field_default(x"FF") ), -- set by data path
+    ( field_name_pad("bth_se"              ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_m"               ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_pad"             ), "RW",  2, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_tver"            ), "RW",  4, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_partition_key"   ), "RW", 16, field_default(65535) ), -- set by M&C
+    ( field_name_pad("bth_fres"            ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_bres"            ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_reserved_a"      ), "RW",  6, field_default(0) ), -- fixed
+    ( field_name_pad("bth_dest_qp"         ), "RW", 16, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_ack_req"         ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_reserved_b"      ), "RW",  7, field_default(0) ), -- fixed
+    ( field_name_pad("bth_psn"             ), "RW", 32, field_default(0) ), -- set by data path
+
+    ( field_name_pad("reth_virtual_address"), "RW", 64, field_default(0) ), -- set by data path
+    ( field_name_pad("reth_r_key"          ), "RW", 32, field_default(0) ), -- set by M&C
+    ( field_name_pad("reth_dma_length"     ), "RW", 32, field_default(0) ), -- set by M&C
+
+    ( field_name_pad("immediate_data"      ), "RW", 32, field_default(0) ) -- set by data path or M&C
+    );
+  constant c_rdma_packetiser_roce_reg_hdr_dat_addr_w    : natural := ceil_log2(field_nof_words(c_rdma_packetiser_roce_hdr_field_arr, c_word_w));
+  constant c_rdma_packetiser_roce_reg_hdr_dat_addr_span : natural := 2**c_rdma_packetiser_roce_reg_hdr_dat_addr_w;
+
+  -- RoCEv2 header for RDMA operation without immediate data
+  -- ETH + IP + UDP + Base Transport Header (BTH) + RDMA Extended Transport Header (RETH), so no immediate data ("no_imm").
+  constant c_rdma_packetiser_roce_no_imm_nof_hdr_fields : natural := 3 + 12 + 4 + 13 + 3;
+  constant c_rdma_packetiser_roce_no_imm_hdr_field_sel  : std_logic_vector(c_rdma_packetiser_roce_no_imm_nof_hdr_fields - 1 downto 0) :=  "111" & "111011111001" & "0100" & "1111111111111" & "111";
+
+  constant c_rdma_packetiser_roce_no_imm_hdr_field_arr : t_common_field_arr(
+    c_rdma_packetiser_roce_no_imm_nof_hdr_fields - 1 downto 0) := (
+    ( field_name_pad("eth_dst_mac"         ), "RW", 48, field_default(0) ), -- set by M&C
+    ( field_name_pad("eth_src_mac"         ), "RW", 48, field_default(0) ), -- set by M&C
+    ( field_name_pad("eth_type"            ), "RW", 16, field_default(x"0800") ), -- fixed
+
+    ( field_name_pad("ip_version"          ), "RW",  4, field_default(4) ), -- fixed
+    ( field_name_pad("ip_header_length"    ), "RW",  4, field_default(5) ), -- fixed
+    ( field_name_pad("ip_services"         ), "RW",  8, field_default(0) ), -- fixed
+    ( field_name_pad("ip_total_length"     ), "RW", 16, field_default(0) ), -- set by data path
+    ( field_name_pad("ip_identification"   ), "RW", 16, field_default(0) ), -- fixed
+    ( field_name_pad("ip_flags"            ), "RW",  3, field_default(2) ), -- fixed
+    ( field_name_pad("ip_fragment_offset"  ), "RW", 13, field_default(0) ), -- fixed
+    ( field_name_pad("ip_time_to_live"     ), "RW",  8, field_default(127) ), -- fixed
+    ( field_name_pad("ip_protocol"         ), "RW",  8, field_default(17) ), -- fixed
+    ( field_name_pad("ip_header_checksum"  ), "RW", 16, field_default(0) ), -- set by data path
+    ( field_name_pad("ip_src_addr"         ), "RW", 32, field_default(0) ), -- set by M&C
+    ( field_name_pad("ip_dst_addr"         ), "RW", 32, field_default(0) ), -- set by M&C
+
+    ( field_name_pad("udp_src_port"        ), "RW", 16, field_default(0) ), -- set by M&C
+    ( field_name_pad("udp_dst_port"        ), "RW", 16, field_default(0) ), -- set by M&C
+    ( field_name_pad("udp_total_length"    ), "RW", 16, field_default(0) ), -- set by data path
+    ( field_name_pad("udp_checksum"        ), "RW", 16, field_default(0) ), -- fixed
+
+    ( field_name_pad("bth_opcode"          ), "RW",  8, field_default(x"FF") ), -- set by data path
+    ( field_name_pad("bth_se"              ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_m"               ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_pad"             ), "RW",  2, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_tver"            ), "RW",  4, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_partition_key"   ), "RW", 16, field_default(65535) ), -- set by M&C
+    ( field_name_pad("bth_fres"            ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_bres"            ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_reserved_a"      ), "RW",  6, field_default(0) ), -- fixed
+    ( field_name_pad("bth_dest_qp"         ), "RW", 16, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_ack_req"         ), "RW",  1, field_default(0) ), -- set by M&C
+    ( field_name_pad("bth_reserved_b"      ), "RW",  7, field_default(0) ), -- fixed
+    ( field_name_pad("bth_psn"             ), "RW", 32, field_default(0) ), -- set by data path
+
+    ( field_name_pad("reth_virtual_address"), "RW", 64, field_default(0) ), -- set by data path
+    ( field_name_pad("reth_r_key"          ), "RW", 32, field_default(0) ), -- set by M&C
+    ( field_name_pad("reth_dma_length"     ), "RW", 32, field_default(0) ) -- set by M&C
+    );
+  constant c_rdma_packetiser_roce_reg_no_imm_hdr_dat_addr_w    : natural := ceil_log2(field_nof_words(c_rdma_packetiser_roce_no_imm_hdr_field_arr, c_word_w));
+  constant c_rdma_packetiser_roce_reg_no_imm_hdr_dat_addr_span : natural := 2**c_rdma_packetiser_roce_reg_no_imm_hdr_dat_addr_w;
+
+  constant c_rdma_packetiser_roce_hdr_len         : natural := 32;  -- octets
+  constant c_rdma_packetiser_roce_imm_len         : natural := 4;   -- octets
+  constant c_rdma_packetiser_roce_icrc_len        : natural := 4;   -- octets
+  constant c_rdma_packetiser_roce_no_imm_hdr_len  : natural := c_rdma_packetiser_roce_hdr_len - c_rdma_packetiser_roce_imm_len;
+
+  constant c_rdma_packetiser_opcode_uc_send_first      : std_logic_vector := "001" & "00000";
+  constant c_rdma_packetiser_opcode_uc_send_middle     : std_logic_vector := "001" & "00001";
+  constant c_rdma_packetiser_opcode_uc_send_last       : std_logic_vector := "001" & "00010"; -- without immediate
+  constant c_rdma_packetiser_opcode_uc_send_last_imm   : std_logic_vector := "001" & "00011"; -- with immediate
+  constant c_rdma_packetiser_opcode_uc_send_only       : std_logic_vector := "001" & "00100"; -- without immediate
+  constant c_rdma_packetiser_opcode_uc_send_only_imm   : std_logic_vector := "001" & "00101"; -- with immediate
+  constant c_rdma_packetiser_opcode_uc_write_first     : std_logic_vector := "001" & "00110";
+  constant c_rdma_packetiser_opcode_uc_write_middle    : std_logic_vector := "001" & "00111";
+  constant c_rdma_packetiser_opcode_uc_write_last      : std_logic_vector := "001" & "01000"; -- without immediate
+  constant c_rdma_packetiser_opcode_uc_write_last_imm  : std_logic_vector := "001" & "01001"; -- with immediate
+  constant c_rdma_packetiser_opcode_uc_write_only      : std_logic_vector := "001" & "01010"; -- without immediate
+  constant c_rdma_packetiser_opcode_uc_write_only_imm  : std_logic_vector := "001" & "01011"; -- with immediate
+
+  function func_rdma_packetiser_map_header(hdr_fields_raw : std_logic_vector; use_immediate : boolean) return t_rdma_packetiser_roce_header;
+
+end rdma_packetiser_pkg;
+
+package body rdma_packetiser_pkg is
+  function func_rdma_packetiser_map_header(hdr_fields_raw : std_logic_vector; use_immediate : boolean) return t_rdma_packetiser_roce_header is
+    variable v : t_rdma_packetiser_roce_header;
+    constant c_hdr_field_arr : t_common_field_arr := sel_a_b(use_immediate, c_rdma_packetiser_roce_hdr_field_arr, c_rdma_packetiser_roce_no_imm_hdr_field_arr);
+  begin
+    -- eth header
+    v.eth.dst_mac          := hdr_fields_raw(field_hi(c_hdr_field_arr, "eth_dst_mac") downto field_lo(c_hdr_field_arr, "eth_dst_mac"));
+    v.eth.src_mac          := hdr_fields_raw(field_hi(c_hdr_field_arr, "eth_src_mac") downto field_lo(c_hdr_field_arr, "eth_src_mac"));
+    v.eth.eth_type         := hdr_fields_raw(field_hi(c_hdr_field_arr, "eth_type")    downto field_lo(c_hdr_field_arr, "eth_type"));
+
+    -- ip header
+    v.ip.version           := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_version")         downto field_lo(c_hdr_field_arr, "ip_version"));
+    v.ip.header_length     := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_header_length")   downto field_lo(c_hdr_field_arr, "ip_header_length"));
+    v.ip.services          := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_services")        downto field_lo(c_hdr_field_arr, "ip_services"));
+    v.ip.total_length      := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_total_length")    downto field_lo(c_hdr_field_arr, "ip_total_length"));
+    v.ip.identification    := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_identification")  downto field_lo(c_hdr_field_arr, "ip_identification"));
+    v.ip.flags             := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_flags")           downto field_lo(c_hdr_field_arr, "ip_flags"));
+    v.ip.fragment_offset   := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_fragment_offset") downto field_lo(c_hdr_field_arr, "ip_fragment_offset"));
+    v.ip.time_to_live      := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_time_to_live")    downto field_lo(c_hdr_field_arr, "ip_time_to_live"));
+    v.ip.protocol          := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_protocol")        downto field_lo(c_hdr_field_arr, "ip_protocol"));
+    v.ip.header_checksum   := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_header_checksum") downto field_lo(c_hdr_field_arr, "ip_header_checksum"));
+    v.ip.src_ip_addr       := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_src_addr")        downto field_lo(c_hdr_field_arr, "ip_src_addr"));
+    v.ip.dst_ip_addr       := hdr_fields_raw(field_hi(c_hdr_field_arr, "ip_dst_addr")        downto field_lo(c_hdr_field_arr, "ip_dst_addr"));
+
+    -- udp header
+    v.udp.src_port         := hdr_fields_raw(field_hi(c_hdr_field_arr, "udp_src_port")     downto field_lo(c_hdr_field_arr, "udp_src_port"));
+    v.udp.dst_port         := hdr_fields_raw(field_hi(c_hdr_field_arr, "udp_dst_port")     downto field_lo(c_hdr_field_arr, "udp_dst_port"));
+    v.udp.total_length     := hdr_fields_raw(field_hi(c_hdr_field_arr, "udp_total_length") downto field_lo(c_hdr_field_arr, "udp_total_length"));
+    v.udp.checksum         := hdr_fields_raw(field_hi(c_hdr_field_arr, "udp_checksum")     downto field_lo(c_hdr_field_arr, "udp_checksum"));
+
+    -- bth header
+    v.bth.opcode           := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_opcode")        downto field_lo(c_hdr_field_arr, "bth_opcode"));
+    v.bth.se               := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_se")            downto field_lo(c_hdr_field_arr, "bth_se"));
+    v.bth.m                := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_m")             downto field_lo(c_hdr_field_arr, "bth_m"));
+    v.bth.pad              := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_pad")           downto field_lo(c_hdr_field_arr, "bth_pad"));
+    v.bth.tver             := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_tver")          downto field_lo(c_hdr_field_arr, "bth_tver"));
+    v.bth.partition_key    := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_partition_key") downto field_lo(c_hdr_field_arr, "bth_partition_key"));
+    v.bth.fres             := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_fres")          downto field_lo(c_hdr_field_arr, "bth_fres"));
+    v.bth.bres             := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_bres")          downto field_lo(c_hdr_field_arr, "bth_bres"));
+    v.bth.reserved_a       := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_reserved_a")    downto field_lo(c_hdr_field_arr, "bth_reserved_a"));
+    v.bth.dest_qp          := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_dest_qp")       downto field_lo(c_hdr_field_arr, "bth_dest_qp"));
+    v.bth.ack_req          := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_ack_req")       downto field_lo(c_hdr_field_arr, "bth_ack_req"));
+    v.bth.reserved_b       := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_reserved_b")    downto field_lo(c_hdr_field_arr, "bth_reserved_b"));
+    v.bth.psn              := hdr_fields_raw(field_hi(c_hdr_field_arr, "bth_psn")           downto field_lo(c_hdr_field_arr, "bth_psn"));
+
+    -- reth header
+    v.reth.virtual_address := hdr_fields_raw(field_hi(c_hdr_field_arr, "reth_virtual_address") downto field_lo(c_hdr_field_arr, "reth_virtual_address"));
+    v.reth.r_key           := hdr_fields_raw(field_hi(c_hdr_field_arr, "reth_r_key")           downto field_lo(c_hdr_field_arr, "reth_r_key"));
+    v.reth.dma_length      := hdr_fields_raw(field_hi(c_hdr_field_arr, "reth_dma_length")      downto field_lo(c_hdr_field_arr, "reth_dma_length"));
+
+    --immediate data
+    v.immediate_data := (others => '0');
+    if use_immediate then
+      v.immediate_data := hdr_fields_raw(field_hi(c_hdr_field_arr, "immediate_data") downto field_lo(c_hdr_field_arr, "immediate_data"));
+    end if;
+
+    return v;
+  end func_rdma_packetiser_map_header;
+
+end rdma_packetiser_pkg;
diff --git a/applications/rdma_demo/libraries/rdma_packetiser/tb/vhdl/tb_rdma_packetiser_assemble_header.vhd b/applications/rdma_demo/libraries/rdma_packetiser/tb/vhdl/tb_rdma_packetiser_assemble_header.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..b6a19a3744ecbbf954656b5f6af1b60f151dd18d
--- /dev/null
+++ b/applications/rdma_demo/libraries/rdma_packetiser/tb/vhdl/tb_rdma_packetiser_assemble_header.vhd
@@ -0,0 +1,206 @@
+-------------------------------------------------------------------------------
+--
+-- 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: R. vd Walle
+-- Purpose:
+-- . test bench for rdma_packetiser_assemble_header
+-- Description:
+-- . Generates DP data using proc_dp_gen_block_data that streams into DUT.
+-- . Verifies the resulting header field array comming from the DUT by
+--   comparing to expected values.
+-- . The generics can be used to test specific conditions.
+--   . g_use_immediate: When true, the DUT will use the optional immediate data
+--     field.
+--   . g_use_msg_cnt_as_immediate: When true, the DUT will use the message count
+--     value as immediate data. When false, the DUT sets the immediate data to
+--     the value of the immediate_data input port.
+--   . g_nof_rep: number of packets the TB should generate.
+--   . g_frame_len: length of the data frames that the TB should generate.
+--   . g_start_address: 64 bit value to use as a start address for the DUT
+--   . g_nof_packets_in_msg: The number of packets the DUT should put in one
+--     RDMA message.
+--   . g_nof_msg: Number of RDMA messages the DUT should create.
+-------------------------------------------------------------------------------
+
+library IEEE, common_lib, dp_lib;
+  use IEEE.std_logic_1164.all;
+  use IEEE.numeric_std.all;
+  use common_lib.common_pkg.all;
+  use common_lib.common_mem_pkg.all;
+  use common_lib.common_network_layers_pkg.all;
+  use common_lib.tb_common_pkg.all;
+  use common_lib.tb_common_mem_pkg.all;
+  use dp_lib.tb_dp_pkg.all;
+  use dp_lib.dp_stream_pkg.all;
+  use work.rdma_packetiser_pkg.all;
+
+entity tb_rdma_packetiser_assemble_header is
+  generic (
+    g_use_immediate            : boolean := true;
+    g_use_msg_cnt_as_immediate : boolean := true;
+    g_nof_rep                  : natural := 60;
+    g_frame_len                : natural := 15;
+    g_start_address            : unsigned(c_longword_w - 1 downto 0) := (others => '0');
+    g_nof_packets_in_msg       : natural := 4;
+    g_nof_msg                  : natural := 3
+  );
+end tb_rdma_packetiser_assemble_header;
+
+architecture tb of tb_rdma_packetiser_assemble_header is
+  constant c_dp_clk_period      : time := 5 ns;  -- 200 MHz
+  constant c_data_w             : natural := c_word_w;
+  constant c_data_init          : natural := 13;
+  constant c_hdr_fields_slv_rst : std_logic_vector(1023 downto 0) := (others => '0');
+  constant c_rdma_hdr_len : natural := c_rdma_packetiser_roce_icrc_len + sel_a_b(
+    g_use_immediate,
+    c_rdma_packetiser_roce_hdr_len, c_rdma_packetiser_roce_no_imm_hdr_len);
+  constant c_block_len          : natural := g_frame_len * (c_data_w / c_octet_w);
+  constant c_dma_len            : natural := c_block_len * g_nof_packets_in_msg;
+
+  signal tb_end              : std_logic := '0';
+
+  signal dp_clk              : std_logic := '1';
+  signal dp_rst              : std_logic;
+
+  signal immediate_data      : std_logic_vector(c_word_w - 1 downto 0) := X"89ABCDEF";
+  signal block_len           : std_logic_vector(c_halfword_w - 1 downto 0) := TO_UVEC(c_block_len, c_halfword_w);
+  signal nof_packets_in_msg  : std_logic_vector(c_word_w - 1 downto 0) := TO_UVEC(g_nof_packets_in_msg, c_word_w);
+  signal nof_msg             : std_logic_vector(c_word_w - 1 downto 0) := TO_UVEC(g_nof_msg, c_word_w);
+  signal dma_len             : std_logic_vector(c_word_w - 1 downto 0) := TO_UVEC(c_dma_len, c_word_w);
+  signal start_address       : std_logic_vector(c_longword_w - 1 downto 0) := std_logic_vector(g_start_address);
+
+  signal hdr_fields_slv      : std_logic_vector(1023 downto 0) := (others => '0');
+  signal rx_rdma_header         : t_rdma_packetiser_roce_header;
+  signal exp_rdma_header     : t_rdma_packetiser_roce_header := func_rdma_packetiser_map_header(c_hdr_fields_slv_rst, g_use_immediate);
+  signal in_en : std_logic := '0';
+
+  signal snk_in  : t_dp_sosi := c_dp_sosi_rst;
+  signal snk_out : t_dp_siso := c_dp_siso_rdy;
+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;
+
+  rx_rdma_header <= func_rdma_packetiser_map_header(hdr_fields_slv, g_use_immediate );
+
+  p_dp_stimuli : process
+  begin
+    -- dp stimuli
+    proc_common_wait_until_low(dp_clk, dp_rst);
+    proc_common_wait_some_cycles(dp_clk, 100);
+    in_en <= '1';
+    for rep in 0 to g_nof_rep - 1 loop
+      proc_dp_gen_block_data(1, true, c_data_w, c_data_w, c_data_init, 0, 0, g_frame_len, 0, 0, '0', TO_DP_BSN(rep), dp_clk, in_en, snk_out, snk_in);
+    end loop;
+    proc_common_wait_some_cycles(dp_clk, 100);
+    in_en <= '0';
+    wait;
+  end process;
+
+  -- check if values in rdma_packetiser_assemble_header match with expected values
+  p_verify_rdma_header : process
+    variable v_exp_ip_total_length      : natural;
+    variable v_exp_udp_total_length     : natural;
+    variable v_exp_bth_opcode           : std_logic_vector(c_byte_w - 1 downto 0);
+    variable v_exp_bth_psn              : natural;
+    variable v_exp_reth_virtual_address : unsigned(c_longword_w - 1 downto 0);
+    variable v_exp_reth_dma_length      : natural;
+    variable v_exp_immediate_data       : std_logic_vector(c_word_w - 1 downto 0);
+    variable v_p, v_m                   : natural := 0;
+  begin
+    for rep in 0 to g_nof_rep - 1 loop
+      proc_common_wait_until_high(dp_clk, snk_in.sop);  -- wait for sop
+
+      v_exp_ip_total_length      := c_network_ip_header_len + c_network_udp_header_len + c_rdma_hdr_len + to_uint(block_len);
+      v_exp_udp_total_length     := c_network_udp_header_len + c_rdma_hdr_len + to_uint(block_len);
+      v_exp_bth_psn              := v_p;
+      v_exp_reth_virtual_address := g_start_address + to_unsigned((v_m mod g_nof_msg) * c_dma_len, c_longword_w);
+      v_exp_reth_dma_length      := c_dma_len;
+      v_exp_immediate_data       := sel_a_b(g_use_immediate,
+      sel_a_b(g_use_msg_cnt_as_immediate, to_uvec((v_m mod g_nof_msg), c_word_w), immediate_data), to_uvec(0, c_word_w));
+
+      -- determine expected opcode
+      if v_p mod g_nof_packets_in_msg = 0 then
+        v_exp_bth_opcode := c_rdma_packetiser_opcode_uc_write_first;
+        if g_nof_packets_in_msg = 1 and g_use_immediate then
+          v_exp_bth_opcode := c_rdma_packetiser_opcode_uc_write_only_imm;
+        elsif g_nof_packets_in_msg = 1 then
+          v_exp_bth_opcode := c_rdma_packetiser_opcode_uc_write_only;
+        end if;
+      elsif v_p mod g_nof_packets_in_msg = g_nof_packets_in_msg - 1 then
+        v_exp_bth_opcode := c_rdma_packetiser_opcode_uc_write_last;
+        if g_use_immediate then
+          v_exp_bth_opcode := c_rdma_packetiser_opcode_uc_write_last_imm;
+        end if;
+      else
+        v_exp_bth_opcode := c_rdma_packetiser_opcode_uc_write_middle;
+      end if;
+
+      -- increase counters
+      v_p := v_p + 1;
+      v_m := v_p / g_nof_packets_in_msg;
+
+      -- assign expected values to signal to view in wave window.
+      exp_rdma_header.ip.total_length      <= to_uvec(v_exp_ip_total_length, c_halfword_w);
+      exp_rdma_header.udp.total_length     <= to_uvec(v_exp_udp_total_length, c_halfword_w );
+      exp_rdma_header.bth.opcode           <= v_exp_bth_opcode;
+      exp_rdma_header.bth.psn              <= to_uvec(v_exp_bth_psn, c_word_w);
+      exp_rdma_header.reth.virtual_address <= std_logic_vector(v_exp_reth_virtual_address);
+      exp_rdma_header.reth.dma_length      <= to_uvec(v_exp_reth_dma_length, c_word_w);
+      exp_rdma_header.immediate_data       <= v_exp_immediate_data;
+      proc_common_wait_some_cycles(dp_clk, 1);
+
+      -- assert when header is not as expected.
+      assert rx_rdma_header                      = exp_rdma_header                      report "Wrong rx_rdma_header" severity error;
+      assert rx_rdma_header.ip.total_length      = exp_rdma_header.ip.total_length      report "Wrong rx_rdma_header.ip.total_length value" severity error;
+      assert rx_rdma_header.udp.total_length     = exp_rdma_header.udp.total_length     report "Wrong rx_rdma_header.udp.total_length value" severity error;
+      assert rx_rdma_header.bth.opcode           = exp_rdma_header.bth.opcode           report "Wrong rx_rdma_header.bth.opcode value" severity error;
+      assert rx_rdma_header.bth.psn              = exp_rdma_header.bth.psn              report "Wrong rx_rdma_header.bth.psn value" severity error;
+      assert rx_rdma_header.reth.virtual_address = exp_rdma_header.reth.virtual_address report "Wrong rx_rdma_header.reth.virtual_address value" severity error;
+      assert rx_rdma_header.reth.dma_length      = exp_rdma_header.reth.dma_length      report "Wrong rx_rdma_header.reth.dma_length value" severity error;
+      assert rx_rdma_header.immediate_data       = exp_rdma_header.immediate_data       report "Wrong rx_rdma_header.immediate_data value" severity error;
+    end loop;
+
+    proc_common_wait_some_cycles(dp_clk, 100);
+    tb_end <= '1';
+    wait;
+  end process;
+
+  u_dut: entity work.rdma_packetiser_assemble_header
+    generic map (
+      g_use_immediate            => g_use_immediate,
+      g_use_msg_cnt_as_immediate => g_use_msg_cnt_as_immediate
+    )
+    port map (
+      st_clk             => dp_clk,
+      st_rst             => dp_rst,
+
+      snk_in             => snk_in,
+      hdr_fields_slv     => hdr_fields_slv,
+
+      immediate_data     => immediate_data,
+      block_len          => block_len,
+      nof_packets_in_msg => nof_packets_in_msg,
+      nof_msg            => nof_msg,
+      dma_len            => dma_len,
+      start_address      => start_address
+    );
+end tb;
diff --git a/applications/rdma_demo/libraries/rdma_packetiser/tb/vhdl/tb_tb_rdma_packetiser_assemble_header.vhd b/applications/rdma_demo/libraries/rdma_packetiser/tb/vhdl/tb_tb_rdma_packetiser_assemble_header.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..f660180caa85165c58e1994f55addcf8c66bdc66
--- /dev/null
+++ b/applications/rdma_demo/libraries/rdma_packetiser/tb/vhdl/tb_tb_rdma_packetiser_assemble_header.vhd
@@ -0,0 +1,61 @@
+-------------------------------------------------------------------------------
+--
+-- 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 : R vd Walle
+-- Purpose: Verify multiple variations of tb_rdma_packetiser_assemble_header
+-- Description:
+-- Usage:
+-- > as 3
+-- > run -all
+-------------------------------------------------------------------------------
+
+library IEEE;
+  use IEEE.std_logic_1164.all;
+  use IEEE.numeric_std.all;
+
+entity tb_tb_rdma_packetiser_assemble_header is
+end tb_tb_rdma_packetiser_assemble_header;
+
+architecture tb of tb_tb_rdma_packetiser_assemble_header is
+  constant c_low_start_addr  : unsigned(63 downto 0) := X"000000000000BCDF"; -- arbitrary low start address
+  constant c_high_start_addr : unsigned(63 downto 0) := X"CBA9876543210000"; -- arbitrary high start address
+  signal tb_end : std_logic := '0';  -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end'
+begin
+  -- All generics of TB
+  --    g_use_immediate            : boolean := true;
+  --    g_use_msg_cnt_as_immediate : boolean := true;
+  --    g_nof_rep                  : natural := 15;
+  --    g_frame_len                : natural := 15;
+  --    g_start_address            : unsigned(c_longword_w - 1 downto 0) := (others => '0');
+  --    g_nof_packets_in_msg       : natural := 4;
+  --    g_nof_msg                  : natural := 3
+
+  u_lo_addr    : entity work.tb_rdma_packetiser_assemble_header generic map( true,  true,  20,   15,   c_low_start_addr,  4, 5);
+  u_hi_addr    : entity work.tb_rdma_packetiser_assemble_header generic map( true,  true,  20,   15,   c_high_start_addr, 4, 5);
+  u_no_mid     : entity work.tb_rdma_packetiser_assemble_header generic map( true,  true,  20,   15,   c_high_start_addr, 2, 5);
+  u_wr_only    : entity work.tb_rdma_packetiser_assemble_header generic map( true,  true,  20,   15,   c_high_start_addr, 1, 5);
+  u_large      : entity work.tb_rdma_packetiser_assemble_header generic map( true,  true,  10,   2000, c_low_start_addr,  3, 1);
+  u_no_imm_cnt : entity work.tb_rdma_packetiser_assemble_header generic map( false, true,  100,  15,   c_low_start_addr,  4, 10);
+  u_no_cnt     : entity work.tb_rdma_packetiser_assemble_header generic map( true,  false, 20,   15,   c_low_start_addr,  4, 5);
+  u_no_imm     : entity work.tb_rdma_packetiser_assemble_header generic map( false, false, 30,   7,    c_high_start_addr, 3, 2);
+  u_one        : entity work.tb_rdma_packetiser_assemble_header generic map( true,  true,  20,   1,    c_low_start_addr,  1, 5);
+  u_many       : entity work.tb_rdma_packetiser_assemble_header generic map( true,  true,  6000, 3,    c_low_start_addr,  5, 1000);
+end tb;