From a0f6204c2adf4f8effdfca82cf367f8c538a936a Mon Sep 17 00:00:00 2001 From: tehKaiN Date: Fri, 11 Jun 2021 17:48:11 +0100 Subject: [PATCH] Initial work on FPGA I2C programmer --- i2c_updater/.gitignore | 3 + i2c_updater/pi-i2c/CMakeLists.txt | 7 ++ i2c_updater/pi-i2c/src/bitstream.cpp | 165 +++++++++++++++++++++++++ i2c_updater/pi-i2c/src/bitstream.hpp | 23 ++++ i2c_updater/pi-i2c/src/endian.hpp | 52 ++++++++ i2c_updater/pi-i2c/src/file.hpp | 35 ++++++ i2c_updater/pi-i2c/src/i2c.cpp | 49 ++++++++ i2c_updater/pi-i2c/src/i2c.hpp | 28 +++++ i2c_updater/pi-i2c/src/main.cpp | 173 +++++++++++++++++++++++++++ 9 files changed, 535 insertions(+) create mode 100644 i2c_updater/.gitignore create mode 100644 i2c_updater/pi-i2c/CMakeLists.txt create mode 100644 i2c_updater/pi-i2c/src/bitstream.cpp create mode 100644 i2c_updater/pi-i2c/src/bitstream.hpp create mode 100644 i2c_updater/pi-i2c/src/endian.hpp create mode 100644 i2c_updater/pi-i2c/src/file.hpp create mode 100644 i2c_updater/pi-i2c/src/i2c.cpp create mode 100644 i2c_updater/pi-i2c/src/i2c.hpp create mode 100644 i2c_updater/pi-i2c/src/main.cpp diff --git a/i2c_updater/.gitignore b/i2c_updater/.gitignore new file mode 100644 index 0000000..0f6eaf8 --- /dev/null +++ b/i2c_updater/.gitignore @@ -0,0 +1,3 @@ +*.bit +build +.vscode diff --git a/i2c_updater/pi-i2c/CMakeLists.txt b/i2c_updater/pi-i2c/CMakeLists.txt new file mode 100644 index 0000000..4eb7e42 --- /dev/null +++ b/i2c_updater/pi-i2c/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.15.2) +project(pistorm_i2c_updater C CXX) + +file(GLOB i2c_updater_sources src/*.cpp src/*.hpp) +add_executable(i2c_updater ${i2c_updater_sources}) +set_property(TARGET i2c_updater PROPERTY CXX_STANDARD 17) +target_compile_options(i2c_updater PRIVATE -Wall) diff --git a/i2c_updater/pi-i2c/src/bitstream.cpp b/i2c_updater/pi-i2c/src/bitstream.cpp new file mode 100644 index 0000000..c86e85f --- /dev/null +++ b/i2c_updater/pi-i2c/src/bitstream.cpp @@ -0,0 +1,165 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "bitstream.hpp" +#include +#include +#include "file.hpp" + +// Based on https://prjtrellis.readthedocs.io/en/latest/architecture/bitstream_format.html , +// Lattice Diamond programmer operation's I2C dump and "ECP5 and ECP5-5G sysCONFIG Usage Guide" + +#define SECTION_ASCII_COMMENTS_BEGIN 0xFF00 +#define SECTION_PREAMBLE_WORD_1 0xFFFF +#define SECTION_PREAMBLE_WORD_2 0xBDB3 + +struct tBitstreamCmd { + enum class tId: uint8_t { + LSC_PROG_CNTRL0 = 0x22, + LSC_RESET_CRC = 0x3B, + LSC_INIT_ADDRESS = 0x46, + ISC_PROGRAM_DONE = 0x5E, + LSC_PROG_INCR_RTI = 0x82, + LSC_PROG_SED_CRC = 0xA2, + LSC_EBR_WRITE = 0xB2, + ISC_PROGRAM_USERCODE = 0xC2, + ISC_PROGRAM_SECURITY = 0xCE, + LSC_VERIFY_ID = 0xE2, + LSC_EBR_ADDRESS = 0xF6, + ISC_NOOP = 0xFF, + }; + + tBitstreamCmd::tId m_eId; + std::array m_Operands; + std::vector m_vData; + + tBitstreamCmd(std::ifstream &FileIn); +}; + +tBitstream::tBitstream(const std::string &FilePath) +{ + std::ifstream FileIn; + FileIn.open(FilePath.c_str(), std::ifstream::in | std::ifstream::binary); + if(!FileIn.good()) { + throw std::runtime_error("Couldn't open file"); + } + + uint16_t uwSectionId; + nFile::readBigEndian(FileIn, uwSectionId); + if(uwSectionId == SECTION_ASCII_COMMENTS_BEGIN) { + // Read comments + std::string Comment; + char c; + while(!FileIn.eof()) { + nFile::readBigEndian(FileIn, c); + if(c == '\xFF') { + break; + } + if(c == '\0') { + m_vComments.push_back(Comment); + Comment.clear(); + } + else { + Comment += c; + } + } + + // Continue reading until we hit preamble + nFile::readBigEndian(FileIn, uwSectionId); + } + + if(uwSectionId != SECTION_PREAMBLE_WORD_1) { + // I want std::format so bad :( + std::stringstream ss; + ss << "Unexpected bitstream section: " << std::setfill('0') << + std::setw(4) << std::ios::hex << uwSectionId; + throw std::runtime_error(ss.str()); + } + + // Read final part of preamble + uint16_t uwPreambleEnd; + nFile::readBigEndian(FileIn, uwPreambleEnd); + if(uwPreambleEnd != SECTION_PREAMBLE_WORD_2) { + // I want std::format so bad :( + std::stringstream ss; + ss << "Unexpected preamble ending" << std::setfill('0') << + std::setw(4) << std::ios::hex << uwPreambleEnd; + throw std::runtime_error(ss.str()); + } + + while(!FileIn.eof()) { + // Read bitstream commands + tBitstreamCmd Cmd(FileIn); + if(FileIn.eof()) { + // Last read haven't been successfull - no more data + break; + } + + // Read extra data, if any + switch(Cmd.m_eId) { + case tBitstreamCmd::tId::ISC_NOOP: + case tBitstreamCmd::tId::LSC_RESET_CRC: + case tBitstreamCmd::tId::LSC_INIT_ADDRESS: + // No additional data + break; + case tBitstreamCmd::tId::LSC_VERIFY_ID: + nFile::readData(FileIn, m_DeviceId.data(), m_DeviceId.size()); + break; + case tBitstreamCmd::tId::LSC_PROG_CNTRL0: + nFile::readBigEndian(FileIn, m_ulCtlReg0); + break; + case tBitstreamCmd::tId::LSC_PROG_INCR_RTI: { + // Store program data + // bool isCrcVerify = Cmd.m_Operands[0] & 0x80; + // bool isCrcAtEnd = !(Cmd.m_Operands[0] & 0x40); + // bool isDummyBits = !(Cmd.m_Operands[0] & 0x20); + // bool isDummyBytes = !(Cmd.m_Operands[0] & 0x10); + auto DummyBytesCount = Cmd.m_Operands[0] & 0xF; + uint16_t uwRowCount = (Cmd.m_Operands[1] << 8) | Cmd.m_Operands[2]; + + for(auto RowIdx = 0; RowIdx < uwRowCount; ++RowIdx) { + // TODO: where to get the size of each data row? From comment? + std::vector vRow; + vRow.resize(26); + uint16_t uwCrc; + nFile::readData(FileIn, vRow.data(), vRow.size()); + nFile::readBigEndian(FileIn, uwCrc); + if(uwCrc == 0) { + // I want std::format so bad :( + std::stringstream ss; + ss << "Empty CRC near pos " << std::hex << FileIn.tellg(); + throw std::runtime_error(ss.str()); + } + m_vProgramData.push_back(vRow); + FileIn.seekg(DummyBytesCount, std::ios::cur); + } + } break; + case tBitstreamCmd::tId::ISC_PROGRAM_USERCODE: + // Read usercode, skip CRC + nFile::readData(FileIn, m_UserCode.data(), m_UserCode.size()); + FileIn.seekg(2, std::ios::cur); + break; + case tBitstreamCmd::tId::ISC_PROGRAM_DONE: + break; + default: { + // I want std::format so bad :( + std::stringstream ss; + ss << "Unhandled bitstream cmd near file pos 0x" << std::hex << + FileIn.tellg() << ": 0x" << std::setw(2) << std::setfill('0') << + int(Cmd.m_eId); + throw std::runtime_error(ss.str()); + } + } + } +} + +tBitstreamCmd::tBitstreamCmd(std::ifstream &FileIn) +{ + uint32_t ulCmdRaw; + nFile::readBigEndian(FileIn, ulCmdRaw); + m_eId = tBitstreamCmd::tId(ulCmdRaw >> 24); + m_Operands[0] = (ulCmdRaw >> 16) & 0xFF; + m_Operands[1] = (ulCmdRaw >> 8) & 0xFF; + m_Operands[2] = (ulCmdRaw >> 0) & 0xFF; +} diff --git a/i2c_updater/pi-i2c/src/bitstream.hpp b/i2c_updater/pi-i2c/src/bitstream.hpp new file mode 100644 index 0000000..e8c0b88 --- /dev/null +++ b/i2c_updater/pi-i2c/src/bitstream.hpp @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _BITSTREAM_HPP_ +#define _BITSTREAM_HPP_ + +#include +#include +#include +#include + +struct tBitstream { + tBitstream(const std::string &FilePath); + + std::array m_DeviceId; + uint32_t m_ulCtlReg0; + std::array m_UserCode; + std::vector m_vComments; + std::vector> m_vProgramData; +}; + +#endif // _BITSTREAM_HPP_ diff --git a/i2c_updater/pi-i2c/src/endian.hpp b/i2c_updater/pi-i2c/src/endian.hpp new file mode 100644 index 0000000..552912f --- /dev/null +++ b/i2c_updater/pi-i2c/src/endian.hpp @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _ENDIAN_HPP_ +#define _ENDIAN_HPP_ + +#include +// #include // we don't have GCC with C++20 on raspi :( + +namespace nEndian { + +template +constexpr t_tVal swapBytes(t_tVal Val) { + t_tVal Out = 0; + for(size_t i = 0; i < sizeof(Val); ++i) { + Out = (Out << 8) | (Val & 0xFF); + Val >>= 8; + } + return Out; +} + +template +constexpr t_tVal littleToNative(const t_tVal &Val) { + // if(std::endian::native == std::endian::big) { + // return swapBytes(Val); + // } + // else { + return Val; + // } +} + +template +constexpr auto nativeToLittle = littleToNative; + +template +constexpr t_tVal bigToNative(const t_tVal &Val) { + // if(std::endian::native == std::endian::big) { + // return Val; + // } + // else { + return swapBytes(Val); + // } +} + +template +constexpr auto nativeToBig = bigToNative; + + +} // namespace nEndian + +#endif // _ENDIAN_HPP_ diff --git a/i2c_updater/pi-i2c/src/file.hpp b/i2c_updater/pi-i2c/src/file.hpp new file mode 100644 index 0000000..5ee7b54 --- /dev/null +++ b/i2c_updater/pi-i2c/src/file.hpp @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _FILE_HPP_ +#define _FILE_HPP_ + +#include +#include "endian.hpp" + +namespace nFile { + +template +t_tStream readLittleEndian(t_tStream &File, t_tData &Data) +{ + File.read(reinterpret_cast(&Data), sizeof(Data)); + Data = nEndian::littleToNative(Data); +} + +template +void readBigEndian(t_tStream &File, t_tData &Data) +{ + File.read(reinterpret_cast(&Data), sizeof(Data)); + Data = nEndian::bigToNative(Data); +} + +template +void readData(t_tStream &File, t_tData *pData, size_t ElementCount) +{ + File.read(reinterpret_cast(pData), ElementCount * sizeof(*pData)); +} + +} // namespace nFile + +#endif // _FILE_HPP_ diff --git a/i2c_updater/pi-i2c/src/i2c.cpp b/i2c_updater/pi-i2c/src/i2c.cpp new file mode 100644 index 0000000..e80396d --- /dev/null +++ b/i2c_updater/pi-i2c/src/i2c.cpp @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "i2c.hpp" + +#include +#include +#include +#include +#include + +tI2c::tI2c(const std::string &Port) +{ + // Open the I2C bus file handle + m_I2cHandle = open(Port.c_str(), O_RDWR); + if(m_I2cHandle < 0) { + // TODO: check errno to see what went wrong + throw std::runtime_error("Can't open the i2c bus\n"); + } +} + +bool tI2c::write(uint8_t ubAddr, const std::vector &vData) { + if(ioctl(m_I2cHandle, I2C_SLAVE, ubAddr) < 0) { + // NOTE: check errno to see what went wrong + return false; + } + + auto BytesWritten = ::write(m_I2cHandle, vData.data(), vData.size()); + if(BytesWritten != ssize_t(vData.size())) { + return false; + } + + return true; +} + +bool tI2c::read(uint8_t ubAddr, uint8_t *pDest, uint32_t ulReadSize) { + if(ioctl(m_I2cHandle, I2C_SLAVE, ubAddr) < 0) { + // NOTE: check errno to see what went wrong + return false; + } + + auto BytesRead = ::read(m_I2cHandle, pDest, ulReadSize); + if(BytesRead != ssize_t(ulReadSize)) { + return false; + } + + return true; +} diff --git a/i2c_updater/pi-i2c/src/i2c.hpp b/i2c_updater/pi-i2c/src/i2c.hpp new file mode 100644 index 0000000..f190ace --- /dev/null +++ b/i2c_updater/pi-i2c/src/i2c.hpp @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _I2C_HPP_ +#define _I2C_HPP_ + +#include +#include + +class tI2c { +public: + tI2c(const std::string &Port); + + bool write(uint8_t ubAddr, const std::vector &vData); + + bool read(uint8_t ubAddr, uint8_t *pDest, uint32_t ulReadSize); + + template + bool read(uint8_t ubAddr, t_tContainer &Cont) { + return read(ubAddr, Cont.data(), Cont.size()); + } + +private: + int m_I2cHandle; +}; + +#endif // _I2C_HPP_ diff --git a/i2c_updater/pi-i2c/src/main.cpp b/i2c_updater/pi-i2c/src/main.cpp new file mode 100644 index 0000000..e25c1b8 --- /dev/null +++ b/i2c_updater/pi-i2c/src/main.cpp @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include +#include "bitstream.hpp" +#include "i2c.hpp" +#include "endian.hpp" + +#define STATUS_BIT_BUSY (1 << 12) +#define STATUS_BIT_ERROR (1 << 13) + +static bool waitForNotBusy(tI2c &I2c, uint8_t ubFpgaAddr) +{ + using namespace std::chrono_literals; + + uint32_t ulStatus; + do { + std::this_thread::sleep_for(10ms); + I2c.write(ubFpgaAddr, {0x3C, 0x00, 0x00 , 0x00}); + bool isRead = I2c.read( + ubFpgaAddr, reinterpret_cast(&ulStatus), sizeof(ulStatus) + ); + if(!isRead) { + return false; + } + ulStatus = nEndian::bigToNative(ulStatus); + if(ulStatus & STATUS_BIT_ERROR) { + return false; + } + } while(ulStatus & STATUS_BIT_BUSY); + return true; +} + +static uint8_t reverseByte(uint8_t ubData) { + // https://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64BitsDiv + ubData = (ubData * 0x0202020202ULL & 0x010884422010ULL) % 1023; + return ubData; +} + +int main(int lArgCount, const char *pArgs[]) +{ + using namespace std::chrono_literals; + + if(lArgCount < 4) { + printf("Usage:\n\t%s i2cPort i2cSlaveAddrHex /path/to/cfg.bit\n", pArgs[0]); + printf("e.g.:\n\t%s /dev/i2c-1 5a file.bit\n", pArgs[0]); + return EXIT_FAILURE; + } + + std::optional Bitstream; + try { + Bitstream = tBitstream(pArgs[3]); + } + catch(const std::exception &Exc) { + printf( + "ERR: bitstream '%s' read fail: '%s'\n", pArgs[3], Exc.what() + ); + return EXIT_FAILURE; + } + + // Display some info about bitstream + printf("Successfully read bitstream:\n"); + for(const auto &Comment: Bitstream->m_vComments) { + printf("\t%s\n", Comment.c_str()); + } + + // Read the I2C address of the FPGA + auto FpgaAddr = std::stoi(pArgs[2], 0, 16); + + // Initialize I2C port + std::optional I2c; + try { + I2c = tI2c(pArgs[1]); + } + catch(const std::exception &Exc) { + printf( + "ERR: i2c port '%s' init fail: '%s'\n", pArgs[1], Exc.what() + ); + return EXIT_FAILURE; + } + + printf("Resetting FPGA...\n"); + // TODO: CRESET:=0 + std::this_thread::sleep_for(1s); + I2c->write(FpgaAddr, {0xA4, 0xC6, 0xF4, 0x8A}); + // TODO: CRESET:=1 + std::this_thread::sleep_for(10ms); + + // 1. Read ID (E0) + printf("Checking device id...\n"); + I2c->write(FpgaAddr, {0xE0, 0x00, 0x00, 0x00}); + std::array DevId; + bool isRead = I2c->read(FpgaAddr, DevId); + if(!isRead) { + printf("ERR: Can't read data from I2C device\n"); + return EXIT_FAILURE; + } + if(DevId != Bitstream->m_DeviceId) { + printf( + "ERR: DevId mismatch! Dev: %02X %02X %02X %02X, " + ".bit: %02X, %02X, %02X, %02X\n", + DevId[0], DevId[1], DevId[2], DevId[3], + Bitstream->m_DeviceId[0], Bitstream->m_DeviceId[1], + Bitstream->m_DeviceId[2], Bitstream->m_DeviceId[3] + ); + return EXIT_FAILURE; + } + + // 2. Enable configuration interface (C6) + printf("Initiating programming...\n"); + I2c->write(FpgaAddr, {0xC6, 0x00, 0x00, 0x00}); + std::this_thread::sleep_for(1ms); + + // 3. Read status register (3C) and wait for busy bit go to 0 + if(!waitForNotBusy(*I2c, FpgaAddr)) { + printf("ERR: status register has error bit set!\n"); + return EXIT_FAILURE; + } + + // 4. LSC_INIT_ADDRESS (46) + I2c->write(FpgaAddr, {0x46, 0x00, 0x00 , 0x00}); + std::this_thread::sleep_for(100ms); + + // 5. Program SRAM parts (82) + uint32_t lRowIdx; + for(const auto &Row: Bitstream->m_vProgramData) { + printf( + "\rProgramming row %5u/%5u...", + ++lRowIdx, Bitstream->m_vProgramData.size() + ); + fflush(stdout); + // Compose and send next SRAM packet + std::vector Packet = {0x82, 0x21, 0x00, 0x00}; + for(const auto &RowByte: Row) { + Packet.push_back(RowByte); + } + I2c->write(FpgaAddr, Packet); + std::this_thread::sleep_for(100us); + + // Some kind of dummy packet + I2c->write(FpgaAddr, {0x00, 0x00, 0x00, 0x00}); + std::this_thread::sleep_for(100us); + } + printf("\n"); + + // 6. Program usercode (C2) + printf("Programming usercode...\n"); + I2c->write(FpgaAddr, { + 0x46, 0x00, 0x00 , 0x00, + reverseByte(Bitstream->m_UserCode[3]), reverseByte(Bitstream->m_UserCode[2]), + reverseByte(Bitstream->m_UserCode[1]), reverseByte(Bitstream->m_UserCode[0]) + }); + + // 7. Program done (5E) + printf("Finalizing programming...\n"); + I2c->write(FpgaAddr, {0x5E, 0x00, 0x00 , 0x00}); + + // 8. Read status register (3C) and wait for busy bit go to 0 + if(!waitForNotBusy(*I2c, FpgaAddr)) { + printf("ERR: status register has error bit set!\n"); + return EXIT_FAILURE; + } + + // 9. Exit programming mode (26) + I2c->write(FpgaAddr, {0x26, 0x00, 0x00 , 0x00}); + + printf("All done!\n"); + return EXIT_SUCCESS; +} -- 2.39.2