Initial work on FPGA I2C programmer

This commit is contained in:
tehKaiN 2021-06-11 17:48:11 +01:00
parent 6109a894ee
commit a0f6204c2a
9 changed files with 535 additions and 0 deletions

3
i2c_updater/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.bit
build
.vscode

View File

@ -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)

View File

@ -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;
}

View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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;
}

View File

@ -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_

View File

@ -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;
}