diff --git a/libraries/technology/tse/hdllib.cfg b/libraries/technology/tse/hdllib.cfg
index fce238d38f245af8e05a0fe07a79077a1b67a2fd..4f94c143b87dc34c5d00cc81a0d0f4f985b040d9 100644
--- a/libraries/technology/tse/hdllib.cfg
+++ b/libraries/technology/tse/hdllib.cfg
@@ -12,3 +12,5 @@ synth_files =
     tech_tse.vhd
 
 test_bench_files =
+    tb_tech_tse_pkg.vhd
+    tb_tech_tse.vhd
diff --git a/libraries/technology/tse/tb_tech_tse.vhd b/libraries/technology/tse/tb_tech_tse.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..05be5548b2ae4e99c42fba3738c6d76fedbae80b
--- /dev/null
+++ b/libraries/technology/tse/tb_tech_tse.vhd
@@ -0,0 +1,246 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright (C) 2009
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-------------------------------------------------------------------------------
+
+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 dp_lib.dp_stream_pkg.ALL;
+USE common_lib.eth_layers_pkg.ALL;
+USE WORK.tse_pkg.ALL;
+USE WORK.tb_tse_pkg.ALL;
+
+
+ENTITY tb_tse IS
+  -- Test bench control parameters
+  GENERIC (
+    --   g_data_type = c_tb_tse_data_type_symbols  = 0
+    --   g_data_type = c_tb_tse_data_type_counter  = 1
+    g_data_type : NATURAL := c_tb_tse_data_type_symbols
+  );
+END tb_tse;
+
+
+ARCHITECTURE tb OF tb_tse IS
+
+  -- as 10
+  -- run 50 us
+  
+  CONSTANT sys_clk_period       : TIME := 10 ns;  -- 100 MHz
+  CONSTANT eth_clk_period       : TIME :=  8 ns;  -- 125 MHz
+  CONSTANT cable_delay          : TIME := 12 ns;
+
+  CONSTANT c_promis_en          : BOOLEAN := FALSE;
+  CONSTANT c_tx_ready_latency   : NATURAL := c_tse_tx_ready_latency;  -- 0, 1 are supported, must match TSE MAC c_tse_tx_ready_latency
+  CONSTANT c_nof_tx_not_valid   : NATURAL := 0;  -- when > 0 then pull tx valid low for c_nof_tx_not_valid beats during tx
+
+  CONSTANT c_word_align         : STD_LOGIC_VECTOR(c_eth_word_align_w-1 DOWNTO 0) := TO_UVEC(c_eth_word_align, c_eth_word_align_w);
+  CONSTANT c_dst_mac            : STD_LOGIC_VECTOR(c_eth_mac_slv'RANGE) := X"10FA01020300";
+  CONSTANT c_src_mac            : STD_LOGIC_VECTOR(c_eth_mac_slv'RANGE) := X"123456789ABC";  -- = 12-34-56-78-9A-BC
+  CONSTANT c_ethertype          : STD_LOGIC_VECTOR(c_eth_type_slv'RANGE) := X"10FA";
+  CONSTANT c_etherlen           : STD_LOGIC_VECTOR(c_eth_type_slv'RANGE) := "0000000000010000";
+
+  -- Packet headers
+  CONSTANT c_eth_header_loopback : t_eth_header := (c_word_align, c_src_mac, c_src_mac, c_ethertype);
+  CONSTANT c_eth_header_etherlen : t_eth_header := (c_word_align, c_src_mac, c_src_mac, c_etherlen);
+  
+  SIGNAL total_header_loopback   : t_eth_total_header;
+  SIGNAL total_header_etherlen   : t_eth_total_header;
+  
+  -- Clocks and reset
+  SIGNAL eth_clk           : STD_LOGIC := '0';  -- tse reference clock
+  SIGNAL sys_clk           : STD_LOGIC := '0';  -- system clock
+  SIGNAL st_clk            : STD_LOGIC;         -- stream clock
+  SIGNAL mm_clk            : STD_LOGIC;         -- memory-mapped bus clock
+  SIGNAL mm_rst            : STD_LOGIC;         -- reset synchronous with mm_clk
+
+  -- TSE MAC control interface
+  SIGNAL mm_init           : STD_LOGIC := '1';
+  SIGNAL mm_miso           : t_mem_miso;
+  SIGNAL mm_mosi           : t_mem_mosi;
+  
+  SIGNAL mm_psc_access     : STD_LOGIC;
+
+  -- TSE MAC transmit interface
+  -- . The tb is the ST source
+  SIGNAL tx_en             : STD_LOGIC := '1';
+  SIGNAL tx_siso           : t_dp_siso;
+  SIGNAL tx_sosi           : t_dp_sosi;
+  -- . MAC specific
+  SIGNAL tx_mac_in         : t_tse_tx_mac;
+  SIGNAL tx_mac_out        : t_tse_tx_mac;
+
+  -- TSE MAC receive interface
+  -- . The tb is the ST sink
+  SIGNAL rx_sosi           : t_dp_sosi;
+  SIGNAL rx_siso           : t_dp_siso;
+  -- . MAC specific
+  SIGNAL rx_mac_out        : t_tse_rx_mac;
+
+  -- TSE PHY interface
+  SIGNAL eth_txp           : STD_LOGIC;
+  SIGNAL eth_rxp           : STD_LOGIC;
+
+  SIGNAL tse_led           : t_tse_led;
+
+BEGIN
+
+  -- run 50 us
+  
+  eth_clk <= NOT eth_clk AFTER eth_clk_period/2;  -- TSE reference clock
+  sys_clk <= NOT sys_clk AFTER sys_clk_period/2;  -- System clock
+
+  mm_clk  <= sys_clk;
+  st_clk  <= sys_clk;
+  
+  -- Use signal to leave unused fields 'X'
+  total_header_loopback.eth <= c_eth_header_loopback;
+  total_header_etherlen.eth <= c_eth_header_etherlen;
+
+  p_mm_setup : PROCESS
+  BEGIN
+    mm_init  <= '1';
+    mm_mosi.wr <= '0';
+    mm_mosi.rd <= '0';
+
+    -- reset release
+    mm_rst <= '1';
+    FOR I IN 0 TO 9 LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+    mm_rst <= '0';
+    FOR I IN 0 TO 9 LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+
+    proc_tse_setup(c_promis_en, c_tse_tx_fifo_depth, c_tse_rx_fifo_depth, c_tx_ready_latency,
+                   c_src_mac, mm_psc_access,
+                   mm_clk, mm_miso, mm_mosi);
+    mm_init <= '0';
+    WAIT;
+  END PROCESS;
+
+  
+  p_ff_transmitter : PROCESS
+  BEGIN
+    -- . Avalon ST
+    tx_sosi.data  <= (OTHERS=>'0');
+    tx_sosi.valid <= '0';
+    tx_sosi.sop   <= '0';
+    tx_sosi.eop   <= '0';
+    tx_sosi.empty <= (OTHERS=>'0');
+    tx_sosi.err   <= (OTHERS=>'0');
+    -- . MAC specific
+    tx_mac_in.crc_fwd <= '0';  -- when '0' then TSE MAC generates the TX CRC field
+
+    WHILE mm_init/='0' LOOP
+      WAIT UNTIL rising_edge(st_clk);
+    END LOOP;
+    FOR I IN 0 TO 9 LOOP WAIT UNTIL rising_edge(st_clk); END LOOP;
+
+    -- Loopback txp->rxp so DST_MAC = c_src_mac to send to itself
+    
+    -- TX frame:
+    -- . I=0 is empty payload, so only 4 words of the ETH header with 46 padding zeros, so empty = 2
+    -- . For I=1 to 46 the payload length remains 46 with padding zeros, so empty = 2
+    -- . For I>46 the payload length is I and empty = 4 - (I mod 4)
+    
+--     proc_tse_tx_packet(total_header_etherlen, 16, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 16, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 16, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+    FOR I IN 0 TO 59 LOOP
+      proc_tse_tx_packet(total_header_loopback, I, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+    END LOOP;
+--     proc_tse_tx_packet(total_header_loopback, 100, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 101, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 102, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 103, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1499, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);  -- verify st empty
+--     proc_tse_tx_packet(total_header_loopback, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+--     proc_tse_tx_packet(total_header_loopback, 1501, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);  -- verify c_eth_payload_max
+--     proc_tse_tx_packet(total_header_loopback,  100, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, tx_en, tx_siso, tx_sosi);
+
+    WAIT;
+  END PROCESS;
+
+  
+  p_ff_receiver : PROCESS
+  BEGIN
+    -- . Avalon ST
+    rx_siso.ready <= '0';
+
+    WHILE mm_init/='0' LOOP
+      WAIT UNTIL rising_edge(st_clk);
+    END LOOP;
+
+    -- Receive forever
+    WHILE TRUE LOOP
+      proc_tse_rx_packet(total_header_loopback, g_data_type, st_clk, rx_sosi, rx_siso);
+    END LOOP;
+
+    WAIT;
+  END PROCESS;
+
+  
+  dut : ENTITY work.tse            -- uses stratix4 architecture tse_sgmii_lvds
+  PORT MAP (
+    -- Clocks and reset
+    mm_rst         => mm_rst,
+    mm_clk         => mm_clk,
+    eth_clk        => eth_clk,
+    tx_snk_clk     => st_clk,
+    rx_src_clk     => st_clk,
+    
+    -- Memory Mapped Slave
+    mm_sla_in      => mm_mosi,
+    mm_sla_out     => mm_miso,
+    
+    -- MAC transmit interface
+    -- . ST sink
+    tx_snk_in      => tx_sosi,
+    tx_snk_out     => tx_siso,
+    -- . MAC specific
+    tx_mac_in      => tx_mac_in,
+    tx_mac_out     => tx_mac_out,
+    
+    -- MAC receive interface
+    -- . ST Source
+    rx_src_in      => rx_siso,
+    rx_src_out     => rx_sosi,
+    -- . MAC specific
+    rx_mac_out     => rx_mac_out,
+
+    -- PHY interface
+    eth_txp        => eth_txp,
+    eth_rxp        => eth_rxp,
+
+    tse_led        => tse_led
+  );
+
+  -- Loopback
+  eth_rxp <= TRANSPORT eth_txp AFTER cable_delay;
+
+END tb;
diff --git a/libraries/technology/tse/tb_tech_tse_pkg.vhd b/libraries/technology/tse/tb_tech_tse_pkg.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..e7f2133cb884e45b18f405194a302a5aa5bfdcec
--- /dev/null
+++ b/libraries/technology/tse/tb_tech_tse_pkg.vhd
@@ -0,0 +1,439 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright (C) 2010
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- JIVE (Joint Institute for VLBI in Europe) <http://www.jive.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-------------------------------------------------------------------------------
+
+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.tb_common_mem_pkg.ALL;
+USE dp_lib.dp_stream_pkg.ALL;
+USE dp_lib.tb_dp_pkg.ALL;
+USE common_lib.eth_layers_pkg.ALL;
+USE WORK.tse_pkg.ALL;
+
+
+PACKAGE tb_tse_pkg IS
+
+  -- Test bench supported packet data types
+  CONSTANT c_tb_tse_data_type_symbols : NATURAL := 0;
+  CONSTANT c_tb_tse_data_type_counter : NATURAL := 1;
+  CONSTANT c_tb_tse_data_type_arp     : NATURAL := 2;
+  CONSTANT c_tb_tse_data_type_ping    : NATURAL := 3;  -- over IP/ICMP
+  CONSTANT c_tb_tse_data_type_udp     : NATURAL := 4;  -- over IP
+  
+  FUNCTION func_tb_tse_header_size(data_type : NATURAL) RETURN NATURAL;  -- raw ethernet: 4 header words, protocol ethernet: 11 header words
+  
+  -- Configure the TSE MAC
+  PROCEDURE proc_tse_setup(CONSTANT c_promis_en         : IN  BOOLEAN;
+                           CONSTANT c_tse_tx_fifo_depth : IN  NATURAL;
+                           CONSTANT c_tse_rx_fifo_depth : IN  NATURAL;
+                           CONSTANT c_tx_ready_latency  : IN  NATURAL;
+                           CONSTANT src_mac             : IN  STD_LOGIC_VECTOR(c_eth_mac_slv'RANGE);
+                           SIGNAL   psc_access          : OUT STD_LOGIC;
+                           SIGNAL   mm_clk              : IN  STD_LOGIC;
+                           SIGNAL   mm_miso             : IN  t_mem_miso;
+                           SIGNAL   mm_mosi             : OUT t_mem_mosi);
+  
+  PROCEDURE proc_tse_tx_packet(CONSTANT total_header    : IN  t_eth_total_header;
+                               CONSTANT data_len        : IN  NATURAL;  -- in symbols = octets = bytes
+                               CONSTANT c_data_type     : IN  NATURAL;  -- c_tb_tse_data_type_*
+                               CONSTANT c_ready_latency : IN  NATURAL;  -- 0, 1 are supported by proc_dp_stream_ready_latency()
+                               CONSTANT c_nof_not_valid : IN  NATURAL;  -- when > 0 then pull tx valid low for c_nof_not_valid beats during tx
+                               SIGNAL   ff_clk          : IN  STD_LOGIC;
+                               SIGNAL   ff_en           : IN  STD_LOGIC;  -- similar purpose as c_nof_not_valid, but not used so pass on signal '1'
+                               SIGNAL   ff_src_in       : IN  t_dp_siso;
+                               SIGNAL   ff_src_out      : OUT t_dp_sosi);
+
+  -- Receive and verify packet from the TSE MAC
+  PROCEDURE proc_tse_rx_packet(CONSTANT total_header : IN  t_eth_total_header;
+                               CONSTANT c_data_type  : IN  NATURAL;  -- c_tb_tse_data_type_*
+                               SIGNAL   ff_clk       : IN  STD_LOGIC;
+                               SIGNAL   ff_snk_in    : IN  t_dp_sosi;
+                               SIGNAL   ff_snk_out   : OUT t_dp_siso);
+
+END tb_tse_pkg;
+
+
+PACKAGE BODY tb_tse_pkg IS
+
+  -- LOCAL ITEMS ---------------------------------------------------------------
+  CONSTANT c_nof_eth_beats  : NATURAL := c_eth_nof_words;               -- nof words in eth part of the header
+  CONSTANT c_nof_hdr_beats  : NATURAL := c_eth_total_header_nof_words;  -- nof words in the total header
+
+  
+  -- GLOBAL ITEMS --------------------------------------------------------------
+  
+  FUNCTION func_tb_tse_header_size(data_type : NATURAL) RETURN NATURAL IS
+  BEGIN
+    CASE data_type IS
+      WHEN c_tb_tse_data_type_symbols => RETURN c_eth_nof_words;
+      WHEN c_tb_tse_data_type_counter => RETURN c_eth_nof_words;
+      WHEN OTHERS => NULL;
+    END CASE;
+    RETURN c_eth_total_header_nof_words;
+  END func_tb_tse_header_size;
+  
+  ------------------------------------------------------------------------------
+  -- MM bus
+  ------------------------------------------------------------------------------
+    
+  -- Use default word addressing for MAC registers according to table 4.8, 4.9
+  -- Use halfword addressing for PCS register to match table 4.17
+  FUNCTION func_map_pcs_addr(pcs_addr : NATURAL) RETURN NATURAL IS
+  BEGIN
+    RETURN pcs_addr * 2 + c_tse_byte_addr_pcs_offset;
+  END func_map_pcs_addr;
+
+
+  ------------------------------------------------------------------------------
+  -- Stream
+  ------------------------------------------------------------------------------  
+  
+  -- Configure the TSE MAC
+  -- . The src_mac[47:0] = 0x123456789ABC for MAC address 12-34-56-78-9A-BC
+  PROCEDURE proc_tse_setup(CONSTANT c_promis_en         : IN  BOOLEAN;
+                           CONSTANT c_tse_tx_fifo_depth : IN  NATURAL;
+                           CONSTANT c_tse_rx_fifo_depth : IN  NATURAL;
+                           CONSTANT c_tx_ready_latency  : IN  NATURAL;
+                           CONSTANT src_mac             : IN  STD_LOGIC_VECTOR(c_eth_mac_slv'RANGE);
+                           SIGNAL   psc_access          : OUT STD_LOGIC;
+                           SIGNAL   mm_clk              : IN  STD_LOGIC;
+                           SIGNAL   mm_miso             : IN  t_mem_miso;
+                           SIGNAL   mm_mosi             : OUT t_mem_mosi) IS
+    CONSTANT c_mac0       : INTEGER := TO_SINT(hton(src_mac(47 DOWNTO 16), 4));
+    CONSTANT c_mac1       : INTEGER := TO_SINT(hton(src_mac(15 DOWNTO  0), 2));
+  BEGIN
+    -- PSC control
+    psc_access <= '1';
+    proc_mem_mm_bus_rd(func_map_pcs_addr(16#22#),           mm_clk, mm_miso, mm_mosi);  -- REV --> 0x0901
+    proc_mem_mm_bus_wr(func_map_pcs_addr(16#28#), 16#0008#, mm_clk, mm_miso, mm_mosi);  -- IF_MODE <-- Force 1GbE, no autonegatiation
+    proc_mem_mm_bus_rd(func_map_pcs_addr(16#00#),           mm_clk, mm_miso, mm_mosi);  -- CONTROL --> 0x1140
+    proc_mem_mm_bus_rd(func_map_pcs_addr(16#02#),           mm_clk, mm_miso, mm_mosi);  -- STATUS --> 0x000D
+    proc_mem_mm_bus_wr(func_map_pcs_addr(16#00#), 16#0140#, mm_clk, mm_miso, mm_mosi);  -- CONTROL <-- Auto negotiate disable
+    psc_access <= '0';
+
+    -- MAC control
+    proc_mem_mm_bus_rd(16#000#, mm_clk, mm_miso, mm_mosi);  -- REV --> CUST_VERSION & 0x0901
+    IF c_promis_en=FALSE THEN
+      proc_mem_mm_bus_wr(16#008#, 16#0100004B#, mm_clk, mm_miso, mm_mosi);
+    ELSE
+      proc_mem_mm_bus_wr(16#008#, 16#0100005B#, mm_clk, mm_miso, mm_mosi);
+    END IF;
+      -- COMMAND_CONFIG <--
+      -- Only the bits relevant to UniBoard are explained here, others are 0
+      -- [    0] = TX_ENA             = 1, enable tx datapath
+      -- [    1] = RX_ENA             = 1, enable rx datapath
+      -- [    2] = XON_GEN            = 0
+      -- [    3] = ETH_SPEED          = 1, enable 1GbE operation
+      -- [    4] = PROMIS_EN          = 0, when 1 then receive all frames
+      -- [    5] = PAD_EN             = 0, when 1 enable receive padding removal (requires ethertype=payload length)
+      -- [    6] = CRC_FWD            = 1, enable receive CRC forward
+      -- [    7] = PAUSE_FWD          = 0
+      -- [    8] = PAUSE_IGNORE       = 0
+      -- [    9] = TX_ADDR_INS        = 0, when 1 then MAX overwrites tx SRC MAC with mac_0,1 or one of the supplemental mac
+      -- [   10] = HD_ENA             = 0
+      -- [   11] = EXCESS_COL         = 0
+      -- [   12] = LATE_COL           = 0
+      -- [   13] = SW_RESET           = 0, when 1 MAC disables tx and rx, clear statistics and flushes receive FIFO
+      -- [   14] = MHAS_SEL           = 0, select multicast address resolutions hash-code mode
+      -- [   15] = LOOP_ENA           = 0
+      -- [18-16] = TX_ADDR_SEL[2:0]   = 000, TX_ADDR_INS insert mac_0,1 or one of the supplemental mac
+      -- [   19] = MAGIC_EN           = 0
+      -- [   20] = SLEEP              = 0
+      -- [   21] = WAKEUP             = 0
+      -- [   22] = XOFF_GEN           = 0
+      -- [   23] = CNT_FRM_ENA        = 0
+      -- [   24] = NO_LGTH_CHECK      = 1, when 0 then check payload length of received frames (requires ethertype=payload length)
+      -- [   25] = ENA_10             = 0
+      -- [   26] = RX_ERR_DISC        = 0, when 1 then discard erroneous frames (requires store and forward mode, so rx_section_full=0)
+      --                                   when 0 then pass on with rx_err[0]=1
+      -- [   27] = DISABLE_RD_TIMEOUT = 0
+      -- [30-28] = RSVD               = 000
+      -- [   31] = CNT_RESET          = 0, when 1 clear statistics
+    proc_mem_mm_bus_wr(16#00C#,       c_mac0, mm_clk, mm_miso, mm_mosi);  -- MAC_0
+    proc_mem_mm_bus_wr(16#010#,       c_mac1, mm_clk, mm_miso, mm_mosi);  -- MAC_1 <-- SRC_MAC = 12-34-56-78-9A-BC
+    proc_mem_mm_bus_wr(16#05C#, 16#0000000C#, mm_clk, mm_miso, mm_mosi);  -- TX_IPG_LENGTH <-- interpacket gap = 12
+    --proc_mem_mm_bus_wr(16#014#, 16#000005EE#, mm_clk, mm_miso, mm_mosi);  -- FRM_LENGTH <-- receive max frame length = 1518
+    proc_mem_mm_bus_wr(16#014#, 16#0000233A#, mm_clk, mm_miso, mm_mosi);  -- FRM_LENGTH <-- receive max frame length = 9018
+
+    -- FIFO legenda:
+    -- . Tx section full  = There is enough data in the FIFO to start reading it, when 0 then store and forward.
+    -- . Rx section full  = There is enough data in the FIFO to start reading it, when 0 then store and forward.
+    -- . Tx section empty = There is not much empty space anymore in the FIFO, warn user via ff_tx_septy
+    -- . Rx section empty = There is not much empty space anymore in the FIFO, inform remote device via XOFF flow control
+    -- . Tx almost full   = Assert ff_tx_a_full and deassert ff_tx_rdy. Furthermore TX_ALMOST_FULL = c_tx_ready_latency+3,
+    --                      so choose 3 for zero tx ready latency
+    -- . Rx almost full   = Assert ff_rx_a_full and if the user is not ready ff_rx_rdy then:
+    --                      --> break off the reception with an error to avoid FIFO overflow
+    -- . Tx almost empty  = Assert ff_tx_a_empty and if the FIFO does not contain a eop yet then:
+    --                      --> break off the transmission with an error to avoid FIFO underflow
+    -- . Rx almost empty  = Assert ff_rx_a_empty
+    -- Typical FIFO values:
+    -- . TX_SECTION_FULL  = 16   > 8   = TX_ALMOST_EMPTY
+    -- . RX_SECTION_FULL  = 16   > 8   = RX_ALMOST_EMPTY
+    -- . TX_SECTION_EMPTY = D-16 < D-3 = Tx FIFO depth - TX_ALMOST_FULL
+    -- . RX_SECTION_EMPTY = D-16 < D-8 = Rx FIFO depth - RX_ALMOST_FULL
+    -- . c_tse_tx_fifo_depth = 1 M9K = 256*32b = 1k * 8b is sufficient when the Tx user respects ff_tx_rdy, to store a complete
+    --                         ETH packet would require 1518 byte, so 2 M9K = 2k * 8b
+    -- . c_tse_rx_fifo_depth = 1 M9K = 256*32b = 1k * 8b is sufficient when the Rx user ff_rx_rdy is sufficiently active
+    proc_mem_mm_bus_wr(16#01C#, c_tse_rx_fifo_depth-16, mm_clk, mm_miso, mm_mosi);  -- RX_SECTION_EMPTY <-- default FIFO depth - 16, >3
+    proc_mem_mm_bus_wr(16#020#,                     16, mm_clk, mm_miso, mm_mosi);  -- RX_SECTION_FULL  <-- default 16
+    proc_mem_mm_bus_wr(16#024#, c_tse_tx_fifo_depth-16, mm_clk, mm_miso, mm_mosi);  -- TX_SECTION_EMPTY <-- default FIFO depth - 16, >3
+    proc_mem_mm_bus_wr(16#028#,                     16, mm_clk, mm_miso, mm_mosi);  -- TX_SECTION_FULL  <-- default 16, >~ 8 otherwise no tx
+    proc_mem_mm_bus_wr(16#02C#,                      8, mm_clk, mm_miso, mm_mosi);  -- RX_ALMOST_EMPTY  <-- default 8
+    proc_mem_mm_bus_wr(16#030#,                      8, mm_clk, mm_miso, mm_mosi);  -- RX_ALMOST_FULL   <-- default 8
+    proc_mem_mm_bus_wr(16#034#,                      8, mm_clk, mm_miso, mm_mosi);  -- TX_ALMOST_EMPTY  <-- default 8
+    proc_mem_mm_bus_wr(16#038#,   c_tx_ready_latency+3, mm_clk, mm_miso, mm_mosi);  -- TX_ALMOST_FULL   <-- default 3
+
+    proc_mem_mm_bus_rd(16#0E8#, mm_clk, mm_miso, mm_mosi);  -- TX_CMD_STAT --> 0x00040000 : [18]=1 TX_SHIFT16, [17]=0 OMIT_CRC
+    proc_mem_mm_bus_rd(16#0EC#, mm_clk, mm_miso, mm_mosi);  -- RX_CMD_STAT --> 0x02000000 : [25]=1 RX_SHIFT16
+
+    WAIT UNTIL rising_edge(mm_clk);
+  END proc_tse_setup;
+
+  
+  -- Transmit user packet
+  -- . Use word aligned payload data, so with half word inserted before the 14 byte header
+  -- . Packets can be send immediately after eachother so new sop directly after last eop
+  -- . The word rate is controlled by respecting ready from the MAC
+  PROCEDURE proc_tse_tx_packet(CONSTANT total_header    : IN  t_eth_total_header;
+                               CONSTANT data_len        : IN  NATURAL;  -- in symbols = octets = bytes
+                               CONSTANT c_data_type     : IN  NATURAL;  -- c_tb_tse_data_type_*
+                               CONSTANT c_ready_latency : IN  NATURAL;  -- 0, 1 are supported by proc_dp_stream_ready_latency()
+                               CONSTANT c_nof_not_valid : IN  NATURAL;  -- when > 0 then pull tx valid low for c_nof_not_valid beats during tx
+                               SIGNAL   ff_clk          : IN  STD_LOGIC;
+                               SIGNAL   ff_en           : IN  STD_LOGIC;  -- similar purpose as c_nof_not_valid, but not used so pass on signal '1'
+                               SIGNAL   ff_src_in       : IN  t_dp_siso;
+                               SIGNAL   ff_src_out      : OUT t_dp_sosi) IS
+    CONSTANT c_eth_header     : t_eth_header := total_header.eth;
+    CONSTANT c_arp_packet     : t_eth_total_header_arr := func_eth_arp_total_header( total_header.eth, total_header.arp);
+    CONSTANT c_icmp_header    : t_eth_total_header_arr := func_eth_icmp_total_header(total_header.eth, total_header.ip, total_header.icmp);
+    CONSTANT c_udp_header     : t_eth_total_header_arr := func_eth_udp_total_header( total_header.eth, total_header.ip, total_header.udp);
+    
+    CONSTANT c_mod            : NATURAL := data_len MOD c_tse_symbols_per_beat;
+    CONSTANT c_nof_data_beats : NATURAL := data_len   / c_tse_symbols_per_beat + sel_a_b(c_mod, 1, 0);
+    CONSTANT c_empty          : NATURAL := sel_a_b(c_mod, c_tse_symbols_per_beat - c_mod, 0);
+    VARIABLE v_sym            : UNSIGNED(c_tse_symbol_w-1 DOWNTO 0) := (OTHERS=>'0');
+    VARIABLE v_num            : UNSIGNED(c_tse_data_w-1 DOWNTO 0) := (OTHERS=>'0');
+  BEGIN
+    ff_src_out.empty <= TO_DP_EMPTY(0);
+    ----------------------------------------------------------------------------
+    -- ETH Header
+    -- . sop
+    ff_src_out.data <= RESIZE_DP_DATA(c_udp_header(0));  -- all arp, icmp and udp contain the same eth header, so it is ok to use c_udp_header
+    proc_dp_stream_ready_latency(c_ready_latency, ff_clk, ff_src_in.ready, ff_en, '0', '1', '1', '0', ff_src_out.sync, ff_src_out.valid, ff_src_out.sop, ff_src_out.eop);
+    ff_src_out.data <= RESIZE_DP_DATA(c_udp_header(1));  -- prepare data before loop, so proc_dp_stream_ready_latency can be called at start of the loops
+    FOR I IN 2 TO c_nof_eth_beats-1 LOOP
+      proc_dp_stream_ready_latency(c_ready_latency, ff_clk, ff_src_in.ready, ff_en, '0', '1', '0', '0', ff_src_out.sync, ff_src_out.valid, ff_src_out.sop, ff_src_out.eop);
+      ff_src_out.data <= RESIZE_DP_DATA(c_udp_header(I));
+    END LOOP;
+    ----------------------------------------------------------------------------
+    -- ETH higher layer headers
+    IF c_data_type=c_tb_tse_data_type_arp THEN
+      FOR I IN c_nof_eth_beats TO c_nof_hdr_beats-2 LOOP
+        proc_dp_stream_ready_latency(c_ready_latency, ff_clk, ff_src_in.ready, ff_en, '0', '1', '0', '0', ff_src_out.sync, ff_src_out.valid, ff_src_out.sop, ff_src_out.eop);
+        ff_src_out.data <= RESIZE_DP_DATA(c_arp_packet(I));
+      END LOOP;
+      proc_dp_stream_ready_latency(c_ready_latency, ff_clk, ff_src_in.ready, ff_en, '0', '1', '0', '0', ff_src_out.sync, ff_src_out.valid, ff_src_out.sop, ff_src_out.eop);
+      -- . eop
+      ff_src_out.data <= RESIZE_DP_DATA(c_arp_packet(c_nof_hdr_beats-1));
+      proc_dp_stream_ready_latency(c_ready_latency, ff_clk, ff_src_in.ready, ff_en, '0', '1', '0', '1', ff_src_out.sync, ff_src_out.valid, ff_src_out.sop, ff_src_out.eop);
+    ELSIF c_data_type=c_tb_tse_data_type_ping OR c_data_type=c_tb_tse_data_type_udp THEN
+      FOR I IN c_nof_eth_beats TO c_nof_hdr_beats-1 LOOP
+        proc_dp_stream_ready_latency(c_ready_latency, ff_clk, ff_src_in.ready, ff_en, '0', '1', '0', '0', ff_src_out.sync, ff_src_out.valid, ff_src_out.sop, ff_src_out.eop);
+        CASE c_data_type IS
+          WHEN c_tb_tse_data_type_ping => ff_src_out.data <= RESIZE_DP_DATA(c_icmp_header(I));
+          WHEN c_tb_tse_data_type_udp =>  ff_src_out.data <= RESIZE_DP_DATA(c_udp_header(I));
+          WHEN OTHERS => NULL;
+        END CASE;
+      END LOOP;
+    END IF;
+    ----------------------------------------------------------------------------
+    -- Data
+    IF c_data_type/=c_tb_tse_data_type_arp THEN
+      FOR I IN 0 TO c_nof_data_beats-1 LOOP
+        proc_dp_stream_ready_latency(c_ready_latency, ff_clk, ff_src_in.ready, ff_en, '0', '1', '0', '0', ff_src_out.sync, ff_src_out.valid, ff_src_out.sop, ff_src_out.eop);
+        CASE c_data_type IS
+          WHEN c_tb_tse_data_type_counter =>
+            -- data : X"00000001", X"00000002", X"00000003", etc
+            v_num := v_num + 1;
+            ff_src_out.data <= RESIZE_DP_DATA(STD_LOGIC_VECTOR(v_num));
+          WHEN OTHERS =>
+            -- data : X"01020304", X"05060708", X"090A0B0C", etc
+            FOR J IN c_tse_symbols_per_beat-1 DOWNTO 0 LOOP
+              v_sym := v_sym + 1;
+              ff_src_out.data((J+1)*c_tse_symbol_w-1 DOWNTO J*c_tse_symbol_w) <= STD_LOGIC_VECTOR(v_sym);
+            END LOOP;
+        END CASE;
+        -- tb : pull valid low for some time during the middle of the payload
+        IF c_nof_not_valid > 0 AND I=c_nof_data_beats/2 THEN
+          ff_src_out.valid <= '0';
+          FOR I IN 0 TO c_nof_not_valid LOOP WAIT UNTIL rising_edge(ff_clk); END LOOP;
+          ff_src_out.valid <= '1';
+        END IF;
+      END LOOP;
+      --------------------------------------------------------------------------
+      -- Last data
+      IF c_empty > 0 THEN
+        -- Overwrite empty data
+        ff_src_out.empty <= TO_DP_EMPTY(c_empty);
+        FOR J IN c_empty-1 DOWNTO 0 LOOP
+          ff_src_out.data((J+1)*c_tse_symbol_w-1 DOWNTO J*c_tse_symbol_w) <= (OTHERS=>'0');
+        END LOOP;
+      END IF;
+      -- . eop
+      proc_dp_stream_ready_latency(c_ready_latency, ff_clk, ff_src_in.ready, ff_en, '0', '1', '0', '1', ff_src_out.sync, ff_src_out.valid, ff_src_out.sop, ff_src_out.eop);
+    END IF;
+    ----------------------------------------------------------------------------
+    -- Initialize for next tx packet
+    ff_src_out.data  <= TO_DP_DATA(0);
+    ff_src_out.valid <= '0';
+    ff_src_out.eop   <= '0';
+    ff_src_out.empty <= TO_DP_EMPTY(0);
+  END proc_tse_tx_packet;
+
+  
+  -- Receive packet
+  -- . Use word aligned payload data, so with half word inserted before the 14 byte header
+  -- . Packets can be always be received, assume the user application is always ready
+  -- . The CRC32 is also passed on to the user at eop.
+  -- . Note that when empty/=0 then the CRC32 is not word aligned, so therefore use prev_data to be able
+  --   to handle part of last data word in case empty/=0 at eop
+  PROCEDURE proc_tse_rx_packet(CONSTANT total_header : IN  t_eth_total_header;
+                               CONSTANT c_data_type  : IN  NATURAL;  -- c_tb_tse_data_type_*
+                               SIGNAL   ff_clk       : IN  STD_LOGIC;
+                               SIGNAL   ff_snk_in    : IN  t_dp_sosi;
+                               SIGNAL   ff_snk_out   : OUT t_dp_siso) IS
+    CONSTANT c_eth_header     : t_eth_header := total_header.eth;
+    CONSTANT c_arp_packet     : t_eth_total_header_arr := func_eth_arp_total_header( total_header.eth, total_header.arp);
+    CONSTANT c_icmp_header    : t_eth_total_header_arr := func_eth_icmp_total_header(total_header.eth, total_header.ip, total_header.icmp);
+    CONSTANT c_udp_header     : t_eth_total_header_arr := func_eth_udp_total_header( total_header.eth, total_header.ip, total_header.udp);
+    VARIABLE v_sym            : UNSIGNED(c_tse_symbol_w-1 DOWNTO 0) := (OTHERS=>'0');
+    VARIABLE v_num            : UNSIGNED(c_tse_data_w-1 DOWNTO 0) := (OTHERS=>'0');
+    VARIABLE v_empty          : NATURAL;
+    VARIABLE v_first          : BOOLEAN := TRUE;
+    VARIABLE v_data           : STD_LOGIC_VECTOR(c_tse_data_w-1 DOWNTO 0);
+    VARIABLE v_prev_data      : STD_LOGIC_VECTOR(c_tse_data_w-1 DOWNTO 0);
+  BEGIN
+    -- Keep ff_rx_snk_out.ready='1' and ff_rx_snk_out.xon='1' all the time
+    ff_snk_out <= c_dp_siso_rdy;
+    ----------------------------------------------------------------------------
+    -- Verify ETH Header
+    -- . wait for sop
+    proc_dp_stream_valid_sop(ff_clk, ff_snk_in.valid, ff_snk_in.sop);
+    ASSERT ff_snk_in.data(31 DOWNTO 16) = X"0000"                            REPORT "RX: Wrong ETH alignment half word not zero" SEVERITY ERROR;
+    ASSERT ff_snk_in.data(15 DOWNTO  0) = c_eth_header.dst_mac(47 DOWNTO 32) REPORT "RX: Wrong ETH dst_mac_addr(47 DOWNTO 32)" SEVERITY ERROR;
+    proc_dp_stream_valid(ff_clk, ff_snk_in.valid);
+    ASSERT ff_snk_in.data(31 DOWNTO  0) = c_eth_header.dst_mac(31 DOWNTO 0)  REPORT "RX: Wrong ETH dst_mac_addr(31 DOWNTO 0)" SEVERITY ERROR;
+    proc_dp_stream_valid(ff_clk, ff_snk_in.valid);
+    ASSERT ff_snk_in.data(31 DOWNTO  0) = c_eth_header.src_mac(47 DOWNTO 16) REPORT "RX: Wrong ETH src_mac_addr(47 DOWNTO 16)" SEVERITY ERROR;
+    proc_dp_stream_valid(ff_clk, ff_snk_in.valid);
+    ASSERT ff_snk_in.data(31 DOWNTO 16) = c_eth_header.src_mac(15 DOWNTO 0)  REPORT "RX: Wrong ETH src_mac_addr(15 DOWNTO 0)" SEVERITY ERROR;
+    ASSERT ff_snk_in.data(15 DOWNTO  0) = c_eth_header.eth_type              REPORT "RX: Wrong ETH ethertype" SEVERITY ERROR;
+    ----------------------------------------------------------------------------
+    -- Verify ETH higher layer headers
+    IF c_data_type=c_tb_tse_data_type_arp THEN
+      FOR I IN c_nof_eth_beats TO c_nof_hdr_beats-1 LOOP
+        proc_dp_stream_valid(ff_clk, ff_snk_in.valid);
+        ASSERT ff_snk_in.data(31 DOWNTO 0) = c_arp_packet(I) REPORT "RX: Wrong ARP response word" SEVERITY ERROR;
+      END LOOP;
+      -- . continue to eop
+      WHILE ff_snk_in.eop /= '1' LOOP
+        proc_dp_stream_valid(ff_clk, ff_snk_in.valid);
+      END LOOP;
+    ELSIF c_data_type=c_tb_tse_data_type_ping OR c_data_type=c_tb_tse_data_type_udp THEN
+      FOR I IN c_nof_eth_beats TO c_nof_hdr_beats-1 LOOP
+        proc_dp_stream_valid(ff_clk, ff_snk_in.valid);
+        IF I/=c_ip_header_checksum_wi THEN  -- do not verify tx ip header checksum
+          CASE c_data_type IS
+            WHEN c_tb_tse_data_type_ping => ASSERT ff_snk_in.data(31 DOWNTO 0) = c_icmp_header(I) REPORT "RX: Wrong IP/ICMP = PING response word" SEVERITY ERROR;
+            WHEN c_tb_tse_data_type_udp =>  ASSERT ff_snk_in.data(31 DOWNTO 0) = c_udp_header(I)  REPORT "RX: Wrong IP/UDP response word" SEVERITY ERROR;
+            WHEN OTHERS => NULL;
+          END CASE;
+        END IF;
+      END LOOP;
+    END IF;
+    ----------------------------------------------------------------------------
+    -- Verify DATA
+    IF c_data_type/=c_tb_tse_data_type_arp THEN
+      -- . continue to eop
+      v_first := TRUE;
+      proc_dp_stream_valid(ff_clk, ff_snk_in.valid);
+      WHILE ff_snk_in.eop /= '1' LOOP
+        v_prev_data := v_data;
+        v_data      := ff_snk_in.data(c_tse_data_w-1 DOWNTO 0);
+        IF v_first = FALSE THEN
+          CASE c_data_type IS
+            WHEN c_tb_tse_data_type_counter =>
+              -- data : X"00000001", X"00000002", X"00000003", etc
+              v_num := v_num + 1;
+              IF UNSIGNED(v_prev_data)/=0 THEN   -- do not verify zero padding
+                ASSERT UNSIGNED(v_prev_data) = v_num REPORT "RX: Wrong data word" SEVERITY ERROR;
+              END IF;
+            WHEN OTHERS =>
+              -- data : X"01020304", X"05060708", X"090A0B0C", etc
+              FOR J IN c_tse_symbols_per_beat-1 DOWNTO 0 LOOP
+                v_sym := v_sym + 1;
+                IF UNSIGNED(v_prev_data((J+1)*c_tse_symbol_w-1 DOWNTO J*c_tse_symbol_w))/=0 THEN   -- do not verify zero padding
+                  ASSERT UNSIGNED(v_prev_data((J+1)*c_tse_symbol_w-1 DOWNTO J*c_tse_symbol_w)) = v_sym REPORT "RX: Wrong data symbol" SEVERITY ERROR;
+                END IF;
+              END LOOP;
+          END CASE;
+        END IF;
+        v_first := FALSE;
+        proc_dp_stream_valid(ff_clk, ff_snk_in.valid);
+      END LOOP;
+      --------------------------------------------------------------------------
+      -- Verify last DATA and CRC32 if empty/=0 else the last word is only the CRC32
+      v_prev_data := v_data;
+      v_data      := ff_snk_in.data(c_tse_data_w-1 DOWNTO 0);
+      v_empty     := TO_INTEGER(UNSIGNED(ff_snk_in.empty(c_tse_empty_w-1 DOWNTO 0)));
+      IF v_empty > 0 THEN
+        FOR J IN v_empty-1 DOWNTO 0 LOOP
+          v_prev_data((J+1)*c_tse_symbol_w-1 DOWNTO J*c_tse_symbol_w) := (OTHERS=>'0');
+        END LOOP;
+        CASE c_data_type IS
+          WHEN c_tb_tse_data_type_counter =>
+            -- data : X"00000001", X"00000002", X"00000003", etc
+            v_num := v_num + 1;
+            FOR J IN v_empty-1 DOWNTO 0 LOOP
+              v_num((J+1)*c_tse_symbol_w-1 DOWNTO J*c_tse_symbol_w) := (OTHERS=>'0');  -- force CRC32 symbols in last data word to 0
+            END LOOP;
+            IF UNSIGNED(v_prev_data)/=0 THEN   -- do not verify zero padding
+              ASSERT UNSIGNED(v_prev_data) = v_num REPORT "RX: Wrong empty data word" SEVERITY ERROR;
+            END IF;
+          WHEN OTHERS =>
+            -- data : X"01020304", X"05060708", X"090A0B0C", etc
+            FOR J IN c_tse_symbols_per_beat-1 DOWNTO v_empty LOOP  -- ignore CRC32 symbols in last data word
+              v_sym := v_sym + 1;
+              IF UNSIGNED(v_prev_data((J+1)*c_tse_symbol_w-1 DOWNTO J*c_tse_symbol_w))/=0 THEN   -- do not verify zero padding
+                ASSERT UNSIGNED(v_prev_data((J+1)*c_tse_symbol_w-1 DOWNTO J*c_tse_symbol_w)) = v_sym REPORT "RX: Wrong empty data symbol" SEVERITY ERROR;
+              END IF;
+            END LOOP;
+        END CASE;
+      END IF;
+    END IF;
+    -- No verify on CRC32 word
+  END proc_tse_rx_packet;
+
+END tb_tse_pkg;