diff --git a/src/cmd.cpp b/src/cmd.cpp
index c28f9f0d783c97700bfebd7940b75981bac2f299..c97d9a9138b6489b05a423818efed39a8eba601f 100644
--- a/src/cmd.cpp
+++ b/src/cmd.cpp
@@ -344,7 +344,7 @@ CMDstatus CMD::Mwrite(int argc, char* argv[], TermOutput& termout, Serverdat *sd
         if (vm.count("help")) {
             termout.strout << "usage: "   << MWRITE << " [options] file(s)" << endl 
                            << "Options: " << generic << endl
-                           << "Example: mwrite --offs=1 --data=[1,2,3,4] /unb0/pn3/mm/sens/sens/temp_high" << endl;
+                           << "Example: write --offs=1 --data=[1,2,3,4] /unb0/pn3/mm/sens/sens/temp_high" << endl;
             return {CMD_STATUS_OK,0,0};
         } else if (vm.count("file")) {
             string addr_str = "";
@@ -360,13 +360,13 @@ CMDstatus CMD::Mwrite(int argc, char* argv[], TermOutput& termout, Serverdat *sd
                 data.resize(data64.size());
                 for(uint i=0; i<data64.size(); i++) {
                     if((data64[i] & 0xffffffff00000000) != 0) {
-                        cout << "cmd() mwrite() warning 64bit value detected, truncating!!!" << endl;
-                        termout.strerr << "cmd() mwrite() warning 64bit value detected, truncating!!!" << endl;
+                        cout << "cmd() write() warning 64bit value detected, truncating!!!" << endl;
+                        termout.strerr << "cmd() write() warning 64bit value detected, truncating!!!" << endl;
                     }
                     data[i]=(int)data64[i];
                 }
             }
-            if(sd->unb->mwrite(termout, addr_str, offs, data)) ret.status=CMD_STATUS_OK;
+            if(sd->unb->write(termout, addr_str, offs, data)) ret.status=CMD_STATUS_OK;
         } else throw runtime_error("missing argument(s)");
     } catch(po::error& e) {
         termout.strerr << e.what() << endl;
diff --git a/src/cmd.h b/src/cmd.h
index 731968d9861f9190b73f5c4ca88de4f1a1b52686..239b6ebe853359044eb60c9fbf6a29d649f045e1 100644
--- a/src/cmd.h
+++ b/src/cmd.h
@@ -52,7 +52,7 @@ private:
   const std::string QUIT="quit";
   const std::string INFO="ls";
   const std::string MREAD="read";
-  const std::string MWRITE="mwrite";
+  const std::string MWRITE="write";
   const std::string RELOAD="reload";
 
 
diff --git a/src/common.cpp b/src/common.cpp
index 1f70b1586c76358bf84497b7ad590ab7f4931858..06776b67d3dfd4f37dc82ee8e66892119153d085 100644
--- a/src/common.cpp
+++ b/src/common.cpp
@@ -71,6 +71,11 @@ void Common::print_node_id(ostringstream& strs, Node *node)
          << node->GetLocalNr() << "/" << endl;
 }
 
