diff --git a/libraries/io/ddr/hdllib.cfg b/libraries/io/ddr/hdllib.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..8f942740c32574e22f64227eebf66c6d62af2b14
--- /dev/null
+++ b/libraries/io/ddr/hdllib.cfg
@@ -0,0 +1,18 @@
+hdl_lib_name = io_ddr
+hdl_library_clause_name = io_ddr_lib
+hdl_lib_uses = common technology tech_ddr tech_ddr3 dp diag 
+hdl_lib_technology = 
+
+build_dir_sim = $HDL_BUILD_DIR
+build_dir_synth = $HDL_BUILD_DIR
+
+synth_files =
+    src/vhdl/io_ddr_driver.vhd
+    src/vhdl/io_ddr.vhd
+
+test_bench_files = 
+    src/vhdl/tb_io_ddr.vhd
+
+quartus_qip_files =
+
+
diff --git a/libraries/io/ddr/src/vhdl/io_ddr.vhd b/libraries/io/ddr/src/vhdl/io_ddr.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..5605fad0fa041a7b1d77164f878951edd8cb71ff
--- /dev/null
+++ b/libraries/io/ddr/src/vhdl/io_ddr.vhd
@@ -0,0 +1,317 @@
+--------------------------------------------------------------------------------
+--
+-- Copyright (C) 2014
+-- 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, technology_lib, tech_ddr_lib, common_lib, dp_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE technology_lib.technology_select_pkg.ALL;
+USE technology_lib.technology_pkg.ALL;
+USE tech_ddr_lib.tech_ddr_pkg.ALL;
+USE dp_lib.dp_stream_pkg.ALL;
+
+ENTITY io_ddr IS
+  GENERIC(
+    g_technology              : NATURAL := c_tech_select_default;
+    g_tech_ddr                : t_c_tech_ddr;
+    g_wr_data_w               : NATURAL := c_tech_ddr_ctlr.data_w;  
+    g_wr_use_ctrl             : BOOLEAN := FALSE;              -- TRUE to allow filling the WR FIFO (by disabling flush) after an EOP
+    g_wr_fifo_depth           : NATURAL := 128;                -- >=16 AND >c_tech_ddr_ctlr.maxburstsize                              , defined at read  side of write FIFO.
+    g_rd_fifo_depth           : NATURAL := 256;                -- >=16 AND >c_tech_ddr_ctlr.maxburstsize > c_ddr_ctrl_nof_latent_reads, defined at write side of read  FIFO. 
+    g_rd_data_w               : NATURAL := c_tech_ddr_ctlr.data_w;
+    g_flush_wr_fifo           : BOOLEAN := FALSE;              -- TRUE instantiates a dp_flush + controller to flush the write fifo when the driver is not ready to write
+    g_flush_sop               : BOOLEAN := FALSE;
+    g_flush_sop_channel       : BOOLEAN := FALSE;
+    g_flush_sop_start_channel : NATURAL := 0;
+    g_flush_nof_channels      : NATURAL := 0
+  );                      
+  PORT (                  
+    ctlr_ref_clk       : IN    STD_LOGIC;
+    ctlr_ref_rst       : IN    STD_LOGIC;
+
+    ctlr_gen_clk       : OUT   STD_LOGIC; -- Controller generated clock
+    ctlr_gen_rst       : OUT   STD_LOGIC;    
+    ctlr_gen_clk_2x    : OUT   STD_LOGIC; -- Controller generated double frequency clock
+    ctlr_gen_rst_2x    : OUT   STD_LOGIC; -- ctlr_gen_rst synchronized to ctlr_gen_clk_2x
+
+    ctlr_init_done     : OUT   STD_LOGIC;
+    ctlr_rdy           : OUT   STD_LOGIC;
+
+    dvr_start_addr     : IN    t_tech_ddr_addr;
+    dvr_end_addr       : IN    t_tech_ddr_addr;
+
+    dvr_en             : IN    STD_LOGIC;
+    dvr_wr_not_rd      : IN    STD_LOGIC;
+    dvr_done           : OUT   STD_LOGIC;
+
+    wr_clk             : IN    STD_LOGIC;
+    wr_rst             : IN    STD_LOGIC;
+
+    wr_sosi            : IN    t_dp_sosi;
+    wr_siso            : OUT   t_dp_siso;
+  
+    rd_sosi            : OUT   t_dp_sosi;
+    rd_siso            : IN    t_dp_siso;
+    
+    rd_clk             : IN    STD_LOGIC;
+    rd_rst             : IN    STD_LOGIC;
+
+    rd_fifo_usedw      : OUT   STD_LOGIC_VECTOR(ceil_log2(g_rd_fifo_depth * (g_tech_ddr.data_w/g_rd_data_w) )-1 DOWNTO 0);
+    
+    phy_in             : IN    t_tech_ddr_phy_in;
+    phy_io             : INOUT t_tech_ddr_phy_io;
+    phy_ou             : OUT   t_tech_ddr_phy_ou
+   );
+END io_ddr;
+
+
+ARCHITECTURE str OF io_ddr IS  
+ 
+  CONSTANT c_wr_fifo_depth : NATURAL := g_wr_fifo_depth * (g_tech_ddr.data_w/g_wr_data_w); -- Multiply fifo depth by the fifo's rd/wr width ratio to get write side depth
+
+  CONSTANT c_latency       : NATURAL := 1;
+
+  SIGNAL ctlr_burst        : STD_LOGIC; 
+  SIGNAL ctlr_burst_size   : STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0);
+  SIGNAL ctlr_address      : STD_LOGIC_VECTOR(ceil_log2(g_tech_ddr.cs_w-1) + g_tech_ddr.ba_w + g_tech_ddr.a_w + g_tech_ddr.a_col_w - g_tech_ddr.rsl_w-1 DOWNTO 0); -- ceil_log2(..-1) because the chip select lines are converted to a logical address
+  SIGNAL ctlr_rd_req       : STD_LOGIC;
+  SIGNAL ctlr_wr_req       : STD_LOGIC;
+
+  SIGNAL ctlr_ref_rst_n    : STD_LOGIC;
+  SIGNAL ctlr_gen_rst_n    : STD_LOGIC;
+
+  SIGNAL i_ctlr_gen_clk    : STD_LOGIC;
+  SIGNAL i_ctlr_gen_rst    : STD_LOGIC;
+  SIGNAL i_ctlr_gen_clk_2x : STD_LOGIC;
+  SIGNAL i_ctlr_init_done  : STD_LOGIC;
+  SIGNAL i_ctlr_rdy        : STD_LOGIC;
+  SIGNAL i_dvr_done        : STD_LOGIC;
+
+  SIGNAL dvr_cur_addr      : t_tech_ddr_addr;
+  SIGNAL dvr_flush         : STD_LOGIC := '0';
+ 
+  SIGNAL ctlr_wr_siso      : t_dp_siso := c_dp_siso_rdy;  -- default xon='1'
+  SIGNAL ctlr_wr_sosi      : t_dp_sosi;
+
+  SIGNAL flush_wr_siso     : t_dp_siso;
+  SIGNAL flush_wr_sosi     : t_dp_sosi;
+
+  SIGNAL ctlr_rd_siso      : t_dp_siso;
+  SIGNAL ctlr_rd_sosi      : t_dp_sosi;
+  
+  SIGNAL wr_fifo_usedw     : STD_LOGIC_VECTOR(ceil_log2(g_wr_fifo_depth)-1 DOWNTO 0);  -- read side depth of the write FIFO
+  
+BEGIN 
+
+  dvr_done <= i_dvr_done;
+
+  ctlr_ref_rst_n  <= NOT(ctlr_ref_rst);  
+  i_ctlr_gen_rst  <= NOT(ctlr_gen_rst_n);
+
+  ctlr_gen_clk    <= i_ctlr_gen_clk;
+  ctlr_gen_rst    <= i_ctlr_gen_rst;
+  ctlr_gen_clk_2x <= i_ctlr_gen_clk_2x;   
+  ctlr_rdy        <= i_ctlr_rdy;  
+  ctlr_init_done  <= i_ctlr_init_done;
+
+  u_wr_fifo : ENTITY dp_lib.dp_fifo_dc_mixed_widths
+  GENERIC MAP (
+    g_wr_data_w         => g_wr_data_w,
+    g_rd_data_w         => g_tech_ddr.data_w,
+    g_use_ctrl          => g_wr_use_ctrl,
+    g_wr_fifo_size      => c_wr_fifo_depth,
+    g_wr_fifo_af_margin => 4 + c_latency, --default (4) + c_latency to compensate for latency introduced by registering wr_siso.ready
+    g_rd_fifo_rl        => 0
+  )
+  PORT MAP (
+    wr_rst         => wr_rst,
+    wr_clk         => wr_clk,
+    rd_rst         => i_ctlr_gen_rst,
+    rd_clk         => i_ctlr_gen_clk,
+
+    snk_out        => wr_siso,
+    snk_in         => wr_sosi,
+  
+    wr_usedw       => OPEN,
+    rd_usedw       => wr_fifo_usedw,
+    rd_emp         => OPEN,
+
+    src_in         => flush_wr_siso,
+    src_out        => flush_wr_sosi
+  );
+
+  u_dp_flush : ENTITY dp_lib.dp_flush -- Always instantiate the flusher as it also contains a RL adapter
+  GENERIC MAP (
+    g_ready_latency => 0,
+    g_framed_xon    => g_wr_use_ctrl,  -- stop flushing when dvr_flush is low and a sop has arrived 
+    g_framed_xoff   => FALSE           -- immediately start flushing when dvr_flush goes high
+  )
+  PORT MAP (
+    rst      => i_ctlr_gen_rst,
+    clk      => i_ctlr_gen_clk,
+   
+    snk_in   => flush_wr_sosi,
+    snk_out  => flush_wr_siso,
+
+    src_out  => ctlr_wr_sosi,
+    src_in   => ctlr_wr_siso,  -- fixed streaming xon='1'
+
+    flush_en => dvr_flush      -- memory mapped xon/xoff control
+  );
+
+  gen_flush : IF g_flush_wr_fifo = TRUE GENERATE  
+    u_flush_ctrl : ENTITY work.ddr_flush_ctrl
+    GENERIC MAP (
+      g_sop               => g_flush_sop,
+      g_sop_channel       => g_flush_sop_channel,
+      g_sop_start_channel => g_flush_sop_start_channel,
+      g_nof_channels      => g_flush_nof_channels     
+    )
+    PORT MAP (
+      rst           => wr_rst,
+      clk           => wr_clk,
+  
+      dvr_en        => dvr_en,
+      dvr_wr_not_rd => dvr_wr_not_rd,
+      dvr_done      => i_dvr_done,
+  
+      wr_sosi       => wr_sosi,
+  
+      dvr_flush     => dvr_flush
+    );
+  END GENERATE;
+
+  u_rd_fifo : ENTITY dp_lib.dp_fifo_dc_mixed_widths
+  GENERIC MAP (
+    g_wr_data_w         => g_tech_ddr.data_w,
+    g_rd_data_w         => g_rd_data_w,
+    g_use_ctrl          => FALSE,
+    g_wr_fifo_size      => g_rd_fifo_depth,
+    g_wr_fifo_af_margin => c_ddr_ctrl_nof_latent_reads, -- >=4 (required by dp_fifo)
+    g_rd_fifo_rl        => 1
+  )
+  PORT MAP (
+    wr_rst   => i_ctlr_gen_rst,
+    wr_clk   => i_ctlr_gen_clk,
+    rd_rst   => rd_rst,
+    rd_clk   => rd_clk,
+
+    snk_out  => ctlr_rd_siso,
+    snk_in   => ctlr_rd_sosi,
+  
+    wr_usedw => OPEN,
+    rd_usedw => rd_fifo_usedw,
+    rd_emp   => OPEN,
+
+    src_in   => rd_siso,
+    src_out  => rd_sosi
+  );
+
+  u_io_ddr_driver : ENTITY work.io_ddr_driver
+  GENERIC MAP (
+    g_tech_ddr      => g_tech_ddr,
+    g_wr_fifo_depth => g_wr_fifo_depth
+  )
+  PORT MAP ( 
+    rst             => i_ctlr_gen_rst,  
+    clk             => i_ctlr_gen_clk,        
+
+    ctlr_rdy        => i_ctlr_rdy,
+    ctlr_init_done  => i_ctlr_init_done,
+    ctlr_wr_req     => ctlr_wr_req,      
+    ctlr_rd_req     => ctlr_rd_req,
+    ctlr_burst      => ctlr_burst,
+    ctlr_burst_size => ctlr_burst_size,
+
+    wr_val          => ctlr_wr_sosi.valid, 
+    wr_rdy          => ctlr_wr_siso.ready,
+    rd_rdy          => ctlr_rd_siso.ready,
+
+    cur_addr        => dvr_cur_addr,
+    start_addr      => dvr_start_addr,
+    end_addr        => dvr_end_addr, 
+
+    dvr_en          => dvr_en,
+    dvr_wr_not_rd   => dvr_wr_not_rd,
+    dvr_done        => i_dvr_done,
+
+    wr_fifo_usedw   => wr_fifo_usedw
+  );
+
+  ctlr_address <= dvr_cur_addr.chip &
+                  dvr_cur_addr.bank &
+                  dvr_cur_addr.row(g_tech_ddr.a_w-1 DOWNTO 0) &
+                  dvr_cur_addr.column(g_tech_ddr.a_col_w-1 DOWNTO g_tech_ddr.rsl_w);
+  
+  gen_uphy_4g_800_master : IF func_tech_ddr_module_size(c_tech_ddr)=4 AND g_tech_ddr.mts=800 GENERATE
+    u_uphy_4g_800_master : COMPONENT uphy_4g_800_master 
+	   PORT MAP (
+	  	pll_ref_clk                => ctlr_ref_clk,                         
+	  	global_reset_n             => ctlr_ref_rst_n,                           
+	  	soft_reset_n               => '1',                                  
+	  	afi_clk                    => i_ctlr_gen_clk,                       
+	  	afi_half_clk               => OPEN,                                 
+	  	afi_reset_n                => ctlr_gen_rst_n,                       
+	  	mem_a                      => phy_ou.a(g_tech_ddr.a_w-1 DOWNTO 0),       
+	  	mem_ba                     => phy_ou.ba(g_tech_ddr.ba_w-1 DOWNTO 0),     
+	  	mem_ck                     => phy_io.clk(g_tech_ddr.clk_w-1 DOWNTO 0),   
+	  	mem_ck_n                   => phy_io.clk_n(g_tech_ddr.clk_w-1 DOWNTO 0), 
+	  	mem_cke                    => phy_ou.cke(g_tech_ddr.clk_w-1 DOWNTO 0),   
+	  	mem_cs_n                   => phy_ou.cs_n(g_tech_ddr.cs_w-1 DOWNTO 0),   
+	  	mem_dm                     => phy_ou.dm(g_tech_ddr.dm_w-1 DOWNTO 0),     
+	  	mem_ras_n                  => phy_ou.ras_n,                         
+	  	mem_cas_n                  => phy_ou.cas_n,                         
+	  	mem_we_n                   => phy_ou.we_n,                          
+	  	mem_reset_n                => phy_ou.reset_n,                       
+	  	mem_dq                     => phy_io.dq(g_tech_ddr.dq_w-1 DOWNTO 0),     
+	  	mem_dqs                    => phy_io.dqs(g_tech_ddr.dqs_w-1 DOWNTO 0),   
+	  	mem_dqs_n                  => phy_io.dqs_n(g_tech_ddr.dqs_w-1 DOWNTO 0), 
+	  	mem_odt                    => phy_ou.odt(g_tech_ddr.cs_w-1 DOWNTO 0),    
+	  	avl_ready                  => i_ctlr_rdy,                           
+	  	avl_burstbegin             => ctlr_burst,                           
+	  	avl_addr                   => ctlr_address,                         
+	  	avl_rdata_valid            => ctlr_rd_sosi.valid,                   
+	  	avl_rdata                  => ctlr_rd_sosi.data(g_tech_ddr.data_w-1 DOWNTO 0),                    
+	  	avl_wdata                  => ctlr_wr_sosi.data(g_tech_ddr.data_w-1 DOWNTO 0),                    
+	  	avl_be                     => (OTHERS => '1'),                      
+	  	avl_read_req               => ctlr_rd_req,                          
+	  	avl_write_req              => ctlr_wr_req,                          
+	  	avl_size                   => ctlr_burst_size,          
+	  	local_init_done            => i_ctlr_init_done,                     
+	  	local_cal_success          => OPEN,                                 
+	  	local_cal_fail             => OPEN,                                 
+	  	oct_rdn                    => phy_in.oct_rdn,                       
+	  	oct_rup                    => phy_in.oct_rup,                       
+  		seriesterminationcontrol   => OPEN,
+	  	parallelterminationcontrol => OPEN,
+	  	pll_mem_clk                => i_ctlr_gen_clk_2x,
+      pll_write_clk              => OPEN,
+      pll_write_clk_pre_phy_clk  => OPEN,
+      pll_addr_cmd_clk           => OPEN,
+      pll_locked                 => OPEN,
+      pll_avl_clk                => OPEN,
+      pll_config_clk             => OPEN,
+      dll_delayctrl              => OPEN
+	  );    
+  END GENERATE;  
+
+END str;
+
diff --git a/libraries/io/ddr/src/vhdl/io_ddr_driver.vhd b/libraries/io/ddr/src/vhdl/io_ddr_driver.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..549c1ee29449c7255cd7c0b55851b7f8fdd63541
--- /dev/null
+++ b/libraries/io/ddr/src/vhdl/io_ddr_driver.vhd
@@ -0,0 +1,275 @@
+--------------------------------------------------------------------------------
+--
+-- Copyright (C) 2014
+-- 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_ddr_lib, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE tech_ddr_lib.tech_ddr_pkg.ALL;
+
+ENTITY io_ddr_driver IS 
+  GENERIC (
+    g_tech_ddr         : t_c_tech_ddr;
+    g_wr_fifo_depth    : NATURAL := 128
+   );  
+  PORT ( 
+    clk                : IN  STD_LOGIC;
+    rst                : IN  STD_LOGIC;
+
+    ctlr_rdy           : IN  STD_LOGIC;
+    ctlr_init_done     : IN  STD_LOGIC;
+    ctlr_burst         : OUT STD_LOGIC;
+    ctlr_burst_size    : OUT STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0);
+    ctlr_wr_req        : OUT STD_LOGIC;   
+    ctlr_rd_req        : OUT STD_LOGIC;
+
+    dvr_en             : IN  STD_LOGIC := '1';
+    dvr_wr_not_rd      : IN  STD_LOGIC;       
+    dvr_done           : OUT STD_LOGIC; -- Requested wr or rd sequence is done.
+   
+    wr_val             : IN  STD_LOGIC; 
+    wr_rdy             : OUT STD_LOGIC;
+    rd_rdy             : IN  STD_LOGIC;     
+
+    cur_addr           : OUT t_tech_ddr_addr; 
+    start_addr         : IN  t_tech_ddr_addr;
+    end_addr           : IN  t_tech_ddr_addr;
+
+    wr_fifo_usedw      : IN  STD_LOGIC_VECTOR
+   );
+END io_ddr_driver;
+
+
+ARCHITECTURE str OF io_ddr_driver IS
+
+  CONSTANT c_chip_addr_w        : NATURAL := ceil_log2(g_tech_ddr.cs_w); --Chip sel lines converted to logical address
+  CONSTANT c_address_w          : NATURAL := c_chip_addr_w + g_tech_ddr.ba_w + g_tech_ddr.a_w + g_tech_ddr.a_col_w +1; -- 1 bit added to detect overflow
+ 
+  CONSTANT c_margin             : NATURAL := 2; -- wr_burst_size is updated one cycle after reading actual nof available words.
+                                                -- Subtract two (wr_fifo_usedw and wr_burst_size are both registered) so we cannot 
+                                                -- post a request for a too large burst size, which could cause the wr_burst state 
+                                                -- to be two valid words short.
+
+  SIGNAL req_burst_cycles       : STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0);
+  SIGNAL nxt_req_burst_cycles   : STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0);
+
+  TYPE t_state_enum IS (s_init, s_idle, s_wait1, s_wait2, s_wait3, s_rd_request, s_wr_request, s_wr_burst);
+
+  SIGNAL state                  : t_state_enum;
+  SIGNAL nxt_state              : t_state_enum; 
+  SIGNAL prev_state             : t_state_enum; 
+
+  SIGNAL wr_burst_size          : NATURAL;
+  SIGNAL rd_burst_size          : NATURAL;
+
+  SIGNAL nxt_wr_burst_size      : NATURAL;
+  SIGNAL nxt_rd_burst_size      : NATURAL;
+
+  SIGNAL i_ctlr_burst_size      : STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0);
+
+  SIGNAL i_dvr_done             : STD_LOGIC;
+  SIGNAL nxt_dvr_done           : STD_LOGIC;
+
+  SIGNAL start_address          : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0);
+  SIGNAL end_address            : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0);
+ 
+  SIGNAL cur_address            : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0);
+  SIGNAL nxt_cur_address        : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0);   
+  SIGNAL diff_address           : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0);
+
+  SIGNAL addresses_rem          : STD_LOGIC_VECTOR(31 DOWNTO 0); -- nof words (on the user side interface) to rd/wr until end addr is reached
+  SIGNAL reg_addresses_rem      : STD_LOGIC_VECTOR(31 DOWNTO 0); -- nof words (on the user side interface) to rd/wr until end addr is reached
+
+  SIGNAL reg_wr_fifo_usedw      : STD_LOGIC_VECTOR(ceil_log2(g_wr_fifo_depth)-1 DOWNTO 0);  -- read side depth of the write FIFO
+
+BEGIN
+
+  ctlr_burst_size <= i_ctlr_burst_size;
+  dvr_done        <= i_dvr_done; 
+
+  p_clk : PROCESS(rst, clk)
+  BEGIN
+    IF rst='1' THEN
+      state                <= s_init;
+      req_burst_cycles     <= (OTHERS => '0');
+      i_dvr_done           <= '0';
+      cur_address          <= (OTHERS=>'0');
+      wr_burst_size        <= 0;
+      rd_burst_size        <= 0;
+      reg_addresses_rem    <= (OTHERS=>'0');
+      reg_wr_fifo_usedw    <= (OTHERS=>'0');
+      prev_state           <= s_idle;
+    ELSIF rising_edge(clk) THEN
+      state                <= nxt_state;
+      req_burst_cycles     <= nxt_req_burst_cycles;
+      i_dvr_done           <= nxt_dvr_done;
+      cur_address          <= nxt_cur_address;
+      wr_burst_size        <= nxt_wr_burst_size;
+      rd_burst_size        <= nxt_rd_burst_size;
+      reg_addresses_rem    <= addresses_rem;
+      reg_wr_fifo_usedw    <= wr_fifo_usedw;
+      prev_state           <= state;
+    END IF;
+  END PROCESS;
+
+  -- Add 1 address (accounting for address resulotion) to diff_address: we also want to write the last address. Shift the result right to provide the correct resolution.
+  addresses_rem <= RESIZE_UVEC( SHIFT_UVEC( INCR_UVEC(diff_address, g_tech_ddr.rsl), g_tech_ddr.rsl_w), addresses_rem'LENGTH); 
+  
+  -- End address - current address
+  diff_address  <= SUB_UVEC(end_address, cur_address, c_address_w);
+
+  p_burst_size : PROCESS (reg_addresses_rem, reg_wr_fifo_usedw)
+    VARIABLE v_burst_size : NATURAL;
+  BEGIN 
+    -- Write burst size is smallest of g_tech_ddr.maxburstsize, addresses_rem and wr_fifo_usedw
+    v_burst_size := g_tech_ddr.maxburstsize+c_margin;
+    IF UNSIGNED(reg_wr_fifo_usedw)>=c_margin AND UNSIGNED(reg_addresses_rem)>=c_margin THEN 
+      IF v_burst_size > SIGNED('0' & reg_addresses_rem) THEN v_burst_size := TO_UINT(reg_addresses_rem); END IF;
+      IF v_burst_size > SIGNED('0' & reg_wr_fifo_usedw) THEN v_burst_size := TO_UINT(reg_wr_fifo_usedw); END IF;
+      v_burst_size := v_burst_size - c_margin;
+    ELSE
+      v_burst_size := 0;
+    END IF;
+    nxt_wr_burst_size <= v_burst_size;
+
+    -- Read burst size is smallest of g_tech_ddr.maxburstsize and addresses_rem
+    v_burst_size := g_tech_ddr.maxburstsize;
+    IF UNSIGNED(reg_addresses_rem)>=1 THEN -- prevent assigning <0 value to natural
+      IF v_burst_size > SIGNED('0' & reg_addresses_rem) THEN v_burst_size := TO_UINT(reg_addresses_rem); END IF;
+    ELSE
+      v_burst_size := 0;
+    END IF;
+    nxt_rd_burst_size <= v_burst_size;
+  END PROCESS;
+
+  p_state : PROCESS(prev_state, state, i_dvr_done, ctlr_rdy, req_burst_cycles, dvr_wr_not_rd, wr_val, wr_fifo_usedw, wr_burst_size, rd_burst_size, dvr_en, ctlr_init_done, reg_addresses_rem, rd_rdy, i_ctlr_burst_size, start_address, cur_address)
+  BEGIN  
+    nxt_state              <= state;   
+    ctlr_wr_req            <= '0';
+    ctlr_rd_req            <= '0';
+    ctlr_burst             <= '0'; 
+    i_ctlr_burst_size      <= (OTHERS => '0');
+    nxt_req_burst_cycles   <= req_burst_cycles;
+    wr_rdy                 <= '0';
+    nxt_dvr_done           <= i_dvr_done;
+    nxt_cur_address        <= cur_address;
+
+    CASE state IS
+     
+      WHEN s_wr_burst => -- Performs the burst portion (word 2+)        
+        ctlr_wr_req <= '1';        
+        IF ctlr_rdy = '1' THEN -- when local_ready goes low, that cycle does not count as a burst cycle          
+          nxt_req_burst_cycles <= INCR_UVEC(req_burst_cycles, -1);
+          wr_rdy               <= '1'; -- wr side uses latency of 0, so wr_rdy<='1' acknowledges a successful write request.
+          IF UNSIGNED(req_burst_cycles) = 1 THEN -- Then we're in the last cycle of this burst sequence
+            nxt_state <= s_wr_request; -- We can only initiate a burst through the wr_request state
+          END IF;
+        END IF;            
+
+      WHEN s_wr_request => -- Performs 1 write or read and goes into s_wr_burst when requested write words >1         
+        nxt_state <= s_wait3; 
+        IF UNSIGNED(reg_addresses_rem) = 0 THEN -- end address reached
+          nxt_dvr_done  <= '1';              
+          nxt_state     <= s_idle;          
+        ELSIF ctlr_rdy = '1' THEN 
+          IF wr_val = '1' THEN
+            -- Always perform 1st write here             
+            ctlr_burst        <= '1'; -- assert burst begin: strictly this is a burst of 1. 
+            ctlr_wr_req       <= '1';            
+            wr_rdy            <= '1';
+            i_ctlr_burst_size <= TO_UVEC(1, g_tech_ddr.maxburstsize_w); -- Set ctlr_burst_size to 1 by default                            
+            IF wr_burst_size > 1 THEN
+              -- Perform any remaining writes in a burst
+              nxt_state            <= s_wr_burst;
+              nxt_req_burst_cycles <= TO_UVEC(wr_burst_size-1, g_tech_ddr.maxburstsize_w); -- Forward the required nof burst cycles (-1 as we've done the 1st in this state already) to burst state 
+              i_ctlr_burst_size    <= TO_UVEC(wr_burst_size  , g_tech_ddr.maxburstsize_w);
+            END IF; -- ELSE: there is only 1 word, so no need for remaining burst   
+            nxt_cur_address   <= INCR_UVEC(cur_address, UNSIGNED(i_ctlr_burst_size)*g_tech_ddr.rsl);
+--            IF UNSIGNED(i_ctlr_burst_size) = 1 THEN -- Prevents FSM from going into this state again too soon (reg_addresses_rem invalid)
+--            nxt_state <= s_wait3; 
+--            END IF;
+          END IF;
+        END IF;        
+
+      WHEN s_rd_request => -- Posts a read request for a burst (0...g_tech_ddr.maxburstsize)      
+        nxt_state <= s_wait3;   
+        IF UNSIGNED(reg_addresses_rem) = 0 THEN -- end address reached
+          nxt_dvr_done  <= '1';              
+          nxt_state     <= s_idle;
+        ELSE 
+          IF rd_rdy = '1' THEN -- Fifo uses its internal almost_full signal to toggle its snk_out.rdy     
+            IF ctlr_rdy = '1' THEN    
+              ctlr_rd_req       <= '1';                   
+              ctlr_burst        <= '1'; -- assert burst begin: strictly this is a burst of 1.                  
+              i_ctlr_burst_size <= TO_UVEC(rd_burst_size, g_tech_ddr.maxburstsize_w);
+              IF rd_burst_size = 0 THEN i_ctlr_burst_size <= TO_UVEC(1, g_tech_ddr.maxburstsize_w); END IF;
+              nxt_cur_address   <= INCR_UVEC(cur_address, UNSIGNED(i_ctlr_burst_size)*g_tech_ddr.rsl);  
+--              IF UNSIGNED(i_ctlr_burst_size) = 1 THEN -- Prevents FSM from going into this state again too soon (reg_addresses_rem invalid)
+--              nxt_state <= s_wait3; 
+--              END IF;
+            END IF;           
+          END IF;
+        END IF; 
+
+      -- This wait state is inserted between two requests when necessary, e.g. when FSM enters wr_request
+      -- from the state wr_request, an extra cycle is needed for reg_addresses_rem to be valid.
+      WHEN s_wait3 =>
+        IF prev_state = s_wr_request THEN nxt_state <= s_wr_request; END IF;
+        IF prev_state = s_rd_request THEN nxt_state <= s_rd_request; END IF;
+   
+     -- In this cycle reg_addresses_rem is valid. This cycle is added so wr_burst_size and rd_burst_size
+     -- (derived from reg_addresses_rem) are valid the next cycle.
+     WHEN s_wait2 =>
+       IF dvr_wr_not_rd = '1' THEN
+         nxt_state <= s_wr_request; 
+       ELSE
+         nxt_state <= s_rd_request;             
+       END IF;
+      
+      -- Wait a cycle so reg_addresses_rem is valid the next cyle.
+      WHEN s_wait1 =>
+        nxt_state <= s_wait2;
+
+      WHEN s_idle =>
+        IF dvr_en = '1' THEN  
+          nxt_cur_address <= start_address; 
+          nxt_dvr_done    <= '0';
+          nxt_state       <= s_wait1;
+        END IF;
+     
+      WHEN OTHERS => -- s_init
+        IF ctlr_init_done = '1' THEN
+          nxt_state <= s_idle;
+        END IF;
+      
+    END CASE;
+  END PROCESS;
+
+  end_address   <= RESIZE_UVEC(  end_addr.chip &  end_addr.bank  &   end_addr.row(g_tech_ddr.a_w-1 DOWNTO 0) &   end_addr.column(g_tech_ddr.a_col_w-1 DOWNTO 0), c_address_w);  
+  start_address <= RESIZE_UVEC(start_addr.chip & start_addr.bank & start_addr.row(g_tech_ddr.a_w-1 DOWNTO 0) & start_addr.column(g_tech_ddr.a_col_w-1 DOWNTO 0), c_address_w); 
+  
+  cur_addr.chip(  c_chip_addr_w     -1 DOWNTO 0) <= cur_address(c_chip_addr_w+g_tech_ddr.ba_w+g_tech_ddr.a_w+g_tech_ddr.a_col_w-1 DOWNTO g_tech_ddr.ba_w+g_tech_ddr.a_w+g_tech_ddr.a_col_w); 
+  cur_addr.bank(  g_tech_ddr.ba_w   -1 DOWNTO 0) <= cur_address(              g_tech_ddr.ba_w+g_tech_ddr.a_w+g_tech_ddr.a_col_w-1 DOWNTO                 g_tech_ddr.a_w+g_tech_ddr.a_col_w);
+  cur_addr.row(   g_tech_ddr.a_w    -1 DOWNTO 0) <= cur_address(                              g_tech_ddr.a_w+g_tech_ddr.a_col_w-1 DOWNTO                                g_tech_ddr.a_col_w);
+  cur_addr.column(g_tech_ddr.a_col_w-1 DOWNTO 0) <= cur_address(                                             g_tech_ddr.a_col_w-1 DOWNTO                                                 0);
+  
+END str;
+
diff --git a/libraries/io/ddr/tb/vhdl/tb_io_ddr.vhd b/libraries/io/ddr/tb/vhdl/tb_io_ddr.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..56183cd5672acacb6b8ad824f850305a3c6fb080
--- /dev/null
+++ b/libraries/io/ddr/tb/vhdl/tb_io_ddr.vhd
@@ -0,0 +1,272 @@
+--------------------------------------------------------------------------------
+--
+-- Copyright (C) 2014
+-- 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/>.
+--
+--------------------------------------------------------------------------------
+
+-- This testbench tests the different type of DDR controllers: 
+--
+--                        - uphy_4g_800_master
+--                        - uphy_4g_800_slave
+--
+-- The DUT can be selected, using the c_tech_ddr constants. 
+--
+-- Testbench is selftesting: 
+--
+-- > run -all 
+--
+
+LIBRARY IEEE, technology_lib, tech_ddr_lib, common_lib, dp_lib, diagnostics_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 technology_lib.technology_pkg.ALL;
+USE tech_ddr_lib.tech_ddr_pkg.ALL;
+
+ENTITY tb_io_ddr IS  
+END ENTITY tb_io_ddr;
+
+ARCHITECTURE str of tb_io_ddr IS
+
+  CONSTANT c_ctlr_ref_clk_period : TIME := 5 ns;  -- 200 MHz
+
+  CONSTANT c_tech_ddr         : t_c_tech_ddr := c_tech_ddr_4g_800m;
+  
+  CONSTANT c_data_w           : NATURAL      := 256; --32;
+ 
+  SIGNAL ctlr_ref_clk         : STD_LOGIC    := '0';
+  SIGNAL ctlr_ref_rst         : STD_LOGIC    := '1';
+  SIGNAL tb_end               : STD_LOGIC    := '0';
+  SIGNAL ctlr_gen_clk         : STD_LOGIC;
+  SIGNAL ctlr_gen_rst         : STD_LOGIC;
+
+  SIGNAL ctlr_rdy             : STD_LOGIC;
+  SIGNAL ctlr_init_done       : STD_LOGIC;
+
+  SIGNAL dvr_start_addr       : t_tech_ddr_addr; 
+  SIGNAL dvr_end_addr         : t_tech_ddr_addr;
+
+  SIGNAL dvr_en               : STD_LOGIC;
+  SIGNAL dvr_wr_not_rd        : STD_LOGIC;
+  SIGNAL dvr_done             : STD_LOGIC;
+
+  SIGNAL wr_siso              : t_dp_siso;
+  SIGNAL wr_sosi              : t_dp_sosi;
+
+  SIGNAL wr_siso_pre_fifo     : t_dp_siso;
+  SIGNAL wr_sosi_pre_fifo     : t_dp_sosi;
+
+  SIGNAL rd_siso              : t_dp_siso;
+  SIGNAL rd_sosi              : t_dp_sosi;
+
+  SIGNAL src_diag_en          : STD_LOGIC;
+  SIGNAL src_val_cnt          : STD_LOGIC_VECTOR(31 DOWNTO 0);
+
+  SIGNAL snk_diag_en          : STD_LOGIC;
+  SIGNAL snk_diag_res         : STD_LOGIC;
+  SIGNAL snk_diag_res_val     : STD_LOGIC;
+  SIGNAL snk_val_cnt          : STD_LOGIC_VECTOR(31 DOWNTO 0);
+
+  SIGNAL phy_in               : t_tech_ddr_phy_in;
+  SIGNAL phy_io               : t_tech_ddr_phy_io;
+  SIGNAL phy_ou               : t_tech_ddr_phy_ou;
+  
+  SIGNAL ras_n                : STD_LOGIC_VECTOR(0 DOWNTO 0);
+  SIGNAL cas_n                : STD_LOGIC_VECTOR(0 DOWNTO 0);
+  SIGNAL we_n                 : STD_LOGIC_VECTOR(0 DOWNTO 0);
+  
+BEGIN
+ 
+  ctlr_ref_clk   <= NOT(ctlr_ref_clk) OR tb_end AFTER c_ctlr_ref_clk_period/2; 
+  ctlr_ref_rst   <= '0' AFTER 100 ns;
+
+  dvr_start_addr <= c_tech_ddr_addr_lo;
+  dvr_end_addr   <= c_tech_ddr_addr_hi_sim;
+
+  p_stimuli : PROCESS
+  BEGIN
+    tb_end        <= '0';
+    dvr_en        <= '0';
+    src_diag_en   <= '0';
+    dvr_wr_not_rd <= '0';
+    snk_diag_en   <= '0';
+
+    WAIT UNTIL ctlr_init_done = '1';   
+    FOR i IN 0 TO 1 LOOP
+      WAIT UNTIL rising_edge(ctlr_gen_clk); -- Give the driver FSM a cycle to go into idle mode
+    END LOOP;
+    
+    -- START WRITE
+    src_diag_en   <= '1';
+    dvr_wr_not_rd <= '1';
+    dvr_en        <= '1';
+
+    WAIT UNTIL rising_edge(ctlr_gen_clk);
+
+    dvr_en        <= '0'; 
+    
+    -- WRITE DONE  
+    WAIT UNTIL dvr_done = '1';
+
+    src_diag_en   <= '0';
+
+    -- START READ
+    snk_diag_en   <= '1';
+    dvr_wr_not_rd <= '0';
+    dvr_en        <= '1';
+
+    WAIT UNTIL rising_edge(ctlr_gen_clk);
+
+    dvr_en        <= '0'; 
+
+    -- READ DONE
+    WAIT UNTIL dvr_done = '1';    
+
+    WAIT FOR 2 us; -- 'Done' means all requests are posted. Wait for the last read data to arrive.
+  
+    ASSERT snk_diag_res_val = '1' REPORT "[ERROR] DIAG_RES INVALID!" SEVERITY FAILURE;
+    ASSERT snk_diag_res = '0' REPORT "[ERROR] NON-ZERO DIAG_RES!" SEVERITY FAILURE;
+    ASSERT FALSE REPORT "[OK] Test passed." SEVERITY NOTE;
+    tb_end        <= '1';
+
+    WAIT;
+  END PROCESS;
+
+  u_diagnostics: ENTITY diagnostics_lib.diagnostics 
+  GENERIC MAP (
+    g_dat_w             => c_data_w,
+    g_nof_streams       => 1
+     ) 
+  PORT MAP (
+    rst                 => ctlr_gen_rst,
+    clk                 => ctlr_gen_clk,
+
+    snk_out_arr(0)      => rd_siso,
+    snk_in_arr(0)       => rd_sosi,
+    snk_diag_en(0)      => snk_diag_en,
+    snk_diag_md(0)      => '1',
+    snk_diag_res(0)     => snk_diag_res,
+    snk_diag_res_val(0) => snk_diag_res_val,
+    snk_val_cnt(0)      => snk_val_cnt,
+
+    src_out_arr(0)      => wr_sosi,
+    src_in_arr(0)       => wr_siso,
+    src_diag_en(0)      => src_diag_en,
+    src_diag_md(0)      => '1',
+    src_val_cnt(0)      => src_val_cnt
+  );
+
+  gen_ddr_4g_memory_model : IF func_tech_ddr_module_size(c_tech_ddr) = 4 GENERATE
+    u_4gb_ddr3_memory_model : COMPONENT alt_mem_if_ddr3_mem_model_top_ddr3_mem_if_dm_pins_en_mem_if_dqsn_en
+	  GENERIC MAP (
+	  	MEM_IF_ADDR_WIDTH            => 15,
+	  	MEM_IF_ROW_ADDR_WIDTH        => 15,
+	  	MEM_IF_COL_ADDR_WIDTH        => 10,
+	  	MEM_IF_CS_PER_RANK           => 1,
+	  	MEM_IF_CONTROL_WIDTH         => 1,
+	  	MEM_IF_DQS_WIDTH             => 8,
+	  	MEM_IF_CS_WIDTH              => 2,
+	  	MEM_IF_BANKADDR_WIDTH        => 3,
+	  	MEM_IF_DQ_WIDTH              => 64,
+	  	MEM_IF_CK_WIDTH              => 2,
+	  	MEM_IF_CLK_EN_WIDTH          => 2,
+	  	DEVICE_WIDTH                 => 1,
+	  	MEM_TRCD                     => 6,
+	  	MEM_TRTP                     => 3,
+	  	MEM_DQS_TO_CLK_CAPTURE_DELAY => 100,
+	  	MEM_CLK_TO_DQS_CAPTURE_DELAY => 100000,
+	  	MEM_IF_ODT_WIDTH             => 2,
+	  	MEM_MIRROR_ADDRESSING_DEC    => 0,
+	  	MEM_REGDIMM_ENABLED          => false,
+	  	DEVICE_DEPTH                 => 1,
+	  	MEM_GUARANTEED_WRITE_INIT    => false,
+	  	MEM_VERBOSE                  => true,
+	  	MEM_INIT_EN                  => false,
+	  	MEM_INIT_FILE                => "",
+	  	DAT_DATA_WIDTH               => 32
+	  )
+	  PORT MAP (
+	  	mem_a       => phy_ou.a(c_tech_ddr.a_w-1 DOWNTO 0),        -- memory.mem_a
+	  	mem_ba      => phy_ou.ba,      --       .mem_ba
+	  	mem_ck      => phy_io.clk,     --       .mem_ck
+	  	mem_ck_n    => phy_io.clk_n,   --       .mem_ck_n
+	  	mem_cke     => phy_ou.cke(c_tech_ddr.cs_w-1 DOWNTO 0),     --       .mem_cke
+	  	mem_cs_n    => phy_ou.cs_n(c_tech_ddr.cs_w-1 DOWNTO 0),    --       .mem_cs_n
+	  	mem_dm      => phy_ou.dm,      --       .mem_dm
+	  	mem_ras_n   => ras_n,          --       .mem_ras_n
+	  	mem_cas_n   => cas_n,          --       .mem_cas_n
+	  	mem_we_n    => we_n,           --       .mem_we_n
+	  	mem_reset_n => phy_ou.reset_n, --       .mem_reset_n
+	  	mem_dq      => phy_io.dq,      --       .mem_dq
+	  	mem_dqs     => phy_io.dqs,     --       .mem_dqs
+	  	mem_dqs_n   => phy_io.dqs_n,   --       .mem_dqs_n
+	  	mem_odt     => phy_ou.odt      --       .mem_odt
+	  );               
+	  
+	  ras_n(0) <= phy_ou.ras_n;
+	  cas_n(0) <= phy_ou.cas_n;
+	  we_n(0)  <= phy_ou.we_n; 
+	END GENERATE;
+  
+  u_io_ddr: ENTITY work.io_ddr
+  GENERIC MAP(
+    g_technology       => c_tech_select_default,
+    g_ddr              => c_tech_ddr,
+    g_wr_data_w        => c_data_w,
+    g_rd_data_w        => c_data_w
+  )                      
+  PORT MAP (                  
+    ctlr_ref_clk       => ctlr_ref_clk,
+    ctlr_ref_rst       => ctlr_ref_rst,
+                                     
+    ctlr_gen_clk       => ctlr_gen_clk,
+    ctlr_gen_rst       => ctlr_gen_rst,      
+
+    ctlr_init_done     => ctlr_init_done,
+    ctlr_rdy           => ctlr_rdy,
+
+    dvr_start_addr     => dvr_start_addr,
+    dvr_end_addr       => dvr_end_addr,
+    dvr_en             => dvr_en,
+    dvr_wr_not_rd      => dvr_wr_not_rd,
+    dvr_done           => dvr_done,
+ 
+    wr_clk             => ctlr_gen_clk,
+    wr_rst             => ctlr_gen_rst,
+
+    wr_sosi            => wr_sosi, 
+    wr_siso            => wr_siso,
+  
+    rd_sosi            => rd_sosi,
+    rd_siso            => rd_siso,
+
+    rd_clk             => ctlr_gen_clk,
+    rd_rst             => ctlr_gen_rst,
+
+    phy_ou             => phy_ou,
+    phy_io             => phy_io,
+    phy_in             => phy_in
+  );
+ 
+END ARCHITECTURE str;
+
+