artiq/artiq/frontend/artiq_flash.py

353 lines
12 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2016-01-05 07:50:59 +08:00
import argparse
import os
import subprocess
import tempfile
import shutil
import re
import atexit
from functools import partial
from collections import defaultdict
2016-01-05 07:50:59 +08:00
from artiq import __artiq_dir__ as artiq_dir
from artiq.tools import verbosity_args, init_logger
from artiq.remoting import SSHClient, LocalClient
2016-01-05 07:50:59 +08:00
from artiq.frontend.bit2bin import bit2bin
2016-01-19 12:41:42 +08:00
def get_argparser():
2016-01-05 07:50:59 +08:00
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="ARTIQ flashing/deployment tool",
epilog="""\
Valid actions:
2016-03-01 03:45:41 +08:00
* gateware: write gateware bitstream to flash
* bootloader: write bootloader to flash
2016-01-05 07:50:59 +08:00
* storage: write storage image to flash
* firmware: write firmware to flash
2016-03-01 03:45:41 +08:00
* load: load gateware bitstream into device (volatile but fast)
* start: trigger the target to (re)load its gateware bitstream from flash
2016-01-05 07:50:59 +08:00
Prerequisites:
* Connect the board through its/a JTAG adapter.
* Have OpenOCD installed and in your $PATH.
* Have access to the JTAG adapter's devices. Udev rules from OpenOCD:
'sudo cp openocd/contrib/99-openocd.rules /etc/udev/rules.d'
and replug the device. Ensure you are member of the
plugdev group: 'sudo adduser $USER plugdev' and re-login.
""")
verbosity_args(parser)
2018-01-20 00:24:59 +08:00
parser.add_argument("-n", "--dry-run",
default=False, action="store_true",
2018-01-20 01:44:23 +08:00
help="only show the openocd script that would be run")
parser.add_argument("-H", "--host", metavar="HOSTNAME",
type=str, default=None,
help="SSH host where the development board is located")
2016-01-05 07:50:59 +08:00
parser.add_argument("-t", "--target", default="kc705",
2018-01-17 09:21:19 +08:00
help="target board, default: %(default)s, one of: "
"kc705 kasli sayma")
2018-01-22 18:25:10 +08:00
parser.add_argument("-V", "--variant", default=None,
help="board variant")
2018-01-20 01:44:23 +08:00
parser.add_argument("-I", "--preinit-command", default=[], action="append",
help="add a pre-initialization OpenOCD command. "
"Useful for selecting a development board "
"when several are connected.")
2016-01-05 07:50:59 +08:00
parser.add_argument("-f", "--storage", help="write file to storage area")
parser.add_argument("-d", "--dir", help="look for files in this directory")
parser.add_argument("--srcbuild", help="look for bitstream, bootloader and firmware in this "
"ARTIQ source build tree")
parser.add_argument("action", metavar="ACTION", nargs="*",
default="gateware bootloader firmware start".split(),
2016-01-05 07:50:59 +08:00
help="actions to perform, default: %(default)s")
2016-01-19 12:41:42 +08:00
return parser
2017-08-21 05:23:56 +08:00
def scripts_path():
p = ["share", "openocd", "scripts"]
if os.name == "nt":
p.insert(0, "Library")
p = os.path.abspath(os.path.join(
os.path.dirname(shutil.which("openocd")),
"..", *p))
return p
def proxy_path():
p = ["share", "bscan-spi-bitstreams"]
p = os.path.abspath(os.path.join(
os.path.dirname(shutil.which("openocd")),
"..", *p))
return p
def find_proxy_bitfile(filename):
for p in [proxy_path(), os.path.expanduser("~/.migen"),
"/usr/local/share/migen", "/usr/share/migen"]:
full_path = os.path.join(p, filename)
if os.access(full_path, os.R_OK):
return full_path
raise FileNotFoundError("Cannot find proxy bitstream {}"
.format(filename))
def add_commands(script, *commands, **substs):
script += [command.format(**substs) for command in commands]
2017-08-21 05:23:56 +08:00
class Programmer:
2018-01-20 01:44:23 +08:00
def __init__(self, client, preinit_script):
self._client = client
self._board_script = []
2018-01-20 01:44:23 +08:00
self._preinit_script = preinit_script
self._loaded = defaultdict(lambda: None)
2018-01-20 01:44:23 +08:00
self._script = []
2017-08-21 05:23:56 +08:00
def _transfer_script(self, script):
2018-01-20 01:44:23 +08:00
if isinstance(self._client, LocalClient):
return "[find {}]".format(script)
def rewriter(content):
def repl(match):
return self._transfer_script(match.group(1).decode()).encode()
return re.sub(rb"\[find (.+?)\]", repl, content, re.DOTALL)
script = os.path.join(scripts_path(), script)
return self._client.upload(script, rewriter)
2018-01-20 01:44:23 +08:00
def add_flash_bank(self, name, tap, index):
add_commands(self._board_script,
"target create {tap}.{name}.proxy testee -chain-position {tap}.tap",
"flash bank {name} jtagspi 0 0 0 0 {tap}.{name}.proxy {ir:#x}",
tap=tap, name=name, ir=0x02 + index)
def load(self, bitfile, pld):
os.stat(bitfile) # check for existence
if self._loaded[pld] == bitfile:
return
self._loaded[pld] = bitfile
bitfile = self._client.upload(bitfile)
add_commands(self._script,
"pld load {pld} {filename}",
pld=pld, filename=bitfile)
def load_proxy(self):
raise NotImplementedError
def write_binary(self, bankname, address, filename):
self.load_proxy()
size = os.path.getsize(filename)
filename = self._client.upload(filename)
add_commands(self._script,
"flash probe {bankname}",
"flash erase_sector {bankname} {firstsector} {lastsector}",
"flash write_bank {bankname} {filename} {address:#x}",
"flash verify_bank {bankname} {filename} {address:#x}",
bankname=bankname, address=address, filename=filename,
firstsector=address // self._sector_size,
lastsector=(address + size - 1) // self._sector_size)
def read_binary(self, bankname, address, length, filename):
self.load_proxy()
filename = self._client.prepare_download(filename)
add_commands(self._script,
"flash probe {bankname}",
"flash read_bank {bankname} {filename} {address:#x} {length}",
bankname=bankname, filename=filename, address=address, length=length)
def start(self):
raise NotImplementedError
2018-01-20 01:44:23 +08:00
def script(self):
return [
*self._board_script,
2018-01-20 01:44:23 +08:00
*self._preinit_script,
"init",
*self._script,
"exit"
]
2018-01-20 01:44:23 +08:00
def run(self):
cmdline = ["openocd"]
if isinstance(self._client, LocalClient):
cmdline += ["-s", scripts_path()]
2018-01-20 02:53:49 +08:00
cmdline += ["-c", "; ".join(self.script())]
2018-01-20 02:53:49 +08:00
cmdline = [arg.replace("{", "{{").replace("}", "}}") for arg in cmdline]
2018-01-20 01:44:23 +08:00
self._client.run_command(cmdline)
self._client.download()
self._script = []
2017-08-21 05:23:56 +08:00
class ProgrammerXC7(Programmer):
_sector_size = 0x10000
2017-08-21 05:23:56 +08:00
def __init__(self, client, preinit_script, board, proxy):
2018-01-20 01:44:23 +08:00
Programmer.__init__(self, client, preinit_script)
self._proxy = proxy
2018-01-20 01:44:23 +08:00
add_commands(self._board_script,
"source {boardfile}",
boardfile=self._transfer_script("board/{}.cfg".format(board)))
self.add_flash_bank("spi0", "xc7", index=0)
2017-08-21 05:23:56 +08:00
def load_proxy(self):
self.load(find_proxy_bitfile(self._proxy), pld=0)
2017-08-21 05:23:56 +08:00
def start(self):
add_commands(self._script,
"xc7_program xc7.tap")
2017-08-21 05:23:56 +08:00
class ProgrammerSayma(Programmer):
_sector_size = 0x10000
2018-01-20 01:44:23 +08:00
def __init__(self, client, preinit_script):
Programmer.__init__(self, client, preinit_script)
add_commands(self._board_script,
2017-08-21 05:23:56 +08:00
"interface ftdi",
"ftdi_device_desc \"Quad RS232-HS\"",
"ftdi_vid_pid 0x0403 0x6011",
"ftdi_channel 0",
# EN_USB_JTAG on ADBUS7: out, high
# nTRST on ADBUS4: out, high, but R46 is DNP
"ftdi_layout_init 0x0098 0x008b",
"reset_config none",
"adapter_khz 5000",
2017-08-21 05:23:56 +08:00
"transport select jtag",
# tap 0, pld 0
"source {}".format(self._transfer_script("cpld/xilinx-xc7.cfg")),
# tap 1, pld 1
"set CHIP XCKU040",
"source {}".format(self._transfer_script("cpld/xilinx-xcu.cfg")))
self.add_flash_bank("spi0", "xcu", index=0)
self.add_flash_bank("spi1", "xcu", index=1)
2017-08-21 05:23:56 +08:00
def load_proxy(self):
self.load(find_proxy_bitfile("bscan_spi_xcku040-sayma.bit"), pld=1)
2017-08-21 05:23:56 +08:00
def start(self):
add_commands(self._script,
"xcu_program xcu.tap")
2017-08-21 05:23:56 +08:00
2016-01-19 12:41:42 +08:00
def main():
args = get_argparser().parse_args()
init_logger(args)
2016-01-05 07:50:59 +08:00
config = {
"kc705": {
"programmer": partial(ProgrammerXC7, board="kc705", proxy="bscan_spi_xc7k325t.bit"),
"variants": ["nist_clock", "nist_qc2"],
"gateware": ("spi0", 0x000000),
"bootloader": ("spi0", 0xaf0000),
"storage": ("spi0", 0xb30000),
"firmware": ("spi0", 0xb40000),
2017-08-21 05:23:56 +08:00
},
2018-01-15 20:59:11 +08:00
"kasli": {
"programmer": partial(ProgrammerXC7, board="kasli", proxy="bscan_spi_xc7a100t.bit"),
2018-01-25 00:00:07 +08:00
"variants": ["opticlock", "master", "satellite"],
"gateware": ("spi0", 0x000000),
"bootloader": ("spi0", 0x400000),
"storage": ("spi0", 0x440000),
"firmware": ("spi0", 0x450000),
2018-01-15 20:59:11 +08:00
},
"sayma": {
"programmer": ProgrammerSayma,
"variants": ["standalone", "master", "satellite"],
"gateware": ("spi0", 0x000000),
"bootloader": ("spi1", 0x000000),
"storage": ("spi1", 0x040000),
"firmware": ("spi1", 0x050000),
2016-01-05 07:50:59 +08:00
},
}[args.target]
2016-01-05 07:50:59 +08:00
variant = args.variant
2018-01-17 09:21:19 +08:00
if "variants" in config:
if variant is not None and variant not in config["variants"]:
raise SystemExit("Invalid variant for this board")
if variant is None:
variant = config["variants"][0]
bin_dir = args.dir
2017-08-21 05:23:56 +08:00
if bin_dir is None:
bin_name = args.target
2018-01-17 09:21:19 +08:00
if variant:
bin_name += "-" + variant
2018-01-17 09:21:19 +08:00
bin_dir = os.path.join(artiq_dir, "binaries", bin_name)
if args.host is None:
client = LocalClient()
else:
client = SSHClient(args.host)
programmer = config["programmer"](client, preinit_script=args.preinit_command)
2016-01-05 07:50:59 +08:00
def artifact_path(*path_filename):
if args.srcbuild is None:
*path, filename = path_filename
return os.path.join(bin_dir, filename)
else:
return os.path.join(args.srcbuild, *path_filename)
try:
for action in args.action:
if action == "gateware":
gateware_bin = artifact_path("gateware", "top.bin")
if not os.access(gateware_bin, os.R_OK):
bin_handle, gateware_bin = tempfile.mkstemp()
gateware_bit = artifact_path("gateware", "top.bit")
with open(gateware_bit, "rb") as bit_file, open(bin_handle, "wb") as bin_file:
bit2bin(bit_file, bin_file)
atexit.register(lambda: os.unlink(gateware_bin))
programmer.write_binary(*config["gateware"], gateware_bin)
elif action == "bootloader":
bootloader_bin = artifact_path("software", "bootloader", "bootloader.bin")
programmer.write_binary(*config["bootloader"], bootloader_bin)
elif action == "storage":
storage_img = args.storage
programmer.write_binary(*config["storage"], storage_img)
elif action == "firmware":
if variant == "satellite":
firmware = "satman"
else:
firmware = "runtime"
firmware_fbi = artifact_path("software", firmware, firmware + ".fbi")
programmer.write_binary(*config["firmware"], firmware_fbi)
elif action == "load":
if args.target == "sayma":
rtm_gateware_bit = artifact_path("rtm_gateware", "rtm.bit")
programmer.load(rtm_gateware_bit, 0)
gateware_bit = artifact_path("gateware", "top.bit")
programmer.load(gateware_bit, 1)
else:
gateware_bit = artifact_path("gateware", "top.bit")
programmer.load(gateware_bit, 0)
elif action == "start":
programmer.start()
else:
raise ValueError("invalid action", action)
except FileNotFoundError as e:
raise SystemExit(e)
2017-08-21 05:23:56 +08:00
2018-01-20 00:24:59 +08:00
if args.dry_run:
2018-01-20 01:44:23 +08:00
print("\n".join(programmer.script()))
2018-01-20 00:24:59 +08:00
else:
programmer.run()
2016-01-05 07:50:59 +08:00
if __name__ == "__main__":
main()