diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0756392 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*__pycache__ +*egg-info \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..b49c24c --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +Copyright (c) 2022, Joppe Blondel + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/remotesyn/Heartbeat.py b/remotesyn/Heartbeat.py new file mode 100644 index 0000000..20af685 --- /dev/null +++ b/remotesyn/Heartbeat.py @@ -0,0 +1,34 @@ +import threading +import time +import base64 + +class Heartbeat(threading.Thread): + def __init__(self, channel): + threading.Thread.__init__(self) + self.channel = channel + self.running = True + self.printing = False + def stop(self): + self.running = False + def run(self): + while self.running: + if self.printing: + print('.', end='', flush=True) + self.channel.exec_command(base64.encodebytes(b'hb')) + time.sleep(2) + +class HeartbeatChecker(threading.Thread): + def __init__(self, server): + threading.Thread.__init__(self) + self.server = server + self.running = True + self.hb = True + def stop(self): + self.running = False + def run(self): + while self.running: + if not self.hb: + self.server.active = False + self.server.event.set() + self.hb = False + time.sleep(5) \ No newline at end of file diff --git a/remotesyn/__init__.py b/remotesyn/__init__.py new file mode 100644 index 0000000..00ee89c --- /dev/null +++ b/remotesyn/__init__.py @@ -0,0 +1,200 @@ +from .exec_ISE import exec_ISE +from .exec_VIVADO import exec_VIVADO +from .exec_REMOTE import exec_REMOTE + +from .Heartbeat import Heartbeat, HeartbeatChecker + +import sys +import os +import shutil +import subprocess +import paramiko +import socket +import threading +import struct +import base64 + +class Executer(threading.Thread): + def __init__(self, args, channel, identifier): + threading.Thread.__init__(self) + self.args = args + self.channel = channel + self.identifier = identifier + self.pid = None + def run(self): + self.pid = subprocess.Popen(f"../{sys.argv[0]} -l -c project.cfg {self.args}", shell=True, cwd=f'{self.identifier}', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + res = self.pid.wait() + self.pid = None + self.channel.sendall(struct.pack('>I', res)) + +class FileTransferSF(threading.Thread): + def __init__(self, channel, fname, identifier, fsize): + threading.Thread.__init__(self) + self.channel = channel + self.fname = fname + self.identifier = identifier + self.fsize = fsize + def run(self): + with open(f"{self.identifier}/{self.fname}", 'wb') as f: + fsize = self.fsize + while fsize>0: + fdata = self.channel.recv(1024) + f.write(fdata) + fsize -= 1024 + +class FileTransferRF(threading.Thread): + def __init__(self, channel, fname, identifier): + threading.Thread.__init__(self) + self.channel = channel + self.fname = fname + self.identifier = identifier + def run(self): + with open(f"{self.identifier}/{self.fname}", 'rb') as f: + stat = os.fstat(f.fileno()) + print(' -> fsize', stat.st_size) + fsize = struct.pack('>q', stat.st_size) + i = stat.st_size + self.channel.sendall(b'OK'+fsize) + while i>0: + fdata = f.read(1024) + self.channel.sendall(fdata) + i -= 1024 + +class Server(paramiko.ServerInterface): + def __init__(self, authorized): + self.event = threading.Event() + self.authorized = authorized + self.active = True + self.hbchecker = HeartbeatChecker(self) + self.hbchecker.start() + self.processes = [] + + def stopall(self): + print("Stop all running processes") + for p in self.processes: + if p.pid is not None: + p.pid.terminate() + + def check_channel_request(self, kind, chanid): + if kind == 'session': + return paramiko.OPEN_SUCCEEDED + + def check_auth_publickey(self, username, key): + keyascii = key.get_base64() + for auth in self.authorized: + authascii = auth.split(' ')[1] + if authascii==keyascii: + return paramiko.AUTH_SUCCESSFUL + return paramiko.AUTH_FAILED + + def get_allowed_auths(self, username): + return 'publickey' + + def rstr(self, b): + l = struct.unpack('>I', b[:4])[0] + return (bytes.decode(b[4:4+l], 'utf-8'), b[4+l:]) + + def sstr(self, s): + return struct.pack('>I', len(s)) + s.encode('utf-8') + + def check_channel_exec_request(self, channel, command): + # self.event.set() + command = base64.decodebytes(command) + cmd = command[:2] + data = command[2:] + + if cmd==b'id': + identifier = struct.unpack('>q', data[:8])[0] + self.identifier = str(identifier) + print('>', identifier) + # Create directory + if os.path.exists(str(identifier)): + shutil.rmtree(str(identifier)) + os.mkdir(str(identifier)) + + elif cmd==b'ex': + print('<', self.identifier) + #shutil.rmtree(str(self.identifier)) + self.active = False + self.hbchecker.stop() + self.event.set() + + elif cmd==b'hb': + self.hbchecker.hb = True + + # List files + elif cmd==b'ls': + dr, data = self.rstr(data) + print('ls', dr) + if not os.path.exists(f"{self.identifier}/{dr}"): + channel.sendall(b'ERFile not found') + es = [] + for f in os.listdir(f'{self.identifier}/{dr}'): + if os.path.isfile(f'{self.identifier}/{dr}/{f}'): + df = 'f' + else: + df = 'd' + es.append(f'{df}{f}') + channel.sendall(b'OK' + self.sstr('\n'.join(es))) + + # Send file + elif cmd==b'sf': + fname, data = self.rstr(data) + fsize = struct.unpack('>q', data)[0] + print('>>', fname, fsize) + os.makedirs(os.path.dirname(f"{self.identifier}/{fname}"), exist_ok=True) + channel.sendall(b'OK\n') + FileTransferSF(channel, fname, self.identifier, fsize).start() + + # Receive file + elif cmd==b'rf': + fname, data = self.rstr(data) + print('<<', fname) + if not os.path.exists(f"{self.identifier}/{fname}"): + channel.sendall(b'ERFile not found') + else: + FileTransferRF(channel, fname, self.identifier).start() + + # Execute synth + elif cmd==b'do': + args, data = self.rstr(data) + print('[]', args) + executer = Executer(args, channel, self.identifier) + executer.start() + self.processes.append(executer) + + return True + +class Connection(threading.Thread): + def __init__(self, client, addr, host_key, authorized): + threading.Thread.__init__(self) + self.client = client + self.addr = addr + self.host_key = host_key + self.running = True + self.authorized = authorized + print(f"Connection from {addr}") + + def stop(self): + self.running = False + self.server.event.set() + + def run(self): + self.running = True + t = paramiko.Transport(self.client) + t.set_gss_host(socket.getfqdn("")) + t.load_server_moduli() + t.add_server_key(self.host_key) + server = Server(self.authorized) + t.start_server(server=server) + self.server = server + + # Wait for the event + while server.active: + server.event.wait(10) + + server.stopall() + shutil.rmtree(server.identifier) + + t.close() + print('connection closed') \ No newline at end of file diff --git a/remotesyn/exec_ISE.py b/remotesyn/exec_ISE.py new file mode 100644 index 0000000..fb833da --- /dev/null +++ b/remotesyn/exec_ISE.py @@ -0,0 +1,349 @@ +import os +import shutil +import subprocess +import time + +class exec_ISE: + def __init__(self, config, builddir): + self.config = config + self.builddir = builddir + self.create_builddir() + + def create_outdir(self, target): + self.outdir = self.config.get('project', 'out_dir', fallback='OUT') + if not os.path.exists(self.outdir): + os.mkdir(self.outdir) + if not os.path.exists(f'{self.outdir}/{target}'): + os.mkdir(f'{self.outdir}/{target}') + + def create_builddir(self): + if not os.path.exists(self.builddir): + os.mkdir(self.builddir) + + def enter_builddir(self): + self.curdir = os.getcwd() + os.chdir(self.builddir) + + def leave_builddir(self): + os.chdir(self.curdir) + + def do_ip_gen(self, target): + # get used IP's for target + ips = self.config.get(target, 'src_ip', fallback='').split() + self.create_outdir(target) + + print("+ Generate IPs") + + for i, ip in enumerate(ips): + # Create cgp file + with open(f'{self.builddir}/coregen_{i}.cgp', 'w') as f: + f.write(f'SET busformat = BusFormatAngleBracketNotRipped\n') + f.write(f'SET designentry = VHDL\n') + f.write(f'SET device = {self.config.get("target", "device")}\n') + f.write(f'SET devicefamily = {self.config.get("target", "family")}\n') + f.write(f'SET package = {self.config.get("target", "package")}\n') + f.write(f'SET speedgrade = {self.config.get("target", "speedgrade")}\n') + f.write(f'SET flowvendor = Other\n') + f.write(f'SET verilogsim = true\n') + f.write(f'SET vhdlsim = true\n') + # crete xco file + with open(f'{self.builddir}/coregen_{i}.xco', 'w') as f: + ipsec = 'ip_%s'%ip + f.write(f'SELECT {ip} {self.config.get(ipsec, ipsec)}\n') + for s in self.config[ipsec]: + if s==ipsec: + continue + f.write(f'CSET {s}={self.config.get(ipsec, s)}\n') + f.write('GENERATE') + # Clear log + if os.path.exists(f'{self.builddir}/coregen.log'): + os.remove(f'{self.builddir}/coregen.log') + # Run coregen + pid = subprocess.Popen(f'coregen -p coregen_{i}.cgp -b coregen_{i}.xco', shell=True, cwd=self.builddir, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + + if not os.path.exists(f'{self.outdir}/{target}/{ip}'): + os.mkdir(f'{self.outdir}/{target}/{ip}') + # Copy files to output directory if succeeded + if res == 0: + shutil.copyfile(f'{self.builddir}/{ip}.vhd', f'{self.outdir}/{target}/{ip}/{ip}.vhd') + shutil.copyfile(f'{self.builddir}/{ip}.v', f'{self.outdir}/{target}/{ip}/{ip}.v') + shutil.copyfile(f'{self.builddir}/{ip}.ngc', f'{self.outdir}/{target}/{ip}/{ip}.ngc') + # Copy log + shutil.copyfile(f'{self.builddir}/coregen.log', f'{self.outdir}/{target}/{ip}/{ip}.log') + + if res!=0: + exit(res) + + def do_synthesize(self, target): + self.create_outdir(target) + self.enter_builddir() + + print('+ Synthesize') + + extra_opts = self.config.get(target, 'extra_options').split('\n') + opts = {} + for o in extra_opts: + tp = o.split()[0] + op = ' '.join(o.split()[1:]) + opts[tp] = op + + if 'xst' not in opts: + opts['xst'] = '' + + if 'netgen' not in opts: + opts['netgen'] = '' + + with open('syn.prj', 'w') as f: + src = self.config.get(target, 'src_vhdl', fallback='').split() + for s in src: + f.write(f'vhdl work "{self.curdir}/{s}"\n') + src = self.config.get(target, 'src_verilog', fallback='').split() + for s in src: + f.write(f'verilog work "{self.curdir}/{s}"\n') + src = self.config.get(target, 'src_sysverilog', fallback='').split() + for s in src: + f.write(f'verilog work "{self.curdir}/{s}"\n') + src = self.config.get(target, 'src_ip', fallback='').split() + for s in src: + f.write(f'vhdl work "{self.curdir}/{self.outdir}/{target}/{s}/{s}.vhd"\n') + with open('prj.scr', 'w') as f: + f.write('run\n-ifn syn.prj\n-ofn syn.ngc\n-ifmt mixed\n') + f.write(f"-top {self.config.get(target, 'toplevel', fallback='_top_')}\n") + f.write(f"-p {self.config.get('target', 'device', fallback='_d_')}") + f.write(self.config.get('target', 'speedgrade', fallback='_s_')) + f.write(f"-{self.config.get('target', 'package', fallback='_p_')}") + f.write(f"\n{opts['xst']}\n") + + pid = subprocess.Popen(f'xst -intstyle xflow -ifn prj.scr', shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + if res!=0: + self.leave_builddir() + exit(res) + + success = True + pid = subprocess.Popen(f"netgen -intstyle xflow -sim -ofmt verilog -w -insert_glbl true {opts['netgen']} syn.ngc", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + if res!=0: + success = False + exit(res) + + self.leave_builddir() + + if success: + if not os.path.exists(f'{self.outdir}/{target}'): + os.mkdir(f'{self.outdir}/{target}') + # Copy files to output directory if succeeded + shutil.copyfile(f'{self.builddir}/syn.ngc', f'{self.outdir}/{target}/{target}.ngc') + shutil.copyfile(f'{self.builddir}/syn.v', f'{self.outdir}/{target}/{target}.v') + shutil.copyfile(f'{self.builddir}/prj.srp', f'{self.outdir}/{target}/syn.log') + + def do_implement(self, target): + self.create_outdir(target) + self.enter_builddir() + + print('+ Implement') + + extra_opts = self.config.get(target, 'extra_options').split('\n') + opts = {} + for o in extra_opts: + tp = o.split()[0] + op = ' '.join(o.split()[1:]) + opts[tp] = op + + if 'ngd' not in opts: + opts['ngd'] = '' + + if 'map' not in opts: + opts['map'] = '' + + if 'par' not in opts: + opts['par'] = '' + + part = f"{self.config.get('target', 'device', fallback='_d_')}{self.config.get('target', 'speedgrade', fallback='_s_')}-{self.config.get('target', 'package', fallback='_p_')}" + cons = self.config.get(target, 'src_constraints', fallback='__con__') + pid = subprocess.Popen(f"ngdbuild -intstyle xflow -p {part} -uc {self.curdir}/{cons} {opts['ngd']} {self.curdir}/{self.outdir}/{target}/{target}.ngc impl.ngd", shell=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + shutil.copyfile(f'impl.bld', f'{self.curdir}/{self.outdir}/{target}/impl-ngd.log') + if res!=0: + self.leave_builddir() + exit(res) + + pid = subprocess.Popen(f"map -intstyle xflow -detail -p {part} {opts['map']} -w impl.ngd -o impl.map.ncd impl.pcf", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + shutil.copyfile(f'impl.map.mrp', f'{self.curdir}/{self.outdir}/{target}/impl-map.log') + if res!=0: + self.leave_builddir() + exit(res) + + pid = subprocess.Popen(f"par -intstyle xflow {opts['par']} -w impl.map.ncd impl.pcf | tee impl.par.log", shell=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + shutil.copyfile(f'impl.par.log', f'{self.curdir}/{self.outdir}/{target}/impl-par.log') + if res!=0: + self.leave_builddir() + exit(res) + + pid = subprocess.Popen(f"netgen -intstyle xflow -sim -ofmt verilog -w -insert_glbl true -sdf_anno true {opts['netgen']} impl.map.ncd", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + if res!=0: + self.leave_builddir() + exit(res) + + self.leave_builddir() + + shutil.copyfile(f'{self.builddir}/impl.map.v', f'{self.outdir}/{target}/{target}.map.v') + shutil.copyfile(f'{self.builddir}/impl.map.sdf', f'{self.outdir}/{target}/{target}.map.sdf') + shutil.copyfile(f'{self.builddir}/impl.pcf.ncd', f'{self.outdir}/{target}/{target}.ncd') + shutil.copyfile(f'{self.builddir}/impl.pcf', f'{self.outdir}/{target}/{target}.pcf') + + def do_bit(self, target): + self.create_outdir(target) + self.enter_builddir() + + print('+ Generate output files') + + extra_opts = self.config.get(target, 'extra_options').split('\n') + opts = {} + for o in extra_opts: + tp = o.split()[0] + op = ' '.join(o.split()[1:]) + opts[tp] = op + + if 'bitgen' not in opts: + opts['bitgen'] = '' + + if 'trce' not in opts: + opts['trce'] = '' + + pid = subprocess.Popen(f"bitgen -intstyle xflow -g Binary:Yes -w {opts['bitgen']} {self.curdir}/{self.outdir}/{target}/{target}.ncd bit.bit {self.curdir}/{self.outdir}/{target}/{target}.pcf", shell=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + self.leave_builddir() + shutil.copyfile(f'{self.builddir}/bit.bit', f'{self.outdir}/{target}/{target}.bit') + shutil.copyfile(f'{self.builddir}/bit.bin', f'{self.outdir}/{target}/{target}.bin') + if res!=0: + exit(res) + + self.enter_builddir() + pid = subprocess.Popen(f"trce -intstyle xflow {opts['trce']} {self.curdir}/{self.outdir}/{target}/{target}.ncd {self.curdir}/{self.outdir}/{target}/{target}.pcf", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + self.leave_builddir() + shutil.copyfile(f'{self.builddir}/{target}.twr', f'{self.outdir}/{target}/timing.log') + if res!=0: + exit(res) + + def do_floorplan(self, target): + self.create_outdir(target) + self.enter_builddir() + part = f"{self.config.get('target', 'device', fallback='_d_')}{self.config.get('target', 'package', fallback='_p_')}{self.config.get('target', 'speedgrade', fallback='_s_')}" + cons = f"{self.curdir}/{self.config.get(target, 'src_constraints', fallback='__con__')}" + with open('paproj.tcl', 'w') as f: + f.write(f'create_project -name paproj -dir paproj -part {part} -force\n') + f.write('set_property design_mode GateLvl [get_property srcset [current_run -impl]]\n') + f.write(f"set_property edif_top_file {self.curdir}/{self.outdir}/{target}/{target}.ngc [get_property srcset [current_run]]\n") + f.write(f"add_files [list {{{cons}}}] -fileset [get_property constrset [current_run]]\n") + f.write(f"set_property target_constrs_file {cons} [current_fileset -constrset]\n") + f.write(f"link_design\nread_xdl -file {self.curdir}/{self.outdir}/{target}/{target}.ncd\n") + pid = subprocess.Popen('planAhead -source paproj.tcl', shell=True) #, stdout=subprocess.DEVNULL, stderr=None) + res = pid.wait() + self.leave_builddir() + + def do_sim(self, target): + self.create_outdir(target) + self.enter_builddir() + + print('+ Simulate') + + if os.path.exists('isim'): + shutil.rmtree('isim') + + with open('sim.prj', 'w') as f: + src = self.config.get(target, 'src_vhdl', fallback='').split() + for s in src: + f.write(f'vhdl work "{self.curdir}/{s}"\n') + src = self.config.get(target, 'src_verilog', fallback='').split() + for s in src: + f.write(f'verilog work "{self.curdir}/{s}"\n') + src = self.config.get(target, 'src_sysverilog', fallback='').split() + for s in src: + f.write(f'verilog work "{self.curdir}/{s}"\n') + # TODO add IP + + extras = '' + if self.config.get(target, 'simtype', fallback='presim') == 'postsim': + extras = f"--{self.config.get(target, 'delay', fallback='typ')}delay work.glbl" + + pid = subprocess.Popen(f"fuse {extras} work.{self.config.get(target, 'toplevel', fallback='toplevel')} -prj sim.prj -o sim", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + if res!=0: + self.leave_builddir() + shutil.copyfile(f'{self.builddir}/fuse.log', f'{self.outdir}/{target}/synth.log') + exit(res) + + with open('sim.tcl', 'w') as f: + f.write("onerror {resume}\n") + f.write("vcd dumpfile sim.vcd\n") + f.write(f"vcd dumpvars -m {self.config.get(target, 'toplevel', fallback='toplevel')} -l {self.config.get(target, 'levels', fallback='10')}\n") + f.write("vcd dumpon\n") + f.write(f"run {self.config.get(target, 'runtime', fallback='100 ns')}\n") + f.write("vcd dumpflush\nquit\n") + + extras = '' + if self.config.get(target, 'simtype', fallback='presim') == 'postsim': + extras = f"-sdf{self.config.get(target, 'delay', fallback='typ')} {self.config.get(target, 'sdfroot', fallback='dut')}={self.curdir}/{self.config.get(target, 'src_sdf', fallback='_s_')}" + + pid = subprocess.Popen(f'./sim -tclbatch sim.tcl {extras} > sim.log', shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + res = pid.returncode + print('') + if res!=0: + exit(res) + + self.leave_builddir() + + if not os.path.exists(f'{self.outdir}/{target}'): + os.mkdir(f'{self.outdir}/{target}') + shutil.copyfile(f'{self.builddir}/sim.vcd', f'{self.outdir}/{target}/output.vcd') + shutil.copyfile(f'{self.builddir}/fuse.log', f'{self.outdir}/{target}/synth.log') + shutil.copyfile(f'{self.builddir}/sim.log', f'{self.outdir}/{target}/output.log') \ No newline at end of file diff --git a/remotesyn/exec_REMOTE.py b/remotesyn/exec_REMOTE.py new file mode 100644 index 0000000..44253cf --- /dev/null +++ b/remotesyn/exec_REMOTE.py @@ -0,0 +1,295 @@ +import os +import paramiko +import struct +import base64 + +from .Heartbeat import Heartbeat + +class exec_REMOTE: + def __init__(self, config, configfile): + self.config = config + self.configfile = configfile + + self.privkey = self.config.get('server', 'privkey', fallback='__privkey__') + self.pubkey = self.config.get('server', 'pubkey', fallback='__pubkey__') + self.hostname = self.config.get('server', 'hostname', fallback='__hostname__') + self.port = self.config.get('server', 'port', fallback='__port__') + + self.tc = self.config.get('project', 'toolchain', fallback='ISE') + + if self.privkey=='__privkey__' or self.pubkey=='__pubkey__' or self.hostname=='__hostname__' or self.port=='__port__': + print("Not enough server information in the config file") + exit(1) + + self.host_key = paramiko.RSAKey(filename=self.privkey) + client = paramiko.SSHClient() + client.load_system_host_keys() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + trans = paramiko.Transport((self.hostname, int(self.port))) + trans.connect(None, pkey=self.host_key) + + self.channel = trans.open_channel('session') + self.hbchannel = trans.open_channel('session') + + self.heartbeat = Heartbeat(self.hbchannel) + self.heartbeat.start() + + # Send project identification + cmd = b'id' + struct.pack('>q', hash(self.host_key.get_base64())) + self.channel.exec_command(base64.encodebytes(cmd)) + + def __del__(self): + self.heartbeat.stop() + self.channel.exec_command(base64.encodebytes(b'ex')) + + def cmd(self, cmd): + self.channel.exec_command(base64.encodebytes(cmd)) + + def sstr(self, s): + return struct.pack('>I', len(s)) + s.encode('utf-8') + + def rstr(self): + l = struct.unpack('>I', self.channel.recv(4))[0] + return bytes.decode(self.channel.recv(l), 'utf-8') + + def recv_dir(self, dr): + self.cmd(b'ls'+self.sstr(dr)) + status = self.channel.recv(2) + if status!=b'OK': + msg = self.channel.recv(1024) + print("Error:", bytes.decode(msg, 'ascii')) + exit(1) + ls = self.rstr() + for p in ls.split('\n'): + tp = p[0] + name = p[1:] + if tp=='d': + self.recv_dir(f'{dr}/{name}') + else: + self.recv_file(f'{dr}/{name}') + + def send_file(self, file, othername=None): + print(f"> {file}") + if not os.path.exists(file): + print(f"Error: {file} does not exists") + with open(file, 'rb') as f: + stat = os.fstat(f.fileno()) + print(' -> fsize', stat.st_size) + if othername is None: + othername = file + fsize = struct.pack('>q', stat.st_size) + + self.cmd(b'sf'+self.sstr(othername)+fsize) + + status = self.channel.recv(3) + if status!=b'OK\n': + print('Something went wrong...') + exit(1) + + i = stat.st_size + while i>0: + fdata = f.read(1024) + i -= 1024 + self.channel.sendall(fdata) + + def recv_file(self, file): + print(f"< {file}") + if os.path.dirname(file) != '': + os.makedirs(os.path.dirname(file), exist_ok=True) + with open(file, 'wb') as f: + self.cmd(b'rf'+self.sstr(file)) + status = self.channel.recv(2) + if status!=b'OK': + msg = self.channel.recv(1024) + print("Error:", bytes.decode(msg, 'ascii')) + exit(1) + fsize = self.channel.recv(8) + fsize = struct.unpack('>q', fsize)[0] + print(' -> fsize', fsize) + while fsize>0: + f.write(self.channel.recv(1024)) + fsize -= 1024 + + def do_ip_gen(self, target): + print("+ Generate IPs") + self.send_file(self.configfile, 'project.cfg') + self.heartbeat.printing = True + self.cmd(b'do'+self.sstr(f'ip {target}')) + res = struct.unpack('>I', self.channel.recv(4))[0] + self.heartbeat.printing = False + print(f' [{res}]') + + # get used IP's for target + ips = self.config.get(target, 'src_ip', fallback='').split() + for i, ip in enumerate(ips): + self.recv_dir(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{ip}") + + if res != 0: + print("Some error occured...") + exit(1) + + def do_synthesize(self, target): + print("+ Synthesize") + + self.send_file(self.configfile, 'project.cfg') + src = self.config.get(target, 'src_vhdl', fallback='').split() + for s in src: + self.send_file(s) + src = self.config.get(target, 'src_verilog', fallback='').split() + for s in src: + self.send_file(s) + src = self.config.get(target, 'src_sysverilog', fallback='').split() + for s in src: + self.send_file(s) + src = self.config.get(target, 'src_ip', fallback='').split() + for s in src: + if self.tc=='ISE': + self.send_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{s}/{s}.vhd") + elif self.tc=="VIVADO": + self.send_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{s}/{s}.xci") + if self.tc=="VIVADO": + self.send_file(f"{self.config.get(target, 'src_constraints', fallback='__con__')}") + + self.heartbeat.printing = True + self.cmd(b'do'+self.sstr(f'syn {target}')) + res = struct.unpack('>I', self.channel.recv(4))[0] + self.heartbeat.printing = False + print(f' [{res}]') + + if res != 0: + print("Some error occured...") + if self.tc=='ISE': + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/syn.log") + elif self.tc=="VIVADO": + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/synth.log") + exit(1) + + if self.tc=='ISE': + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.ngc") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.v") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/syn.log") + elif self.tc=="VIVADO": + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/synth.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/post_synth.dcp") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/synth_netlist.v") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/synth_netlist.sdf") + + def do_implement(self, target): + print("+ Implement") + + self.send_file(self.configfile, 'project.cfg') + if self.tc=='ISE': + self.send_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.ngc") + self.send_file(f"{self.config.get(target, 'src_constraints', fallback='__con__')}") + elif self.tc=="VIVADO": + self.send_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/post_synth.dcp") + + self.heartbeat.printing = True + self.cmd(b'do'+self.sstr(f'impl {target}')) + res = struct.unpack('>I', self.channel.recv(4))[0] + self.heartbeat.printing = False + print(f' [{res}]') + + if res != 0: + print("Some error occured...") + if self.tc=='ISE': + # FIXME possible that not all are there + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl-ngd.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl-map.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl-par.log") + elif self.tc=="VIVADO": + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl.log") + exit(1) + + if self.tc=='ISE': + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl-ngd.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl-map.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl-par.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.map.v") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.map.sdf") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.ncd") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.pcf") + elif self.tc=="VIVADO": + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/post_impl.dcp") + + def do_bit(self, target): + print("+ Generate output files") + + self.send_file(self.configfile, 'project.cfg') + if self.tc=='ISE': + self.send_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.ncd") + self.send_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.pcf") + elif self.tc=="VIVADO": + self.send_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/post_impl.dcp") + + self.heartbeat.printing = True + self.cmd(b'do'+self.sstr(f'bit {target}')) + res = struct.unpack('>I', self.channel.recv(4))[0] + self.heartbeat.printing = False + print(f' [{res}]') + + if res != 0: + print("Some error occured...") + if self.tc=='ISE': + # TODO what to send? + pass + elif self.tc=="VIVADO": + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/bit.log") + exit(1) + + if self.tc=='ISE': + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/timing.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.bit") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{target}.bin") + elif self.tc=="VIVADO": + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/bit.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/out.bit") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/out.bin") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/power.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/timing.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/util.log") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/system.xsa") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/total.dcp") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl_netlist.sdf") + self.recv_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/impl_netlist.v") + + def do_floorplan(self, target): + print("Error: floorplan editing not implemented for remote execution") + exit(1) + + def do_sim(self, target): + print("+ Simulate") + + self.send_file(self.configfile, 'project.cfg') + + src = self.config.get(target, 'src_vhdl', fallback='').split() + for s in src: + self.send_file(s) + src = self.config.get(target, 'src_verilog', fallback='').split() + for s in src: + self.send_file(s) + src = self.config.get(target, 'src_sysverilog', fallback='').split() + for s in src: + self.send_file(s) + src = self.config.get(target, 'src_sdf', fallback='') + if src!='': + self.send_file(src) + src = self.config.get(target, 'src_ip', fallback='').split() + for s in src: + if self.tc=='ISE': + self.send_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{s}/{s}.vhd") + elif self.tc=="VIVADO": + self.send_file(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}/{s}/{s}.xci") + if self.tc=="VIVADO": + src = self.config.get(target, 'src_c', fallback='').split() + for s in src: + self.send_file(s) + + self.heartbeat.printing = True + self.cmd(b'do'+self.sstr(f'sim {target}')) + res = struct.unpack('>I', self.channel.recv(4))[0] + self.heartbeat.printing = False + print(f' [{res}]') + + self.recv_dir(f"{self.config.get('project', 'out_dir', fallback='OUT')}/{target}") \ No newline at end of file diff --git a/remotesyn/exec_VIVADO.py b/remotesyn/exec_VIVADO.py new file mode 100644 index 0000000..53b4994 --- /dev/null +++ b/remotesyn/exec_VIVADO.py @@ -0,0 +1,402 @@ +import os +import shutil +import subprocess +import time +import glob + +class exec_VIVADO: + def __init__(self, config, builddir): + self.config = config + self.builddir = builddir + self.create_builddir() + + def create_outdir(self, target): + self.outdir = self.config.get('project', 'out_dir', fallback='OUT') + if not os.path.exists(self.outdir): + os.mkdir(self.outdir) + if not os.path.exists(f'{self.outdir}/{target}'): + os.mkdir(f'{self.outdir}/{target}') + + def create_builddir(self): + if not os.path.exists(self.builddir): + os.mkdir(self.builddir) + + def enter_builddir(self): + self.curdir = os.getcwd() + os.chdir(self.builddir) + + def leave_builddir(self): + os.chdir(self.curdir) + + def do_ip_gen(self, target): + # get used IP's for target + ips = self.config.get(target, 'src_ip', fallback='').split() + self.create_outdir(target) + + print("+ Generate IPs") + + dev = self.config.get('target', 'device', fallback='_d_') + sgrade = self.config.get('target', 'speedgrade', fallback='_s_') + pkg = self.config.get('target', 'package', fallback='_p_') + + for i, ip in enumerate(ips): + self.enter_builddir() + ipsec = 'ip_%s'%ip + ipname = self.config.get(ipsec, ipsec) + + ipconfig = '[ list \\\n' + for s in self.config[ipsec]: + if s==ipsec: + continue + ipconfig += f' CONFIG.{s.upper()} {{{self.config.get(ipsec, s)}}}\\\n' + ipconfig += ' ]' + + with open('do.tcl', 'w') as f: + f.write(f"file mkdir {ip}\ncreate_project -in_memory\nset_property part {dev}{pkg}{sgrade} [current_project]\n") + f.write(f"create_ip -name {ipname.split(':')[2]} -vendor {ipname.split(':')[1]} -library {ipname.split(':')[0 ]} -module_name {ip} -dir {ip}\n") + f.write(f"set_property -dict {ipconfig} [ get_ips {ip} ]\n") + f.write(f"export_ip_user_files -of_objects [get_files {ip}/{ip}/{ip}.xci ] -no_script -sync -force -quiet\n") + f.write(f"upgrade_ip [get_ips]\ngenerate_target all [get_ips]\n#synth_ip [get_ips]\n") + # f.write(f"export_simulation -directory {ip} -simulator xsim -absolute_path -export_source_files -of_objects [get_files {ip}/{ip}/{ip}.xci]") + + pid = subprocess.Popen('vivado -mode batch -source do.tcl', shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + + if ret != 0: + self.leave_builddir() + if not os.path.exists(f'{self.outdir}/{target}/{ip}'): + os.mkdir(f'{self.outdir}/{target}/{ip}') + shutil.copyfile(f"{self.builddir}/vivado.log", f'{self.outdir}/{target}/{ip}/log.log') + exit(ret) + + self.leave_builddir() + if not os.path.exists(f'{self.outdir}/{target}/{ip}'): + os.mkdir(f'{self.outdir}/{target}/{ip}') + shutil.copyfile(f"{self.builddir}/{ip}/{ip}/{ip}.vho", f'{self.outdir}/{target}/{ip}/{ip}.vho') + shutil.copyfile(f"{self.builddir}/{ip}/{ip}/{ip}.veo", f'{self.outdir}/{target}/{ip}/{ip}.veo') + shutil.copyfile(f"{self.builddir}/{ip}/{ip}/{ip}.xci", f'{self.outdir}/{target}/{ip}/{ip}.xci') + shutil.copyfile(f"{self.builddir}/vivado.log", f'{self.outdir}/{target}/{ip}/log.log') + + for f in glob.glob(f"{self.builddir}/{ip}/{ip}/*.c"): + shutil.copy(f, f'{self.outdir}/{target}/{ip}/') + for f in glob.glob(f"{self.builddir}/{ip}/{ip}/*.h"): + shutil.copy(f, f'{self.outdir}/{target}/{ip}/') + + def do_synthesize(self, target): + self.create_outdir(target) + self.enter_builddir() + + print('+ Synthesize') + + extra_opts = self.config.get(target, 'extra_options').split('\n') + opts = {} + for o in extra_opts: + tp = o.split()[0] + op = ' '.join(o.split()[1:]) + opts[tp] = op + + if 'syn' not in opts: + opts['syn'] = '' + + if 'netlist_top' not in opts: + opts['netlist_top'] = self.config.get(target, 'toplevel', fallback='toplevel') + + dev = self.config.get('target', 'device', fallback='_d_') + sgrade = self.config.get('target', 'speedgrade', fallback='_s_') + pkg = self.config.get('target', 'package', fallback='_p_') + + with open('do.tcl', 'w') as f: + src = self.config.get(target, 'src_vhdl', fallback='').split() + for s in src: + f.write(f'read_vhdl "{self.curdir}/{s}"\n') + src = self.config.get(target, 'src_verilog', fallback='').split() + for s in src: + f.write(f'read_verilog "{self.curdir}/{s}"\n') + src = self.config.get(target, 'src_sysverilog', fallback='').split() + for s in src: + f.write(f'read_verilog -sv "{self.curdir}/{s}"\n') + src = self.config.get(target, 'src_constraints', fallback='') + f.write(f'read_xdc "{self.curdir}/{src}"\n') + src = self.config.get(target, 'src_ip', fallback='').split() + for s in src: + if os.path.exists(s): + shutil.rmtree(s) + os.mkdir(s) + shutil.copyfile(f'{self.curdir}/{self.outdir}/{target}/{s}/{s}.xci', f'{s}/{s}.xci') + f.write(f'read_ip "{s}/{s}.xci"\n') + + f.write(f"set_property part {dev}{pkg}{sgrade} [current_project]\n") + f.write(f"upgrade_ip [get_ips]\ngenerate_target all [get_ips]\nsynth_ip [get_ips]\n") + f.write(f"synth_design -top {self.config.get(target, 'toplevel', fallback='toplevel')} -part {dev}{pkg}{sgrade} {opts['syn']}\n") + + f.write(f"write_checkpoint -force post_synth.dcp\nwrite_verilog -force -mode timesim -cell {opts['netlist_top']} -sdf_anno true -nolib netlist.v\n") + f.write(f"write_sdf -force -cell {opts['netlist_top']} -mode timesim netlist.sdf\n") + + pid = subprocess.Popen('vivado -mode batch -source do.tcl', shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + + if ret != 0: + self.leave_builddir() + shutil.copyfile(f"{self.builddir}/vivado.log", f'{self.outdir}/{target}/synth.log') + exit(ret) + + self.leave_builddir() + shutil.copyfile(f'{self.builddir}/netlist.v', f'{self.outdir}/{target}/synth_netlist.v') + shutil.copyfile(f'{self.builddir}/netlist.sdf', f'{self.outdir}/{target}/synth_netlist.sdf') + shutil.copyfile(f'{self.builddir}/post_synth.dcp', f'{self.outdir}/{target}/post_synth.dcp') + shutil.copyfile(f"{self.builddir}/vivado.log", f'{self.outdir}/{target}/synth.log') + + def do_implement(self, target): + self.create_outdir(target) + self.enter_builddir() + + print('+ Implement') + + extra_opts = self.config.get(target, 'extra_options').split('\n') + opts = {} + for o in extra_opts: + tp = o.split()[0] + op = ' '.join(o.split()[1:]) + opts[tp] = op + + if 'opt' not in opts: + opts['opt'] = '' + if 'place' not in opts: + opts['place'] = '' + if 'route' not in opts: + opts['route'] = '' + + with open('do.tcl', 'w') as f: + f.write(f"open_checkpoint {self.curdir}/{self.outdir}/{target}/post_synth.dcp\n") + f.write(f"opt_design {opts['opt']}\nplace_design {opts['place']}\nroute_design {opts['route']}\n") + f.write(f"write_checkpoint -force {self.curdir}/{self.outdir}/{target}/post_impl.dcp\n") + + pid = subprocess.Popen('vivado -mode batch -source do.tcl', shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + + if ret != 0: + self.leave_builddir() + shutil.copyfile(f"{self.builddir}/vivado.log", f'{self.outdir}/{target}/impl.log') + exit(ret) + + self.leave_builddir() + shutil.copyfile(f"{self.builddir}/vivado.log", f'{self.outdir}/{target}/impl.log') + + def do_bit(self, target): + self.create_outdir(target) + self.enter_builddir() + + print('+ Generate output files') + + extra_opts = self.config.get(target, 'extra_options').split('\n') + opts = {} + for o in extra_opts: + tp = o.split()[0] + op = ' '.join(o.split()[1:]) + opts[tp] = op + + if 'netlist_top' not in opts: + opts['netlist_top'] = self.config.get(target, 'toplevel', fallback='toplevel') + + with open('do.tcl', 'w') as f: + f.write(f"open_checkpoint {self.curdir}/{self.outdir}/{target}/post_impl.dcp\n") + f.write(f"set_property SEVERITY {{Warning}} [get_drc_checks NSTD-1]\nset_property SEVERITY {{Warning}} [get_drc_checks UCIO-1]\n") + f.write(f"set_property BITSTREAM.General.UnconstrainedPins {{Allow}} [current_design]\n") + f.write(f"write_debug_probes -force out.ltx\nwrite_bitstream -force -bin_file out.bit\nreport_timing_summary -file timing.log\nreport_power -file power.log\n") + f.write(f"report_utilization -file util.log\n") + f.write(f"write_checkpoint -force {self.curdir}/{self.outdir}/{target}/total.dcp\n") + f.write(f"open_checkpoint {self.curdir}/{self.outdir}/{target}/total.dcp\n") + f.write(f"write_hw_platform -fixed -force -file {self.curdir}/{self.outdir}/{target}/system.xsa\n") + f.write(f"write_verilog -force -mode timesim -cell {opts['netlist_top']} -rename_top {opts['netlist_top']} -sdf_anno true netlist.v\n") # -nolib + f.write(f"write_sdf -force -cell {opts['netlist_top']} -rename_top {opts['netlist_top']} -mode timesim netlist.sdf\n") + + pid = subprocess.Popen('vivado -mode batch -source do.tcl', shell=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + + if ret != 0: + self.leave_builddir() + shutil.copyfile(f"{self.builddir}/vivado.log", f'{self.outdir}/{target}/bit.log') + exit(ret) + + self.leave_builddir() + + shutil.copyfile(f"{self.builddir}/vivado.log", f'{self.outdir}/{target}/bit.log') + shutil.copyfile(f"{self.builddir}/timing.log", f'{self.outdir}/{target}/timing.log') + shutil.copyfile(f"{self.builddir}/util.log", f'{self.outdir}/{target}/util.log') + shutil.copyfile(f"{self.builddir}/power.log", f'{self.outdir}/{target}/power.log') + shutil.copyfile(f"{self.builddir}/out.bit", f'{self.outdir}/{target}/out.bit') + shutil.copyfile(f"{self.builddir}/out.bin", f'{self.outdir}/{target}/out.bin') + shutil.copyfile(f"{self.builddir}/netlist.v", f'{self.outdir}/{target}/impl_netlist.v') + shutil.copyfile(f"{self.builddir}/netlist.sdf", f'{self.outdir}/{target}/impl_netlist.sdf') + + def do_floorplan(self, target): + self.create_outdir(target) + self.enter_builddir() + + print('+ Open floorplan viewer') + + with open('do.tcl', 'w') as f: + f.write(f"open_checkpoint {self.curdir}/{self.outdir}/{target}/post_impl.dcp\n") + f.write(f"start_gui") + + pid = subprocess.Popen('vivado -mode batch -source do.tcl', shell=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + + self.leave_builddir() + + if ret != 0: + exit(ret) + + def do_sim(self, target): + self.create_outdir(target) + self.enter_builddir() + + print('+ Simulate') + + if os.path.exists('sim'): + shutil.rmtree('sim') + + dev = self.config.get('target', 'device', fallback='_d_') + sgrade = self.config.get('target', 'speedgrade', fallback='_s_') + pkg = self.config.get('target', 'package', fallback='_p_') + + with open('do.tcl', 'w') as f: + f.write(f"create_project -force -part {dev}{pkg}{sgrade} sim sim\n") + src = self.config.get(target, 'src_vhdl', fallback='').split() + for s in src: + f.write(f'add_files -norecurse -scan_for_includes {self.curdir}/{s}\n') + f.write(f'import_files -norecurse {self.curdir}/{s}\n') + src = self.config.get(target, 'src_verilog', fallback='').split() + for s in src: + f.write(f'add_files -norecurse -scan_for_includes {self.curdir}/{s}\n') + f.write(f'import_files -norecurse {self.curdir}/{s}\n') + src = self.config.get(target, 'src_sysverilog', fallback='').split() + for s in src: + f.write(f'add_files -norecurse -scan_for_includes {self.curdir}/{s}\n') + f.write(f'import_files -norecurse {self.curdir}/{s}\n') + src = self.config.get(target, 'src_ip', fallback='').split() + for s in src: + f.write(f'add_files -norecurse {self.curdir}/{self.outdir}/{s}/{s}.xci\n') + src = self.config.get(target, 'src_c', fallback='').split() + for s in src: + if s.endswith('.h'): + continue + f.write(f'add_files -norecurse -scan_for_includes {self.curdir}/{s}\n') + f.write(f'import_files -norecurse {self.curdir}/{s}\n') + + if self.config.get(target, 'src_sdf', fallback='__sdf__') != '__sdf__': + s = self.config.get(target, 'src_sdf', fallback='__sdf__') + f.write(f'add_files -norecurse -scan_for_includes {self.curdir}/{s}\n') + f.write(f'import_files -norecurse {self.curdir}/{s}\n') + f.write(f"file mkdir sim/sim.sim/sim_1/behav/xsim\nfile copy -force {self.curdir}/{s} {self.curdir}/{self.builddir}/sim/sim.sim/sim_1/behav/xsim/netlist.sdf\n") + # f.write(f"file mkdir sim/sim.sim/sim_1/behav/xsim\nfile copy -force {self.curdir}/{s} {self.curdir}/{self.builddir}/sim/sim.sim/sim_1/behav/xsim/{os.path.split(s)[1]}\n") + + f.write(f"set_property top {self.config.get(target, 'toplevel', fallback='toplevel')} [get_filesets sim_1]\n") + f.write("set_property top_lib xil_defaultlib [get_filesets sim_1]\n") + f.write("launch_simulation -noclean_dir -scripts_only -absolute_path\n") + + pid = subprocess.Popen('vivado -mode batch -source do.tcl', shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + + if ret != 0: + self.leave_builddir() + print("Something went wrong...") + shutil.copyfile(f"{self.builddir}/vivado.log", f'{self.outdir}/{target}/prepare.log') + exit(ret) + + shutil.copyfile(f"vivado.log", f'{self.curdir}/{self.outdir}/{target}/prepare.log') + + extras = '' + if self.config.get(target, 'simtype', fallback='presim') == 'postsim': + extras = f"-{self.config.get(target, 'delay', fallback='typ')}delay -transport_int_delays -pulse_r 0 -pulse_int_r 0 -L simprims_ver" + + pid = subprocess.Popen(f'sed -i "s/xelab/xelab {extras}/g" elaborate.sh', shell=True, cwd='sim/sim.sim/sim_1/behav/xsim', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + if ret!=0: + print("Something went wrong with editing elaborate stage...") + exit(ret) + + if self.config.get(target, 'simtype', fallback='presim') == 'postsim': + pid = subprocess.Popen(f"sed -i '/ \/I /d' netlist.sdf && sed -i '/glbl.v/d' *.prj", shell=True, cwd='sim/sim.sim/sim_1/behav/xsim', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + # pid = subprocess.Popen(f"sed -i '/glbl.v/d' *.prj", shell=True, cwd='sim/sim.sim/sim_1/behav/xsim', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + if ret!=0: + print("Something went wrong with editing project files...") + exit(ret) + + pid = subprocess.Popen(f'bash compile.sh', shell=True, cwd='sim/sim.sim/sim_1/behav/xsim', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + if ret!=0: + self.leave_builddir() + print("Compile error") + shutil.copyfile(f"{self.builddir}/sim/sim.sim/sim_1/behav/xsim/compile.log", f'{self.outdir}/{target}/compile.log') + exit(ret) + shutil.copyfile(f"{self.curdir}/{self.builddir}/sim/sim.sim/sim_1/behav/xsim/compile.log", f'{self.curdir}/{self.outdir}/{target}/compile.log') + + pid = subprocess.Popen(f'bash elaborate.sh', shell=True, cwd='sim/sim.sim/sim_1/behav/xsim', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + if ret!=0: + self.leave_builddir() + print("Elaborate error") + shutil.copyfile(f"{self.builddir}/sim/sim.sim/sim_1/behav/xsim/elaborate.log", f'{self.outdir}/{target}/elaborate.log') + exit(ret) + shutil.copyfile(f"{self.curdir}/{self.builddir}/sim/sim.sim/sim_1/behav/xsim/elaborate.log", f'{self.curdir}/{self.outdir}/{target}/elaborate.log') + + with open(f"sim/sim.sim/sim_1/behav/xsim/{self.config.get(target, 'toplevel', fallback='toplevel')}.tcl", 'w') as f: + f.write(f"open_vcd out.vcd\nlog_vcd\nrun {self.config.get(target, 'runtime', fallback='100 ns')}\nclose_vcd\nquit\n") + + pid = subprocess.Popen(f'bash simulate.sh', shell=True, cwd='sim/sim.sim/sim_1/behav/xsim', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + while pid.poll() is None: + print('.', end='', flush=True) + time.sleep(2) + ret = pid.returncode + print('') + if ret!=0: + self.leave_builddir() + print("Simulation error") + shutil.copyfile(f"{self.builddir}/sim/sim.sim/sim_1/behav/xsim/simulate.log", f'{self.outdir}/{target}/simulate.log') + exit(ret) + shutil.copyfile(f"{self.curdir}/{self.builddir}/sim/sim.sim/sim_1/behav/xsim/simulate.log", f'{self.curdir}/{self.outdir}/{target}/simulate.log') + + self.leave_builddir() + shutil.copyfile(f'{self.builddir}/sim/sim.sim/sim_1/behav/xsim/out.vcd', f'{self.outdir}/{target}/output.vcd') \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..aa35c71 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +paramiko \ No newline at end of file diff --git a/scripts/remotesyn b/scripts/remotesyn new file mode 100644 index 0000000..77765c5 --- /dev/null +++ b/scripts/remotesyn @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 + +import sys +import os +import configparser +import paramiko +import socket + +from remotesyn import Connection, exec_ISE, exec_VIVADO, exec_REMOTE + +def server(accidentals, positionals): + addr = accidentals['server'].split(':') + if len(addr)!=2: + print("Host address must be in form hostname:port") + exit(1) + host_key = paramiko.RSAKey(filename=accidentals['privkey']) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((addr[0], int(addr[1]))) + sock.listen(100) + + # Get authorized hosts + with open(accidentals['authorized'], 'r') as f: + authorized = f.read().split('\n') + + running = True + threads = [] + try: + while running: + client, addr = sock.accept() + conn = Connection(client, addr, host_key, authorized) + conn.start() + threads.append(conn) + except KeyboardInterrupt: + print("Stopping server") + running = False + for t in threads: + t.stop() + finally: + sock.close() + +def main(accidentals, positionals): + if 'server' in accidentals: + return server(accidentals, positionals) + elif 'local' in accidentals: + config = accidentals['config'] + if config.get('project', 'toolchain') == 'ISE': + execInterface = exec_ISE(config, accidentals['builddir']) + else: + execInterface = exec_VIVADO(config, accidentals['builddir']) + else: + config = accidentals['config'] + execInterface = exec_REMOTE(config, accidentals['configname']) + + i = 0 + while i< len(positionals): + action = positionals[i] + i += 1 + + if action == 'ip': + if i>=len(positionals): + print("Unexpected end of input") + exit(1) + target = positionals[i] + i += 1 + execInterface.do_ip_gen(target) + + elif action == 'syn': + if i>=len(positionals): + print("Unexpected end of input") + exit(1) + target = positionals[i] + i += 1 + execInterface.do_synthesize(target) + + elif action == 'impl': + if i>=len(positionals): + print("Unexpected end of input") + exit(1) + target = positionals[i] + i += 1 + execInterface.do_implement(target) + + elif action == 'bit': + if i>=len(positionals): + print("Unexpected end of input") + exit(1) + target = positionals[i] + i += 1 + execInterface.do_bit(target) + + elif action == 'all': + if i>=len(positionals): + print("Unexpected end of input") + exit(1) + target = positionals[i] + i += 1 + execInterface.do_synthesize(target) + execInterface.do_implement(target) + execInterface.do_bit(target) + + elif action == 'floorplan': + if i>=len(positionals): + print("Unexpected end of input") + exit(1) + target = positionals[i] + i += 1 + execInterface.do_floorplan(target) + + elif action == 'sim': + if i>=len(positionals): + print("Unexpected end of input") + exit(1) + target = positionals[i] + i += 1 + execInterface.do_sim(target) + + else: + print("Unknown action...") + exit(1) + +def print_help(): + print("Unified FPGA synthesizer frontend\r\n(c) Joppe Blondel - 2022\r\n") + print(f"Usage: {sys.argv[0]} [ OPTIONS ] action [ target ] ...") + print("where OPTIONS := { -h | -l | -s host:port privkey pubkey authorized | -c config | -b build_dir }") + print(" action := { ip | syn | impl | bit | all | floorplan | sim | init }") + print("") + print("Options:") + print(" -h Show this help message") + print(" -l Local build") + print(" -s Start server with server information s-info. host:port is the address to bind") + print(" to, privkey and pubkey are the ssh keys of the server and authorized is the") + print(" authorized_keys file for the SSH server") + print(" -c Configuration file, defaults to project.cfg") + print(" -b Build directory, defaults to .build") + print("") + print("Actions:") + print("ip Generate IP files from vendor provided libraries") + print("syn Synthesize design for target") + print("impl Route and place design for target") + print("bit Generate output files and run analysis for target") + print("all Generate IP, synthesize, route and place design for target") + print("floorplan Run floorplan editor, currently only for local execution") + print("sim Run simulation target") + print("init Initialize project") + +if __name__=="__main__": + accidentals = {} + positionals = [] + + if len(sys.argv)==1: + print_help() + exit(1) + + i = 1 + while i=len(sys.argv): + print("Unexpected end of input") + exit(1) + accidentals['server'] = sys.argv[i+1] + i += 1 + if i+1>=len(sys.argv): + print("Unexpected end of input") + exit(1) + accidentals['privkey'] = sys.argv[i+1] + i += 1 + if i+1>=len(sys.argv): + print("Unexpected end of input") + exit(1) + accidentals['pubkey'] = sys.argv[i+1] + i += 1 + if i+1>=len(sys.argv): + print("Unexpected end of input") + exit(1) + accidentals['authorized'] = sys.argv[i+1] + i += 1 + elif sys.argv[i] == '-c': + if i+1>=len(sys.argv): + print("Unexpected end of input") + exit(1) + accidentals['config'] = sys.argv[i+1] + i += 1 + elif sys.argv[i] == '-b': + if i+1>=len(sys.argv): + print("Unexpected end of input") + exit(1) + accidentals['builddir'] = sys.argv[i+1] + i += 1 + + else: + if sys.argv[i] == 'init': + print("+ Generate example configuration file") + with open('example.cfg', 'w') as f: + f.write("# PROJECT SETTINGS\n") + f.write("# ----------------\n") + f.write("[server]\nhostname = localhost\nport = 8080\nprivkey = keys/id_rsa\npubkey = keys/id_rsa.pub\n\n") + f.write("[project]\n# Toolchain selection. choose between [ISE, VIVADO]\ntoolchain = ISE\nout_dir = OUT\n\n") + f.write("[target]\nfamily = spartan6\ndevice = xc6lsx9\npackage = tgq144\nspeedgrade = -2\n\n") + f.write("# HARDWARE TARGETS\n") + f.write("# ----------------\n") + f.write("src_vhdl = RTL/toplevel.vhd\nsrc_verilog = \nsrc_sysverilog = \n") + f.write("src_constraints = CON/toplevel.ucf\nsrc_ip = \ntoplevel = toplevel\nextra_options = ") + exit(1) + + positionals.append(sys.argv[i]) + i += 1 + + if 'config' not in accidentals and 'server' not in accidentals: + # Running in client mode and no config file specified -> pick default + accidentals['config'] = 'project.cfg' + + if 'config' in accidentals and 'server' not in accidentals: + if os.path.exists(accidentals['config']): + accidentals['configname'] = accidentals['config'] + config = configparser.ConfigParser() + config.read(accidentals['config']) + accidentals['config'] = config + else: + print(f"Config file \'{accidentals['config']}\' not found") + exit(1) + + if ('server' in accidentals or 'local' in accidentals) and 'builddir' not in accidentals: + accidentals['builddir'] = '.build' + + main(accidentals, positionals) + exit(0) \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..224a779 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e29451e --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +from setuptools import setup + +with open("README.md", 'r') as f: + long_description = f.read() + +setup( + name='remotesyn', + version='0.1', + description='Remote FPGA synthesis abstraction tool', + + author='Joppe Blondel', + author_email='joppe@blondel.nl', + download_url='', + url='https://git.joppeb.nl/joppe/remotesyn', + + keywords = ['FPGA', 'Synthesis', 'Xilinx', 'ISE', 'Vivado',], + classifiers=[ + 'Development Status :: 3 - Alpha', + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python :: 3', + ], + + packages=['remotesyn'], + licence='BSD Licence', + install_requires=['paramiko'], + scripts=['scripts/remotesyn'] +) \ No newline at end of file