]> git.sesse.net Git - pistorm/commitdiff
Initial work on FPGA I2C programmer
authortehKaiN <kain@piwnica.ws>
Fri, 11 Jun 2021 16:48:11 +0000 (17:48 +0100)
committertehKaiN <kain@piwnica.ws>
Fri, 11 Jun 2021 16:48:11 +0000 (17:48 +0100)
i2c_updater/.gitignore [new file with mode: 0644]
i2c_updater/pi-i2c/CMakeLists.txt [new file with mode: 0644]
i2c_updater/pi-i2c/src/bitstream.cpp [new file with mode: 0644]
i2c_updater/pi-i2c/src/bitstream.hpp [new file with mode: 0644]
i2c_updater/pi-i2c/src/endian.hpp [new file with mode: 0644]
i2c_updater/pi-i2c/src/file.hpp [new file with mode: 0644]
i2c_updater/pi-i2c/src/i2c.cpp [new file with mode: 0644]
i2c_updater/pi-i2c/src/i2c.hpp [new file with mode: 0644]
i2c_updater/pi-i2c/src/main.cpp [new file with mode: 0644]

diff --git a/i2c_updater/.gitignore b/i2c_updater/.gitignore
new file mode 100644 (file)
index 0000000..0f6eaf8
--- /dev/null
@@ -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 (file)
index 0000000..4eb7e42
--- /dev/null
@@ -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 (file)
index 0000000..c86e85f
--- /dev/null
@@ -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 <sstream>
+#include <iomanip>
+#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<uint8_t, 3> m_Operands;
+       std::vector<uint8_t> 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<uint8_t> 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 (file)
index 0000000..e8c0b88
--- /dev/null
@@ -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 <string>
+#include <vector>
+#include <array>
+#include <fstream>
+
+struct tBitstream {
+       tBitstream(const std::string &FilePath);
+
+       std::array<uint8_t, 4> m_DeviceId;
+       uint32_t m_ulCtlReg0;
+       std::array<uint8_t, 4> m_UserCode;
+       std::vector<std::string> m_vComments;
+       std::vector<std::vector<uint8_t>> 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 (file)
index 0000000..552912f
--- /dev/null
@@ -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 <cstdint>
+// #include <bit> // we don't have GCC with C++20 on raspi :(
+
+namespace nEndian {
+
+template<typename t_tVal>
+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<typename t_tVal>
+constexpr t_tVal littleToNative(const t_tVal &Val) {
+       // if(std::endian::native == std::endian::big) {
+       //      return swapBytes(Val);
+       // }
+       // else {
+               return Val;
+       // }
+}
+
+template<typename t_tVal>
+constexpr auto nativeToLittle = littleToNative<t_tVal>;
+
+template<typename t_tVal>
+constexpr t_tVal bigToNative(const t_tVal &Val) {
+       // if(std::endian::native == std::endian::big) {
+       //      return Val;
+       // }
+       // else {
+               return swapBytes(Val);
+       // }
+}
+
+template<typename t_tVal>
+constexpr auto nativeToBig = bigToNative<t_tVal>;
+
+
+} // 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 (file)
index 0000000..5ee7b54
--- /dev/null
@@ -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 <fstream>
+#include "endian.hpp"
+
+namespace nFile {
+
+template<typename t_tStream, typename t_tData>
+t_tStream readLittleEndian(t_tStream &File, t_tData &Data)
+{
+       File.read(reinterpret_cast<char*>(&Data), sizeof(Data));
+       Data = nEndian::littleToNative(Data);
+}
+
+template<typename t_tStream, typename t_tData>
+void readBigEndian(t_tStream &File, t_tData &Data)
+{
+       File.read(reinterpret_cast<char*>(&Data), sizeof(Data));
+       Data = nEndian::bigToNative(Data);
+}
+
+template<typename t_tStream, typename t_tData>
+void readData(t_tStream &File, t_tData *pData, size_t ElementCount)
+{
+       File.read(reinterpret_cast<char*>(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 (file)
index 0000000..e80396d
--- /dev/null
@@ -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 <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/i2c-dev.h>
+#include <stdexcept>
+
+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<uint8_t> &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 (file)
index 0000000..f190ace
--- /dev/null
@@ -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 <string>
+#include <vector>
+
+class tI2c {
+public:
+       tI2c(const std::string &Port);
+
+       bool write(uint8_t ubAddr, const std::vector<uint8_t> &vData);
+
+       bool read(uint8_t ubAddr, uint8_t *pDest, uint32_t ulReadSize);
+
+       template <typename t_tContainer>
+       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 (file)
index 0000000..e25c1b8
--- /dev/null
@@ -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 <cstdio>
+#include <string>
+#include <optional>
+#include <thread>
+#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<uint8_t*>(&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<tBitstream> 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<tI2c> 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<uint8_t, 4> 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<uint8_t> 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;
+}