From c84aae2e68a8ef77778f144e8931250c354fc21d Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Thu, 7 Sep 2023 00:19:31 -0400 Subject: [PATCH 1/2] Get minimal-working JED generation. --- libtrellis/tools/ecppack.cpp | 113 +++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/libtrellis/tools/ecppack.cpp b/libtrellis/tools/ecppack.cpp index ab560e02..5e2bb973 100644 --- a/libtrellis/tools/ecppack.cpp +++ b/libtrellis/tools/ecppack.cpp @@ -29,6 +29,28 @@ uint32_t convert_hexstring(std::string value_str) return uint32_t(strtoul(value_str.c_str(), nullptr, 0)); } +uint16_t calc_checksum(std::string bytes) +{ + uint16_t checksum = 0; + + for(auto &c: bytes) + { + checksum += c; + } + + return checksum; +} + +uint32_t get_num_config_fuses(Trellis::ChipInfo &ci) { + if(ci.name == "LCMXO2-4000") { + return (5758 + 767 + 1)*128; // UFM is 0 bytes after end of CFG, 1 dummy page at end. + } if(ci.name == "LCMXO2-7000") { + return (9211 + 1 + 2046 + 2)*128; // UFM is 16 bytes after end of CFG, 2 dummy pages at end. + } else { + throw runtime_error(fmt("Can not extract number of config fuses from FPGA family " << ci.name)); + } +} + int main(int argc, char *argv[]) { using namespace Trellis; @@ -45,6 +67,7 @@ int main(int argc, char *argv[]) options.add_options()("freq", po::value(), "config frequency in MHz"); options.add_options()("svf", po::value(), "output SVF file"); options.add_options()("svf-rowsize", po::value(), "SVF row size in bits (default 8000)"); + options.add_options()("jed", po::value(), "output JED file"); options.add_options()("compress", "compress bitstream to reduce size"); options.add_options()("spimode", po::value(), "SPI Mode to use (fast-read, dual-spi, qspi)"); options.add_options()("background", "enable background reconfiguration in bitstream"); @@ -338,5 +361,95 @@ int main(int argc, char *argv[]) } } + if (vm.count("jed")) { + // Create JTAG bitstream without SPI flash related settings, as these + // seem to confuse the chip sometimes when configuring over JTAG + if (!bitopts.empty() && !(bitopts.size() == 1 && bitopts.count("compress"))) { + bitopts.erase("spimode"); + bitopts.erase("freq"); + b = Bitstream::serialise_chip(c, bitopts); + } + + vector bitstream = b.get_bytes(); + ofstream jed_file(vm["jed"].as(), ios_base::binary); + uint16_t checksum = 0; + uint16_t full_checksum = 0; + + jed_file << "\x02*" << endl; // STX plus "design specification" (not filled in). + full_checksum += calc_checksum("\x02*\n"); + // jed_file << "\x03" << hex << uppercase << setw(4) << full_checksum << endl; + // return 0; + + ostringstream fusecnt_field; + uint32_t fusecnt; + try { + fusecnt = get_num_config_fuses(c.info); + } catch (runtime_error &e) { + cerr << "Failed to extract JED file size: " << e.what() << endl; + return 1; + } + + fusecnt_field << "QF" << fusecnt << '*' << endl; + full_checksum += calc_checksum(fusecnt_field.str()); + jed_file << fusecnt_field.str(); + + jed_file << "G0*" << endl; // Security fuse not supported yet. + full_checksum += calc_checksum("G0*\n"); + jed_file << "F0*" << endl; // Default fuse value. + full_checksum += calc_checksum("F0*\n"); + jed_file << "L0" << endl; + full_checksum += calc_checksum("L0\n"); + + + size_t i = 0; + while(i < fusecnt/8) { + if(i < bitstream.size()) { + size_t len = min(size_t(16), bitstream.size() - i); + + for (unsigned int j = 0; j < len; j++) { + uint8_t byte = uint8_t(bitstream[j + i]); + checksum += reverse_byte(byte); + full_checksum += calc_checksum(std::bitset<8>{byte}.to_string()); + jed_file << std::bitset<8>{byte}; + } + + // Pad to 128 bits if at end of bitstream. + if(len < 16) { + for(unsigned int k = 0; k < 16 - len; k++) { + uint8_t byte = 0; + checksum += reverse_byte(byte); + full_checksum += calc_checksum(std::bitset<8>{byte}.to_string()); + jed_file << std::bitset<8>{byte}; + } + } + + + } else { + // Fill in remaining 128-bit rows + for(unsigned int j = 0; j < 16; j++) { + uint8_t byte = 0; + checksum += reverse_byte(byte); + full_checksum += calc_checksum(std::bitset<8>{byte}.to_string()); + jed_file << std::bitset<8>{byte}; + } + } + + jed_file << endl; + full_checksum += calc_checksum("\n"); + i += 16; + } + + jed_file << "*" << endl; + full_checksum += calc_checksum("*\n"); + + ostringstream oss; + oss << "C" << hex << uppercase << setfill('0') << setw(4) << checksum << '*' << endl; + full_checksum += calc_checksum(oss.str()); + jed_file << oss.str(); + + full_checksum += calc_checksum("\x03"); + jed_file << "\x03" << hex << uppercase << setw(4) << setfill('0') << full_checksum << endl; + } + return 0; } From 839f9f51f61a09bb7397a122333bc212521543c5 Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Fri, 8 Sep 2023 18:19:11 -0400 Subject: [PATCH 2/2] Improve robustness of ecppack JED file generator. Allow NOTEs to be generated. --- libtrellis/tools/ecppack.cpp | 68 +++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/libtrellis/tools/ecppack.cpp b/libtrellis/tools/ecppack.cpp index 5e2bb973..73f71e6f 100644 --- a/libtrellis/tools/ecppack.cpp +++ b/libtrellis/tools/ecppack.cpp @@ -42,15 +42,33 @@ uint16_t calc_checksum(std::string bytes) } uint32_t get_num_config_fuses(Trellis::ChipInfo &ci) { - if(ci.name == "LCMXO2-4000") { + if(ci.name == "LCMXO2-256") { + return (575 + 0 + 1)*128; // No UFM, 1 dummy page at end. + } else if(ci.name == "LCMXO2-640") { + return (1151 + 191 + 1)*128; // UFM is 0 bytes after end of CFG, 1 dummy page at end. + } else if(ci.name == "LCMXO2-1200" || ci.name == "LCMXO2-640U") { + return (2175 + 511 + 1)*128; // UFM is 0 bytes after end of CFG, 1 dummy page at end. + } else if(ci.name == "LCMXO2-2000" || ci.name == "LCMXO2-1200U") { + return (3198 + 639 + 1)*128; // UFM is 0 bytes after end of CFG, 1 dummy page at end. + } else if(ci.name == "LCMXO2-4000" || ci.name == "LCMXO2-2000U") { return (5758 + 767 + 1)*128; // UFM is 0 bytes after end of CFG, 1 dummy page at end. - } if(ci.name == "LCMXO2-7000") { + } else if(ci.name == "LCMXO2-7000") { return (9211 + 1 + 2046 + 2)*128; // UFM is 16 bytes after end of CFG, 2 dummy pages at end. } else { throw runtime_error(fmt("Can not extract number of config fuses from FPGA family " << ci.name)); } } +int num_digits(uint32_t num) { + int count = 0; + do { + num /= 10; + count++; + } while(num != 0); + + return count; +} + int main(int argc, char *argv[]) { using namespace Trellis; @@ -68,6 +86,7 @@ int main(int argc, char *argv[]) options.add_options()("svf", po::value(), "output SVF file"); options.add_options()("svf-rowsize", po::value(), "SVF row size in bits (default 8000)"); options.add_options()("jed", po::value(), "output JED file"); + options.add_options()("jed-note", po::value>(), "emit NOTE field in JED file"); options.add_options()("compress", "compress bitstream to reduce size"); options.add_options()("spimode", po::value(), "SPI Mode to use (fast-read, dual-spi, qspi)"); options.add_options()("background", "enable background reconfiguration in bitstream"); @@ -377,8 +396,15 @@ int main(int argc, char *argv[]) jed_file << "\x02*" << endl; // STX plus "design specification" (not filled in). full_checksum += calc_checksum("\x02*\n"); - // jed_file << "\x03" << hex << uppercase << setw(4) << full_checksum << endl; - // return 0; + + if (vm.count("jed-note")) { + ostringstream note_field; + for(auto &n: vm["jed-note"].as>()) { + note_field << "NOTE " << n << "*" << endl; + full_checksum += calc_checksum(note_field.str()); + jed_file << note_field.str(); + } + } ostringstream fusecnt_field; uint32_t fusecnt; @@ -389,6 +415,7 @@ int main(int argc, char *argv[]) return 1; } + // TODO: QP (package information not implied in textual representation). fusecnt_field << "QF" << fusecnt << '*' << endl; full_checksum += calc_checksum(fusecnt_field.str()); jed_file << fusecnt_field.str(); @@ -397,9 +424,26 @@ int main(int argc, char *argv[]) full_checksum += calc_checksum("G0*\n"); jed_file << "F0*" << endl; // Default fuse value. full_checksum += calc_checksum("F0*\n"); - jed_file << "L0" << endl; - full_checksum += calc_checksum("L0\n"); + // The JEDEC spec says leading 0s are optional. My own experience is + // that some programmers require leading 0s. + ostringstream list_field; + list_field << "L" << setw(num_digits(fusecnt)) << setfill('0') << 0 << endl; + full_checksum += calc_checksum(list_field.str()); + jed_file << list_field.str(); + + // Strip the leading comment- it wastes precious fuse space and + // some programmers (e.g STEP-MXO2) even rely on the preamble being + // the first bytes. + vector preamble = {0xFF, 0xFF, 0xBD, 0xB3, 0xFF, 0xFF }; + auto start_iter = search(begin(bitstream), end(bitstream), begin(preamble), end(preamble)); + + if(start_iter == end(bitstream)) { + cerr << "Could not extract preamble from bitstream" << endl; + return 1; + } + + auto start_offs = start_iter - bitstream.begin(); size_t i = 0; while(i < fusecnt/8) { @@ -407,9 +451,9 @@ int main(int argc, char *argv[]) size_t len = min(size_t(16), bitstream.size() - i); for (unsigned int j = 0; j < len; j++) { - uint8_t byte = uint8_t(bitstream[j + i]); + uint8_t byte = uint8_t(bitstream[j + i + start_offs]); checksum += reverse_byte(byte); - full_checksum += calc_checksum(std::bitset<8>{byte}.to_string()); + full_checksum += calc_checksum(bitset<8>{byte}.to_string()); jed_file << std::bitset<8>{byte}; } @@ -442,10 +486,10 @@ int main(int argc, char *argv[]) jed_file << "*" << endl; full_checksum += calc_checksum("*\n"); - ostringstream oss; - oss << "C" << hex << uppercase << setfill('0') << setw(4) << checksum << '*' << endl; - full_checksum += calc_checksum(oss.str()); - jed_file << oss.str(); + ostringstream checksum_field; + checksum_field << "C" << hex << uppercase << setfill('0') << setw(4) << checksum << '*' << endl; + full_checksum += calc_checksum(checksum_field.str()); + jed_file << checksum_field.str(); full_checksum += calc_checksum("\x03"); jed_file << "\x03" << hex << uppercase << setw(4) << setfill('0') << full_checksum << endl;