302 lines
6.9 KiB
Python
302 lines
6.9 KiB
Python
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
|