From aab38dd51ee8402f4a19c17faf0cc0fa92926121 Mon Sep 17 00:00:00 2001 From: Gonzalo Avila Alterach Date: Thu, 11 Jun 2026 16:56:28 -0300 Subject: [PATCH] feat: add passive serial mode for usb blaster (altera/intel) --- README.md | 1 + doc/vendors/intel.rst | 13 +++++ src/main.cpp | 48 +++++++++++++++- src/usbBlaster.cpp | 124 +++++++++++++++++++++++++++++++++++++++++- src/usbBlaster.hpp | 13 +++++ 5 files changed, 195 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 042e492..cdfdb66 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ openFPGALoader -- a program to flash FPGA -d, --device arg device to use (/dev/ttyUSBx) --detect detect FPGA, add -f to show connected flash --dfu DFU mode + --passive-serial USB-Blaster passive serial mode --dump-flash Dump flash mode --bulk-erase Bulk erase flash --enable-quad Enable quad mode for SPI Flash diff --git a/doc/vendors/intel.rst b/doc/vendors/intel.rst index 00d9cd6..6bf7cd6 100644 --- a/doc/vendors/intel.rst +++ b/doc/vendors/intel.rst @@ -199,6 +199,19 @@ default behavior and user must explictly change this by using * ``--flash-sector UFM1,CFM2`` to erase and update ``UFM1``, ``UFM0`` and ``CFM2`` (equivalent to ``--flash-sector UFM1,UFM0,CFM2``) +USB-Blaster passive serial mode +------------------------------- + +USB-Blaster I cables can load a raw RBF directly through the passive +serial pins: + +.. code-block:: bash + + openFPGALoader -c usb-blaster --passive-serial project_name.rbf + +The pin mapping is TCK to DCLK, TDO to CONF_DONE, TMS to nCONFIG, and TDI to +DATA0. RBF bytes are transmitted LSB first. + Intel/Altera (Old Boards) ========================= diff --git a/src/main.cpp b/src/main.cpp index 44de606..451e360 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,6 +57,9 @@ #include "progressBar.hpp" #include "spiFlash.hpp" #include "rawParser.hpp" +#ifdef ENABLE_USBBLASTER +#include "usbBlaster.hpp" +#endif #ifdef ENABLE_XILINX_SUPPORT #include "xilinx.hpp" #endif @@ -93,6 +96,7 @@ struct arguments { bool is_list_command; bool spi; bool dfu; + bool passive_serial; std::string file_type; std::string fpga_part; std::string bridge_path; @@ -157,8 +161,8 @@ int main(int argc, char **argv) false, false, false, false, false, 0, "", "", "", "-", "", -1, -1, 0, false, "-", false, false, false, false, Device::PRG_NONE, false, - /* spi dfu file_type fpga_part bridge_path probe_firmware */ - false, false, "", "", "", "", + /* spi dfu passive_serial file_type fpga_part bridge_path probe_firmware */ + false, false, false, "", "", "", "", /* index_chain file_size target_flash external_flash spi_flash_type altsetting */ -1, 0, "primary", false, true, -1, /* vid, pid, index bus_addr, device_addr */ @@ -381,6 +385,36 @@ int main(int argc, char **argv) } #endif + /* ------------------------------ */ + /* USB-Blaster passive serial mode */ + /* ------------------------------ */ + if (args.passive_serial) { +#ifdef ENABLE_USBBLASTER + if (cable.type != MODE_USBBLASTER) { + printError("Passive serial mode requires a USB-Blaster cable"); + return EXIT_FAILURE; + } + + try { + RawParser bit(args.bit_file, false); + if (bit.parse() != EXIT_SUCCESS) + throw std::runtime_error("Failed to parse raw RBF file"); + + UsbBlaster blaster(cable, args.probe_firmware, args.verbose); + blaster.programPassiveSerial(bit.getData(), bit.getLength() / 8); + } catch (std::exception &e) { + printError("Passive serial programming failed: " + std::string(e.what())); + return EXIT_FAILURE; + } + + printSuccess("Passive serial programming DONE"); + return EXIT_SUCCESS; +#else + printError("USB-Blaster support was not enabled at compile time"); + return EXIT_FAILURE; +#endif + } + /* jtag base */ @@ -941,6 +975,8 @@ int parse_opt(int argc, char **argv, struct arguments *args, ("detect", "detect FPGA, add -f to show connected flash", cxxopts::value(args->detect)) ("dfu", "DFU mode", cxxopts::value(args->dfu)) + ("passive-serial", "USB-Blaster passive serial mode", + cxxopts::value(args->passive_serial)) ("dump-flash", "Dump flash mode") ("bulk-erase", "Bulk erase flash", cxxopts::value(args->bulk_erase_flash)) @@ -1111,6 +1147,14 @@ int parse_opt(int argc, char **argv, struct arguments *args, else if (result.count("external-flash")) args->prg_type = Device::WR_FLASH; + if (args->passive_serial) { + if (args->spi || args->dfu || args->xvc || args->detect) { + printError("Error: --passive-serial cannot be combined with " + "--spi, --dfu, --xvc or --detect"); + return -1; + } + } + if (result.count("freq")) { double freq; if (parse_eng(freqo, &freq)) { diff --git a/src/usbBlaster.cpp b/src/usbBlaster.cpp index 97eb284..2d97eee 100644 --- a/src/usbBlaster.cpp +++ b/src/usbBlaster.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -41,7 +42,8 @@ UsbBlaster::UsbBlaster(const cable_t &cable, const std::string &firmware_path, int8_t verbose): _verbose(verbose > 1), _nb_bit(0), - _curr_tms(0), _buffer_size(64) + _curr_tms(0), _buffer_size(64), + _passive_serial_mode(false) { if (cable.pid == 0x6001 || cable.pid == 0x6002 || cable.pid == 0x6003) #ifdef ENABLE_USB_BLASTERI @@ -85,7 +87,8 @@ UsbBlaster::UsbBlaster(const cable_t &cable, const std::string &firmware_path, UsbBlaster::~UsbBlaster() { - _in_buf[_nb_bit++] = 0; + if (!_passive_serial_mode) + _in_buf[_nb_bit++] = 0; flush(); free(_in_buf); } @@ -305,6 +308,123 @@ int UsbBlaster::flush() return write(false, 0); } +int UsbBlaster::setPassiveSerialPins(bool dclk, bool nconfig, bool data0) +{ + uint8_t value = DEFAULT | DO_WRITE | DO_BITBB; + if (dclk) + value |= _tck_pin; + if (nconfig) + value |= _tms_pin; + if (data0) + value |= _tdi_pin; + + return ll_driver->write(&value, 1, NULL, 0); +} + +bool UsbBlaster::readConfDone() +{ + uint8_t value = DEFAULT | DO_READ | DO_BITBB | _tms_pin; + uint8_t input = 0; + int ret = ll_driver->write(&value, 1, &input, 1); + if (ret <= 0) + throw std::runtime_error("Failed to read CONF_DONE"); + + return (input & 0x01) != 0; +} + +int UsbBlaster::shiftPassiveSerial(const uint8_t *data, uint32_t length) +{ + uint8_t buffer[64]; + uint32_t offset = 0; + + while (offset < length) { + uint32_t chunk = length - offset; + if (chunk > 63) + chunk = 63; + + buffer[0] = DO_SHIFT | DO_WRITE | static_cast(chunk); + memcpy(&buffer[1], &data[offset], chunk); + int ret = ll_driver->write(buffer, static_cast(chunk + 1), NULL, 0); + if (ret != static_cast(chunk + 1)) + return -1; + offset += chunk; + } + + return length; +} + +int UsbBlaster::clockPassiveSerial(uint32_t cycles) +{ + uint8_t buffer[64]; + uint8_t value = DEFAULT | DO_WRITE | DO_BITBB | _tms_pin; + uint32_t remaining = cycles; + + while (remaining != 0) { + uint32_t chunk = remaining; + if (chunk > 31) + chunk = 31; + + for (uint32_t i = 0; i < chunk; i++) { + buffer[2 * i] = value; + buffer[2 * i + 1] = value | _tck_pin; + } + buffer[2 * chunk] = value; + + int length = static_cast(2 * chunk + 1); + if (ll_driver->write(buffer, length, NULL, 0) != length) + return -1; + remaining -= chunk; + } + + return static_cast(cycles); +} + +void UsbBlaster::programPassiveSerial(const uint8_t *data, uint32_t length) +{ + if (data == NULL || length == 0) + throw std::runtime_error("Passive serial bitstream is empty"); + if (flush() < 0) + throw std::runtime_error("Failed to flush USB-Blaster"); + + /* + * The normal destructor command drives TMS low, which is nCONFIG in + * passive serial mode and would immediately reset the configured FPGA. + */ + _passive_serial_mode = true; + + try { + if (setPassiveSerialPins(false, false, false) != 1) + throw std::runtime_error("Failed to assert nCONFIG"); + usleep(5); + + if (setPassiveSerialPins(false, true, false) != 1) + throw std::runtime_error("Failed to release nCONFIG"); + + /* + * nSTATUS is not available. Assume device will be ready after a small delay. + */ + usleep(2000); + if (readConfDone()) + throw std::runtime_error("CONF_DONE did not clear after nCONFIG"); + + if (shiftPassiveSerial(data, length) != static_cast(length)) + throw std::runtime_error("Failed to write passive serial data"); + + if (!readConfDone()) + throw std::runtime_error("CONF_DONE is low after passive serial programming"); + + /* Complete device initialization after CONF_DONE is asserted. */ + if (clockPassiveSerial(10) != 10) + throw std::runtime_error("Failed to write passive serial startup clocks"); + } catch (...) { + setPassiveSerialPins(false, true, false); + throw; + } + + if (setPassiveSerialPins(false, true, false) != 1) + throw std::runtime_error("Failed to leave passive serial pins idle"); +} + /* simply call write and return buffer */ int UsbBlaster::writeByte(uint8_t *tdo, int nb_byte) diff --git a/src/usbBlaster.hpp b/src/usbBlaster.hpp index 82b80b1..cd8d926 100644 --- a/src/usbBlaster.hpp +++ b/src/usbBlaster.hpp @@ -82,12 +82,24 @@ class UsbBlaster : public JtagInterface { int flush() override; + /*! + * \brief configure an FPGA using the passive serial pin mapping + * TCK=DCLK, TDO=CONF_DONE, TMS=nCONFIG and TDI=DATA0 + * \param data raw configuration bytes, shifted LSB first + * \param length number of bytes to send + */ + void programPassiveSerial(const uint8_t *data, uint32_t length); + private: UsbBlaster_ll *ll_driver; int writeByte(uint8_t *tdo, int nb_byte); int writeBit(uint8_t *tdo, int nb_bit); int write(bool read, int rd_len); int setBitmode(uint8_t mode); + int setPassiveSerialPins(bool dclk, bool nconfig, bool data0); + bool readConfDone(); + int shiftPassiveSerial(const uint8_t *data, uint32_t length); + int clockPassiveSerial(uint32_t cycles); uint8_t *_in_buf; int8_t _verbose; @@ -97,6 +109,7 @@ class UsbBlaster : public JtagInterface { int _nb_bit; uint8_t _curr_tms; uint16_t _buffer_size; + bool _passive_serial_mode; }; #ifdef ENABLE_USB_BLASTERI