From a97eaf3e6608ba5bfa598cb398bf835b67a09dda Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Sun, 27 Nov 2022 12:41:17 +0100 Subject: [PATCH] src/iceVWireless: first draft Signed-off-by: Gwenhael Goavec-Merou --- CMakeLists.txt | 4 + src/iceVWireless.cpp | 241 +++++++++++++++++++++++++++++++++++++++++++ src/iceVWireless.hpp | 174 +++++++++++++++++++++++++++++++ src/main.cpp | 31 +++++- 4 files changed, 448 insertions(+), 2 deletions(-) create mode 100644 src/iceVWireless.cpp create mode 100644 src/iceVWireless.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 32b0bda..67b58e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -209,6 +209,10 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") # uart_ll only tested on Linux target_sources(openFPGALoader PRIVATE src/uart_ll.cpp) list (APPEND OPENFPGALOADER_HEADERS src/uart_ll.hpp) + # iceV Wireless only tested on Linux + target_sources(openFPGALoader PRIVATE src/iceVWireless.cpp) + list (APPEND OPENFPGALOADER_HEADERS src/iceVWireless.hpp) + add_definitions(-DENABLE_ICEVWIRELESS=1) endif() if (ENABLE_UDEV) diff --git a/src/iceVWireless.cpp b/src/iceVWireless.cpp new file mode 100644 index 0000000..597576b --- /dev/null +++ b/src/iceVWireless.cpp @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2022 Gwenhael Goavec-Merou + */ + +#include "iceVWireless.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "display.hpp" +#include "uart_ll.hpp" +#include "rawParser.hpp" + +IceV_Wireless::IceV_Wireless(const std::string &device, + const std::string &filename, Device::prog_type_t prg_type) + :uart(device, 9600, 8, false), _filename(filename) +{ + _mode = (prg_type == Device::WR_SRAM) ? PRG_RAM : PRG_SPIFFS; + if (!uart.flush()) + throw std::runtime_error("Flush serial interface failed"); + if (!read_vbat()) + throw std::runtime_error("Fail to read vbat"); + if (!read_info()) + throw std::runtime_error("Fail to read info"); +} + +IceV_Wireless::~IceV_Wireless() {} + +bool IceV_Wireless::write_cmd(uint8_t cmd, + uint32_t reg, size_t regsize) +{ + char payload[4] = { + static_cast((reg >> 0) & 0xFF), + static_cast((reg >> 8) & 0xFF), + static_cast((reg >> 16) & 0xFF), + static_cast((reg >> 24) & 0xFF)}; + return write_cmd(cmd, payload, regsize); +} + +bool IceV_Wireless::write_cmd(uint8_t cmd, + const char *reg, uint32_t regsize) +{ + int kBuffSize = 4 + 4 + regsize; + uint8_t buffer[kBuffSize]; + int pos = 0; + memset(buffer, '\0', kBuffSize); + /* cmd */ + buffer[pos++] = 0xE0 + (cmd & 0x0F); + buffer[pos++] = 0xBE; + buffer[pos++] = 0xFE; + buffer[pos++] = 0xCA; + /* register size */ + buffer[pos++] = (regsize >> 0) & 0xFF; + buffer[pos++] = (regsize >> 8) & 0xFF; + buffer[pos++] = (regsize >> 16) & 0xFF; + buffer[pos++] = (regsize >> 24) & 0xFF; + /* register/payload */ + memcpy(buffer+pos, reg, regsize); + /* write buffer */ + if (uart.write(buffer, kBuffSize) != kBuffSize) { + printError("Error: uart write failed"); + return false; + } + return true; +} + +int IceV_Wireless::read_tokens(std::vector *rx) +{ + std::string payload; + /* read message from ESP32-C3 */ + int ret = uart.read_until(&payload, '\n'); + if (ret < 0) + return 32; + + std::regex regex{R"([\s]+)"}; // split on space + std::sregex_token_iterator it{payload.begin(), payload.end(), regex, -1}; + std::vector words{it, {}}; + + /* decode / check */ + uint16_t errorCode; + bool found = false; + for (size_t i = 0; i < words.size(); i++){ + if (words[i] == "RX") { + if (i + 2 <= words.size()) { + found = true; + errorCode = stoul(words[i+1], nullptr, 16); + rx->resize(words.size() - i - 2); + std::copy(words.begin()+i+2, words.end(), rx->begin()); + break; + } + } + } + if (!found) + return 64; + return errorCode; +} + +int IceV_Wireless::read_data(std::string *rx) +{ + std::vector rxv; + int errorCode = read_tokens(&rxv); + if (errorCode != 0) + return errorCode; + + rx->resize(rxv[0].size()); + std::copy(rxv[0].begin(), rxv[0].end(), + rx->begin()); + return errorCode; +} + +int IceV_Wireless::wr_rd(uint8_t cmd, uint32_t reg, uint32_t regsize, + std::vector *rx) +{ + write_cmd(cmd, reg, regsize); + return read_tokens(rx); +} + +int IceV_Wireless::wr_rd(uint8_t cmd, uint32_t reg, uint32_t regsize, + std::string *rx) +{ + write_cmd(cmd, reg, regsize); + return read_data(rx); +} + +int IceV_Wireless::wr_rd(uint8_t cmd, const std::string ®, + size_t regsize, std::string *rx) +{ + write_cmd(cmd, reg.c_str(), regsize); + return read_data(rx); +} + +bool IceV_Wireless::read_vbat() +{ + std::string rx; + uint32_t val = wr_rd(READ_VBAT, 0, 4, &rx); + if (val != 0) + return false; + val = stoul(rx, nullptr, 16); + printInfo("Vbat = " + std::to_string(val) + " mV"); + return true; +} + +bool IceV_Wireless::read_info() +{ + std::vector rx; + uint32_t val = wr_rd(READ_INFO, 0, 4, &rx); + if (val != 0) + return false; + printInfo("info: version: " + rx[0] + " ipaddr: " + + rx[1]); + + return true; +} + +// send a file for direct load to FPGA or write to SPIFFS +bool IceV_Wireless::send_file(uint8_t cmd, const std::string &filename) +{ + std::string rx; + std::string tx; + // open file + + printInfo("Open file " + filename + " ", false); + RawParser _bit(filename, false); + printSuccess("DONE"); + + printInfo("Parse file ", false); + if (_bit.parse() == EXIT_FAILURE) { + printError("FAIL"); + throw std::runtime_error("Failed to parse bitstream"); + } + printSuccess("DONE"); + uint32_t size = _bit.getLength() / 8; + uint8_t *data = _bit.getData(); + tx.append(reinterpret_cast(data), size); + + // send to the C3 over usb + + uint32_t ret = wr_rd(cmd, tx, size, &rx); + if (ret != 0) { + printError("FAIL"); + throw std::runtime_error("Error " + std::to_string(ret)); + } + printSuccess("DONE"); + + return (ret == 0) ? true : false; +} + +bool IceV_Wireless::read_reg(uint32_t reg) +{ + std::string rx; + uint32_t val = wr_rd(READ_REG, reg, 4, &rx); + if (val != 0) + return false; + printf("Read reg %u = %4x\n", reg, val); + return true; +} + +/* TODO: write reg */ +bool IceV_Wireless::write_reg(uint32_t reg, uint32_t data) +{ + std::string rx; + std::string tx; + for (int i = 0; i < 4; i++) + tx.append(1, static_cast(reg >> (i * 8))); + for (int i = 0; i < 4; i++) + tx.append(i, static_cast(data >> (i * 8))); + uint32_t val = wr_rd(WRITE_REG, tx, 8, &rx); + return (val == 0) ? true : false; +} + +/* TODO: psram write */ +/* TODO: psram read */ +/* TODO: psram init */ +/* TODO: send cred */ +bool IceV_Wireless::send_cred(uint8_t cred_type, std::string value) +{ + std::string rep; + value+='\0'; + /* 3 -> SSID + * 4 -> PASS + */ + uint32_t val = wr_rd((uint8_t)(SEND_CRED + (cred_type & 0x01)), + value, static_cast(value.size()), &rep); + return val == 0; +} + +// load config from SPIFFS (0: default, 1: spi pass) +bool IceV_Wireless::load_cfg(uint32_t reg) +{ + std::string rep; + uint32_t val = wr_rd(LOAD_CFG, reg, 4, &rep); + return val == 0; +} diff --git a/src/iceVWireless.hpp b/src/iceVWireless.hpp new file mode 100644 index 0000000..a9a1fe6 --- /dev/null +++ b/src/iceVWireless.hpp @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2022 Gwenhael Goavec-Merou + */ + +#ifndef SRC_ICEVWIRELESS_HPP_ +#define SRC_ICEVWIRELESS_HPP_ + +#include + +#include +#include + +#include "device.hpp" +#include "uart_ll.hpp" + +/*! + * \file iceVWireless + * \class IceV_Wireless + * \brief iceV Wireless protocol implementation + * \author Gwenhael Goavec-Merou + */ +class IceV_Wireless { + public: + /*! + * \brief constructor + * \param[in] device: /dev/xxx + * \param[in] filename: bitstream file name + * \param[in] prg_type: write / load + */ + IceV_Wireless(const std::string &device, + const std::string &filename, Device::prog_type_t prg_type); + + ~IceV_Wireless(); + + /* iceV Wireless commands */ + enum cmd_lst { + READ_REG = 0, + WRITE_REG = 1, + READ_VBAT = 2, + SEND_CRED = 3, + /* 4: SEND_CRED password */ + READ_INFO = 5, + LOAD_CFG = 6, + PSRAM_INIT = 10, + PSRAM_READ = 11, + PSRAM_WRITE = 12, + PRG_SPIFFS = 14, + PRG_RAM = 15 + }; + + /*! + * \brief read and display ESP32-C3 ADC (battery voltage) + * \return false is something is wrong + */ + bool read_vbat(); + + /*! + * \brief read and display firmware version and IP address + * \return false is something is wrong + */ + bool read_info(); + + /*! + * \brief read the specified register + * \param[in] reg: register address + * \return false is something is wrong + */ + bool read_reg(uint32_t reg); + + /*! + * \brief write the specified register + * \param[in] reg: register address + * \param[in] data: value to write + * \return false is something is wrong + */ + bool write_reg(uint32_t reg, uint32_t data); + + /*! + * \brief write wireless SSID or pass + * \param[in] reg: 0 -> SSID, 1 -> password + * \param[in] value: string to send + * \return false if something went wrong + */ + bool send_cred(uint8_t reg, std::string value); + + /*! + * \brief send content of specified file to SPIFFS or ice40 RAM + * \param[in] cmd: destination code (14 -> SPIFFS, 15 -> RAM) + * \param[in] filename: bitstream path + * \return false if something went wrong + */ + bool send_file(uint8_t cmd, const std::string &filename); + bool load_cfg(uint32_t reg); + + /*! + * \brief send bitstream to the device (SPIFFS or RAM) + */ + void program() {send_file(_mode, _filename);} + + private: + /*! + * \brief write command + payload, followed by a read_tokens call + * \param[in] cmd: command + * \param[in] reg: payload (uint32_t) + * \param[in] regsize: payload len + * \param[out] rx: iceV Wireless answer (vector) + * \return error/status code + */ + int wr_rd(uint8_t cmd, uint32_t reg, uint32_t regsize, + std::vector *rx); + + /*! + * \brief write command + payload, followed by a read_data call + * \param[in] cmd: command + * \param[in] reg: payload (uint32_t) + * \param[in] regsize: payload len + * \param[out] rx: iceV Wireless answer (scalar) + * \return error/status code + */ + int wr_rd(uint8_t cmd, uint32_t reg, uint32_t regsize, + std::string *rx); + + /*! + * \brief write command + payload, followed by a read_data call + * \param[in] cmd: command + * \param[in] reg: payload (string) + * \param[in] regsize: payload len + * \param[out] rx: iceV Wireless answer (scalar) + * \return error/status code + */ + int wr_rd(uint8_t cmd, const std::string ®, size_t regsize, + std::string *rx); + + /*! + * \brief build and write a sequence (MAGIC + size + payload) + * \param[in] cmd: command + * \param[in] reg: payload (char *) + * \param[in] regsize: payload len + * \return false if issue with UART, true otherwise + */ + bool write_cmd(uint8_t cmd, const char *reg, uint32_t regsize); + + /*! + * \brief build and write a sequence (MAGIC + size + payload) + * \param[in] cmd: command + * \param[in] reg: payload (uint32_t) + * \param[in] regsize: payload len + * \return false if issue with UART, true otherwise + */ + bool write_cmd(uint8_t cmd, uint32_t reg, size_t regsize); + + /*! + * \brief read full ice4VWireless message, split sequence + * returns error and fill vector with answer + * \param[out] rx: ice4VWireless splitted answer + * \return status code + */ + int read_tokens(std::vector *rx); + + /*! + * \brief similar to read_tokens but fill string with first + * answer part + * \param[out] rx: ice4VWireless answer + * \return status code + */ + int read_data(std::string *rx); + + Uart_ll uart; /*! lowlevel uart access */ + std::string _filename; /*! bitstream name */ + cmd_lst _mode; /*! SPIFFS or RAM */ +}; + +#endif // SRC_ICEVWIRELESS_HPP_ diff --git a/src/main.cpp b/src/main.cpp index 6ba74f0..ee7657d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,9 @@ #include "ftdispi.hpp" #include "gowin.hpp" #include "ice40.hpp" +#if ENABLE_ICEVWIRELESS +#include "iceVWireless.hpp" +#endif #include "lattice.hpp" #include "libusb_ll.hpp" #include "jtag.hpp" @@ -63,6 +66,7 @@ struct arguments { bool is_list_command; bool spi; bool dfu; + bool iceVWireless; string file_type; string fpga_part; string bridge_path; @@ -109,8 +113,8 @@ int main(int argc, char **argv) /* command line args. */ 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, "", "", "", "", + /* spi dfu iceVWireless file_type fpga_part bridge_path probe_firmware */ + false, false, false, "", "", "", "", /* index_chain file_size target_flash external_flash altsetting */ -1, 0, "primary", false, -1, /* vid, pid, index bus_addr, device_addr */ @@ -139,6 +143,25 @@ int main(int argc, char **argv) if (args.prg_type == Device::WR_FLASH) cout << "write to flash" << endl; +#ifdef ENABLE_ICEVWIRELESS + /* ------------------- */ + /* iCE V Wireless */ + /* ------------------- */ + if (args.iceVWireless) { + /* if no instruction from user -> select load */ + if (args.prg_type == Device::PRG_NONE) + args.prg_type = Device::WR_SRAM; + try { + IceV_Wireless ice(args.device, args.bit_file, args.prg_type); + ice.program(); + } catch (std::exception &e) { + printError("IceV Wirelesss failed:\n\t" + string(e.what())); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } +#endif + if (args.board[0] != '-') { if (board_list.find(args.board) != board_list.end()) { board = &(board_list[args.board]); @@ -749,6 +772,10 @@ int parse_opt(int argc, char **argv, struct arguments *args, ("freq", "jtag frequency (Hz)", cxxopts::value(freqo)) ("f,write-flash", "write bitstream in flash (default: false)") +#if ENABLE_ICEVWIRELESS + ("iceV_wireless", "iceV wireless protocol", + cxxopts::value(args->iceVWireless)) +#endif ("index-chain", "device index in JTAG-chain", cxxopts::value(args->index_chain)) ("ip", "IP address (only for XVC client)",