Add initial program_fpga.py script

This commit is contained in:
abejgonzalez 2023-05-08 18:13:48 -07:00
parent b99c0bfcc0
commit 8104cd3f66
2 changed files with 206 additions and 0 deletions

View File

@ -0,0 +1,194 @@
#!/usr/bin/env python3
import argparse
import os
import subprocess
import sys
import pwd
import re
from pathlib import Path
from typing import Optional, Dict, Any, List
pciDevicesPath = Path('/sys/bus/pci/devices')
def get_bridge_bdf(id: str) -> str:
for entry in pciDevicesPath.iterdir():
if re.match('^0000:' + re.escape(id) + ':[a-fA-F0-9]{2}\.[0-7]$', entry.name):
bridgePath = entry.resolve().absolute().parent
if bridgePath.exists():
return bridgePath.name
print(":ERROR: Unable to obtain bridge BDF")
sys.exit(1)
def get_fpga_bdfs(id: str) -> List[str]:
result = []
for entry in pciDevicesPath.iterdir():
if re.match('^0000:' + re.escape(id) + ':[a-fA-F0-9]{2}\.[0-7]$', entry.name):
result.append(entry.name)
return result
def get_fpga_devs(id) -> List[Path]:
def readUevent(path: Path) -> Dict[Any, Any]:
if not (path / 'uevent').exists():
return {}
return { entry[0]: entry[1] for entry in [line.strip('\n\r ').split('=') for line in open(f'{path}/uevent', 'r').readlines()] if len(entry) >= 2 }
def xdmaResolver(path: Path) -> List[Path]:
xdmaDevs = []
for f in ['resource', 'resource0', 'resource1']:
rsrcPath = (path / f)
if rsrcPath.exists():
xdmaDevs.append(rsrcPath)
xdmaPath = (path / 'xdma')
if xdmaPath.is_dir():
ueventEntries = [readUevent(xdmaPath / entry.name) for entry in xdmaPath.iterdir() if (xdmaPath / entry.name).is_dir()]
xdmaDevs.extend([Path('/dev') / uevent['DEVNAME'] for uevent in ueventEntries if 'DEVNAME' in uevent and (Path('/dev') / uevent['DEVNAME']).exists()])
return xdmaDevs
resolvers = {
'xdma' : xdmaResolver
}
returnDevs = []
fpgaDevices = get_fpga_bdfs(id)
for fpgaDev in fpgaDevices:
path = pciDevicesPath / fpgaDev
fpgaDevUevent = readUevent(path)
if 'DRIVER' not in fpgaDevUevent:
print(":WARNING: Verify that 'xdma' driver is loaded")
continue
if fpgaDevUevent['DRIVER'] not in resolvers:
continue
returnDevs.extend(resolvers[fpgaDevUevent['DRIVER']](path.resolve()))
return returnDevs
# clear SERR bit in command register
# https://support.xilinx.com/s/question/0D52E00006hpjPHSAY/dell-r720-poweredge-server-reboots-on-fpga-reprogramming?language=en_US
def clear_serr(id: str) -> None:
bridgeBDF = get_bridge_bdf(id)
run = subprocess.run(['setpci', '-s', bridgeBDF, 'COMMAND=0000:0100'])
if run.returncode != 0:
print(":ERROR: Unable to clear SERR bit")
sys.exit(1)
# clear fatal error reporting enable bit in the device control register
# https://support.xilinx.com/s/question/0D52E00006hpjPHSAY/dell-r720-poweredge-server-reboots-on-fpga-reprogramming?language=en_US
def clear_fatal_error_reporting(id: str) -> None:
bridgeBDF = get_bridge_bdf(id)
run = subprocess.run(['setpci', '-s', bridgeBDF, 'CAP_EXP+8.w=0000:0004'])
if run.returncode != 0:
print(":ERROR: Unable to clear SERR bit")
sys.exit(1)
def write_to_linux_device_path(path: Path, data: str = '1\n') -> None:
try:
open(path, 'w').write(data)
except:
print(f":ERROR: Cannot write to {path} value: {data}")
sys.exit(1)
def remove(id: str) -> None:
bridgeBDF = get_bridge_bdf(id)
deviceBDFs = get_fpga_bdfs(id)
for deviceBDF in deviceBDFs:
removePath = pciDevicesPath / bridgeBDF / deviceBDF / 'remove'
if removePath.exists():
write_to_linux_device_path(removePath)
def rescan(id: str) -> None:
bridgeBDF = get_bridge_bdf(id)
if bridgeBDF is not None:
rescanPath = pciDevicesPath / bridgeBDF / 'rescan'
write_to_linux_device_path(rescanPath)
else:
write_to_linux_device_path('/sys/bus/pci/rescan')
# enable memory mapped transfers for the fpga
# https://support.xilinx.com/s/question/0D52E00006iHlNoSAK/lspci-reports-bar-0-disabled?language=en_US
def enable_memmapped_transfers(id: str) -> None:
deviceBDFs = get_fpga_bdfs(id)
for deviceBDF in deviceBDFs:
run = subprocess.run(['setpci', '-s', deviceBDF, 'COMMAND=0x02'])
if run.returncode != 0:
print(f":ERROR: Unable to enable memmapped transfers on {deviceBDF}")
sys.exit(1)
def program_fpga(serial: str, board: str, bitstream: str) -> None:
print(":WARNING: This only can target the 1st FPGA on a machine currently...")
pVivado = subprocess.Popen(
[
'vivado',
'-mode', 'tcl',
'-nolog', '-nojournal', '-notrace',
'-source', scriptPath / 'program_fpga.tcl',
'-tclargs',
'-board', board,
'-bitstream_path', bitstream,
],
stdin=subprocess.DEVNULL
)
pVivado.wait()
if pVivado.returncode != 0:
print(":ERROR: Unable to flash FPGA")
sys.exit(1)
def get_serial_from_bdf(id: str) -> str:
deviceBDFs = get_fpga_bdfs(parsed_args.bus_id)
if len(deviceBDFs) == 0:
print(f":ERROR: Unable to obtain Extended Device BDF for {parsed_args.bus_id}")
sys.exit(1)
return "TODO"
def main(args: List[str]) -> int:
parser = argparse.ArgumentParser(description="Program a Xilinx XDMA-enabled FPGA")
megroup = parser.add_mutually_exclusive_group(required=True)
megroup.add_argument("--bus_id", help="Bus number of FPGA to flash (i.e. ****:<THIS>:**.*)")
megroup.add_argument("--serial_no", help="Serial number of FPGA to flash (i.e. what 'get_hw_target' shows in Vivado)")
parser.add_argument("--bitstream", help="Bitstream to flash onto FPGA", required=True, type=Path)
parser.add_argument("--board", help="FPGA board to flash", required=True)
parsed_args = parser.parse_args(args)
scriptPath = Path(__file__).resolve().parent
eUserId = os.geteuid()
sudoUserId = os.getenv('SUDO_UID')
isAdmin = (eUserId == 0) and (sudoUserId is None)
userId = eUserId if sudoUserId is None else int(sudoUserId)
if not isAdmin:
print(":ERROR: Requires running script with 'sudo'")
sys.exit(1)
if not parsed_args.bitstream.is_file() or not parsed_args.bitstream.exists():
print(f":ERROR: Invalid bitstream: {parsed_args.bitstream}")
sys.exit(1)
else:
parsed_args.bitstream = parsed_args.bitstream.absolute()
if parsed_args.bus_id:
serialNumber = get_serial_from_bdf(id)
clear_serr(parsed_args.bus_id)
clear_fatal_error_reporting(parsed_args.bus_id)
remove(parsed_args.bus_id)
program_fpga(serialNumber, parsed_args.board, parsed_args.bitstream)
rescan(parsed_args.bus_id)
enable_memmapped_transfers(parsed_args.bus_id)
print(f"Successfully programmed FPGA {parsed_args.bus_id} with {parsed_args.bitstream}")
if parsed_args.serial_no:
program_fpga(parsed_args.serial_no, parsed_args.board, parsed_args.bitstream)
print(f"Successfully programmed FPGA {parsed_args.serial_no} with {parsed_args.bitstream}")
print(":WARNING: Please warm reboot the machine")
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

@ -40,6 +40,18 @@ puts "HW device: $hw_device"
open_hw_manager
connect_hw_server -allow_non_jtag
## by default vivado opens a default hw target
#close_hw_target
# note: helps view amount of fpgas
get_hw_targets
# TODO:
# when no FPGA is programmed
# can use id to index into get_hw_targets and program that specific FPGA
# when you notice a PCI-ID associated w/ the FPGA
# TODO: how do you determine the ID of the FPGA from the PCI-BDF
open_hw_target
current_hw_device [get_hw_devices $hw_device]
refresh_hw_device -update_hw_probes false [lindex [get_hw_devices $hw_device] 0]