From dbc46cdaff310da38faa176293e5c38b018c28d7 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Sat, 7 Jun 2025 17:27:53 +0200 Subject: [PATCH] latticeSSPI: new Driver to access Lattice ECP5 via Slave SPI --- CMakeLists.txt | 29 ++-- src/latticeSSPI.cpp | 374 ++++++++++++++++++++++++++++++++++++++++++++ src/latticeSSPI.hpp | 46 ++++++ src/main.cpp | 1 + 4 files changed, 440 insertions(+), 10 deletions(-) create mode 100644 src/latticeSSPI.cpp create mode 100644 src/latticeSSPI.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9eb56bf..d7a4eb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,7 +100,6 @@ set(OPENFPGALOADER_SOURCE src/efinix.cpp src/efinixHexParser.cpp src/fx2_ll.cpp - src/ice40.cpp src/ihexParser.cpp src/pofParser.cpp src/rawParser.cpp @@ -109,8 +108,6 @@ set(OPENFPGALOADER_SOURCE src/usbBlaster.cpp src/epcq.cpp src/svf_jtag.cpp - src/jedParser.cpp - src/feaparser.cpp src/display.cpp src/jtag.cpp src/ftdiJtagBitbang.cpp @@ -118,12 +115,10 @@ set(OPENFPGALOADER_SOURCE src/configBitstreamParser.cpp src/ftdipp_mpsse.cpp src/main.cpp - src/latticeBitParser.cpp src/libusb_ll.cpp src/gowin.cpp src/device.cpp src/jlink.cpp - src/lattice.cpp src/progressBar.cpp src/fsparser.cpp src/mcsParser.cpp @@ -152,7 +147,6 @@ set(OPENFPGALOADER_HEADERS src/efinix.hpp src/efinixHexParser.hpp src/fx2_ll.hpp - src/ice40.hpp src/ihexParser.hpp src/pofParser.hpp src/progressBar.hpp @@ -168,8 +162,6 @@ set(OPENFPGALOADER_HEADERS src/fsparser.hpp src/part.hpp src/board.hpp - src/jedParser.hpp - src/feaparser.hpp src/display.hpp src/mcsParser.hpp src/ftdipp_mpsse.hpp @@ -183,8 +175,6 @@ set(OPENFPGALOADER_HEADERS src/gowin.hpp src/cable.hpp src/ftdispi.hpp - src/lattice.hpp - src/latticeBitParser.hpp src/xilinx.hpp src/xilinxMapParser.hpp src/colognechip.hpp @@ -192,6 +182,25 @@ set(OPENFPGALOADER_HEADERS src/esp_usb_jtag.hpp ) +# Lattice Drivers / Files parsers. +list(APPEND OPENFPGALOADER_SOURCE + src/ice40.cpp + src/lattice.cpp + src/latticeSSPI.cpp + src/feaparser.cpp + src/jedParser.cpp + src/latticeBitParser.cpp +) + +list(APPEND OPENFPGALOADER_HEADERS + src/ice40.hpp + src/lattice.hpp + src/latticeSSPI.hpp + src/jedParser.hpp + src/feaparser.hpp + src/latticeBitParser.hpp +) + link_directories( ${LIBUSB_LIBRARY_DIRS} ${LIBFTDI_LIBRARY_DIRS} diff --git a/src/latticeSSPI.cpp b/src/latticeSSPI.cpp new file mode 100644 index 0000000..ccfef1b --- /dev/null +++ b/src/latticeSSPI.cpp @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 Gwenhael Goavec-Merou + */ + +#define __STDC_FORMAT_MACROS +#include +#include +#include + +#include +#include + +#include "device.hpp" +#include "ftdispi.hpp" +#include "latticeBitParser.hpp" +#include "progressBar.hpp" + +#include "latticeSSPI.hpp" + +#define SSPI_READ_ID 0xE0 +#define SSPI_ISC_READ_STATUS 0x3C +#define ISC_ENABLE 0xC6 /* ISC_ENABLE - Offline Mode */ +#define ISC_DISABLE 0x26 /* ISC_DISABLE */ +#define READ_BUSY_FLAG 0xF0 +#define FLASH_ERASE 0x0E + +# define REG_STATUS_DONE (1 << 8) /* Flash or SRAM Done Flag (ISC_EN=0 -> 1 Successful Flash to SRAM transfer, ISC_EN=1 -> 1 Programmed) */ +# define REG_STATUS_ISC_EN (1 << 9) /* Enable Configuration Interface (1=Enable, 0=Disable) */ +# define REG_STATUS_BUSY (1 << 12) /* Busy Flag (1 = Busy) */ +# define REG_STATUS_FAIL (1 << 13) /* Fail Flag (1 = Operation failed) */ +# define REG_STATUS_PP_CFG (1 << 15) /* Password Protection All Enabled for CFG0 and CFG1 flash sectors 0=Disabled (Default), 1=Enabled */ +# define REG_STATUS_PP_FSK (1 << 16) /* Password Protection Enabled for Feature and Security Key flash sectors 0=Disabled (Default), 1=Enabled */ +# define REG_STATUS_PP_UFM (1 << 17) /* Password Protection enabled for all UFM flash sectors 0=Disabled (Default), 1=Enabled */ +# define REG_STATUS_AUTH_DONE (1 << 18) /* Authentication done */ +# define REG_STATUS_PRI_BOOT_FAIL (1 << 21) /* Primary boot failure (1= Fail) even though secondary boot successful */ +# define REG_STATUS_CNF_CHK_MASK (0x0f << 23) /* Configuration Status Check */ +# define REG_STATUS_EXEC_ERR (1 << 26) /*** NOT specified for MachXO3D ***/ +# define REG_STATUS_DEV_VERIFIED (1 << 27) /* I=0 Device verified correct, I=1 Device failed to verify */ + +LatticeSSPI::LatticeSSPI(FtdiSpi *spi, const std::string &filename, + const std::string &file_type, + const int8_t verbose): + Device(NULL, filename, file_type, false, verbose), _spi(spi) +{ + _spi->setMode(0); +} + +uint32_t LatticeSSPI::char_array_to_word(uint8_t *in) +{ + return static_cast(in[0] << 24) | + static_cast(in[1] << 16) | + static_cast(in[2] << 8) | + static_cast(in[3] << 0); +} + +bool LatticeSSPI::EnableISC(uint8_t flash_mode) +{ + cmd_class_c(ISC_ENABLE); + + if (!pollBusyFlag()) + return false; + if (!checkStatus(REG_STATUS_ISC_EN, REG_STATUS_ISC_EN)) + return false; + return true; +} + +bool LatticeSSPI::DisableISC() +{ + cmd_class_c(ISC_DISABLE); + + if (!pollBusyFlag()) + return false; + if (!checkStatus(0, REG_STATUS_ISC_EN)) + return false; + return true; +} + +uint32_t LatticeSSPI::readStatusReg() +{ + uint8_t rx[4]; + + cmd_class_a(SSPI_ISC_READ_STATUS, rx, 4 * 8); + + return static_cast(char_array_to_word(rx)); +} + +/* + * Read Only command. + * 8bits command + 24 dummy bits + len rx + * len: nb bits + */ +bool LatticeSSPI::cmd_class_a(uint8_t cmd, uint8_t *rx, uint32_t len) +{ + const uint32_t xferByteLen = (len + 7) / 8; + const uint32_t kBytetLen = xferByteLen + 3; // Convert bits to bytes after adding dummy + uint8_t lrx[kBytetLen]; + uint8_t ltx[kBytetLen]; + memset(ltx, 0x00, kBytetLen); + + _spi->spi_put(cmd, ltx, lrx, kBytetLen); + + memcpy(rx, &lrx[3], xferByteLen); + + return true; +} + +bool LatticeSSPI::cmd_class_b(uint8_t cmd, uint8_t *tx, uint32_t len) +{ + throw std::runtime_error("Not implemented"); + return true; +} + +bool LatticeSSPI::cmd_class_c(uint8_t cmd) +{ + const uint8_t ltx[3] = {0x00, 0x00, 0x00}; + + _spi->spi_put(cmd, ltx, NULL, 3); + + return true; +} + +uint32_t LatticeSSPI::idCode() +{ + uint8_t rx[4]; + cmd_class_a(SSPI_READ_ID, rx, 32); + return char_array_to_word(rx); +} + +bool LatticeSSPI::pollBusyFlag(bool verbose) +{ + uint8_t rx; + int timeout = 0; + do { + cmd_class_a(READ_BUSY_FLAG, &rx, 8); + if (verbose) + printf("pollBusyFlag :%02x\n", rx); + if (timeout == 100000000){ + std::cerr << "timeout" << std::endl; + return false; + } else { + timeout++; + } + } while (rx != 0); + + return true; +} + +bool LatticeSSPI::flashErase(uint32_t mask) +{ + const uint8_t tx[] = {0x01, 0x00, 0x00}; + _spi->spi_put(FLASH_ERASE, tx, 0, 3); + + if (!pollBusyFlag()) + return false; + + if (!checkStatus(0, REG_STATUS_FAIL)) + return false; + + return true; +} + +bool LatticeSSPI::program_mem() +{ + bool err; + LatticeBitParser _bit(_filename, false, _verbose); + + printInfo("Open file: ", false); + printSuccess("DONE"); + + err = _bit.parse(); + + printInfo("Parse file: ", false); + if (err == EXIT_FAILURE) { + printError("FAIL"); + return false; + } else { + printSuccess("DONE"); + } + + if (_verbose) + _bit.displayHeader(); + + /* Prepare bitstream */ + const uint8_t *data = _bit.getData(); + int length = _bit.getLength()/8; + + /* read ID Code 0xE0 and compare to bitstream */ + uint32_t bit_idcode = std::stoul(_bit.getHeaderVal("idcode").c_str(), NULL, 16); + uint32_t idcode = idCode(); + if (idcode != bit_idcode) { + char mess[256]; + snprintf(mess, 256, "mismatch between target's idcode and bitstream idcode\n" + "\tbitstream has 0x%08X hardware requires 0x%08x", bit_idcode, idcode); + printError(mess); + return false; + } + + if (_verbose) { + printf("IDCode : 0x%08x\n", idcode); + displayReadReg(readStatusReg()); + } + + /* LSC_REFRESH 0x79 -- "Equivalent to toggle PROGRAMN pin" + * We REFRESH only if the fpga is in a status of error due to + * the previous bitstream. For example, this happens if + * no bitstream is present on the SPI FLASH + */ + cmd_class_c(0x79); + usleep(8000); + + /* ISC Enable 0xC6 */ + printInfo("Enable configuration: ", false); + if (!EnableISC(0x00)) { + printError("FAIL"); + displayReadReg(readStatusReg()); + return false; + } else { + printSuccess("DONE"); + } + displayReadReg(readStatusReg()); + + /* ISC ERASE + * For Nexus family (from svf file): 1 byte to tx 0x00 + */ + printInfo("SRAM erase: ", false); + //uint32_t mask_erase[1] = {FLASH_ERASE_SRAM}; + + if (flashErase(0x01/*mask_erase[0]*/) == false) { + printError("FAIL"); + displayReadReg(readStatusReg()); + return false; + } else { + printSuccess("DONE"); + } + + /* LSC_INIT_ADDRESS */ + cmd_class_c(0x46); + + /* Switch to manual CS. + * This signal must be low for LSC_BITSTREAM_BURST and + * all bitstream TX. + */ + _spi->setCSmode(FtdiSpi::SPI_CS_MANUAL); + _spi->clearCs(); + cmd_class_c(0x7A); + + uint8_t tmp[1024]; + int size = 1024; + + ProgressBar progress("Loading", length, 50, _quiet); + + for (int i = 0; i < length; i += size) { + progress.display(i); + + if (length < i + size) { + size = length-i; + } + + for (int ii = 0; ii < size; ii++) + tmp[ii] = data[i+ii]; + + _spi->spi_put(tmp, NULL, size); + } + progress.done(); + /* Switch CS to Automatic mode */ + _spi->setCs(); + _spi->setCSmode(FtdiSpi::SPI_CS_AUTO); + + usleep(1000); + + /* disable configuration mode */ + printInfo("Disable configuration: ", false); + if (!DisableISC()) { + printError("FAIL"); + displayReadReg(readStatusReg()); + return false; + } else { + printSuccess("DONE"); + } + + if (_verbose) + displayReadReg(readStatusReg()); + uint8_t nop[] = {0xff, 0xff, 0xff, 0xff}; + _spi->spi_put(nop, NULL, 4); + + return true; +} + +void LatticeSSPI::program(unsigned int offset, bool unprotect_flash) +{ + bool retval = true; + if (_mode == FLASH_MODE) + throw std::runtime_error("Flash mode not avaible when programming in Slave SPI"); + + retval = program_mem(); + if (!retval) + throw std::exception(); +} + +void LatticeSSPI::displayReadReg(uint32_t dev) +{ + uint8_t err; + printf("displayReadReg 0x%08x\n", dev); + if (dev & 1<<0) printf("\tTRAN Mode\n"); + printf("\tConfig Target Selection : %" PRIx32 "\n", (dev >> 1) & 0x07); + if (dev & 1<<4) printf("\tJTAG Active\n"); + if (dev & 1<<5) printf("\tPWD Protect\n"); + if (dev & 1<<6) printf("\tOTP\n"); + if (dev & 1<<7) printf("\tDecrypt Enable\n"); + if (dev & REG_STATUS_DONE) printf("\tDone Flag\n"); + if (dev & REG_STATUS_ISC_EN) printf("\tISC Enable\n"); + if (dev & 1 << 10) printf("\tWrite Enable\n"); + if (dev & 1 << 11) printf("\tRead Enable\n"); + if (dev & REG_STATUS_BUSY) printf("\tBusy Flag\n"); + if (dev & REG_STATUS_FAIL) printf("\tFail Flag\n"); + if (dev & 1 << 14) printf("\tFFEA OTP\n"); + if (dev & 1 << 15) printf("\tDecrypt Only\n"); + if (dev & 1 << 16) printf("\tPWD Enable\n"); + if (dev & 1 << 17) printf("\tUFM OTP\n"); + if (dev & 1 << 18) printf("\tASSP\n"); + if (dev & 1 << 19) printf("\tSDM Enable\n"); + if (dev & 1 << 20) printf("\tEncryption PreAmble\n"); + if (dev & 1 << 21) printf("\tStd PreAmble\n"); + if (dev & 1 << 22) printf("\tSPIm Fail1\n"); + err = (dev >> 23)&0x07; + + printf("\tBSE Error Code\n"); + printf("\t\t"); + switch (err) { + case 0: + printf("No err\n"); + break; + case 1: + printf("ID ERR\n"); + break; + case 2: + printf("CMD ERR\n"); + break; + case 3: + printf("CRC ERR\n"); + break; + case 4: + printf("Preamble ERR\n"); + break; + case 5: + printf("Abort ERR\n"); + break; + case 6: + printf("Overflow ERR\n"); + break; + case 7: + printf("SDM EOF\n"); + break; + case 8: + printf("Authentication ERR\n"); + break; + case 9: + printf("Authentication Setup ERR\n"); + break; + case 10: + printf("Bitstream Engine Timeout ERR\n"); + break; + default: + printf("unknown error: %x\n", err); + } + + if (dev & REG_STATUS_EXEC_ERR) printf("\tEXEC Error\n"); + if ((dev >> 27) & 0x01) printf("\tDevice failed to verify\n"); + if ((dev >> 28) & 0x01) printf("\tInvalid Command\n"); + if ((dev >> 29) & 0x01) printf("\tSED Error\n"); + if ((dev >> 30) & 0x01) printf("\tBypass Mode\n"); + if ((dev >> 31) & 0x01) printf("\tFT Mode\n"); +} diff --git a/src/latticeSSPI.hpp b/src/latticeSSPI.hpp new file mode 100644 index 0000000..3d24057 --- /dev/null +++ b/src/latticeSSPI.hpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 Gwenhael Goavec-Merou + */ + +#ifndef SRC_LATTICESSPI_HPP_ +#define SRC_LATTICESSPI_HPP_ + +#include + +#include "device.hpp" +#include "ftdispi.hpp" + +class LatticeSSPI: public Device { + public: + LatticeSSPI(FtdiSpi *spi, const std::string &filename, + const std::string &file_type, const int8_t verbose); + + uint32_t idCode() override; + int userCode(); + void reset() override {} + void program(unsigned int offset, bool unprotect_flash) override; + + bool protect_flash(uint32_t len) override { return false; } + bool unprotect_flash() override { return false; } + bool bulk_erase_flash() override { return false; } + + private: + bool program_mem(); + bool pollBusyFlag(bool verbose=false); + uint32_t readStatusReg(); + void displayReadReg(uint32_t dev); + bool EnableISC(uint8_t flash_mode); + bool DisableISC(); + bool flashErase(uint32_t mask); + bool checkStatus(uint32_t val, uint32_t mask) { + return ((readStatusReg() & mask) == val) ? true : false; + } + + bool cmd_class_a(uint8_t cmd, uint8_t *rx, uint32_t len); + bool cmd_class_b(uint8_t cmd, uint8_t *tx, uint32_t len); + bool cmd_class_c(uint8_t cmd); + uint32_t char_array_to_word(uint8_t *in); + FtdiSpi *_spi; +}; +#endif /* SRC_LATTICESSPI_HPP_ */ diff --git a/src/main.cpp b/src/main.cpp index 6e154b3..d873e9b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,7 @@ #include "gowin.hpp" #include "ice40.hpp" #include "lattice.hpp" +#include "latticeSSPI.hpp" #include "libusb_ll.hpp" #include "jtag.hpp" #include "part.hpp"