import atexit import getopt import os import subprocess import signal import sys import time import pathlib import platform progname = sys.argv[0] diagnostics = False quiet = False verbose = False port = 80 machine = "eecs-digital-56.mit.edu" projectdir = "." of = "obj" p = False user = "builder" outfile = f"{of}/out.bit" logfile = f"{of}/build.log" synthrpt = [ "report_timing", "report_timing_summary", "report_utilization", ] placerpt = synthrpt.copy() placerpt.extend(["report_clock_utilization"]) routerpt = [ "report_drc", "report_power", "report_route_status", "report_timing", "report_timing_summary", ] usagestr = f""" {progname}: build SystemVerilog code remotely for 2022 6.205 labs usage: {progname} [-dqv] [-m machine] [-p projectdir] [-o dir] options: -d: emit additional diagnostics during synthesis/implementation -q: quiet: do not generate any vivado logs except for errors. -v: be verbose (for debugging stuffs / if you see a bug) -m: override the DNS name queried to perform the build. use with care. -p: build the project located in projectdir (default is '.') -o: set the output products directory (default is {of}) """ def debuglog(s): if verbose: print(s) def usage(): print(usagestr) sys.exit(1) def getargs(): global diagnostics global quiet global machine global logfile global outfile global projectdir global of global verbose try: opts, args = getopt.getopt(sys.argv[1:], "dm:o:p:qv") except getopt.GetoptError as err: print(err) usage() if args: usage() for o, v in opts: if o == "-d": diagnostics = True elif o == "-q": quiet = True elif o == "-m": machine = v elif o == "-p": projectdir = v elif o == "-o": of = v elif o == "-v": verbose = True else: print(f"unrecognized option {o}") usage() outfile = f"{of}/out.bit" logfile = f"{of}/build.log" def make_posix(path): return str(pathlib.Path(path).as_posix()) def regfiles(): ftt = {} debuglog(f"projectdir is {projectdir}") for dirpath, subdirs, files in os.walk(projectdir): if ( "src" not in dirpath and "xdc" not in dirpath and "data" not in dirpath and "ip" not in dirpath ): continue if dirpath.startswith("./"): dirpath = dirpath[2:] for file in files: fpath = os.path.join(dirpath, file) debuglog(f"considering {fpath}") fpath = make_posix(fpath) if file.lower().endswith(".v"): ftt[fpath] = "source" elif file.lower().endswith(".sv"): ftt[fpath] = "source" elif file.lower().endswith(".vh"): ftt[fpath] = "source" elif file.lower().endswith(".svh"): ftt[fpath] = "source" elif file.lower().endswith(".xdc"): ftt[fpath] = "xdc" elif file.lower().endswith(".mem"): ftt[fpath] = "mem" elif file.lower().endswith(".xci"): ftt[fpath] = "ip" elif file.lower().endswith(".prj"): ftt[fpath] = "mig" debuglog(f"elaborated file list {ftt}") return ftt # messages are newline delineated per lab-bs.1 # utilize this to cheat a little bit def spqsend(p, msg): debuglog(f"writing {len(msg)} bytes over the wire") debuglog(f"full message: {msg}") p.stdin.write(msg + b"\n") p.stdin.flush() def spsend(p, msg): debuglog(f"running {msg}") p.stdin.write((msg + "\n").encode()) p.stdin.flush() def sprecv(p): l = p.stdout.readline().decode() debuglog(f"got {l}") return l def xsprecv(p): l = sprecv(p) if l.startswith("ERR"): print("received unexpected server error!") print(l) sys.exit(1) return l def spstart(xargv): debuglog(f"spawning {xargv}") p = subprocess.PIPE return subprocess.Popen(xargv, stdin=p, stdout=p, stderr=p) def copyfiles(p, ftt): for f, t in ftt.items(): fsize = os.path.getsize(f) with open(f, "rb") as fd: spsend(p, f"write {f} {fsize}") time.sleep(0.1) # ? spqsend(p, fd.read()) xsprecv(p) spsend(p, f"type {f} {t}") xsprecv(p) # size message returns ... %zu bytes def readfile(p, file, targetfile): spsend(p, f"size {file}") size = int(xsprecv(p).split()[-2]) spsend(p, f"read {file}") with open(targetfile, "wb+") as fd: fd.write(p.stdout.read(size)) xsprecv(p) def build(p): cmd = "build" if diagnostics: cmd += " -d" if quiet: cmd += " -q" cmd += f" obj" print(f"Output target will be {outfile}") spsend(p, cmd) print("Building your code ... (this may take a while, be patient)") result = sprecv(p) if result.startswith("ERR"): print("Something went wrong!") else: readfile(p, "obj/out.bit", outfile) print(f"Build succeeded, output at {outfile}") readfile(p, "obj/build.log", logfile) print(f"Log file available at {logfile}") if diagnostics: for rpt in synthrpt: readfile(p, f"obj/synthrpt_{rpt}.rpt", f"{of}/synthrpt_{rpt}.rpt") for rpt in placerpt: readfile(p, f"obj/placerpt_{rpt}.rpt", f"{of}/placerpt_{rpt}.rpt") for rpt in routerpt: readfile(p, f"obj/routerpt_{rpt}.rpt", f"{of}/routerpt_{rpt}.rpt") print(f"Diagnostics available in {of}") def main(): global p getargs() ftt = regfiles() if not os.path.isdir(of): print(f"output path {of} does not exist! create it or use -o?") usage() if platform.system() == "Darwin" or platform.system() == "Linux": xargv = [ "ssh", "-p", f"{port}", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", ] elif platform.system() == "Windows": xargv = [ "ssh", "-p", f"{port}", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=nul", ] else: raise RuntimeError( "Your OS is not recognized, unsure of how to format SSH command." ) xargv.append(f"{user}@{machine}") p = spstart(xargv) spsend(p, "help") result = xsprecv(p) debuglog(result) copyfiles(p, ftt) build(p) spsend(p, "exit") p.wait() if __name__ == "__main__": try: main() except (Exception, KeyboardInterrupt) as e: if p: debuglog("killing ssh") os.kill(p.pid, signal.SIGINT) p.wait() raise e