Skip to content

Commit

Permalink
[fud] Add save_temps option to Xilinx synthesis (#851)
Browse files Browse the repository at this point in the history
* First attempt at save_temps

* Fix abspath invocation

* Document save_temps

* Fix read_file output function

* 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.)

* Fix close_and_get in SSH context

* Rename `keep` to `keep_tmpdir`

* Comment about save_temps
  • Loading branch information
sampsyo authored Jan 5, 2022
1 parent 2a6705a commit 6b701db
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 15 deletions.
9 changes: 7 additions & 2 deletions docs/fud/synthesis.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,14 @@ 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`.
You can then find the results in a directory named `fud-out-N` for some number `N`.

### How it Works

Expand Down
14 changes: 8 additions & 6 deletions fud/fud/stages/remote_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,20 @@ 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_tmpdir=False):
"""Close the SSH connection to the server.
Also removes the remote temporary directory.
Also removes the remote temporary directory, unless the
`keep_tmpdir` flag is set.
"""

@self.stage.step()
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_tmpdir:
client.exec_command(f"rm -r {tmpdir}")
client.close()

finalize_ssh(client, remote_tmpdir)
Expand Down Expand Up @@ -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_tmpdir=False):
"""Close the SSH connection and retrieve a single file.
Produces the resulting downloaded file.
Expand All @@ -176,8 +178,8 @@ 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)
self._close(client, remote_tmpdir, keep_tmpdir=keep_tmpdir)
return local_path
21 changes: 14 additions & 7 deletions fud/fud/stages/xilinx/xclbin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -40,6 +40,10 @@ 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"]
self.device = self.config["stages", self.name, "device"]

Expand Down Expand Up @@ -75,7 +79,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)
Expand Down Expand Up @@ -173,9 +180,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()
Expand All @@ -200,14 +207,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_tmpdir=self.save_temps,
)
else:
xclbin = read_file(
return read_file(
tmpdir,
Source("xclbin/kernel.xclbin", SourceType.String),
)
return xclbin
25 changes: 25 additions & 0 deletions fud/fud/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ 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
Expand All @@ -82,6 +84,29 @@ 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.path.abspath(name)


class Conversions:
@staticmethod
def path_to_directory(data):
Expand Down

0 comments on commit 6b701db

Please sign in to comment.