diff --git a/libraries/io/eth/hdllib.cfg b/libraries/io/eth/hdllib.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..445b27d5158cf1b8159b91d1e4ef6ddf36af65ff
--- /dev/null
+++ b/libraries/io/eth/hdllib.cfg
@@ -0,0 +1,35 @@
+hdl_lib_name = eth
+hdl_library_clause_name = eth_lib
+hdl_lib_uses = dp common tech_tse
+
+build_sim_dir = $HDL_BUILD_DIR
+build_synth_dir = 
+
+synth_files =
+    src/vhdl/eth_pkg.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_checksum.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_hdr_store.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_hdr_status.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_hdr_ctrl.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_hdr.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_crc_ctrl.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_crc_word.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_mm_registers.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_mm_reg_frame.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_udp_channel.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_buffer.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_control.vhd
+    $UNB/Firmware/modules/tse/src/vhdl/eth_ihl_to_20.vhd
+    src/vhdl/eth.vhd
+    
+    src/vhdl/avs_eth.vhd
+    src/vhdl/avs_eth_coe.vhd
+    
+test_bench_files = 
+    $UNB/Firmware/modules/tse/tb/vhdl/tb_eth_checksum.vhd
+    $UNB/Firmware/modules/tse/tb/vhdl/tb_eth_crc_ctrl.vhd
+    $UNB/Firmware/modules/tse/tb/vhdl/tb_eth_hdr.vhd
+    tb/vhdl/tb_eth.vhd
+    tb/vhdl/tb_tb_eth.vhd
+    tb/vhdl/tb_eth_udp_offload.vhd
+    $UNB/Firmware/modules/tse/tb/vhdl/tb_eth_ihl_to_20.vhd
diff --git a/libraries/io/eth/src/vhdl/avs_eth.vhd b/libraries/io/eth/src/vhdl/avs_eth.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..98c8d722e0f90c9741ee7ce6815c6a0419ddd52e
--- /dev/null
+++ b/libraries/io/eth/src/vhdl/avs_eth.vhd
@@ -0,0 +1,190 @@
+-------------------------------------------------------------------------------
+--
+-- 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, tech_tse_lib;
+USE IEEE.std_logic_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE dp_lib.dp_stream_pkg.ALL;
+USE tech_tse_lib.tech_tse_pkg.ALL;
+USE work.eth_pkg.ALL;
+
+
+ENTITY avs_eth IS
+  PORT (
+    ---------------------------------------------------------------------------
+    -- Clock interface
+    ---------------------------------------------------------------------------
+    csi_mm_reset            : IN STD_LOGIC;
+    csi_mm_clk              : IN STD_LOGIC;
+    
+    ---------------------------------------------------------------------------
+    -- Memory Mapped Slave interface
+    ---------------------------------------------------------------------------
+    -- TSE MAC
+    -- . MOSI
+    mms_tse_address         : IN  STD_LOGIC_VECTOR(c_tse_byte_addr_w-1 DOWNTO 0);
+    mms_tse_write           : IN  STD_LOGIC;
+    mms_tse_read            : IN  STD_LOGIC;
+    mms_tse_writedata       : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    -- . MISO
+    mms_tse_readdata        : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    mms_tse_waitrequest     : OUT STD_LOGIC;
+    
+    -- ETH registers
+    -- . MOSI
+    mms_reg_address         : IN  STD_LOGIC_VECTOR(c_eth_reg_addr_w-1 DOWNTO 0);
+    mms_reg_write           : IN  STD_LOGIC;
+    mms_reg_read            : IN  STD_LOGIC;
+    mms_reg_writedata       : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    -- . MISO
+    mms_reg_readdata        : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    
+    -- ETH packet RAM
+    -- . MOSI
+    mms_ram_address         : IN  STD_LOGIC_VECTOR(c_eth_ram_addr_w-1 DOWNTO 0);
+    mms_ram_write           : IN  STD_LOGIC;
+    mms_ram_read            : IN  STD_LOGIC;
+    mms_ram_writedata       : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    -- . MISO
+    mms_ram_readdata        : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    
+    ---------------------------------------------------------------------------
+    -- Avalon Interrupt Sender interface: ins_*
+    ---------------------------------------------------------------------------
+    ins_interrupt_irq       : OUT STD_LOGIC;
+    
+    ---------------------------------------------------------------------------
+    -- Avalon Conduit interfaces: coe_*_export
+    ---------------------------------------------------------------------------
+    -- PHY interface
+    coe_eth_clk_export      : IN STD_LOGIC;
+    coe_eth_txp_export      : OUT STD_LOGIC;
+    coe_eth_rxp_export      : IN  STD_LOGIC;
+    
+    -- LED
+    coe_led_an_export       : OUT STD_LOGIC;
+    coe_led_link_export     : OUT STD_LOGIC;
+    coe_led_disp_err_export : OUT STD_LOGIC;
+    coe_led_char_err_export : OUT STD_LOGIC;
+    coe_led_crs_export      : OUT STD_LOGIC;
+    coe_led_col_export      : OUT STD_LOGIC
+  );
+END avs_eth;
+
+
+ARCHITECTURE wrap OF avs_eth IS
+
+  -- Wrap all records to STD_LOGIC
+  
+  -- ST UDP interface
+  SIGNAL udp_tx_snk_in_arr : t_dp_sosi_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  SIGNAL udp_rx_src_in_arr : t_dp_siso_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  
+  -- MM interface
+  SIGNAL tse_sla_in        : t_mem_mosi;  -- ETH TSE MAC registers
+  SIGNAL tse_sla_out       : t_mem_miso;
+  SIGNAL reg_sla_in        : t_mem_mosi;  -- ETH control and status registers
+  SIGNAL reg_sla_out       : t_mem_miso;
+  SIGNAL ram_sla_in        : t_mem_mosi;  -- ETH rx frame and tx frame memory
+  SIGNAL ram_sla_out       : t_mem_miso;
+  
+  -- LED interface
+  SIGNAL tse_led           : t_tse_led;
+    
+BEGIN
+
+  -- Run internal ST at MM clock
+  -- Disable UDP off-load interface
+  udp_tx_snk_in_arr <= (OTHERS=>c_dp_sosi_rst);  -- default not valid if not used
+  udp_rx_src_in_arr <= (OTHERS=>c_dp_siso_rdy);  -- default ready if not used
+
+  -- TSE MAC
+  -- . MOSI
+  tse_sla_in.address  <= RESIZE_MEM_ADDRESS(mms_tse_address);
+  tse_sla_in.wr       <= mms_tse_write;
+  tse_sla_in.rd       <= mms_tse_read;
+  tse_sla_in.wrdata   <= RESIZE_MEM_DATA(mms_tse_writedata);
+  -- . MISO
+  mms_tse_readdata    <= tse_sla_out.rddata(c_word_w-1 DOWNTO 0);
+  mms_tse_waitrequest <= tse_sla_out.waitrequest;
+  
+  -- ETH registers
+  -- . MOSI
+  reg_sla_in.address  <= RESIZE_MEM_ADDRESS(mms_reg_address);
+  reg_sla_in.wr       <= mms_reg_write;
+  reg_sla_in.rd       <= mms_reg_read;
+  reg_sla_in.wrdata   <= RESIZE_MEM_DATA(mms_reg_writedata);
+  -- . MISO
+  mms_reg_readdata    <= reg_sla_out.rddata(c_word_w-1 DOWNTO 0);
+  
+  -- ETH packet RAM
+  -- . MOSI
+  ram_sla_in.address  <= RESIZE_MEM_ADDRESS(mms_ram_address);
+  ram_sla_in.wr       <= mms_ram_write;
+  ram_sla_in.rd       <= mms_ram_read;
+  ram_sla_in.wrdata   <= RESIZE_MEM_DATA(mms_ram_writedata);
+  -- . MISO
+  mms_ram_readdata    <= ram_sla_out.rddata(c_word_w-1 DOWNTO 0);
+  
+  -- LEDs
+  coe_led_an_export       <= tse_led.an;
+  coe_led_link_export     <= tse_led.link;
+  coe_led_disp_err_export <= tse_led.disp_err;
+  coe_led_char_err_export <= tse_led.char_err;
+  coe_led_crs_export      <= tse_led.crs;
+  coe_led_col_export      <= tse_led.col;
+  
+  u_eth : ENTITY work.eth
+  PORT MAP (
+    -- Clocks and reset
+    mm_rst            => csi_mm_reset,        -- reset synchronous with mm_clk
+    mm_clk            => csi_mm_clk,          -- memory-mapped bus clock
+    eth_clk           => coe_eth_clk_export,  -- ethernet phy reference clock
+    st_rst            => csi_mm_reset,        -- reset synchronous with st_clk
+    st_clk            => csi_mm_clk,          -- packet stream clock
+    
+    -- UDP transmit interface
+    udp_tx_snk_in_arr  => udp_tx_snk_in_arr,
+    udp_tx_snk_out_arr => OPEN,
+    -- UDP receive interface
+    udp_rx_src_in_arr  => udp_rx_src_in_arr,
+    udp_rx_src_out_arr => OPEN,
+
+    -- Memory Mapped Slaves
+    tse_sla_in        => tse_sla_in,         -- ETH TSE MAC registers
+    tse_sla_out       => tse_sla_out,
+    reg_sla_in        => reg_sla_in,         -- ETH control and status registers
+    reg_sla_out       => reg_sla_out,
+    reg_sla_interrupt => ins_interrupt_irq,  -- ETH interrupt
+    ram_sla_in        => ram_sla_in,         -- ETH rx frame and tx frame memory
+    ram_sla_out       => ram_sla_out,
+
+    -- PHY interface
+    eth_txp           => coe_eth_txp_export,
+    eth_rxp           => coe_eth_rxp_export,
+
+    -- LED interface
+    tse_led           => tse_led
+  );
+  
+END wrap;
diff --git a/libraries/io/eth/src/vhdl/avs_eth_coe.vhd b/libraries/io/eth/src/vhdl/avs_eth_coe.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..6a52b0d78c0cc76bbd202c6f2332fc620660ff8f
--- /dev/null
+++ b/libraries/io/eth/src/vhdl/avs_eth_coe.vhd
@@ -0,0 +1,139 @@
+-------------------------------------------------------------------------------
+--
+-- 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/>.
+--
+-------------------------------------------------------------------------------
+
+-- Purpose: AVS wrapper to make a MM slave ports for ETH available as conduit
+-- Description:
+-- Remark:
+-- . The avs_eth_coe_hw.tcl determines the read latency per port
+
+LIBRARY IEEE, common_lib, tech_tse_lib;
+USE IEEE.std_logic_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE tech_tse_lib.tech_tse_pkg.ALL;
+USE work.eth_pkg.ALL;
+
+
+ENTITY avs_eth_coe IS
+  PORT (
+    ----------------------------------------------------------------------------
+    -- MM side
+    
+    -- Clock interface
+    csi_mm_reset               : IN  STD_LOGIC;
+    csi_mm_clk                 : IN  STD_LOGIC;
+    
+    -- TSE MAC
+    mms_tse_address            : IN  STD_LOGIC_VECTOR(c_tse_byte_addr_w-1 DOWNTO 0);
+    mms_tse_write              : IN  STD_LOGIC;
+    mms_tse_read               : IN  STD_LOGIC;
+    mms_tse_writedata          : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    mms_tse_readdata           : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);  -- read latency is 0
+    mms_tse_waitrequest        : OUT STD_LOGIC;                              -- necessary because read latency is 0
+    
+    -- ETH registers
+    mms_reg_address            : IN  STD_LOGIC_VECTOR(c_eth_reg_addr_w-1 DOWNTO 0);
+    mms_reg_write              : IN  STD_LOGIC;
+    mms_reg_read               : IN  STD_LOGIC;
+    mms_reg_writedata          : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    mms_reg_readdata           : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);  -- read latency is 1
+    
+    -- ETH packet RAM
+    mms_ram_address            : IN  STD_LOGIC_VECTOR(c_eth_ram_addr_w-1 DOWNTO 0);
+    mms_ram_write              : IN  STD_LOGIC;
+    mms_ram_read               : IN  STD_LOGIC;
+    mms_ram_writedata          : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    mms_ram_readdata           : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);  -- read latency is 2
+    
+    -- Interrupt Sender interface
+    ins_interrupt_irq          : OUT STD_LOGIC;  -- relates to the ETH registers port
+    
+    ----------------------------------------------------------------------------
+    -- User side
+    
+    -- Clock interface
+    coe_reset_export           : OUT STD_LOGIC;
+    coe_clk_export             : OUT STD_LOGIC;
+    
+    -- TSE MAC
+    coe_tse_address_export     : OUT STD_LOGIC_VECTOR(c_tse_byte_addr_w-1 DOWNTO 0);
+    coe_tse_write_export       : OUT STD_LOGIC;
+    coe_tse_read_export        : OUT STD_LOGIC;
+    coe_tse_writedata_export   : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    coe_tse_readdata_export    : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    coe_tse_waitrequest_export : IN  STD_LOGIC;
+    
+    -- ETH registers
+    coe_reg_address_export     : OUT STD_LOGIC_VECTOR(c_eth_reg_addr_w-1 DOWNTO 0);
+    coe_reg_write_export       : OUT STD_LOGIC;
+    coe_reg_read_export        : OUT STD_LOGIC;
+    coe_reg_writedata_export   : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    coe_reg_readdata_export    : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    
+    -- ETH packet RAM
+    coe_ram_address_export     : OUT STD_LOGIC_VECTOR(c_eth_ram_addr_w-1 DOWNTO 0);
+    coe_ram_write_export       : OUT STD_LOGIC;
+    coe_ram_read_export        : OUT STD_LOGIC;
+    coe_ram_writedata_export   : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    coe_ram_readdata_export    : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    
+    -- Interrupt Sender interface
+    coe_irq_export             : IN  STD_LOGIC
+  );
+END avs_eth_coe;
+
+
+ARCHITECTURE wrap OF avs_eth_coe IS
+BEGIN
+
+  ------------------------------------------------------------------------------
+  -- Wires
+  
+  -- Clock interface
+  coe_reset_export         <= csi_mm_reset;
+  coe_clk_export           <= csi_mm_clk;
+  
+  -- TSE MAC
+  coe_tse_address_export   <= mms_tse_address;
+  coe_tse_write_export     <= mms_tse_write;
+  coe_tse_read_export      <= mms_tse_read;
+  coe_tse_writedata_export <= mms_tse_writedata;      
+  mms_tse_readdata         <= coe_tse_readdata_export;
+  mms_tse_waitrequest      <= coe_tse_waitrequest_export;
+                                                            
+  -- ETH registers
+  coe_reg_address_export   <= mms_reg_address;
+  coe_reg_write_export     <= mms_reg_write;
+  coe_reg_read_export      <= mms_reg_read;
+  coe_reg_writedata_export <= mms_reg_writedata;
+  mms_reg_readdata         <= coe_reg_readdata_export;
+                                                            
+  -- ETH packet RAM
+  coe_ram_address_export   <= mms_ram_address;
+  coe_ram_write_export     <= mms_ram_write;
+  coe_ram_read_export      <= mms_ram_read;
+  coe_ram_writedata_export <= mms_ram_writedata;
+  mms_ram_readdata         <= coe_ram_readdata_export;
+                                                            
+  -- Interrupt Sender interface
+  ins_interrupt_irq        <= coe_irq_export;
+
+END wrap;
diff --git a/libraries/io/eth/src/vhdl/eth.vhd b/libraries/io/eth/src/vhdl/eth.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..a43247081470ced371d75046778a8f5aca41d503
--- /dev/null
+++ b/libraries/io/eth/src/vhdl/eth.vhd
@@ -0,0 +1,613 @@
+-------------------------------------------------------------------------------
+--
+-- 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, tech_tse_lib;
+USE IEEE.std_logic_1164.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 tech_tse_lib.tech_tse_pkg.ALL;
+USE work.eth_pkg.ALL;
+
+-- Purpose:
+--   Provide Ethernet control access to a node and some UDP ports for streaming
+--   data.
+-- Description:
+--   Connect the 1GbE TSE to the microprocessor and to streaming UDP ports. The
+--   packets for the streaming channels are directed based on the UDP port
+--   number and all other packets are transfered to the default control channel.
+
+ENTITY eth IS
+  GENERIC (
+    g_cross_clock_domain : BOOLEAN := TRUE;  -- use FALSE when mm_clk and st_clk are the same, else use TRUE to cross the clock domain
+    g_ETH_PHY            : STRING  := "LVDS"; -- "LVDS" (default): uses LVDS IOs for ctrl_unb_common, "XCVR": uses tranceiver PHY
+    g_ihl20              : BOOLEAN := FALSE
+  );
+  PORT (
+    -- Clocks and reset
+    mm_rst             : IN  STD_LOGIC;  -- reset synchronous with mm_clk
+    mm_clk             : IN  STD_LOGIC;  -- memory-mapped bus clock
+    eth_clk            : IN  STD_LOGIC;  -- ethernet phy reference clock
+    st_rst             : IN  STD_LOGIC;  -- reset synchronous with st_clk
+    st_clk             : IN  STD_LOGIC;  -- packet stream clock
+    
+    cal_rec_clk        : IN  STD_LOGIC := '0';  -- Calibration & reconfig clock when using XCVR
+    
+    -- UDP transmit interface
+    udp_tx_snk_in_arr  : IN  t_dp_sosi_arr(c_eth_nof_udp_ports-1 DOWNTO 0) := (OTHERS=> c_dp_sosi_rst);  -- ST sinks, default not valid if not used
+    udp_tx_snk_out_arr : OUT t_dp_siso_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+    -- UDP receive interface
+    udp_rx_src_in_arr  : IN  t_dp_siso_arr(c_eth_nof_udp_ports-1 DOWNTO 0) := (OTHERS=> c_dp_siso_rdy);  -- ST sources, default ready if not used
+    udp_rx_src_out_arr : OUT t_dp_sosi_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+
+    -- Memory Mapped Slaves
+    tse_sla_in         : IN  t_mem_mosi;  -- ETH TSE MAC registers
+    tse_sla_out        : OUT t_mem_miso;
+    reg_sla_in         : IN  t_mem_mosi;  -- ETH control and status registers
+    reg_sla_out        : OUT t_mem_miso;
+    reg_sla_interrupt  : OUT STD_LOGIC;   -- Interrupt
+    ram_sla_in         : IN  t_mem_mosi;  -- ETH rx frame and tx frame memory
+    ram_sla_out        : OUT t_mem_miso;
+
+    -- PHY interface
+    eth_txp            : OUT STD_LOGIC;
+    eth_rxp            : IN  STD_LOGIC;
+
+    -- LED interface
+    tse_led            : OUT t_tse_led
+  );
+END eth;
+
+
+ARCHITECTURE str OF eth IS
+
+  ------------------------------------------------------------------------------
+  -- ETH Rx packet buffer and Tx packet buffer
+  ------------------------------------------------------------------------------
+  
+  -- Use MM bus data width = c_word_w = 32
+  CONSTANT c_mm_ram  : t_c_mem := (latency  => c_mem_ram_rd_latency,
+                                   adr_w    => c_eth_ram_addr_w,
+                                   dat_w    => c_word_w,
+                                   nof_dat  => c_eth_ram_nof_words,
+                                   init_sl  => '0');
+                                   
+  SIGNAL mem_in         : t_mem_mosi;  -- big endian on ST and TSE MAC network side
+  SIGNAL mem_out        : t_mem_miso;  -- big endian on ST and TSE MAC network side
+  SIGNAL mem_in_endian  : t_mem_mosi;  -- keep big endian on MM side
+  SIGNAL mem_out_endian : t_mem_miso;  -- keep big endian on MM side
+                                   
+  ------------------------------------------------------------------------------
+  -- ETH stream
+  ------------------------------------------------------------------------------
+  
+  -- Multiplex - demultiplex
+  CONSTANT c_mux_nof_ports    : NATURAL := 1 + c_eth_nof_udp_ports; -- One for control + nof UDP ports
+  CONSTANT c_demux_nof_ports  : NATURAL := c_mux_nof_ports;
+  CONSTANT c_demux_combined   : BOOLEAN := FALSE;  -- when TRUE then all downstream sinks must be ready, when FALSE then only the
+                                                   -- selected sink needs to be ready (see dp_demux for more explanation).
+  -- All Rx (so UDP off-load and other ETH traffic)
+  SIGNAL rx_adapt_siso        : t_dp_siso;
+  SIGNAL rx_adapt_sosi        : t_dp_sosi;
+  
+  SIGNAL rx_crc_siso          : t_dp_siso;
+  SIGNAL rx_crc_sosi          : t_dp_sosi;
+  
+  SIGNAL rx_ihl20_siso        : t_dp_siso;
+  SIGNAL rx_ihl20_sosi        : t_dp_sosi;
+  
+  SIGNAL rx_hdr_siso          : t_dp_siso;
+  SIGNAL rx_hdr_sosi          : t_dp_sosi;
+  SIGNAL rx_hdr_status        : t_eth_hdr_status;
+  
+  -- Rx demux
+  SIGNAL rx_channel_siso      : t_dp_siso;
+  SIGNAL rx_channel_sosi      : t_dp_sosi;
+  
+  SIGNAL demux_siso_arr       : t_dp_siso_arr(0 TO c_mux_nof_ports-1);
+  SIGNAL demux_sosi_arr       : t_dp_sosi_arr(0 TO c_mux_nof_ports-1);
+  
+  -- ETH Rx
+  SIGNAL eth_rx_siso          : t_dp_siso;
+  SIGNAL eth_rx_sosi          : t_dp_sosi;
+  
+  SIGNAL rx_frame_rd          : STD_LOGIC;
+  SIGNAL rx_frame_ack         : STD_LOGIC;
+  SIGNAL rx_frame_done        : STD_LOGIC;
+  SIGNAL rx_frame_sosi        : t_dp_sosi;
+  SIGNAL rx_frame_hdr_words   : t_eth_total_header_arr;
+  SIGNAL rx_frame_hdr_fields  : t_eth_total_header;
+  SIGNAL rx_frame_hdr_status  : t_eth_hdr_status;
+  SIGNAL rx_frame_crc_word    : STD_LOGIC_VECTOR(c_eth_data_w-1 DOWNTO 0);
+  
+  -- ETH Tx
+  SIGNAL eth_tx_siso          : t_dp_siso;
+  SIGNAL eth_tx_sosi          : t_dp_sosi;
+  
+  -- Tx mux
+  SIGNAL mux_siso_arr         : t_dp_siso_arr(0 TO c_mux_nof_ports-1);
+  SIGNAL mux_sosi_arr         : t_dp_sosi_arr(0 TO c_mux_nof_ports-1);
+  
+  -- All Tx (so UDP off-load and other ETH traffic)
+  SIGNAL tx_mux_siso          : t_dp_siso;
+  SIGNAL tx_mux_sosi          : t_dp_sosi;
+  
+  SIGNAL tx_hdr_siso          : t_dp_siso;
+  SIGNAL tx_hdr_sosi          : t_dp_sosi;
+  
+  ------------------------------------------------------------------------------
+  -- MM registers (in st_clk domain)
+  ------------------------------------------------------------------------------
+  
+  -- . write/read back
+  SIGNAL reg_demux            : t_eth_mm_reg_demux;
+  SIGNAL reg_config           : t_eth_mm_reg_config;
+  SIGNAL reg_control          : t_eth_mm_reg_control;
+  SIGNAL reg_continue_wr      : STD_LOGIC;
+  -- . read only
+  SIGNAL reg_frame            : t_eth_mm_reg_frame;
+  SIGNAL reg_status           : t_eth_mm_reg_status;
+  SIGNAL reg_status_wr        : STD_LOGIC;
+  
+  ------------------------------------------------------------------------------
+  -- TSE MAC
+  ------------------------------------------------------------------------------
+  -- . MAC Transmit Stream
+  SIGNAL tse_tx_siso        : t_dp_siso;
+  SIGNAL tse_tx_sosi        : t_dp_sosi;
+  -- . MAC specific
+  SIGNAL tse_tx_mac_in      : t_tse_tx_mac;
+  SIGNAL tse_tx_mac_out     : t_tse_tx_mac;
+  -- . MAC Receive Stream
+  SIGNAL tse_rx_sosi        : t_dp_sosi;
+  SIGNAL tse_rx_siso        : t_dp_siso;
+  -- . MAC specific
+  SIGNAL tse_rx_mac_out     : t_tse_rx_mac;
+
+BEGIN
+
+  ------------------------------------------------------------------------------
+  -- MM registers
+  ------------------------------------------------------------------------------
+  
+  u_mm_registers : ENTITY work.eth_mm_registers
+  GENERIC MAP (
+    g_cross_clock_domain => g_cross_clock_domain
+  )
+  PORT MAP (
+    -- Clocks and reset
+    mm_rst             => mm_rst,
+    mm_clk             => mm_clk,
+    st_rst             => st_rst,
+    st_clk             => st_clk,
+    -- Memory Mapped Slave
+    sla_in             => reg_sla_in,
+    sla_out            => reg_sla_out,
+    sla_interrupt      => reg_sla_interrupt,
+    -- MM registers in st_clk domain
+    -- . write/read back
+    st_reg_demux       => reg_demux,
+    st_reg_config      => reg_config,
+    st_reg_control     => reg_control,
+    st_reg_continue_wr => reg_continue_wr,
+    -- . read only
+    st_reg_frame       => reg_frame,
+    st_reg_status      => reg_status,
+    st_reg_status_wr   => reg_status_wr
+  );
+
+  -- Packet buffer
+  u_mm_ram : ENTITY common_lib.common_ram_crw_crw
+  GENERIC MAP (
+    g_ram     => c_mm_ram
+  )
+  PORT MAP (
+    rst_a     => mm_rst,
+    clk_a     => mm_clk,
+    wr_en_a   => ram_sla_in.wr,
+    adr_a     => ram_sla_in.address(c_mm_ram.adr_w-1 DOWNTO 0),
+    wr_dat_a  => ram_sla_in.wrdata(c_mm_ram.dat_w-1 DOWNTO 0),
+    rd_en_a   => ram_sla_in.rd,
+    rd_dat_a  => ram_sla_out.rddata(c_mm_ram.dat_w-1 DOWNTO 0),
+    rd_val_a  => ram_sla_out.rdval,
+    rst_b     => st_rst,
+    clk_b     => st_clk,
+    wr_en_b   => mem_in_endian.wr,
+    adr_b     => mem_in_endian.address(c_mm_ram.adr_w-1 DOWNTO 0),
+    wr_dat_b  => mem_in_endian.wrdata(c_mm_ram.dat_w-1 DOWNTO 0),
+    rd_en_b   => mem_in_endian.rd,
+    rd_dat_b  => mem_out_endian.rddata(c_mm_ram.dat_w-1 DOWNTO 0),
+    rd_val_b  => mem_out_endian.rdval
+  );
+  
+  -- The Rx, Tx packet buffer is big-endian
+  mem_in_endian <= func_mem_swap_endianess(mem_in, c_word_sz);
+  mem_out       <= func_mem_swap_endianess(mem_out_endian, c_word_sz);
+  
+  
+  ------------------------------------------------------------------------------
+  -- RX : Adapt the TSE RX source ready latency from 2 to 1
+  ------------------------------------------------------------------------------
+  
+  u_adapt : ENTITY dp_lib.dp_latency_adapter
+  GENERIC MAP (
+    g_in_latency  => c_eth_rx_ready_latency,  -- = 2
+    g_out_latency => c_eth_ready_latency      -- = 1
+  )
+  PORT MAP (
+    rst     => st_rst,
+    clk     => st_clk,
+    -- ST sink
+    snk_out => tse_rx_siso,
+    snk_in  => tse_rx_sosi,
+    -- ST source
+    src_in  => rx_adapt_siso,
+    src_out => rx_adapt_sosi
+  );
+     
+  ------------------------------------------------------------------------------
+  -- RX : Replace the CRC word with the stream error field from the TSE MAC
+  ------------------------------------------------------------------------------
+  
+  u_crc_ctrl : ENTITY work.eth_crc_ctrl
+  PORT MAP (
+    rst            => st_rst,
+    clk            => st_clk,
+
+    -- Streaming Sink
+    snk_in_err     => rx_adapt_sosi.err(c_tse_error_w-1 DOWNTO 0),  -- preserve error field from TSE MAC stream
+    snk_in         => rx_adapt_sosi,
+    snk_out        => rx_adapt_siso,
+    
+    -- Streaming Source
+    src_in         => rx_crc_siso,
+    src_out        => rx_crc_sosi,        -- replaced CRC word by snk_in_err, so CRC word /=0 indicates any TSE error
+    src_out_err    => OPEN                -- flag snk_in_err/=0 at src_out.eop
+  );
+  
+  ------------------------------------------------------------------------------
+  -- RX : Strip the option words from the IPv4 header
+  ------------------------------------------------------------------------------
+  gen_ihl20: IF g_ihl20 GENERATE
+    u_ihl20 : ENTITY work.eth_ihl_to_20
+    PORT MAP (
+      rst            => st_rst,
+      clk            => st_clk,
+
+      -- Streaming Sink
+      snk_in         => rx_crc_sosi,
+      snk_out        => rx_crc_siso,
+      
+      -- Streaming Source
+      src_in         => rx_ihl20_siso,
+      src_out        => rx_ihl20_sosi
+    );
+  END GENERATE;
+  
+  no_ihl20: IF not g_ihl20 GENERATE
+    rx_ihl20_sosi <= rx_crc_sosi;
+    rx_crc_siso   <= rx_ihl20_siso;
+  END GENERATE;
+  
+  
+  ------------------------------------------------------------------------------
+  -- RX : For IP verify IP header checksum
+  ------------------------------------------------------------------------------
+  
+  u_rx_frame : ENTITY work.eth_hdr
+  GENERIC MAP (
+    g_header_store_and_forward     => TRUE,
+    g_ip_header_checksum_calculate => TRUE
+  )
+  PORT MAP (
+    -- Clocks and reset
+    rst             => st_rst,
+    clk             => st_clk,
+    
+    -- Streaming Sink
+    snk_in          => rx_ihl20_sosi,
+    snk_out         => rx_ihl20_siso,
+    
+    -- Streaming Source
+    src_in          => rx_hdr_siso,
+    src_out         => rx_hdr_sosi,
+    
+    -- Header info
+    hdr_status      => rx_hdr_status
+  );
+  
+  
+  ------------------------------------------------------------------------------
+  -- Demux the UDP off-load traffic and the keep the other ETH traffic
+  ------------------------------------------------------------------------------
+
+  -- Put UDP off-load traffic on channel > 0
+  -- Put other ETH    traffic on channel = 0 for further internal processing
+  u_udp_channel : ENTITY work.eth_udp_channel
+  PORT MAP (
+    -- Clocks and reset
+    rst            => st_rst,
+    clk            => st_clk,
+    
+    -- Streaming Sink
+    snk_in         => rx_hdr_sosi,
+    snk_out        => rx_hdr_siso,
+    
+    -- Streaming Source with channel field
+    src_in         => rx_channel_siso,
+    src_out        => rx_channel_sosi,
+    
+    -- Demux control
+    reg_demux      => reg_demux,
+    hdr_status     => rx_hdr_status
+  );
+  
+  -- Demultiplex channel 0 for internal handling and the other channels > 0 for external UDP off-load.
+  u_rx_demux : ENTITY dp_lib.dp_demux
+  GENERIC MAP (
+    g_nof_output    => c_demux_nof_ports,
+    g_combined      => c_demux_combined
+  )
+  PORT MAP (
+    rst         => st_rst,
+    clk         => st_clk,
+    -- ST sinks
+    snk_out     => rx_channel_siso,
+    snk_in      => rx_channel_sosi,
+    -- ST source
+    src_in_arr  => demux_siso_arr,
+    src_out_arr => demux_sosi_arr
+  );
+    
+  -- Fixed local ETH port
+  eth_rx_sosi       <= demux_sosi_arr(0);
+  demux_siso_arr(0) <= eth_rx_siso;
+
+  -- UDP offload ports
+  gen_udp_rx_demux : FOR i IN 1 TO c_eth_nof_udp_ports GENERATE
+    udp_rx_src_out_arr(i-1) <= demux_sosi_arr(i);
+    demux_siso_arr(i)       <= udp_rx_src_in_arr(i-1);
+  END GENERATE;
+ 
+  
+  ------------------------------------------------------------------------------
+  -- ETH RX frame buffer
+  ------------------------------------------------------------------------------
+  
+  u_rx_buffer : ENTITY work.eth_buffer
+  PORT MAP (
+    -- Clocks and reset
+    rst             => st_rst,
+    clk             => st_clk,
+    
+    -- Streaming Sink
+    snk_in          => eth_rx_sosi,
+    snk_out         => eth_rx_siso,
+    
+    -- Streaming Source
+    -- . The src_rd, src_ack and src_done act instead of src_in.ready to have src_in ready per frame instead of per data word
+    src_rd          => rx_frame_rd,    -- request frame pulse
+    src_ack         => rx_frame_ack,   -- acknowledge request
+    src_done        => rx_frame_done,  -- signal frame received
+    src_out         => rx_frame_sosi
+  );  
+
+    
+  ------------------------------------------------------------------------------
+  -- ETH RX frame monitor
+  ------------------------------------------------------------------------------
+  
+  -- Extract total header
+  u_rx_hdr_info : ENTITY work.eth_hdr
+  GENERIC MAP (
+    g_header_store_and_forward     => FALSE,
+    g_ip_header_checksum_calculate => FALSE
+  )
+  PORT MAP (
+    -- Clocks and reset
+    rst             => st_rst,
+    clk             => st_clk,
+    
+    -- Streaming Sink
+    snk_in          => rx_frame_sosi,
+    
+    -- Header info
+    hdr_words       => rx_frame_hdr_words,
+    hdr_fields      => rx_frame_hdr_fields,
+    hdr_status      => rx_frame_hdr_status
+  );
+  
+  -- Extract CRC word (enumerate: 0=OK, >0 AND odd = Error)
+  u_rx_crc_word : ENTITY work.eth_crc_word
+  PORT MAP (
+    rst            => st_rst,
+    clk            => st_clk,
+    
+    -- Streaming Sink
+    snk_in         => rx_frame_sosi,
+    
+    -- CRC word
+    crc_word       => rx_frame_crc_word,
+    crc_word_val   => OPEN
+  );
+  
+  u_mm_reg_frame : ENTITY work.eth_mm_reg_frame
+  PORT MAP (
+    -- Clocks and reset
+    rst             => st_rst,
+    clk             => st_clk,
+    
+    -- Inputs need for the frame register
+    hdr_fields      => rx_frame_hdr_fields,
+    hdr_status      => rx_frame_hdr_status,
+    erc_word        => rx_frame_crc_word,
+    reg_config      => reg_config,
+    
+    -- Frame register
+    reg_frame       => reg_frame
+  );
+
+    
+  ------------------------------------------------------------------------------
+  -- ETH Control
+  ------------------------------------------------------------------------------
+  
+  u_control : ENTITY work.eth_control
+  PORT MAP (
+    -- Clocks and reset
+    rst               => st_rst,
+    clk               => st_clk,
+    
+    -- Control register
+    reg_config        => reg_config,
+    reg_control       => reg_control,
+    reg_continue_wr   => reg_continue_wr,
+    reg_status        => reg_status,
+    reg_status_wr     => reg_status_wr,
+        
+    -- Streaming sink Rx frame
+    rcv_rd            => rx_frame_rd,
+    rcv_ack           => rx_frame_ack,
+    rcv_done          => rx_frame_done,
+    rcv_in            => rx_frame_sosi,
+    rcv_hdr_words     => rx_frame_hdr_words,
+    rcv_hdr_status    => rx_frame_hdr_status,
+
+    -- Streaming source Tx frame
+    xmt_in            => eth_tx_siso,
+    xmt_out           => eth_tx_sosi,
+    
+    -- MM frame memory    
+    mem_in            => mem_in,
+    mem_out           => mem_out
+  );
+  
+    
+  ------------------------------------------------------------------------------
+  -- TX : Mux UDP
+  ------------------------------------------------------------------------------
+   
+  -- Fixed local ETH
+  eth_tx_siso     <= mux_siso_arr(0);
+  mux_sosi_arr(0) <= eth_tx_sosi;
+
+  -- UDP offload ports
+  gen_udp_tx_mux : FOR i IN 1 TO c_eth_nof_udp_ports GENERATE
+    udp_tx_snk_out_arr(i-1) <= mux_siso_arr(i);
+    mux_sosi_arr(i)         <= udp_tx_snk_in_arr(i-1);
+  END GENERATE;
+  
+  -- Multiplex the two input streams on to the single ETH stream
+  u_tx_mux : ENTITY dp_lib.dp_mux
+  GENERIC MAP (
+    g_data_w          => c_eth_data_w,
+    g_empty_w         => c_eth_empty_w,
+    g_in_channel_w    => 1,
+    g_error_w         => 1,
+    g_use_empty       => TRUE,
+    g_use_in_channel  => FALSE,
+    g_use_error       => FALSE,
+    g_nof_input       => c_mux_nof_ports,
+    g_use_fifo        => FALSE,
+    g_fifo_size       => array_init(1024, c_mux_nof_ports),  -- input FIFOs are not used, but generic must match g_nof_input
+    g_fifo_fill       => array_init(   0, c_mux_nof_ports)   -- input FIFOs are not used, but generic must match g_nof_input
+  )
+  PORT MAP (
+    rst         => st_rst,
+    clk         => st_clk,
+    -- ST sinks
+    snk_out_arr => mux_siso_arr,     -- OUT = request to upstream ST source
+    snk_in_arr  => mux_sosi_arr,
+    -- ST source
+    src_in      => tx_mux_siso,  -- IN  = request from downstream ST sink
+    src_out     => tx_mux_sosi
+  );  
+  
+  ------------------------------------------------------------------------------
+  -- TX : For IP insert IP header checksum
+  ------------------------------------------------------------------------------
+  
+  u_tx_frame : ENTITY work.eth_hdr
+  GENERIC MAP (
+    g_header_store_and_forward     => TRUE,
+    g_ip_header_checksum_calculate => TRUE
+  )
+  PORT MAP (
+    -- Clocks and reset
+    rst             => st_rst,
+    clk             => st_clk,
+    
+    -- Streaming Sink
+    snk_in          => tx_mux_sosi,
+    snk_out         => tx_mux_siso,
+    
+    -- Streaming Source
+    src_in          => tx_hdr_siso,
+    src_out         => tx_hdr_sosi
+  );
+  
+  
+  ------------------------------------------------------------------------------
+  -- TSE MAC
+  ------------------------------------------------------------------------------
+  tx_hdr_siso <= tse_tx_siso;
+  tse_tx_sosi <= func_dp_stream_error_set(tx_hdr_sosi, 0);   -- set err field (value 0 for OK)
+  
+  tse_tx_mac_in.crc_fwd <= '0';  -- when '0' then TSE MAC generates the TX CRC field
+
+  u_tse : ENTITY work.tse
+  GENERIC MAP (
+    g_ETH_PHY      => g_ETH_PHY
+  )
+  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,
+    cal_rec_clk    => cal_rec_clk,
+    -- Memory Mapped Slave
+    mm_sla_in      => tse_sla_in,
+    mm_sla_out     => tse_sla_out,
+    -- MAC transmit interface
+    -- . ST sink
+    tx_snk_in      => tse_tx_sosi,
+    tx_snk_out     => tse_tx_siso,
+    -- . MAC specific
+    tx_mac_in      => tse_tx_mac_in,
+    tx_mac_out     => tse_tx_mac_out,  -- OPEN
+    -- MAC receive interface
+    -- . ST Source
+    rx_src_in      => tse_rx_siso,
+    rx_src_out     => tse_rx_sosi,
+    -- . MAC specific
+    rx_mac_out     => tse_rx_mac_out,
+    -- PHY interface
+    eth_txp        => eth_txp,
+    eth_rxp        => eth_rxp,
+    -- LED interface
+    tse_led        => tse_led
+  );
+  
+END str;
diff --git a/libraries/io/eth/src/vhdl/eth_pkg.vhd b/libraries/io/eth/src/vhdl/eth_pkg.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..a9a45752bb2fdcc83509d683e634237e43084bc4
--- /dev/null
+++ b/libraries/io/eth/src/vhdl/eth_pkg.vhd
@@ -0,0 +1,353 @@
+-------------------------------------------------------------------------------
+--
+-- 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, tech_tse_lib;
+USE IEEE.std_logic_1164.ALL;
+USE IEEE.numeric_std.ALL;
+USE common_lib.common_pkg.ALL;
+USE dp_lib.dp_stream_pkg.ALL;
+USE common_lib.eth_layers_pkg.ALL;
+USE tech_tse_lib.tech_tse_pkg.ALL;
+
+
+PACKAGE eth_pkg IS
+
+  CONSTANT c_eth_data_w                : NATURAL := c_tse_data_w;   -- = c_word_w
+  CONSTANT c_eth_empty_w               : NATURAL := c_tse_empty_w;  -- = ceil_log2(c_word_sz) = 2;
+  CONSTANT c_eth_error_w               : NATURAL := c_tse_error_w;  -- = 6, but no error field, pass error info on via checksum or CRC /= 0 in packet word
+  
+  CONSTANT c_eth_rx_ready_latency      : NATURAL := c_tse_rx_ready_latency;  -- = 2 = default when FIFO is used
+  CONSTANT c_eth_tx_ready_latency      : NATURAL := c_tse_tx_ready_latency;  -- = 0, c_tse_tx_ready_latency + 3 = TX_ALMOST_FULL
+  CONSTANT c_eth_ready_latency         : NATURAL := 1;                       -- = 1, fixed ETH module internal RL
+
+  -- Maximum feasible frame size
+  CONSTANT c_eth_max_frame_sz          : NATURAL := 1024*9;  -- 9 kByte fits 9020 = word align (2) + eth header (14) + maximum jumbo payload (9000) + CRC (4)
+  CONSTANT c_eth_max_frame_nof_words   : NATURAL := c_eth_max_frame_sz/c_word_sz;
+  CONSTANT c_eth_max_frame_nof_words_w : NATURAL := ceil_log2(c_eth_max_frame_nof_words);  -- = 12 bit
+  
+  -- Actual frame space in RAM <= c_eth_max_frame_sz, default >= 1520, <= 9020 for jumbo
+  --CONSTANT c_eth_frame_sz              : NATURAL := 1024*3/2;  -- Use 1536 = 3/2 M9K, to benefit from Rx and Tx in single buffer of 2*1.5=3 M9K, but does
+                                                                 -- yield simulation warning: Address pointed at port A is out of bound!
+  --CONSTANT c_eth_frame_sz              : NATURAL := 1024*8;  -- Use 8192 = 8 M9K for jumbo frame support with payload size <= 8172
+  --CONSTANT c_eth_frame_sz              : NATURAL := 1024*9;  -- Use 9216 = 9 M9K for jumbo frame support with payload size <= 9000
+  CONSTANT c_eth_frame_sz              : NATURAL := 1024*2;    -- Use 2048 = 2 M9K to avoid simulation warning: Address pointed at port A is out of bound!
+                                                               -- when the module is used in an Nios II SOPC system
+                                                               -- The FIFOs seem to require nof words to be a power of 2, but that is taken care of locally if necessary
+  CONSTANT c_eth_frame_nof_words       : NATURAL := c_eth_frame_sz/c_word_sz;
+  CONSTANT c_eth_frame_nof_words_w     : NATURAL := ceil_log2(c_eth_frame_nof_words);      -- >= 9 bit, <= 12 bit
+                                                         
+  TYPE t_eth_data_arr IS ARRAY (INTEGER RANGE <>) OF STD_LOGIC_VECTOR(c_eth_data_w-1 DOWNTO 0);
+  
+  
+  ------------------------------------------------------------------------------
+  -- Definitions for eth header status
+  ------------------------------------------------------------------------------
+  
+  TYPE t_eth_hdr_status IS RECORD
+    is_arp            : STD_LOGIC;
+    is_ip             : STD_LOGIC;
+    is_icmp           : STD_LOGIC;
+    is_udp            : STD_LOGIC;
+    ip_checksum       : STD_LOGIC_VECTOR(c_halfword_w-1 DOWNTO 0);
+    ip_checksum_val   : STD_LOGIC;
+    ip_checksum_is_ok : STD_LOGIC;
+    udp_port          : STD_LOGIC_VECTOR(c_udp_port_slv'RANGE);
+    is_dhcp           : STD_LOGIC;
+  END RECORD;
+  
+  CONSTANT c_eth_hdr_status_rst : t_eth_hdr_status := ('0', '0', '0', '0',
+                                                       (OTHERS=>'0'), '0', '0',
+                                                       (OTHERS=>'0'), '0');
+  
+  ------------------------------------------------------------------------------
+  -- Definitions for eth demux udp
+  ------------------------------------------------------------------------------
+  
+  CONSTANT c_eth_nof_udp_ports : NATURAL := 4;                               -- support 2, 4, 8, ... UDP off-load ports
+  CONSTANT c_eth_channel_w     : NATURAL := ceil_log2(c_eth_nof_udp_ports + 1);         -- + 1 for all other packets that go to the default port
+  CONSTANT c_eth_nof_channels  : NATURAL := 2**c_eth_channel_w;
+    
+  ------------------------------------------------------------------------------
+  -- MM register map
+  ------------------------------------------------------------------------------
+  
+  CONSTANT c_eth_reg_demux_nof_words    : NATURAL := c_eth_nof_udp_ports;
+  CONSTANT c_eth_reg_config_nof_words   : NATURAL := 4;
+  CONSTANT c_eth_reg_control_nof_words  : NATURAL := 1;
+  CONSTANT c_eth_reg_frame_nof_words    : NATURAL := 1;
+  CONSTANT c_eth_reg_status_nof_words   : NATURAL := 1;
+  
+  CONSTANT c_eth_reg_demux_wi           : NATURAL := 0;
+  CONSTANT c_eth_reg_config_wi          : NATURAL := c_eth_reg_demux_wi   + c_eth_reg_demux_nof_words;
+  CONSTANT c_eth_reg_control_wi         : NATURAL := c_eth_reg_config_wi  + c_eth_reg_config_nof_words;
+  CONSTANT c_eth_reg_frame_wi           : NATURAL := c_eth_reg_control_wi + c_eth_reg_control_nof_words;
+  CONSTANT c_eth_reg_status_wi          : NATURAL := c_eth_reg_frame_wi   + c_eth_reg_frame_nof_words;
+  CONSTANT c_eth_reg_continue_wi        : NATURAL := c_eth_reg_status_wi  + c_eth_reg_status_nof_words;
+
+  -- . write/read back registers
+  TYPE t_eth_demux_ports_arr IS ARRAY(1 TO c_eth_nof_udp_ports) OF STD_LOGIC_VECTOR(c_udp_port_w-1 DOWNTO 0);
+  TYPE t_eth_mm_reg_demux IS RECORD
+    udp_ports_en : STD_LOGIC_VECTOR(1 TO c_eth_nof_udp_ports);  -- [16]
+    udp_ports    : t_eth_demux_ports_arr;                             -- [15:0]
+  END RECORD;
+
+  TYPE t_eth_mm_reg_config IS RECORD
+    udp_port             : STD_LOGIC_VECTOR(c_udp_port_w-1 DOWNTO 0);       -- [15:0]
+    ip_address           : STD_LOGIC_VECTOR(c_ip_addr_w-1 DOWNTO 0);        -- [31:0]
+    mac_address          : STD_LOGIC_VECTOR(c_eth_mac_addr_w-1 DOWNTO 0);   -- [15:0], [31:0]
+  END RECORD;
+  
+  TYPE t_eth_mm_reg_control IS RECORD
+    tx_nof_words      : STD_LOGIC_VECTOR(c_eth_max_frame_nof_words_w-1 DOWNTO 0);  -- 12 bit
+    tx_empty          : STD_LOGIC_VECTOR(c_eth_empty_w-1 DOWNTO 0);                -- 2 bit
+    tx_request        : STD_LOGIC;                                                 -- 1 bit
+    tx_en             : STD_LOGIC;                                                 -- 1 bit
+    rx_en             : STD_LOGIC;                                                 -- 1 bit
+  END RECORD;
+  
+  TYPE t_eth_mm_reg_control_bi IS RECORD  -- bit indices
+    tx_nof_words      : NATURAL;          -- [26:18]    
+    tx_empty          : NATURAL;          -- [17:16]    
+    tx_request        : NATURAL;          -- [2]        
+    tx_en             : NATURAL;          -- [1]        
+    rx_en             : NATURAL;          -- [0]        
+  END RECORD;
+  
+  CONSTANT c_eth_mm_reg_control_bi  : t_eth_mm_reg_control_bi := (18, 16, 2, 1, 0);
+  CONSTANT c_eth_mm_reg_control_rst : t_eth_mm_reg_control := ((OTHERS=>'0'), (OTHERS=>'0'), '0', '0', '0');
+
+  -- . read only registers
+  TYPE t_eth_mm_reg_frame IS RECORD
+    is_dhcp           : STD_LOGIC;
+    is_udp_ctrl_port  : STD_LOGIC;
+    is_udp            : STD_LOGIC;
+    is_icmp           : STD_LOGIC;
+    ip_address_match  : STD_LOGIC;
+    ip_checksum_is_ok : STD_LOGIC;
+    is_ip             : STD_LOGIC;
+    is_arp            : STD_LOGIC;
+    mac_address_match : STD_LOGIC;
+    eth_mac_error     : STD_LOGIC_VECTOR(c_eth_error_w-1 DOWNTO 0);
+  END RECORD;
+  
+  TYPE t_eth_mm_reg_frame_bi IS RECORD  -- bit indices
+    is_dhcp           : NATURAL;        -- [15]
+    is_udp_ctrl_port  : NATURAL;        -- [14]
+    is_udp            : NATURAL;        -- [13]
+    is_icmp           : NATURAL;        -- [12]
+    ip_address_match  : NATURAL;        -- [11]
+    ip_checksum_is_ok : NATURAL;        -- [10]
+    is_ip             : NATURAL;        -- [9]
+    is_arp            : NATURAL;        -- [8]
+    mac_address_match : NATURAL;        -- [7]
+    eth_mac_error     : NATURAL;        -- [6] not used, [5:0] = TSE MAC error
+  END RECORD;
+  
+  CONSTANT c_eth_mm_reg_frame_bi  : t_eth_mm_reg_frame_bi := (15, 14, 13, 12, 11, 10, 9, 8, 7, 0);
+  CONSTANT c_eth_mm_reg_frame_rst : t_eth_mm_reg_frame    := ('0', '0', '0', '0', '0', '0', '0', '0', '0', (OTHERS=>'0'));
+    
+  TYPE t_eth_mm_reg_status IS RECORD
+    rx_nof_words      : STD_LOGIC_VECTOR(c_eth_max_frame_nof_words_w-1 DOWNTO 0);  -- 12 bit
+    rx_empty          : STD_LOGIC_VECTOR(c_eth_empty_w-1 DOWNTO 0);                -- 2 bit
+    tx_avail          : STD_LOGIC;                                                 -- 1 bit
+    tx_done           : STD_LOGIC;                                                 -- 1 bit
+    rx_avail          : STD_LOGIC;                                                 -- 1 bit
+  END RECORD;
+  
+  TYPE t_eth_mm_reg_status_bi IS RECORD  -- bit indices
+    rx_nof_words      : NATURAL;         -- [26:18]
+    rx_empty          : NATURAL;         -- [17:16]
+    tx_avail          : NATURAL;         -- [2]
+    tx_done           : NATURAL;         -- [1]
+    rx_avail          : NATURAL;         -- [0]
+  END RECORD;
+  
+  CONSTANT c_eth_mm_reg_status_bi  : t_eth_mm_reg_status_bi := (18, 16, 2, 1, 0);
+  CONSTANT c_eth_mm_reg_status_rst : t_eth_mm_reg_status    := ((OTHERS=>'0'), (OTHERS=>'0'), '0', '0', '0');
+  
+  -- Register mapping functions
+  FUNCTION func_eth_mm_reg_demux(  mm_reg : STD_LOGIC_VECTOR)     RETURN t_eth_mm_reg_demux;
+  FUNCTION func_eth_mm_reg_demux(  mm_reg : t_eth_mm_reg_demux)   RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_eth_mm_reg_config( mm_reg : STD_LOGIC_VECTOR)     RETURN t_eth_mm_reg_config;
+  FUNCTION func_eth_mm_reg_config( mm_reg : t_eth_mm_reg_config)  RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_eth_mm_reg_control(mm_reg : STD_LOGIC_VECTOR)     RETURN t_eth_mm_reg_control;
+  FUNCTION func_eth_mm_reg_control(mm_reg : t_eth_mm_reg_control) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_eth_mm_reg_frame(  mm_reg : STD_LOGIC_VECTOR)     RETURN t_eth_mm_reg_frame;
+  FUNCTION func_eth_mm_reg_frame(  mm_reg : t_eth_mm_reg_frame)   RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_eth_mm_reg_status( mm_reg : STD_LOGIC_VECTOR)     RETURN t_eth_mm_reg_status;
+  FUNCTION func_eth_mm_reg_status( mm_reg : t_eth_mm_reg_status)  RETURN STD_LOGIC_VECTOR;
+  
+  ------------------------------------------------------------------------------
+  -- Definitions for eth_mm_registers
+  ------------------------------------------------------------------------------
+  
+  CONSTANT c_eth_reg_nof_words    : NATURAL := c_eth_reg_demux_nof_words +
+                                               c_eth_reg_config_nof_words +
+                                               c_eth_reg_control_nof_words +
+                                               c_eth_reg_frame_nof_words +
+                                               c_eth_reg_status_nof_words;
+  CONSTANT c_eth_reg_addr_w       : NATURAL := ceil_log2(c_eth_reg_nof_words + 1);  -- + 1 for c_eth_continue_wi
+  
+  ------------------------------------------------------------------------------
+  -- Definitions for ETH Rx packet buffer and Tx packet buffer
+  ------------------------------------------------------------------------------
+  
+  -- Use MM bus data width = c_word_w = 32
+  CONSTANT c_eth_ram_rx_offset : NATURAL := 0;
+  CONSTANT c_eth_ram_tx_offset : NATURAL := c_eth_frame_nof_words;
+  CONSTANT c_eth_ram_nof_words : NATURAL := c_eth_frame_nof_words*2;
+  CONSTANT c_eth_ram_addr_w    : NATURAL := ceil_log2(c_eth_ram_nof_words);
+  
+END eth_pkg;
+
+
+PACKAGE BODY eth_pkg IS
+  
+  -- Register mapping functions
+  FUNCTION func_eth_mm_reg_demux(mm_reg : STD_LOGIC_VECTOR) RETURN t_eth_mm_reg_demux IS
+    VARIABLE v_reg : t_eth_mm_reg_demux;
+  BEGIN
+    -- Demux UDP MM registers
+    FOR I IN 1 TO c_eth_nof_udp_ports LOOP
+      v_reg.udp_ports_en(I) := mm_reg(c_udp_port_w   + (I-1)*c_word_w);                        -- [16]   = UDP port enable
+      v_reg.udp_ports(I)    := mm_reg(c_udp_port_w-1 + (I-1)*c_word_w DOWNTO (I-1)*c_word_w);  -- [15:0] = UDP port number
+    END LOOP;
+    RETURN v_reg;
+  END func_eth_mm_reg_demux;
+
+  FUNCTION func_eth_mm_reg_demux(mm_reg : t_eth_mm_reg_demux) RETURN STD_LOGIC_VECTOR IS
+    VARIABLE v_reg : STD_LOGIC_VECTOR(c_eth_reg_demux_nof_words*c_word_w-1 DOWNTO 0);
+  BEGIN
+    v_reg                                                          := (OTHERS=>'0');           -- rsvd
+    FOR I IN 1 TO c_eth_nof_udp_ports LOOP
+      v_reg(c_udp_port_w   + (I-1)*c_word_w)                       := mm_reg.udp_ports_en(I);  -- [16]   = UDP port enable
+      v_reg(c_udp_port_w-1 + (I-1)*c_word_w DOWNTO (I-1)*c_word_w) := mm_reg.udp_ports(I);     -- [15:0] = UDP port number
+    END LOOP;
+    RETURN v_reg;
+  END func_eth_mm_reg_demux;
+  
+  -- MM config register
+  FUNCTION func_eth_mm_reg_config(mm_reg : STD_LOGIC_VECTOR) RETURN t_eth_mm_reg_config IS
+    VARIABLE v_reg : t_eth_mm_reg_config;
+  BEGIN
+    v_reg.udp_port                        := mm_reg(c_udp_port_w+3*c_word_w-1 DOWNTO 3*c_word_w);  -- [15:0]  = control UDP port number
+    v_reg.ip_address                      := mm_reg(             3*c_word_w-1 DOWNTO 2*c_word_w);  -- [31:0]  = this node IP address
+    v_reg.mac_address(47 DOWNTO 32)       := mm_reg(  c_halfword_w+c_word_w-1 DOWNTO   c_word_w);  -- [47:32] = this node MAC address
+    v_reg.mac_address(31 DOWNTO 0)        := mm_reg(               c_word_w-1 DOWNTO   0);         -- [31:0]  = this node MAC address
+    RETURN v_reg;
+  END func_eth_mm_reg_config;
+
+  FUNCTION func_eth_mm_reg_config(mm_reg : t_eth_mm_reg_config) RETURN STD_LOGIC_VECTOR IS
+    VARIABLE v_reg : STD_LOGIC_VECTOR(c_eth_reg_config_nof_words*c_word_w-1 DOWNTO 0);
+  BEGIN
+    v_reg                                              := (OTHERS=>'0');                            -- rsvd
+    v_reg(c_udp_port_w+3*c_word_w-1 DOWNTO 3*c_word_w) := mm_reg.udp_port;                          -- [15:0]  = control UDP port number
+    v_reg(             3*c_word_w-1 DOWNTO 2*c_word_w) := mm_reg.ip_address;                        -- [31:0]  = this node IP address
+    v_reg(  c_halfword_w+c_word_w-1 DOWNTO   c_word_w) := mm_reg.mac_address(47 DOWNTO 32);         -- [47:32] = this node MAC address
+    v_reg(               c_word_w-1 DOWNTO   0)        := mm_reg.mac_address(31 DOWNTO 0);          -- [31:0]  = this node MAC address
+    RETURN v_reg;
+  END func_eth_mm_reg_config;
+    
+  -- MM control register
+  FUNCTION func_eth_mm_reg_control(mm_reg : STD_LOGIC_VECTOR) RETURN t_eth_mm_reg_control IS
+    VARIABLE v_reg : t_eth_mm_reg_control;
+  BEGIN
+    v_reg.tx_nof_words := mm_reg(c_eth_max_frame_nof_words_w+c_eth_empty_w+16-1 DOWNTO c_eth_empty_w+16);  -- [29:18]
+    v_reg.tx_empty     := mm_reg(                            c_eth_empty_w+16-1 DOWNTO               16);  -- [17:16]
+    v_reg.tx_request   := mm_reg(                                                                     2);  -- [2]
+    v_reg.tx_en        := mm_reg(                                                                     1);  -- [1]
+    v_reg.rx_en        := mm_reg(                                                                     0);  -- [0]
+    RETURN v_reg;
+  END func_eth_mm_reg_control;
+
+  FUNCTION func_eth_mm_reg_control(mm_reg : t_eth_mm_reg_control) RETURN STD_LOGIC_VECTOR IS
+    VARIABLE v_reg : STD_LOGIC_VECTOR(c_eth_reg_control_nof_words*c_word_w-1 DOWNTO 0);
+  BEGIN
+    v_reg                                                                     := (OTHERS=>'0');        -- rsvd
+    v_reg(c_eth_max_frame_nof_words_w+c_eth_empty_w+16-1 DOWNTO c_eth_empty_w+16) := mm_reg.tx_nof_words;  -- [29:18]
+    v_reg(                            c_eth_empty_w+16-1 DOWNTO               16) := mm_reg.tx_empty;      -- [17:16]
+    v_reg(                                                                     2) := mm_reg.tx_request;    -- [2]
+    v_reg(                                                                     1) := mm_reg.tx_en;         -- [1]
+    v_reg(                                                                     0) := mm_reg.rx_en;         -- [0]
+    RETURN v_reg;
+  END func_eth_mm_reg_control;
+    
+  -- MM frame register
+  FUNCTION func_eth_mm_reg_frame(mm_reg : STD_LOGIC_VECTOR) RETURN t_eth_mm_reg_frame IS
+    VARIABLE v_reg : t_eth_mm_reg_frame;
+  BEGIN
+    v_reg.is_dhcp           := mm_reg(              c_byte_w+7);  -- [15]
+    v_reg.is_udp_ctrl_port  := mm_reg(              c_byte_w+6);  -- [14]
+    v_reg.is_udp            := mm_reg(              c_byte_w+5);  -- [13]
+    v_reg.is_icmp           := mm_reg(              c_byte_w+4);  -- [12]
+    v_reg.ip_address_match  := mm_reg(              c_byte_w+3);  -- [11]
+    v_reg.ip_checksum_is_ok := mm_reg(              c_byte_w+2);  -- [10]
+    v_reg.is_ip             := mm_reg(              c_byte_w+1);  -- [9]
+    v_reg.is_arp            := mm_reg(              c_byte_w+0);  -- [8]
+    v_reg.mac_address_match := mm_reg(              c_byte_w-1);  -- [7]
+    v_reg.eth_mac_error     := mm_reg(c_eth_error_w-1 DOWNTO 0);  -- [7] not used, [5:0] = TSE MAC error
+    RETURN v_reg;
+  END func_eth_mm_reg_frame;
+  
+  FUNCTION func_eth_mm_reg_frame(mm_reg : t_eth_mm_reg_frame) RETURN STD_LOGIC_VECTOR IS
+    VARIABLE v_reg : STD_LOGIC_VECTOR(c_eth_reg_frame_nof_words*c_word_w-1 DOWNTO 0);
+  BEGIN
+    v_reg                           := (OTHERS=>'0');             -- rsvd
+    v_reg(              c_byte_w+7) := mm_reg.is_dhcp;            -- [15]
+    v_reg(              c_byte_w+6) := mm_reg.is_udp_ctrl_port;   -- [14]
+    v_reg(              c_byte_w+5) := mm_reg.is_udp;             -- [13]
+    v_reg(              c_byte_w+4) := mm_reg.is_icmp;            -- [12]
+    v_reg(              c_byte_w+3) := mm_reg.ip_address_match;   -- [11]
+    v_reg(              c_byte_w+2) := mm_reg.ip_checksum_is_ok;  -- [10]
+    v_reg(              c_byte_w+1) := mm_reg.is_ip;              -- [9]
+    v_reg(              c_byte_w+0) := mm_reg.is_arp;             -- [8]
+    v_reg(              c_byte_w-1) := mm_reg.mac_address_match;  -- [7]
+    v_reg(c_eth_error_w-1 DOWNTO 0) := mm_reg.eth_mac_error;      -- [6] not used, [5:0] = TSE MAC error
+    RETURN v_reg;
+  END func_eth_mm_reg_frame;
+  
+  -- MM status register
+  FUNCTION func_eth_mm_reg_status(mm_reg : STD_LOGIC_VECTOR) RETURN t_eth_mm_reg_status IS
+    VARIABLE v_reg : t_eth_mm_reg_status;
+  BEGIN
+    v_reg.rx_nof_words := mm_reg(c_eth_max_frame_nof_words_w+c_eth_empty_w+16-1 DOWNTO c_eth_empty_w+16);  -- [29:18]
+    v_reg.rx_empty     := mm_reg(                            c_eth_empty_w+16-1 DOWNTO               16);  -- [17:16]
+    v_reg.tx_avail     := mm_reg(                                                                     2);  -- [2]
+    v_reg.tx_done      := mm_reg(                                                                     1);  -- [1]
+    v_reg.rx_avail     := mm_reg(                                                                     0);  -- [0]
+    RETURN v_reg;
+  END func_eth_mm_reg_status;
+
+  FUNCTION func_eth_mm_reg_status(mm_reg : t_eth_mm_reg_status) RETURN STD_LOGIC_VECTOR IS
+    VARIABLE v_reg : STD_LOGIC_VECTOR(c_eth_reg_status_nof_words*c_word_w-1 DOWNTO 0);
+  BEGIN
+    v_reg                                                                     := (OTHERS=>'0');        -- rsvd
+    v_reg(c_eth_max_frame_nof_words_w+c_eth_empty_w+16-1 DOWNTO c_eth_empty_w+16) := mm_reg.rx_nof_words;  -- [29:18]
+    v_reg(                            c_eth_empty_w+16-1 DOWNTO               16) := mm_reg.rx_empty;      -- [17:16]
+    v_reg(                                                                     2) := mm_reg.tx_avail;      -- [2]
+    v_reg(                                                                     1) := mm_reg.tx_done;       -- [1]
+    v_reg(                                                                     0) := mm_reg.rx_avail;      -- [0]
+    RETURN v_reg;
+  END func_eth_mm_reg_status;
+  
+END eth_pkg;
+
diff --git a/libraries/io/eth/tb/vhdl/tb_eth.vhd b/libraries/io/eth/tb/vhdl/tb_eth.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..058afe95d11c089a72246f976554fdbf974cfd9e
--- /dev/null
+++ b/libraries/io/eth/tb/vhdl/tb_eth.vhd
@@ -0,0 +1,531 @@
+-------------------------------------------------------------------------------
+--
+-- 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, tech_tse_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 common_lib.eth_layers_pkg.ALL;
+USE WORK.eth_pkg.ALL;
+USE tech_tse_lib.tech_tse_pkg.ALL;
+USE tech_tse_lib.tb_tech_tse_pkg.ALL;
+
+
+ENTITY tb_eth 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 = c_tb_tse_data_type_arp      = 2
+    --   g_data_type = c_tb_tse_data_type_ping     = 3
+    --   g_data_type = c_tb_tse_data_type_udp      = 4
+    g_data_type : NATURAL := c_tb_tse_data_type_udp
+  );
+END tb_eth;
+
+
+ARCHITECTURE tb OF tb_eth IS
+
+  -- as 10
+  -- run 100 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_cross_clock_domain : BOOLEAN := TRUE;  -- use FALSE when mm_clk and st_clk are the same, else use TRUE to cross the clock domain
+  
+  -- TSE constants
+  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
+  
+  -- Payload user data
+  CONSTANT c_tb_nof_data        : NATURAL := 0;  -- nof UDP user data, nof ping padding data
+  CONSTANT c_tb_ip_nof_data     : NATURAL := c_udp_header_len + c_tb_nof_data; -- nof IP data,
+                                          -- also suits ICMP, because c_icmp_header_len = c_udp_header_len
+  CONSTANT c_tb_reply_payload   : BOOLEAN := TRUE;  -- TRUE copy rx payload into response payload, else header only (e.g. for ARP)
+
+  -- Packet headers
+  -- . Ethernet header
+  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_lcu_src_mac        : STD_LOGIC_VECTOR(c_eth_mac_slv'RANGE) := X"10FA01020300";
+  CONSTANT c_dut_src_mac        : STD_LOGIC_VECTOR(c_eth_mac_slv'RANGE) := X"123456789ABC";  -- = 12-34-56-78-9A-BC
+  CONSTANT c_dut_src_mac_hi     : NATURAL := TO_UINT(c_dut_src_mac(c_eth_mac_addr_w-1 DOWNTO c_word_w));
+  CONSTANT c_dut_src_mac_lo     : NATURAL := TO_UINT(c_dut_src_mac(        c_word_w-1 DOWNTO        0));
+                                -- support only ARP and IPv4 over ETH
+  CONSTANT c_dut_ethertype      : NATURAL := sel_a_b(g_data_type-c_tb_tse_data_type_arp, c_eth_type_ip, c_eth_type_arp);
+  CONSTANT c_tx_eth_header      : t_eth_header := (word_align => c_word_align,
+                                                   dst_mac    => c_dut_src_mac,
+                                                   src_mac    => c_lcu_src_mac,
+                                                   eth_type   => TO_UVEC(c_dut_ethertype, c_eth_type_w));
+  CONSTANT c_exp_eth_header     : t_eth_header := (word_align => c_tx_eth_header.word_align, -- =
+                                                   dst_mac    => c_tx_eth_header.src_mac,    -- \/
+                                                   src_mac    => c_tx_eth_header.dst_mac,    -- /\
+                                                   eth_type   => c_tx_eth_header.eth_type);  -- =
+                                                   
+  -- . IP header
+  CONSTANT c_lcu_ip_addr        : NATURAL := 16#05060708#;  -- = 05:06:07:08
+  CONSTANT c_dut_ip_addr        : NATURAL := 16#01020304#;
+  CONSTANT c_tb_ip_total_length : NATURAL := c_ip_total_length + c_tb_ip_nof_data;
+                                             -- support only ping protocol or UDP protocol over IP
+  CONSTANT c_tb_ip_protocol     : NATURAL := sel_a_b(g_data_type-c_tb_tse_data_type_ping, c_ip_protocol_udp, c_ip_protocol_icmp);
+  
+  CONSTANT c_tx_ip_header       : t_ip_header := (version         => TO_UVEC(c_ip_version,         c_ip_version_w),
+                                                  header_length   => TO_UVEC(c_ip_header_length,   c_ip_header_length_w),
+                                                  services        => TO_UVEC(c_ip_services,        c_ip_services_w),
+                                                  total_length    => TO_UVEC(c_tb_ip_total_length, c_ip_total_length_w),
+                                                  identification  => TO_UVEC(c_ip_identification,  c_ip_identification_w),
+                                                  flags           => TO_UVEC(c_ip_flags,           c_ip_flags_w),
+                                                  fragment_offset => TO_UVEC(c_ip_fragment_offset, c_ip_fragment_offset_w),
+                                                  time_to_live    => TO_UVEC(c_ip_time_to_live,    c_ip_time_to_live_w),
+                                                  protocol        => TO_UVEC(c_tb_ip_protocol,     c_ip_protocol_w),
+                                                  header_checksum => TO_UVEC(c_ip_header_checksum, c_ip_header_checksum_w),  -- init value (or try 0xEBBD = 60349)
+                                                  src_ip_addr     => TO_UVEC(c_lcu_ip_addr,        c_ip_addr_w),
+                                                  dst_ip_addr     => TO_UVEC(c_dut_ip_addr,        c_ip_addr_w));
+                                                  
+  CONSTANT c_exp_ip_header      : t_ip_header := (version         => c_tx_ip_header.version,          -- =
+                                                  header_length   => c_tx_ip_header.header_length,    -- =
+                                                  services        => c_tx_ip_header.services,         -- =
+                                                  total_length    => c_tx_ip_header.total_length,     -- =
+                                                  identification  => c_tx_ip_header.identification,   -- =
+                                                  flags           => c_tx_ip_header.flags,            -- =
+                                                  fragment_offset => c_tx_ip_header.fragment_offset,  -- =
+                                                  time_to_live    => c_tx_ip_header.time_to_live,     -- =
+                                                  protocol        => c_tx_ip_header.protocol,         -- =
+                                                  header_checksum => c_tx_ip_header.header_checksum,  -- init value
+                                                  src_ip_addr     => c_tx_ip_header.dst_ip_addr,      -- \/
+                                                  dst_ip_addr     => c_tx_ip_header.src_ip_addr);     -- /\
+                                                  
+  -- . ARP packet
+  CONSTANT c_tx_arp_packet      : t_arp_packet := (htype => TO_UVEC(c_arp_htype,        c_arp_htype_w),
+                                                   ptype => TO_UVEC(c_arp_ptype,        c_arp_ptype_w),
+                                                   hlen  => TO_UVEC(c_arp_hlen,         c_arp_hlen_w),
+                                                   plen  => TO_UVEC(c_arp_plen,         c_arp_plen_w),
+                                                   oper  => TO_UVEC(c_arp_oper_request, c_arp_oper_w),
+                                                   sha   => c_lcu_src_mac,
+                                                   spa   => TO_UVEC(c_lcu_ip_addr,      c_ip_addr_w),
+                                                   tha   => c_dut_src_mac,
+                                                   tpa   => TO_UVEC(c_dut_ip_addr,      c_ip_addr_w));
+
+  CONSTANT c_exp_arp_packet     : t_arp_packet := (htype => c_tx_arp_packet.htype,
+                                                   ptype => c_tx_arp_packet.ptype,
+                                                   hlen  => c_tx_arp_packet.hlen,
+                                                   plen  => c_tx_arp_packet.plen,
+                                                   oper  => TO_UVEC(c_arp_oper_reply, c_arp_oper_w),  -- reply
+                                                   sha   => c_tx_arp_packet.tha,                      --  \/
+                                                   spa   => c_tx_arp_packet.tpa,                      --  /\  \/
+                                                   tha   => c_tx_arp_packet.sha,                      -- /  \ /\ 
+                                                   tpa   => c_tx_arp_packet.spa);                     --     /  \
+                                                   
+  CONSTANT c_arp_htype              : NATURAL := 1;                   -- Hardware type, 1=ethernet
+  CONSTANT c_arp_ptype              : NATURAL := c_eth_type_ip;       -- Protocol type, do ARP for IPv4
+  CONSTANT c_arp_hlen               : NATURAL := c_eth_mac_addr_len;  -- Hardware length = 6
+  CONSTANT c_arp_plen               : NATURAL := c_ip_addr_len;       -- Protocol length = 4
+  CONSTANT c_arp_oper               : NATURAL := 1;                   -- Operator, 1=request, (2=reply)
+ 
+  -- . ICMP header
+  CONSTANT c_tx_icmp_header      : t_icmp_header := (msg_type => TO_UVEC(c_icmp_msg_type_request, c_icmp_msg_type_w),  -- ping request
+                                                     code     => TO_UVEC(c_icmp_code,     c_icmp_code_w),
+                                                     checksum => TO_UVEC(c_icmp_checksum, c_icmp_checksum_w),          -- init value
+                                                     id       => TO_UVEC(c_icmp_id,       c_icmp_id_w),
+                                                     sequence => TO_UVEC(c_icmp_sequence, c_icmp_sequence_w));
+  CONSTANT c_exp_icmp_header     : t_icmp_header := (msg_type => TO_UVEC(c_icmp_msg_type_reply, c_icmp_msg_type_w), -- ping reply
+                                                     code     => c_tx_icmp_header.code,
+                                                     checksum => c_tx_icmp_header.checksum,                         -- init value
+                                                     id       => c_tx_icmp_header.id,
+                                                     sequence => c_tx_icmp_header.sequence);
+  
+  -- . UDP header
+  CONSTANT c_dut_udp_port_ctrl   : NATURAL := 11;                  -- ETH demux UDP for control
+  CONSTANT c_dut_udp_port_st0    : NATURAL := 57;                  -- ETH demux UDP port 0
+  CONSTANT c_dut_udp_port_st1    : NATURAL := 58;                  -- ETH demux UDP port 1
+  CONSTANT c_dut_udp_port_st2    : NATURAL := 59;                  -- ETH demux UDP port 2
+  CONSTANT c_dut_udp_port_en     : NATURAL := 16#10000#;           -- ETH demux UDP port enable bit 16
+  CONSTANT c_lcu_udp_port        : NATURAL := 10;                  -- UDP port used for src_port
+  CONSTANT c_dut_udp_port_st     : NATURAL := c_dut_udp_port_st0;  -- UDP port used for dst_port
+  CONSTANT c_tb_udp_total_length : NATURAL := c_udp_total_length + c_tb_nof_data;
+  CONSTANT c_tx_udp_header       : t_udp_header := (src_port     => TO_UVEC(c_lcu_udp_port,        c_udp_port_w),
+                                                    dst_port     => TO_UVEC(c_dut_udp_port_ctrl,   c_udp_port_w),       -- or use c_dut_udp_port_st#
+                                                    total_length => TO_UVEC(c_tb_udp_total_length, c_udp_total_length_w),
+                                                    checksum     => TO_UVEC(c_udp_checksum,        c_udp_checksum_w));  -- init value
+
+  CONSTANT c_exp_udp_header      : t_udp_header := (src_port     => c_tx_udp_header.dst_port,      -- \/
+                                                    dst_port     => c_tx_udp_header.src_port,      -- /\
+                                                    total_length => c_tx_udp_header.total_length,  -- =
+                                                    checksum     => c_tx_udp_header.checksum);     -- init value
+
+  SIGNAL tx_total_header     : t_eth_total_header;  -- transmitted packet header
+  SIGNAL exp_total_header    : t_eth_total_header;  -- expected received packet header
+  
+  -- ETH control
+  CONSTANT c_dut_control_rx_en   : NATURAL := 2**c_eth_mm_reg_control_bi.rx_en;
+  CONSTANT c_dut_control_tx_en   : NATURAL := 2**c_eth_mm_reg_control_bi.tx_en;
+    
+  -- 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 st_rst              : STD_LOGIC := '1';  -- reset synchronous with st_clk
+  SIGNAL mm_clk              : STD_LOGIC;         -- memory-mapped bus clock
+  SIGNAL mm_rst              : STD_LOGIC := '1';  -- reset synchronous with mm_clk
+  
+  -- ETH TSE interface
+  SIGNAL dut_tse_init        : STD_LOGIC := '1';
+  SIGNAL dut_eth_init        : STD_LOGIC := '1';
+  SIGNAL eth_tse_miso        : t_mem_miso;
+  SIGNAL eth_tse_mosi        : t_mem_mosi;
+  SIGNAL eth_psc_access      : STD_LOGIC;
+  SIGNAL eth_txp             : STD_LOGIC;
+  SIGNAL eth_rxp             : STD_LOGIC;
+  SIGNAL eth_led             : t_tse_led;
+  
+  -- ETH MM registers interface
+  SIGNAL eth_reg_miso        : t_mem_miso;
+  SIGNAL eth_reg_mosi        : t_mem_mosi;
+  SIGNAL eth_reg_interrupt   : STD_LOGIC;
+  
+  SIGNAL eth_mm_reg_control  : t_eth_mm_reg_control;
+  SIGNAL eth_mm_reg_status   : t_eth_mm_reg_status;
+  
+  SIGNAL eth_ram_miso        : t_mem_miso;
+  SIGNAL eth_ram_mosi        : t_mem_mosi;
+  
+  -- ETH UDP data path interface
+  SIGNAL udp_tx_sosi_arr     : t_dp_sosi_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  SIGNAL udp_tx_siso_arr     : t_dp_siso_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  SIGNAL udp_rx_siso_arr     : t_dp_siso_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  SIGNAL udp_rx_sosi_arr     : t_dp_sosi_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  
+  
+  -- LCU TSE interface
+  SIGNAL lcu_init            : STD_LOGIC := '1';
+  SIGNAL lcu_tse_miso        : t_mem_miso;
+  SIGNAL lcu_tse_mosi        : t_mem_mosi;
+  SIGNAL lcu_psc_access      : STD_LOGIC;
+  SIGNAL lcu_tx_en           : STD_LOGIC := '1';
+  SIGNAL lcu_tx_siso         : t_dp_siso;
+  SIGNAL lcu_tx_sosi         : t_dp_sosi;
+  SIGNAL lcu_tx_mac_in       : t_tse_tx_mac;
+  SIGNAL lcu_tx_mac_out      : t_tse_tx_mac;
+  SIGNAL lcu_rx_sosi         : t_dp_sosi;
+  SIGNAL lcu_rx_siso         : t_dp_siso;
+  SIGNAL lcu_rx_mac_out      : t_tse_rx_mac;
+  SIGNAL lcu_txp             : STD_LOGIC;
+  SIGNAL lcu_rxp             : STD_LOGIC;
+  SIGNAL lcu_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;
+
+  p_reset : PROCESS
+  BEGIN
+    -- reset release
+    st_rst <= '1';
+    mm_rst <= '1';
+    FOR I IN 0 TO 9 LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+    mm_rst <= '0';
+    WAIT UNTIL rising_edge(st_clk);
+    st_rst <= '0';
+    FOR I IN 0 TO 9 LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+    WAIT;
+  END PROCESS;
+  
+  -- Use signal to leave unused fields 'X'
+  tx_total_header.eth  <= c_tx_eth_header;
+  tx_total_header.arp  <= c_tx_arp_packet;
+  tx_total_header.ip   <= c_tx_ip_header;
+  tx_total_header.icmp <= c_tx_icmp_header;
+  tx_total_header.udp  <= c_tx_udp_header;
+  
+  exp_total_header.eth  <= c_exp_eth_header;
+  exp_total_header.arp  <= c_exp_arp_packet;
+  exp_total_header.ip   <= c_exp_ip_header;
+  exp_total_header.icmp <= c_exp_icmp_header;
+  exp_total_header.udp  <= c_exp_udp_header;
+  
+  ------------------------------------------------------------------------------
+  -- DUT
+  ------------------------------------------------------------------------------
+  p_tse_setup : PROCESS
+  BEGIN
+    dut_tse_init <= '1';
+    eth_tse_mosi.wr <= '0';
+    eth_tse_mosi.rd <= '0';
+    -- Wait for ETH init
+    WHILE dut_eth_init='1' LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+    -- Setup the TSE MAC
+    proc_tse_setup(c_promis_en, c_tse_tx_fifo_depth, c_tse_rx_fifo_depth, c_tx_ready_latency,
+                   c_dut_src_mac, eth_psc_access,
+                   mm_clk, eth_tse_miso, eth_tse_mosi);
+    dut_tse_init <= '0';
+    WAIT;
+  END PROCESS;
+  
+  
+  p_eth_control : PROCESS
+    VARIABLE v_eth_control_word : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  BEGIN
+    -- ETH setup
+    dut_eth_init <= '1';
+    eth_reg_mosi.wr <= '0';
+    eth_reg_mosi.rd <= '0';
+    eth_ram_mosi.address <= (OTHERS=>'0');
+    eth_ram_mosi.wr      <= '0';
+    eth_ram_mosi.rd      <= '0';
+    
+    -- Wait for reset release
+    WHILE mm_rst='1' LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+    
+    -- Setup the DEMUX UDP
+    proc_mem_mm_bus_wr(c_eth_reg_demux_wi+0, c_dut_udp_port_en+c_dut_udp_port_st0, mm_clk, eth_reg_miso, eth_reg_mosi);  -- UDP port stream 0
+    proc_mem_mm_bus_wr(c_eth_reg_demux_wi+1, c_dut_udp_port_en+c_dut_udp_port_st1, mm_clk, eth_reg_miso, eth_reg_mosi);  -- UDP port stream 1
+    proc_mem_mm_bus_wr(c_eth_reg_demux_wi+2, c_dut_udp_port_en+c_dut_udp_port_st2, mm_clk, eth_reg_miso, eth_reg_mosi);  -- UDP port stream 2
+    proc_mem_mm_bus_rd(c_eth_reg_demux_wi+0,                                       mm_clk, eth_reg_miso, eth_reg_mosi);
+    proc_mem_mm_bus_rd(c_eth_reg_demux_wi+1,                                       mm_clk, eth_reg_miso, eth_reg_mosi);
+    proc_mem_mm_bus_rd(c_eth_reg_demux_wi+2,                                       mm_clk, eth_reg_miso, eth_reg_mosi);
+    
+    -- Setup the RX config
+    proc_mem_mm_bus_wr(c_eth_reg_config_wi+0, c_dut_src_mac_lo,                    mm_clk, eth_reg_miso, eth_reg_mosi);  -- control MAC address lo word
+    proc_mem_mm_bus_wr(c_eth_reg_config_wi+1, c_dut_src_mac_hi,                    mm_clk, eth_reg_miso, eth_reg_mosi);  -- control MAC address hi halfword
+    proc_mem_mm_bus_wr(c_eth_reg_config_wi+2, c_dut_ip_addr,                       mm_clk, eth_reg_miso, eth_reg_mosi);  -- control IP address
+    proc_mem_mm_bus_wr(c_eth_reg_config_wi+3, c_dut_udp_port_ctrl,                 mm_clk, eth_reg_miso, eth_reg_mosi);  -- control UDP port
+    -- Enable RX
+    proc_mem_mm_bus_wr(c_eth_reg_control_wi+0, c_dut_control_rx_en,                mm_clk, eth_reg_miso, eth_reg_mosi);  -- control rx en
+    dut_eth_init <= '0';
+    
+    -- Wait for TSE init
+    WHILE dut_tse_init='1' LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+    
+    -- Response control
+    WHILE TRUE LOOP
+      eth_mm_reg_status  <= c_eth_mm_reg_status_rst;
+      eth_mm_reg_control <= c_eth_mm_reg_control_rst;
+      -- wait for rx_avail interrupt
+      IF eth_reg_interrupt='1' THEN
+        -- read status register to read the status
+        proc_mem_mm_bus_rd(c_eth_reg_status_wi+0, mm_clk, eth_reg_miso, eth_reg_mosi);  -- read result available in eth_mm_reg_status
+        proc_mem_mm_bus_rd_latency(c_mem_reg_rd_latency, mm_clk);
+        eth_mm_reg_status <= func_eth_mm_reg_status(eth_reg_miso.rddata);
+        WAIT UNTIL rising_edge(mm_clk);
+        -- write status register to acknowledge the interrupt
+        proc_mem_mm_bus_wr(c_eth_reg_status_wi+0, 0, mm_clk, eth_reg_miso, eth_reg_mosi);  -- void value
+        -- prepare control register for response
+        IF c_tb_reply_payload=TRUE THEN
+          eth_mm_reg_control.tx_nof_words <= INCR_UVEC(eth_mm_reg_status.rx_nof_words, -1);  -- -1 to skip the CRC word for the response
+          eth_mm_reg_control.tx_empty     <= eth_mm_reg_status.rx_empty;
+        ELSE
+          eth_mm_reg_control.tx_nof_words <= TO_UVEC(c_eth_total_header_nof_words, c_eth_max_frame_nof_words_w);
+          eth_mm_reg_control.tx_empty     <= TO_UVEC(0, c_eth_empty_w);
+        END IF;
+        eth_mm_reg_control.tx_en <= '1';
+        eth_mm_reg_control.rx_en <= '1';
+        WAIT UNTIL rising_edge(mm_clk);
+        -- wait for interrupt removal due to status register read access
+        WHILE eth_reg_interrupt='1' LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+        -- write control register to enable tx
+        IF c_tb_reply_payload=TRUE THEN
+          -- . copy the received payload to the response payload (overwrite part of the default response header in case of raw ETH)
+          FOR I IN func_tb_tse_header_size(g_data_type) TO TO_UINT(eth_mm_reg_control.tx_nof_words)-1 LOOP
+            proc_mem_mm_bus_rd(c_eth_ram_rx_offset+I, mm_clk, eth_ram_miso, eth_ram_mosi);
+            proc_mem_mm_bus_rd_latency(c_mem_ram_rd_latency, mm_clk);
+            proc_mem_mm_bus_wr(c_eth_ram_tx_offset+I, TO_SINT(eth_ram_miso.rddata(c_word_w-1 DOWNTO 0)), mm_clk, eth_ram_miso, eth_ram_mosi);
+          END LOOP;
+        --ELSE
+          -- . only reply header
+        END IF;
+        v_eth_control_word := func_eth_mm_reg_control(eth_mm_reg_control);
+        proc_mem_mm_bus_wr(c_eth_reg_control_wi+0, TO_UINT(v_eth_control_word),  mm_clk, eth_reg_miso, eth_reg_mosi);
+        -- write continue register to make the ETH module continue
+        proc_mem_mm_bus_wr(c_eth_reg_continue_wi, 0, mm_clk, eth_reg_miso, eth_reg_mosi);  -- void value
+      END IF;
+      WAIT UNTIL rising_edge(mm_clk);
+    END LOOP;
+    
+    WAIT;
+  END PROCESS;
+
+  ------------------------------------------------------------------------------
+  -- LCU
+  ------------------------------------------------------------------------------
+  p_lcu_setup : PROCESS
+  BEGIN
+    lcu_init <= '1';
+    lcu_tse_mosi.wr <= '0';
+    lcu_tse_mosi.rd <= '0';
+    -- Wait for reset release
+    WHILE mm_rst='1' LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+    -- Setup the LCU TSE MAC
+    proc_tse_setup(c_promis_en, c_tse_tx_fifo_depth, c_tse_rx_fifo_depth, c_tx_ready_latency,
+                   c_lcu_src_mac, lcu_psc_access,
+                   mm_clk, lcu_tse_miso, lcu_tse_mosi);
+    -- Wait for DUT init done
+    WHILE dut_tse_init/='0' LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+    lcu_init <= '0';
+    WAIT;
+  END PROCESS;
+  
+  p_lcu_transmitter : PROCESS
+  BEGIN
+    -- . Avalon ST
+    lcu_tx_sosi.data  <= (OTHERS=>'0');
+    lcu_tx_sosi.valid <= '0';
+    lcu_tx_sosi.sop   <= '0';
+    lcu_tx_sosi.eop   <= '0';
+    lcu_tx_sosi.empty <= (OTHERS=>'0');
+    lcu_tx_sosi.err   <= (OTHERS=>'0');
+    -- . MAC specific
+    lcu_tx_mac_in.crc_fwd <= '0';  -- when '0' then TSE MAC generates the TX CRC field
+
+    WHILE lcu_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;
+
+--     proc_tse_tx_packet(tx_total_header,  100, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+    FOR I IN 0 TO 40 LOOP
+      proc_tse_tx_packet(tx_total_header,    I, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+      --FOR J IN 0 TO 9 LOOP WAIT UNTIL rising_edge(st_clk); END LOOP;
+    END LOOP;
+--     proc_tse_tx_packet(tx_total_header,  104, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header,  105, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header, 1472, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header, 1500, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header,  101, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header, 1000, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header,  102, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header, 1000, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header,  103, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header,  104, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+--     proc_tse_tx_packet(tx_total_header,  105, g_data_type, c_tx_ready_latency, c_nof_tx_not_valid, st_clk, lcu_tx_en, lcu_tx_siso, lcu_tx_sosi);
+
+    WAIT;
+  END PROCESS;
+
+  
+  p_lcu_receiver : PROCESS
+  BEGIN
+    -- . Avalon ST
+    lcu_rx_siso <= c_dp_siso_hold;
+
+    WHILE lcu_init/='0' LOOP WAIT UNTIL rising_edge(st_clk); END LOOP;
+
+    -- Verification of multiple rx packets is only supported when all packets
+    -- are of the same g_data_type, because the rx process can only support
+    -- one expected result. The proc_tse_rx_packet does not (yet) interpret the
+    -- actually received packet, it relies on the preset expected total_header.
+    
+    -- Receive forever
+    WHILE TRUE LOOP
+      proc_tse_rx_packet(exp_total_header, g_data_type, st_clk, lcu_rx_sosi, lcu_rx_siso);
+    END LOOP;
+
+    WAIT;
+  END PROCESS;
+
+  -- Wire ethernet cable between lcu and dut
+  eth_rxp <= TRANSPORT lcu_txp AFTER cable_delay;
+  lcu_rxp <= TRANSPORT eth_txp AFTER cable_delay;
+  
+  gen_udp_rx_siso_rdy: FOR i IN 0 TO c_eth_nof_udp_ports-1 GENERATE
+    udp_rx_siso_arr(i).ready <= '1';
+  END GENERATE;
+  
+  dut : ENTITY work.eth
+  GENERIC MAP (
+    g_cross_clock_domain => c_cross_clock_domain
+  )
+  PORT MAP (
+    -- Clocks and reset
+    mm_rst            => mm_rst,
+    mm_clk            => mm_clk,
+    eth_clk           => eth_clk,
+    st_rst            => st_rst,
+    st_clk            => st_clk,
+    -- UDP transmit interface
+    -- . ST sink
+    udp_tx_snk_in_arr  => udp_tx_sosi_arr,
+    udp_tx_snk_out_arr => udp_tx_siso_arr,
+    -- UDP receive interface
+    -- . ST source
+    udp_rx_src_in_arr  => udp_rx_siso_arr,
+    udp_rx_src_out_arr => udp_rx_sosi_arr,
+    -- Control Memory Mapped Slaves
+    tse_sla_in        => eth_tse_mosi,
+    tse_sla_out       => eth_tse_miso,
+    reg_sla_in        => eth_reg_mosi,
+    reg_sla_out       => eth_reg_miso,
+    reg_sla_interrupt => eth_reg_interrupt,
+    ram_sla_in        => eth_ram_mosi,
+    ram_sla_out       => eth_ram_miso,
+    -- PHY interface
+    eth_txp           => eth_txp,
+    eth_rxp           => eth_rxp,
+    -- LED interface
+    tse_led           => eth_led
+  );
+
+  lcu : ENTITY work.tse
+  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      => lcu_tse_mosi,
+    mm_sla_out     => lcu_tse_miso,
+    
+    -- MAC transmit interface
+    -- . ST sink
+    tx_snk_in      => lcu_tx_sosi,
+    tx_snk_out     => lcu_tx_siso,
+    -- . MAC specific
+    tx_mac_in      => lcu_tx_mac_in,
+    tx_mac_out     => lcu_tx_mac_out,
+    
+    -- MAC receive interface
+    -- . ST Source
+    rx_src_in      => lcu_rx_siso,
+    rx_src_out     => lcu_rx_sosi,
+    -- . MAC specific
+    rx_mac_out     => lcu_rx_mac_out,
+
+    -- PHY interface
+    eth_txp        => lcu_txp,
+    eth_rxp        => lcu_rxp,
+
+    tse_led        => lcu_led
+  );
+
+END tb;
diff --git a/libraries/io/eth/tb/vhdl/tb_eth_udp_offload.vhd b/libraries/io/eth/tb/vhdl/tb_eth_udp_offload.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..a4067eb15d17a857304b0ea4080fdfb340519cb4
--- /dev/null
+++ b/libraries/io/eth/tb/vhdl/tb_eth_udp_offload.vhd
@@ -0,0 +1,486 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright (C) 2012
+-- 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, tech_tse_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 common_lib.common_str_pkg.ALL;
+USE common_lib.common_lfsr_sequences_pkg.ALL;
+USE common_lib.tb_common_pkg.ALL;
+USE dp_lib.dp_stream_pkg.ALL;
+USE dp_lib.tb_dp_pkg.ALL;
+USE tech_tse_lib.tech_tse_pkg.ALL;
+USE tech_tse_lib.tb_tech_tse_pkg.ALL;
+USE WORK.eth_layers_pkg.ALL;
+USE WORK.eth_pkg.ALL;
+
+ENTITY tb_eth_udp_offload IS
+  GENERIC (
+    g_data_w       : NATURAL := c_word_w;
+    g_symbol_w     : NATURAL := 8;     
+    g_in_en        : t_dp_flow_control_enum := e_random  
+  );
+END tb_eth_udp_offload;
+
+
+ARCHITECTURE tb OF tb_eth_udp_offload IS
+
+  -- tb default
+  CONSTANT c_rl               : NATURAL := 1;
+  CONSTANT c_pulse_active     : NATURAL := 1;
+  CONSTANT c_pulse_period     : NATURAL := 7;
+  
+  -- tb specific
+  CONSTANT c_nof_repeat       : NATURAL := 10;
+  CONSTANT c_bsn_w            : NATURAL := 16;
+  CONSTANT c_symbol_init      : NATURAL := 0;
+  CONSTANT c_symbol_mod       : INTEGER := 2**g_symbol_w;  -- used to avoid TO_UVEC warning for smaller g_symbol_w : "NUMERIC_STD.TO_UNSIGNED: vector truncated"
+  CONSTANT c_err_init         : NATURAL := 0;
+  CONSTANT c_sync_period      : NATURAL := 7;
+  CONSTANT c_sync_offset      : NATURAL := 2;
+  
+  -- clock and reset
+  CONSTANT c_mm_clk_period    : TIME := 20 ns;
+  CONSTANT c_st_clk_period    : TIME := 5 ns;
+  CONSTANT c_eth_clk_period   : TIME := 8 ns;  -- 125 MHz
+
+  -- ETH CONSTANTS
+  -- ===========================================================================================================================================================
+
+  -- Payload user data
+  CONSTANT c_tb_nof_data        : NATURAL := 1440;  -- nof UDP user data, nof ping padding data. NOTE: non-multiples of g_data_w/g_symbol_w not supported as dp_packet_enc/dec do not support encoding/decoding empty
+  CONSTANT c_tb_ip_nof_data     : NATURAL := c_udp_header_len + c_tb_nof_data; -- nof IP data,
+
+  -- Headers
+  -- . Ethernet header
+  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_lcu_src_mac        : STD_LOGIC_VECTOR(c_eth_mac_slv'RANGE) := X"10FA01020300";
+  CONSTANT c_dut_src_mac        : STD_LOGIC_VECTOR(c_eth_mac_slv'RANGE) := X"123456789ABC";  -- = 12-34-56-78-9A-BC
+  CONSTANT c_dut_src_mac_hi     : NATURAL := TO_UINT(c_dut_src_mac(c_eth_mac_addr_w-1 DOWNTO c_word_w));
+  CONSTANT c_dut_src_mac_lo     : NATURAL := TO_UINT(c_dut_src_mac(        c_word_w-1 DOWNTO        0));
+   
+  CONSTANT c_tx_eth_header      : t_eth_header := (word_align => c_word_align,
+                                                   dst_mac    => c_dut_src_mac,
+                                                   src_mac    => c_lcu_src_mac,
+                                                   eth_type   => TO_UVEC(c_eth_type_ip, c_eth_type_w)); --TO_UVEC(c_dut_ethertype, c_eth_type_w));
+  -- . IP header
+  CONSTANT c_lcu_ip_addr        : NATURAL := 16#05060708#;  -- = 05:06:07:08
+  CONSTANT c_dut_ip_addr        : NATURAL := 16#01020304#;
+  CONSTANT c_tb_ip_total_length : NATURAL := c_ip_total_length + c_tb_ip_nof_data;
+                                             -- support only ping protocol or UDP protocol over IP
+  CONSTANT c_tb_ip_protocol     : NATURAL := c_ip_protocol_udp; --sel_a_b(g_data_type-c_tb_tse_data_type_ping, c_ip_protocol_udp, c_ip_protocol_icmp);
+  
+  CONSTANT c_tx_ip_header       : t_ip_header := (version         => TO_UVEC(c_ip_version,         c_ip_version_w),
+                                                  header_length   => TO_UVEC(c_ip_header_length,   c_ip_header_length_w),
+                                                  services        => TO_UVEC(c_ip_services,        c_ip_services_w),
+                                                  total_length    => TO_UVEC(c_tb_ip_total_length, c_ip_total_length_w),
+                                                  identification  => TO_UVEC(c_ip_identification,  c_ip_identification_w),
+                                                  flags           => TO_UVEC(c_ip_flags,           c_ip_flags_w),
+                                                  fragment_offset => TO_UVEC(c_ip_fragment_offset, c_ip_fragment_offset_w),
+                                                  time_to_live    => TO_UVEC(c_ip_time_to_live,    c_ip_time_to_live_w),
+                                                  protocol        => TO_UVEC(c_tb_ip_protocol,     c_ip_protocol_w),
+                                                  header_checksum => TO_UVEC(c_ip_header_checksum, c_ip_header_checksum_w),  -- init value (or try 0xEBBD = 60349)
+                                                  src_ip_addr     => TO_UVEC(c_lcu_ip_addr,        c_ip_addr_w),
+                                                  dst_ip_addr     => TO_UVEC(c_dut_ip_addr,        c_ip_addr_w));
+
+  -- . UDP header
+  CONSTANT c_dut_udp_port_ctrl   : NATURAL := 11;                  -- ETH demux UDP for control
+  CONSTANT c_dut_udp_port_st0    : NATURAL := 57;                  -- ETH demux UDP port 0
+  CONSTANT c_dut_udp_port_st1    : NATURAL := 58;                  -- ETH demux UDP port 1
+  CONSTANT c_dut_udp_port_st2    : NATURAL := 59;                  -- ETH demux UDP port 2
+  CONSTANT c_dut_udp_port_en     : NATURAL := 16#10000#;           -- ETH demux UDP port enable bit 16
+  CONSTANT c_lcu_udp_port        : NATURAL := 10;                  -- UDP port used for src_port
+  CONSTANT c_dut_udp_port_st     : NATURAL := c_dut_udp_port_st0;  -- UDP port used for dst_port
+  CONSTANT c_tb_udp_total_length : NATURAL := c_udp_total_length + c_tb_nof_data;
+  CONSTANT c_tx_udp_header       : t_udp_header := (src_port     => TO_UVEC(c_lcu_udp_port,        c_udp_port_w),
+                                                    dst_port     => TO_UVEC(c_dut_udp_port_st,   c_udp_port_w),       -- or use c_dut_udp_port_ctrl
+                                                    total_length => TO_UVEC(c_tb_udp_total_length, c_udp_total_length_w),
+                                                    checksum     => TO_UVEC(c_udp_checksum,        c_udp_checksum_w));  -- init value
+ 
+  CONSTANT c_total_hdr_slv       : STD_LOGIC_VECTOR(c_eth_total_header_nof_words*c_word_w-1 DOWNTO 0) := c_tx_eth_header.word_align     &
+                                                                                                         c_tx_eth_header.dst_mac        &
+                                                                                                         c_tx_eth_header.src_mac        &
+                                                                                                         c_tx_eth_header.eth_type       &
+                                                                                                         c_tx_ip_header.version         &
+                                                                                                         c_tx_ip_header.header_length   &
+                                                                                                         c_tx_ip_header.services        &
+                                                                                                         c_tx_ip_header.total_length    &
+                                                                                                         c_tx_ip_header.identification  &
+                                                                                                         c_tx_ip_header.flags           &
+                                                                                                         c_tx_ip_header.fragment_offset &
+                                                                                                         c_tx_ip_header.time_to_live    &
+                                                                                                         c_tx_ip_header.protocol        &
+                                                                                                         c_tx_ip_header.header_checksum &
+                                                                                                         c_tx_ip_header.src_ip_addr     &
+                                                                                                         c_tx_ip_header.dst_ip_addr     &
+                                                                                                         c_tx_udp_header.src_port       & 
+                                                                                                         c_tx_udp_header.dst_port       &
+                                                                                                         c_tx_udp_header.total_length   &
+                                                                                                         c_tx_udp_header.checksum;
+
+  -- ===========================================================================================================================================================
+
+  -- TSE constants
+  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
+
+  -- ETH control
+  CONSTANT c_dut_control_rx_en   : NATURAL := 2**c_eth_mm_reg_control_bi.rx_en;
+
+  -- ETH TSE interface
+  SIGNAL eth_psc_access      : STD_LOGIC;
+
+  SIGNAL tb_eth_hdr           : t_eth_header := c_tx_eth_header;
+  SIGNAL tb_ip_hdr            : t_ip_header  := c_tx_ip_header;
+  SIGNAL tb_udp_hdr           : t_udp_header := c_tx_udp_header;
+
+  SIGNAL eth_clk              : STD_LOGIC := '0';  -- tse reference clock
+  SIGNAL mm_clk               : STD_LOGIC := '0';
+  SIGNAL mm_rst               : STD_LOGIC;
+  SIGNAL st_rst               : STD_LOGIC;
+  SIGNAL st_clk               : STD_LOGIC := '0';
+
+  SIGNAL tb_end               : STD_LOGIC := '0';
+
+  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 in_en                : STD_LOGIC := '1';
+  
+  -- tb verify
+  SIGNAL verify_en            : STD_LOGIC := '0';
+  SIGNAL verify_done          : STD_LOGIC := '0';
+  
+  SIGNAL prev_udp_rx_ready    : STD_LOGIC_VECTOR(0 TO c_rl);
+  SIGNAL prev_udp_rx_data     : STD_LOGIC_VECTOR(g_data_w-1 DOWNTO 0);
+
+  SIGNAL out_gap              : STD_LOGIC := '1';
+
+  -- dp_hdr_insert/remove signals
+  SIGNAL reg_hdr_mosi         : t_mem_mosi;
+  SIGNAL ram_hdr_mosi         : t_mem_mosi;
+  SIGNAL ram_hdr_miso         : t_mem_miso; 
+
+  -- ETH TSE interface
+  SIGNAL eth_tse_miso        : t_mem_miso;
+  SIGNAL eth_tse_mosi        : t_mem_mosi;
+  SIGNAL eth_serial_loopback : STD_LOGIC;
+   
+  -- ETH UDP data path from tx generation to rx verification
+  SIGNAL udp_tx_sosi         : t_dp_sosi;
+  SIGNAL udp_tx_siso         : t_dp_siso;
+
+  SIGNAL udp_tx_pkt_sosi     : t_dp_sosi;
+  SIGNAL udp_tx_pkt_siso     : t_dp_siso;
+
+  SIGNAL udp_tx_hdr_pkt_sosi_arr : t_dp_sosi_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  SIGNAL udp_tx_hdr_pkt_siso_arr : t_dp_siso_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+
+  SIGNAL udp_rx_frame_pkt_siso_arr : t_dp_siso_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+  SIGNAL udp_rx_frame_pkt_sosi_arr : t_dp_sosi_arr(c_eth_nof_udp_ports-1 DOWNTO 0);
+
+  SIGNAL udp_rx_pkt_siso     : t_dp_siso;
+  SIGNAL udp_rx_pkt_sosi     : t_dp_sosi;
+
+  SIGNAL udp_rx_siso         : t_dp_siso;
+  SIGNAL udp_rx_sosi         : t_dp_sosi;
+
+  -- ETH MM registers interface
+  SIGNAL eth_reg_miso        : t_mem_miso;
+  SIGNAL eth_reg_mosi        : t_mem_mosi;
+  SIGNAL eth_reg_interrupt   : STD_LOGIC;
+
+  SIGNAL eth_ram_miso        : t_mem_miso;
+  SIGNAL eth_ram_mosi        : t_mem_mosi;
+
+  SIGNAL dut_eth_init        : STD_LOGIC := '1';
+  SIGNAL dut_tse_init        : STD_LOGIC := '1';  
+
+BEGIN
+
+  mm_clk <= (NOT mm_clk) OR tb_end AFTER c_mm_clk_period/2;
+  mm_rst <= '1', '0' AFTER c_mm_clk_period*20;
+
+  st_clk <= (NOT st_clk) OR tb_end AFTER c_st_clk_period/2;
+  st_rst <= '1', '0' AFTER c_st_clk_period*7;
+
+  eth_clk <= NOT eth_clk OR tb_end AFTER c_eth_clk_period/2;  -- TSE reference clock
+  
+  random_0 <= func_common_random(random_0) WHEN rising_edge(st_clk);
+  random_1 <= func_common_random(random_1) WHEN rising_edge(st_clk);
+  
+  proc_common_gen_duty_pulse(c_pulse_active, c_pulse_period,   '1', st_rst, st_clk, pulse_en, pulse_0);
+  proc_common_gen_duty_pulse(c_pulse_active, c_pulse_period+1, '1', st_rst, st_clk, pulse_en, pulse_1);
+
+  ------------------------------------------------------------------------------
+  -- STREAM CONTROL
+  ------------------------------------------------------------------------------
+  
+  in_en          <= '1'                     WHEN g_in_en=e_active      ELSE
+                    random_0(random_0'HIGH) WHEN g_in_en=e_random      ELSE
+                    pulse_0                 WHEN g_in_en=e_pulse;
+                       
+   udp_rx_siso.ready <= '1';
+   udp_rx_siso.xon   <= '1';
+
+  ------------------------------------------------------------------------------
+  -- TSE SETUP
+  ------------------------------------------------------------------------------ 
+  p_tse_setup : PROCESS
+  BEGIN
+    dut_tse_init <= '1';
+    eth_tse_mosi.wr <= '0';
+    eth_tse_mosi.rd <= '0';
+     -- Wait for ETH init
+    WHILE dut_eth_init='1' LOOP WAIT UNTIL rising_edge(mm_clk); END LOOP;
+    -- Setup the TSE MAC
+    proc_tse_setup(c_promis_en, c_tse_tx_fifo_depth, c_tse_rx_fifo_depth, c_tx_ready_latency,
+                   c_dut_src_mac, eth_psc_access,
+                   mm_clk, eth_tse_miso, eth_tse_mosi);
+    dut_tse_init <= '0';
+    WAIT;
+  END PROCESS;
+
+
+  ------------------------------------------------------------------------------
+  -- DATA GENERATION
+  ------------------------------------------------------------------------------
+  
+  -- Generate data path input data
+  p_stimuli : PROCESS
+    VARIABLE v_sync      : STD_LOGIC := '0';
+    VARIABLE v_bsn       : STD_LOGIC_VECTOR(c_bsn_w-1 DOWNTO 0) := (OTHERS=>'0');
+    VARIABLE v_symbol    : NATURAL := c_symbol_init;
+    VARIABLE v_channel   : NATURAL := 1;
+    VARIABLE v_err       : NATURAL := c_err_init;
+
+    VARIABLE v_mm_wr_addr : NATURAL := 0;
+    VARIABLE v_mm_wr_hdr  : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  BEGIN
+    udp_tx_sosi  <= c_dp_sosi_rst;
+    reg_hdr_mosi <= c_mem_mosi_rst;    
+    ram_hdr_mosi <= c_mem_mosi_rst;    
+    eth_reg_mosi <= c_mem_mosi_rst;    
+
+    dut_eth_init <= '1';
+
+    proc_common_wait_until_low(mm_clk, mm_rst);
+    proc_common_wait_some_cycles(mm_clk, 5);
+
+    -- Set up the UDP demux
+    proc_mem_mm_bus_wr(c_eth_reg_demux_wi+0, c_dut_udp_port_en+c_dut_udp_port_st0, mm_clk, eth_reg_miso, eth_reg_mosi);  -- UDP port stream 0
+    proc_common_wait_some_cycles(mm_clk, 5);
+
+    -- Setup the RX config
+    proc_mem_mm_bus_wr(c_eth_reg_config_wi+0, c_dut_src_mac_lo,     mm_clk, eth_reg_miso, eth_reg_mosi);  -- control MAC address lo word
+    proc_mem_mm_bus_wr(c_eth_reg_config_wi+1, c_dut_src_mac_hi,     mm_clk, eth_reg_miso, eth_reg_mosi);  -- control MAC address hi halfword
+    proc_mem_mm_bus_wr(c_eth_reg_config_wi+2, c_dut_ip_addr,        mm_clk, eth_reg_miso, eth_reg_mosi);  -- control IP address
+    proc_mem_mm_bus_wr(c_eth_reg_config_wi+3, c_dut_udp_port_ctrl,  mm_clk, eth_reg_miso, eth_reg_mosi);  -- control UDP port
+    -- Enable RX
+    proc_mem_mm_bus_wr(c_eth_reg_control_wi+0, c_dut_control_rx_en, mm_clk, eth_reg_miso, eth_reg_mosi);  -- control rx en
+    dut_eth_init <= '0';
+
+    -- MM Stimuli: write HEADER to RAM
+    FOR i IN c_eth_total_header_nof_words DOWNTO 1 LOOP
+      -- Extract words from SLV from left to right
+      v_mm_wr_hdr := c_total_hdr_slv(i*c_word_w-1 DOWNTO i*c_word_w - c_word_w);
+      proc_mem_mm_bus_wr(v_mm_wr_addr, v_mm_wr_hdr, mm_clk, ram_hdr_mosi);
+      proc_common_wait_some_cycles(mm_clk, 5);
+
+      IF v_mm_wr_addr<c_eth_total_header_nof_words-1 THEN
+        v_mm_wr_addr := v_mm_wr_addr + 1;
+      END IF;
+    END LOOP;
+
+    -- Release the header onto the DP
+    proc_mem_mm_bus_wr(0, 1, mm_clk, reg_hdr_mosi);
+    
+    -- Begin of ST stimuli
+    proc_common_wait_until_low(st_clk, st_rst);
+    proc_common_wait_some_cycles(st_clk, 50);
+
+    FOR R IN 0 TO c_nof_repeat-1 LOOP
+      v_sync := sel_a_b(R MOD c_sync_period = c_sync_offset, '1', '0');  -- v_bsn = R
+      proc_dp_gen_block_data(c_rl, TRUE, g_data_w, g_symbol_w, v_symbol, 0, 0, c_tb_nof_data, v_channel, v_err, v_sync, TO_DP_BSN(R), st_clk, in_en, udp_tx_siso, udp_tx_sosi);
+      v_bsn     := INCR_UVEC(v_bsn, 1);
+      v_symbol  := (v_symbol + c_tb_nof_data) MOD c_symbol_mod;
+      v_err     := v_err + 1;
+      --proc_common_wait_some_cycles(st_clk, 10);               -- create gap between frames
+    END LOOP;
+    
+    -- End of stimuli
+    proc_common_wait_some_cycles(st_clk, 50);  -- depends on stream control
+    verify_done <= '1';
+    proc_common_wait_some_cycles(st_clk, 1);
+    verify_done <= '0';
+
+    -- Resync to MM clk
+    proc_common_wait_some_cycles(mm_clk, 5);
+
+    -- Read the stripped header via MM bus and print it in the transcript window
+    print_str("Reading stripped header from RAM:");
+    FOR i IN 0 TO c_eth_total_header_nof_words-1 LOOP
+      proc_mem_mm_bus_rd(i, mm_clk, ram_hdr_mosi);
+      proc_mem_mm_bus_rd_latency(c_mem_reg_rd_latency, mm_clk);
+      print_str("[" & time_to_str(now) & "] 0x" & slv_to_hex(ram_hdr_miso.rddata(c_word_w-1 DOWNTO 0)));
+    END LOOP;
+
+    proc_common_wait_some_cycles(st_clk, 10);
+    tb_end <= '1';
+    WAIT;
+  END PROCESS;
+ 
+  ------------------------------------------------------------------------------
+  -- DATA VERIFICATION
+  ------------------------------------------------------------------------------
+  
+  verify_en <= '1' WHEN rising_edge(st_clk) AND udp_rx_sosi.sop='1';  -- verify enable after first output sop
+  
+  -- SOSI control
+  proc_dp_verify_valid(c_rl, st_clk, verify_en, udp_rx_siso.ready, prev_udp_rx_ready, udp_rx_sosi.valid);        -- Verify that the output valid fits with the output ready latency
+  proc_dp_verify_gap_invalid(st_clk, udp_rx_sosi.valid, udp_rx_sosi.sop, udp_rx_sosi.eop, out_gap);                        -- Verify that the output valid is low between blocks from eop to sop
+    
+  -- SOSI data
+  -- . verify that the output is incrementing symbols, like the input stimuli
+  proc_dp_verify_symbols(c_rl, g_data_w, g_symbol_w, st_clk, verify_en, udp_rx_siso.ready, udp_rx_sosi.valid, udp_rx_sosi.eop, udp_rx_sosi.data(g_data_w-1 DOWNTO 0), udp_rx_sosi.empty, prev_udp_rx_data);
+  
+  ------------------------------------------------------------------------------
+  -- DUT
+  ------------------------------------------------------------------------------
+  dut : ENTITY work.eth
+  GENERIC MAP (
+    g_cross_clock_domain => TRUE 
+  )
+  PORT MAP (
+    -- Clocks and reset
+    mm_rst            => mm_rst,
+    mm_clk            => mm_clk,
+    eth_clk           => eth_clk,
+    st_rst            => st_rst,
+    st_clk            => st_clk,
+    -- UDP transmit interface
+    -- . ST sink
+    udp_tx_snk_in_arr  => udp_tx_hdr_pkt_sosi_arr,
+    udp_tx_snk_out_arr => udp_tx_hdr_pkt_siso_arr,
+    -- UDP receive interface
+    -- . ST source
+    udp_rx_src_in_arr  => udp_rx_frame_pkt_siso_arr,
+    udp_rx_src_out_arr => udp_rx_frame_pkt_sosi_arr,
+    -- Control Memory Mapped Slaves
+    tse_sla_in        => eth_tse_mosi,
+    tse_sla_out       => eth_tse_miso,
+    reg_sla_in        => eth_reg_mosi,
+    reg_sla_out       => eth_reg_miso,
+    reg_sla_interrupt => eth_reg_interrupt,
+    ram_sla_in        => eth_ram_mosi,
+    ram_sla_out       => eth_ram_miso,
+    -- PHY interface
+    eth_txp           => eth_serial_loopback,
+    eth_rxp           => eth_serial_loopback,
+    -- LED interface
+    tse_led           => OPEN
+  );
+
+  u_hdr_insert : ENTITY dp_lib.dp_hdr_insert
+  GENERIC MAP (
+    g_data_w        => g_data_w,
+    g_symbol_w      => g_symbol_w,
+    g_hdr_nof_words => c_eth_total_header_nof_words
+  )
+  PORT MAP (
+    mm_rst      => mm_rst,
+    mm_clk      => mm_clk, 
+                           
+    st_rst      => st_rst, 
+    st_clk      => st_clk, 
+
+    reg_mosi    => reg_hdr_mosi,                           
+    ram_mosi    => ram_hdr_mosi,
+                           
+    snk_out     => udp_tx_pkt_siso,
+    snk_in      => udp_tx_pkt_sosi,
+                           
+    src_in      => udp_tx_hdr_pkt_siso_arr(0),
+    src_out     => udp_tx_hdr_pkt_sosi_arr(0)
+  );       
+
+  u_dp_packet_enc : ENTITY dp_lib.dp_packet_enc
+  GENERIC MAP (
+    g_data_w => g_data_w
+  )
+  PORT MAP (
+    rst       => st_rst,
+    clk       => st_clk,
+
+    snk_out   => udp_tx_siso,
+    snk_in    => udp_tx_sosi,
+
+    src_in    => udp_tx_pkt_siso,
+    src_out   => udp_tx_pkt_sosi
+  );             
+                           
+  u_frame_remove : ENTITY dp_lib.dp_frame_remove
+  GENERIC MAP (            
+    g_data_w        => g_data_w,
+    g_symbol_w      => g_symbol_w,
+    g_hdr_nof_words => c_eth_total_header_nof_words,
+    g_tail_nof_words=> (c_eth_crc_len * g_symbol_w) / c_word_w
+  )                        
+  PORT MAP (               
+    mm_rst      => mm_rst, 
+    mm_clk      => mm_clk, 
+                           
+    st_rst      => st_rst, 
+    st_clk      => st_clk, 
+                           
+    snk_out     => udp_rx_frame_pkt_siso_arr(0),
+    snk_in      => udp_rx_frame_pkt_sosi_arr(0),
+
+    sla_in      => ram_hdr_mosi,
+    sla_out     => ram_hdr_miso,
+
+    src_in      => udp_rx_pkt_siso,
+    src_out     => udp_rx_pkt_sosi
+  );
+
+  u_dp_packet_dec : ENTITY dp_lib.dp_packet_dec
+  GENERIC MAP (
+    g_data_w => g_data_w
+  )
+  PORT MAP (
+    rst       => st_rst,
+    clk       => st_clk,
+
+    snk_out   => udp_rx_pkt_siso,
+    snk_in    => udp_rx_pkt_sosi,
+
+    src_in    => udp_rx_siso,
+    src_out   => udp_rx_sosi 
+  );
+  
+END tb;
diff --git a/libraries/io/eth/tb/vhdl/tb_tb_eth.vhd b/libraries/io/eth/tb/vhdl/tb_tb_eth.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..38fd02194b65992d4aaf760ca267c80860b37082
--- /dev/null
+++ b/libraries/io/eth/tb/vhdl/tb_tb_eth.vhd
@@ -0,0 +1,44 @@
+-------------------------------------------------------------------------------
+--
+-- 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, tech_tse_lib;
+USE IEEE.std_logic_1164.ALL;
+USE tech_tse_lib.tb_tech_tse_pkg.ALL;
+
+
+ENTITY tb_tb_eth IS
+END tb_tb_eth;
+
+
+ARCHITECTURE tb OF tb_tb_eth IS
+
+BEGIN
+
+  -- Try ETH settings : GENERIC MAP (g_data_type => )
+  
+  u_use_symbols : ENTITY work.tb_eth GENERIC MAP (c_tb_tse_data_type_symbols);
+  u_use_counter : ENTITY work.tb_eth GENERIC MAP (c_tb_tse_data_type_counter);
+  u_use_arp     : ENTITY work.tb_eth GENERIC MAP (c_tb_tse_data_type_arp    );
+  u_use_ping    : ENTITY work.tb_eth GENERIC MAP (c_tb_tse_data_type_ping   );
+  u_use_udp     : ENTITY work.tb_eth GENERIC MAP (c_tb_tse_data_type_udp    );
+  
+END tb;