diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..add715602bd0de9c242d2e1312668d7ee28ed6de
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,74 @@
+include dir_list.mk
+
+SYN_TOP = $(TOP)/syn/cute_wr/wr_core_demo
+EB_TGT = $(EB_DIR)/api/libetherbone.a
+BIT_TGT = $(SYN_TOP)/cute_top_wrc.bit
+WRC_TGT = $(FW_DIR)/wrc.elf
+
+.PHONY: all install clean distclean etherbone etherbone-clean etherbone-install tools tools-clean tools-install toolchain firmware gateware firmware-clean gateware-clean program program-clean
+
+all:	    etherbone tools firmware gateware
+
+install:    etherbone-install tools-install
+
+clean:	    etherbone-clean tools-clean toolchain-clean firmware-clean gateware-clean program-clean
+
+distclean: clean
+	git clean -xfd .
+	for i in gateware firmware; do cd submodules/$$i; git clean -xfd .; cd $(TOP); done
+
+etherbone: $(EB_TGT)
+firmware: $(WRC_TGT)
+gateware: $(BIT_TGT)
+
+$(EB_TGT):
+	$(MAKE) -C $(EB_DIR)/api all
+
+etherbone-clean:
+	$(MAKE) -C $(EB_DIR)/api clean
+
+etherbone-install:
+	$(MAKE) -C $(EB_DIR)/api install
+
+tools: etherbone
+	$(MAKE) -C tools EB=$(EB_DIR)/api all
+
+tools-clean:
+	$(MAKE) -C tools EB=$(EB_DIR)/api clean
+
+tools-install:
+	$(MAKE) -C tools EB=$(EB_DIR)/api install
+
+$(FW_DIR)/.config:
+	$(MAKE) -C $(FW_DIR) etherbone_defconfig
+
+$(WRC_TGT): $(EB_TGT) $(FW_DIR)/.config
+	$(MAKE) -C $(FW_DIR) EB=$(EB_DIR)/api all
+
+firmware-clean:
+	$(MAKE) -C $(FW_DIR) EB=$(EB_DIR)/api clean
+
+$(BIT_TGT): firmware
+	$(MAKE) -C $(SYN_TOP) all
+
+gateware-clean:
+	$(MAKE) -C $(SYN_TOP) clean
+
+program: $(BIT_TGT)
+	./tools/impact_cutewr $<
+
+program-clean:
+	rm -f *.bit
+	rm -f _impactbatch.log
+
+# not for 64 bit arch
+gcc-4.5.3-lm32.tar.xz:
+	wget http://www.ohwr.org/attachments/1301/gcc-4.5.3-lm32.tar.xz
+
+toolchain:  gcc-4.5.3-lm32.tar.xz
+	tar xvJf gcc-4.5.3-lm32.tar.xz
+	mv lm32 toolchain
+	touch toolchain
+
+toolchain-clean:
+	rm -rf toolchain
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f08786e6563634458784d55d873359cfd7a3fbd4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# CUTE-wr projects
+
+## Code structure
+* Gateware:
+    `submodules/wr-cores`
+    cute-wr branch that work on hardware version 2.1
+
+* Firmware:
+    `submodules/wrpc-sw`
+    Full support of substituting wrpc RAM in target bit file using data2mem. See http://www.ohwr.org/issues/1006
+
+* Software:
+    multiboot support
+    TBD
+
+## Building
+    git submodule update --recursive --init
+    make
diff --git a/dir_list.mk b/dir_list.mk
new file mode 100644
index 0000000000000000000000000000000000000000..df1a03911fb99ecccc3b53786f1e1429cfd3e8de
--- /dev/null
+++ b/dir_list.mk
@@ -0,0 +1,5 @@
+TOP	    := $(dir $(lastword $(MAKEFILE_LIST)))
+SUBMODULES  := $(TOP)submodules
+GW_DIR	    := $(SUBMODULES)/wr-cores
+FW_DIR	    := $(SUBMODULES)/wrpc-sw
+EB_DIR	    := $(GW_DIR)/ip_cores/etherbone-core
diff --git a/submodules/hdl-make b/submodules/hdl-make
index aabe6c840ed4d8414dd62bb67781e10672184103..b0ccccc6a3c687408cf706ca1ec93556872598b6 160000
--- a/submodules/hdl-make
+++ b/submodules/hdl-make
@@ -1 +1 @@
-Subproject commit aabe6c840ed4d8414dd62bb67781e10672184103
+Subproject commit b0ccccc6a3c687408cf706ca1ec93556872598b6
diff --git a/syn/cute_wr/wr_core_demo/Makefile b/syn/cute_wr/wr_core_demo/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3aec1e0a8d38d1de7a99cd7d2cba3c392c9a6b57
--- /dev/null
+++ b/syn/cute_wr/wr_core_demo/Makefile
@@ -0,0 +1,29 @@
+TARGET = cute_top
+PROJECT = cute_top_wrc
+
+include ../../../dir_list.mk
+
+.PHONY: all
+all: $(TARGET)_wrc.bit
+
+$(PROJECT).xise: Manifest.py
+	python2.7 $(TOP)submodules/hdl-make/hdlmake ise-project
+
+# Xilinx UG658
+$(TARGET)_wrc.bit: $(FW_DIR)/wrc.elf $(TARGET).bit $(TARGET)_bd.bmm
+	data2mem -bm $(word 3, $^) -bd $< -bt $(word 2, $^) -o b $@
+
+$(TARGET).bmm:  $(GW_DIR)/top/cute_wr/wr_core_demo/cute.bmm
+	cp $< $@
+
+$(TARGET)_bd.bmm $(TARGET).bit: $(PROJECT).xise $(TARGET).bmm
+	printf "project open $< \n\
+	    project set {Hierarchy Separator} / \n\
+	    xfile add $(word 2, $^) \n\
+	    process run {Generate Programming File} -force rerun" | xtclsh
+
+ISE_CRAP := *.b *.html $(TARGET).bgn $(TARGET).bld $(TARGET).cmd_log *.drc $(TARGET).lso *.ncd $(TARGET).ngc $(TARGET).ngd $(TARGET).ngr $(TARGET).pad $(TARGET).par $(TARGET).pcf $(TARGET).prj $(TARGET).ptwx $(TARGET).stx $(TARGET).syr $(TARGET).twr $(TARGET).twx $(TARGET).gise $(TARGET).unroutes $(TARGET).ut $(TARGET).xpi $(TARGET).xst $(TARGET)_bitgen.xwbt $(TARGET)_guide.ncd $(TARGET)_map.map $(TARGET)_map.mrp $(TARGET)_map.ncd $(TARGET)_map.ngm $(TARGET)_map.xrpt $(TARGET)_ngdbuild.xrpt $(TARGET)_pad.csv $(TARGET)_pad.txt $(TARGET)_par.xrpt $(TARGET)_summary.xml $(TARGET)_usage.xml $(TARGET)_xst.xrpt webtalk.log webtalk_pn.xml run.tcl $(PROJECT).gise
+
+clean:
+	rm -f $(PROJECT).xise $(ISE_CRAP) *.bmm
+	rm -rf xst xlnx_auto_*_xdb iseconfig _xmsgs _ngo
diff --git a/syn/cute_wr/wr_core_demo/Manifest.py b/syn/cute_wr/wr_core_demo/Manifest.py
new file mode 100644
index 0000000000000000000000000000000000000000..97eb180c062490a654618b56dd41232ca4265483
--- /dev/null
+++ b/syn/cute_wr/wr_core_demo/Manifest.py
@@ -0,0 +1,15 @@
+target = "xilinx"
+action = "synthesis"
+
+syn_device = "xc6slx45t"
+syn_grade = "-3"
+syn_package = "fgg484"
+syn_top = "cute_top"
+top_module = "cute_top"
+syn_project = "cute_top_wrc.xise"
+
+modules = {
+    "local" : [
+        "../../../submodules/wr-cores/top/cute_wr/wr_core_demo"
+    ]
+}
diff --git a/tools/Makefile b/tools/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3855009f05d664f6c3da406562ac73329409fac9
--- /dev/null
+++ b/tools/Makefile
@@ -0,0 +1,21 @@
+PREFIX  ?= /usr/local
+STAGING ?=
+EB      ?= ../submodules/wr-cores/ip_cores/etherbone-core/api
+TARGETS := eb-flash eb-info eb-console
+
+EXTRA_FLAGS ?=
+CFLAGS      ?= $(EXTRA_FLAGS) -Wall -O2 -I $(EB)
+LIBS        ?= -L $(EB) -Wl,-rpath,$(PREFIX)/lib -letherbone
+
+all:	$(TARGETS)
+clean:
+	rm -f $(TARGETS)
+install:
+	mkdir -p $(STAGING)$(PREFIX)/bin
+	cp $(TARGETS) $(STAGING)$(PREFIX)/bin
+
+%:	%.c
+	gcc $(CFLAGS) -o $@ $< $(LIBS)
+
+%:	%.cpp
+	g++ $(CFLAGS) -o $@ $< $(LIBS) -leca -ltlu
diff --git a/tools/eb-console.c b/tools/eb-console.c
new file mode 100644
index 0000000000000000000000000000000000000000..60d0bc0efb9c946e855a4e689dd5c563affe4026
--- /dev/null
+++ b/tools/eb-console.c
@@ -0,0 +1,192 @@
+/** @file eb-info.c
+ *  @brief Report the contents of an FPGA using Etherbone.
+ *
+ *  Copyright (C) 2013 GSI Helmholtz Centre for Heavy Ion Research GmbH 
+ *
+ *  A complete skeleton of an application using the Etherbone library.
+ *
+ *  @author Wesley W. Terpstra <w.terpstra@gsi.de>
+ *
+ *******************************************************************************
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 3 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *******************************************************************************
+ */
+
+#define _XOPEN_SOURCE 500 /* strtoull, usleep, tcgetattr */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <etherbone.h>
+#include <unistd.h> /* getopt */
+#include <termios.h>
+#include <fcntl.h>
+
+#define CERN_ID	0xce42
+#define UART_ID	0xe2d13d04
+#define VUART_TX 0x10
+#define VUART_RX 0x14
+#define STDIN_FD 0
+#define BATCH_SIZE 200
+
+const char *program;
+
+static void help(void) {
+  fprintf(stderr, "Usage: %s [OPTION] <proto/host/port>\n", program);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "  -i <index>     select between multiple uarts\n");
+  fprintf(stderr, "  -h             display this help and exit\n");
+  fprintf(stderr, "\n");
+  fprintf(stderr, "Report bugs to <w.terpstra@gsi.de>\n");
+}
+
+static void die(const char* msg, eb_status_t status) {
+  fprintf(stderr, "%s: %s: %s\n", program, msg, eb_status(status));
+  exit(1);
+}
+
+int main(int argc, char** argv) {
+  int opt, error, c, i, flags, busy;
+  char *tail;
+  struct sdb_device sdb[10];
+  struct termios tc, old;
+  eb_status_t status;
+  eb_socket_t socket;
+  eb_device_t device;
+  eb_address_t tx, rx;
+  eb_data_t rx_data[BATCH_SIZE], tx_data, done;
+  eb_cycle_t cycle;
+  char byte;
+  
+  /* Default arguments */
+  program = argv[0];
+  error = 0;
+  i = -1;
+  
+  /* Process the command-line arguments */
+  error = 0;
+  while ((opt = getopt(argc, argv, "i:h")) != -1) {
+    switch (opt) {
+    case 'h':
+      help();
+      return 0;
+    case 'i':
+      i = strtol(optarg, &tail, 0);
+      if (*tail != 0) {
+        fprintf(stderr, "%s: specify a proper number, not '%s'!\n", program, optarg);
+        error = 1;
+      }
+      break;
+    case ':':
+    case '?':
+      error = 1;
+      break;
+    default:
+      fprintf(stderr, "%s: bad getopt result\n", program);
+      error = 1;
+    }
+  }
+  
+  if (error) return 1;
+  
+  if (optind + 1 != argc) {
+    fprintf(stderr, "%s: expecting three non-optional arguments: <proto/host/port>\n", program);
+    return 1;
+  }
+  
+  if ((status = eb_socket_open(EB_ABI_CODE, 0, EB_DATAX|EB_ADDRX, &socket)) != EB_OK)
+    die("eb_socket_open", status);
+  
+  if ((status = eb_device_open(socket, argv[optind], EB_DATAX|EB_ADDRX, 3, &device)) != EB_OK)
+    die(argv[optind], status);
+  
+  c = sizeof(sdb)/sizeof(struct sdb_device);
+  if ((status = eb_sdb_find_by_identity(device, CERN_ID, UART_ID, &sdb[0], &c)) != EB_OK)
+    die("eb_sdb_find_by_identity", status);
+  
+  if (i == -1) {
+    if (c != 1) {
+      fprintf(stderr, "%s: found %d UARTs on that device; pick one with -i #\n", program, c);
+      exit(1);
+    } else {
+      i = 0;
+    }
+  }
+  if (i >= c) {
+    fprintf(stderr, "%s: could not find UART #%d on that device (%d total)\n", program, i, c);
+    exit(1);
+  }
+  
+  printf("Connected to uart at address %"PRIx64"\n", sdb[i].sdb_component.addr_first);
+  tx = sdb[i].sdb_component.addr_first + VUART_TX;
+  rx = sdb[i].sdb_component.addr_first + VUART_RX;
+  
+  /* disable input buffering and echo */
+  tcgetattr(STDIN_FD, &old);
+  tcgetattr(STDIN_FD, &tc);
+  tc.c_cflag = B9600 | CS8 | CLOCAL | CREAD;
+  tc.c_iflag = IGNPAR;
+  tc.c_oflag = 0;
+  tc.c_lflag = 0;
+  tc.c_cc[VMIN]=1;
+  tc.c_cc[VTIME]=0;
+  tcflush(STDIN_FD, TCIFLUSH);
+  tcsetattr(STDIN_FD, TCSANOW, &tc);
+  
+  flags = fcntl(STDIN_FD, F_GETFL, 0);
+  flags |= O_NONBLOCK;
+  fcntl(STDIN_FD, F_SETFL, flags);
+  
+  /* be lazy and just poll for now */
+  busy = 0;
+  while (1) {
+    if (!busy) usleep(10000); /* 10ms */
+    
+    /* Poll for status */
+    eb_cycle_open(device, 0, eb_block, &cycle);
+    eb_cycle_read(cycle, rx, EB_BIG_ENDIAN|EB_DATA32, &rx_data[0]);
+    eb_cycle_read(cycle, tx, EB_BIG_ENDIAN|EB_DATA32, &done);
+    eb_cycle_close(cycle);
+    
+    /* Bulk read anything extra */
+    if ((rx_data[0] & 0x100) != 0) {
+      eb_cycle_open(device, 0, eb_block, &cycle);
+      for (i = 1; i < BATCH_SIZE; ++i)
+        eb_cycle_read(cycle, rx, EB_BIG_ENDIAN|EB_DATA32, &rx_data[i]);
+      eb_cycle_close(cycle);
+    
+      for (i = 0; i < BATCH_SIZE; ++i) {
+        if ((rx_data[i] & 0x100) == 0) continue;
+        byte = rx_data[i] & 0xFF;
+        fputc(byte, stdout);
+      }
+      fflush(stdout);
+    }
+    
+    busy = busy && (done & 0x100) == 0;
+    
+    if (!busy && read(STDIN_FD, &byte, 1) == 1) {
+      if (byte == 3) { /* control-C */
+        tcsetattr(STDIN_FD, TCSANOW, &old);
+        exit(0);
+      }
+      tx_data = byte;
+      eb_device_write(device, tx, EB_BIG_ENDIAN|EB_DATA32, tx_data, eb_block, 0);
+      busy = 1;
+    }
+  }
+  
+  return 0;
+}
diff --git a/tools/eb-flash.c b/tools/eb-flash.c
new file mode 100644
index 0000000000000000000000000000000000000000..b3d47488ad55a39af7fdc4db1b34df2f23b9558f
--- /dev/null
+++ b/tools/eb-flash.c
@@ -0,0 +1,1042 @@
+/** @file eb-flash.c
+ *  @brief Program a flash device using Etherbone.
+ *
+ *  Copyright (C) 2013 GSI Helmholtz Centre for Heavy Ion Research GmbH 
+ *
+ *  A complete skeleton of an application using the Etherbone library.
+ *
+ *  @author Wesley W. Terpstra <w.terpstra@gsi.de>
+ *
+ *******************************************************************************
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 3 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *******************************************************************************
+ */
+
+#define _POSIX_C_SOURCE 200112L /* strtoull */
+
+#include <unistd.h> /* getopt */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <etherbone.h>
+
+#define GSI_ID   0x651
+#define FLASH_ID 0x5cf12a1c
+
+#define MAX_SECTOR_SIZE	4194304
+
+static const char* program;
+static const char* device_address;
+static const char* firmware;
+
+static eb_address_t address;
+static int address_set;
+static eb_address_t erase_address;
+static int erase_address_set;
+static eb_width_t address_width;
+static eb_width_t data_width;
+static eb_format_t endian;
+static eb_address_t sector_size;
+static eb_address_t page_size;
+static long wait_us;
+static int cycles_per_poll;
+static int retries;
+static int full;
+static int probe;
+static int verify;
+static int invert;
+static int verbose;
+static int quiet;
+static int old_erase;
+
+static eb_address_t firmware_length;
+static eb_format_t format;
+static eb_data_t ffs;
+static eb_data_t mask_0f;
+static uint8_t* erase_bitmap;
+static int erase_bitmap_size;
+static FILE* firmware_f;
+
+static void help(void) {
+  fprintf(stderr, "Usage: %s [OPTION] <proto/host/port> <firmware>\n", program);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "  -t <address>   target flash base address   (sdb-auto-located)\n");
+  fprintf(stderr, "  -e <address>   erase control register address (end-of-device)\n");
+  fprintf(stderr, "  -a <width>     acceptable address bus widths     (8/16/32/64)\n");
+  fprintf(stderr, "  -d <width>     acceptable data bus widths        (8/16/32/64)\n");
+  fprintf(stderr, "  -b             big-endian operation                    (auto)\n");
+  fprintf(stderr, "  -l             little-endian operation                 (auto)\n");
+  fprintf(stderr, "  -i <0/1>       invert bit order of bytes in firmware   (auto)\n");
+  fprintf(stderr, "  -s <bytes>     flash sector size                       (auto)\n");
+  fprintf(stderr, "  -x <bytes>     flash page size                          (256)\n");
+  fprintf(stderr, "  -w <seconds>   poll interval while erasing              (0.1)\n");
+  fprintf(stderr, "  -c <cycles>    number of cycles per bridge access         (4)\n");
+  fprintf(stderr, "  -r <retries>   number of times to attempt autonegotiation (3)\n");
+  fprintf(stderr, "  -f             full; skip quick scan and erase everything\n");
+  fprintf(stderr, "  -p             disable self-describing wishbone device probe\n");
+  fprintf(stderr, "  -n             do not verify contents after programming\n");
+  fprintf(stderr, "  -v             verbose operation\n");
+  fprintf(stderr, "  -q             quiet: do not display warnings\n");
+  fprintf(stderr, "  -h             display this help and exit\n");
+  fprintf(stderr, "\n");
+  fprintf(stderr, "Report bugs to <w.terpstra@gsi.de>\n");
+}
+
+static int bitmask_get(int offset) {
+  return (erase_bitmap[offset/8] >> (offset&7)) & 1;
+}
+
+static void bitmask_set(int offset, int value) {
+  uint8_t* val = &erase_bitmap[offset/8];
+  int bit = 1 << (offset&7);
+  *val = (*val&~bit)|(value?bit:0);
+}
+
+static eb_data_t flip_bits(eb_data_t x) {
+  eb_data_t mask;
+  if (!invert) return x;
+  
+  /* 0x0F0F */
+  mask = mask_0f;;
+  x = ((x >> 4) & mask) | ((x & mask) << 4);
+  
+  /* 0x0F0F -> 0x3333 */
+  mask ^= (mask << 2);
+  x = ((x >> 2) & mask) | ((x & mask) << 2);
+  
+  /* 0x3333 -> 0x5555 */
+  mask ^= (mask << 1);
+  x = ((x >> 1) & mask) | ((x & mask) << 1);
+  
+  return x;
+}
+
+static eb_status_t erase_sector(eb_device_t device, eb_address_t sector) {
+  eb_cycle_t cycle;
+  eb_format_t control;
+  eb_status_t status;
+  
+  control = (format&EB_ENDIAN_MASK) | EB_DATA32;
+  
+  if (old_erase)
+    return eb_device_write(device, erase_address, control, sector, 0, eb_block);
+  
+  if ((status = eb_cycle_open(device, 0, eb_block, &cycle)) != EB_OK) return status;
+  eb_cycle_write(cycle, erase_address, control, 0x6);        /* write enable */
+  eb_cycle_write(cycle, erase_address, control, 0x80000000); /* execute */
+  eb_cycle_write(cycle, erase_address, control, 0xD8);       /* sector erase */
+  if (((erase_address-address) >> 24) != 0) 
+    eb_cycle_write(cycle, erase_address, control, (sector >> 24) & 0xFF);
+  eb_cycle_write(cycle, erase_address, control, (sector >> 16) & 0xFF);
+  eb_cycle_write(cycle, erase_address, control, (sector >>  8) & 0xFF);
+  eb_cycle_write(cycle, erase_address, control, (sector >>  0) & 0xFF);
+  eb_cycle_write(cycle, erase_address, control, 0x80000000); /* execute */
+  return eb_cycle_close(cycle);
+}
+
+static void wait_for_busy(eb_device_t device, long interval) {
+  long delay, used;
+  eb_status_t status;
+  eb_data_t result;
+  
+  do {
+    /* Sleep as requested */
+    for (delay = interval; delay > 0; delay -= used) {
+      used = eb_socket_run(eb_device_socket(device), delay);
+    }
+    
+    result = 0;
+    status = eb_device_read(device, erase_address, (format&EB_ENDIAN_MASK)|EB_DATA32, &result, 0, eb_block);
+    if (status != EB_OK && status != EB_SEGFAULT) {
+      fprintf(stderr, "%s: polling erase status failed: %s\n", program, eb_status(status));
+      exit(1);
+    }
+  } while (status == EB_SEGFAULT);
+}
+
+static void find_sector_size(eb_user_data_t user, eb_device_t dev, eb_operation_t op, eb_status_t status) {
+  eb_address_t* sector_size = (eb_address_t*)user;
+  eb_address_t result;
+  
+  if (status != EB_OK) {
+    fprintf(stderr, "%s: pattern read failed: %s\n", program, eb_status(status));
+    exit(1);
+  }
+  
+  result = page_size;
+  for (; op != EB_NULL; op = eb_operation_next(op)) {
+    if (eb_operation_had_error(op)) {
+      fprintf(stderr, "%s: wishbone segfault reading %s %s bits from address 0x%"EB_ADDR_FMT"\n",
+                      program, 
+                      eb_width_data(eb_operation_format(op)), 
+                      eb_format_endian(eb_operation_format(op)), 
+                      eb_operation_address(op));
+      exit(1);
+    }
+    
+    /* If the data is 0, then it was erased */
+    if (eb_operation_data(op) != 0) {
+      result <<= 1;
+    } else {
+      break;
+    }
+  }
+  
+  *sector_size = result;
+}
+
+static eb_address_t detect_sector_size(eb_device_t device) {
+  eb_address_t sector_size;
+  eb_address_t max_size;
+  eb_address_t target;
+  eb_address_t end;
+  eb_status_t  status;
+  eb_cycle_t   cycle;
+  
+  /* Plan:
+   *   0- Find largest possible sector that fits between start and end
+   *   1- Write 0s at positions 2^x-1 inside that sector
+   *   2- Erase the start address of the sector
+   *   3- Read the values at all the addresses that were zeroed.
+   *   4- The last non-zero seen determines the sector size
+   */
+  
+  if (!quiet) {
+    printf("Autodetecting sector size ... ");
+    fflush(stdout);
+  }
+  
+  /* Find largest sector that fits by aligning target */
+  end = address + firmware_length-1;
+  target = address + firmware_length/2;
+  
+  sector_size = page_size;
+  target &= ~(sector_size-1);
+  while (address <= target && target+sector_size-1 <= end) {
+    sector_size <<= 1;
+    target &= ~(sector_size-1);
+    if (sector_size >= MAX_SECTOR_SIZE) break; /* faster */
+  }
+  
+  max_size = sector_size >> 1;
+  target = (address + firmware_length/2) & ~(max_size-1);
+  
+  /* Start writing test pattern */
+  for (sector_size = page_size; sector_size < max_size; sector_size <<= 1) {
+    if (!quiet) {
+      printf("\rAutodetecting sector size: write 0x%"EB_ADDR_FMT" ... ", target+sector_size);
+      fflush(stdout);
+    }
+    status = eb_device_write(device, target+sector_size, format, 0, 0, eb_block);
+    if (status != EB_OK) {
+      fprintf(stderr, "\r%s: failed to write test pattern at 0x%"EB_ADDR_FMT": %s\n", program, target+sector_size, eb_status(status));
+      exit(1);
+    }
+    wait_for_busy(device, 0);
+  }
+  
+  if (!quiet) {
+    printf("\rAutodetecting sector size: erasing 0x%"EB_ADDR_FMT" ... ", target);
+    fflush(stdout);
+  }
+  if ((status = erase_sector(device, target)) != EB_OK) {
+    fprintf(stderr, "\r%s: failed to erase test sector 0x%"EB_ADDR_FMT": %s\n", program, target, eb_status(status));
+    exit(1);
+  }
+  
+  wait_for_busy(device, wait_us);
+  
+  if (!quiet) {
+    printf("\rAutodetecting sector size: scanning ...            ");
+    fflush(stdout);
+  }
+  if ((status = eb_cycle_open(device, &sector_size, find_sector_size, &cycle)) != EB_OK) {
+    fprintf(stderr, "\r%s: could not create cycle: %s\n", program, eb_status(status));
+    exit(1);
+  }
+  
+  for (sector_size = page_size; sector_size < max_size; sector_size <<= 1) {
+    eb_cycle_read(cycle, target+sector_size, format, 0);
+  }
+  
+  sector_size = 0;
+  eb_cycle_close(cycle);
+  while (sector_size == 0) {
+    eb_socket_run(eb_device_socket(device), -1);
+  }
+  
+  if (!quiet) {
+    printf("\rAutodetecting sector size: found = 0x%"EB_ADDR_FMT"\n", sector_size);
+  }
+  
+  return sector_size;
+}
+
+static void detect_decrement(eb_user_data_t user, eb_device_t dev, eb_operation_t op, eb_status_t status) {
+  int* counter = (int*)user;
+  --*counter;
+  
+  if (status != EB_OK) {
+    fprintf(stderr, "%s: scan operation failed: %s\n", program, eb_status(status));
+    exit(1);
+  }
+  
+  for (; op != EB_NULL; op = eb_operation_next(op)) {
+    if (eb_operation_had_error(op)) {
+      fprintf(stderr, "%s: wishbone segfault reading %s %s bits from address 0x%"EB_ADDR_FMT"\n",
+                      program, 
+                      eb_width_data(eb_operation_format(op)), 
+                      eb_format_endian(eb_operation_format(op)), 
+                      eb_operation_address(op));
+      exit(1);
+    }
+    
+    /* If the data is not FFFFFF, then set the bit indicating we must erase it */
+    if (eb_operation_data(op) != ffs) {
+      bitmask_set((eb_operation_address(op) - address) / sector_size, 1);
+    }
+  }
+}
+
+static int scan_count = 0;
+static void quick_scan_gap(eb_device_t device, eb_address_t offset, eb_address_t len) {
+  eb_address_t sector;
+  eb_address_t first_sector;
+  eb_address_t last_sector;
+  eb_address_t position;
+  eb_address_t last_position;
+  eb_status_t status;
+  eb_cycle_t cycle;
+  int i;
+  int size;
+  int ops;
+  int inflight;
+  
+  size = format & EB_DATAX;
+  ops = 0;
+  inflight = 0;
+  
+  if ((status = eb_cycle_open(device, &inflight, detect_decrement, &cycle)) != EB_OK) {
+    fprintf(stderr, "\r%s: could not create cycle: %s\n", program, eb_status(status));
+  }
+  
+  first_sector = (address/sector_size) * sector_size;
+  last_sector = ((address+firmware_length+sector_size-1)/sector_size)*sector_size;
+  
+  i = 0;
+  for (sector = first_sector; sector != last_sector; sector += sector_size) {
+    if (bitmask_get(i++)) continue;
+    
+    last_position = sector+offset+len;
+    for (position = sector+offset; position != last_position; position += size) {
+      eb_cycle_read(cycle, position, format, 0);
+      
+      if (!quiet && (++scan_count % 65536) == 0) {
+        scan_count = 0;
+        printf("\rScanning offset 0x%"EB_ADDR_FMT" ... ", position);
+        fflush(stdout);
+      }
+      
+      /* use the same number of operations per cycle as programming/verifying */
+      if (++ops == (page_size/size)) {
+        ops = 0;
+        ++inflight;
+        eb_cycle_close(cycle);
+        
+        if ((status = eb_cycle_open(device, &inflight, detect_decrement, &cycle)) != EB_OK) {
+          fprintf(stderr, "\r%s: could not create cycle: %s\n", program, eb_status(status));
+        }
+        
+        while (inflight >= cycles_per_poll)
+          eb_socket_run(eb_device_socket(device), -1);
+      }
+    }
+  }
+  
+  if (ops > 0) {
+    ++inflight;
+    eb_cycle_close(cycle);
+  } else {
+    eb_cycle_abort(cycle);
+  }
+  
+  while (inflight > 0) {
+    eb_socket_run(eb_device_socket(device), -1);
+  }
+}
+
+static void quick_scan(eb_device_t device) {
+  eb_address_t offset, gap;
+  
+  if (!quiet) {
+    printf("Scanning flash ... ");
+    fflush(stdout);
+  }
+  
+  gap = 8;
+  scan_count = 0;
+  quick_scan_gap(device, 0, gap);
+  
+  for (offset = gap; offset != sector_size; offset += gap, gap += gap) {
+    quick_scan_gap(device, offset, gap);
+  }
+  
+  if (!quiet) printf("done!\n");
+}
+
+static void erase_flash(eb_device_t device) {
+  eb_address_t sector;
+  eb_address_t first_sector;
+  eb_address_t last_sector;
+  eb_status_t status;
+  int i;
+  first_sector = (address/sector_size) * sector_size;
+  last_sector = ((address+firmware_length+sector_size-1)/sector_size)*sector_size;
+  
+  if (!quiet)
+    printf("Erasing flash ... ");
+  
+  i = 0;
+  for (sector = first_sector; sector != last_sector; sector += sector_size) {
+    if (!quiet) {
+      printf("\rErasing 0x%"EB_ADDR_FMT" ... ", sector);
+      fflush(stdout);
+    }
+    
+    if (!bitmask_get(i++)) continue;
+    
+    if ((status = erase_sector(device, sector)) != EB_OK) {
+      fprintf(stderr, "\r%s: failed to erase 0x%"EB_ADDR_FMT": %s\n", program, sector, eb_status(status));
+      exit(1);
+    }
+    
+    wait_for_busy(device, wait_us);
+  }
+  
+  if (!quiet) printf("done!\n");
+}
+
+static void program_decrement(eb_user_data_t user, eb_device_t dev, eb_operation_t op, eb_status_t status) {
+  int* counter = (int*)user;
+  --*counter;
+  
+  if (status != EB_OK) {
+    fprintf(stderr, "%s: program operation failed: %s\n", program, eb_status(status));
+    exit(1);
+  }
+  
+  for (; op != EB_NULL; op = eb_operation_next(op)) {
+    if (eb_operation_had_error(op)) {
+      fprintf(stderr, "%s: wishbone segfault writing %s %s bits to address 0x%"EB_ADDR_FMT"\n",
+                      program, 
+                      eb_width_data(eb_operation_format(op)), 
+                      eb_format_endian(eb_operation_format(op)), 
+                      eb_operation_address(op));
+      exit(1);
+    }
+  }
+}
+
+static void program_flash(eb_device_t device) {
+  eb_status_t status;
+  eb_cycle_t  cycle;
+  eb_address_t offset;
+  eb_address_t stop_offset;
+  eb_data_t data;
+  int size;
+  int ops;
+  int inflight;
+  int byte;
+  int may_skip_ffs;
+  uint8_t buf[8];
+
+  size = format & EB_DATAX;
+  ops = 0;
+  inflight = 0;
+  may_skip_ffs = 1;
+  
+  if (!quiet) printf("Programming flash ... ");
+  
+  if ((status = eb_cycle_open(device, &inflight, program_decrement, &cycle)) != EB_OK) {
+    fprintf(stderr, "\r%s: could not create cycle: %s\n", program, eb_status(status));
+  }
+  
+  stop_offset = address + firmware_length;
+  for (offset = address; offset != stop_offset; offset += size) {
+    if (fread(buf, 1, size, firmware_f) != size) {
+      fprintf(stderr, "\r%s: short read from '%s'\n", program, firmware);
+      exit(1);
+    }
+    
+    data = 0;
+    if ((format & EB_ENDIAN_MASK) == EB_BIG_ENDIAN) {
+      for (byte = 0; byte < size; ++byte) {
+        data <<= 8;
+        data |= buf[byte];
+      }
+    } else {
+      for (byte = size-1; byte >= 0; --byte) {
+        data <<= 8;
+        data |= buf[byte];
+      }
+    }
+    
+    if (!quiet && offset % 65536 == 0) {
+      printf("\rProgramming 0x%"EB_ADDR_FMT"... ", offset);
+      fflush(stdout);
+    }
+    
+    /* Don't write pages of FFs; skip them */
+    if (may_skip_ffs && data == ffs) {
+      continue;
+    }
+    
+    if (offset == erase_address) {
+      if (!quiet)
+        fprintf(stderr, "\r%s: warning: firmware tried to write to erase address!\n", program);
+      continue;
+    }
+    
+    /* Flip bits in the bytes? */
+    data = flip_bits(data);
+    
+    eb_cycle_write(cycle, offset, format, data);
+    may_skip_ffs = 0;
+    ++ops;
+    
+    /* Align cycles to page boundaries */
+    if ((offset+size) % page_size == 0) {
+      ops = 0;
+      may_skip_ffs = 1;
+      
+      ++inflight;
+      eb_cycle_close(cycle);
+      
+      if ((status = eb_cycle_open(device, &inflight, program_decrement, &cycle)) != EB_OK) {
+        fprintf(stderr, "\r%s: could not create cycle: %s\n", program, eb_status(status));
+      }
+      
+      while (inflight >= cycles_per_poll)
+        eb_socket_run(eb_device_socket(device), -1);
+    }
+  }
+  
+  if (ops > 0) {
+    ++inflight;
+    eb_cycle_close(cycle);
+  } else {
+    eb_cycle_abort(cycle);
+  }
+  
+  while (inflight > 0) {
+    eb_socket_run(eb_device_socket(device), -1);
+  }
+  
+  if (!quiet) printf("done!\n");
+}
+
+static void verify_decrement(eb_user_data_t user, eb_device_t dev, eb_operation_t op, eb_status_t status) {
+  static eb_address_t last_offset = -1;
+  eb_data_t data;
+  uint8_t buf[8];
+  int byte, size;
+  int* counter = (int*)user;
+  --*counter;
+  
+  if (status != EB_OK) {
+    fprintf(stderr, "%s: verify operation failed: %s\n", program, eb_status(status));
+    exit(1);
+  }
+  
+  for (; op != EB_NULL; op = eb_operation_next(op)) {
+    if (eb_operation_had_error(op)) {
+      fprintf(stderr, "%s: wishbone segfault reading %s %s bits from address 0x%"EB_ADDR_FMT"\n",
+                      program, 
+                      eb_width_data(eb_operation_format(op)), 
+                      eb_format_endian(eb_operation_format(op)), 
+                      eb_operation_address(op));
+      exit(1);
+    }
+    
+    if (last_offset != eb_operation_address(op)) {
+      last_offset = eb_operation_address(op);
+      fseeko(firmware_f, last_offset-address, SEEK_SET);
+    }
+    
+    size = eb_operation_format(op) & EB_DATAX;
+    
+    if (fread(buf, 1, size, firmware_f) != size) {
+      fprintf(stderr, "\r%s: short verify read from '%s'\n", program, firmware);
+      exit(1);
+    }
+    last_offset += size;
+    
+    data = 0;
+    if ((format & EB_ENDIAN_MASK) == EB_BIG_ENDIAN) {
+      for (byte = 0; byte < size; ++byte) {
+        data <<= 8;
+        data |= buf[byte];
+      }
+    } else {
+      for (byte = size-1; byte >= 0; --byte) {
+        data <<= 8;
+        data |= buf[byte];
+      }
+    }
+    
+    data = flip_bits(data);
+    
+    if (eb_operation_data(op) != data) {
+      fprintf(stderr, "%s: verify failed at address 0x%"EB_ADDR_FMT" (0x%"EB_DATA_FMT" != 0x%"EB_DATA_FMT")\n",
+                      program, eb_operation_address(op), eb_operation_data(op), data);
+      exit(1);
+    }
+  }
+}
+
+static void verify_flash(eb_device_t device) {
+  eb_status_t status;
+  eb_cycle_t  cycle;
+  eb_address_t offset;
+  eb_address_t stop_offset;
+  int size;
+  int ops;
+  int inflight;
+
+  size = format & EB_DATAX;
+  ops = 0;
+  inflight = 0;
+  
+  if (!quiet) printf("Verifying flash ... ");
+  
+  if ((status = eb_cycle_open(device, &inflight, verify_decrement, &cycle)) != EB_OK) {
+    fprintf(stderr, "\r%s: could not create cycle: %s\n", program, eb_status(status));
+  }
+  
+  stop_offset = address + firmware_length;
+  for (offset = address; offset != stop_offset; offset += size) {
+    if (offset % 65536 == 0) {
+      if (!quiet) printf("\rVerifying 0x%"EB_ADDR_FMT"... ", offset);
+      fflush(stdout);
+    }
+    
+    eb_cycle_read(cycle, offset, format, 0);
+    ++ops;
+    
+    if ((offset+size) % page_size == 0) {
+      ops = 0;
+      ++inflight;
+      eb_cycle_close(cycle);
+      
+      if ((status = eb_cycle_open(device, &inflight, verify_decrement, &cycle)) != EB_OK) {
+        fprintf(stderr, "\r%s: could not create cycle: %s\n", program, eb_status(status));
+      }
+      
+      while (inflight >= cycles_per_poll)
+        eb_socket_run(eb_device_socket(device), -1);
+    }
+  }
+  
+  if (ops > 0) {
+    ++inflight;
+    eb_cycle_close(cycle);
+  } else {
+    eb_cycle_abort(cycle);
+  }
+  
+  while (inflight > 0) {
+    eb_socket_run(eb_device_socket(device), -1);
+  }
+  
+  if (!quiet) printf("done!\n");
+}
+
+int main(int argc, char** argv) {
+  long value;
+  char* value_end;
+  int opt, error, i;
+  
+  eb_socket_t socket;
+  eb_status_t status;
+  eb_device_t device;
+  eb_width_t line_width;
+  eb_width_t device_support;
+  
+  /* Default arguments */
+  program = argv[0];
+  address = 0;
+  address_set = 0;
+  erase_address = 0;
+  erase_address_set = 0;
+  address_width = EB_ADDRX;
+  data_width = EB_DATAX;
+  endian = 0; /* auto-detect */
+  sector_size = 0; /* auto */
+  page_size = 256;
+  wait_us = 100000; /* 100 ms */
+  cycles_per_poll = 4;
+  retries = 3;
+  full = 0;
+  probe = 1;
+  verify = 1;
+  invert = -1; /* auto */
+  verbose = 0;
+  quiet = 0;
+  
+  /* Process the command-line arguments */
+  error = 0;
+  while ((opt = getopt(argc, argv, "t:e:a:d:bli:s:w:c:r:fpnvqh")) != -1) {
+    switch (opt) {
+    case 't':
+      address = strtoull(optarg, &value_end, 0);
+      address_set = 1;
+      if (*value_end != 0) {
+        fprintf(stderr, "%s: invalid <address> argument -- '%s'\n", program, optarg);
+        error = 1;
+      }
+      break;
+    case 'e':
+      erase_address = strtoull(optarg, &value_end, 0);
+      if (*value_end) {
+        fprintf(stderr, "%s: invalid erase address -- '%s'\n", program, optarg);
+        error = 1;
+      }
+      erase_address_set = 1;
+      break;
+    case 'a':
+      value = eb_width_parse_address(optarg, &address_width);
+      if (value != EB_OK) {
+        fprintf(stderr, "%s: invalid address width -- '%s'\n", program, optarg);
+        error = 1;
+      }
+      break;
+    case 'd':
+      value = eb_width_parse_data(optarg, &data_width);
+      if (value != EB_OK) {
+        fprintf(stderr, "%s: invalid data width -- '%s'\n", program, optarg);
+        error = 1;
+      }
+      break;
+    case 'b':
+      endian = EB_BIG_ENDIAN;
+      break;
+    case 'l':
+      endian = EB_LITTLE_ENDIAN;
+      break;
+    case 'i':
+      invert = strtol(optarg, &value_end, 0);
+      if (*value_end || invert < 0 || invert > 1) {
+        fprintf(stderr, "%s: invalid invert option -- '%s'; must be 0 or 1\n", program, optarg);
+        error = 1;
+      }
+      break;
+    case 's':
+      sector_size = strtoull(optarg, &value_end, 0);
+      if (*value_end || sector_size < 256 || ((sector_size-1)&sector_size) != 0) {
+        fprintf(stderr, "%s: invalid sector size -- '%s'; must be a large power of two\n", program, optarg);
+        error = 1;
+      }
+      break;
+    case 'x':
+      page_size = strtoull(optarg, &value_end, 0);
+      if (*value_end || page_size < 1 || ((page_size-1)&page_size) != 0) {
+        fprintf(stderr, "%s: invalid page size -- '%s'; must be a power of two\n", program, optarg);
+        error = 1;
+      }
+      break;
+    case 'w':
+      wait_us = strtof(optarg, &value_end) * 1000000.0;
+      if (*value_end || wait_us < 100000 || wait_us > 10000000) {
+        fprintf(stderr, "%s: invalid wait time -- '%s'; must be between 0.01 and 10 seconds\n", program, optarg);
+        error = 1;
+      }
+      break;
+    case 'c':
+      cycles_per_poll = strtol(optarg, &value_end, 0);
+      if (*value_end || cycles_per_poll <= 0 || cycles_per_poll > 100) {
+        fprintf(stderr, "%s: invalid number of cycles -- '%s'; must be between 1 and 100\n", program, optarg);
+        return 1;
+      }
+      break;
+    case 'r':
+      retries = strtol(optarg, &value_end, 0);
+      if (*value_end || retries < 0 || retries > 100) {
+        fprintf(stderr, "%s: invalid number of retries -- '%s'; must be between 0 and 100\n", program, optarg);
+        return 1;
+      }
+      break;
+    case 'f':
+      full = 1;
+      break;
+    case 'p':
+      probe = 0;
+      break;
+    case 'n':
+      verify = 0;
+      break;
+    case 'v':
+      verbose = 1;
+      break;
+    case 'q':
+      quiet = 1;
+      break;
+    case 'h':
+      help();
+      return 1;
+    case ':':
+    case '?':
+      error = 1;
+      break;
+    default:
+      fprintf(stderr, "%s: bad getopt result\n", program);
+      error = 1;
+    }
+  }
+  
+  if (error) return 1;
+  
+  if (optind + 2 != argc) {
+    fprintf(stderr, "%s: expecting three non-optional arguments: <proto/host/port> <firmware>\n", program);
+    return 1;
+  }
+  
+  device_address = argv[optind];
+  
+  firmware = argv[optind+1];
+  
+  if ((firmware_f = fopen(firmware, "r")) == 0) {
+    fprintf(stderr, "%s: fopen, %s -- '%s'\n",
+                    program, strerror(errno), firmware);
+    return 1;
+  }
+  
+  if (fseeko(firmware_f, 0, SEEK_END) != 0) {
+    fprintf(stderr, "%s: fseeko, %s -- '%s'\n",
+                    program, strerror(errno), firmware);
+    return 1;
+  }
+  
+  firmware_length = ftello(firmware_f);
+  fseeko(firmware_f, 0, SEEK_SET);
+  
+  if (firmware_length % page_size != 0) {
+    fprintf(stderr, "%s: invalid firmware image -- '%s'; must be aligned to a %d-byte page\n",
+                    program, firmware, (int)page_size);
+    return 1;
+  }
+
+  erase_bitmap_size = firmware_length / page_size / 8;  
+  erase_bitmap = (uint8_t*)calloc(erase_bitmap_size, 1);
+  if (!erase_bitmap) {
+    fprintf(stderr, "%s: could not allocate erase bitmap (%d bytes)\n",
+                    program, erase_bitmap_size);
+    return 1;
+  }
+  
+  if (verbose) 
+    printf("Opened '%s' with 0x%"EB_ADDR_FMT" bytes of data\n", firmware, firmware_length);
+  
+  if (invert == -1) {
+    if (strlen(firmware) > 4 && !strcmp(firmware+strlen(firmware)-4, ".rpd")) {
+      invert = 1;
+    } else {
+      invert = 0;
+    }
+    if (verbose)
+      printf("  based on filename, WILL%s invert bits within bytes\n", invert?"":" NOT");
+  }
+  
+  if (verbose)
+    fprintf(stdout, "Opening socket with %s-bit address and %s-bit data widths\n", 
+                    eb_width_address(address_width), eb_width_data(data_width));
+  
+  if ((status = eb_socket_open(EB_ABI_CODE, 0, address_width|data_width, &socket)) != EB_OK) {
+    fprintf(stderr, "%s: failed to open Etherbone socket: %s\n", program, eb_status(status));
+    return 1;
+  }
+  
+  if (verbose)
+    fprintf(stdout, "Connecting to '%s' with %d retry attempts...\n", device_address, retries);
+  
+  if ((status = eb_device_open(socket, device_address, EB_ADDRX|EB_DATAX, retries, &device)) != EB_OK) {
+    fprintf(stderr, "%s: failed to open Etherbone device: %s\n", program, eb_status(status));
+    return 1;
+  }
+  
+  line_width = eb_device_width(device);
+  if (verbose)
+    fprintf(stdout, "  negotiated %s-bit address and %s-bit data session.\n", 
+                    eb_width_address(line_width), eb_width_data(line_width));
+  
+  if (probe) {
+    struct sdb_device info;
+    int num;
+    
+    if (verbose)
+      fprintf(stdout, "Scanning remote bus for Wishbone devices...\n");
+
+    if (address_set == 0) {
+      num = 1;
+      if ((status = eb_sdb_find_by_identity(device, GSI_ID, FLASH_ID, &info, &num)) != EB_OK) {
+        fprintf(stderr, "%s: failed to find flash device: %s\n", program, eb_status(status));
+        return 1;
+      }
+      if (num == 0) {
+        fprintf(stderr, "%s: no known flash devices found\n", program);
+        return 1;
+      }
+      if (num > 1) {
+        fprintf(stderr, "%s: found %d flash devices -- choose one with '-t'.\n", program, num);
+        return 1;
+      }
+      
+      address = info.sdb_component.addr_first;
+      address_set = 1;
+      if (verbose)
+        printf("  found flash device at address 0x%"EB_ADDR_FMT"\n", address);
+    } else {
+      if ((status = eb_sdb_find_by_address(device, address, &info)) != EB_OK) {
+        fprintf(stderr, "%s: failed to find SDB record at 0x%"EB_ADDR_FMT": %s\n", program, address, eb_status(status));
+        return 1;
+      }
+    }
+    
+    if ((info.bus_specific & SDB_WISHBONE_LITTLE_ENDIAN) != 0)
+      device_support = EB_LITTLE_ENDIAN;
+    else
+      device_support = EB_BIG_ENDIAN;
+    device_support |= info.bus_specific & EB_DATAX;
+    
+    if (!erase_address_set) {
+      erase_address = info.sdb_component.addr_last-3;
+      erase_address_set = 1;
+      if (verbose)
+        fprintf(stdout, "  using 0x%"EB_ADDR_FMT" as erase register\n", erase_address);
+    }
+    
+    if (info.abi_ver_major == 1 && info.abi_ver_minor == 0) {
+      if (!quiet) fprintf(stderr, "warning: old flash firmware detected! (falling back to slow erase)\n");
+      if (wait_us < 4000000) {
+        wait_us = 4000000;
+      }
+    }
+    if (info.abi_ver_major == 1 && info.abi_ver_minor <= 1) {
+      if (!quiet) fprintf(stderr, "warning: old flash firmware detected! (using hardware erase command)\n");
+      old_erase = 1;
+    } else {
+      old_erase = 0;
+    }
+  } else {
+    device_support = endian | EB_DATAX;
+  }
+  
+  /* If unset by user and SDB */
+  if (!address_set) {
+    fprintf(stderr, "%s: could not auto-detect target flash address\n", program);
+    return 1;
+  }
+  
+  if (!erase_address_set) {
+    fprintf(stderr, "%s: could not auto-detect erase register address\n", program);
+    return 1;
+  }
+  
+  if (address % page_size != 0) {
+    fprintf(stderr, "%s: invalid target address -- 0x%"EB_ADDR_FMT"; must be aligned to a %d-byte page\n",
+                    program, address, (int)page_size);
+    return 1;
+  }
+  
+  /* Did the user request a bad endian? We use it anyway, but issue warning. */
+  if (endian != 0 && (device_support & EB_ENDIAN_MASK) != endian) {
+    if (!quiet)
+      fprintf(stderr, "%s: warning: target device is %s (writing as %s).\n",
+                      program, eb_format_endian(device_support), eb_format_endian(endian));
+  }
+  
+  if (endian == 0) {
+    /* Select the probed endian. May still be 0 if device not found. */
+    endian = device_support & EB_ENDIAN_MASK;
+  }
+  
+  /* We need to know the endian */
+  if (endian == 0) {
+    fprintf(stderr, "%s: error: must know endian to program firmware\n",
+                    program);
+    return 1;
+  }
+  
+  /* What widths does the connection support? */
+  line_width &= EB_DATAX;
+  line_width |= line_width-1;
+
+  /* Pick the largest supported data width */
+  format = device_support & line_width;
+  format |= format >> 1;
+  format |= format >> 2;
+  format = (format+1) >> 1;
+  
+  if (format == 0) {
+    fprintf(stderr, "%s: device widths %s do not fit this connection (widths %s)\n", 
+                    program, eb_width_data(device_support), eb_width_data(line_width));
+    return 1;
+  }
+  
+  /* If the page size is smaller than the write format... wtf? */
+  if (page_size < format) {
+    fprintf(stderr, "%s: page size %d is smaller than device write size %d!\n",
+                    program, (int)page_size, format);
+    return 1;
+  }
+
+  format |= endian;
+  if (verbose)
+    printf("Will operate using %s bit %s operations\n",
+      eb_format_data(format), eb_format_endian(format));
+  
+  if (!sector_size) {
+    sector_size = detect_sector_size(device);
+  }
+  
+  ffs = 0;
+  ffs = ~ffs;
+  ffs >>= (sizeof(eb_data_t) - (format&EB_DATAX))*8;
+  
+  mask_0f = 0x0f;
+  for (i = 0; i < sizeof(eb_data_t); ++i)
+    mask_0f |= mask_0f << 8;
+  
+  if (full) {
+    memset(erase_bitmap, 0xFF, erase_bitmap_size);
+  } else {
+    quick_scan(device);
+  }
+  
+  erase_flash(device);
+  program_flash(device);
+  if (verify) verify_flash(device);
+  
+  if ((status = eb_device_close(device)) != EB_OK) {
+    fprintf(stderr, "%s: failed to close Etherbone device: %s\n", program, eb_status(status));
+    return 1;
+  }
+  
+  if ((status = eb_socket_close(socket)) != EB_OK) {
+    fprintf(stderr, "%s: failed to close Etherbone socket: %s\n", program, eb_status(status));
+    return 1;
+  }
+  
+  return 0;
+}
diff --git a/tools/eb-info.c b/tools/eb-info.c
new file mode 100644
index 0000000000000000000000000000000000000000..639419cfd673aee81295804be39c99134b5e07ae
--- /dev/null
+++ b/tools/eb-info.c
@@ -0,0 +1,126 @@
+/** @file eb-info.c
+ *  @brief Report the contents of an FPGA using Etherbone.
+ *
+ *  Copyright (C) 2013 GSI Helmholtz Centre for Heavy Ion Research GmbH 
+ *
+ *  A complete skeleton of an application using the Etherbone library.
+ *
+ *  @author Wesley W. Terpstra <w.terpstra@gsi.de>
+ *
+ *******************************************************************************
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 3 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *******************************************************************************
+ */
+
+#define _POSIX_C_SOURCE 200112L /* strtoull */
+
+#include <unistd.h> /* getopt */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <etherbone.h>
+
+#define GSI_ID	0x651
+#define ROM_ID	0x2d39fa8b
+
+const char *program;
+
+static void help(void) {
+  fprintf(stderr, "Usage: %s [OPTION] <proto/host/port>\n", program);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "  -h             display this help and exit\n");
+  fprintf(stderr, "\n");
+  fprintf(stderr, "Report bugs to <w.terpstra@gsi.de>\n");
+}
+
+static void die(const char* msg, eb_status_t status) {
+  fprintf(stderr, "%s: %s: %s\n", program, msg, eb_status(status));
+  exit(1);
+}
+
+int main(int argc, char** argv) {
+  int opt, error, c, i, len;
+  struct sdb_device sdb;
+  eb_status_t status;
+  eb_socket_t socket;
+  eb_device_t device;
+  eb_cycle_t cycle;
+  eb_data_t *data;
+  
+  /* Default arguments */
+  program = argv[0];
+  error = 0;
+  
+  /* Process the command-line arguments */
+  error = 0;
+  while ((opt = getopt(argc, argv, "h")) != -1) {
+    switch (opt) {
+    case 'h':
+      help();
+      return 0;
+    case ':':
+    case '?':
+      error = 1;
+      break;
+    default:
+      fprintf(stderr, "%s: bad getopt result\n", program);
+      error = 1;
+    }
+  }
+  
+  if (error) return 1;
+  
+  if (optind + 1 != argc) {
+    fprintf(stderr, "%s: expecting three non-optional arguments: <proto/host/port>\n", program);
+    return 1;
+  }
+  
+  if ((status = eb_socket_open(EB_ABI_CODE, 0, EB_DATAX|EB_ADDRX, &socket)) != EB_OK)
+    die("eb_socket_open", status);
+  
+  if ((status = eb_device_open(socket, argv[optind], EB_DATAX|EB_ADDRX, 3, &device)) != EB_OK)
+    die(argv[optind], status);
+  
+  c = 1;
+  if ((status = eb_sdb_find_by_identity(device, GSI_ID, ROM_ID, &sdb, &c)) != EB_OK)
+    die("eb_sdb_find_by_identity", status);
+  if (c != 1) {
+    fprintf(stderr, "Found %d ROM identifiers on that device\n", c);
+    exit(1);
+  }
+  
+  if ((status = eb_cycle_open(device, 0, 0, &cycle)) != EB_OK)
+    die("eb_cycle_open", status);
+  
+  len = ((sdb.sdb_component.addr_last - sdb.sdb_component.addr_first) + 1) / 4;
+  if ((data = malloc(len * sizeof(eb_data_t))) == 0)
+    die("malloc", EB_OOM);
+  
+  for (i = 0; i < len; ++i)
+    eb_cycle_read(cycle, sdb.sdb_component.addr_first + i*4, EB_DATA32|EB_BIG_ENDIAN, &data[i]);
+  
+  if ((status = eb_cycle_close(cycle)) != EB_OK)
+    die("eb_cycle_close", status);
+  
+  for (i = 0; i < len; ++i) {
+    printf("%c%c%c%c", 
+      (char)(data[i] >> 24) & 0xff, 
+      (char)(data[i] >> 16) & 0xff, 
+      (char)(data[i] >>  8) & 0xff,
+      (char)(data[i]      ) & 0xff);
+  }
+  
+  return 0;
+}
diff --git a/tools/impact_cutewr b/tools/impact_cutewr
new file mode 100755
index 0000000000000000000000000000000000000000..894a3795985eec79e609a44f13dd8efff17ac5c4
--- /dev/null
+++ b/tools/impact_cutewr
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# Downloads bitfile to an cutewr board in batch, using Impact from Xilinx ISE
+
+if [ ! -r "${1}" ]; then
+  echo "Usage: $0 filename.bit" >&2
+  exit 1
+fi
+
+set -e
+test -r $XILINX_SETTINGS && . $XILINX_SETTINGS
+test -d $XILINX
+
+# don't use -v switch to verify chip, since I don't create a .msk file.
+# See Xilinx Answer Record 22228.
+echo "
+setMode -bs
+setCable -port auto
+Identify
+assignFile -p 1 -file \"${1}\"
+Program -p 1 -e -parallel -master -internalClk -clkFreq 40 -defaultVersion 0
+quit" | impact -batch