diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index 6ccf230..5c93959 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -118,6 +118,7 @@ jobs: run: | #brew update brew install --only-dependencies openFPGALoader + brew install hidapi - name: '🚧 Build tarball' run: | @@ -214,4 +215,4 @@ jobs: with: token: ${{ github.token }} tag: 'nightly' - files: artifact/**/* \ No newline at end of file + files: artifact/**/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 442327f..45b3d56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ option(ENABLE_USB_BLASTERII "enable Altera USB Blaster II support" ${ set(BLASTERII_PATH "" CACHE STRING "usbBlasterII firmware directory") set(ISE_PATH "/opt/Xilinx/14.7" CACHE STRING "ise root directory (default: /opt/Xilinx/14.7)") +set(VIVADO_PATH "" CACHE STRING "Vivado root directory (something like /opt/Xilinx/Vivado/VERSION)") # Libgpiod is only available on Linux OS. if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") @@ -64,6 +65,8 @@ else() set(ENABLE_LIBGPIOD OFF) endif() +option(ENABLE_XILINX_PLATFORM_CABLE_USB "enable Xilinx Platform Cable USB (XPCU) support" ${ENABLE_CABLE_ALL}) + # XVC and RemoteBitbang are not available on Windows OS. if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Windows") option(ENABLE_REMOTEBITBANG "enable remote bitbang driver" ${ENABLE_CABLE_ALL}) @@ -102,6 +105,11 @@ set(USE_LIBFTDI OFF) set(USE_LIBUSB OFF) set(USE_LIBUSB_LL OFF) +# USB BlasterII and Xilinx Platform Cable USB requires FX2 driver +if (ENABLE_XILINX_PLATFORM_CABLE_USB OR ENABLE_USB_BLASTERII) + set(USE_FX2_LL ON) +endif() + # Only adds libftdi as dependency when a cable # need this library. if (ENABLE_FTDI_BASED_CABLE OR ENABLE_USB_BLASTERI OR ENABLE_XILINX_VIRTUAL_CABLE_SERVER) @@ -112,7 +120,7 @@ endif(ENABLE_FTDI_BASED_CABLE OR ENABLE_USB_BLASTERI OR ENABLE_XILINX_VIRTUAL_CA # Only adds libusb as dependency when a cable need this library if (ENABLE_DFU OR ENABLE_ANLOGIC_CABLE OR ENABLE_CH347 OR ENABLE_DIRTYJTAG - OR ENABLE_ESP_USB OR ENABLE_JLINK OR ENABLE_GOWIN_GWU2X OR ENABLE_USB_BLASTERII OR ENABLE_USB_SCAN) + OR ENABLE_ESP_USB OR ENABLE_JLINK OR ENABLE_GOWIN_GWU2X OR USE_FX2_LL OR ENABLE_USB_SCAN) set(USE_LIBUSB ON) endif() @@ -120,11 +128,6 @@ if (ENABLE_USB_SCAN OR ENABLE_GOWIN_GWU2X) set(USE_LIBUSB_LL ON) endif() -# Only enable fx2_ll when cable using it -if (ENABLE_USB_BLASTERII) - set(USE_FX2_LL ON) -endif() - #################################################################################################### # Cross-compilation support #################################################################################################### @@ -179,28 +182,23 @@ include(GNUInstallDirs) add_definitions(-DDATA_DIR=\"${CMAKE_INSTALL_FULL_DATAROOTDIR}\") add_definitions(-DISE_DIR=\"${ISE_PATH}\") +add_definitions(-DVIVADO_DIR=\"${VIVADO_PATH}\") #################################################################################################### # Dependencies check/search #################################################################################################### -if (USE_LIBFTDI) - # Try to find the LibFTDI1 with cmake - find_package(LibFTDI1 QUIET) -endif() - if (USE_PKGCONFIG) find_package(PkgConfig REQUIRED) # Backward compat when the libftdi is not found # by using cmake if (USE_LIBFTDI) - if (NOT LIBFTDI_FOUND) - pkg_check_modules(LIBFTDI REQUIRED libftdi1) - string(REPLACE "." ";" VERSION_LIST ${LIBFTDI_VERSION}) - list(GET VERSION_LIST 0 LIBFTDI_VERSION_MAJOR) - list(GET VERSION_LIST 1 LIBFTDI_VERSION_MINOR) - endif() + pkg_check_modules(LIBFTDI REQUIRED libftdi1) + string(REPLACE "." ";" VERSION_LIST ${LIBFTDI_VERSION}) + list(GET VERSION_LIST 0 LIBFTDI_VERSION_MAJOR) + list(GET VERSION_LIST 1 LIBFTDI_VERSION_MINOR) + string(REGEX REPLACE "[^0-9].*" "" LIBFTDI_VERSION_MINOR "${LIBFTDI_VERSION_MINOR}") else() set(LIBFTDI_LIBRARY_DIRS "") set(LIBFTDI_INCLUDE_DIRS "") @@ -590,6 +588,12 @@ list (APPEND OPENFPGALOADER_HEADERS src/svf_jtag.hpp) add_definitions(-DENABLE_SVF_JTAG) endif() +# Xilinx Platform Cable USB +if (ENABLE_XILINX_PLATFORM_CABLE_USB) + list(APPEND OPENFPGALOADER_SOURCE src/xilinxPlatformCableUSB.cpp) + list(APPEND OPENFPGALOADER_HEADERS src/xilinxPlatformCableUSB.hpp) +endif() + # Xilinx Virtual Cable if (ENABLE_XILINX_VIRTUAL_CABLE_CLIENT) list (APPEND OPENFPGALOADER_SOURCE src/xvc_client.cpp) @@ -772,6 +776,13 @@ else() message("Remote bitbang client support disabled") endif() +if (ENABLE_XILINX_PLATFORM_CABLE_USB) + add_definitions(-DENABLE_XILINX_PLATFORM_CABLE_USB=1) + message("Xilinx Platform Cable USB (XPCU) support enabled") +else() + message("Xilinx Platform Cable USB (XCPU) support disabled") +endif() + if (ENABLE_XILINX_VIRTUAL_CABLE_CLIENT) add_definitions(-DENABLE_XVC_CLIENT=1) set(CMAKE_EXE_LINKER_FLAGS "-pthread ${CMAKE_EXE_LINKER_FLAGS}") @@ -799,6 +810,7 @@ if (ZLIB_FOUND) add_definitions(-DHAS_ZLIB=1) else() message("zlib library not found: can't flash intel/altera devices") + message("or reads compressed bitstreams") endif() if (LINK_CMAKE_THREADS) @@ -813,7 +825,6 @@ if (USE_LIBFTDI) add_definitions(-DFTDI_VERSION=${FTDI_VAL}) endif() - install(TARGETS openFPGALoader DESTINATION bin) #################################################################################################### diff --git a/README.md b/README.md index b9e767b..0950423 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,8 @@ openFPGALoader -- a program to flash FPGA non volatile memory (default: 0) --pins arg pin config TDI:TDO:TCK:TMS or MOSI:MISO:SCK:CS[:HOLDN:WPN] - --probe-firmware arg firmware for JTAG probe (usbBlasterII) + --probe-firmware arg firmware for JTAG probe + (usbBlasterII/xilinxPlatformCableUsb) --protect-flash arg protect SPI flash area --quiet Produce quiet output (no progress bar) -r, --reset reset FPGA after operations diff --git a/doc/boards.yml b/doc/boards.yml index a7185c4..5e3d8d0 100644 --- a/doc/boards.yml +++ b/doc/boards.yml @@ -34,6 +34,13 @@ Memory: OK Flash: NT +- ID: axc3000 + Description: Arrow AXC3000 + URL: https://www.arrow.com/de/products/axc3000/trenz-electronic-gmbh.html + FPGA: Altera Agilex 3 A3CY100BM16AE7S + Memory: OK + Flash: NT + - ID: axe5000 Description: A5EC008BM16AE6S FPGA Starter Kit URL: https://www.arrow.com/en/products/axe5000/trenz-electronic-gmbh.html @@ -380,8 +387,8 @@ Memory: OK Flash: OK Constraints: - - ECPIX-5-45F - - ECPIX-5-85F + - ECPIX-5-45F + - ECPIX-5-85F - ID: ecpix5_r03 Description: LambdaConcept ECPIX-5 (FT4232) @@ -390,8 +397,8 @@ Memory: OK Flash: OK Constraints: - - ECPIX-5-45F - - ECPIX-5-85F + - ECPIX-5-45F + - ECPIX-5-85F - ID: fireant Description: Fireant Trion T8 @@ -451,8 +458,8 @@ Memory: NA Flash: OK Constraints: - - iCEBreaker-bitsy-v0 - - iCEBreaker-bitsy-v1 + - iCEBreaker-bitsy-v0 + - iCEBreaker-bitsy-v1 - ID: ice40_generic Description: icestick @@ -751,6 +758,13 @@ Memory: OK Flash: NT +- ID: sp701 + Description: Xilinx SP701 + URL: https://www.amd.com/en/products/adaptive-socs-and-fpgas/evaluation-boards/sp701.html + FPGA: Spartan 7 xc7s100-2-fgga676 + Memory: OK + Flash: OK + - ID: spartanEdgeAccelBoard Description: SeeedStudio Spartan Edge Accelerator Board URL: http://wiki.seeedstudio.com/Spartan-Edge-Accelerator-Board @@ -855,6 +869,13 @@ Memory: OK Flash: IF +- ID: brs-100-gw1nr9 + Description: BrisbaneSilicon BRS-100-GW1NR9 + URL: https://brisbanesilicon.com.au/brs-100-gw1nr9/ + FPGA: littleBee GW1NR-9C + Memory: OK + Flash: IF/EF + - ID: trion_t20_bga256_jtag Description: Efinix Trion T20 BGA256 Dev Kit URL: https://www.efinixinc.com/products-devkits-triont20.html @@ -904,10 +925,10 @@ Memory: OK Flash: OK Constraints: - - ULX3S-12F - - ULX3S-25F - - ULX3S-45F - - ULX3S-85F + - ULX3S-12F + - ULX3S-25F + - ULX3S-45F + - ULX3S-85F - ID: ulx3s_dfu Description: Radiona ULX3S DFU mode diff --git a/doc/cable.yml b/doc/cable.yml index f8c8230..ee41f2a 100644 --- a/doc/cable.yml +++ b/doc/cable.yml @@ -33,6 +33,13 @@ bus_blaster_b: URL: http://dangerousprototypes.com/docs/Bus_Blaster +ch347: + + - Name: CH347 + Description: CH347 is a USB HS bus converter with UART, I2C, SPI and JTAG interfaces + URL: https://github.com/wuxx/USB-HS-Bridge + + ch347_jtag: - Name: ch347 JTAG adapter @@ -54,33 +61,6 @@ cmsisdap: URL: https://os.mbed.com/docs/mbed-os/v6.11/debug-test/daplink.html -gatemate_pgm: - - - Name: gatemate pgm - Description: Cologne Chip GateMate FPGA Programmer. FT232H-based JTAG/SPI programmer cable - URL: https://colognechip.com/programmable-logic/gatemate/ - - -gatemate_evb_jtag: - - Name: gatemate evb JTAG - Description: Cologne Chip GateMate JTAG programmer - URL: https://colognechip.com/programmable-logic/gatemate/ - - -gatemate_evb_spi: - - - Name: gatemate evb spi - Description: Cologne Chip GateMate SPI programmer - URL: https://colognechip.com/programmable-logic/gatemate/ - - -gwu2x: - - - Name: gwu2x - Description: Gowin GWUX2X - URL: https://www.gowinsemi.com/en/product/detail/55/ - - dfu: - Name: DFU interface @@ -94,7 +74,7 @@ digilent: Description: FT2232 JTAG / UART cable -diglent_b: +digilent_b: - Name: digilent cable Description: digilent FT2232 JTAG / UART cable (interface B) @@ -108,12 +88,14 @@ digilent_hs2: digilent_hs3: + - Name: digilent hs3 Description: JTAG programmer cable from digilent URL: https://digilent.com/shop/jtag-hs3-programming-cable/ dirtyJtag: + - Name: dirty Jtag Description: JTAG probe firmware for STM32F1 URL: https://github.com/jeanthom/DirtyJTAG @@ -121,16 +103,17 @@ dirtyJtag: New version `dirtyjtag2 `__ is also supported -efinix_spi_ft4232: +ecpix5-debug: - - Name: efinix SPI (ft4232) - Description: efinix SPI interface (FTDI4232 interface A) + - Name: ecpix5-debug + Description: LambdaConcept ECPIX5 (45k/85k) UART/JTAG interface + URL: https://shop.lambdaconcept.com/home/46-ecpix-5.html -efinix_jtag_ft4232: +efinix_jtag_ft2232: - - Name: efinix JTAG (ft4232) - Description: efinix JTAG interface (FTDI4232 interface B) + - Name: efinix JTAG (ft2232) + Description: efinix JTAG interface (FTDI2232 interface B) efinix_spi_ft2232: @@ -139,10 +122,16 @@ efinix_spi_ft2232: Description: efinix SPI interface (FTDI2232 interface A) -efinix_jtag_ft2232: +efinix_jtag_ft4232: - - Name: efinix JTAG (ft2232) - Description: efinix JTAG interface (FTDI2232 interface B) + - Name: efinix JTAG (ft4232) + Description: efinix JTAG interface (FTDI4232 interface B) + + +efinix_spi_ft4232: + + - Name: efinix SPI (ft4232) + Description: efinix SPI interface (FTDI4232 interface A) ft2232: @@ -196,23 +185,54 @@ ft4232: Description: quad interface programmer cable. MPSSE capable. URL: https://ftdichip.com/products/ft4232h-56q/ + ft4232hp: - Name: FT4232HP (interface A) Description: quad interface programmer cable. MPSSE capable. High Speed USB Bridge with Type-C/PD3.0 Controller URL: https://ftdichip.com/wp-content/uploads/2023/02/DS_FT4233HP.pdf + ft4232hp_b: - Name: FT4232HP (interface B) Description: quad interface programmer cable. MPSSE capable. High Speed USB Bridge with Type-C/PD3.0 Controller URL: https://ftdichip.com/wp-content/uploads/2023/02/DS_FT4233HP.pdf -ecpix5-debug: - - Name: ecpix5-debug - Description: LambdaConcept ECPIX5 (45k/85k) UART/JTAG interface - URL: https://shop.lambdaconcept.com/home/46-ecpix-5.html +gatemate_pgm: + + - Name: gatemate pgm + Description: Cologne Chip GateMate FPGA Programmer. FT232H-based JTAG/SPI programmer cable + URL: https://colognechip.com/programmable-logic/gatemate/ + + +gatemate_evb_jtag: + + - Name: gatemate evb JTAG + Description: Cologne Chip GateMate JTAG programmer + URL: https://colognechip.com/programmable-logic/gatemate/ + + +gatemate_evb_spi: + + - Name: gatemate evb spi + Description: Cologne Chip GateMate SPI programmer + URL: https://colognechip.com/programmable-logic/gatemate/ + + +gwu2x: + + - Name: gwu2x + Description: Gowin GWUX2X + URL: https://www.gowinsemi.com/en/product/detail/55/ + + +jetson-nano-gpio: + + - Name: Bitbang GPIO + Description: Bitbang GPIO pins on Jetson Nano Linux host. Use /dev/mem to have a faster clock. + URL: https://github.com/jwatte/jetson-gpio-example jlink: @@ -235,6 +255,13 @@ jtag-smt2-nc: URL: https://digilent.com/shop/jtag-smt2-nc-surface-mount-programming-module +libgpiod: + + - Name: Bitbang GPIO + Description: Bitbang GPIO pins on Linux host. + URL: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/ + + lpc-link2: - Name: lpc-link2 @@ -246,7 +273,8 @@ numato: - Name: numato Description: Embedded cable for Numato Systems Mimas-A7 board - + + orbtrace: - Name: orbtrace interface @@ -261,13 +289,6 @@ papilio: URL: https://papilio.cc/ -steppenprobe: - - - Name: steppenprobe - Description: Open Source Hardware JTAG/SWD/UART/SWO interface board based on FTDI FT2232H - URL: https://github.com/diegoherranz/steppenprobe - - remote-bitgang: - Name: OpenOCD remote bitbang @@ -275,6 +296,13 @@ remote-bitgang: URL: https://github.com/openocd-org/openocd/blob/master/doc/manual/jtag/drivers/remote_bitbang.txt +steppenprobe: + + - Name: steppenprobe + Description: Open Source Hardware JTAG/SWD/UART/SWO interface board based on FTDI FT2232H + URL: https://github.com/diegoherranz/steppenprobe + + tigard: - Name: tigard @@ -302,6 +330,13 @@ usb-blasterIII: URL: https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_usb_blstr_ii_cable.pdf +xilinxPlatformCableUsb: + + - Name: Xilinx Platform Cable USB (XPCU) + Description: Xilinx Platform Cable USB (XPCU) from AMD/Xilinx + URL: https://www.amd.com/en/products/adaptive-socs-and-fpgas/board-accessories/hw-usb-ii-g.html + + xvc-client: - Name: Xilinx Virtual Cable @@ -314,22 +349,3 @@ xvc-server: - Name: Xilinx Virtual Cable (server side) Description: Xilinx Virtual Cable (XVC) is a TCP/IP-based protocol that acts like a JTAG cable. URL: https://github.com/Xilinx/XilinxVirtualCable - - -libgpiod: - - - Name: Bitbang GPIO - Description: Bitbang GPIO pins on Linux host. - URL: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/ - -jetson-nano-gpio: - - - Name: Bitbang GPIO - Description: Bitbang GPIO pins on Jetson Nano Linux host. Use /dev/mem to have a faster clock. - URL: https://github.com/jwatte/jetson-gpio-example - -ch347: - - - Name: CH347 - Description: CH347 is a USB HS bus converter with UART, I2C, SPI and JTAG interfaces - URL: https://github.com/wuxx/USB-HS-Bridge diff --git a/doc/vendors/gowin.rst b/doc/vendors/gowin.rst index 103c3e3..4258156 100644 --- a/doc/vendors/gowin.rst +++ b/doc/vendors/gowin.rst @@ -13,6 +13,7 @@ GOWIN GW1N * Sipeed Tang Nano 4K * Honeycomb * RUNBER + * BrisbaneSilicon BRS-100-GW1NR9 ``.fs`` file is the default format generated by *Gowin IDE*, so nothing special must be done to generates this file. @@ -37,12 +38,13 @@ where ``BOARD_NAME`` is: * ``tangnano20k`` * ``tangprimer20k`` * ``runber`` +* ``brs-100-gw1nr9`` Flash ----- .. ATTENTION:: - Only with Trenz TEC0117 and runber. + Only with Trenz TEC0117, runber and brs-100-gw1nr9. with ``-f``, file load: @@ -54,6 +56,7 @@ where ``BOARD_NAME`` is: * ``tec0117`` * ``runber`` +* ``brs-100-gw1nr9`` It's possible to flash external SPI Flash (connected to MSPI) in bscan mode by using ``--external-flash`` instead of ``-f``. diff --git a/scripts/msys2/PKGBUILD b/scripts/msys2/PKGBUILD index 51376e9..98de171 100644 --- a/scripts/msys2/PKGBUILD +++ b/scripts/msys2/PKGBUILD @@ -9,6 +9,7 @@ url="https://github.com/trabucayre/openFPGALoader" license=('Apache-2.0') depends=( "${MINGW_PACKAGE_PREFIX}-libftdi" + "${MINGW_PACKAGE_PREFIX}-hidapi" "${MINGW_PACKAGE_PREFIX}-zlib" ) makedepends=( diff --git a/spiOverJtag/xilinx_spiOverJtag.tcl b/spiOverJtag/xilinx_spiOverJtag.tcl deleted file mode 100644 index 493feea..0000000 --- a/spiOverJtag/xilinx_spiOverJtag.tcl +++ /dev/null @@ -1,82 +0,0 @@ -set parts [lindex $argv 0] - -set project_name "spiOverJtag" - -set build_path tmp_${parts} -file delete -force $build_path - -# Project creation -set grade [dict create \ - xc7a15tfgg484 -1 \ - xc7a25tcpg238 -1 \ - xc7a25tcsg325 -1 \ - xc7a35tcpg236 -1 \ - xc7a35tcsg324 -1 \ - xc7a35tftg256 -1 \ - xc7a50tcpg236 -2 \ - xc7a50tcsg325 -2 \ - xc7a75tfgg484 -2 \ - xc7a100tfgg484 -2 \ - xc7a200tsbg484 -1 \ - xc7k70tfbg676 -1 \ - xc7k160tffg676 -1 \ - xc7k325tffg676 -1 \ - xc7k325tffg900 -2 \ - xc7s25csga225 -1 \ - xc7s50csga324 -1 \ - ] - -set pkg_name [dict create \ - xc7a25tcpg238 xc7a_cpg238 \ - xc7a25tcsg325 xc7a_csg325 \ - xc7a35tcpg236 xc7a_cpg236 \ - xc7a35tcsg324 xc7a_csg324 \ - xc7a35tftg256 xc7a_ftg256 \ - xc7a50tcpg236 xc7a_cpg236 \ - xc7a50tcsg325 xc7a_csg325 \ - xc7a15tfgg484 xc7a_fgg484 \ - xc7a75tfgg484 xc7a_fgg484 \ - xc7a100tfgg484 xc7a_fgg484 \ - xc7a200tsbg484 xc7a_sbg484 \ - xc7a200tfbg484 xc7a_fbg484 \ - xc7k70tfbg676 xc7k_fbg676 \ - xc7k160tffg676 xc7k_ffg676 \ - xc7k325tffg676 xc7k_ffg676 \ - xc7k325tffg900 xc7k_ffg900 \ - xc7s25csga225 xc7s_csga225 \ - xc7s50csga324 xc7s_csga324 \ - ] - -set curr_grade [dict get $grade $parts] -set curr_pins [dict get $pkg_name $parts] - -create_project $project_name $build_path -part ${parts}${curr_grade} - -add_files -norecurse xilinx_spiOverJtag.vhd -add_files -norecurse -fileset constrs_1 constr_${curr_pins}.xdc - -set_property VERILOG_DEFINE {TOOL_VIVADO} [current_fileset] - -# set the current synth run -current_run -synthesis [get_runs synth_1] -reset_run synth_1 - -set obj [get_runs impl_1] -set_property AUTO_INCREMENTAL_CHECKPOINT 1 [get_runs impl_1] - -set_property "needs_refresh" "1" $obj - -# set the current impl run -current_run -implementation [get_runs impl_1] - -puts "INFO: Project created: $project_name" - -launch_runs synth_1 -jobs 4 -wait_on_run synth_1 -## do implementation -launch_runs impl_1 -jobs 4 -wait_on_run impl_1 -## make bit file -launch_runs impl_1 -jobs 4 -to_step write_bitstream -wait_on_run impl_1 -exit diff --git a/src/board.hpp b/src/board.hpp index 57efc97..ef05c6e 100644 --- a/src/board.hpp +++ b/src/board.hpp @@ -236,6 +236,7 @@ static std::map board_list = { JTAG_BOARD("spartanEdgeAccelBoard", "", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), JTAG_BOARD("spec45", "xc6slx45tfgg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), JTAG_BOARD("spec150", "xc6slx150tfgg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("sp701", "xc7s100fgga676", "digilent_ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), JTAG_BOARD("stlv7325", "xc7k325tffg676", "ft4232", SPI_FLASH, 0, 0, CABLE_MHZ(3)), JTAG_BOARD("tangconsole", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), JTAG_BOARD("tangnano", "", "ch552_jtag", SPI_FLASH, 0, 0, CABLE_DEFAULT), @@ -249,6 +250,7 @@ static std::map board_list = { JTAG_BOARD("te0712_8", "xc7a200tfbg484", "", SPI_FLASH, 0, 0, CABLE_MHZ(15)), JTAG_BOARD("tec0117", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), JTAG_BOARD("tec0330", "xc7vx330tffg1157", "", SPI_FLASH, 0, 0, CABLE_MHZ(15)), + JTAG_BOARD("brs-100-gw1nr9", "gw1nr-lv9qn88pc7/i6", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), SPI_BOARD("titanium_ti60_f225","efinix", "titanium", "efinix_spi_ft4232", DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), JTAG_BOARD("titanium_ti60_f225_jtag", "ti60f225","efinix_jtag_ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), diff --git a/src/cable.hpp b/src/cable.hpp index 84c555a..ab28d74 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -30,6 +30,7 @@ enum communication_type { MODE_CH347, /*! CH347 JTAG mode */ MODE_GWU2X, /*! Gowin GWU2X JTAG mode */ MODE_ESP, /*! esp32c3, esp32s3 */ + MODE_XPCU, /*! Xilinx Platform Cable USB (XPCU) */ }; /*! @@ -99,6 +100,7 @@ static std::map cable_list = { {"dfu", CABLE_DEF(MODE_DFU, 0, 0 )}, {"digilent", FTDI_SER(0x0403, 0x6010, FTDI_INTF_A, 0xe8, 0xeb, 0x00, 0x60)}, {"digilent_b", FTDI_SER(0x0403, 0x6010, FTDI_INTF_B, 0xe8, 0xeb, 0x00, 0x60)}, + {"digilent_ft4232", FTDI_SER(0x0403, 0x6011, FTDI_INTF_A, 0x08, 0x2B, 0x08, 0x0B)}, {"digilent_hs2", FTDI_SER(0x0403, 0x6014, FTDI_INTF_A, 0xe8, 0xeb, 0x00, 0x60)}, {"digilent_hs3", FTDI_SER(0x0403, 0x6014, FTDI_INTF_A, 0x88, 0x8B, 0x20, 0x30)}, {"digilent_ad", FTDI_SER(0x0403, 0x6014, FTDI_INTF_A, 0x08, 0x0B, 0x80, 0x80)}, @@ -136,6 +138,7 @@ static std::map cable_list = { {"usb-blasterII", CABLE_DEF(MODE_USBBLASTER, 0x09Fb, 0x6810 )}, {"usb-blasterII_1", CABLE_DEF(MODE_USBBLASTER, 0x09Fb, 0x6010 )}, {"usb-blasterIII", FTDI_SER(0x09fb, 0x6022, FTDI_INTF_A, 0x08, 0x3B, 0x58, 0xb0)}, + {"xilinxPlatformCableUsb", CABLE_DEF(MODE_XPCU, 0x03fd, 0x0013)}, {"xvc-client", CABLE_DEF(MODE_XVC_CLIENT, 0x0000, 0x0000 )}, #ifdef ENABLE_LIBGPIOD {"libgpiod", CABLE_DEF(MODE_LIBGPIOD_BITBANG, 0, 0x0000 )}, diff --git a/src/efinix.cpp b/src/efinix.cpp index 899f099..6884a67 100644 --- a/src/efinix.cpp +++ b/src/efinix.cpp @@ -218,10 +218,13 @@ void Efinix::program(unsigned int offset, bool unprotect_flash) throw std::runtime_error("device mismatch: " + device + " != " + target); } } - std::string mode = bit->getHeaderVal("mode"); - if (mode.find("passive") != std::string::npos) { - delete bit; - throw std::runtime_error("passive mode not supported for flash"); + std::map hdr = bit->getHeader(); + if(hdr.find("mode") != hdr.end()) { + std::string mode = bit->getHeaderVal("mode"); + if (mode.find("passive") != std::string::npos) { + delete bit; + throw std::runtime_error("passive mode not supported for flash"); + } } } catch (std::runtime_error& e) { throw; diff --git a/src/esp_usb_jtag.cpp b/src/esp_usb_jtag.cpp index 1649586..4d4bfe8 100644 --- a/src/esp_usb_jtag.cpp +++ b/src/esp_usb_jtag.cpp @@ -245,13 +245,17 @@ static uint16_t esp_usb_target_chip_id = 0; /* not applicable for FPGA, they hav /* end copy from openocd */ -esp_usb_jtag::esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid = ESPUSBJTAG_VID, int pid = ESPUSBJTAG_PID): +esp_usb_jtag::esp_usb_jtag(uint32_t clkHZ, int8_t verbose, + int vid = ESPUSBJTAG_VID, int pid = ESPUSBJTAG_PID, + uint8_t bus_addr = 0, uint8_t dev_addr = 0, + const std::string &serial = ""): _verbose(verbose > 1), dev_handle(NULL), usb_ctx(NULL), _tdi(0), _tms(0), /* Default for emard firmware. */ _esp_usb_jtag_caps(0x2000), _write_ep(0x02), _vid(ESPUSBJTAG_VID), _pid(ESPUSBJTAG_PID) { + libusb_device **devs = NULL; int ret; char mess[256]; @@ -260,18 +264,61 @@ esp_usb_jtag::esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid = ESPUSBJTAG_ throw std::exception(); } - dev_handle = libusb_open_device_with_vid_pid(usb_ctx, - _vid, _pid); - if (!dev_handle) { - _esp_usb_jtag_caps = 0x030A; - _write_ep = 0x03; - _pid = 0x1002; - dev_handle = libusb_open_device_with_vid_pid(usb_ctx, - _vid, _pid); + ssize_t cnt = libusb_get_device_list(usb_ctx, &devs); + if (cnt < 0) { + std::cerr << "esp_usb_jtag: libusb_get_device_list failed: " + << libusb_error_name(static_cast(cnt)) << std::endl; + libusb_exit(usb_ctx); + throw std::exception(); } + + for (ssize_t i = 0; i < cnt; i++) { + libusb_device *dev = devs[i]; + struct libusb_device_descriptor desc; + if (libusb_get_device_descriptor(dev, &desc) < 0) + continue; + if (desc.idVendor != vid || desc.idProduct != pid) + continue; + /* bus/device filter (only when both are user-supplied) */ + if (bus_addr != 0 && dev_addr != 0 && + (libusb_get_bus_number(dev) != bus_addr || + libusb_get_device_address(dev) != dev_addr)) + continue; + + /* serial filter */ + if (!serial.empty()) { + libusb_device_handle *probe = NULL; + if (libusb_open(dev, &probe) < 0) + continue; + unsigned char raw[256] = {0}; + int n = 0; + if (desc.iSerialNumber) + n = libusb_get_string_descriptor_ascii(probe, + desc.iSerialNumber, raw, sizeof(raw)); + libusb_close(probe); + if (n <= 0) + continue; + const std::string found(reinterpret_cast(raw), n); + + if (found.find(serial) == std::string::npos) + continue; + } + + if (libusb_open(dev, &dev_handle) == 0) + break; + dev_handle = NULL; + } + libusb_free_device_list(devs, 1); + if (!dev_handle) { - snprintf(mess, 256, "fails to open esp_usb_jtag device"); - printError(mess); + std::cerr << "fails to open esp_usb_jtag device vid:pid 0x" + << std::hex << vid << ":0x" << pid; + if (bus_addr || dev_addr) + std::cerr << " bus:dev " << std::dec << static_cast(bus_addr) + << ":" << static_cast(dev_addr); + if (!serial.empty()) + std::cerr << " serial '" << serial << "'"; + std::cerr << std::endl; libusb_exit(usb_ctx); throw std::exception(); } diff --git a/src/esp_usb_jtag.hpp b/src/esp_usb_jtag.hpp index da0fb8f..65357b2 100644 --- a/src/esp_usb_jtag.hpp +++ b/src/esp_usb_jtag.hpp @@ -12,6 +12,8 @@ #include +#include + #include "jtagInterface.hpp" /*! @@ -23,7 +25,9 @@ class esp_usb_jtag : public JtagInterface { public: - esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid, int pid); + esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid, int pid, + uint8_t bus_addr, uint8_t dev_addr, + const std::string &serial); virtual ~esp_usb_jtag(); int setClkFreq(uint32_t clkHZ) override; diff --git a/src/fx2_ll.cpp b/src/fx2_ll.cpp index f937040..afd911e 100644 --- a/src/fx2_ll.cpp +++ b/src/fx2_ll.cpp @@ -14,7 +14,7 @@ #include "display.hpp" #include "fx2_ll.hpp" #include "ihexParser.hpp" - +#include "progressBar.hpp" #define FX2_FIRM_LOAD 0xA0 #define FX2_GCR_CPUCS 0xe600 @@ -42,27 +42,36 @@ FX2_ll::FX2_ll(uint16_t uninit_vid, uint16_t uninit_pid, throw std::runtime_error("claim interface failed"); } /* load firmware */ - load_firmware(firmware_path); + if (!load_firmware(firmware_path)) { + libusb_close(dev_handle); + libusb_exit(usb_ctx); + throw std::runtime_error("fx2 firmware load failed"); + } close(); reenum = true; } } /* try to open an already init device - * since fx2 may be not immediately ready + * since fx2 may be not immediatly ready * retry with a delay */ + ProgressBar progress("Waiting reload", 100, 50, false); int timeout = 100; do { dev_handle = libusb_open_device_with_vid_pid(usb_ctx, vid, pid); timeout--; + progress.display(100-timeout); if (!dev_handle) sleep(1); } while (!dev_handle && timeout > 0 && reenum); - if (!dev_handle) + if (!dev_handle) { + progress.fail(); throw std::runtime_error("FX2: fail to open device"); + } + progress.done(); ret = libusb_claim_interface(dev_handle, 0); if (ret) { @@ -72,6 +81,18 @@ FX2_ll::FX2_ll(uint16_t uninit_vid, uint16_t uninit_pid, } } +bool FX2_ll::set_interface_alt_setting(const int if_num, const int alt_setting) +{ + const int ret = libusb_set_interface_alt_setting(dev_handle, if_num, + alt_setting); + if (ret && ret != LIBUSB_ERROR_NOT_FOUND) { + printWarn("unable to select alternate interface 1: " + + std::string(libusb_error_name(ret))); + return false; + } + return true; +} + /* destructor: close current device and * destroy context */ @@ -134,11 +155,11 @@ int FX2_ll::read(uint8_t endpoint, uint8_t *buff, uint16_t len) /* write len data using control */ int FX2_ll::write_ctrl(uint8_t bRequest, uint16_t wValue, - uint8_t *buff, uint16_t len) + uint8_t *buff, uint16_t len, uint16_t wIndex) { int ret = libusb_control_transfer(dev_handle, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, - bRequest, wValue, 0x0000, buff, len, 100); + bRequest, wValue, wIndex, buff, len, 100); if (ret < 0) { printError("Unable to send control request: " + std::string(libusb_error_name(ret))); @@ -150,11 +171,11 @@ int FX2_ll::write_ctrl(uint8_t bRequest, uint16_t wValue, /* read len data using control */ int FX2_ll::read_ctrl(uint8_t bRequest, uint16_t wValue, - uint8_t *buff, uint16_t len) + uint8_t *buff, uint16_t len, uint16_t wIndex) { int ret = libusb_control_transfer(dev_handle, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, - bRequest, wValue, 0x0000, buff, len, 100); + bRequest, wValue, wIndex, buff, len, 100); if (ret < 0) { printError("Unable to read control request: " + std::string(libusb_error_name(ret))); @@ -177,6 +198,7 @@ bool FX2_ll::load_firmware(std::string firmware_path) return false; /* load */ std::vector data = ihex.getDataArray(); + ProgressBar progress("Loading firmware", data.size(), 50, false); for (size_t i = 0; i < data.size(); i++) { IhexParser::data_line_t data_line = data[i]; @@ -186,14 +208,16 @@ bool FX2_ll::load_firmware(std::string firmware_path) while (toSend > 0) { uint16_t xfer_len = (toSend > 64) ? 64: toSend; if (!write_ctrl(FX2_FIRM_LOAD, addr, tx_buff, xfer_len)) { - printError("load firmware failed\n"); + progress.fail(); return false; } toSend -= xfer_len; tx_buff += xfer_len; addr += xfer_len; } + progress.display(i); } + progress.done(); /* unset reset */ if (!reset(0)) diff --git a/src/fx2_ll.hpp b/src/fx2_ll.hpp index 37eb1af..8040530 100644 --- a/src/fx2_ll.hpp +++ b/src/fx2_ll.hpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 /* - * Copyright (c) 2021 Gwenhael Goavec-Merou + * Copyright (c) 2021-2026 Gwenhael Goavec-Merou */ #ifndef SRC_FX2_LL_HPP_ @@ -31,6 +31,14 @@ class FX2_ll { uint16_t vid, uint16_t pid, const std::string &firmware_path); ~FX2_ll(); + /*! + * \brief libusb_set_interface_alt_setting wrapper + * \param[in] if_num: Interface number + * \param[in] alt_setting: alternate setting + * \return true on success + */ + bool set_interface_alt_setting(const int if_num, const int alt_setting); + /*! * \brief bulk write * \param[in] endpoint: endpoint to use @@ -53,20 +61,22 @@ class FX2_ll { * \param[in] wValue * \param[in] buff: buffer to write * \param[in] len: buffer length + * \param[in] wIndex: the index field for the setup packet * \return false when transfer fails, true otherwise */ int write_ctrl(uint8_t bRequest, uint16_t wValue, - uint8_t *buff, uint16_t len); + uint8_t *buff, uint16_t len, uint16_t wIndex=0x0000); /*! * \brief control write * \param[in] bRequest * \param[in] wValue * \param[in] buff: buffer to write * \param[in] len: buffer length + * \param[in] wIndex: the index field for the setup packet * \return false when transfer fails, true otherwise */ int read_ctrl(uint8_t bRequest, uint16_t wValue, - uint8_t *buff, uint16_t len); + uint8_t *buff, uint16_t len, uint16_t wIndex=0x0000); private: /*! diff --git a/src/gowin.cpp b/src/gowin.cpp index 0090af6..4c25190 100644 --- a/src/gowin.cpp +++ b/src/gowin.cpp @@ -436,7 +436,7 @@ void Gowin::checkCRC() * is used, try to compare with this value */ try { - std::string hdr = _fs->getHeaderVal("checkSum"); + std::string hdr = _fs->getHeaderVal("CheckSum"); if (!hdr.empty()) { if (ucode == strtol(hdr.c_str(), NULL, 16)) goto success; diff --git a/src/jtag.cpp b/src/jtag.cpp index b5ed9a5..2b99023 100644 --- a/src/jtag.cpp +++ b/src/jtag.cpp @@ -58,6 +58,9 @@ #ifdef ENABLE_XVC_CLIENT #include "xvc_client.hpp" #endif +#ifdef ENABLE_XILINX_PLATFORM_CABLE_USB +#include "xilinxPlatformCableUSB.hpp" +#endif #define DEBUG 0 @@ -162,7 +165,8 @@ Jtag::Jtag(const cable_t &cable, const jtag_pins_conf_t *pin_conf, break; case MODE_ESP: #ifdef ENABLE_ESP_USB - _jtag = new esp_usb_jtag(clkHZ, verbose, 0x303a, 0x1001); + _jtag = new esp_usb_jtag(clkHZ, verbose, 0x303a, 0x1001, + cable.bus_addr, cable.device_addr, serial); #else std::cerr << "Jtag: support for esp32s3 cable was not enabled at compile time" << std::endl; throw std::exception(); @@ -206,6 +210,15 @@ Jtag::Jtag(const cable_t &cable, const jtag_pins_conf_t *pin_conf, case MODE_REMOTEBITBANG: _jtag = new RemoteBitbang_client(ip_adr, port, verbose); break; +#endif + case MODE_XPCU: +#ifdef ENABLE_XILINX_PLATFORM_CABLE_USB + _jtag = new XilinxPlatformCableUSB(0x03fd, 0x0013, clkHZ, + firmware_path, verbose); + break; +#else + printError("Jtag: support for Xilinx Platform Cable USB (XPCU) was not enabled at compile time"); + throw std::exception(); #endif default: std::cerr << "Jtag: unknown cable type" << std::endl; diff --git a/src/main.cpp b/src/main.cpp index f6bdcb7..3998aee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -79,7 +79,7 @@ struct arguments { std::string secondary_bit_file; std::string device; std::string cable; - std::string ftdi_serial; + std::string usb_serial_num; int ftdi_channel; int status_pin; uint32_t freq; @@ -264,13 +264,6 @@ int main(int argc, char **argv) cable.config.interface = mapping[args.ftdi_channel]; } - if (!args.ftdi_serial.empty()) { - if (cable.type != MODE_FTDI_SERIAL && cable.type != MODE_FTDI_BITBANG){ - printError("Error: FTDI serial param is for FTDI cables."); - return EXIT_FAILURE; - } - } - if (args.status_pin != -1) { if (cable.type != MODE_FTDI_SERIAL){ printError("Error: FTDI status pin is for FTDI MPSSE cables."); @@ -279,6 +272,14 @@ int main(int argc, char **argv) } #endif + if (!args.usb_serial_num.empty()) { + if (cable.type != MODE_FTDI_SERIAL && cable.type != MODE_FTDI_BITBANG && + cable.type != MODE_ESP){ + printError("Error: usb-serial-num param is for FTDI and esp32s3 cables."); + return EXIT_FAILURE; + } + } + if (args.vid != 0) { printInfo("Cable VID overridden"); cable.vid = args.vid; @@ -389,7 +390,7 @@ int main(int argc, char **argv) Jtag *jtag; try { - jtag = new Jtag(cable, &pins_config, args.device, args.ftdi_serial, + jtag = new Jtag(cable, &pins_config, args.device, args.usb_serial_num, args.freq, args.verbose, args.ip_adr, args.port, args.invert_read_edge, args.probe_firmware, args.user_misc_devs); @@ -667,7 +668,7 @@ int run_xvc_server(const struct arguments &args, const cable_t &cable, try { XVC_server *xvc = NULL; xvc = new XVC_server(args.port, cable, pins_config, args.device, - args.ftdi_serial, args.freq, args.verbose, args.ip_adr, + args.usb_serial_num, args.freq, args.verbose, args.ip_adr, args.invert_read_edge, args.probe_firmware); /* create connection */ xvc->open_connection(); @@ -889,6 +890,7 @@ int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *pins_config) { std::string freqo; + std::string ftdi_serial; std::vector pins, bus_dev_num; bool verbose = false, quiet = false; int8_t verbose_level = -2; @@ -927,8 +929,10 @@ int parse_opt(int argc, char **argv, struct arguments *args, ("busdev-num", "select a probe by it bus and device number (bus_num:device_addr)", cxxopts::value>(bus_dev_num)) - ("ftdi-serial", "FTDI chip serial number", - cxxopts::value(args->ftdi_serial)) + ("usb-serial-num", "USB iSerial (FTDI chip serial number or ESP32 iSerialNumber substring)", + cxxopts::value(args->usb_serial_num)) + ("ftdi-serial", "FTDI chip serial number (Deprecated)", + cxxopts::value(ftdi_serial)) ("ftdi-channel", "FTDI chip channel number (channels 0-3 map to A-D)", cxxopts::value(args->ftdi_channel)) @@ -982,7 +986,7 @@ int parse_opt(int argc, char **argv, struct arguments *args, cxxopts::value(args->offset)) ("pins", "pin config TDI:TDO:TCK:TMS or MOSI:MISO:SCK:CS[:HOLDN:WPN]", cxxopts::value>(pins)) - ("probe-firmware", "firmware for JTAG probe (usbBlasterII)", + ("probe-firmware", "firmware for JTAG probe (usbBlasterII/xilinxPlatformCableUsb)", cxxopts::value(args->probe_firmware)) ("protect-flash", "protect SPI flash area", cxxopts::value(args->protect_flash)) @@ -1027,7 +1031,8 @@ int parse_opt(int argc, char **argv, struct arguments *args, cxxopts::value(args->read_register)) ("user-flash", "User flash file (Gowin LittleBee FPGA only)", cxxopts::value(args->user_flash)) - ("V,Version", "Print program version"); + ("V,version", "Print program version") + ("Version", "Print program version (Deprecated)"); options.parse_positional({"bitstream"}); auto result = options.parse(argc, argv); @@ -1055,7 +1060,7 @@ int parse_opt(int argc, char **argv, struct arguments *args, args->verbose = verbose_level; } - if (result.count("Version")) { + if (result.count("Version") || result.count("version")) { std::cout << "openFPGALoader " << VERSION << std::endl; return 1; } @@ -1133,6 +1138,14 @@ int parse_opt(int argc, char **argv, struct arguments *args, } } + if (result.count("ftdi-serial")) { + if (result.count("usb-serial-num")) { + printError("Error: ftdi-serial and usb-serial-num can't be used at the same time."); + return -1; + } + args->usb_serial_num = ftdi_serial; + } + if (result.count("busdev-num")) { if (bus_dev_num.size() != 2) { printError("Error: busdev-num must be xx:yy"); diff --git a/src/part.hpp b/src/part.hpp index 5e6e8dd..1fee941 100644 --- a/src/part.hpp +++ b/src/part.hpp @@ -307,6 +307,7 @@ static std::map fpga_list = { {0x02e050dd, {"altera", "arria 10", "SX660", 10}}, /* Altera Agilex 3 */ + {0x4361b0dd, {"altera", "agilex 3", "A3CY100", 10}}, {0x436db0dd, {"altera", "agilex 3", "A3CZ135", 10}}, /* Altera Agilex 5 */ diff --git a/src/remoteBitbang_client.cpp b/src/remoteBitbang_client.cpp index 7c1da31..64aef53 100644 --- a/src/remoteBitbang_client.cpp +++ b/src/remoteBitbang_client.cpp @@ -76,25 +76,27 @@ RemoteBitbang_client::~RemoteBitbang_client() } int RemoteBitbang_client::writeTMS(const uint8_t *tms, uint32_t len, - bool flush_buffer, __attribute__((unused)) const uint8_t tdi) + bool flush_buffer, const uint8_t tdi) { // empty buffer // if asked flush if (len == 0) return ((flush_buffer) ? flush() : 0); + _last_tdi = (tdi) ? TDI_BIT : 0; + uint8_t base_v = '0' + _last_tdi; for (uint32_t pos = 0; pos < len; pos++) { // buffer full -> write - if (_num_bytes == _buffer_size) - ll_write(NULL); + if ((_num_bytes + 2) >= _buffer_size) + flush(); _last_tms = (tms[pos >> 3] & (1 << (pos & 0x07))) ? TMS_BIT : 0; _xfer_buf[_num_bytes++] = base_v + _last_tms; _xfer_buf[_num_bytes++] = base_v + _last_tms + TCK_BIT; } // flush where it's asked or if the buffer is full - if (flush_buffer || _num_bytes == _buffer_size * 8) + if (flush_buffer || _num_bytes == _buffer_size) return flush(); return len; } @@ -107,9 +109,11 @@ int RemoteBitbang_client::writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, uint8_t base_v = '0' + _last_tms; for (uint32_t pos = 0; pos < len; pos++) { - if (_num_bytes == _buffer_size) - ll_write(NULL); // NULL because _num_bytes is always 0 when read - _last_tdi = (tx[pos >> 3] & (1 << (pos & 0x07))) ? TDI_BIT : 0; + const uint32_t pos_byte = (pos >> 3); + const uint32_t pos_bit = (1 << (pos & 0x07)); + if ((_num_bytes + 2) >= _buffer_size) + flush(); + _last_tdi = (tx[pos_byte] & pos_bit) ? TDI_BIT : 0; if (end && pos == len - 1) { _last_tms = TMS_BIT; base_v = '0' + _last_tms; @@ -120,9 +124,9 @@ int RemoteBitbang_client::writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, uint8_t tdo; ll_write(&tdo); if (tdo == '1') - rx[pos >> 3] |= (1 << (pos & 0x07)); + rx[pos_byte] |= pos_bit; else - rx[pos >> 3] &= ~(1 << (pos & 0x07)); + rx[pos_byte] &= ~pos_bit; } } @@ -130,30 +134,23 @@ int RemoteBitbang_client::writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, } // toggle clk with constant TDI and TMS. -int RemoteBitbang_client::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) +int RemoteBitbang_client::toggleClk([[maybe_unused]] uint8_t tms, + [[maybe_unused]] uint8_t tdi, uint32_t clk_len) { // nothing to do if (clk_len == 0) return 0; - if (_num_bytes != 0) - flush(); - _last_tms = tms; - _last_tdi = tdi; - uint8_t val = (_last_tms | _last_tdi); - - // flush buffer before starting - if (_num_bytes != 0) - flush(); + const uint8_t val = (_last_tms | _last_tdi); for (uint32_t len = 0; len < clk_len; len++) { - if (len == _num_bytes) - ll_write(NULL); + if ((_num_bytes + 2) >= _buffer_size) + flush(); _xfer_buf[_num_bytes++] = '0' + val; _xfer_buf[_num_bytes++] = '0' + (val | TCK_BIT); } - ll_write(NULL); + flush(); return clk_len; } diff --git a/src/usbBlaster.cpp b/src/usbBlaster.cpp index 245ac53..97eb284 100644 --- a/src/usbBlaster.cpp +++ b/src/usbBlaster.cpp @@ -49,8 +49,8 @@ UsbBlaster::UsbBlaster(const cable_t &cable, const std::string &firmware_path, #else throw std::runtime_error("usb-blasterI: Not build"); #endif -#ifdef ENABLE_USB_BLASTERII else if (cable.pid == 0x6810 || cable.pid == 0x6010) +#ifdef ENABLE_USB_BLASTERII ll_driver = new UsbBlasterII(firmware_path); #else throw std::runtime_error("usb-blasterII: Not build"); @@ -459,7 +459,7 @@ UsbBlasterII::UsbBlasterII(const std::string &firmware_path) else fpath = firmware_path; fx2 = new FX2_ll(0x09fb, 0x6810, 0x09fb, 0x6010, fpath); - if (!fx2->read_ctrl(0x94, 0, buf, 5)) { + if (!fx2->read_ctrl(0x94, 0, buf, 5, 0x0000)) { throw std::runtime_error("Unable to read firmware version."); } printInfo("USB-Blaster II firmware version: " + std::string((char *)buf)); diff --git a/src/xilinx.cpp b/src/xilinx.cpp index 78573bb..efeec19 100644 --- a/src/xilinx.cpp +++ b/src/xilinx.cpp @@ -659,7 +659,6 @@ void Xilinx::program(unsigned int offset, bool unprotect_flash) /* Check for BPI flash boards */ if (_is_bpi_board) { program_bpi(bit, offset); - reset(); } else { if (_flash_chips & PRIMARY_FLASH) { select_flash_chip(PRIMARY_FLASH); @@ -669,8 +668,6 @@ void Xilinx::program(unsigned int offset, bool unprotect_flash) select_flash_chip(SECONDARY_FLASH); program_spi(secondary_bit, _secondary_file_extension, offset, unprotect_flash); } - - reset(); } } else { if (_fpga_family == SPARTAN3_FAMILY) @@ -810,6 +807,9 @@ void Xilinx::program_bpi(ConfigBitstreamParser *bit, unsigned int offset) throw std::runtime_error("BPI flash programming failed"); } + /* Reset the board if skip_reset is not set */ + post_flash_access(); + printInfo("BPI flash programming complete"); } @@ -1277,6 +1277,18 @@ bool Xilinx::dumpFlash(uint32_t base_addr, uint32_t len) bool Xilinx::detect_flash() { + if (_is_bpi_board) { + if (!_bpi_flash) { + if (!load_bpi_bridge()) + return false; + } + if (!_bpi_flash->detect()) { + printError("BPI flash detection failed"); + return false; + } + return post_flash_access(); + } + if (_flash_chips & PRIMARY_FLASH) { select_flash_chip(PRIMARY_FLASH); if (!FlashInterface::detect_flash()) diff --git a/src/xilinxPlatformCableUSB.cpp b/src/xilinxPlatformCableUSB.cpp new file mode 100644 index 0000000..4245e90 --- /dev/null +++ b/src/xilinxPlatformCableUSB.cpp @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2022-2026 Gwenhael Goavec-Merou + */ + +#include +#include +#include + +#include +#include +#include + +#include "display.hpp" +#include "fx2_ll.hpp" +#include "xilinxPlatformCableUSB.hpp" + +#define XPCU_BREQUEST 0xB0 +#define XPCU_INITIALIZED_VID 0x03fd +#define XPCU_INITIALIZED_PID 0x0008 +#define XPCU_CMD_DISABLE 0x10 +#define XPCU_CMD_ENABLE 0x18 +#define XPCU_CMD_SET_SPEED 0x28 +#define XPCU_CMD_STATUS 0x38 +#define XPCU_CMD_RETURN_CONSTANT 0x40 +#define XPCU_CMD_GET_VERSION 0x50 +#define XPCU_CMD_GPIO_TRANSFER 0xA6 +#define XPCU_EP_JTAG_OUT 0x02 +#define XPCU_EP_JTAG_IN 0x86 +#define XPCU_STATUS_CONNECTED 0x40 +#define XPCU_SPEED_CLASS_ENABLE 0x10 +#define XPCU_VERSION_CPLD 0x01 +#define XPCU_VERSION_CONST1 0x02 +#define XPCU_VERSION_CONST2 0x03 + +#define TCK_OFFSET 0 +#define TDO_OFFSET 4 +#define TDI_OFFSET 0 +#define TMS_OFFSET 4 + +#define TCK_IDX (1 << TCK_OFFSET) +#define TDO_IDX (1 << TDO_OFFSET) +#define TDI_IDX (1 << TDI_OFFSET) +#define TMS_IDX (1 << TMS_OFFSET) + +XilinxPlatformCableUSB::XilinxPlatformCableUSB(const uint16_t vid, + const uint16_t pid, + uint32_t clkHz, + const std::string &firmware_path, + int8_t verbose): _verbose(verbose), _nb_bit(0), _nb_tdo_bit(0), + _curr_tms(0), _curr_tdi(0), _buffer_size(4096), + _buffer_bit_size((_buffer_size / 2 * 4) - 1) +{ + std::string firmware_file; + /* firmare path must be known: + * 1/ provided by user + * 2/ from Vivado install directory + * 3/ from ISE install directory + */ + if (firmware_path.empty() && strlen(ISE_DIR) == 0 && strlen(VIVADO_DIR) == 0) { + printError("missing FX2 firmware"); + printError("use --probe-firmware with something"); + printError("like /opt/Xilinx/14.7/ISE_DS/ISE/bin/lin64/xusb_xp2.hex for ISE"); + printError("or /opt/Xilinx/Vivado/VERSION/data/xicom/xusb_xp2.hex for Vivado"); + printError("Or use -DISE_DIR=/opt/Xilinx/14.7 / -DVIVADO_DIR=/opt/Xilinx/Vivado/VERSION at build time"); + throw std::runtime_error("xilinxPlatformCableUSB: missing firmware"); + } + + /* Extract firmware according to possibilities */ + if (!firmware_path.empty()) + firmware_file = firmware_path; + else if (strlen(VIVADO_DIR) > 0) + firmware_file = VIVADO_DIR "/data/xicom/xusb_xp2.hex"; + else if (strlen(ISE_DIR) > 0) + firmware_file = ISE_DIR "/ISE_DS/ISE/bin/lin64/xusb_xp2.hex"; + + try { + fx2 = std::make_unique(vid, pid, XPCU_INITIALIZED_VID, + XPCU_INITIALIZED_PID, firmware_file); + } catch (std::exception &e) { + printError(e.what()); + throw std::runtime_error("lowlevel init failed"); + } + + fx2->set_interface_alt_setting(0, 1); + + displayCableVersion(); + + /* Write GPIO bit */ + fx2->write_ctrl(XPCU_BREQUEST, 0x030, nullptr, 0, (1 << 3)); + + if (!enableDevice(true)) + throw std::runtime_error("Unable to enable device"); + + uint8_t buf[1]; + if (!fx2->read_ctrl(XPCU_BREQUEST, XPCU_CMD_STATUS, buf, 1)) + throw std::runtime_error("Unable to read status."); + + char mess[64]; + snprintf(mess, sizeof(mess), "status %02x connected: %s", + buf[0], (buf[0] & XPCU_STATUS_CONNECTED) ? "yes" : "no"); + printInfo(mess); + + _in_buf = std::make_unique(_buffer_size); + + setClkFreq(clkHz); +} + +XilinxPlatformCableUSB::~XilinxPlatformCableUSB() +{ + flush(); + enableDevice(false); +} + +int XilinxPlatformCableUSB::setClkFreq(uint32_t clkHz) +{ + /* speed table: index Hz; bit 4 must always be set in the speed class */ + static constexpr uint32_t speeds[] = {12000000, 6000000, 3000000, 1500000, 750000}; + uint8_t speed = 4; /* default: slowest */ + for (uint8_t i = 0; i < sizeof(speeds) / sizeof(speeds[0]); i++) { + if (speeds[i] <= clkHz) { + speed = i; + break; + } + } + if (!fx2->write_ctrl(XPCU_BREQUEST, XPCU_CMD_SET_SPEED, nullptr, 0, + speed | XPCU_SPEED_CLASS_ENABLE)) { + printError("setClkFreq: failed to set speed"); + return -1; + } + _clkHZ = speeds[speed]; + printInfo("Jtag frequency : requested " + std::to_string(clkHz) + + " Hz -> real " + std::to_string(_clkHZ) + " Hz"); + return _clkHZ; +} + +int XilinxPlatformCableUSB::writeTMS(const uint8_t *tms, uint32_t len, + bool flush_buffer, const uint8_t tdi) +{ + int ret; + + if (len == 0) + return flush_buffer ? flush() : 0; + + _curr_tdi = tdi ? 1 : 0; + + for (uint32_t i = 0; i < len; i++) { + _curr_tms = (tms[i >> 3] >> (i & 0x07)) & 0x01; + if (storeBit(_curr_tdi, _curr_tms, 1, 0)) { + if (write(nullptr, 0) < 0) + return -1; + } + } + + if (flush_buffer) { + ret = flush(); + if (ret < 0) + return ret; + } + + return len; +} + +int XilinxPlatformCableUSB::writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, bool end) +{ + if (len == 0) + return 0; + + if (rx && _nb_bit != 0) { + if (write(nullptr, 0) < 0) + return -1; + } + + uint32_t rx_offset = 0; + + for (uint32_t i = 0; i < len; i++) { + bool last_bit = (i == len - 1 && end); + _curr_tdi = tx ? (0x01 & ((tx[i >> 3]) >> (i & 0x07))) : 0; + + if (last_bit) + _curr_tms = 1; + + if (storeBit(_curr_tdi, _curr_tms, 1, rx ? 1 : 0)) { + uint32_t bits = _nb_tdo_bit; + if (write(rx, rx_offset) < 0) + return -1; + rx_offset += bits; + } + } + + if (_nb_bit != 0 && (end || rx)) { + if (write(rx, rx_offset) < 0) + return -1; + } + + return len; +} + +int XilinxPlatformCableUSB::toggleClk([[maybe_unused]] uint8_t tms, + [[maybe_unused]] uint8_t tdi, uint32_t clk_len) +{ + for (uint32_t i = 0; i < clk_len; i++) { + if (storeBit(_curr_tdi, _curr_tms, 1, 0)) { + if (write(nullptr, 0) < 0) + return -1; + } + } + + /* Flush buffer if not empty */ + if (_nb_bit != 0) { + if (flush() < 0) + return -1; + } + + return clk_len; +} + +int XilinxPlatformCableUSB::flush() +{ + return write(nullptr, 0); +} + +/* TMS 1st nibble, TDI 2nd nibble, TDO 3rd nibble, TCK 4th nibble + * byte n byte n+1 + * [7:4 3:0] [7:4 3:0] + * TMS TDI TDO TCK + */ +bool XilinxPlatformCableUSB::storeBit(uint8_t tdi, uint8_t tms, + uint8_t tck, uint8_t tdo) noexcept +{ + const uint32_t buf_pos = (_nb_bit >> 2) << 1; + const uint8_t bit_pos = _nb_bit & 0x03; + + if (bit_pos == 0) + _in_buf[buf_pos] = _in_buf[buf_pos + 1] = 0; + + if (tms) + _in_buf[buf_pos] |= (TMS_IDX << bit_pos); + if (tdi) + _in_buf[buf_pos] |= (TDI_IDX << bit_pos); + if (tdo) + _in_buf[buf_pos + 1] |= (TDO_IDX << bit_pos); + if (tck) + _in_buf[buf_pos + 1] |= (TCK_IDX << bit_pos); + + _nb_bit++; + if (tdo) + _nb_tdo_bit++; + + return _nb_bit >= _buffer_bit_size; +} + +/* Compute how many bytes EP6 will return for nb_bit TDO bits. + * The device uses a shift-register encoding: a 16-bit register that grows + * to 32-bit after 16 bits, then a new register starts every 32 bits. + */ +uint32_t XilinxPlatformCableUSB::rxBufSize(uint32_t nb_bit) noexcept +{ + const uint32_t full_groups = nb_bit / 32; + const uint32_t rem = nb_bit & 31u; + return full_groups * 4 + (rem == 0 ? 0 : (rem > 16 ? 4 : 2)); +} + +int XilinxPlatformCableUSB::write(uint8_t *rx, uint32_t rx_offset) +{ + if (_nb_bit == 0) + return 0; + + /* N ops: N/4 pairs of 2 bytes each (round up to complete pair) */ + uint32_t xfer_tx = (_nb_bit >> 1) & ~0x01u; + xfer_tx += ((_nb_bit & 0x03) != 0) ? 2 : 0; + + /* count is 0-indexed per protocol spec */ + if (!fx2->write_ctrl(XPCU_BREQUEST, XPCU_CMD_GPIO_TRANSFER, nullptr, 0, + _nb_bit - 1)) { + printError("Fails to write GPIO transfer control message"); + return -1; + } + + if (fx2->write(XPCU_EP_JTAG_OUT, _in_buf.get(), xfer_tx) != (int)xfer_tx) + return -1; + + if (rx) { + if (_nb_tdo_bit != _nb_bit) { + printError("Unable to decode mixed TDO/non-TDO transfer"); + return -1; + } + + uint32_t xfer_rx = rxBufSize(_nb_tdo_bit); + std::vector rx_buf(xfer_rx); + if (fx2->read(XPCU_EP_JTAG_IN, rx_buf.data(), xfer_rx) != (int)xfer_rx) + return -1; + + /* Decode shift-register encoded TDO bits into rx. + * Each group of up to 32 bits occupies a 16 or 32-bit little-endian + * shift register: bit_k is at position (reg_size - group + k). + */ + uint32_t buf_off = 0; + uint32_t remaining = _nb_tdo_bit; + uint32_t bit_idx = 0; + while (remaining > 0) { + const uint32_t group = (remaining > 32) ? 32 : remaining; + const uint32_t reg_size = (group > 16) ? 32 : 16; + const uint32_t shift = reg_size - group; + uint32_t reg = 0; + for (uint32_t b = 0; b < reg_size / 8; b++) + reg |= (uint32_t)rx_buf[buf_off + b] << (b * 8); + const uint32_t base = rx_offset + bit_idx; + if ((base & 7u) == 0 && (group & 7u) == 0) { + uint8_t *out = rx + (base >> 3); + for (uint32_t b = 0; b < group / 8; b++) + out[b] = static_cast((reg >> (shift + b * 8)) & 0xFF); + } else { + for (uint32_t k = 0; k < group; k++) { + const uint32_t out_bit = base + k; + if ((reg >> (shift + k)) & 1) + rx[out_bit >> 3] |= (1 << (out_bit & 7)); + else + rx[out_bit >> 3] &= ~(1 << (out_bit & 7)); + } + } + buf_off += reg_size / 8; + bit_idx += group; + remaining -= group; + } + } + + _nb_bit = 0; + _nb_tdo_bit = 0; + return 0; +} + +bool XilinxPlatformCableUSB::enableDevice(bool enable) +{ + if (!fx2->write_ctrl(XPCU_BREQUEST, + (enable ? XPCU_CMD_ENABLE : XPCU_CMD_DISABLE), nullptr, 0)) { + char mess[64]; + snprintf(mess, sizeof(mess), "Unable to %s device", + (enable ? "enable" : "disable")); + printError(mess); + return false; + } + return true; +} + +void XilinxPlatformCableUSB::displayCableVersion() +{ + uint8_t buf[2]; + + if (!fx2->read_ctrl(XPCU_BREQUEST, XPCU_CMD_RETURN_CONSTANT, buf, 2)) + throw std::runtime_error("Unable to read constant."); + const uint16_t const0 = ((uint16_t)buf[0] << 8) | buf[1]; + + if (!fx2->read_ctrl(XPCU_BREQUEST, XPCU_CMD_GET_VERSION, buf, 2)) + throw std::runtime_error("Unable to read firmware version."); + const uint16_t fx2_firmware = ((uint16_t)buf[0] << 8) | buf[1]; + + if (!fx2->read_ctrl(XPCU_BREQUEST, XPCU_CMD_GET_VERSION, buf, 2, + XPCU_VERSION_CPLD)) + throw std::runtime_error("Unable to read CPLD version."); + const uint16_t cpld_firmware = ((uint16_t)buf[0] << 8) | buf[1]; + + if (!fx2->read_ctrl(XPCU_BREQUEST, XPCU_CMD_GET_VERSION, buf, 2, + XPCU_VERSION_CONST1)) + throw std::runtime_error("Unable to read const 1."); + const uint16_t const1 = ((uint16_t)buf[0] << 8) | buf[1]; + + if (!fx2->read_ctrl(XPCU_BREQUEST, XPCU_CMD_GET_VERSION, buf, 2, + XPCU_VERSION_CONST2)) + throw std::runtime_error("Unable to read const 2."); + const uint16_t const2 = ((uint16_t)buf[0] << 8) | buf[1]; + + printf("FX2 version: %04x\n", fx2_firmware); + printf("CPLD version: %04x\n", cpld_firmware); + printf("Const0 version: %04x\n", const0); + printf("Const1 version: %04x\n", const1); + printf("Const2 version: %04x\n", const2); +} diff --git a/src/xilinxPlatformCableUSB.hpp b/src/xilinxPlatformCableUSB.hpp new file mode 100644 index 0000000..0ad44a5 --- /dev/null +++ b/src/xilinxPlatformCableUSB.hpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2022-2026 Gwenhael Goavec-Merou + */ + +#ifndef SRC_XILINXPLATFORMCABLEUSB_HPP_ +#define SRC_XILINXPLATFORMCABLEUSB_HPP_ + +#include +#include +#include + +#include "fx2_ll.hpp" +#include "jtagInterface.hpp" + +#define XPCU_DEFAULT_VID 0x03fd +#define XPCU_DEFAULT_PID 0x0013 + +/*! + * \file xilinxPlatformCableUSB.hpp + * \class XilinxPlatformCableUSB + * \brief concrete class between jtag implementation and Xilinx USB Cable probe + * \author Gwenhael Goavec-Merou + */ +class XilinxPlatformCableUSB : public JtagInterface { + public: + XilinxPlatformCableUSB(const uint16_t vid = XPCU_DEFAULT_VID, + const uint16_t pid = XPCU_DEFAULT_PID, + uint32_t clkHz = 750000, + const std::string &firmware_path = "", + int8_t verbose = 0); + ~XilinxPlatformCableUSB() override; + + int setClkFreq(uint32_t clkHz) override; + + /*! + * \brief drive TMS to move in JTAG state machine + * \param tms serie of TMS state + * \param len number of TMS state + * \param flush_buffer force flushing the buffer + * \param tdi TDI constant value + * \return number of state written + */ + int writeTMS(const uint8_t *tms, uint32_t len, bool flush_buffer, + const uint8_t tdi = 1) override; + + /*! + * \brief send TDI bits (mainly in shift DR/IR state) + * \param tx array of TDI values (used to write) + * \param rx array of TDO values (used when read) + * \param len number of bit to send/receive + * \param end in JTAG state machine last bit and tms are set in same time + * \return number of bit written and/or read + */ + int writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, bool end) override; + + /*! + * \brief toggle clock with static tms and tdi + * \param tms state of tms signal + * \param tdi state of tdi signal + * \param clk_len number of clock cycle + * \return number of clock cycle send + */ + int toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) override; + + /*! + * \brief return internal buffer size (in byte). + * \return _buffer_size + */ + int get_buffer_size() override { return static_cast(_buffer_size); } + + bool isFull() override { return _nb_bit >= _buffer_bit_size; } + + int flush() override; + + /*! + * \brief display fx2 & CPLD firmwares version + */ + void displayCableVersion(); + + /*! + * \brief enable device output + * \param[in] enable: enable or disable device + * \return true when transfer success, false otherwise + */ + bool enableDevice(bool enable); + + private: + /* \brief pack one JTAG bit into the internal buffer + * \return true when buffer is full, false otherwise + */ + bool storeBit(uint8_t tdi, uint8_t tms, uint8_t tck, uint8_t tdo) noexcept; + int write(uint8_t *rx, uint32_t rx_offset = 0); + static uint32_t rxBufSize(uint32_t nb_bit) noexcept; + + int8_t _verbose; + + std::unique_ptr _in_buf; + uint32_t _nb_bit; + uint32_t _nb_tdo_bit; + uint8_t _curr_tms; + uint8_t _curr_tdi; + uint32_t _buffer_size; + uint32_t _buffer_bit_size; + std::unique_ptr fx2; +}; + +#endif // SRC_XILINXPLATFORMCABLEUSB_HPP_