[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1055214: marked as done (bookworm-pu: package fpga-icestorm/0~20220915gita545498-3)



Your message dated Sat, 29 Jun 2024 10:46:15 +0000
with message-id <E1sNVax-002bXz-E0@coccia.debian.org>
and subject line Released with 12.6
has caused the Debian Bug report #1055214,
regarding bookworm-pu: package fpga-icestorm/0~20220915gita545498-3
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1055214: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1055214
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian.org@packages.debian.org
Usertags: pu
X-Debbugs-Cc: fpga-icestorm@packages.debian.org, dxld@darkboxed.org
Control: affects -1 + src:fpga-icestorm

[ Reason ]
Andras Pal reported fpga-icestorm's "icebram" utility being broken in
stable (#1055171) due to incompatible changes to yosys's output.

[ Impact ]
Users will not be able to easily embed ROM images into their FPGA
designs during or after netlist build.

[ Tests ]
I've tested the updated version against Andras' reproducer
and confirmed it fixes the issue.

[ Risks ]
The risk of breakage is low, icebram is already broken in stable so a
rewrite wont hurt.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
Upstream fixed this by rewriting the icebram utility as a major
refactoring was needed to fix it.

The version in unstable differs from the stable version in this
respect and some minor hardware enablement additions in icebox. I
belive simply updating the version in stable is warrented here.

[ Other info ]
Upstream discussion:
https://github.com/YosysHQ/icestorm/issues/301
https://github.com/YosysHQ/icestorm/pull/309

--Daniel
diff -Nru fpga-icestorm-0~20220915gita545498/debian/changelog fpga-icestorm-0~20230218gitd20a5e9/debian/changelog
--- fpga-icestorm-0~20220915gita545498/debian/changelog	2022-11-16 23:51:42.000000000 +0100
+++ fpga-icestorm-0~20230218gitd20a5e9/debian/changelog	2023-11-02 11:10:26.000000000 +0100
@@ -1,3 +1,23 @@
+fpga-icestorm (0~20230218gitd20a5e9-1~deb12u1) bookworm; urgency=medium
+
+  * Fix yosys incompatibility (Closes: #1055171)
+
+ -- Daniel Gröber <dxld@darkboxed.org>  Thu, 02 Nov 2023 11:10:26 +0100
+
+fpga-icestorm (0~20230218gitd20a5e9-1) unstable; urgency=medium
+
+  * New upstream version 0~20230218gitd20a5e9
+
+ -- Daniel Gröber <dxld@darkboxed.org>  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 <dxld@darkboxed.org>  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/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 20:42:04.000000000 +0200
+++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0003-Fix-examples-icemulti-all-target.patch	2023-11-02 11:10:08.000000000 +0100
@@ -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 01:00:00.000000000 +0100
+++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0004-Fix-up5k_rgb-failing-timing-analysis.patch	2023-11-02 11:10:08.000000000 +0100
@@ -0,0 +1,18 @@
+From: =?utf-8?q?Daniel_Gr=C3=B6ber?= <dxld@darkboxed.org>
+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 16:45:36.000000000 +0200
+++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0004-Remove-hard-coded-path-in-icebox_vlog.py.patch	2023-11-02 11:10:08.000000000 +0100
@@ -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 16:45:36.000000000 +0200
+++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/0005-Fix-GCC-10-build-with-missing-include.patch	2023-11-02 11:10:08.000000000 +0100
@@ -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 16:45:22.000000000 +0200
+++ fpga-icestorm-0~20230218gitd20a5e9/debian/patches/series	2023-11-02 11:10:08.000000000 +0100
@@ -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 23:50:15.000000000 +0100
+++ fpga-icestorm-0~20230218gitd20a5e9/debian/tests/control	2023-11-02 11:10:08.000000000 +0100
@@ -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 12:37:29.000000000 +0200
+++ fpga-icestorm-0~20230218gitd20a5e9/docs/io_tile.html	2023-02-18 16:37:53.000000000 +0100
@@ -428,6 +428,9 @@
 
 <tr><td>0 3</td><td><span style="font-family:monospace">PLLCONFIG_8</span></td><td rowspan="1"><span style="font-family:monospace">TEST_MODE</span></td></tr>
 
+<tr><td>0 5</td><td><span style="font-family:monospace">PLLCONFIG_2</span></td><td rowspan="1">Enable ICEGATE for <span style="font-family:monospace">PLLOUTGLOBALA</span></td></tr>
+<tr><td>0 5</td><td><span style="font-family:monospace">PLLCONFIG_4</span></td><td rowspan="1">Enable ICEGATE for <span style="font-family:monospace">PLLOUTGLOBALB</span></td></tr>
+
 </table></td><td>
 
 <table class="ctab">
@@ -502,4 +505,12 @@
 are being used.
 </p>
 
+<p>
+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
+<span style="font-family:monospace">PIN_INPUT_LATCH</span>, then the ICEGATE
+function is enabled for the corresponding <span style="font-family:monospace">CORE</span>
+output of the PLL.
+</p>
+
 </body></html>
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 12:37:29.000000000 +0200
+++ fpga-icestorm-0~20230218gitd20a5e9/icebox/icebox.py	2023-02-18 16:37:53.000000000 +0100
@@ -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 12:37:29.000000000 +0200
+++ fpga-icestorm-0~20230218gitd20a5e9/icebram/icebram.cc	2023-02-18 16:37:53.000000000 +0100
@@ -1,5 +1,6 @@
 //
 //  Copyright (C) 2016  Clifford Wolf <clifford@clifford.at>
+//  Copyright (C) 2023  Sylvain Munaut <tnt@246tNt.com>
 //
 //  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 <stdio.h>
+#include <stdint.h>
 #include <stdlib.h>
+#include <limits.h>
 #include <unistd.h>
-#include <string.h>
-#include <assert.h>
-#include <stdint.h>
 #include <sys/time.h>
 
-#include <map>
-#include <vector>
-#include <string>
+#include <cstring>
 #include <fstream>
 #include <iostream>
+#include <map>
+#include <string>
+#include <valarray>
+#include <vector>
 
 #ifdef __EMSCRIPTEN__
 #include <emscripten.h>
 #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<vector<bool>> &hexfile, const vector<int> &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<std::vector<bool>> m_data;
+	size_t m_word_size;
+
+	std::vector<bool> parse_digits(std::vector<int> &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::vector<bool>, std::pair<std::vector<bool>, 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<bool>(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<bool>
+HexFile::parse_digits(std::vector<int> &digits) const
+{
+	std::vector<bool> 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<vector<bool>> &hexfile, string &line)
+bool
+HexFile::parse_line(std::string &line)
 {
-	vector<int> digits;
+	std::vector<int> 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] <from_hexfile> <to_hexfile>\n", cmd);
-	printf("       %s [options] -g [-s <seed>] <width> <depth>\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 <from_hexfile> later.\n");
-	printf("\n");
-	printf("    -s <seed>\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<bool>(this->m_word_size));
 }
 
-int main(int argc, char **argv)
+std::map<std::vector<bool>, std::pair<std::vector<bool>, 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::vector<bool>, std::pair<std::vector<bool>, 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; i<int(this->m_word_size); i++)
 	{
-		switch (opt)
+		std::vector<bool> pattern_from, pattern_to;
+
+		for (int j=0; j<int(this->m_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<bool> m_data;
+	int m_read_mode;
+
+	int m_pos[2];
+	int m_data_line;
+	int m_config_line;
+	std::vector<std::string> &m_lines;
+
+	friend class AscFile;
+
+protected:
+	void load_data   ();
+	void save_data   ();
+	void load_config ();
+
+public:
+	EBRData(std::vector<std::string> &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::vector<bool>, std::pair<std::vector<bool>, int>> &pattern);
+};
 
-		x =  uint64_t(seed_nr) << 32;
-		x ^= uint64_t(depth) << 16;
-		x ^= uint64_t(width) << 10;
+class AscFile
+{
+private:
+	std::vector<std::string> m_lines;
+	std::map<int, EBRData> 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::vector<bool>, std::pair<std::vector<bool>, 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<std::string> &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::vector<bool>, std::pair<std::vector<bool>, int>> &pattern)
+{
+	const std::map<int, std::vector<int>> subidx_map =  {
+		{ 0, { 0 } },
+		{ 1, { 0, 1 } },
+		{ 2, { 0, 2, 1, 3 } },
+		{ 3, { 0, 4, 2, 6, 1, 5, 3, 7 } },
+	};
+
+	const std::vector<int> &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<bool> fbs(256);
 
-	const char *from_hexfile_n = argv[optind];
-	ifstream from_hexfile_f(from_hexfile_n);
-	vector<vector<bool>> from_hexfile;
+			// Create "From Bit Slice" from local memory
+			for (int oaddr=0; oaddr<256/P; oaddr++)
+				for (int iaddr=0; iaddr<P; iaddr++)
+					fbs.at(oaddr*P+iaddr) = this->m_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; iaddr<P; iaddr++)
+					this->m_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<vector<bool>> 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<bool>(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::vector<bool>, std::pair<std::vector<bool>, 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::vector<bool>, std::pair<std::vector<bool>, 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<vector<bool>, pair<vector<bool>, 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<bool> 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<string> ascfile_lines;
-	map<string, vector<vector<bool>>> 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<bool> 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] <from_hexfile> <to_hexfile>\n", cmd);
+	printf("       %s [options] -g [-s <seed>] <width> <depth>\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 <from_hexfile> later.\n");
+	printf("\n");
+	printf("    -s <seed>\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);
 }

--- End Message ---
--- Begin Message ---
Version: 12.6

The upload requested in this bug has been released as part of 12.6.

--- End Message ---

Reply to: