[Pkg-electronics-devel] Bug#1055214: bookworm-pu: package fpga-icestorm/0~20220915gita545498-3

Daniel Gröber dxld at darkboxed.org
Thu Nov 2 10:36:23 GMT 2023


Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian.org at packages.debian.org
Usertags: pu
X-Debbugs-Cc: fpga-icestorm at packages.debian.org, dxld at 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
-------------- next part --------------
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 at 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 at 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 at 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 at 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 at clifford.at>
+//  Copyright (C) 2023  Sylvain Munaut <tnt at 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);
 }


More information about the Pkg-electronics-devel mailing list