From 2d61ed7727e16dcee68f654cb72a5613776e2b30 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 29 Dec 2021 14:22:33 -0800 Subject: [PATCH 1/8] First attempt at save_temps --- fud/fud/stages/remote_context.py | 12 +++++++----- fud/fud/stages/xilinx/xclbin.py | 10 ++++++++-- fud/fud/utils.py | 23 +++++++++++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/fud/fud/stages/remote_context.py b/fud/fud/stages/remote_context.py index e55690f77b..18078a5b14 100644 --- a/fud/fud/stages/remote_context.py +++ b/fud/fud/stages/remote_context.py @@ -120,10 +120,11 @@ def run_remote(client: SourceType.UnTyped, tmpdir: SourceType.String): run_remote(client, tmpdir) - def _close(self, client, remote_tmpdir): + def _close(self, client, remote_tmpdir, keep=False): """Close the SSH connection to the server. - Also removes the remote temporary directory. + Also removes the remote temporary directory, unless the `keep` + flag is set. """ @self.stage.step() @@ -131,7 +132,8 @@ def finalize_ssh(client: SourceType.UnTyped, tmpdir: SourceType.String): """ Remove created temporary files and close ssh connection. """ - client.exec_command(f"rm -r {tmpdir}") + if not keep: + client.exec_command(f"rm -r {tmpdir}") client.close() finalize_ssh(client, remote_tmpdir) @@ -159,7 +161,7 @@ def copy_back( copy_back(client, remote_tmpdir, local_tmpdir) self._close(client, remote_tmpdir) - def close_and_get(self, client, remote_tmpdir, path): + def close_and_get(self, client, remote_tmpdir, path, keep=False): """Close the SSH connection and retrieve a single file. Produces the resulting downloaded file. @@ -179,5 +181,5 @@ def fetch_file( return dest_path.open("rb") local_path = fetch_file(client, remote_tmpdir) - self._close(client, remote_tmpdir) + self._close(client, remote_tmpdir, keep=keep) return local_path diff --git a/fud/fud/stages/xilinx/xclbin.py b/fud/fud/stages/xilinx/xclbin.py index 8c84583058..15f835ba16 100644 --- a/fud/fud/stages/xilinx/xclbin.py +++ b/fud/fud/stages/xilinx/xclbin.py @@ -5,7 +5,7 @@ from fud.stages import Source, SourceType, Stage from fud.stages.remote_context import RemoteExecution from fud.stages.futil import FutilStage -from fud.utils import TmpDir, shell +from fud.utils import TmpDir, FreshDir, shell class XilinxStage(Stage): @@ -40,6 +40,8 @@ def __init__(self, config): self.remote_exec = RemoteExecution(self) self.temp_location = self.config["stages", self.name, "temp_location"] + self.save_temps = bool(self.config["stages", self.name, "save_temps"]) + self.mode = self.config["stages", self.name, "mode"] self.device = self.config["stages", self.name, "device"] @@ -75,7 +77,10 @@ def copy_file( """Copy an input file.""" shutil.copyfile(src_path, Path(tmpdir) / dest_path) - tmpdir = Source(TmpDir(), SourceType.Directory) + tmpdir = Source( + FreshDir() if self.save_temps else TmpDir(), + SourceType.Directory, + ) for src_path, dest_path in input_files.items(): if not isinstance(src_path, Source): src_path = Source(src_path, SourceType.Path) @@ -204,6 +209,7 @@ def read_file( client, tmpdir, "xclbin/kernel.xclbin", + keep=self.save_temps, ) else: xclbin = read_file( diff --git a/fud/fud/utils.py b/fud/fud/utils.py index b97dd53d80..c418185277 100644 --- a/fud/fud/utils.py +++ b/fud/fud/utils.py @@ -71,6 +71,7 @@ def remove(self): class TmpDir(Directory): + """A temporary directory that is automatically deleted.""" def __init__(self): self.tmpdir_obj = TemporaryDirectory() self.name = self.tmpdir_obj.name @@ -82,6 +83,28 @@ def __str__(self): return self.name +class FreshDir(Directory): + """A new empty directory for saving results into. + + The directory is created in the current working directory with an + arbitrary name. This way, `FreshDir` works like `TmpDir` except the + directory is not automatically removed. (It can still be manually + deleted, of course.) + """ + def __init__(self): + # Select a name that doesn't exist. + i = 0 + while True: + name = 'fud-out-{}'.format(i) + if not os.path.exists(name): + break + i += 1 + + # Create the directory. + os.mkdir(name) + self.name = os.abspath(name) + + class Conversions: @staticmethod def path_to_directory(data): From 865133ad25b9b90f63dc7120ee213c01bc70274e Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 29 Dec 2021 14:25:33 -0800 Subject: [PATCH 2/8] Fix abspath invocation --- fud/fud/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fud/fud/utils.py b/fud/fud/utils.py index c418185277..7cf8773cc4 100644 --- a/fud/fud/utils.py +++ b/fud/fud/utils.py @@ -72,6 +72,7 @@ def remove(self): class TmpDir(Directory): """A temporary directory that is automatically deleted.""" + def __init__(self): self.tmpdir_obj = TemporaryDirectory() self.name = self.tmpdir_obj.name @@ -91,18 +92,19 @@ class FreshDir(Directory): directory is not automatically removed. (It can still be manually deleted, of course.) """ + def __init__(self): # Select a name that doesn't exist. i = 0 while True: - name = 'fud-out-{}'.format(i) + name = "fud-out-{}".format(i) if not os.path.exists(name): break i += 1 # Create the directory. os.mkdir(name) - self.name = os.abspath(name) + self.name = os.path.abspath(name) class Conversions: From d326e098c5757a4b41122363848b4991547cd678 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 29 Dec 2021 14:32:19 -0800 Subject: [PATCH 3/8] Document save_temps --- docs/fud/synthesis.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/fud/synthesis.md b/docs/fud/synthesis.md index 15c3daa678..3bbf4587c9 100644 --- a/docs/fud/synthesis.md +++ b/docs/fud/synthesis.md @@ -92,6 +92,10 @@ Here's an example of going all the way from a Calyx program to that: On our machines, compiling even a simple example like the above for simulation takes about 2 minutes, end to end. +By default, the Xilinx tools run in a temporary directory that is deleted when `fud` finishes. +To instead keep the sandbox directory, use `-s xclbin.save_temps true`. +You can then find the results in a directory named `fud-out-N` for some number `N`. + ### How it Works The first step is to generate input files. From 6d8e7df4f3135e08b6914b31b90506322381d6f3 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 30 Dec 2021 10:22:50 -0800 Subject: [PATCH 4/8] Fix read_file output function --- fud/fud/stages/xilinx/xclbin.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fud/fud/stages/xilinx/xclbin.py b/fud/fud/stages/xilinx/xclbin.py index 15f835ba16..e9a7ec060a 100644 --- a/fud/fud/stages/xilinx/xclbin.py +++ b/fud/fud/stages/xilinx/xclbin.py @@ -178,9 +178,9 @@ def compile_xclbin(client: SourceType.UnTyped, tmpdir: SourceType.String): def read_file( tmpdir: SourceType.Directory, name: SourceType.String, - ) -> SourceType.Stream: + ) -> SourceType.Path: """Read an output file.""" - return Path(tmpdir.name) / name.data + return Path(tmpdir.name) / name if self.remote_exec.use_ssh: self.remote_exec.import_libs() @@ -205,15 +205,14 @@ def read_file( compile_xclbin(client, tmpdir) if self.remote_exec.use_ssh: - xclbin = self.remote_exec.close_and_get( + return self.remote_exec.close_and_get( client, tmpdir, "xclbin/kernel.xclbin", keep=self.save_temps, ) else: - xclbin = read_file( + return read_file( tmpdir, Source("xclbin/kernel.xclbin", SourceType.String), ) - return xclbin From 1a4e9e672d600aa600ecb6dcfc635bc3c7e1ff5d Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 30 Dec 2021 10:35:09 -0800 Subject: [PATCH 5/8] Little docs fixes * 2 minutes turns out to be what it takes to crash in synthesis. 5 minutes is about what it takes to successfully produce an `xclbin`. * The fud invocation should use `-o` instead of `--to` because the output file is binary. (fud will crash when trying to decode it to a printable string.) --- docs/fud/synthesis.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/fud/synthesis.md b/docs/fud/synthesis.md index 3bbf4587c9..c8d24699d9 100644 --- a/docs/fud/synthesis.md +++ b/docs/fud/synthesis.md @@ -88,9 +88,10 @@ Hopefully someone will figure this out and document it in the future. The first step in the Xilinx toolchain is to generate [an `xclbin` executable file][xclbin]. Here's an example of going all the way from a Calyx program to that: - fud e --to xclbin examples/futil/dot-product.futil + fud e examples/futil/dot-product.futil -o foo.xclbin -On our machines, compiling even a simple example like the above for simulation takes about 2 minutes, end to end. +On our machines, compiling even a simple example like the above for simulation takes about 5 minutes, end to end. +A failed run takes about 2 minutes to produce an error. By default, the Xilinx tools run in a temporary directory that is deleted when `fud` finishes. To instead keep the sandbox directory, use `-s xclbin.save_temps true`. From b5b00901d0fb9313fd06b6da9b2f3cea8143397f Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Thu, 30 Dec 2021 11:36:52 -0800 Subject: [PATCH 6/8] Fix close_and_get in SSH context --- fud/fud/stages/remote_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fud/fud/stages/remote_context.py b/fud/fud/stages/remote_context.py index 18078a5b14..397694f516 100644 --- a/fud/fud/stages/remote_context.py +++ b/fud/fud/stages/remote_context.py @@ -178,7 +178,7 @@ def fetch_file( dest_path = tmpfile.name with self.SCPClient(client.get_transport()) as scp: scp.get(src_path, dest_path) - return dest_path.open("rb") + return Path(dest_path) local_path = fetch_file(client, remote_tmpdir) self._close(client, remote_tmpdir, keep=keep) From 53940e52bf11a3be2ca4c7a1d00be6300610d486 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 5 Jan 2022 09:25:52 -0800 Subject: [PATCH 7/8] Rename `keep` to `keep_tmpdir` --- fud/fud/stages/remote_context.py | 12 ++++++------ fud/fud/stages/xilinx/xclbin.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fud/fud/stages/remote_context.py b/fud/fud/stages/remote_context.py index 397694f516..0df6afde8b 100644 --- a/fud/fud/stages/remote_context.py +++ b/fud/fud/stages/remote_context.py @@ -120,11 +120,11 @@ def run_remote(client: SourceType.UnTyped, tmpdir: SourceType.String): run_remote(client, tmpdir) - def _close(self, client, remote_tmpdir, keep=False): + def _close(self, client, remote_tmpdir, keep_tmpdir=False): """Close the SSH connection to the server. - Also removes the remote temporary directory, unless the `keep` - flag is set. + Also removes the remote temporary directory, unless the + `keep_tmpdir` flag is set. """ @self.stage.step() @@ -132,7 +132,7 @@ def finalize_ssh(client: SourceType.UnTyped, tmpdir: SourceType.String): """ Remove created temporary files and close ssh connection. """ - if not keep: + if not keep_tmpdir: client.exec_command(f"rm -r {tmpdir}") client.close() @@ -161,7 +161,7 @@ def copy_back( copy_back(client, remote_tmpdir, local_tmpdir) self._close(client, remote_tmpdir) - def close_and_get(self, client, remote_tmpdir, path, keep=False): + def close_and_get(self, client, remote_tmpdir, path, keep_tmpdir=False): """Close the SSH connection and retrieve a single file. Produces the resulting downloaded file. @@ -181,5 +181,5 @@ def fetch_file( return Path(dest_path) local_path = fetch_file(client, remote_tmpdir) - self._close(client, remote_tmpdir, keep=keep) + self._close(client, remote_tmpdir, keep_tmpdir=keep_tmpdir) return local_path diff --git a/fud/fud/stages/xilinx/xclbin.py b/fud/fud/stages/xilinx/xclbin.py index e9a7ec060a..487345353c 100644 --- a/fud/fud/stages/xilinx/xclbin.py +++ b/fud/fud/stages/xilinx/xclbin.py @@ -209,7 +209,7 @@ def read_file( client, tmpdir, "xclbin/kernel.xclbin", - keep=self.save_temps, + keep_tmpdir=self.save_temps, ) else: return read_file( From a8d87c459a23c54cef78b11c70fc92d4d6065349 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 5 Jan 2022 09:27:01 -0800 Subject: [PATCH 8/8] Comment about save_temps --- fud/fud/stages/xilinx/xclbin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fud/fud/stages/xilinx/xclbin.py b/fud/fud/stages/xilinx/xclbin.py index 487345353c..22a04aa472 100644 --- a/fud/fud/stages/xilinx/xclbin.py +++ b/fud/fud/stages/xilinx/xclbin.py @@ -40,6 +40,8 @@ def __init__(self, config): self.remote_exec = RemoteExecution(self) self.temp_location = self.config["stages", self.name, "temp_location"] + # As a debugging aid, the pass can optionally preserve the + # (local or remote) sandbox where the Xilinx commands ran. self.save_temps = bool(self.config["stages", self.name, "save_temps"]) self.mode = self.config["stages", self.name, "mode"]