+uint Common::node_number(Node *node)
+{
+    return (node->GetUniboardNr() * 4) + node->GetLocalNr();
+}
+
 std::string Common::string_node_id(int node)
 {
     auto n = select_node(node);
@@ -105,7 +110,7 @@ bool Common::monitor(TermOutput& termout)
         if(idx > 0) termout.strout << ",";
         termout.strout << "[";
         try {
-            if(node->composite_call(termout.strout, "system")) {
+            if(node->cread(termout.strout, "system")) {
                 retcnt++;
             }
         } catch(std::exception& e) {
@@ -128,11 +133,13 @@ bool Common::read(TermOutput& termout, const string addr, const uint offs, const
     std::string type          = addr_to_type(addr);
     std::string peripheral    = addr_to_peripheral(addr);
 
+    termout.strout << "[";
     for(uint idx=0;idx<nodes.size();idx++) {
         auto node = select_node(nodes[idx]);
-        print_node_id(termout.strout,node);
+        //print_node_id(termout.strout,node);
+        uint node_nr = node_number(node);
         if(idx > 0) termout.strout << ",";
-        termout.strout << "[";
+        termout.strout << "(" << node_nr << ",0,";
 
         if(type == "mm") {
             RegisterMap *regmap = node->get_RegisterMap(termout.strout);
@@ -142,7 +149,7 @@ bool Common::read(TermOutput& termout, const string addr, const uint offs, const
                 retcnt++;
             }
         } else if(type == "dev") {
-            if(node->composite_call(termout.strout, peripheral)) {
+            if(node->cread(termout.strout, peripheral)) {
                 retcnt++;
             } else {
                 termout.strerr << "no such peripheral: " << peripheral;
@@ -150,36 +157,55 @@ bool Common::read(TermOutput& termout, const string addr, const uint offs, const
         } else {
             termout.strerr << "no such type: " << type;
         }
-        termout.strout << "]";
+        termout.strout << ")";
     }
+    termout.strout << "]";
     retval = (retcnt==nodes.size());
+    if(nodes.size() == 0) {
+        termout.strerr << "no nodes";
+        retval = false;
+    }
     return retval;
 }
 
-bool Common::mwrite(TermOutput& termout, const string addr, 
+bool Common::write(TermOutput& termout, const string addr, 
                     const uint offs, const std::vector<int>& data)
 {
     bool retval = false;
     uint retcnt = 0;
-    std::vector<int> nodes;
+    std::vector<int> nodes = path_to_node(addr);
+
+    std::string relative_addr = addr_to_relative_addr(addr);
+    std::string type          = addr_to_type(addr);
+    std::string peripheral    = addr_to_peripheral(addr);
 
+    termout.strout << "[";
     for(uint idx = 0; idx<nodes.size(); idx++) {
         auto node = select_node(nodes[idx]);
-        print_node_id(termout.strout, node);
-
+        //print_node_id(termout.strout, node);
+        uint node_nr = node_number(node);
         if(idx > 0) termout.strout << ",";
-        termout.strout << "[" << node->GetGlobalNr() << "],";
+        termout.strout << "(" << node_nr << ",0,";
 
-        termout.strout << "[";
-        for(uint i = 0; i < data.size(); i++) {
-            if(i > 0) termout.strout << ",";
-            termout.strout << data[i];
+        if(type == "mm") {
+            if(node->mwrite(termout.strout, addr, offs, data)) retcnt++;
+        } else if(type == "dev") {
+            if(node->cwrite(termout.strout, peripheral)) {
+                retcnt++;
+            } else {
+                termout.strerr << "no such peripheral: " << peripheral;
+            }
+        } else {
+            termout.strerr << "no such type: " << type;
         }
-        termout.strout << "]";
+        termout.strout << ")";
 
-        if(node->mwrite(termout.strout,addr, offs, data)) retcnt++;
     }
     retval = (retcnt==nodes.size());
+    if(nodes.size() == 0) {
+        termout.strerr << "no nodes";
+        retval = false;
+    }
     return retval;
 }
 
@@ -233,8 +259,8 @@ std::vector<int> Common::unb_name_to_number(const std::string unb_name)
             try {
                 unbs.push_back(std::stoi(unb_range, nullptr));
             } catch (const std::invalid_argument& ia) {
-                cerr << "Common::unb_name_to_number: Invalid argument: " 
-                     << ia.what() << " - but trying vector..." << endl;
+                //cerr << "Common::unb_name_to_number: Invalid argument: " 
+                //     << ia.what() << " - but trying vector..." << endl;
                 istringstream vecstr(unb_range);
                 unbs=parse_int_vector_range(vecstr);
             }
@@ -253,8 +279,8 @@ std::vector<int> Common::pn_name_to_number(const std::string pn_name)
             try {
                 pns.push_back(std::stoi(pn_range,nullptr));
             } catch (const std::invalid_argument& ia) {
-                cerr << "Common::pn_name_to_number: Invalid argument: " 
-                     << ia.what() << " - but trying vector..." << endl;
+                //cerr << "Common::pn_name_to_number: Invalid argument: " 
+                //     << ia.what() << " - but trying vector..." << endl;
                 istringstream vecstr(pn_range);
                 pns=parse_int_vector_range(vecstr);
             }
diff --git a/src/common.h b/src/common.h
index 46214968b1d7f4a59f7978afea480903db559127..d64a55ee5bab6f95df5c685da27b517356888bc7 100644
--- a/src/common.h
+++ b/src/common.h
@@ -42,6 +42,7 @@ private:
 
     Node * select_node(const int nodeId);
     void print_node_id(ostringstream& strs, Node *node);
+    uint node_number(Node *node);
 
 public:
     Common(std::list<class Node*>& nodelist);
@@ -50,7 +51,7 @@ public:
     std::vector<int> get_nodes(void);
     bool monitor(TermOutput& termout);
 
-    bool mwrite(TermOutput& termout, const std::string addr, 
+    bool write(TermOutput& termout, const std::string addr, 
                const uint offs, const std::vector<int>& data);
 
     bool read(TermOutput& termout, const string addr, const uint offs, const int len);
diff --git a/src/node.cpp b/src/node.cpp
index 4764f896193b2d24cbff5d0414468c013e226212..4a9268e7925ef8912770ae3605be190bb6d16d06 100644
--- a/src/node.cpp
+++ b/src/node.cpp
@@ -63,7 +63,7 @@ Node::~Node()
     delete periph_system;
 }
 
-bool Node::composite_call(ostringstream& strs,const string peripheral) {
+bool Node::cread(ostringstream& strs,const string peripheral) {
     if(peripheral == "system") {
         return periph_system->read_system_info(strs);
     } else if(peripheral == "name") { 
@@ -82,6 +82,11 @@ bool Node::composite_call(ostringstream& strs,const string peripheral) {
     else return false;
 }
 
+bool Node::cwrite(ostringstream& strs,const string peripheral) {
+    strs << "cwrite() not yet implemented TODO";
+    return false;
+}
+
 RegisterMap * Node::get_RegisterMap(std::ostringstream& strs)
 {
     return periph_system->getRegisterMap();
diff --git a/src/node.h b/src/node.h
index 78f3e16dae85e079fde21cf50d955e17366620fb..0aecf9f5e0acedf32d48bc6414efa8b626c8eafd 100644
--- a/src/node.h
+++ b/src/node.h
@@ -82,7 +82,8 @@ class Node {
   const uint GetNr()         { return LocalNr; }
   const std::string GetType() { return Type; }
 
-  bool composite_call(std::ostringstream& strs, const std::string peripheral);
+  bool cread(std::ostringstream& strs, const std::string peripheral);
+  bool cwrite(std::ostringstream& strs, const std::string peripheral);
   bool mread(std::ostringstream& strs,const std::string addr, const uint offs, 
              std::vector<int>& data, const uint len);
   bool mwrite(std::ostringstream& strs,const std::string addr, const uint offs, 
diff --git a/src/sdpunb.cpp b/src/sdpunb.cpp
index 29e62f9032140de42513d087a1f1542df5368634..527d771fcc789826109aac63207f538e55e8e4c1 100644
--- a/src/sdpunb.cpp
+++ b/src/sdpunb.cpp
@@ -55,11 +55,11 @@ Serverdat SD;
 
 string print_termout(TermOutput& termout)
 {
-    string s = "output={\n";
+    string s = "output={";
     s += termout.strout.str();
     s += "}\n";
 
-    s += "errors={\n";
+    s += "errors={";
     s += termout.strerr.str();
     s += "}\n";
     return s;
@@ -76,26 +76,29 @@ void monitor(void)
 #define c_NOF_RETRIES_MONITOR 3
     int retries = c_NOF_RETRIES_MONITOR;
 
+//    while(ServerRunning) {
+//            usleep(1000000);
+//    }
     while(ServerRunning) {
-            usleep(1000000);
-
-            while(!SD.monitor_mutex.try_lock()) { cerr << "mutex not ready\n"; usleep(100000); }
-            if(!SD.unb->monitor(termout)) {
-                retries--;
-                cerr << "Retrying " << retries << endl;
-            } else {
-                retries = c_NOF_RETRIES_MONITOR;
-            }
-            SD.monitor_mutex.unlock();
+        usleep(1000000);
+
+        while(!SD.monitor_mutex.try_lock()) { cerr << "mutex not ready\n"; usleep(100000); }
+        if(!SD.unb->monitor(termout)) {
+            retries--;
+            cerr << "Retrying " << retries << endl;
+        } else {
+            retries = c_NOF_RETRIES_MONITOR;
+        }
+        SD.monitor_mutex.unlock();
 
-            if(cmdstatusnew.status != CMD_EMPTY) cmdstatus = cmdstatusnew;
-            cout << "Monitor thread: " << print_termout(termout) << endl;
-            termout.clear();
+        if(cmdstatusnew.status != CMD_EMPTY) cmdstatus = cmdstatusnew;
+        cout << "Monitor thread: " << print_termout(termout) << endl;
+        termout.clear();
 
-            if(retries <= 0) {
-                //cerr << "Re-initializing, Read register maps again!" << endl;
-                //raise(SIGHUP)
-            }
+        if(retries <= 0) {
+            //cerr << "Re-initializing, Read register maps again!" << endl;
+            //raise(SIGHUP)
+        }
     }
 }
 
@@ -121,7 +124,6 @@ void control_server(TCPSSocket *sock, const int clientId, std::atomic<size_t> *n
             while(!quit) {
                 string prompt = "sdpunb:" + cmdname + ":" + to_string(cmdstatus.status) + ":CMD>";
                 SD.controlSocket[clientId]->tx((unsigned char *)prompt.c_str(), strlen(prompt.c_str()));
-
                 int len =  SD.controlSocket[clientId]->rx(tcpbuf, c_TCP_CONTROL_BUFFERSIZE);
                 if(len < 1) {
                     quit = true;
diff --git a/src/tools/ccfg.cpp b/src/tools/ccfg.cpp
index 9fc9bb7168ff5b3c1e72d7a36bec1738cad8db33..315b179d473aaed8bd756815630bbb5ca265ce17 100644
--- a/src/tools/ccfg.cpp
+++ b/src/tools/ccfg.cpp
@@ -227,7 +227,8 @@ RegisterMap ccfg_upe_to_regmap_add(std::istringstream& iss, RegisterMap &regmap)
             ss >> c; // 'a'
             ss >> c; // 'n'
             ss >> c; // '='
-            ss >> hex >> peripheral_span; // in dwords
+            ss >> hex >> peripheral_span;
+            peripheral_span /= 4; // byte to dwords addressing
 
             stage = 1;
 
diff --git a/test/upe/node_io.py b/test/upe/node_io.py
new file mode 100644
index 0000000000000000000000000000000000000000..4509389eff815e098a8223249eb867c5728f6f25
--- /dev/null
+++ b/test/upe/node_io.py
@@ -0,0 +1,722 @@
+#!/usr/bin/env python
+###############################################################################
+#
+# Copyright (C) 2012
+# ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+
+#-------------------------------------------------------------------------------
+# Name:        node_io.py
+# Purpose:     communication module for accessing node registers
+#
+# Author:      donker (ASTRON)
+#
+# Created:     16-12-2011
+#-------------------------------------------------------------------------------
+
+import struct
+import random
+import time
+import sys
+import select
+from copy import deepcopy
+import unb_hw as unb
+import unb_tech as tech
+import udp
+import traceback
+
+import unbclient_io
+import common as cm
+import imp
+
+# Import of register_info is conditional. If the file is not there the default settings will be set and
+# pi_system_info.py will create the register_info.py file.
+# When the file is there everything is normal and register_info is imported
+
+try:
+    import register_info
+except ImportError:
+    import register_info_default as register_info
+#    register_info.technology="stratixiv"
+#    register_info.nodes = 1000*[None]
+#    for i in range(1000):
+#        register_info.nodes[i] = dict()
+#        register_info.nodes[i]['PIO_SYSTEM_INFO'] = (0x0, 128)
+#        register_info.nodes[i]['ROM_SYSTEM_INFO'] = (0x1000, 4096)
+
+print(register_info.nodes)
+"""
+USAGE:
+
+# first import node_io module
+import node_io
+
+# assign node_io class io
+io = node_io.NodeIO(nodelist, base_ip='10.99.31.1')
+  - nodelist = list of tuples [(node_no,type),(node_no,type),..]
+    - node = 0..n
+    - type = 'FN' | 'BN' | 'PN2'
+  - base_ip = ip address of first node, use 'localhost' for testmode
+
+# Now the following commands can be used:
+
+# To write data to:
+# - a memory register 0x0 .. 0xffffffff
+# - a fifo register
+
+status = io.writeRegister(nodes=[8], name="REG_INPUT", offset=0, data=[])
+status = io.writeFifo(nodes=[8], name="REG_INPUT", data=[])
+
+  - nodes  = list of nodes to write
+             if nodes[0] == 'all', all nodes are selected
+             if nodes[0] == 'front', all front nodes are selected
+             if nodes[0] == 'back', all back nodes are selected
+  - name   = name of register to write:
+             for names see register_info_fn.py or register_info_bn.py
+  - offset = word offset from base-address
+  - data   = data to write, list of values
+  - status = list of tuples for each node one [(node_no, status),..]
+             status = 0: OK
+             status = 1: register error
+             status = 2: communication timeout
+             status = 3: communication format error
+
+
+# To read data from
+# - a memory register 0x0 .. 0xffffffff
+# - a fifo register
+
+response = io.readRegister(nodes=[0,1,8,9], nvalues=8, name="REG_INPUT", offset=0)
+response = io.readFifo(nodes=[0,1,8,9], nvalues=8, name="REG_INPUT")
+
+  - nodes   = list of nodes to read
+             if nodes[0] == 'all', all nodes are selected
+             if nodes[0] == 'front', all nodes with front firmware are selected
+             if nodes[0] == 'back', all nodes with back firmware are selected
+  - nvalues = number of words to read
+  - name    = name of register to read (see address.py)
+  - offset  = word offset from base-address
+  - response= list of tuples, [(node_nr, status, data),..]
+
+
+# return status as string
+# status is returned status number from above functions
+#
+
+response = io.statusToStr(status)
+  - status 0 = ok
+  - status 1 = address error
+  - status 2 = communication timeout
+  - status 3 = communication format error
+"""
+
+
+class NodeIO():
+
+    # constants
+    UDP_BLOCK_SZ = 360
+
+    # status codes
+    ST_OK           = 0
+    ST_REGISTER_ERR = 1
+    ST_TIMEOUT_ERR  = 2
+    ST_FORMAT_ERR   = 3
+    ST_PACK_ERR     = 4
+    ST_UNPACK_ERR   = 5
+    ST_SIZE_ERR     = 6
+    ST_PSN_ERR      = 7
+    ST_MAX_RETRIES  = 8
+
+    # opcodes
+    MEMORY_READ  = 0x1
+    MEMORY_WRITE = 0x2
+    MODIFY_AND   = 0x3
+    MODIFY_OR    = 0x4
+    MODIFY_XOR   = 0x5
+    FLASH_WRITE  = 0x6
+    FLASH_READ   = 0x7
+    FLASH_ERASE  = 0x8
+    FIFO_READ    = 0x9
+    FIFO_WRITE   = 0xA
+
+    # retries
+    MAX_ATTEMPTS = 3
+
+    RECV_TIMEOUT = 0.2  # in seconds
+
+    def __init__(self, nodelist, base_ip ='localhost', port=5000):
+        port = 3335
+        if port == 3335:
+            self.use_unbserver = True
+            self.node = []
+            self.unbclient = unbclient_io.UNBclientIO(serverhost=base_ip,tcpport=port)
+        else:
+            self.use_unbserver = False
+            self.node = _Nodes(nodelist=nodelist, base_ip=base_ip)
+            self.unbclient = []
+
+        imp.reload(register_info)
+        self.register = Register()
+        #self.psn = self.getPSN()
+
+        if base_ip == 'sim':
+            import sim_io
+            self.sim = True
+            self.simIO = sim_io.SimIO(nodelist)
+        else:
+            self.sim = False
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self ,type, value, traceback):
+        self.register = False
+        self.node = dict()
+        return False
+
+    def __del__(self):
+        self.register = False
+        self.node = dict()
+
+    # generate random Packet Sequence Number
+    def getPSN(self):
+        return random.randint(0x0,0xffffffff)
+
+    def pack(self, data):
+        fmt = "I" * len(data)
+        return struct.pack(fmt, *data)
+
+    def unpack(self, packedData):
+        fmt = "I" * int(len(packedData) / 4)
+        return struct.unpack(fmt, packedData)
+
+    def statusToStr(self, status):
+        if status == self.ST_OK:
+            status_str = 'OK'
+        elif status == self.ST_REGISTER_ERR:
+            status_str = "address error"
+        elif status == self.ST_TIMEOUT_ERR:
+            status_str = "communication timeout"
+        elif status == self.ST_FORMAT_ERR:
+            status_str = "communication format error"
+        elif status == self.ST_PACK_ERR:
+            status_str = "pack error"
+        elif status == self.ST_UNPACK_ERR:
+            status_str = "unpack error"
+        elif status == self.ST_SIZE_ERR:
+            status_str = "size error"
+        elif status == self.ST_PSN_ERR:
+            status_str = "psn not equal error"
+        elif status == self.ST_MAX_RETRIES:
+            status_str = "max retries error"
+        else:
+            status_str = "unknown status code"
+        return status_str
+
+    def setNofRetries(self, nof_retries):
+        self.MAX_ATTEMPTS = nof_retries
+
+    def _empty_recv_buffers(self, nodes):
+        _node_can_recv = self.node.can_recv(nodes)
+        while len(_node_can_recv) > 0:
+            print("empty receive buffers for %s" % str(_node_can_recv))
+            for i in _node_can_recv:
+                _status, _data = self.node.recv(i)
+            _node_can_recv = self.node.can_recv(nodes)
+
+    # return a list of tuples for each node [(node_no, status),..]
+    def write(self, nodes, opcode, address, data, wait_for_response=True):
+        tic = time.time()
+        _nodes    = {i: 0 for i in nodes}
+        status    = {}
+        psn       = {i: self.getPSN() for i in nodes}
+        attempts  = {i: 0 for i in nodes}
+        n2send    = {i: 0 for i in nodes}
+        nsend     = {i: 0 for i in nodes}
+        addr      = {i: 0 for i in nodes}
+        ndata     = len(data)
+
+        for i in nodes:
+            if address[i] != -1:
+                status[i] = self.ST_OK
+            else:
+                status[i] = self.ST_REGISTER_ERR
+                del _nodes[i]
+
+        self._empty_recv_buffers(nodes)
+
+        main_timeout = time.time() + self.RECV_TIMEOUT
+        while True:
+            if time.time() > main_timeout:
+                for i in _nodes:
+                    status[i] = self.ST_TIMEOUT_ERR
+                print("node_io.wite() cmd_timeout for {}".format(sorted(_nodes)))
+                break
+
+            for i in sorted(_nodes):
+                if attempts[i] >= self.MAX_ATTEMPTS:
+                    status[i] = self.ST_TIMEOUT_ERR
+                    del _nodes[i]
+                else:
+                    status[i] = self.ST_OK
+
+            for i in sorted(_nodes):
+                if opcode == self.FIFO_WRITE:
+                    addr[i] = address[i]
+                else:
+                    addr[i] = address[i] + (nsend[i] * cm.c_word_sz)
+
+                n2send[i] = min(self.UDP_BLOCK_SZ, ndata - nsend[i])
+                block_start = nsend[i]
+                block_end   = block_start + n2send[i]
+                #print "node {}, write data[{}:{}] {:08x} to addr={:08x}".format(i, block_start, block_end, data[block_start:block_end], addr[i])
+                packed_data = self.pack(data[block_start:block_end])  # CHECK
+                packed_header = self.pack([psn[i]] + [opcode] + [n2send[i]] + [addr[i]])
+                packed_payload = packed_header + packed_data
+                self.node.send(i, packed_payload)
+                attempts[i] += 1
+
+                if wait_for_response is False:
+                    del _nodes[i]
+                    status[i] = self.ST_OK
+                    #time.sleep(0.5)
+
+            _recv = sorted(_nodes)
+            recv_timeout = time.time() + self.RECV_TIMEOUT
+            main_timeout = recv_timeout # + 0.5
+            while _recv:
+                if time.time() > recv_timeout:
+                    for i in _recv:
+                        status[i] = self.ST_TIMEOUT_ERR
+                    print("node_io.wite() recv_timeout for {}".format(sorted(_recv)))
+                    _recv = []
+                    continue
+
+                _node_can_recv = self.node.can_recv(_recv)
+                #print("i can receive = ", str(_node_can_recv))
+                for i in _node_can_recv:
+                    try:
+                        (status[i], raw_data) = self.node.recv(i)
+                        if status[i] == self.ST_TIMEOUT_ERR:
+                            print("timeout for node %s" % str(i))
+                            raise Exception
+
+                        data_in = self.unpack(raw_data)
+                        if data_in[0] != psn[i]:
+                            print("psn not equal for node %s" % str(i))
+                            status[i] = self.ST_PSN_ERR
+                            raise Exception
+                        elif data_in[1] != addr[i]:
+                            print("addr not equal for node %s" % str(i))
+                            status[i] = self.ST_FORMAT_ERR
+                            raise Exception
+
+                        nsend[i] += n2send[i]
+                    except IndexError:
+                        print("size error for node %s" % str(i))
+                        status[i] = self.ST_SIZE_ERR
+                    except struct.error:
+                        print("struct unpack error for node %s" % str(i))
+                        status[i] = self.ST_UNPACK_ERR
+                    except:
+                        pass
+
+                    if status[i] == self.ST_OK:  # communication was ok
+                        _recv.remove(i)
+                    if nsend[i] == ndata:
+                        del _nodes[i]
+
+            if not _nodes:
+                break
+
+        all_status = []
+        for i in nodes:
+            all_status.append((i, status[i]))
+        toc = time.time()
+        #print("write time = %f sec" % (toc-tic))
+        return all_status
+
+
+    # return a list of lists for each node one
+    # [] = error
+    def read(self, nodes, opcode, address, nvalues):
+        tic = time.time()
+        _nodes    = {i: 0 for i in nodes}
+        status    = {}
+        data      = {}
+        psn       = {i: self.getPSN() for i in nodes}
+        attempts  = {i: 0 for i in nodes}
+        nrequest  = {i: 0 for i in nodes}
+        nreceived = {i: 0 for i in nodes}
+        addr      = {i: 0 for i in nodes}
+
+        for i in nodes:
+            data[i] = []
+            if address[i] != -1:
+                status[i] = self.ST_OK
+            else:
+                status[i] = self.ST_REGISTER_ERR
+                del _nodes[i]
+
+        self._empty_recv_buffers(nodes)
+
+        main_timeout = time.time() + self.RECV_TIMEOUT
+        while True:
+            if time.time() > main_timeout:
+                for i in _nodes:
+                    status[i] = self.ST_TIMEOUT_ERR
+                print("node_io.read() cmd_timeout for nodes {}".format(sorted(_nodes)))
+                break
+
+            for i in sorted(_nodes):
+                if attempts[i] >= self.MAX_ATTEMPTS:
+                    status[i] = self.ST_TIMEOUT_ERR
+                    del _nodes[i]
+                else:
+                    status[i] = self.ST_OK
+
+            for i in _nodes:
+                if opcode == self.FIFO_READ:
+                    addr[i] = address[i]
+                else:
+                    addr[i] = address[i] + (nreceived[i] * cm.c_word_sz)
+
+                nrequest[i] = min(self.UDP_BLOCK_SZ, nvalues - nreceived[i])
+                packed_header = self.pack([psn[i]] + [opcode] + [nrequest[i]] + [addr[i]])
+                self.node.send(i, packed_header)
+                attempts[i] += 1
+                if nvalues > 64:
+                    time.sleep(0.001)
+
+            _recv = sorted(_nodes)
+            recv_timeout = time.time() + self.RECV_TIMEOUT
+            #main_timeout = recv_timeout + 0.5
+            while _recv:  # if True all requested nodes has respond
+                if time.time() > recv_timeout:
+                    for i in _recv:
+                        status[i] = self.ST_TIMEOUT_ERR
+                    print("node_io.read() recv_timeout for nodes {}".format(sorted(_recv)))
+                    _recv = []
+                    continue
+
+                _node_can_recv = self.node.can_recv(_recv)
+                for i in _node_can_recv:
+                    try:
+                        status[i], raw_data = self.node.recv(i)
+                        if status[i] == self.ST_TIMEOUT_ERR:
+                            print("timeout for node %s" % str(i))
+                            raise Exception
+
+                        data_in = self.unpack(raw_data)
+                        if data_in[0] != psn[i]:
+                            print("psn not equal for node %s" % str(i))
+                            status[i] = self.ST_PSN_ERR
+                            raise Exception
+                        elif data_in[1] != addr[i]:
+                            print("addr not equal for node %s" % str(i))
+                            status[i] = self.ST_FORMAT_ERR
+                            raise Exception
+
+                        data[i].extend(data_in[2:])
+                        nreceived[i] = len(data[i])
+
+                        if status[i] == self.ST_OK:  # communication was ok
+                            if len(data_in[2:]) == nrequest[i]:
+                                _recv.remove(i)  # expected data size received this time
+                            if nreceived[i] == nvalues:
+                                del _nodes[i]
+
+                    except IndexError:
+                        print("size error for node %s" % str(i))
+                        data[i] = []
+                        status[i] = self.ST_SIZE_ERR
+                    except struct.error:
+                        print("struct unpack error for node %s" % str(i))
+                        status[i] = self.ST_UNPACK_ERR
+                    except:
+                        print(('Caught %s', str(sys.exc_info()[0])))
+                        print((str(sys.exc_info()[1])))
+                        print(('TRACEBACK:\n%s', traceback.format_exc()))
+                        print('Aborting NOW')
+                        sys.exit('error')
+                        pass
+
+                    if status[i] == self.ST_OK:
+                        attempts[i] -= 1  # needed if more than 64 values must be send
+
+            if not _nodes:  # if True all nodes done
+                break
+
+        all_result = []
+        for i in nodes:
+            all_result.append((i, status[i], data[i]))
+        toc = time.time()
+        #print("read time = %f" %(toc-tic))
+        return all_result
+
+
+    # Commands for user
+    def writeRegister(self, nodes, name, offset, data, wait_for_response=True):
+        if self.sim is True:
+            response = self.simIO.writeRegister(nodes, name, offset, data)
+        elif self.use_unbserver==True:
+            (ret,out,err) = self.unbclient.mwrite(nodes, name, offset, data)
+            response = []
+            for nn in nodes:
+                if ret==True:
+                    response.append((nn, self.ST_OK))
+                else:
+                    response.append((nn, self.ST_REGISTER_ERR))
+                    print("error:",err)
+        else:
+            address = self.register.getValidAddr(nodes, name, offset, len(data))
+            response = self.write(nodes, self.MEMORY_WRITE, address, data, wait_for_response)
+        return response
+
+    def logWriteRegisterStatus(self, tc, status, piStr, regStr, instanceStr='', data=[], vLevel=5):
+        # Evaluate per node
+        for nodeNr, nodeStatus in status:
+            if instanceStr == '':
+                nodeStr = tc.to_node_string(nodeNr)
+            else:
+                nodeStr = tc.to_node_string(nodeNr, ', ') + instanceStr
+            if nodeStatus != self.ST_OK:
+                tc.append_log(2, piStr + nodeStr + regStr + ' FAILED (%d = %s)' % (nodeStatus, self.statusToStr(nodeStatus)))
+                tc.set_result('FAILED')
+                sys.exit()
+            elif len(data) == 1:
+                # Log the register access string and only log the written data here if it is only one word
+                tc.append_log(vLevel, piStr + nodeStr + regStr + ' = %d' % data[0])
+            else:
+                # Only log the register access string. For lists of multiple data do not log the written data and let the user decide how to log it outside this function (simply leave data argument empty)
+                tc.append_log(vLevel, piStr + nodeStr + regStr)
+
+    def readRegister(self, nodes, name, offset, nvalues):
+        print("readRegister nodes=",nodes," name=",name," offset=",offset," nvalues=",nvalues)
+        if self.sim is True:
+            response = self.simIO.readRegister(nodes, name, offset, nvalues)
+        elif self.use_unbserver==True:
+            (ret,response,err) = self.unbclient.mread(nodes, name, offset, nvalues)
+            if ret==True:
+                print("error:",err)
+        else:
+            address = self.register.getValidAddr(nodes, name, offset, nvalues)
+            response = self.read(nodes, self.MEMORY_READ, address, nvalues)
+        print("readRegister response=",response)
+        return response
+
+    def logReadRegisterStatus(self, tc, status, piStr, regStr, instanceStr='', vLevel=9):
+        # Purpose :
+        # . in case the node access FAILED set_result('FAILED') and log it with vLevel = 2
+        # . default with vLevel=9 nothing is logged when the access went OK, but the by lowering vLevel it could log the access in case the caller does not do any logging
+        # Evaluate per node
+        for nodeNr, nodeStatus, nodeData in status:
+            if instanceStr == '':
+                nodeStr = tc.to_node_string(nodeNr)
+            else:
+                nodeStr = tc.to_node_string(nodeNr, ', ') + instanceStr
+            if nodeStatus != self.ST_OK:
+                tc.append_log(2, piStr + nodeStr + regStr + ' FAILED (%d = %s)' % (nodeStatus, self.statusToStr(nodeStatus)))
+                tc.set_result('FAILED')
+                sys.exit()
+            else:
+                # Only log the register access string. Do not log the read data and let the user decide how to log it outside this function
+                tc.append_log(vLevel, piStr + nodeStr + regStr)
+
+    def writeFifo(self, nodes, name, data):
+        address = self.register.getValidAddr(nodes, name, 0, 0)
+        response = self.write(nodes, self.FIFO_WRITE, address, data)
+        return response
+
+    def readFifo(self, nodes, name, nvalues):
+        address = self.register.getValidAddr(nodes, name, 0, 0)
+        response = self.read(nodes, self.FIFO_READ, address, nvalues)
+        return response
+
+    def wait_for_time(self, hw_time=0, sim_time=(0, 'us')):
+        if self.sim is False:
+            time.sleep(hw_time)                                     # hw_time argument has unit seconds and can be a floating point number, so no need for us or ms sleep method
+        else:
+            self.simIO.wait_for_sim_time(sim_time[0], sim_time[1])  # sim_time argument has a unit argument in simulation
+
+    def technology(self, node_nr=None):
+        if node_nr in register_info.technology:
+            return register_info.technology[node_nr]
+        return 'unknown node nr'
+
+    def design_name(self, node_nr=None):
+        if node_nr in register_info.design_name:
+            return register_info.design_name[node_nr]
+        return 'unknown node nr'
+
+    def is_unb1(self, node_nr=None):
+        if node_nr is None:
+            node_nr = sorted(register_info.technology.keys())[0]
+        return tech.tech_to_unb(tech.str_to_tech(register_info.technology[node_nr])) == 1
+
+    def is_unb2(self, node_nr=None):
+        if node_nr is None:
+            node_nr = sorted(register_info.technology.keys())[0]
+        return tech.tech_to_unb(tech.str_to_tech(register_info.technology[node_nr])) == 2
+
+
+# Nodes class that holds udp ports for all nodes
+class _Nodes():
+    NODE_NO = 0
+    NODE_TYPE = 1
+    NODE_PORT = 2
+
+    def __init__(self, nodelist, base_ip='localhost', port=5000):
+        self.node = dict()
+        self.fn   = list()
+        self.bn   = list()
+        self.pn2  = list()
+
+        for (node_no, node_type) in nodelist:
+            if node_type == 'FN':
+                self.fn.append(node_no)
+            elif node_type == 'BN':
+                self.bn.append(node_no)
+            elif node_type == 'PN2':
+                self.pn2.append(node_no)
+
+        if base_ip == 'localhost':
+            print(nodelist)
+            (node_no, node_type) = nodelist[0]
+            self.node[node_no] = (node_no, node_type, udp.Client(host=base_ip, port=port))
+        elif base_ip != 'sim':
+            base_ip_i = [int(i) for i in base_ip.split('.')]
+            _ip_i = deepcopy(base_ip_i)
+            for (node_no, node_type) in nodelist:
+                if node_type in ['FN', 'BN']:
+                    node_id  = node_no % unb.c_nof_nodes
+                    board_id = node_no // unb.c_nof_nodes
+                else:
+                    node_id  = node_no % unb.c_nof_nodes
+                    board_id = node_no // unb.c_nof_nodes
+                _ip_i[2] = base_ip_i[2] + board_id
+                _ip_i[3] = base_ip_i[3] + node_id
+                _ip_s = [str(i) for i in _ip_i]
+                host = '.'.join(_ip_s)
+                self.node[node_no] = (node_no, node_type, udp.Client(host=host, port=port))
+
+    def __del__(self):
+        self.node = dict()
+        self.fn   = dict()
+        self.bn   = dict()
+        self.pn2  = dict()
+
+    def nodeType(self, node):
+        return self.node[node][self.NODE_TYPE]
+
+    def send(self, node_nr, data):
+        # print("send node %d" %(node_nr))
+        self.node[node_nr][self.NODE_PORT].send(data)
+
+    def recv(self, node_nr):
+        # print("recv node %d" %(node_nr))
+        return self.node[node_nr][self.NODE_PORT].recv()
+
+    def can_recv_node_nr(self, node_nr):
+        sck = self.node[node_nr][self.NODE_PORT].sock
+        socket_list = [sck]
+        # Get the list sockets which are readable
+        read_sockets, write_sockets, error_sockets = select.select(socket_list , [], [], 0)
+        if sck in read_sockets:
+            return True
+        return False
+
+    def can_recv(self, nodes):
+        #print("nodes={}".format(nodes))
+        sockets = {self.node[i][self.NODE_PORT].sock: i for i in nodes}
+        # Get the list sockets which are readable
+        read_sockets, write_sockets, error_sockets = select.select(list(sockets.keys()) , [], [], 0)
+        #if read_sockets != []:
+        #    print("read_sockets={}".format(read_sockets))
+        #print("write_sockets={}".format(write_sockets))
+        #print("error_sockets={}".format(error_sockets))
+        node_list = []
+        for sck in read_sockets:
+            node_list.append(sockets[sck])
+        return node_list
+
+# Register class
+class Register():
+
+    def __init__(self):
+        self.reg = None
+        self.reg_info = {'PIO_SYSTEM_INFO':    [0x00000000, 128],
+                         'ROM_SYSTEM_INFO':    [0x00001000, 4096],
+                         'ROM_SYSTEM_INFO_V2': [0x00010000, 32768]}
+
+    def setRegister(self, nodenumber):
+        self.reg = register_info.nodes[nodenumber]
+        return True
+
+    def getBaseAddr(self, nodenumber, name):
+        if self.setRegister(nodenumber):
+            return self.reg.nodes[nodenumber][name][0]
+        return -1
+
+    def getSpan(self, nodenumber, name):
+        if self.setRegister(nodenumber):
+            return self.reg.nodes[nodenumber][name][1]
+        return -1
+
+    def isValid(self, nodenumber, name, offset, size):
+        if name in self.reg_info:
+            return True
+        if self.setRegister(nodenumber):
+            if name in self.reg.register:
+                base, span = self.reg.nodes[nodenumber][name]
+                if (offset + size) * cm.c_word_sz <= span:
+                    return True
+        return False
+
+    def getValidAddr(self, nodenumbers, name, offset, size):
+        """
+        name: Name of register
+        offset: offset to base address in words
+        size: register size to get in words
+        """
+
+        _offset = offset * cm.c_word_sz
+        end = (size + offset) * cm.c_word_sz
+        addresses = dict()
+
+        for nodenumber in nodenumbers:
+            if name in self.reg_info:
+                reg_info = self.reg_info[name]
+            elif name in register_info.nodes[nodenumber]:
+                reg_info = register_info.nodes[nodenumber][name]
+            else:
+                reg_info = None
+
+            if reg_info is not None:
+                base = reg_info[0]
+                span = reg_info[1]
+                addr = base + _offset
+                if end > span:
+                    addr = -1
+            else:
+                 addr = -1
+
+            addresses[nodenumber] = addr
+
+        return addresses
+
diff --git a/test/upe/unbclient_io.py b/test/upe/unbclient_io.py
new file mode 100644
index 0000000000000000000000000000000000000000..b545bcb81bbda31c6404aec87362230e8e443465
--- /dev/null
+++ b/test/upe/unbclient_io.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python
+
+import time
+import socket
+import struct
+
+class UNBclientIO():
+    def __init__(self, serverhost, tcpport=3335):
+
+        self.myName = 'UNBclientIO'
+        self.pId = self.myName + ' - '
+        self.tcpport = tcpport
+        self.serverhost = serverhost
+        self.serverhost = 'localhost'
+
+        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.s.connect((self.serverhost, self.tcpport))
+        
+        (ret,banner) = self.read_section('output')
+        self.read_prompt('')
+        print("Connected to unbserver on: ",self.serverhost," (tcpport=",self.tcpport,")")
+        print("Received banner: ",banner)
+
+    def __del__(self):
+        self.s.close()
+
+    def verify_section_name(self,buf,section_name):
+        ret=False
+        result=buf
+        #print("verify_section_name=",result)
+        col=buf.split('=')
+        verifyname=col[0].strip('\n')
+        
+        if verifyname == section_name:
+            ret=True
+            result=buf.strip('}')
+            col=result.split('{')
+            result=col[1]
+        return (ret,result)
+
+    def prefixok(self,buf):
+        col=buf.split(':')
+        prefix=col[0].strip('\n')
+        return prefix == 'sdpunb'
+
+    def cmdname(self,buf):
+        col=buf.split(':')
+        return col[1]
+
+    def cmdok(self,buf):
+        col=buf.split(':')
+        print("cmdok=",col[2])
+        return col[2] == '0'
+
+    def read_until_eos(self,eos):
+        new_in = 0
+        can_read = 0
+        while new_in==0:
+            buf = self.s.recv(600000, socket.MSG_PEEK)
+            if not buf:
+                print('server is disconnected')
+                break
+            else:
+                i=0
+                for c in buf:
+                    if chr(c)==eos:
+                        can_read=i+1
+                        new_in=1
+                        break
+                    else:
+                        i+=1
+
+        buf = self.s.recv(can_read)
+        print('received:',buf)
+        return buf.decode('utf-8')
+
+    def read_section(self,section):
+        result_found=False
+        response=''
+        while result_found==False:
+            buf=self.read_until_eos('}')
+            if not buf:
+                print('server is disconnected')
+                result_found=True
+                break
+            else:
+                (result_found,response)=self.verify_section_name(buf,section)
+                if result_found==False:
+                    print('Unexpected Response of section=',section)
+
+        return (result_found,response)
+
+    def read_prompt(self,cmdname):
+        result_found=False
+        response=''
+        while result_found==False:
+            buf=self.read_until_eos('>')
+            if not buf:
+                print('server is disconnected')
+                result_found=True
+                break
+            else:
+                if self.prefixok(buf) == False:
+                    print('Unexpected Response, Prefix NOT OK')
+                else:
+                    if self.cmdname(buf) == cmdname:
+                        result_found=True
+        return self.cmdok(buf)
+
+    def find_response(self, cmdname=''):
+        (ret,out) = self.read_section('output')
+
+        (ret,err) = self.read_section('errors')
+        if cmdname != '':
+            ret=self.read_prompt(cmdname)
+        return (ret,out,err)
+
+#==================================================================================
+    def nodes_to_unb_pn(self, nodes):
+        unbs=[]
+        pns=[]
+        for n in nodes:
+            unbs.append(n >> 3)
+            pns.append(n % 4)
+        u_unbs = list(set(unbs)) # use only unique values
+        u_pns = list(set(pns))
+        return (u_unbs, u_pns)
+
+    def mwrite(self, nodes, addrname, offset, data):
+        cmdname='write'
+        ret=False
+
+        (unbs,pns) = self.nodes_to_unb_pn(nodes)
+
+        cmd  = cmdname + ' --offs=' + str(offset)
+        cmd += ' --data=' + str(data).replace(" ","")
+        cmd += ' /unb' + str(unbs).replace(" ","") + '/pn' + str(pns).replace(" ","")
+        cmd += '/mm/' + addrname
+
+        print("send:",cmd)
+        my_cmd_as_bytes = str.encode(cmd + '\n')
+        self.s.send(my_cmd_as_bytes)
+        (ret,out,err) = self.find_response(cmdname)
+        err=err.strip('\n')
+        print('err=',err)
+        print('out=',out)
+        print('ret=',ret)
+        return (ret,out,err.strip('\n'))
+
+    def mread(self, nodes, addrname, offset, nvalues):
+        cmdname='read'
+        ret=False
+
+        (unbs,pns) = self.nodes_to_unb_pn(nodes)
+
+        cmd  = cmdname + ' --nvalues=' + str(nvalues)
+        cmd += ' --offs=' + str(offset)
+        cmd += ' /unb' + str(unbs).replace(" ","") + '/pn' + str(pns).replace(" ","")
+        cmd += '/mm/' + addrname
+
+        print("send:",cmd)
+        my_cmd_as_bytes = str.encode(cmd + '\n')
+        self.s.send(my_cmd_as_bytes)
+        (ret,out,err) = self.find_response(cmdname)
+        err=err.strip('\n')
+        print('err=',err)
+        print('out=',out)
+        print('ret=',ret)
+        if ret == True:
+            node_and_value=eval(out)
+        else:
+            node_and_value=[]
+            for n in nodes:
+                node_and_value.append((n,1,[])) # FIXME: sdpunb server better do this
+        return (ret,node_and_value,err.strip('\n'))
+