From 0536ab47544644e4898ee473c22db245f9c5680e Mon Sep 17 00:00:00 2001 From: Ricardo Barbedo Date: Sat, 21 Jan 2023 14:20:49 +0100 Subject: [PATCH] Add target-flash and secondary-bitstream CLI options for VCU118 --- README.md | 125 +++++++++++++++++----------------- doc/boards.yml | 2 +- src/main.cpp | 41 +++++++++--- src/spiInterface.hpp | 2 + src/xilinx.cpp | 156 ++++++++++++++++++++++++++++++++++++------- src/xilinx.hpp | 33 ++++++--- 6 files changed, 257 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 3875f1e..3562b38 100644 --- a/README.md +++ b/README.md @@ -49,68 +49,73 @@ openFPGALoader -c cmsisdap fpga_bitstream.bit ## Usage ``` -Usage: ./openFPGALoader [OPTION...] BIT_FILE +Usage: openFPGALoader [OPTION...] BIT_FILE openFPGALoader -- a program to flash FPGA - --altsetting arg DFU interface altsetting (only for DFU mode) - --bitstream arg bitstream - -b, --board arg board name, may be used instead of cable - -B, --bridge arg disable spiOverJtag model detection by providing - bitstream(intel/xilinx) - -c, --cable arg jtag interface - --invert-read-edge JTAG mode / FTDI: read on negative edge instead - of positive - --vid arg probe Vendor ID - --pid arg probe Product ID - --cable-index arg probe index (FTDI and cmsisDAP) - --busdev-num arg select a probe by it bus and device number - (bus_num:device_addr) - --ftdi-serial arg FTDI chip serial number - --ftdi-channel arg FTDI chip channel number (channels 0-3 map to - A-D) - -d, --device arg device to use (/dev/ttyUSBx) - --detect detect FPGA - --dfu DFU mode - --dump-flash Dump flash mode - --external-flash select ext flash for device with internal and - external storage - --file-size arg provides size in Byte to dump, must be used with - dump-flash - --file-type arg provides file type instead of let's deduced by - using extension - --flash-sector arg flash sector (Lattice parts only) - --fpga-part arg fpga model flavor + package - --freq arg jtag frequency (Hz) - -f, --write-flash write bitstream in flash (default: false) - --index-chain arg device index in JTAG-chain - --ip arg IP address (only for XVC client) - --list-boards list all supported boards - --list-cables list all supported cables - --list-fpga list all supported FPGA - -m, --write-sram write bitstream in SRAM (default: true) - -o, --offset arg start offset in EEPROM - --pins arg pin config TDI:TDO:TCK:TMS - --probe-firmware arg firmware for JTAG probe (usbBlasterII) - --protect-flash arg protect SPI flash area - --quiet Produce quiet output (no progress bar) - -r, --reset reset FPGA after operations - --scan-usb scan USB to display connected probes - --skip-load-bridge skip writing bridge to SRAM when in write-flash - mode - --skip-reset skip resetting the device when in write-flash - mode - --spi SPI mode (only for FTDI in serial mode) - --unprotect-flash Unprotect flash blocks - -v, --verbose Produce verbose output - --verbose-level arg verbose level -1: quiet, 0: normal, 1:verbose, - 2:debug - -h, --help Give this help list - --verify Verify write operation (SPI Flash only) - --xvc Xilinx Virtual Cable Functions - --port arg Xilinx Virtual Cable Port (default 3721) - --mcufw arg Microcontroller firmware - --conmcu Connect JTAG to MCU - -V, --Version Print program version + --altsetting arg DFU interface altsetting (only for DFU mode) + --bitstream arg bitstream + --secondary-bitstream arg + secondary bitstream (some Xilinx UltraScale + boards) + -b, --board arg board name, may be used instead of cable + -B, --bridge arg disable spiOverJtag model detection by + providing bitstream(intel/xilinx) + -c, --cable arg jtag interface + --invert-read-edge JTAG mode / FTDI: read on negative edge + instead of positive + --vid arg probe Vendor ID + --pid arg probe Product ID + --cable-index arg probe index (FTDI and cmsisDAP) + --busdev-num arg select a probe by it bus and device number + (bus_num:device_addr) + --ftdi-serial arg FTDI chip serial number + --ftdi-channel arg FTDI chip channel number (channels 0-3 map to + A-D) + --detect detect FPGA + --dfu DFU mode + --dump-flash Dump flash mode + --bulk-erase Bulk erase flash + --target-flash arg for boards with multiple flash chips (some + Xilinx UltraScale boards), select the target + flash: primary (default), secondary or both + --external-flash select ext flash for device with internal and + external storage + --file-size arg provides size in Byte to dump, must be used + with dump-flash + --file-type arg provides file type instead of let's deduced + by using extension + --flash-sector arg flash sector (Lattice parts only) + --fpga-part arg fpga model flavor + package + --freq arg jtag frequency (Hz) + -f, --write-flash write bitstream in flash (default: false) + --index-chain arg device index in JTAG-chain + --ip arg IP address (only for XVC client) + --list-boards list all supported boards + --list-cables list all supported cables + --list-fpga list all supported FPGA + -m, --write-sram write bitstream in SRAM (default: true) + -o, --offset arg start offset in EEPROM + --pins arg pin config TDI:TDO:TCK:TMS + --probe-firmware arg firmware for JTAG probe (usbBlasterII) + --protect-flash arg protect SPI flash area + --quiet Produce quiet output (no progress bar) + -r, --reset reset FPGA after operations + --scan-usb scan USB to display connected probes + --skip-load-bridge skip writing bridge to SRAM when in + write-flash mode + --skip-reset skip resetting the device when in write-flash + mode + --spi SPI mode (only for FTDI in serial mode) + --unprotect-flash Unprotect flash blocks + -v, --verbose Produce verbose output + --verbose-level arg verbose level -1: quiet, 0: normal, + 1:verbose, 2:debug + -h, --help Give this help list + --verify Verify write operation (SPI Flash only) + --port arg Xilinx Virtual Cable Port (default 3721) + --mcufw arg Microcontroller firmware + --conmcu Connect JTAG to MCU + -V, --Version Print program version Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options. diff --git a/doc/boards.yml b/doc/boards.yml index 64513f4..1551ec7 100644 --- a/doc/boards.yml +++ b/doc/boards.yml @@ -689,4 +689,4 @@ URL: https://www.xilinx.com/products/boards-and-kits/vcu118.html FPGA: Virtex UltraScale+ xcvu9p-flga2104 Memory: OK - Flash: NA + Flash: OK diff --git a/src/main.cpp b/src/main.cpp index d3db154..6ba74f0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,7 @@ struct arguments { bool reset, detect, verify, scan_usb; unsigned int offset; string bit_file; + string secondary_bit_file; string device; string cable; string ftdi_serial; @@ -68,6 +69,7 @@ struct arguments { string probe_firmware; int index_chain; unsigned int file_size; + string target_flash; bool external_flash; int16_t altsetting; uint16_t vid; @@ -105,12 +107,12 @@ int main(int argc, char **argv) jtag_pins_conf_t pins_config = {0, 0, 0, 0}; /* command line args. */ - struct arguments args = {0, false, false, false, false, 0, "", "", "-", "", -1, + struct arguments args = {0, false, false, false, false, 0, "", "", "", "-", "", -1, 0, false, "-", false, false, false, false, Device::PRG_NONE, false, /* spi dfu file_type fpga_part bridge_path probe_firmware */ false, false, "", "", "", "", - /* index_chain file_size external_flash altsetting */ - -1, 0, false, -1, + /* index_chain file_size target_flash external_flash altsetting */ + -1, 0, "primary", false, -1, /* vid, pid, index bus_addr, device_addr */ 0, 0, -1, 0, 0, "127.0.0.1", 0, false, false, "", false, false, @@ -536,9 +538,9 @@ int main(int argc, char **argv) Device *fpga; try { if (fab == "xilinx") { - fpga = new Xilinx(jtag, args.bit_file, args.file_type, - args.prg_type, args.fpga_part, args.bridge_path, args.verify, - args.verbose); + fpga = new Xilinx(jtag, args.bit_file, args.secondary_bit_file, + args.file_type, args.prg_type, args.fpga_part, args.bridge_path, + args.target_flash, args.verify, args.verbose); } else if (fab == "altera") { fpga = new Altera(jtag, args.bit_file, args.file_type, args.prg_type, args.fpga_part, args.bridge_path, args.verify, @@ -569,7 +571,9 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - if ((!args.bit_file.empty() || !args.file_type.empty()) + if ((!args.bit_file.empty() || + !args.secondary_bit_file.empty() || + !args.file_type.empty()) && args.prg_type != Device::RD_FLASH) { try { fpga->program(args.offset, args.unprotect_flash); @@ -691,6 +695,9 @@ int parse_opt(int argc, char **argv, struct arguments *args, cxxopts::value(args->altsetting)) ("bitstream", "bitstream", cxxopts::value(args->bit_file)) + ("secondary-bitstream", "secondary bitstream (some Xilinx" + " UltraScale boards)", + cxxopts::value(args->secondary_bit_file)) ("b,board", "board name, may be used instead of cable", cxxopts::value(args->board)) ("B,bridge", "disable spiOverJtag model detection by providing " @@ -722,8 +729,12 @@ int parse_opt(int argc, char **argv, struct arguments *args, ("dump-flash", "Dump flash mode") ("bulk-erase", "Bulk erase flash", cxxopts::value(args->bulk_erase_flash)) + ("target-flash", + "for boards with multiple flash chips (some Xilinx UltraScale" + " boards), select the target flash: primary (default), secondary or both", + cxxopts::value(args->target_flash)) ("external-flash", - "select ext flash for device with internal and external storage", + "select ext flash for device with internal and external storage", cxxopts::value(args->external_flash)) ("file-size", "provides size in Byte to dump, must be used with dump-flash", @@ -918,11 +929,25 @@ int parse_opt(int argc, char **argv, struct arguments *args, args->pin_config = true; } + if (args->target_flash == "both" || args->target_flash == "secondary") { + if ((args->prg_type == Device::WR_FLASH || args->prg_type == Device::RD_FLASH) && + args->secondary_bit_file.empty() && + !args->protect_flash && + !args->unprotect_flash && + !args->bulk_erase_flash + ) { + printError("Error: secondary bitfile not specified"); + cout << options.help() << endl; + throw std::exception(); + } + } + if (args->list_cables || args->list_boards || args->list_fpga || args->scan_usb) args->is_list_command = true; if (args->bit_file.empty() && + args->secondary_bit_file.empty() && args->file_type.empty() && !args->is_list_command && !args->detect && diff --git a/src/spiInterface.hpp b/src/spiInterface.hpp index 1b5ef48..a1c3995 100644 --- a/src/spiInterface.hpp +++ b/src/spiInterface.hpp @@ -27,6 +27,8 @@ class SPIInterface { bool protect_flash(uint32_t len); bool unprotect_flash(); bool bulk_erase_flash(); + void set_filename(const std::string &filename) {_spif_filename = filename;} + /*! * \brief write len byte into flash starting at offset, * optionally verify after write and unprotect diff --git a/src/xilinx.cpp b/src/xilinx.cpp index b539ff6..39c5ea7 100644 --- a/src/xilinx.cpp +++ b/src/xilinx.cpp @@ -20,6 +20,7 @@ #include "rawParser.hpp" #include "display.hpp" +#include "spiInterface.hpp" #include "xilinx.hpp" #include "xilinxMapParser.hpp" #include "part.hpp" @@ -98,15 +99,41 @@ static uint8_t *get_ircode( return inst_map.at(inst).data(); } +static void open_bitfile( + const std::string &filename, const std::string &extension, + ConfigBitstreamParser **parser, bool reverse, bool verbose) +{ + printInfo("Open file ", false); + if (extension == "bit") { + *parser = new BitParser(filename, reverse, verbose); + } else if (extension == "mcs") { + *parser = new McsParser(filename, reverse, verbose); + } else { + *parser = new RawParser(filename, reverse); + } + + printSuccess("DONE"); + + printInfo("Parse file ", false); + if ((*parser)->parse() == EXIT_FAILURE) { + throw std::runtime_error("Failed to parse bitstream"); + } + + printSuccess("DONE"); +} + Xilinx::Xilinx(Jtag *jtag, const std::string &filename, + const std::string &secondary_filename, const std::string &file_type, Device::prog_type_t prg_type, const std::string &device_package, const std::string &spiOverJtagPath, + const std::string &target_flash, bool verify, int8_t verbose): Device(jtag, filename, file_type, verify, verbose), SPIInterface(filename, verbose, 256, verify), _device_package(device_package), _spiOverJtagPath(spiOverJtagPath), - _irlen(6) + _irlen(6), _filename(filename), _secondary_filename(secondary_filename), + _target_flash(target_flash) { if (prg_type == Device::RD_FLASH) { _mode = Device::READ_MODE; @@ -125,7 +152,13 @@ Xilinx::Xilinx(Jtag *jtag, const std::string &filename, } } - _user_instruction = "USER1"; + select_flash_chip(PRIMARY_FLASH); + + if (target_flash == "both" || target_flash == "secondary") { + _secondary_file_extension = secondary_filename.substr( + secondary_filename.find_last_of(".") + 1); + _mode = Device::SPI_MODE; + } uint32_t idcode = _jtag->get_target_device_id(); std::string family = fpga_list[idcode].family; @@ -299,7 +332,8 @@ int Xilinx::idCode() void Xilinx::program(unsigned int offset, bool unprotect_flash) { - ConfigBitstreamParser *bit; + ConfigBitstreamParser *bit = nullptr; + ConfigBitstreamParser *secondary_bit = nullptr; bool reverse = false; /* nothing to do */ @@ -334,32 +368,27 @@ void Xilinx::program(unsigned int offset, bool unprotect_flash) if (_mode == Device::MEM_MODE || _fpga_family == XCF_FAMILY) reverse = true; - printInfo("Open file ", false); try { - if (_file_extension == "bit") - bit = new BitParser(_filename, reverse, _verbose); - else if (_file_extension == "mcs") - bit = new McsParser(_filename, reverse, _verbose); - else - bit = new RawParser(_filename, reverse); + if (_target_flash == "both" || _target_flash == "primary") { + open_bitfile(_filename, _file_extension, &bit, reverse, _verbose); + } + if (_target_flash == "both" || _target_flash == "secondary") { + open_bitfile(_secondary_filename, _secondary_file_extension, + &secondary_bit, reverse, _verbose); + } } catch (std::exception &e) { - printError("FAIL"); - return; - } - - printSuccess("DONE"); - - printInfo("Parse file ", false); - if (bit->parse() == EXIT_FAILURE) { printError("FAIL"); delete bit; + delete secondary_bit; return; - } else { - printSuccess("DONE"); } - if (_verbose) - bit->displayHeader(); + if (_verbose) { + if (bit) + bit->displayHeader(); + if (secondary_bit) + secondary_bit->displayHeader(); + } if (_fpga_family == XCF_FAMILY) { xcf_program(bit); @@ -368,7 +397,17 @@ void Xilinx::program(unsigned int offset, bool unprotect_flash) } if (_mode == Device::SPI_MODE) { + if (_target_flash == "both" || _target_flash == "primary") { + select_flash_chip(PRIMARY_FLASH); program_spi(bit, offset, unprotect_flash); + } + if (_target_flash == "both" || _target_flash == "secondary") { + select_flash_chip(SECONDARY_FLASH); + program_spi(secondary_bit, offset, unprotect_flash); + } + + reset(); + } else { if (_fpga_family == SPARTAN3_FAMILY) xc3s_flow_program(bit); @@ -570,8 +609,65 @@ bool Xilinx::dumpFlash(uint32_t base_addr, uint32_t len) return true; } - /* dump SPI Flash */ - return SPIInterface::dump(base_addr, len); + if (_target_flash == "both" || _target_flash == "primary") { + select_flash_chip(PRIMARY_FLASH); + SPIInterface::set_filename(_filename); + if (!SPIInterface::dump(base_addr, len)) + return false; + } + if (_target_flash == "both" || _target_flash == "secondary") { + select_flash_chip(SECONDARY_FLASH); + SPIInterface::set_filename(_secondary_filename); + if (!SPIInterface::dump(base_addr, len)) + return false; + } + + return true; +} + +bool Xilinx::protect_flash(uint32_t len) +{ + if (_target_flash == "both" || _target_flash == "primary") { + select_flash_chip(PRIMARY_FLASH); + if (!SPIInterface::protect_flash(len)) + return false; + } + if (_target_flash == "both" || _target_flash == "secondary") { + select_flash_chip(SECONDARY_FLASH); + if (!SPIInterface::protect_flash(len)) + return false; + } + return true; +} + +bool Xilinx::unprotect_flash() +{ + if (_target_flash == "both" || _target_flash == "primary") { + select_flash_chip(PRIMARY_FLASH); + if (!SPIInterface::unprotect_flash()) + return false; + } + if (_target_flash == "both" || _target_flash == "secondary") { + select_flash_chip(SECONDARY_FLASH); + if (!SPIInterface::unprotect_flash()) + return false; + } + return true; +} + +bool Xilinx::bulk_erase_flash() +{ + if (_target_flash == "both" || _target_flash == "primary") { + select_flash_chip(PRIMARY_FLASH); + if (!SPIInterface::bulk_erase_flash()) + return false; + } + if (_target_flash == "both" || _target_flash == "secondary") { + select_flash_chip(SECONDARY_FLASH); + if (!SPIInterface::bulk_erase_flash()) + return false; + } + return true; } /* flow program for xc3s (legacy mode) */ @@ -1460,3 +1556,15 @@ int Xilinx::spi_wait(uint8_t cmd, uint8_t mask, uint8_t cond, return 0; } } + +void Xilinx::select_flash_chip(xilinx_flash_chip_t flash_chip) { + switch (flash_chip) { + case SECONDARY_FLASH: + _user_instruction = "USER2"; + break; + case PRIMARY_FLASH: + default: + _user_instruction = "USER1"; + break; + } +} diff --git a/src/xilinx.hpp b/src/xilinx.hpp index c20b97e..458fe2f 100644 --- a/src/xilinx.hpp +++ b/src/xilinx.hpp @@ -12,14 +12,17 @@ #include "device.hpp" #include "jtag.hpp" #include "spiInterface.hpp" +#include "jedParser.hpp" class Xilinx: public Device, SPIInterface { public: Xilinx(Jtag *jtag, const std::string &filename, + const std::string &secondary_filename, const std::string &file_type, Device::prog_type_t prg_type, const std::string &device_package, const std::string &spiOverJtagPath, + const std::string &target_flash, bool verify, int8_t verbose); ~Xilinx(); @@ -32,21 +35,15 @@ class Xilinx: public Device, SPIInterface { /*! * \brief protect SPI flash blocks */ - bool protect_flash(uint32_t len) override { - return SPIInterface::protect_flash(len); - } + bool protect_flash(uint32_t len) override; /*! * \brief unprotect SPI flash blocks */ - bool unprotect_flash() override { - return SPIInterface::unprotect_flash(); - } + bool unprotect_flash() override; /*! * \brief erase SPI flash blocks */ - bool bulk_erase_flash() override { - return SPIInterface::bulk_erase_flash(); - } + bool bulk_erase_flash() override; int idCode() override; void reset() override; @@ -185,6 +182,20 @@ class Xilinx: public Device, SPIInterface { * \return false if missing device mode, true otherwise */ bool load_bridge(); + + enum xilinx_flash_chip_t { + PRIMARY_FLASH, + SECONDARY_FLASH + }; + + /*! + * \brief Starting from UltraScale, Xilinx devices can support dual + * QSPI flash configuration, with two different flash chips + * on the board. Target the selected one via the bridge by + * chaging the USER instruction to use. + */ + void select_flash_chip(xilinx_flash_chip_t flash_chip); + std::string _device_package; std::string _spiOverJtagPath; /**< spiOverJtag explicit path */ int _xc95_line_len; /**< xc95 only: number of col by flash line */ @@ -194,6 +205,10 @@ class Xilinx: public Device, SPIInterface { char _cpld_base_name[7]; /**< cpld name (without package size) */ int _irlen; /**< IR bit length */ std::map> _ircode_map; /**< bscan instructions based on model */ + std::string _filename; /* path to the primary flash file */ + std::string _secondary_filename; /* path to the secondary flash file (SPIx8) */ + std::string _secondary_file_extension; /* file type for the secondary flash file */ + std::string _target_flash; /* in boards with two flash chips, select the target (1, 2 or both) */ std::string _user_instruction; /* which USER bscan instruction to interface with SPI */ };