├── .gitignore ├── Makefile ├── README.md ├── Screenshot_2021-08-20_16-52-06.png ├── Usage-in-Vivado.png ├── Wemos-D1-Mini.jpg ├── arduino-cli.yaml ├── board_detect.py ├── esp8266-wemos-d1-mini-pinout.png ├── formatter.conf └── xvc-esp8266 ├── credentials.h └── xvc-esp8266.ino /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.elf 3 | *credentials* 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # https://github.com/arduino/arduino-cli/releases 2 | 3 | port := $(shell python3 board_detect.py 2>/dev/null) 4 | fqbn=esp8266:esp8266:d1_mini_clone 5 | src=xvc-esp8266 6 | 7 | default: 8 | arduino-cli compile --warnings=all --fqbn="${fqbn}" "${src}" 9 | 10 | upload: 11 | @# echo $(port) 12 | arduino-cli compile --fqbn="${fqbn}" "${src}" 13 | arduino-cli -v upload -p "${port}" --fqbn="${fqbn}" "${src}" 14 | 15 | install_platform: 16 | arduino-cli core update-index --config-file arduino-cli.yaml 17 | arduino-cli core install esp8266:esp8266 18 | 19 | deps: 20 | pip3 install pyserial 21 | 22 | install_arduino_cli: 23 | curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh 24 | 25 | format: 26 | astyle --options="formatter.conf" xvc-esp8266/xvc-esp8266.ino 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Xilinx Virtual Cable Server for ESP8266 2 | 3 | #### Overview 4 | 5 | ESP8266 implementation of XVC (Xilinx Virtual Cable) protocol based on xvcd 6 | (https://github.com/tmbinc/xvcd). 7 | 8 | By connecting the ESP8266 to the JTAG pins (TDI, TDO, TMS, TCK) of the target 9 | FPGA, you can access the JTAG port of the FPGA from a Xilinx tool (Vivado, 10 | etc.) via WiFi. 11 | 12 | Author: Gennaro Tortone (https://github.com/gtortone) 13 | 14 | Author 2: Dhiru Kholia - Remove optimizations (in hope of gaining wider 15 | compatibility), Mirror functions from `xvcpi` (to make debugging easier). 16 | 17 | Note: This project was tested with Vivado 2021.1, WeMos D1 Mini as the JTAG 18 | programmer (XVC daemon), and EBAZ4205 'Development' FPGA Board in August 2021. 19 | 20 | #### How to use 21 | 22 | Change the WiFi credentials in the `credentials.h` file. 23 | 24 | Note: The default pin mappings for the common, low-cost `WeMos D1 Mini` ESP8266 25 | development board are: 26 | 27 | | ESP8266 | JTAG | 28 | |----------|------| 29 | | D6 | TDI | 30 | | D4 | TDO | 31 | | D7 | TCK | 32 | | D5 | TMS | 33 | 34 | Feel free to experiment with different ESP8266 development boards - most should 35 | just work with any problems. 36 | 37 | ![Common ESP8266 Dev Board](./esp8266-wemos-d1-mini-pinout.png) 38 | 39 | Next, build the program using Arduino IDE and write it to the ESP8266 board. 40 | 41 | Finally, select the `Add Xilinx Virtual Cable (XVC)` option in the `Hardware 42 | Manager` in Vivado and mention the `IP address` of the ESP8266 board. 43 | 44 | ![Vivado Usage](./Usage-in-Vivado.png) 45 | 46 | #### How to use (Linux version) 47 | 48 | ``` 49 | make install_arduino_cli 50 | make install_platform 51 | make deps 52 | make upload 53 | ``` 54 | 55 | ### Tips 56 | 57 | If you see the `End of startup status: LOW` error message in Vivado, check the 58 | FPGA power supply's voltage and current ratings. 59 | 60 | ### Rough Performance Stats ("Speed") 61 | 62 | If cost and ease-of-availability are the driving constraints (at the cost of 63 | speed), then this project is 'usable' and can probably suffice. If higher 64 | programming speed is a requirement, I recommend using `xc3sprog` or 65 | `openFPGALoader` with an FT2232H board. 66 | 67 | This project might be the cheapest `Vivado-Compatible` JTAG programmer. 68 | 69 | https://github.com/gtortone/esp-xvcd is much faster but there have been reports 70 | of `FPGA programming failures` with it. 71 | 72 | https://github.com/pftbest/xvc-esp8266 - this has GPIO optimizations + a nice 73 | program structure - thanks! 74 | 75 | Also see the following `Related Projects` section. 76 | 77 | ### Related Ideas / Projects 78 | 79 | - https://github.com/kholia/xvc-pico 80 | - https://github.com/kholia/xvcpi 81 | - https://github.com/kholia/xvc-esp32 82 | - https://github.com/kholia/Colorlight-5A-75B 83 | - https://github.com/fusesoc/blinky#ebaz4205-development-board 84 | 85 | ## License 86 | 87 | Probably -> CC0 1.0 Universal (CC0 1.0) - Public Domain Dedication 88 | 89 | https://creativecommons.org/publicdomain/zero/1.0/ 90 | -------------------------------------------------------------------------------- /Screenshot_2021-08-20_16-52-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kholia/xvc-esp8266/f2402445ce2e07f34d5f9f0a864ad74db44cbc88/Screenshot_2021-08-20_16-52-06.png -------------------------------------------------------------------------------- /Usage-in-Vivado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kholia/xvc-esp8266/f2402445ce2e07f34d5f9f0a864ad74db44cbc88/Usage-in-Vivado.png -------------------------------------------------------------------------------- /Wemos-D1-Mini.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kholia/xvc-esp8266/f2402445ce2e07f34d5f9f0a864ad74db44cbc88/Wemos-D1-Mini.jpg -------------------------------------------------------------------------------- /arduino-cli.yaml: -------------------------------------------------------------------------------- 1 | board_manager: 2 | additional_urls: 3 | - https://arduino.esp8266.com/stable/package_esp8266com_index.json 4 | -------------------------------------------------------------------------------- /board_detect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # pip3 install pyserial 4 | 5 | import serial.tools.list_ports 6 | import serial 7 | 8 | a = serial.tools.list_ports.comports() 9 | for w in a: 10 | # https://devicehunt.com/view/type/usb/vendor/1A86/device/7523 11 | # https://devicehunt.com/view/type/usb/vendor/10C4/device/EA60 12 | if "1a86" in w.hwid.lower() or "10c4" in w.hwid.lower(): 13 | print(w.device) 14 | -------------------------------------------------------------------------------- /esp8266-wemos-d1-mini-pinout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kholia/xvc-esp8266/f2402445ce2e07f34d5f9f0a864ad74db44cbc88/esp8266-wemos-d1-mini-pinout.png -------------------------------------------------------------------------------- /formatter.conf: -------------------------------------------------------------------------------- 1 | # This configuration file contains a selection of the available options provided by the formatting tool "Artistic Style" 2 | # http://astyle.sourceforge.net/astyle.html 3 | # 4 | # If you wish to change them, don't edit this file. 5 | # Instead, copy it in the same folder of file "preferences.txt" and modify the copy. This way, you won't lose your custom formatter settings when upgrading the IDE 6 | # If you don't know where file preferences.txt is stored, open the IDE, File -> Preferences and you'll find a link 7 | 8 | mode=c 9 | 10 | # 2 spaces indentation 11 | indent=spaces=2 12 | 13 | # also indent macros 14 | indent-preprocessor 15 | 16 | # indent classes, switches (and cases), comments starting at column 1 17 | indent-classes 18 | indent-switches 19 | indent-cases 20 | indent-col1-comments 21 | 22 | # put a space around operators 23 | pad-oper 24 | 25 | # put a space after if/for/while 26 | pad-header 27 | 28 | # if you like one-liners, keep them 29 | keep-one-line-statements 30 | 31 | remove-comment-prefix 32 | -------------------------------------------------------------------------------- /xvc-esp8266/credentials.h: -------------------------------------------------------------------------------- 1 | static const char* MY_SSID = ""; 2 | static const char* MY_PASSPHRASE = ""; 3 | -------------------------------------------------------------------------------- /xvc-esp8266/xvc-esp8266.ino: -------------------------------------------------------------------------------- 1 | // https://github.com/gtortone/esp-xvcd (upstream) 2 | 3 | #include 4 | #include "credentials.h" 5 | 6 | // Pin out 7 | static constexpr const int tms_gpio = D5; 8 | static constexpr const int tck_gpio = D7; 9 | static constexpr const int tdo_gpio = D4; 10 | static constexpr const int tdi_gpio = D6; 11 | 12 | //#define VERBOSE 13 | #define MAX_WRITE_SIZE 512 14 | #define ERROR_OK 1 15 | // #define XVCD_AP_MODE 16 | #define XVCD_STATIC_IP 17 | 18 | IPAddress ip(192, 168, 1, 13); 19 | IPAddress gateway(192, 168, 1, 1); 20 | IPAddress netmask(255, 255, 255, 0); 21 | const int port = 2542; 22 | 23 | WiFiServer server(port); 24 | WiFiClient client; 25 | 26 | // JTAG buffers 27 | uint8_t cmd[16]; 28 | uint8_t buffer[1024], result[512]; 29 | 30 | /* Transition delay coefficients */ 31 | static const unsigned int jtag_delay = 10; // NOTE! 32 | 33 | static std::uint32_t jtag_xfer(std::uint_fast8_t n, std::uint32_t tms, std::uint32_t tdi) 34 | { 35 | std::uint32_t tdo = 0; 36 | for (uint_fast8_t i = 0; i < n; i++) { 37 | jtag_write(0, tms & 1, tdi & 1); 38 | tdo |= jtag_read() << i; 39 | jtag_write(1, tms & 1, tdi & 1); 40 | tms >>= 1; 41 | tdi >>= 1; 42 | } 43 | return tdo; 44 | } 45 | 46 | static bool jtag_read(void) 47 | { 48 | return digitalRead(tdo_gpio) & 1; 49 | } 50 | 51 | static void jtag_write(std::uint_fast8_t tck, std::uint_fast8_t tms, std::uint_fast8_t tdi) 52 | { 53 | digitalWrite(tck_gpio, tck); 54 | digitalWrite(tms_gpio, tms); 55 | digitalWrite(tdi_gpio, tdi); 56 | 57 | for (std::uint32_t i = 0; i < jtag_delay; i++) 58 | asm volatile ("nop"); 59 | } 60 | 61 | static int jtag_init(void) 62 | { 63 | pinMode(tdo_gpio, INPUT); 64 | pinMode(tdi_gpio, OUTPUT); 65 | pinMode(tck_gpio, OUTPUT); 66 | pinMode(tms_gpio, OUTPUT); 67 | 68 | digitalWrite(tdi_gpio, 0); 69 | digitalWrite(tck_gpio, 0); 70 | digitalWrite(tms_gpio, 1); 71 | 72 | return ERROR_OK; 73 | } 74 | 75 | int sread(void *target, int len) { 76 | 77 | uint8_t *t = (uint8_t *) target; 78 | 79 | while (len) { 80 | int r = client.read(t, len); 81 | if (r <= 0) 82 | return r; 83 | t += r; 84 | len -= r; 85 | } 86 | return 1; 87 | } 88 | 89 | int srcmd(void * target, int maxlen) { 90 | uint8_t *t = (uint8_t *) target; 91 | 92 | while (maxlen) { 93 | int r = client.read(t, 1); 94 | if (r <= 0) 95 | return r; 96 | 97 | if (*t == ':') { 98 | return 1; 99 | } 100 | 101 | t += r; 102 | maxlen -= r; 103 | } 104 | 105 | return 0; 106 | } 107 | 108 | void setup() { 109 | delay(1000); 110 | 111 | Serial.begin(115200); 112 | Serial.println(); 113 | Serial.println(); 114 | Serial.println(); 115 | #ifdef XVCD_AP_MODE 116 | WiFi.mode(WIFI_AP); 117 | WiFi.softAPConfig(ip, ip, netmask); 118 | WiFi.softAP(MY_SSID, MY_PASSPHRASE); 119 | #else 120 | #ifdef XVCD_STATIC_IP 121 | WiFi.config(ip, gateway, netmask); 122 | #endif 123 | WiFi.begin(MY_SSID, MY_PASSPHRASE); 124 | 125 | while (WiFi.status() != WL_CONNECTED) { 126 | delay(500); 127 | Serial.print("."); 128 | } 129 | 130 | Serial.println(""); 131 | Serial.println("WiFi connected"); 132 | Serial.println("IP address: "); 133 | Serial.println(WiFi.localIP()); 134 | #endif 135 | Serial.print("Starting XVC Server on port "); 136 | Serial.println(port); 137 | 138 | jtag_init(); 139 | 140 | server.begin(); 141 | server.setNoDelay(true); 142 | } 143 | 144 | void loop() { 145 | 146 | start: if (!client.connected()) { 147 | // try to connect to a new client 148 | client = server.available(); 149 | } else { 150 | // read data from the connected client 151 | if (client.available()) { 152 | while (client.connected()) { 153 | 154 | do { 155 | 156 | if (srcmd(cmd, 8) != 1) 157 | goto start; 158 | 159 | if (memcmp(cmd, "getinfo:", 8) == 0) { 160 | #ifdef VERBOSE 161 | Serial.write("XVC_info\n"); 162 | #endif 163 | client.write("xvcServer_v1.0:"); 164 | client.print(MAX_WRITE_SIZE); 165 | client.write("\n"); 166 | goto start; 167 | } 168 | 169 | if (memcmp(cmd, "settck:", 7) == 0) { 170 | #ifdef VERBOSE 171 | Serial.write("XVC_tck\n"); 172 | #endif 173 | int ntck; 174 | if (sread(&ntck, 4) != 1) { 175 | Serial.println("reading tck failed\n"); 176 | goto start; 177 | } 178 | // Actually TCK frequency is fixed, but replying a fixed TCK will halt hw_server 179 | client.write((const uint8_t *)&ntck, 4); 180 | goto start; 181 | } 182 | 183 | if (memcmp(cmd, "shift:", 6) != 0) { 184 | cmd[15] = '\0'; 185 | Serial.print("invalid cmd "); 186 | Serial.println((char *)cmd); 187 | goto start; 188 | } 189 | 190 | int len; 191 | if (sread(&len, 4) != 1) { 192 | Serial.println("reading length failed\n"); 193 | goto start; 194 | } 195 | 196 | unsigned int nr_bytes = (len + 7) / 8; 197 | 198 | #ifdef VERBOSE 199 | Serial.print("len = "); 200 | Serial.print(len); 201 | Serial.print(" nr_bytes = "); 202 | Serial.println(nr_bytes); 203 | #endif 204 | if (nr_bytes * 2 > sizeof(buffer)) { 205 | Serial.println("buffer size exceeded"); 206 | goto start; 207 | } 208 | 209 | if (sread(buffer, nr_bytes * 2) != 1) { 210 | Serial.println("reading data failed\n"); 211 | goto start; 212 | } 213 | 214 | memset((uint8_t *)result, 0, nr_bytes); 215 | 216 | jtag_write(0, 1, 1); 217 | 218 | int bytesLeft = nr_bytes; 219 | int bitsLeft = len; 220 | int byteIndex = 0; 221 | uint32_t tdi, tms, tdo; 222 | 223 | while (bytesLeft > 0) { 224 | tms = 0; 225 | tdi = 0; 226 | tdo = 0; 227 | if (bytesLeft >= 4) { 228 | memcpy(&tms, &buffer[byteIndex], 4); 229 | memcpy(&tdi, &buffer[byteIndex + nr_bytes], 4); 230 | tdo = jtag_xfer(32, tms, tdi); 231 | memcpy(&result[byteIndex], &tdo, 4); 232 | bytesLeft -= 4; 233 | bitsLeft -= 32; 234 | byteIndex += 4; 235 | } else { 236 | memcpy(&tms, &buffer[byteIndex], bytesLeft); 237 | memcpy(&tdi, &buffer[byteIndex + nr_bytes], bytesLeft); 238 | tdo = jtag_xfer(bitsLeft, tms, tdi); 239 | memcpy(&result[byteIndex], &tdo, bytesLeft); 240 | bytesLeft = 0; 241 | break; 242 | } 243 | } 244 | 245 | jtag_write(0, 1, 0); 246 | 247 | if (client.write(result, nr_bytes) != nr_bytes) { 248 | Serial.println("write"); 249 | } 250 | } while (1); 251 | } 252 | } 253 | } 254 | } 255 | --------------------------------------------------------------------------------