├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── settings.json └── tasks.json ├── README.md ├── picture.jpg ├── sketch.json ├── vivado_ila.png └── xvc-esp32.ino /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.elf -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "${env:HOME}/.arduino15/packages/m5stack/hardware/esp32/1.0.4/**", 8 | "${env:HOME}/Arduino/libraries/M5Atom/**", 9 | "${env:HOME}/Arduino/libraries/FastLED/**" 10 | ], 11 | "defines": [ 12 | "ESP32" 13 | ], 14 | "compilerPath": "${env:HOME}/.arduino15/packages/m5stack/tools/xtensa-esp32-elf-gcc/1.22.0-80-g6c4433a-5.2.0/bin/xtensa-esp32-elf-gcc", 15 | "cStandard": "c11", 16 | "cppStandard": "c++14", 17 | "intelliSenseMode": "gcc-arm", 18 | "compilerArgs": [], 19 | "forcedInclude": [ 20 | "${env:HOME}/.arduino15/packages/m5stack/hardware/esp32/1.0.4/cores/esp32/Arduino.h" 21 | ] 22 | } 23 | ], 24 | "version": 4 25 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "bitset": "cpp", 4 | "memory": "cpp", 5 | "random": "cpp", 6 | "optional": "cpp", 7 | "cstdint": "cpp", 8 | "array": "cpp", 9 | "*.tcc": "cpp", 10 | "cctype": "cpp", 11 | "clocale": "cpp", 12 | "cmath": "cpp", 13 | "cstdarg": "cpp", 14 | "cstddef": "cpp", 15 | "cstdio": "cpp", 16 | "cstdlib": "cpp", 17 | "cstring": "cpp", 18 | "ctime": "cpp", 19 | "cwchar": "cpp", 20 | "cwctype": "cpp", 21 | "deque": "cpp", 22 | "unordered_map": "cpp", 23 | "unordered_set": "cpp", 24 | "vector": "cpp", 25 | "exception": "cpp", 26 | "algorithm": "cpp", 27 | "functional": "cpp", 28 | "system_error": "cpp", 29 | "tuple": "cpp", 30 | "type_traits": "cpp", 31 | "fstream": "cpp", 32 | "initializer_list": "cpp", 33 | "iomanip": "cpp", 34 | "iosfwd": "cpp", 35 | "istream": "cpp", 36 | "limits": "cpp", 37 | "new": "cpp", 38 | "ostream": "cpp", 39 | "numeric": "cpp", 40 | "sstream": "cpp", 41 | "stdexcept": "cpp", 42 | "streambuf": "cpp", 43 | "cinttypes": "cpp", 44 | "utility": "cpp", 45 | "typeinfo": "cpp" 46 | } 47 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "type": "shell", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | }, 13 | "problemMatcher":"$gcc", 14 | "command": "arduino-cli compile --build-properties build.code_debug=3 && arduino-cli upload --port /dev/ttyUSB0" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xilinx Virtual Cable Server for ESP32 2 | 3 | ## 概要 4 | 5 | Xilinx製FPGAへの書き込みを行うためのプロトコル (XVC : Xilinx Virtual Cable) のESP32向け実装です。 6 | 7 | Derek Mulcahy氏の Raspberry Pi向け実装(https://github.com/derekmulcahy/xvcpi) をESP32向けに移植しています。 8 | 9 | ESP32を対象FPGAのJTAGピン (TDI, TDO, TMS, TCK) に接続することにより、WiFi経由でXilinx製のツール (Vivadoなど) からFPGAのJTAGポートにアクセスすることができます。 10 | 11 | ![M5Atom](picture.jpg) 12 | 13 | 写真の例では、M5Stack製のESP32モジュール [M5Atom Lite](https://docs.m5stack.com/#/en/core/atom_lite) とZynq XC7Z010を接続しています。 14 | 15 | この状態で、VivadoのHardware Managerから `Add Virtual Cable` でVirtual Cableとして追加して接続すると、通常のJTAGアダプタと同様にILAの波形観測などを行えます。 16 | 17 | ![ILA](vivado_ila.png) 18 | 19 | ## 使い方 20 | 21 | `xvc-esp32.ino` を使いたいESP32ボード向けにArduino IDEでビルドして書き込むだけです。 22 | 23 | その際、現状のスケッチではWiFiの接続先をコードに埋め込んでいますので、適宜修正してください。 24 | 25 | ```c++ 26 | static const char* MY_SSID = "ssid"; 27 | static const char* MY_PASSPHRASE = "wifi_passphrase"; 28 | ``` 29 | 30 | また、現状は M5Atom用のピン定義になっていますので、使いたいESP32ボード向けにピン定義を変更してください。 31 | 32 | ``` 33 | /* GPIO numbers for each signal. Negative values are invalid */ 34 | /* Note that currently only supports GPIOs below 32 to improve performance */ 35 | static constexpr const int tms_gpio = 22; 36 | static constexpr const int tck_gpio = 19; 37 | static constexpr const int tdo_gpio = 21; 38 | static constexpr const int tdi_gpio = 25; 39 | ``` 40 | 41 | コメントにあるように、現状は高速化のためにGPIO0~31までのみ使用可能です。 42 | 43 | 44 | ## ライセンス 45 | 46 | 大本の AVNET による実装が CC0 1.0 だったので、Derek Mulcahy氏の Raspberry Pi向け実装も CC0 1.0となっています。 47 | 48 | 従って、本実装も (日本国内でのCC0の有効性はともかく) CC0 1.0 ということにします。 -------------------------------------------------------------------------------- /picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciniml/xvc-esp32/c184d1dc1d87ed5bce84138b08e31feac69bec8f/picture.jpg -------------------------------------------------------------------------------- /sketch.json: -------------------------------------------------------------------------------- 1 | { 2 | "cpu": { 3 | "fqbn": "m5stack:esp32:m5stack-atom", 4 | "port": "/dev/ttyUSB0" 5 | } 6 | } -------------------------------------------------------------------------------- /vivado_ila.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciniml/xvc-esp32/c184d1dc1d87ed5bce84138b08e31feac69bec8f/vivado_ila.png -------------------------------------------------------------------------------- /xvc-esp32.ino: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Description : Xilinx Virtual Cable Server for ESP32 4 | * 5 | * See Licensing information at End of File. 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | static const char* MY_SSID = "ssid"; 20 | static const char* MY_PASSPHRASE = "wifi_passphrase"; 21 | 22 | #define ERROR_JTAG_INIT_FAILED -1 23 | #define ERROR_OK 1 24 | 25 | /* GPIO numbers for each signal. Negative values are invalid */ 26 | /* Note that currently only supports GPIOs below 32 to improve performance */ 27 | static constexpr const int tms_gpio = 22; 28 | static constexpr const int tck_gpio = 19; 29 | static constexpr const int tdo_gpio = 21; 30 | static constexpr const int tdi_gpio = 25; 31 | 32 | //static inline volatile std::uint32_t* gpio_set_reg(std::uint8_t pin) { return pin >= 32 ? &GPIO.out1_w1ts.val : &GPIO.out_w1ts; } 33 | //static inline volatile std::uint32_t* gpio_clear_reg(std::uint8_t pin) { return pin >= 32 ? &GPIO.out1_w1tc.val : &GPIO.out_w1tc; } 34 | 35 | #define GPIO_CLEAR (GPIO.out_w1tc) 36 | #define GPIO_SET (GPIO.out_w1ts) 37 | #define GPIO_IN (GPIO.in) 38 | 39 | /* Transition delay coefficients */ 40 | static const unsigned int jtag_delay = 10; 41 | 42 | static std::uint32_t jtag_xfer(std::uint_fast8_t n, std::uint32_t tms, std::uint32_t tdi) 43 | { 44 | std::uint32_t tdo = 0; 45 | const std::uint32_t tdo_bit = (1u << (n - 1)); 46 | for (int i = 0; i < n; i++) { 47 | jtag_write(0, tms & 1, tdi & 1); 48 | asm volatile ("nop"); 49 | asm volatile ("nop"); 50 | asm volatile ("nop"); 51 | jtag_write(1, tms & 1, tdi & 1); 52 | for (std::uint32_t i = 0; i < jtag_delay; i++) asm volatile ("nop"); 53 | tdo >>= 1; 54 | tdo |= jtag_read() ? tdo_bit : 0; 55 | jtag_write(0, tms & 1, tdi & 1); 56 | for (std::uint32_t i = 0; i < jtag_delay; i++) asm volatile ("nop"); 57 | tms >>= 1; 58 | tdi >>= 1; 59 | 60 | } 61 | return tdo; 62 | } 63 | 64 | static bool jtag_read(void) 65 | { 66 | return !!(GPIO_IN & 1<(target); 95 | while (len) { 96 | int r = read(fd, t, len); 97 | if (r <= 0) 98 | return r; 99 | t += r; 100 | len -= r; 101 | } 102 | return 1; 103 | } 104 | 105 | static constexpr const char* TAG = "XVC"; 106 | 107 | struct Socket 108 | { 109 | int fd; 110 | Socket() : fd(-1) {} 111 | Socket(int fd) : fd(fd) {} 112 | Socket(int domain, int family, int protocol) 113 | { 114 | this->fd = socket(domain, family, protocol); 115 | } 116 | Socket(const Socket&) = delete; 117 | Socket(Socket&& rhs) : fd(rhs.fd) { rhs.fd = -1; } 118 | ~Socket() { this->release(); } 119 | void release() { 120 | if( this->is_valid() ) { 121 | closesocket(this->fd); 122 | this->fd = -1; 123 | } 124 | } 125 | int get() const { return this->fd;} 126 | 127 | Socket& operator=(Socket&& rhs) { this->fd = rhs.fd; rhs.fd = -1; return *this; } 128 | bool is_valid() const { return this->fd > 0; } 129 | operator int() const { return this->get();} 130 | }; 131 | 132 | class XvcServer 133 | { 134 | private: 135 | Socket listen_socket; 136 | Socket client_socket; 137 | unsigned char buffer[2048], result[1024]; 138 | public: 139 | XvcServer(std::uint16_t port) { 140 | Socket sock(AF_INET, SOCK_STREAM, 0); 141 | { 142 | int value = 1; 143 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)); 144 | } 145 | 146 | sockaddr_in address; 147 | address.sin_addr.s_addr = INADDR_ANY; 148 | address.sin_port = htons(port); 149 | address.sin_family = AF_INET; 150 | 151 | if( bind(sock, reinterpret_cast(&address), sizeof(address)) < 0 ) { 152 | ESP_LOGE(TAG, "Failed to bind socket."); 153 | } 154 | if( listen(sock, 0) < 0 ) { 155 | ESP_LOGE(TAG, "Failed to listen the socket."); 156 | } 157 | 158 | ESP_LOGI(TAG, "Begin XVC Server. port=%d", port); 159 | this->listen_socket = std::move(sock); 160 | } 161 | XvcServer(const XvcServer&) = delete; 162 | 163 | bool wait_connection() 164 | { 165 | fd_set conn; 166 | int maxfd = this->listen_socket.get(); 167 | 168 | FD_ZERO(&conn); 169 | FD_SET(this->listen_socket.get(), &conn); 170 | 171 | fd_set read = conn, except = conn; 172 | int fd; 173 | 174 | if (select(maxfd + 1, &read, 0, &except, 0) < 0) { 175 | ESP_LOGE(TAG, "select"); 176 | return false; 177 | } 178 | 179 | for (fd = 0; fd <= maxfd; ++fd) { 180 | if (FD_ISSET(fd, &read)) { 181 | if (fd == this->listen_socket.get()) { 182 | int newfd; 183 | sockaddr_in address; 184 | socklen_t nsize = sizeof(address); 185 | newfd = accept(this->listen_socket.get(), reinterpret_cast(&address), &nsize); 186 | 187 | ESP_LOGI(TAG, "connection accepted - fd %d\n", newfd); 188 | if (newfd < 0) { 189 | ESP_LOGE(TAG, "accept returned an error."); 190 | } else { 191 | if (newfd > maxfd) { 192 | maxfd = newfd; 193 | } 194 | FD_SET(newfd, &conn); 195 | this->client_socket = Socket(newfd); 196 | return true; 197 | } 198 | } 199 | 200 | } 201 | } 202 | 203 | return false; 204 | } 205 | 206 | bool handle_data() 207 | { 208 | const char xvcInfo[] = "xvcServer_v1.0:2048\n"; 209 | int fd = this->client_socket.get(); 210 | 211 | std::uint8_t cmd[16]; 212 | std::memset(cmd, 0, 16); 213 | 214 | if (sread(fd, cmd, 2) != 1) 215 | return false; 216 | 217 | if (memcmp(cmd, "ge", 2) == 0) { 218 | if (sread(fd, cmd, 6) != 1) 219 | return 1; 220 | memcpy(result, xvcInfo, strlen(xvcInfo)); 221 | if (write(fd, result, strlen(xvcInfo)) != strlen(xvcInfo)) { 222 | ESP_LOGE(TAG, "write"); 223 | return 1; 224 | } 225 | ESP_LOGD(TAG, "%u : Received command: 'getinfo'\n", (int)time(NULL)); 226 | ESP_LOGD(TAG, "\t Replied with %s\n", xvcInfo); 227 | return true; 228 | } else if (memcmp(cmd, "se", 2) == 0) { 229 | if (sread(fd, cmd, 9) != 1) 230 | return 1; 231 | memcpy(result, cmd + 5, 4); 232 | if (write(fd, result, 4) != 4) { 233 | ESP_LOGE(TAG, "write"); 234 | return 1; 235 | } 236 | ESP_LOGD(TAG, "%u : Received command: 'settck'\n", (int)time(NULL)); 237 | ESP_LOGD(TAG, "\t Replied with '%.*s'\n\n", 4, cmd + 5); 238 | 239 | return true; 240 | } else if (memcmp(cmd, "sh", 2) == 0) { 241 | if (sread(fd, cmd, 4) != 1) 242 | return false; 243 | ESP_LOGD(TAG, "%u : Received command: 'shift'\n", (int)time(NULL)); 244 | } else { 245 | 246 | ESP_LOGE(TAG, "invalid cmd '%s'\n", cmd); 247 | return false; 248 | } 249 | 250 | int len; 251 | if (sread(fd, &len, 4) != 1) { 252 | ESP_LOGE(TAG, "reading length failed\n"); 253 | return false; 254 | } 255 | 256 | int nr_bytes = (len + 7) / 8; 257 | if (nr_bytes * 2 > sizeof(buffer)) { 258 | ESP_LOGE(TAG, "buffer size exceeded\n"); 259 | return false; 260 | } 261 | 262 | if (sread(fd, buffer, nr_bytes * 2) != 1) { 263 | ESP_LOGE(TAG, "reading data failed\n"); 264 | return false; 265 | } 266 | memset(result, 0, nr_bytes); 267 | 268 | ESP_LOGD(TAG, "Number of Bits : %d\n", len); 269 | ESP_LOGD(TAG, "Number of Bytes : %d \n", nr_bytes); 270 | 271 | jtag_write(0, 1, 1); 272 | 273 | int bytesLeft = nr_bytes; 274 | int bitsLeft = len; 275 | int byteIndex = 0; 276 | uint32_t tdi, tms, tdo; 277 | 278 | while (bytesLeft > 0) { 279 | tms = 0; 280 | tdi = 0; 281 | tdo = 0; 282 | if (bytesLeft >= 4) { 283 | memcpy(&tms, &buffer[byteIndex], 4); 284 | memcpy(&tdi, &buffer[byteIndex + nr_bytes], 4); 285 | 286 | tdo = jtag_xfer(32, tms, tdi); 287 | memcpy(&result[byteIndex], &tdo, 4); 288 | 289 | bytesLeft -= 4; 290 | bitsLeft -= 32; 291 | byteIndex += 4; 292 | 293 | ESP_LOGD(TAG, "LEN : 0x%08x\n", 32); 294 | ESP_LOGD(TAG, "TMS : 0x%08x\n", tms); 295 | ESP_LOGD(TAG, "TDI : 0x%08x\n", tdi); 296 | ESP_LOGD(TAG, "TDO : 0x%08x\n", tdo); 297 | } else { 298 | memcpy(&tms, &buffer[byteIndex], bytesLeft); 299 | memcpy(&tdi, &buffer[byteIndex + nr_bytes], bytesLeft); 300 | 301 | tdo = jtag_xfer(bitsLeft, tms, tdi); 302 | memcpy(&result[byteIndex], &tdo, bytesLeft); 303 | 304 | bytesLeft = 0; 305 | 306 | ESP_LOGD(TAG, "LEN : 0x%08x\n", bitsLeft); 307 | ESP_LOGD(TAG, "TMS : 0x%08x\n", tms); 308 | ESP_LOGD(TAG, "TDI : 0x%08x\n", tdi); 309 | ESP_LOGD(TAG, "TDO : 0x%08x\n", tdo); 310 | break; 311 | } 312 | } 313 | 314 | jtag_write(0, 1, 0); 315 | 316 | if (write(fd, result, nr_bytes) != nr_bytes) { 317 | ESP_LOGE(TAG, "write"); 318 | return false; 319 | } 320 | 321 | return true; 322 | } 323 | 324 | void run() 325 | { 326 | if( this->client_socket.is_valid() ) { 327 | if( !this->handle_data() ) { 328 | this->client_socket.release(); 329 | } 330 | } 331 | else { 332 | if( this->wait_connection() ) { 333 | // Nothing to do. 334 | } 335 | } 336 | } 337 | }; 338 | 339 | static void serialTask(void*) 340 | { 341 | std::size_t pendingBytesH2T = 0; 342 | std::size_t bytesWrittenH2T = 0; 343 | static std::uint8_t h2tBuffer[128]; 344 | std::size_t pendingBytesT2H = 0; 345 | std::size_t bytesWrittenT2H = 0; 346 | static std::uint8_t t2hBuffer[128]; 347 | 348 | while(true) { 349 | if( Serial.available() ) { 350 | Serial1.write(Serial.read()); 351 | } 352 | else if( Serial1.available() ) { 353 | Serial.write(Serial1.read()); 354 | } 355 | else { 356 | vTaskDelay(pdMS_TO_TICKS(1)); 357 | } 358 | } 359 | } 360 | 361 | void setup() 362 | { 363 | jtag_init(); 364 | 365 | WiFi.begin(MY_SSID, MY_PASSPHRASE); 366 | Serial.begin(115200); 367 | Serial1.begin(115200, SERIAL_8N1, 33, 23); 368 | 369 | TaskHandle_t handle; 370 | xTaskCreatePinnedToCore(serialTask,"serial", 4096, nullptr, 1, &handle, APP_CPU_NUM); 371 | } 372 | 373 | enum class AppState 374 | { 375 | WaitingAPConnection, 376 | APConnected, 377 | ClientConnected, 378 | }; 379 | 380 | static std::unique_ptr server; 381 | 382 | void loop() 383 | { 384 | static AppState state = AppState::WaitingAPConnection; 385 | switch(state) { 386 | case AppState::WaitingAPConnection: { 387 | if( WiFi.isConnected() ) { 388 | ESP_LOGI(TAG, "WiFi connection ready. IP: %s", WiFi.localIP().toString().c_str()); 389 | state = AppState::APConnected; 390 | } 391 | break; 392 | } 393 | case AppState::APConnected: { 394 | if( !WiFi.isConnected() ) { 395 | ESP_LOGI(TAG, "disconnected from WiFi."); 396 | server.release(); 397 | state = AppState::WaitingAPConnection; 398 | break; 399 | } 400 | 401 | if( !server ) { 402 | server.reset(new XvcServer(2542)); 403 | } 404 | if( server ) { 405 | server->run(); 406 | } 407 | break; 408 | } 409 | } 410 | } 411 | 412 | 413 | /* 414 | * This work, "xvc-esp32.ino", is a derivative of "xvcpi.c" (https://github.com/derekmulcahy/xvcpi) 415 | * by Derek Mulcahy. 416 | * 417 | * "xvc-esp32.ino" is licensed under CC0 1.0 Universal (http://creativecommons.org/publicdomain/zero/1.0/) 418 | * by Kenta IDA (fuga@fugafuga.org) 419 | * 420 | * The original license information of "xvcpi.c" is attached below. 421 | */ 422 | 423 | /* 424 | * This work, "xvcpi.c", is a derivative of "xvcServer.c" (https://github.com/Xilinx/XilinxVirtualCable) 425 | * by Avnet and is used by Xilinx for XAPP1251. 426 | * 427 | * "xvcServer.c" is licensed under CC0 1.0 Universal (http://creativecommons.org/publicdomain/zero/1.0/) 428 | * by Avnet and is used by Xilinx for XAPP1251. 429 | * 430 | * "xvcServer.c", is a derivative of "xvcd.c" (https://github.com/tmbinc/xvcd) 431 | * by tmbinc, used under CC0 1.0 Universal (http://creativecommons.org/publicdomain/zero/1.0/). 432 | * 433 | * Portions of "xvcpi.c" are derived from OpenOCD (http://openocd.org) 434 | * 435 | * "xvcpi.c" is licensed under CC0 1.0 Universal (http://creativecommons.org/publicdomain/zero/1.0/) 436 | * by Derek Mulcahy.* 437 | */ --------------------------------------------------------------------------------