From 541f86da1086dec7005013b420d5f1cf96ca9569 Mon Sep 17 00:00:00 2001 From: Gregory Ling <gling@iastate.edu> Date: Thu, 7 Nov 2024 21:14:38 -0600 Subject: [PATCH] Added uart ports and booting --- chipforge/cli.py | 5 +- chipforge/firmware/__init__.py | 3 +- chipforge/firmware/boot.py | 30 +++ chipforge/firmware/flash.py | 241 +++++++++++----------- chipforge/firmware/interface/ftdi.py | 11 + chipforge/firmware/interface/hk.py | 3 +- chipforge/firmware/interface/hk_base.py | 1 - chipforge/firmware/interface/hk_ft232h.py | 15 +- chipforge/firmware/interface/hk_pico.py | 17 +- chipforge/firmware/interface/hk_stm32.py | 14 +- chipforge/firmware/interface/hk_xilinx.py | 20 +- chipforge/firmware/list.py | 11 +- chipforge/firmware/projectid.py | 3 +- chipforge/firmware/usb_reset.py | 33 +++ 14 files changed, 247 insertions(+), 160 deletions(-) create mode 100644 chipforge/firmware/boot.py create mode 100644 chipforge/firmware/interface/ftdi.py create mode 100644 chipforge/firmware/usb_reset.py diff --git a/chipforge/cli.py b/chipforge/cli.py index e0164b7..d314b11 100644 --- a/chipforge/cli.py +++ b/chipforge/cli.py @@ -8,7 +8,7 @@ import pkg_resources from .autoupdate import autoupdate from .fpga import fpga -from .firmware import flash, projectid, list_devices, free +from .firmware import flash, projectid, list_devices, free, boot from . import colors as c def get_version(): @@ -47,4 +47,5 @@ cli.add_command(flash) cli.add_command(fpga) cli.add_command(free) cli.add_command(list_devices) -cli.add_command(projectid) \ No newline at end of file +cli.add_command(projectid) +cli.add_command(boot) \ No newline at end of file diff --git a/chipforge/firmware/__init__.py b/chipforge/firmware/__init__.py index f7ca616..76e1ed2 100644 --- a/chipforge/firmware/__init__.py +++ b/chipforge/firmware/__init__.py @@ -1,4 +1,5 @@ from .flash import flash from .list import list_devices from .free import free -from .projectid import projectid \ No newline at end of file +from .projectid import projectid +from .boot import boot \ No newline at end of file diff --git a/chipforge/firmware/boot.py b/chipforge/firmware/boot.py new file mode 100644 index 0000000..5b69858 --- /dev/null +++ b/chipforge/firmware/boot.py @@ -0,0 +1,30 @@ +import click +from .. import colors as c +from .interface.hk import HKSpiBase, pass_device_with_options +from .usb_reset import usb_reset + +def abort_if_false(ctx, param, value): + if not value: + ctx.abort() + +@click.command() +@click.option('--yes', is_flag=True, callback=abort_if_false, + expose_value=False, + prompt='Are you sure you want to boot everyone? That\'s not a very nice thing to do...') +@pass_device_with_options +def boot(hk: HKSpiBase): + """ + Boot everyone user off of a usb device + """ + + if hasattr(hk, 'sysfs'): + if not usb_reset(sysfs=hk.sysfs): + click.echo(c.error('BOOT FAILED')) + return + + if hasattr(hk, 'usb_device'): + if not usb_reset(busnum=hk.usb_device.bus, devnum=hk.usb_device.address): + click.echo(c.error('BOOT FAILED')) + return + + click.echo(c.success('SUCCESS')) \ No newline at end of file diff --git a/chipforge/firmware/flash.py b/chipforge/firmware/flash.py index 67b86ac..886cd13 100644 --- a/chipforge/firmware/flash.py +++ b/chipforge/firmware/flash.py @@ -20,113 +20,140 @@ def flash(hk: HKSpiBase, hex_path: str): HEX_PATH should be either the path to a .hex file, or the path to a folder containing one. """ - if os.path.isdir(hex_path): - for file in os.listdir(hex_path): - if file.endswith('.hex'): - hex_path = os.path.join(hex_path, file) - break - else: - click.echo(c.error()) - click.echo(c.error(f'Could not find a {c.code(".hex")} file in {c.code(hex_path)}')) - click.echo(c.error()) - exit(1) - - hk.identify() - hk.cpu_reset_hold() - - time.sleep(0.5) - hk.led0.toggle() - - hk.flash_reset() - hk.flash_identify() - - - buf = bytearray() - addr = 0 - nbytes = 0 - total_bytes = 0 - - erased = [] - def ensure_erased(addr): - if (addr & hk.flash_sector_mask) not in erased: - erased.append(addr & hk.flash_sector_mask) - hk.flash_erase_sector(addr) - - click.echo(c.info('> Programming Flash')) - with open(hex_path, mode='r') as f: - x = f.readline() - while x != '': - if x[0] == '@': - addr = int(x[1:],16) - click.echo(f' Jumping to address 0x{addr:x}') + with hk: + if os.path.isdir(hex_path): + for file in os.listdir(hex_path): + if file.endswith('.hex'): + hex_path = os.path.join(hex_path, file) + break else: - # click.echo(x) - values = bytearray.fromhex(x[0:len(x)-1]) - buf[nbytes:nbytes] = values - nbytes += len(values) - # click.echo(binascii.hexlify(values)) + click.echo(c.error()) + click.echo(c.error(f'Could not find a {c.code(".hex")} file in {c.code(hex_path)}')) + click.echo(c.error()) + exit(1) - x = f.readline() + hk.identify() + hk.cpu_reset_hold() + + time.sleep(0.5) + hk.led0.toggle() + + hk.flash_reset() + hk.flash_identify() - if nbytes >= 256 or (x != '' and x[0] == '@' and nbytes > 0): + + buf = bytearray() + addr = 0 + nbytes = 0 + total_bytes = 0 + + erased = [] + def ensure_erased(addr): + if (addr & hk.flash_sector_mask) not in erased: + erased.append(addr & hk.flash_sector_mask) + hk.flash_erase_sector(addr) + + click.echo(c.info('> Programming Flash')) + with open(hex_path, mode='r') as f: + x = f.readline() + while x != '': + if x[0] == '@': + addr = int(x[1:],16) + click.echo(f' Jumping to address 0x{addr:x}') + else: + # click.echo(x) + values = bytearray.fromhex(x[0:len(x)-1]) + buf[nbytes:nbytes] = values + nbytes += len(values) + # click.echo(binascii.hexlify(values)) + + x = f.readline() + + if nbytes >= 256 or (x != '' and x[0] == '@' and nbytes > 0): + total_bytes += nbytes + # click.echo('\n----------------------\n') + # click.echo(binascii.hexlify(buf)) + # click.echo('\ntotal_bytes = {}'.format(total_bytes)) + + ensure_erased(addr) + hk.flash_write_page(addr, buf) + + if nbytes > 256: + buf = buf[255:] + addr += 256 + nbytes -= 256 + click.echo('*** over 256 hit') + else: + buf = bytearray() + addr += 256 + nbytes =0 + + if nbytes > 0: total_bytes += nbytes # click.echo('\n----------------------\n') # click.echo(binascii.hexlify(buf)) - # click.echo('\ntotal_bytes = {}'.format(total_bytes)) + # click.echo('\nnbytes = {}'.format(nbytes)) ensure_erased(addr) hk.flash_write_page(addr, buf) - if nbytes > 256: - buf = buf[255:] - addr += 256 - nbytes -= 256 - click.echo('*** over 256 hit') - else: - buf = bytearray() - addr += 256 - nbytes =0 - - if nbytes > 0: - total_bytes += nbytes - # click.echo('\n----------------------\n') - # click.echo(binascii.hexlify(buf)) - # click.echo('\nnbytes = {}'.format(nbytes)) + click.echo(c.success(f'> Programmed {c.code(total_bytes)} total bytes')) - ensure_erased(addr) - hk.flash_write_page(addr, buf) + click.echo(c.info('> Verifying Flash')) - click.echo(c.success(f'> Programmed {c.code(total_bytes)} total bytes')) + buf = bytearray() + addr = 0 + nbytes = 0 + total_bytes = 0 - click.echo(c.info('> Verifying Flash')) + while (hk.is_busy()): + time.sleep(0.5) - buf = bytearray() - addr = 0 - nbytes = 0 - total_bytes = 0 + # slave.write([CARAVEL_REG_WRITE, 0x0b, 0x01]) + # slave.write([CARAVEL_REG_WRITE, 0x0b, 0x00]) - while (hk.is_busy()): - time.sleep(0.5) - - # slave.write([CARAVEL_REG_WRITE, 0x0b, 0x01]) - # slave.write([CARAVEL_REG_WRITE, 0x0b, 0x00]) - - with open(hex_path, mode='r') as f: - x = f.readline() - while x != '': - if x[0] == '@': - addr = int(x[1:],16) - click.echo(f' Jumping to address 0x{addr:x}') - else: - values = bytearray.fromhex(x[0:len(x)-1]) - buf[nbytes:nbytes] = values - nbytes += len(values) + with open(hex_path, mode='r') as f: x = f.readline() - - if nbytes >= 256 or (x != '' and x[0] == '@' and nbytes > 0): + while x != '': + if x[0] == '@': + addr = int(x[1:],16) + click.echo(f' Jumping to address 0x{addr:x}') + else: + values = bytearray.fromhex(x[0:len(x)-1]) + buf[nbytes:nbytes] = values + nbytes += len(values) + x = f.readline() + + if nbytes >= 256 or (x != '' and x[0] == '@' and nbytes > 0): + total_bytes += nbytes + + read_cmd = bytearray((CARAVEL_PASSTHRU, CMD_READ_LO_SPEED,(addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff)) + buf2 = hk.slave.exchange(read_cmd, nbytes) + if buf == buf2: + click.echo(f' Page 0x{addr:x} is valid') + else: + click.echo(c.error()) + click.echo(c.error(f'Page 0x{addr:x} compare failed')) + click.echo(c.error(binascii.hexlify(buf))) + click.echo(c.error('<----->')) + click.echo(c.error(binascii.hexlify(buf2))) + click.echo(c.error()) + exit(1) + + if nbytes > 256: + buf = buf[255:] + addr += 256 + nbytes -= 256 + click.echo('*** over 256 hit') + else: + buf = bytearray() + addr += 256 + nbytes =0 + + if nbytes > 0: total_bytes += nbytes - read_cmd = bytearray((CARAVEL_PASSTHRU, CMD_READ_LO_SPEED,(addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff)) + read_cmd = bytearray((CARAVEL_PASSTHRU, CMD_READ_LO_SPEED, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff)) buf2 = hk.slave.exchange(read_cmd, nbytes) if buf == buf2: click.echo(f' Page 0x{addr:x} is valid') @@ -139,36 +166,12 @@ def flash(hk: HKSpiBase, hex_path: str): click.echo(c.error()) exit(1) - if nbytes > 256: - buf = buf[255:] - addr += 256 - nbytes -= 256 - click.echo('*** over 256 hit') - else: - buf = bytearray() - addr += 256 - nbytes =0 - - if nbytes > 0: - total_bytes += nbytes - - read_cmd = bytearray((CARAVEL_PASSTHRU, CMD_READ_LO_SPEED, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff)) - buf2 = hk.slave.exchange(read_cmd, nbytes) - if buf == buf2: - click.echo(f' Page 0x{addr:x} is valid') - else: - click.echo(c.error()) - click.echo(c.error(f'Page 0x{addr:x} compare failed')) - click.echo(c.error(binascii.hexlify(buf))) - click.echo(c.error('<----->')) - click.echo(c.error(binascii.hexlify(buf2))) - click.echo(c.error()) - exit(1) + click.echo(c.success(f'> Verified {c.code(total_bytes)} total bytes')) - click.echo(c.success(f'> Verified {c.code(total_bytes)} total bytes')) + click.echo(f'\nUART port is at {c.code(hk.uart_port)}') - hk.cpu_reset_release() + hk.cpu_reset_release() - hk.led0.toggle() - time.sleep(0.3) - hk.led0.toggle() \ No newline at end of file + hk.led0.toggle() + time.sleep(0.3) + hk.led0.toggle() \ No newline at end of file diff --git a/chipforge/firmware/interface/ftdi.py b/chipforge/firmware/interface/ftdi.py new file mode 100644 index 0000000..3f5ac08 --- /dev/null +++ b/chipforge/firmware/interface/ftdi.py @@ -0,0 +1,11 @@ +from pyftdi.ftdi import Ftdi +from pyftdi.usbtools import UsbTools, UsbDeviceDescriptor +from serial.tools.list_ports import comports +from ..usb_reset import get_bus_dev_from_sysfs_path + +def get_ftdi_url(dev: UsbDeviceDescriptor): + return UsbTools.build_dev_strings('ftdi', Ftdi.VENDOR_IDS, Ftdi.PRODUCT_IDS, [(dev, 1)])[0][0] + +def get_uart_port(dev: UsbDeviceDescriptor): + ports = [port for port in comports() if port.vid == 0x0403 and port.pid == dev.pid and port.serial_number == dev.sn and get_bus_dev_from_sysfs_path(port.device_path) == (dev.bus, dev.address)] + return ports[-1].device \ No newline at end of file diff --git a/chipforge/firmware/interface/hk.py b/chipforge/firmware/interface/hk.py index 2f89751..a718093 100644 --- a/chipforge/firmware/interface/hk.py +++ b/chipforge/firmware/interface/hk.py @@ -43,8 +43,7 @@ def pass_device_with_options(fn): else: drivers = ALL_DRIVERS - with HKSpi(drivers=drivers, serial_number=serial_number, device_id=device_id, override=override) as hk: - return fn(hk, *args, **kwargs) + fn(HKSpi(drivers=drivers, serial_number=serial_number, device_id=device_id, override=override), *args, **kwargs) wrapped_fn.__name__ = fn.__name__ wrapped_fn.__doc__ = fn.__doc__ diff --git a/chipforge/firmware/interface/hk_base.py b/chipforge/firmware/interface/hk_base.py index 6d7a2b2..557e403 100644 --- a/chipforge/firmware/interface/hk_base.py +++ b/chipforge/firmware/interface/hk_base.py @@ -23,7 +23,6 @@ class HKSpiBase: serial_number = '' if self.serial_number == None else f'({c.default(self.serial_number)})' return f'{c.code(self.device_id)} {serial_number} \t- {c.warning(self.__class__.__name__)} ({self.description})' - def identify(self): click.echo(c.info('> Caravel Hardware Info:')) diff --git a/chipforge/firmware/interface/hk_ft232h.py b/chipforge/firmware/interface/hk_ft232h.py index 974cd6b..821477f 100644 --- a/chipforge/firmware/interface/hk_ft232h.py +++ b/chipforge/firmware/interface/hk_ft232h.py @@ -2,16 +2,19 @@ from .gpio import Gpio from .defs import * from pyftdi.ftdi import Ftdi from pyftdi.spi import SpiController -from pyftdi.usbtools import UsbTools +from pyftdi.usbtools import UsbDeviceDescriptor from .hk_base import HKSpiBase +from .ftdi import get_ftdi_url, get_uart_port class HKSpiFT232H(HKSpiBase): description = 'eFabless Caravel Breakout' - def __init__(self, url: str, serial_number: str): - self.serial_number = serial_number - self.device_id = url - + def __init__(self, dev: UsbDeviceDescriptor): + self.usb_device = dev + self.serial_number = dev.sn + self.device_id = get_ftdi_url(dev) + self.uart_port = get_uart_port(dev) + def __enter__(self): self.spi = SpiController(cs_count=1) self.spi.configure(self.device_id) @@ -39,5 +42,5 @@ class HKSpiFT232H(HKSpiBase): # Find FT232H FTDI chips ftdidevs = [d for d in Ftdi.list_devices() if d[0].description == 'Single RS232-HS'] # build_dev_strings returns [(url, descriptor)], we only want the URL - return [HKSpiFT232H(UsbTools.build_dev_strings('ftdi', Ftdi.VENDOR_IDS, Ftdi.PRODUCT_IDS, (d,))[0][0], d[0].sn) for d in ftdidevs] + return [HKSpiFT232H(d[0]) for d in ftdidevs] diff --git a/chipforge/firmware/interface/hk_pico.py b/chipforge/firmware/interface/hk_pico.py index 499e404..b417bca 100644 --- a/chipforge/firmware/interface/hk_pico.py +++ b/chipforge/firmware/interface/hk_pico.py @@ -1,3 +1,4 @@ +from ..usb_reset import usb_reset from .hk_base import HKSpiBase, DummyLED from serial import Serial from serial.tools.list_ports import comports @@ -11,12 +12,10 @@ class PicoSPI: def open(self): if self.port == None: - print([repr(f) for f in comports()]) - self.port = next((f.device for f in comports() if f.description == 'STM32 USB-SPI Adapter'), None) - if self.port == None: - print('Could not find Pico SPI adapter') - exit(1) + print('Could not find Pico SPI adapter') + exit(1) + usb_reset(sysfs=self.sysfs) self.serial = Serial(self.port) def close(self): @@ -63,9 +62,11 @@ class PicoSPI: class HKSpiPico(HKSpiBase): description = 'ISU Chip Fab Caravel PMOD Breakout' - def __init__(self, comport): - self.serial_number = comport.serial_number - self.device_id = comport.device + def __init__(self, sysfs): + self.sysfs = sysfs + self.serial_number = sysfs.serial_number + self.device_id = sysfs.device + self.uart_port = next((f.device for f in comports() if f.location == sysfs.location[:-2] + '.0'), None) self.slave = PicoSPI(self.device_id) self.led0 = DummyLED() self.led1 = DummyLED() diff --git a/chipforge/firmware/interface/hk_stm32.py b/chipforge/firmware/interface/hk_stm32.py index c038dc8..c507892 100644 --- a/chipforge/firmware/interface/hk_stm32.py +++ b/chipforge/firmware/interface/hk_stm32.py @@ -11,10 +11,8 @@ class STM32SPI: def open(self): if self.port == None: - self.port = next((f.device for f in comports() if f.description == 'STM32 USB-SPI Adapter'), None) - if self.port == None: - print('Could not find USB SPI adapter') - exit(1) + print('Could not find Pico SPI adapter') + exit(1) self.serial = Serial(self.port, rtscts=True) @@ -67,9 +65,11 @@ class STM32SPI: class HKSpiSTM32(HKSpiBase): description = 'Caravel FPGA' - def __init__(self, comport): - self.serial_number = comport.serial_number - self.device_id = comport.device + def __init__(self, sysfs): + self.sysfs = sysfs + self.serial_number = sysfs.serial_number + self.device_id = sysfs.device + self.uart_port = None self.slave = STM32SPI(self.device_id) self.led0 = DummyLED() self.led1 = DummyLED() diff --git a/chipforge/firmware/interface/hk_xilinx.py b/chipforge/firmware/interface/hk_xilinx.py index 39631a5..8ba8a00 100644 --- a/chipforge/firmware/interface/hk_xilinx.py +++ b/chipforge/firmware/interface/hk_xilinx.py @@ -2,7 +2,8 @@ from .hk_base import HKSpiBase, DummyLED from pyftdi.jtag import JtagEngine from pyftdi.bits import BitSequence from pyftdi.ftdi import Ftdi -from pyftdi.usbtools import UsbTools +from pyftdi.usbtools import UsbDeviceDescriptor +from .ftdi import get_ftdi_url, get_uart_port def hexdump(data): return " ".join(f'{d:02x}' for d in data) @@ -19,11 +20,8 @@ class XilinxJtagSPI: def open(self): if self.url == None: - try: - self.url = HKSpiXilinx.list_devices()[0].device_id - except: - print('Could not find a Xilinx JTAG adapter') - exit(1) + print('Could not find a Xilinx JTAG adapter') + exit(1) self.jtag = JtagEngine(frequency=3E4) self.jtag.configure(self.url) @@ -95,9 +93,11 @@ class XilinxJtagSPI: class HKSpiXilinx(HKSpiBase): description = 'Xilinx FPGA' - def __init__(self, url: str, serial_number: str): - self.serial_number = serial_number - self.device_id = url + def __init__(self, dev: UsbDeviceDescriptor): + self.usb_device = dev + self.serial_number = dev.sn + self.device_id = get_ftdi_url(dev) + self.uart_port = get_uart_port(dev) self.slave = XilinxJtagSPI(self.device_id) self.led0 = DummyLED() self.led1 = DummyLED() @@ -116,5 +116,5 @@ class HKSpiXilinx(HKSpiBase): # Find Xilinx FPGA boards ftdidevs = [d for d in Ftdi.list_devices() if d[0].description == 'Digilent USB Device'] # build_dev_strings returns [(url, descriptor)], we only want the URL - return [HKSpiXilinx(UsbTools.build_dev_strings('ftdi', Ftdi.VENDOR_IDS, Ftdi.PRODUCT_IDS, (d,))[0][0], d[0].sn) for d in ftdidevs] + return [HKSpiXilinx(d[0]) for d in ftdidevs] diff --git a/chipforge/firmware/list.py b/chipforge/firmware/list.py index 0b7f24a..5062608 100644 --- a/chipforge/firmware/list.py +++ b/chipforge/firmware/list.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python3 import click +from .. import colors as c from .interface.defs import * from .interface.hk import ALL_DRIVERS, Claims, HKSpiBase @@ -12,5 +12,10 @@ def list_devices(): with Claims() as claims: devices = [device for driver in ALL_DRIVERS for device in driver.list_devices()] - for i, device in enumerate(devices): - click.echo(f' {i}) {device} {claims.get_owner_string(device)}') \ No newline at end of file + for i, d in enumerate(devices): + serial_number = '' if d.serial_number == None else f' ({c.default(d.serial_number)})' + + click.echo(f' {i}) {c.code(d.device_id)}{serial_number} {claims.get_owner_string(d)}' + + f'\n {c.warning(d.__class__.__name__)} ({d.description})' + + f'\n UART port: {c.code(d.uart_port)}' + + '\n') \ No newline at end of file diff --git a/chipforge/firmware/projectid.py b/chipforge/firmware/projectid.py index e9b42aa..8e6b662 100644 --- a/chipforge/firmware/projectid.py +++ b/chipforge/firmware/projectid.py @@ -9,4 +9,5 @@ def projectid(hk: HKSpiBase): """ Read the project ID of a physical board """ - hk.identify() \ No newline at end of file + with hk: + hk.identify() \ No newline at end of file diff --git a/chipforge/firmware/usb_reset.py b/chipforge/firmware/usb_reset.py new file mode 100644 index 0000000..a922232 --- /dev/null +++ b/chipforge/firmware/usb_reset.py @@ -0,0 +1,33 @@ +import os +import fcntl + +def get_bus_dev_from_sysfs_path(sysfs_path: str): + path = os.path.realpath(sysfs_path) + busnum = None + devnum = None + while path: + if os.path.exists(os.path.join(path, 'busnum')): + with open(os.path.join(path, 'busnum')) as busnum_file: + busnum = int(busnum_file.readline()) + with open(os.path.join(path, 'devnum')) as devnum_file: + devnum = int(devnum_file.readline()) + break + path = os.path.dirname(path) + return (busnum, devnum) + +def usb_reset(busnum=None, devnum=None, sysfs=None, sysfs_path=None, tty=None): + if tty: + sysfs_path = f'/sys/class/tty/{tty}/device' + + if sysfs: + sysfs_path = sysfs.device_path + + if sysfs_path: + busnum, devnum = get_bus_dev_from_sysfs(sysfs_path) + + if busnum == None or devnum == None: + return False + + with open(f'/dev/bus/usb/{("000" + str(busnum))[-3:]}/{("000" + str(devnum))[-3:]}', 'w', os.O_WRONLY) as port: + fcntl.ioctl(port, 21780) + return True -- GitLab