diff --git a/README.md b/README.md index 0eb8e5c..8d408bc 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ openFPGALoader -- a program to flash FPGA --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 diff --git a/doc/board-compatibility-list.md b/doc/board-compatibility-list.md index 7b3c89b..c45058c 100644 --- a/doc/board-compatibility-list.md +++ b/doc/board-compatibility-list.md @@ -43,11 +43,11 @@ openFPGALoader -b arty -f bitstream.bit # Writing in flash (non-volatile) | **orangeCrab** | [Orange Crab](https://github.com/gregdavill/OrangeCrab) | ECP5
LFE5U-25F-8MG285C | OK (JTAG) | OK (DFU) | | **pipistrello** | [Saanlima Pipistrello LX45](http://pipistrello.saanlima.com/index.php?title=Welcome_to_Pipistrello) | Spartan6
xc6slx45-csg324 | OK | OK | | **qmtechCycloneV** | [QMTech CycloneV Core Board](https://fr.aliexpress.com/i/1000006622149.html) | Cyclone V
5CEFA2F23I7 | OK | OK | -| **runber** | [SeeedStudio Gowin RUNBER](https://www.seeedstudio.com/Gowin-RUNBER-Development-Board-p-4779.html) | littleBee
GW1N-4 | OK | IF | +| **runber** | [SeeedStudio Gowin RUNBER](https://www.seeedstudio.com/Gowin-RUNBER-Development-Board-p-4779.html) | littleBee
GW1N-4 | OK | IF/EF | | | [Scarab Hardware MiniSpartan6+](https://www.kickstarter.com/projects/1812459948/minispartan6-a-powerful-fpga-board-and-easy-to-use) | Spartan6
xc6slx25-3-ftg256 | OK | NT | | **spartanEdgeAccelBoard** | [SeeedStudio Spartan Edge Accelerator Board](http://wiki.seeedstudio.com/Spartan-Edge-Accelerator-Board) | Spartan7
xc7s15ftgb196 | OK | NA | | **tangnano** | [Sipeed Tang Nano](https://tangnano.sipeed.com/en/) | littleBee
GW1N-1 | OK | | -| **tangnano4k** | [Sipeed Tang Nano 4K](https://tangnano.sipeed.com/en/) | littleBee
GW1NSR-4C | OK | | +| **tangnano4k** | [Sipeed Tang Nano 4K](https://tangnano.sipeed.com/en/) | littleBee
GW1NSR-4C | OK | IF/EF | | **tec0117** | [Trenz Gowin LittleBee (TEC0117)](https://shop.trenz-electronic.de/en/TEC0117-01-FPGA-Module-with-GOWIN-LittleBee-and-8-MByte-internal-SDRAM) | littleBee
GW1NR-9 | OK | IF | | **xtrx** | [FairWaves XTRXPro](https://www.crowdsupply.com/fairwaves/xtrx) | Artix
xc7a50tcpg236 | OK | OK | | **xyloni_spi** | [Efinix Xyloni](https://www.efinixinc.com/products-devkits-xyloni.html) | Trion
T8F81 | NA | AS | @@ -55,6 +55,7 @@ openFPGALoader -b arty -f bitstream.bit # Writing in flash (non-volatile) | **zedboard** | [Avnet ZedBoard](https://www.avnet.com/wps/portal/us/products/avnet-boards/avnet-board-families/zedboard/) | zynq7000
xc7z020clg484 | OK | NA | - *IF* Internal Flash +- *EF* External Flash - *AS* Active Serial flash mode - *NA* Not Available - *NT* Not Tested diff --git a/doc/gowin.md b/doc/gowin.md index abdd75b..70a629e 100644 --- a/doc/gowin.md +++ b/doc/gowin.md @@ -32,3 +32,6 @@ openFPGALoader -f -b BOARD_NAME impl/pnr/*.fs where *BOARD_NAME* is: - **tec0117** - **runber** + +It's possible to flash external SPI Flash (connected to MSPI) in bscan mode +by using `--external-flash` instead of `-f` diff --git a/src/gowin.cpp b/src/gowin.cpp index a5204cd..2eb5fa1 100644 --- a/src/gowin.cpp +++ b/src/gowin.cpp @@ -18,6 +18,8 @@ #include "progressBar.hpp" #include "display.hpp" #include "fsparser.hpp" +#include "rawParser.hpp" +#include "spiFlash.hpp" using namespace std; @@ -57,44 +59,73 @@ using namespace std; #define EF_PROGRAM 0x71 #define EFLASH_ERASE 0x75 +/* BSCAN spi (external flash) (see below for details) */ +/* most common pins def */ +#define BSCAN_SPI_SCK (1 << 1) +#define BSCAN_SPI_CS (1 << 3) +#define BSCAN_SPI_DI (1 << 5) +#define BSCAN_SPI_DO (1 << 7) +#define BSCAN_SPI_MSK ((0x01 << 6)) +/* GW1NSR-4C pins def */ +#define BSCAN_GW1NSR_4C_SPI_SCK (1 << 7) +#define BSCAN_GW1NSR_4C_SPI_CS (1 << 5) +#define BSCAN_GW1NSR_4C_SPI_DI (1 << 3) +#define BSCAN_GW1NSR_4C_SPI_DO (1 << 1) +#define BSCAN_GW1NSR_4C_SPI_MSK ((0x01 << 0)) + Gowin::Gowin(Jtag *jtag, const string filename, const string &file_type, - Device::prog_type_t prg_type, + Device::prog_type_t prg_type, bool external_flash, bool verify, int8_t verbose): Device(jtag, filename, file_type, - verify, verbose), is_gw1n1(false) + verify, verbose), is_gw1n1(false), _external_flash(external_flash), + _spi_sck(BSCAN_SPI_SCK), _spi_cs(BSCAN_SPI_CS), + _spi_di(BSCAN_SPI_DI), _spi_do(BSCAN_SPI_DO), + _spi_msk(BSCAN_SPI_MSK) { _fs = NULL; uint32_t idcode = _jtag->get_target_device_id();; + if (prg_type == Device::WR_FLASH) + _mode = Device::FLASH_MODE; + else + _mode = Device::MEM_MODE; + if (!_file_extension.empty()) { if (_file_extension == "fs") { - if (prg_type == Device::WR_FLASH) - _mode = Device::FLASH_MODE; - else - _mode = Device::MEM_MODE; try { _fs = new FsParser(_filename, _mode == Device::MEM_MODE, _verbose); } catch (std::exception &e) { throw std::runtime_error(e.what()); } - - printInfo("Parse file ", false); - if (_fs->parse() == EXIT_FAILURE) { - printError("FAIL"); - delete _fs; - throw std::runtime_error("can't parse file"); - } else { - printSuccess("DONE"); + } else { + /* non fs file is only allowed with external flash */ + if (!external_flash) + throw std::runtime_error("incompatible file format"); + try { + _fs = new RawParser(_filename, false); + } catch (std::exception &e) { + throw std::runtime_error(e.what()); } + } - if (_verbose) - _fs->displayHeader(); + printInfo("Parse file ", false); + if (_fs->parse() == EXIT_FAILURE) { + printError("FAIL"); + delete _fs; + throw std::runtime_error("can't parse file"); + } else { + printSuccess("DONE"); + } + + if (_verbose) + _fs->displayHeader(); + + /* for fs file check match with targeted device */ + if (_file_extension == "fs") { string idcode_str = _fs->getHeaderVal("idcode"); uint32_t fs_idcode = std::stoul(idcode_str.c_str(), NULL, 16); if ((fs_idcode & 0x0fffffff) != idcode) { throw std::runtime_error("mismatch between target's idcode and fs idcode"); } - } else { - throw std::runtime_error("incompatible file format"); } } _jtag->setClkFreq(2500000); @@ -102,6 +133,14 @@ Gowin::Gowin(Jtag *jtag, const string filename, const string &file_type, /* erase and program flash differ for GW1N1 */ if (idcode == 0x0900281B) is_gw1n1 = true; + /* bscan spi external flash differ for GW1NSR-4C */ + if (idcode == 0x0100981b) { + _spi_sck = BSCAN_GW1NSR_4C_SPI_SCK; + _spi_cs = BSCAN_GW1NSR_4C_SPI_CS; + _spi_di = BSCAN_GW1NSR_4C_SPI_DI; + _spi_do = BSCAN_GW1NSR_4C_SPI_DO; + _spi_msk = BSCAN_GW1NSR_4C_SPI_MSK; + } } Gowin::~Gowin() @@ -155,11 +194,13 @@ void Gowin::programFlash() /* check if file checksum == checksum in FPGA */ status = readUserCode(); - if (_fs->checksum() != status) { + int checksum = static_cast(_fs)->checksum(); + if (checksum != status) { printError("CRC check : FAIL"); - printf("%04x %04x\n", _fs->checksum(), status); - } else + printf("%04x %04x\n", checksum, status); + } else { printSuccess("CRC check: Success"); + } if (_verbose) displayReadReg(readStatusReg()); @@ -176,8 +217,39 @@ void Gowin::program(unsigned int offset) if (_mode == NONE_MODE || !_fs) return; + data = _fs->getData(); + length = _fs->getLength(); + if (_mode == FLASH_MODE) { - programFlash(); + if (!_external_flash) { /* write into internal flash */ + programFlash(); + } else { /* write bitstream into external flash */ + _jtag->setClkFreq(10000000); + + if (!EnableCfg()) + throw std::runtime_error("Error: fail to enable configuration"); + + eraseSRAM(); + wr_rd(XFER_DONE, NULL, 0, NULL, 0); + wr_rd(NOOP, NULL, 0, NULL, 0); + + wr_rd(0x3D, NULL, 0, NULL, 0); + + SPIFlash spiFlash(this, (_verbose ? 1 : (_quiet ? -1 : 0))); + spiFlash.reset(); + spiFlash.read_id(); + spiFlash.read_status_reg(); + if (spiFlash.erase_and_prog(offset, data, length / 8) != 0) + throw std::runtime_error("Error: write to flash failed"); + if (_verify) + if (!spiFlash.verify(offset, data, length / 8, 256)) + throw std::runtime_error("Error: flash vefication failed"); + if (!DisableCfg()) + throw std::runtime_error("Error: fail to disable configuration"); + + reset(); + } + return; } @@ -185,9 +257,6 @@ void Gowin::program(unsigned int offset) displayReadReg(readStatusReg()); } - data = _fs->getData(); - length = _fs->getLength(); - wr_rd(READ_IDCODE, NULL, 0, NULL, 0); /* erase SRAM */ @@ -207,11 +276,13 @@ void Gowin::program(unsigned int offset) /* check if file checksum == checksum in FPGA */ status = readUserCode(); - if (_fs->checksum() != status) { + uint32_t checksum = static_cast(_fs)->checksum(); + if (checksum != status) { printError("SRAM Flash: FAIL"); - printf("%04x %04x\n", _fs->checksum(), status); - } else + printf("%04x %04x\n", checksum, status); + } else { printSuccess("SRAM Flash: Success"); + } if (_verbose) displayReadReg(readStatusReg()); } @@ -528,3 +599,130 @@ bool Gowin::eraseSRAM() return false; } } + +/* SPI wrapper + * extflash access may be done using specific mode or + * boundary scan. But former is only available with mode=[11] + * so use Bscan + * + * it's a bitbanging mode with: + * Pins Name of SPI Flash | SCLK | CS | DI | DO | + * Bscan Chain[7:0] | 7 6 | 5 4 | 3 2 | 1 0 | + * (ctrl & data) | 0 | 0 | 0 | 1 | + * ctrl 0 -> out, 1 -> in + * data 1 -> high, 0 -> low + * but all byte must be bit reversal... + */ + +#define spi_gowin_write(_wr, _rd, _len) do { \ + _jtag->shiftDR(_wr, _rd, _len); \ + _jtag->toggleClk(6); } while (0) + +int Gowin::spi_put(uint8_t cmd, uint8_t *tx, uint8_t *rx, uint32_t len) +{ + uint8_t jrx[len+1], jtx[len+1]; + jtx[0] = cmd; + if (tx) + memcpy(jtx+1, tx, len); + else + memset(jtx+1, 0, len); + int ret = spi_put(jtx, (rx)? jrx : NULL, len+1); + if (rx) + memcpy(rx, jrx+1, len); + return ret; +} + +int Gowin::spi_put(uint8_t *tx, uint8_t *rx, uint32_t len) +{ + /* set CS/SCK/DI low */ + uint8_t t = _spi_msk | _spi_do; + t &= ~_spi_cs; + spi_gowin_write(&t, NULL, 8); + _jtag->flush(); + + /* send bit/bit full tx content (or set di to 0 when NULL) */ + for (uint32_t i = 0; i < len * 8; i++) { + uint8_t r; + t = _spi_msk | _spi_do; + if (tx != NULL && tx[i>>3] & (1 << (7-(i&0x07)))) + t |= _spi_di; + spi_gowin_write(&t, NULL, 8); + t |= _spi_sck; + spi_gowin_write(&t, (rx) ? &r : NULL, 8); + _jtag->flush(); + /* if read reconstruct bytes */ + if (rx) { + if (r & _spi_do) + rx[i >> 3] |= 1 << (7-(i & 0x07)); + else + rx[i >> 3] &= ~(1 << (7-(i & 0x07))); + } + } + /* set CS and unset SCK (next xfer) */ + t &= ~_spi_sck; + t |= _spi_cs; + spi_gowin_write(&t, NULL, 8); + _jtag->flush(); + + return 0; +} + +int Gowin::spi_wait(uint8_t cmd, uint8_t mask, uint8_t cond, + uint32_t timeout, bool verbose) +{ + uint32_t count = 0; + uint8_t rx, t; + + /* set CS/SCK/DI low */ + t = _spi_msk | _spi_do; + spi_gowin_write(&t, NULL, 8); + + /* send command bit/bit */ + for (int i = 0; i < 8; i++) { + t = _spi_msk | _spi_do; + if ((cmd & (1 << (7-i))) != 0) + t |= _spi_di; + spi_gowin_write(&t, NULL, 8); + t |= _spi_sck; + spi_gowin_write(&t, NULL, 8); + _jtag->flush(); + } + + t = _spi_msk | _spi_do; + do { + rx = 0; + /* read status register bit/bit with di == 0 */ + for (int i = 0; i < 8; i++) { + uint8_t r; + t &= ~_spi_sck; + spi_gowin_write(&t, NULL, 8); + t |= _spi_sck; + spi_gowin_write(&t, &r, 8); + _jtag->flush(); + if ((r & _spi_do) != 0) + rx |= 1 << (7-i); + } + + count++; + if (count == timeout) { + printf("timeout: %x\n", rx); + break; + } + if (verbose) + printf("%x %x %x %u\n", rx, mask, cond, count); + } while ((rx & mask) != cond); + + /* set CS & unset SCK (next xfer) */ + t &= ~_spi_sck; + t |= _spi_cs; + spi_gowin_write(&t, NULL, 8); + _jtag->flush(); + + if (count == timeout) { + printf("%02x\n", rx); + std::cout << "wait: Error" << std::endl; + return -ETIME; + } + + return 0; +} diff --git a/src/gowin.hpp b/src/gowin.hpp index d36b448..492d95b 100644 --- a/src/gowin.hpp +++ b/src/gowin.hpp @@ -12,14 +12,14 @@ #include #include "device.hpp" -#include "fsparser.hpp" +#include "configBitstreamParser.hpp" #include "jtag.hpp" -#include "jedParser.hpp" +#include "spiInterface.hpp" -class Gowin: public Device { +class Gowin: public Device, SPIInterface { public: Gowin(Jtag *jtag, std::string filename, const std::string &file_type, - Device::prog_type_t prg_type, + Device::prog_type_t prg_type, bool external_flash, bool verify, int8_t verbose); ~Gowin(); int idCode() override; @@ -27,6 +27,13 @@ class Gowin: public Device { void program(unsigned int offset) override; void programFlash(); + /* spi interface */ + int spi_put(uint8_t cmd, uint8_t *tx, uint8_t *rx, + uint32_t len) override; + int spi_put(uint8_t *tx, uint8_t *rx, uint32_t len) override; + int spi_wait(uint8_t cmd, uint8_t mask, uint8_t cond, + uint32_t timeout, bool verbose) override; + private: bool wr_rd(uint8_t cmd, uint8_t *tx, int tx_len, uint8_t *rx, int rx_len, bool verbose = false); @@ -40,7 +47,13 @@ class Gowin: public Device { void displayReadReg(uint32_t dev); uint32_t readStatusReg(); uint32_t readUserCode(); - FsParser *_fs; + ConfigBitstreamParser *_fs; bool is_gw1n1; + bool _external_flash; /**< select between int or ext flash */ + uint8_t _spi_sck; /**< clk signal offset in bscan SPI */ + uint8_t _spi_cs; /**< cs signal offset in bscan SPI */ + uint8_t _spi_di; /**< di signal (mosi) offset in bscan SPI */ + uint8_t _spi_do; /**< do signal (miso) offset in bscan SPI */ + uint8_t _spi_msk; /** default spi msk with only do out */ }; #endif // GOWIN_HPP_ diff --git a/src/main.cpp b/src/main.cpp index d0128a6..3663da3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -58,6 +58,7 @@ struct arguments { string probe_firmware; int index_chain; unsigned int file_size; + bool external_flash; }; int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *pins_config); @@ -73,7 +74,7 @@ int main(int argc, char **argv) /* command line args. */ struct arguments args = {0, false, false, false, 0, "", "", "-", "", -1, 0, "-", false, false, false, false, Device::WR_SRAM, false, - false, false, "", "", "", -1, 0}; + false, false, "", "", "", -1, 0, false}; /* parse arguments */ try { if (parse_opt(argc, argv, &args, &pins_config)) @@ -397,7 +398,7 @@ int main(int argc, char **argv) args.prg_type, args.verify, args.verbose); } else if (fab == "Gowin") { fpga = new Gowin(jtag, args.bit_file, args.file_type, - args.prg_type, args.verify, args.verbose); + args.prg_type, args.external_flash, args.verify, args.verbose); } else if (fab == "lattice") { fpga = new Lattice(jtag, args.bit_file, args.file_type, args.prg_type, args.verify, args.verbose); @@ -499,6 +500,9 @@ int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *p cxxopts::value(args->detect)) ("dfu", "DFU mode", cxxopts::value(args->dfu)) ("dump-flash", "Dump flash mode") + ("external-flash", + "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", cxxopts::value(args->file_size)) ("file-type", "provides file type instead of let's deduced by using extension", @@ -569,6 +573,8 @@ int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *p args->prg_type = Device::WR_SRAM; else if (result.count("dump-flash")) args->prg_type = Device::RD_FLASH; + else if (result.count("external-flash")) + args->prg_type = Device::WR_FLASH; if (result.count("freq")) { double freq;