Version in base suite: 0~20220915gita545498-3 Base version: fpga-icestorm_0~20220915gita545498-3 Target version: fpga-icestorm_0~20230218gitd20a5e9-1~deb12u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/f/fpga-icestorm/fpga-icestorm_0~20220915gita545498-3.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/f/fpga-icestorm/fpga-icestorm_0~20230218gitd20a5e9-1~deb12u1.dsc debian/changelog | 20 debian/gbp.conf | 1 debian/patches/0003-Fix-examples-icemulti-all-target.patch | 2 debian/patches/0004-Fix-up5k_rgb-failing-timing-analysis.patch | 18 debian/patches/0004-Remove-hard-coded-path-in-icebox_vlog.py.patch | 2 debian/patches/0005-Fix-GCC-10-build-with-missing-include.patch | 4 debian/patches/series | 1 debian/tests/control | 2 docs/io_tile.html | 11 icebox/icebox.py | 12 icebram/icebram.cc | 838 ++++++---- 11 files changed, 641 insertions(+), 270 deletions(-) diff -Nru fpga-icestorm-0~20220915gita545498/debian/changelog fpga-icestorm-0~20230218gitd20a5e9/debian/changelog --- fpga-icestorm-0~20220915gita545498/debian/changelog 2022-11-16 22:51:42.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/debian/changelog 2023-11-02 10:10:26.000000000 +0000 @@ -1,3 +1,23 @@ +fpga-icestorm (0~20230218gitd20a5e9-1~deb12u1) bookworm; urgency=medium + + * Fix yosys incompatibility (Closes: #1055171) + + -- Daniel Gröber Thu, 02 Nov 2023 11:10:26 +0100 + +fpga-icestorm (0~20230218gitd20a5e9-1) unstable; urgency=medium + + * New upstream version 0~20230218gitd20a5e9 + + -- Daniel Gröber Tue, 13 Jun 2023 14:52:48 +0200 + +fpga-icestorm (0~20220915gita545498-4) unstable; urgency=medium + + * autopkgtest: Fix missing icetime command + * Refresh patches + * Add patch fixing up5k_rgb icetime failure + + -- Daniel Gröber Tue, 13 Jun 2023 11:56:01 +0200 + fpga-icestorm (0~20220915gita545498-3) unstable; urgency=medium * Fix autopkgtest, examples-compile now needs nextpnr diff -Nru fpga-icestorm-0~20220915gita545498/debian/gbp.conf fpga-icestorm-0~20230218gitd20a5e9/debian/gbp.conf --- fpga-icestorm-0~20220915gita545498/debian/gbp.conf 2022-03-27 14:25:45.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/debian/gbp.conf 2023-11-02 10:10:26.000000000 +0000 @@ -1,2 +1,3 @@ [DEFAULT] pristine-tar = True +debian-branch = debian/bookworm diff -Nru fpga-icestorm-0~20220915gita545498/debian/patches/0003-Fix-examples-icemulti-all-target.patch fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0003-Fix-examples-icemulti-all-target.patch --- fpga-icestorm-0~20220915gita545498/debian/patches/0003-Fix-examples-icemulti-all-target.patch 2022-10-29 18:42:04.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0003-Fix-examples-icemulti-all-target.patch 2023-06-15 19:29:14.000000000 +0000 @@ -7,7 +7,7 @@ 1 file changed, 2 insertions(+) diff --git a/examples/icemulti/Makefile b/examples/icemulti/Makefile -index d8a8320..1e38f9c 100644 +index a7ce692..96b31e1 100644 --- a/examples/icemulti/Makefile +++ b/examples/icemulti/Makefile @@ -1,3 +1,5 @@ diff -Nru fpga-icestorm-0~20220915gita545498/debian/patches/0004-Fix-up5k_rgb-failing-timing-analysis.patch fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0004-Fix-up5k_rgb-failing-timing-analysis.patch --- fpga-icestorm-0~20220915gita545498/debian/patches/0004-Fix-up5k_rgb-failing-timing-analysis.patch 1970-01-01 00:00:00.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0004-Fix-up5k_rgb-failing-timing-analysis.patch 2023-06-15 19:29:14.000000000 +0000 @@ -0,0 +1,18 @@ +From: =?utf-8?q?Daniel_Gr=C3=B6ber?= +Date: Wed, 17 May 2023 21:09:31 +0200 +Subject: Fix up5k_rgb failing timing analysis + +--- + examples/up5k_rgb/rgb.pcf | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/examples/up5k_rgb/rgb.pcf b/examples/up5k_rgb/rgb.pcf +index 0954260..19a93d1 100644 +--- a/examples/up5k_rgb/rgb.pcf ++++ b/examples/up5k_rgb/rgb.pcf +@@ -1,3 +1,4 @@ + set_io RGB0 39 + set_io RGB1 40 + set_io RGB2 41 ++set_frequency clk 32 +\ No newline at end of file diff -Nru fpga-icestorm-0~20220915gita545498/debian/patches/0004-Remove-hard-coded-path-in-icebox_vlog.py.patch fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0004-Remove-hard-coded-path-in-icebox_vlog.py.patch --- fpga-icestorm-0~20220915gita545498/debian/patches/0004-Remove-hard-coded-path-in-icebox_vlog.py.patch 2022-03-27 14:45:36.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0004-Remove-hard-coded-path-in-icebox_vlog.py.patch 2023-06-15 19:29:14.000000000 +0000 @@ -6,6 +6,8 @@ icebox/icebox_vlog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) +diff --git a/icebox/icebox_vlog.py b/icebox/icebox_vlog.py +index 74ac3d3..9ba2e30 100755 --- a/icebox/icebox_vlog.py +++ b/icebox/icebox_vlog.py @@ -384,7 +384,7 @@ def seg_to_net(seg, default=None): diff -Nru fpga-icestorm-0~20220915gita545498/debian/patches/0005-Fix-GCC-10-build-with-missing-include.patch fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0005-Fix-GCC-10-build-with-missing-include.patch --- fpga-icestorm-0~20220915gita545498/debian/patches/0005-Fix-GCC-10-build-with-missing-include.patch 2022-03-27 14:45:36.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0005-Fix-GCC-10-build-with-missing-include.patch 2023-06-15 19:29:14.000000000 +0000 @@ -4,8 +4,10 @@ --- icetime/icetime.cc | 1 + - 2 files changed, 3 insertions(+), 1 deletion(-) + 1 file changed, 1 insertion(+) +diff --git a/icetime/icetime.cc b/icetime/icetime.cc +index fef65d2..2bace03 100644 --- a/icetime/icetime.cc +++ b/icetime/icetime.cc @@ -34,6 +34,7 @@ diff -Nru fpga-icestorm-0~20220915gita545498/debian/patches/series fpga-icestorm-0~20230218gitd20a5e9/debian/patches/series --- fpga-icestorm-0~20220915gita545498/debian/patches/series 2022-03-27 14:45:22.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/series 2023-06-15 19:29:14.000000000 +0000 @@ -1,3 +1,4 @@ 0004-Remove-hard-coded-path-in-icebox_vlog.py.patch 0005-Fix-GCC-10-build-with-missing-include.patch 0003-Fix-examples-icemulti-all-target.patch +0004-Fix-up5k_rgb-failing-timing-analysis.patch diff -Nru fpga-icestorm-0~20220915gita545498/debian/tests/control fpga-icestorm-0~20230218gitd20a5e9/debian/tests/control --- fpga-icestorm-0~20220915gita545498/debian/tests/control 2022-11-16 22:50:15.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/debian/tests/control 2023-05-25 13:13:35.000000000 +0000 @@ -3,6 +3,6 @@ Restrictions: superficial Tests: examples-compile -Depends: fpga-icestorm-chipdb, yosys, nextpnr-ice40 +Depends: @, yosys, nextpnr-ice40 Restrictions: rw-build-tree allow-stderr Architecture: !s390x diff -Nru fpga-icestorm-0~20220915gita545498/docs/io_tile.html fpga-icestorm-0~20230218gitd20a5e9/docs/io_tile.html --- fpga-icestorm-0~20220915gita545498/docs/io_tile.html 2022-09-15 10:37:29.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/docs/io_tile.html 2023-02-18 15:37:53.000000000 +0000 @@ -428,6 +428,9 @@ 0 3PLLCONFIG_8TEST_MODE +0 5PLLCONFIG_2Enable ICEGATE for PLLOUTGLOBALA +0 5PLLCONFIG_4Enable ICEGATE for PLLOUTGLOBALB + @@ -502,4 +505,12 @@ are being used.

+

+The input path that are stolen are also used to implement the ICEGATE function. +If the input pin type of the input path being stolen is set to +PIN_INPUT_LATCH, then the ICEGATE +function is enabled for the corresponding CORE +output of the PLL. +

+ diff -Nru fpga-icestorm-0~20220915gita545498/icebox/icebox.py fpga-icestorm-0~20230218gitd20a5e9/icebox/icebox.py --- fpga-icestorm-0~20220915gita545498/icebox/icebox.py 2022-09-15 10:37:29.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/icebox/icebox.py 2023-02-18 15:37:53.000000000 +0000 @@ -1795,6 +1795,8 @@ "FILTER_RANGE_1": ( 0, 2, "PLLCONFIG_7"), "FILTER_RANGE_2": ( 0, 2, "PLLCONFIG_8"), "TEST_MODE": ( 0, 3, "PLLCONFIG_8"), + "ENABLE_ICEGATE_PORTA": ( 0, 5, "PLLCONFIG_2"), # Controls global output only ! + "ENABLE_ICEGATE_PORTB": ( 0, 5, "PLLCONFIG_4"), # Controls global output only ! # PLL Ports "PLLOUT_A": ( 6, 0, 1), @@ -1887,6 +1889,8 @@ "FILTER_RANGE_1": (11, 0, "PLLCONFIG_7"), "FILTER_RANGE_2": (11, 0, "PLLCONFIG_8"), "TEST_MODE": (12, 0, "PLLCONFIG_8"), + "ENABLE_ICEGATE_PORTA": (14, 0, "PLLCONFIG_2"), # Controls global output only ! + "ENABLE_ICEGATE_PORTB": (14, 0, "PLLCONFIG_4"), # Controls global output only ! # PLL Ports # TODO(awygle) confirm these @@ -1981,6 +1985,8 @@ "FILTER_RANGE_1": (11, 31, "PLLCONFIG_7"), "FILTER_RANGE_2": (11, 31, "PLLCONFIG_8"), "TEST_MODE": (12, 31, "PLLCONFIG_8"), + "ENABLE_ICEGATE_PORTA": (14, 31, "PLLCONFIG_2"), # Controls global output only ! + "ENABLE_ICEGATE_PORTB": (14, 31, "PLLCONFIG_4"), # Controls global output only ! # PLL Ports "PLLOUT_A": ( 12, 31, 1), @@ -2045,6 +2051,8 @@ "TEST_MODE": (12, 21, "PLLCONFIG_8"), "DELAY_ADJMODE_FB": (13, 21, "PLLCONFIG_4"), "DELAY_ADJMODE_REL": (13, 21, "PLLCONFIG_9"), + "ENABLE_ICEGATE_PORTA": (14, 21, "PLLCONFIG_2"), # Controls global output only ! + "ENABLE_ICEGATE_PORTB": (14, 21, "PLLCONFIG_4"), # Controls global output only ! # PLL Ports "PLLOUT_A": ( 12, 21, 1), @@ -2138,6 +2146,8 @@ "FILTER_RANGE_1": ( 15, 0, "PLLCONFIG_7"), "FILTER_RANGE_2": ( 15, 0, "PLLCONFIG_8"), "TEST_MODE": ( 16, 0, "PLLCONFIG_8"), + "ENABLE_ICEGATE_PORTA": ( 18, 0, "PLLCONFIG_2"), # Controls global output only ! + "ENABLE_ICEGATE_PORTB": ( 18, 0, "PLLCONFIG_4"), # Controls global output only ! # PLL Ports "PLLOUT_A": ( 16, 0, 1), @@ -2231,6 +2241,8 @@ "FILTER_RANGE_1": ( 15, 33, "PLLCONFIG_7"), "FILTER_RANGE_2": ( 15, 33, "PLLCONFIG_8"), "TEST_MODE": ( 16, 33, "PLLCONFIG_8"), + "ENABLE_ICEGATE_PORTA": ( 18, 33, "PLLCONFIG_2"), # Controls global output only ! + "ENABLE_ICEGATE_PORTB": ( 18, 33, "PLLCONFIG_4"), # Controls global output only ! # PLL Ports "PLLOUT_A": ( 16, 33, 1), diff -Nru fpga-icestorm-0~20220915gita545498/icebram/icebram.cc fpga-icestorm-0~20230218gitd20a5e9/icebram/icebram.cc --- fpga-icestorm-0~20220915gita545498/icebram/icebram.cc 2022-09-15 10:37:29.000000000 +0000 +++ fpga-icestorm-0~20230218gitd20a5e9/icebram/icebram.cc 2023-02-18 15:37:53.000000000 +0000 @@ -1,5 +1,6 @@ // // Copyright (C) 2016 Clifford Wolf +// Copyright (C) 2023 Sylvain Munaut // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -14,54 +15,122 @@ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // + #include +#include #include +#include #include -#include -#include -#include #include -#include -#include -#include +#include #include #include +#include +#include +#include +#include #ifdef __EMSCRIPTEN__ #include #endif -using std::map; -using std::pair; -using std::vector; -using std::string; -using std::ifstream; -using std::getline; - -uint64_t x; -uint64_t xorshift64star(void) { - x ^= x >> 12; // a - x ^= x << 25; // b - x ^= x >> 27; // c - return x * UINT64_C(2685821657736338717); -} -void push_back_bitvector(vector> &hexfile, const vector &digits) + +struct app_opts { + char *prog; + + int extra_argc; + char **extra_argv; + + bool generate; + bool verbose; + uint32_t seed_nr; + bool seed; +}; + +static void help(const char *cmd); + + +// --------------------------------------------------------------------------- +// Update mode +// --------------------------------------------------------------------------- + + +// Hex Data File +// ------------- + +class HexFile { - if (digits.empty()) - return; +private: + std::vector> m_data; + size_t m_word_size; + + std::vector parse_digits(std::vector &digits) const; + bool parse_line(std::string &line); +public: + HexFile(const char *filename, bool pad_words); + virtual ~HexFile() { }; + + void pad_words_to(size_t size); + void pad_to(size_t size); + + size_t size() const { return this->m_data.size(); }; + size_t word_size() const { return this->m_word_size; }; + + std::map, std::pair, int>> generate_pattern(HexFile &to) const; +}; + +HexFile::HexFile(const char *filename, bool pad_words=false) +{ + std::ifstream stream(filename); + + if (!stream.is_open()) { + fprintf(stderr, "Failed to open file %s\n", filename); + exit(1); + } - hexfile.push_back(vector(digits.size() * 4)); + // Parse file + std::string line; + for (int i=1; std::getline(stream, line); i++) + if (!this->parse_line(line)) { + fprintf(stderr, "Can't parse line %d of %s: %s\n", i, filename, line.c_str()); + exit(1); + } + + // Check word size + this->m_word_size = this->m_data.at(0).size(); + + for (auto &w : this->m_data) + { + if ((w.size() != this->m_word_size) && !pad_words) { + fprintf(stderr, "Inconsistent word sizes in %s\n", filename); + exit(1); + } + if (w.size() > this->m_word_size) + this->m_word_size = w.size(); + } + + // If requested, pad them + this->pad_words_to(this->m_word_size); +} + +std::vector +HexFile::parse_digits(std::vector &digits) const +{ + std::vector line_data(digits.size() * 4); for (int i = 0; i < int(digits.size()) * 4; i++) if ((digits.at(digits.size() - i/4 -1) & (1 << (i%4))) != 0) - hexfile.back().at(i) = true; + line_data.at(i) = true; + + return line_data; } -void parse_hexfile_line(const char *filename, int linenr, vector> &hexfile, string &line) +bool +HexFile::parse_line(std::string &line) { - vector digits; + std::vector digits; for (char c : line) { if ('0' <= c && c <= '9') @@ -76,331 +145,566 @@ else if ('_' == c) ; else if (' ' == c || '\t' == c || '\r' == c) { - push_back_bitvector(hexfile, digits); - digits.clear(); - } else goto error; + if (digits.size()) { + this->m_data.push_back(this->parse_digits(digits)); + digits.clear(); + } + } else { + return false; + } } - push_back_bitvector(hexfile, digits); + if (digits.size()) + this->m_data.push_back(this->parse_digits(digits)); + + return true; +} + +void +HexFile::pad_words_to(size_t size) +{ + if (this->m_word_size > size) + return; - return; + for (auto &w : this->m_data) + if (w.size() < size) + w.resize(size, false); -error: - fprintf(stderr, "Can't parse line %d of %s: %s\n", linenr, filename, line.c_str()); - exit(1); + this->m_word_size = size; } -void help(const char *cmd) +void +HexFile::pad_to(size_t size) { - printf("\n"); - printf("Usage: %s [options] \n", cmd); - printf(" %s [options] -g [-s ] \n", cmd); - printf("\n"); - printf("Replace BRAM initialization data in a .asc file. This can be used\n"); - printf("for example to replace firmware images without re-running synthesis\n"); - printf("and place&route.\n"); - printf("\n"); - printf(" -g\n"); - printf(" generate a hex file with random contents.\n"); - printf(" use this to generate the hex file used during synthesis, then\n"); - printf(" use the same file as later.\n"); - printf("\n"); - printf(" -s \n"); - printf(" seed random generator with fixed value.\n"); - printf("\n"); - printf(" -v\n"); - printf(" verbose output\n"); - printf("\n"); - exit(1); + while (this->m_data.size() < size) + this->m_data.push_back(std::vector(this->m_word_size)); } -int main(int argc, char **argv) +std::map, std::pair, int>> +HexFile::generate_pattern(HexFile &to) const { -#ifdef __EMSCRIPTEN__ - EM_ASM( - if (ENVIRONMENT_IS_NODE) - { - FS.mkdir('/hostcwd'); - FS.mount(NODEFS, { root: '.' }, '/hostcwd'); - FS.mkdir('/hostfs'); - FS.mount(NODEFS, { root: '/' }, '/hostfs'); - } - ); -#endif + std::map, std::pair, int>> pattern; - bool verbose = false; - bool generate = false; - bool seed = false; - uint32_t seed_opt = 0; - - int opt; - while ((opt = getopt(argc, argv, "vgs:")) != -1) + for (int i=0; im_word_size); i++) { - switch (opt) + std::vector pattern_from, pattern_to; + + for (int j=0; jm_data.size()); j++) { - case 'v': - verbose = true; - break; - case 'g': - generate = true; - break; - case 's': - seed = true; - seed_opt = atoi(optarg); - break; - default: - help(argv[0]); + pattern_from.push_back(this->m_data.at(j).at(i)); + pattern_to.push_back(to.m_data.at(j).at(i)); + + if (pattern_from.size() == 256) { + if (pattern.count(pattern_from)) { + fprintf(stderr, "Conflicting from pattern for bit slice from_hexfile[%d:%d][%d]!\n", j, j-255, i); + exit(1); + } + pattern[pattern_from] = std::make_pair(pattern_to, 0); + pattern_from.clear(), pattern_to.clear(); + } } } - if (generate) - { - if (optind+2 != argc) - help(argv[0]); + return pattern; +} - int width = atoi(argv[optind]); - int depth = atoi(argv[optind+1]); - if (width <= 0 || width % 4 != 0) { - fprintf(stderr, "Hexfile width (%d bits) is not divisible by 4 or nonpositive!\n", width); - exit(1); - } +// Bitstream File +// -------------- - if (depth <= 0 || depth % 256 != 0) { - fprintf(stderr, "Hexfile number of words (%d) is not divisible by 256 or nonpositive!\n", depth); - exit(1); - } +class EBRData +{ +private: + std::vector m_data; + int m_read_mode; + + int m_pos[2]; + int m_data_line; + int m_config_line; + std::vector &m_lines; + + friend class AscFile; + +protected: + void load_data (); + void save_data (); + void load_config (); + +public: + EBRData(std::vector &lines, int pos[2]); + virtual ~EBRData() { }; - if (verbose && seed) - fprintf(stderr, "Seed: %d\n", seed_opt); - - // If -s is provided: seed with the given value. - // If -s is not provided: seed with the PID and current time, which are unlikely - // to repeat simultaneously. - uint32_t seed_nr; - if (!seed) { -#if defined(__wasm) - seed_nr = 0; -#else - seed_nr = getpid(); -#endif - } else { - seed_nr = seed_opt; - } + void apply_pattern(std::map, std::pair, int>> &pattern); +}; - x = uint64_t(seed_nr) << 32; - x ^= uint64_t(depth) << 16; - x ^= uint64_t(width) << 10; +class AscFile +{ +private: + std::vector m_lines; + std::map m_ebr; - xorshift64star(); - xorshift64star(); - xorshift64star(); + EBRData &get_ebr(int pos[2]); - if (!seed) { - struct timeval tv; - gettimeofday(&tv, NULL); - x ^= uint64_t(tv.tv_sec) << 20; - x ^= uint64_t(tv.tv_usec); - } +public: + AscFile(); + virtual ~AscFile() { }; + + void load_config(std::istream &is); + void save_config(std::ostream &os); + + size_t n_ebrs() const { return this->m_ebr.size(); }; + + void apply_pattern(std::map, std::pair, int>> &pattern); +}; - xorshift64star(); - xorshift64star(); - xorshift64star(); - for (int i = 0; i < depth; i++) { - for (int j = 0; j < width / 4; j++) { - int digit = xorshift64star() & 15; - std::cout << "0123456789abcdef"[digit]; +EBRData::EBRData(std::vector &lines, int pos[2]) : + m_data(4096), + m_pos{pos[0], pos[1]}, + m_data_line(-1), m_config_line(-1), m_lines(lines) +{ + +} + +void +EBRData::load_data() +{ + auto si = this->m_lines.begin() + this->m_data_line + 16; + auto ei = this->m_lines.begin() + this->m_data_line; + int idx = 4096; + + for (auto line=si; line!=ei; line--) { + for (char c : *line) { + int digit; + + if ('0' <= c && c <= '9') + digit = c - '0'; + else if ('a' <= c && c <= 'f') + digit = 10 + c - 'a'; + else if ('A' <= c && c <= 'F') + digit = 10 + c - 'A'; + else { + fprintf(stderr, "Invalid char in BRAM data\n"); + exit(1); } - std::cout << std::endl; + + idx -= 4; + + for (int subidx=3; subidx>=0; subidx--) + if (digit & (1 << subidx)) + this->m_data.at(idx+subidx) = true; } + } +} - exit(0); +void +EBRData::save_data() +{ + auto si = this->m_lines.begin() + this->m_data_line + 16; + auto ei = this->m_lines.begin() + this->m_data_line; + int idx = 4096; + + for (auto line=si; line!=ei; line--) { + // Hex String + char hex[65]; + idx -= 256; + for (int bit=0; bit<256; bit+=4) { + int digit = (this->m_data[idx+bit+3] ? 8 : 0) | + (this->m_data[idx+bit+2] ? 4 : 0) | + (this->m_data[idx+bit+1] ? 2 : 0) | + (this->m_data[idx+bit+0] ? 1 : 0); + hex[63-(bit>>2)] = "0123456789abcdef"[digit]; + } + hex[64] = 0; + + // Put new line + *line = std::string(hex); } +} - if (optind+2 != argc) - help(argv[0]); +void +EBRData::load_config() +{ + this->m_read_mode = ( + ((this->m_lines.at(this->m_config_line+3).at(7) == '1') ? 2 : 0) | // RamConfig.CBIT_2 + ((this->m_lines.at(this->m_config_line+4).at(7) == '1') ? 1 : 0) // RamConfig.CBIT_3 + ); +} +void +EBRData::apply_pattern(std::map, std::pair, int>> &pattern) +{ + const std::map> subidx_map = { + { 0, { 0 } }, + { 1, { 0, 1 } }, + { 2, { 0, 2, 1, 3 } }, + { 3, { 0, 4, 2, 6, 1, 5, 3, 7 } }, + }; + + const std::vector &subidx = subidx_map.at(this->m_read_mode); + int W = 16 >> this->m_read_mode; + int P = 16 / W; - // ------------------------------------------------------- - // Load from_hexfile and to_hexfile + for (int blk_base=0; blk_base<4096; blk_base+=4096/P) + { + for (int bit_base=0; bit_base<16; bit_base+=P) + { + std::vector fbs(256); - const char *from_hexfile_n = argv[optind]; - ifstream from_hexfile_f(from_hexfile_n); - vector> from_hexfile; + // Create "From Bit Slice" from local memory + for (int oaddr=0; oaddr<256/P; oaddr++) + for (int iaddr=0; iaddrm_data.at(blk_base+bit_base+oaddr*16+subidx.at(iaddr)); + + // Perform substitution + auto p = pattern.find(fbs); + if (p == pattern.end()) + continue; + + auto &tbs = p->second.first; + p->second.second++; + + // Map "To Bit Slice" back into local memory + for (int oaddr=0; oaddr<256/P; oaddr++) + for (int iaddr=0; iaddrm_data.at(blk_base+bit_base+oaddr*16+subidx.at(iaddr)) = tbs.at(oaddr*P+iaddr); + } + } +} - const char *to_hexfile_n = argv[optind+1]; - ifstream to_hexfile_f(to_hexfile_n); - vector> to_hexfile; - string line; +AscFile::AscFile() +{ + // Nothing to do for now +} - for (int i = 1; getline(from_hexfile_f, line); i++) - parse_hexfile_line(from_hexfile_n, i, from_hexfile, line); +EBRData & +AscFile::get_ebr(int pos[2]) +{ + int p = pos[0] | (pos[1] << 8); + return (*this->m_ebr.emplace(p, EBRData{this->m_lines, pos}).first).second; +} - for (int i = 1; getline(to_hexfile_f, line); i++) - parse_hexfile_line(to_hexfile_n, i, to_hexfile, line); +void +AscFile::load_config(std::istream &is) +{ + std::string line; + int pos[2]; - if (to_hexfile.size() > 0 && from_hexfile.size() > to_hexfile.size()) { - if (verbose) - fprintf(stderr, "Padding to_hexfile from %d words to %d\n", - int(to_hexfile.size()), int(from_hexfile.size())); - do - to_hexfile.push_back(vector(to_hexfile.at(0).size())); - while (from_hexfile.size() > to_hexfile.size()); + // Load data and track where each EBR is configured and initialized + for (int l=0; std::getline(is, line); l++) { + // Save line + this->m_lines.push_back(line); + + // Keep position of RAM infos + if (line.substr(0, 9) == ".ram_data") { + sscanf(line.substr(10).c_str(), "%d %d", &pos[0], &pos[1]); + this->get_ebr(pos).m_data_line = l; + } else if (line.substr(0, 10) == ".ramt_tile") { + sscanf(line.substr(11).c_str(), "%d %d", &pos[0], &pos[1]); + pos[1] -= 1; + this->get_ebr(pos).m_config_line = l; + } } - if (from_hexfile.size() != to_hexfile.size()) { - fprintf(stderr, "Hexfiles have different number of words! (%d vs. %d)\n", int(from_hexfile.size()), int(to_hexfile.size())); - exit(1); + // Only keep EBR that are initialized + for (auto it = this->m_ebr.begin(); it != this->m_ebr.end(); ) + if (it->second.m_data_line < 0) + it = this->m_ebr.erase(it); + else + ++it; + + // Load data config for those + for (auto &ebr : this->m_ebr) { + ebr.second.load_data(); + ebr.second.load_config(); } +} - if (from_hexfile.size() % 256 != 0) { - fprintf(stderr, "Hexfile number of words (%d) is not divisible by 256!\n", int(from_hexfile.size())); - exit(1); +void +AscFile::save_config(std::ostream &os) +{ + // Update all EBRs + for (auto &ebr : this->m_ebr) + ebr.second.save_data(); + + // Output new config + for (auto &l: this->m_lines) + os << l << std::endl; +} + +void +AscFile::apply_pattern(std::map, std::pair, int>> &pattern) +{ + for (auto &ebr : this->m_ebr) + ebr.second.apply_pattern(pattern); +} + + +// Update process +// --------------- + +static int +update(struct app_opts *opts) +{ + if (opts->extra_argc != 2) + help(opts->prog); + + // Parse two source files + HexFile hf_from (opts->extra_argv[0]); + HexFile hf_to (opts->extra_argv[1], true); + + // Perform checks + if ((hf_to.word_size() > 0) && (hf_from.word_size() > hf_to.word_size())) { + if (opts->verbose) + fprintf(stderr, "Padding to_hexfile words from %lu bits to %lu bits\n", + hf_to.word_size(), hf_from.word_size()); + hf_to.pad_words_to(hf_from.word_size()); } - for (size_t i = 1; i < from_hexfile.size(); i++) - if (from_hexfile.at(i-1).size() != from_hexfile.at(i).size()) { - fprintf(stderr, "Inconsistent word width at line %d of %s!\n", int(i), from_hexfile_n); - exit(1); - } + if (hf_to.word_size() != hf_from.word_size()) { + fprintf(stderr, "Hexfiles have different word sizes! (%lu bits vs. %lu bits)\n", + hf_from.word_size(), hf_to.word_size()); + return 1; + } - for (size_t i = 1; i < to_hexfile.size(); i++) { - while (to_hexfile.at(i-1).size() > to_hexfile.at(i).size()) - to_hexfile.at(i).push_back(false); - if (to_hexfile.at(i-1).size() != to_hexfile.at(i).size()) { - fprintf(stderr, "Inconsistent word width at line %d of %s!\n", int(i+1), to_hexfile_n); - exit(1); - } + if ((hf_to.size() > 0) && (hf_from.size() > hf_to.size())) { + if (opts->verbose) + fprintf(stderr, "Padding to_hexfile from %lu words to %lu\n", + hf_to.size(), hf_from.size()); + hf_to.pad_to(hf_from.size()); } - if (from_hexfile.size() == 0 || from_hexfile.at(0).size() == 0) { + if (hf_to.size() != hf_from.size()) { + fprintf(stderr, "Hexfiles have different number of words! (%lu vs. %lu)\n", + hf_from.size(), hf_to.size()); + return 1; + } + + if (hf_from.size() % 256 != 0) { + fprintf(stderr, "Hexfile number of words (%lu) is not divisible by 256!\n", + hf_from.size()); + return 1; + } + + if (hf_from.size() == 0 || hf_from.word_size() == 0) { fprintf(stderr, "Empty from/to hexfiles!\n"); - exit(1); + return 1; } - if (verbose) - fprintf(stderr, "Loaded pattern for %d bits wide and %d words deep memory.\n", int(from_hexfile.at(0).size()), int(from_hexfile.size())); + // Debug + if (opts->verbose) + fprintf(stderr, "Loaded pattern for %lu bits wide and %lu words deep memory.\n", + hf_from.word_size(), hf_from.size()); + // Generate mapping for slices + std::map, std::pair, int>> pattern = hf_from.generate_pattern(hf_to); + if (opts->verbose) + fprintf(stderr, "Extracted %lu bit slices from from/to hexfile data.\n", pattern.size()); - // ------------------------------------------------------- - // Create bitslices from pattern data + // Load FPGA config from stdin + AscFile bitstream; + bitstream.load_config(std::cin); - map, pair, int>> pattern; + if (opts->verbose) + fprintf(stderr, "Found %lu initialized bram cells in asc file.\n", bitstream.n_ebrs()); - for (int i = 0; i < int(from_hexfile.at(0).size()); i++) - { - vector pattern_from, pattern_to; + // Apply pattern + bitstream.apply_pattern(pattern); - for (int j = 0; j < int(from_hexfile.size()); j++) - { - pattern_from.push_back(from_hexfile.at(j).at(i)); - pattern_to.push_back(to_hexfile.at(j).at(i)); + // Check pattern was applied uniformly + int min_replace_cnt = INT_MAX; + int max_replace_cnt = INT_MIN; - if (pattern_from.size() == 256) { - if (pattern.count(pattern_from)) { - fprintf(stderr, "Conflicting from pattern for bit slice from_hexfile[%d:%d][%d]!\n", j, j-255, i); - exit(1); - } - pattern[pattern_from] = std::make_pair(pattern_to, 0); - pattern_from.clear(), pattern_to.clear(); - } - } + for (auto &it : pattern) { + max_replace_cnt = std::max(max_replace_cnt, it.second.second); + min_replace_cnt = std::min(min_replace_cnt, it.second.second); + } - assert(pattern_from.empty()); - assert(pattern_to.empty()); + if (min_replace_cnt != max_replace_cnt) { + fprintf(stderr, "Found some bitslices up to %d times, others only %d times!\n", max_replace_cnt, min_replace_cnt); + return 1; } - if (verbose) - fprintf(stderr, "Extracted %d bit slices from from/to hexfile data.\n", int(pattern.size())); + if (max_replace_cnt == 0) { + fprintf(stderr, "No memory instances were replaced.\n"); + return 1; + } + if (opts->verbose) + fprintf(stderr, "Found and replaced %d instances of the memory.\n", max_replace_cnt); - // ------------------------------------------------------- - // Read ascfile from stdin + // Save new FPGA config to stdout + bitstream.save_config(std::cout); - vector ascfile_lines; - map>> ascfile_hexdata; + return 0; +} - for (int i = 1; getline(std::cin, line); i++) - { - next_asc_stmt: - ascfile_lines.push_back(line); - if (line.substr(0, 9) == ".ram_data") - { - auto &hexdata = ascfile_hexdata[line]; +// --------------------------------------------------------------------------- +// Generate mode +// --------------------------------------------------------------------------- - for (; getline(std::cin, line); i++) { - if (line.substr(0, 1) == ".") - goto next_asc_stmt; - parse_hexfile_line("stdin", i, hexdata, line); - } - } +static uint64_t +xorshift64star(uint64_t *x) +{ + *x ^= *x >> 12; // a + *x ^= *x << 25; // b + *x ^= *x >> 27; // c + return *x * UINT64_C(2685821657736338717); +} + +static int +generate(struct app_opts *opts) +{ + if (opts->extra_argc != 2) + help(opts->prog); + + int width = atoi(opts->extra_argv[0]); + int depth = atoi(opts->extra_argv[1]); + + if (width <= 0 || width % 4 != 0) { + fprintf(stderr, "Hexfile width (%d bits) is not divisible by 4 or nonpositive!\n", width); + exit(1); } - if (verbose) - fprintf(stderr, "Found %d initialized bram cells in asc file.\n", int(ascfile_hexdata.size())); + if (depth <= 0 || depth % 256 != 0) { + fprintf(stderr, "Hexfile number of words (%d) is not divisible by 256 or nonpositive!\n", depth); + exit(1); + } + if (opts->verbose && opts->seed) + fprintf(stderr, "Seed: %d\n", opts->seed_nr); - // ------------------------------------------------------- - // Replace bram data - int max_replace_cnt = 0; + if (!opts->seed) { +#if defined(__wasm) + opts->seed_nr = 0; +#else + opts->seed_nr = getpid(); +#endif + } - for (auto &bram_it : ascfile_hexdata) - { - auto &bram_data = bram_it.second; + uint64_t x; - for (int i = 0; i < 16; i++) - { - vector from_bitslice; + x = uint64_t(opts->seed_nr) << 32; + x ^= uint64_t(depth) << 16; + x ^= uint64_t(width) << 10; - for (int j = 0; j < 256; j++) - from_bitslice.push_back(bram_data.at(j / 16).at(16 * (j % 16) + i)); + xorshift64star(&x); + xorshift64star(&x); + xorshift64star(&x); - auto p = pattern.find(from_bitslice); - if (p != pattern.end()) - { - auto &to_bitslice = p->second.first; + if (!opts->seed) { + struct timeval tv; + gettimeofday(&tv, NULL); + x ^= uint64_t(tv.tv_sec) << 20; + x ^= uint64_t(tv.tv_usec); + } - for (int j = 0; j < 256; j++) - bram_data.at(j / 16).at(16 * (j % 16) + i) = to_bitslice.at(j); + xorshift64star(&x); + xorshift64star(&x); + xorshift64star(&x); - max_replace_cnt = std::max(++p->second.second, max_replace_cnt); - } + for (int i = 0; i < depth; i++) { + for (int j = 0; j < width / 4; j++) { + int digit = xorshift64star(&x) & 15; + std::cout << "0123456789abcdef"[digit]; } + std::cout << std::endl; } - int min_replace_cnt = max_replace_cnt; - for (auto &it : pattern) - min_replace_cnt = std::min(min_replace_cnt, it.second.second); + return 0; +} - if (min_replace_cnt != max_replace_cnt) { - fprintf(stderr, "Found some bitslices up to %d times, others only %d times!\n", max_replace_cnt, min_replace_cnt); - exit(1); - } - if (verbose) - fprintf(stderr, "Found and replaced %d instances of the memory.\n", max_replace_cnt); +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- +static void +help(const char *cmd) +{ + printf("\n"); + printf("Usage: %s [options] \n", cmd); + printf(" %s [options] -g [-s ] \n", cmd); + printf("\n"); + printf("Replace BRAM initialization data in a .asc file. This can be used\n"); + printf("for example to replace firmware images without re-running synthesis\n"); + printf("and place&route.\n"); + printf("\n"); + printf(" -g\n"); + printf(" generate a hex file with random contents.\n"); + printf(" use this to generate the hex file used during synthesis, then\n"); + printf(" use the same file as later.\n"); + printf("\n"); + printf(" -s \n"); + printf(" seed random generator with fixed value.\n"); + printf("\n"); + printf(" -v\n"); + printf(" verbose output\n"); + printf("\n"); + exit(1); +} - // ------------------------------------------------------- - // Write ascfile to stdout +static void +opts_defaults(struct app_opts *opts) +{ + // Clear + memset(opts, 0x00, sizeof(*opts)); +} - for (size_t i = 0; i < ascfile_lines.size(); i++) { - auto &line = ascfile_lines.at(i); - std::cout << line << std::endl; - if (ascfile_hexdata.count(line)) { - for (auto &word : ascfile_hexdata.at(line)) { - for (int k = word.size()-4; k >= 0; k -= 4) { - int digit = (word[k+3] ? 8 : 0) + (word[k+2] ? 4 : 0) + (word[k+1] ? 2 : 0) + (word[k] ? 1 : 0); - std::cout << "0123456789abcdef"[digit]; - } - std::cout << std::endl; - } +static void +opts_parse(struct app_opts *opts, int argc, char *argv[]) +{ + int opt; + + opts->prog = argv[0]; + + while ((opt = getopt(argc, argv, "vgs:")) != -1) + { + switch (opt) + { + case 'v': + opts->verbose = true; + break; + case 'g': + opts->generate = true; + break; + case 's': + opts->seed = true; + opts->seed_nr = atoi(optarg); + break; + default: + help(argv[0]); } } - return 0; + opts->extra_argc = argc - optind; + opts->extra_argv = &argv[optind]; +} + +int main(int argc, char **argv) +{ + struct app_opts opts; + +#ifdef __EMSCRIPTEN__ + EM_ASM( + if (ENVIRONMENT_IS_NODE) + { + FS.mkdir('/hostcwd'); + FS.mount(NODEFS, { root: '.' }, '/hostcwd'); + FS.mkdir('/hostfs'); + FS.mount(NODEFS, { root: '/' }, '/hostfs'); + } + ); +#endif + + opts_defaults(&opts); + opts_parse(&opts, argc, argv); + + if (opts.generate) + return generate(&opts); + else + return update(&opts); }