def __init__(self, firmware_file=None, vendor_id=VID_QIHW, product_id=PID_GLASGOW): self.usb_context = usb1.USBContext() self.usb_poller = _PollerThread(self.usb_context) self.usb_poller.start() self._open_device(vendor_id, product_id) device_id = self.usb.getDevice().getbcdDevice() if device_id & 0xFF00 in (0x0000, 0xA000): revision = chr(ord("A") + (device_id & 0xFF) - 1) logger.debug("found rev%s device without firmware", revision) if firmware_file is None: raise GlasgowDeviceError("firmware is not uploaded") else: logger.debug("loading firmware from %s", firmware_file) with open(firmware_file, "rb") as f: self._download_firmware(input_data(f, fmt="ihex")) # let the device re-enumerate and re-acquire it time.sleep(1) self._open_device(VID_QIHW, PID_GLASGOW) # still not the right firmware? if self.usb.getDevice().getbcdDevice() & 0xFF00 in (0x0000, 0xA000): raise GlasgowDeviceError("firmware upload failed") # https://github.com/vpelletier/python-libusb1/issues/39 # serial = self.usb.getDevice().getSerialNumber() serial = self.usb.getASCIIStringDescriptor( self.usb.getDevice().device_descriptor.iSerialNumber) logger.debug("found device with serial %s", serial)
def __init__(self, firmware_file=None, vendor_id=VID_QIHW, product_id=PID_GLASGOW): super().__init__(vendor_id, product_id) device_id = self.usb.getDevice().getbcdDevice() if device_id & 0xFF00 in (0x0000, 0xA000): revision = chr(ord("A") + (device_id & 0xFF) - 1) logger.debug("found rev%s device without firmware", revision) if firmware_file is None: raise GlasgowDeviceError("Firmware is not uploaded") else: logger.debug("loading firmware from %s", firmware_file) with open(firmware_file, "rb") as f: self.load_ram(input_data(f, fmt="ihex")) # let the device re-enumerate and re-acquire it time.sleep(1) super().__init__(VID_QIHW, PID_GLASGOW) # still not the right firmware? if self.usb.getDevice().getbcdDevice() & 0xFF00 in (0x0000, 0xA000): raise GlasgowDeviceError("Firmware upload failed") logger.debug("found device with serial %s", self.usb.getDevice().getSerialNumber()) self.claimed_ports = []
def __init__(self, firmware_file=None): super().__init__(VID_QIHW, PID_GLASGOW) if self._device.getDevice().getbcdDevice() == 0: if firmware_file is None: raise GlasgowDeviceError("Firmware is not uploaded") else: # TODO: log? with open(firmware_file, "rb") as f: self.load_ram(input_data(f, fmt="ihex")) # let the device re-enumerate and re-acquire it time.sleep(1) super().__init__(VID_QIHW, PID_GLASGOW) # still not the right firmware? if self._device.getDevice().getbcdDevice() == 0: raise GlasgowDeviceError("Firmware upload failed")
async def interact(self, device, args, avr_iface): await avr_iface.programming_enable() signature = await avr_iface.read_signature() device = devices_by_signature[signature] self.logger.info("device signature: %s (%s)", "{:02x} {:02x} {:02x}".format(*signature), "unknown" if device is None else device.name) if args.operation is not None and device is None: raise GlasgowAppletError("cannot operate on unknown device") if args.operation == "read": if args.fuses: fuses = await avr_iface.read_fuse_range(range(device.fuses_size)) if device.fuses_size > 2: self.logger.info("fuses: low %s high %s extra %s", "{:08b}".format(fuses[0]), "{:08b}".format(fuses[1]), "{:08b}".format(fuses[2])) elif device.fuses_size > 1: self.logger.info("fuses: low %s high %s", "{:08b}".format(fuses[0]), "{:08b}".format(fuses[1])) else: self.logger.info("fuse: %s", "{:08b}".format(fuses[0])) if args.lock_bits: lock_bits = await avr_iface.read_lock_bits() self.logger.info("lock bits: %s", "{:08b}".format(lock_bits)) if args.calibration: calibration = \ await avr_iface.read_calibration_range(range(device.calibration_size)) self.logger.info("calibration bytes: %s", " ".join(["%02x" % b for b in calibration])) if args.program: self._check_format(args.program, "program memory") self.logger.info("reading program memory (%d bytes)", device.program_size) output_data(args.program, await avr_iface.read_program_memory_range(range(device.program_size))) if args.eeprom: self._check_format(args.eeprom, "EEPROM") self.logger.info("reading EEPROM (%d bytes)", device.eeprom_size) output_data(args.eeprom, await avr_iface.read_eeprom_range(range(device.eeprom_size))) if args.operation == "write-fuses": if args.high and device.fuses_size < 2: raise GlasgowAppletError("device does not have high fuse") if args.low: self.logger.info("writing low fuse") await avr_iface.write_fuse(0, args.low) written = await avr_iface.read_fuse(0) if written != args.low: raise GlasgowAppletError("verification of low fuse failed: %s" % "{:08b} != {:08b}".format(written, args.low)) if args.high: self.logger.info("writing high fuse") await avr_iface.write_fuse(1, args.high) written = await avr_iface.read_fuse(1) if written != args.high: raise GlasgowAppletError("verification of high fuse failed: %s" % "{:08b} != {:08b}".format(written, args.high)) if args.operation == "write-lock": self.logger.info("writing lock bits") await avr_iface.write_lock_bits(args.bits) written = await avr_iface.read_lock_bits() if written != args.bits: raise GlasgowAppletError("verification of lock bits failed: %s" % "{:08b} != {:08b}".format(written, args.bits)) if args.operation == "write-program": self.logger.info("erasing chip") await avr_iface.chip_erase() self._check_format(args.file, "program memory") data = input_data(args.file) self.logger.info("writing program memory (%d bytes)", sum([len(chunk) for address, chunk in data])) for address, chunk in data: chunk = bytes(chunk) await avr_iface.write_program_memory_range(address, chunk, device.program_page) written = await avr_iface.read_program_memory_range(range(address, len(chunk))) if written != chunk: raise GlasgowAppletError("verification failed at address %#06x: %s != %s" % (address, written.hex(), chunk.hex())) if args.operation == "write-eeprom": self._check_format(args.file, "EEPROM") data = input_data(args.file) self.logger.info("writing EEPROM (%d bytes)", sum([len(chunk) for address, chunk in data])) for address, chunk in data: chunk = bytes(chunk) await avr_iface.write_eeprom_range(address, chunk, device.eeprom_page) written = await avr_iface.read_eeprom_range(range(address, len(chunk))) if written != chunk: raise GlasgowAppletError("verification failed at address %#06x: %s != %s" % (address, written.hex(), chunk.hex())) await avr_iface.programming_disable()
async def _main(): args = get_argparser().parse_args() create_logger(args) try: firmware_file = os.path.join(os.path.dirname(__file__), "glasgow.ihex") if args.action in ("build", "test", "tool"): pass elif args.action == "factory": device = GlasgowHardwareDevice(firmware_file, VID_CYPRESS, PID_FX2) else: device = GlasgowHardwareDevice(firmware_file) if args.action == "voltage": if args.voltage is not None: await device.reset_alert(args.ports) await device.poll_alert() # clear any remaining alerts try: await device.set_voltage(args.ports, args.voltage) except: await device.set_voltage(args.ports, 0.0) raise if args.set_alert and args.voltage != 0.0: await asyncio.sleep( 0.050) # let the output capacitor discharge a bit await device.set_alert_tolerance(args.ports, args.voltage, args.tolerance / 100) print("Port\tVio\tVlimit\tVsense\tMonitor") alerts = await device.poll_alert() for port in args.ports: vio = await device.get_voltage(port) vlimit = await device.get_voltage_limit(port) vsense = await device.measure_voltage(port) alert = await device.get_alert(port) notice = "" if port in alerts: notice += " (ALERT)" print("{}\t{:.2}\t{:.2}\t{:.3}\t{:.2}-{:.2}\t{}".format( port, vio, vlimit, vsense, alert[0], alert[1], notice)) if args.action == "voltage-limit": if args.voltage is not None: await device.set_voltage_limit(args.ports, args.voltage) print("Port\tVio\tVlimit") for port in args.ports: vio = await device.get_voltage(port) vlimit = await device.get_voltage_limit(port) print("{}\t{:.2}\t{:.2}".format(port, vio, vlimit)) if args.action == "run": if args.applet: target, applet = _applet(device.revision, args) device.demultiplexer = DirectDemultiplexer(device) await device.download_target( target, rebuild=args.rebuild, toolchain_opts=_toolchain_opts(args)) if args.trace: logger.info("starting applet analyzer") await device.write_register(target.analyzer.addr_done, 0) analyzer_iface = await device.demultiplexer.claim_interface( target.analyzer, target.analyzer.mux_interface, args=None) trace_decoder = TraceDecoder(target.analyzer.event_sources) vcd_writer = VCDWriter( args.trace, timescale="1 ns", check_values=False, comment='Generated by Glasgow for bitstream ID %s' % target.get_bitstream_id().hex()) async def run_analyzer(): if not args.trace: return signals = {} strobes = set() for field_name, field_trigger, field_width in trace_decoder.events( ): if field_trigger == "throttle": var_type = "wire" var_init = 0 elif field_trigger == "change": var_type = "wire" var_init = "x" elif field_trigger == "strobe": if field_width > 0: var_type = "tri" var_init = "z" else: var_type = "event" var_init = "" else: assert False signals[field_name] = vcd_writer.register_var( scope="", name=field_name, var_type=var_type, size=field_width, init=var_init) if field_trigger == "strobe": strobes.add(field_name) init = True while not trace_decoder.is_done(): trace_decoder.process(await analyzer_iface.read()) for cycle, events in trace_decoder.flush(): if events == "overrun": target.analyzer.logger.error( "FIFO overrun, shutting down") for name in signals: vcd_writer.change(signals[name], next_timestamp, "x") timestamp += 1e3 # 1us break event_repr = " ".join("{}={}".format(n, v) for n, v in events.items()) target.analyzer.logger.trace( "cycle %d: %s", cycle, event_repr) timestamp = 1e9 * (cycle + 0) // target.sys_clk_freq next_timestamp = 1e9 * (cycle + 1) // target.sys_clk_freq if init: init = False vcd_writer._timestamp = timestamp for name, value in events.items(): vcd_writer.change(signals[name], timestamp, value) for name, _value in events.items(): if name in strobes: vcd_writer.change(signals[name], next_timestamp, "z") vcd_writer.flush() vcd_writer.close(timestamp) async def run_applet(): logger.info("running handler for applet %r", args.applet) try: iface = await applet.run(device, args) await applet.interact(device, args, iface) except GlasgowAppletError as e: applet.logger.error(str(e)) finally: if args.trace: await device.write_register( target.analyzer.addr_done, 1) done, pending = await asyncio.wait( [run_analyzer(), run_applet()], return_when=asyncio.FIRST_EXCEPTION) for task in done: await task # Work around bugs in python-libusb1 that cause segfaults on interpreter shutdown. await device.demultiplexer.flush() else: with args.bitstream as f: logger.info("downloading bitstream from %r", f.name) await device.download_bitstream(f.read()) if args.action == "tool": tool = GlasgowApplet.all_applets[args.applet].tool_cls() await tool.run(args) if args.action == "flash": logger.info("reading device configuration") header = await device.read_eeprom("fx2", 0, 8 + 4 + GlasgowConfig.size) header[0] = 0xC2 # see below fx2_config = FX2Config.decode(header, partial=True) if (len(fx2_config.firmware) != 1 or fx2_config.firmware[0][0] != 0x4000 - GlasgowConfig.size or len(fx2_config.firmware[0][1]) != GlasgowConfig.size): raise SystemExit( "Unrecognized or corrupted configuration block") glasgow_config = GlasgowConfig.decode(fx2_config.firmware[0][1]) logger.info("device has serial %s-%s", glasgow_config.revision, glasgow_config.serial) if fx2_config.disconnect: logger.info("device has flashed firmware") else: logger.info("device does not have flashed firmware") if glasgow_config.bitstream_size: logger.info("device has flashed bitstream ID %s", glasgow_config.bitstream_id.hex()) else: logger.info("device does not have flashed bitstream") new_bitstream = b"" if args.remove_bitstream: logger.info("removing bitstream") glasgow_config.bitstream_size = 0 glasgow_config.bitstream_id = b"\x00" * 16 elif args.bitstream: logger.info("using bitstream from %s", args.bitstream.name) with args.bitstream as f: new_bitstream = f.read() glasgow_config.bitstream_size = len(new_bitstream) glasgow_config.bitstream_id = b"\xff" * 16 elif args.applet: logger.info("building bitstream for applet %s", args.applet) target, applet = _applet(device.revision, args) new_bitstream_id = target.get_bitstream_id() new_bitstream = target.get_bitstream(**_toolchain_opts(args)) # We always build and reflash the bitstream in case the one currently # in EEPROM is corrupted. If we only compared the ID, there would be # no easy way to recover from that case. There's also no point in # storing the bitstream hash (as opposed to Verilog hash) in the ID, # as building the bitstream takes much longer than flashing it. logger.info("built bitstream ID %s", new_bitstream_id.hex()) glasgow_config.bitstream_size = len(new_bitstream) glasgow_config.bitstream_id = new_bitstream_id fx2_config.firmware[0] = (0x4000 - GlasgowConfig.size, glasgow_config.encode()) if args.remove_firmware: logger.info("removing firmware") fx2_config.disconnect = False new_image = fx2_config.encode() new_image[0] = 0xC0 # see below else: logger.info( "using firmware from %r", args.firmware.name if args.firmware else firmware_file) with (args.firmware or open(firmware_file, "rb")) as f: for (addr, chunk) in input_data(f, fmt="ihex"): fx2_config.append(addr, chunk) fx2_config.disconnect = True new_image = fx2_config.encode() if new_bitstream: logger.info("programming bitstream") old_bitstream = await device.read_eeprom( "ice", 0, len(new_bitstream)) if old_bitstream != new_bitstream: for (addr, chunk) in diff_data(old_bitstream, new_bitstream): await device.write_eeprom("ice", addr, chunk) logger.info("verifying bitstream") if await device.read_eeprom( "ice", 0, len(new_bitstream)) != new_bitstream: logger.critical("bitstream programming failed") return 1 else: logger.info("bitstream identical") logger.info("programming configuration and firmware") old_image = await device.read_eeprom("fx2", 0, len(new_image)) if old_image != new_image: for (addr, chunk) in diff_data(old_image, new_image): await device.write_eeprom("fx2", addr, chunk) logger.info("verifying configuration and firmware") if await device.read_eeprom("fx2", 0, len(new_image)) != new_image: logger.critical( "configuration/firmware programming failed") return 1 else: logger.info("configuration and firmware identical") if args.action == "build": target, applet = _applet(args.rev, args) if args.type in ("v", "verilog"): logger.info("building Verilog for applet %r", args.applet) target.get_verilog().write(args.filename or args.applet + ".v") if args.type in ("bin", "bitstream"): logger.info("building bitstream for applet %r", args.applet) with open(args.filename or args.applet + ".bin", "wb") as f: f.write(target.get_bitstream(**_toolchain_opts(args))) if args.type in ("zip", "archive"): logger.info("building archive for applet %r", args.applet) with target.get_build_tree() as tree: if args.filename: basename, = os.path.splitext(args.filename) else: basename = args.applet shutil.make_archive(basename, format="zip", root_dir=tree, logger=logger) if args.action == "test": logger.info("testing applet %r", args.applet) applet = GlasgowApplet.all_applets[args.applet]() loader = unittest.TestLoader() stream = unittest.runner._WritelnDecorator(sys.stderr) result = unittest.TextTestResult(stream=stream, descriptions=True, verbosity=2) result.failfast = True def startTest(test): unittest.TextTestResult.startTest(result, test) result.stream.write("\n") result.startTest = startTest if args.tests == []: suite = loader.loadTestsFromTestCase(applet.test_cls) suite.run(result) else: for test in args.tests: suite = loader.loadTestsFromName(test, module=applet.test_cls) suite.run(result) if not result.wasSuccessful(): for _, traceback in result.errors + result.failures: print(traceback, end="", file=sys.stderr) return 1 if args.action == "factory": logger.info("reading device configuration") header = await device.read_eeprom("fx2", 0, 8 + 4 + GlasgowConfig.size) if not re.match(rb"^\xff+$", header): if args.force: logger.warning( "device already factory-programmed, proceeding anyway") else: logger.error("device already factory-programmed") return 1 fx2_config = FX2Config(vendor_id=VID_QIHW, product_id=PID_GLASGOW, device_id=1 + ord(args.rev) - ord('A'), i2c_400khz=True) glasgow_config = GlasgowConfig(args.rev, args.serial) fx2_config.append(0x4000 - GlasgowConfig.size, glasgow_config.encode()) image = fx2_config.encode() # Let FX2 hardware enumerate. This won't load the configuration block # into memory automatically, but the firmware has code that does that # if it detects a C0 load. image[0] = 0xC0 logger.info("programming device configuration") await device.write_eeprom("fx2", 0, image) logger.info("verifying device configuration") if await device.read_eeprom("fx2", 0, len(image)) != image: logger.critical("factory programming failed") return 1 except GlasgowDeviceError as e: logger.error(e) return 1 except GatewareBuildError as e: applet.logger.error(e) return 1 return 0
def builtin_firmware(): with importlib.resources.open_text(__package__, "firmware.ihex") as f: return input_data(f, fmt="ihex")
async def _main(): args = get_argparser().parse_args() create_logger(args) device = None try: # TODO(py3.7): use importlib.resources firmware_filename = os.path.join(os.path.dirname(__file__), "glasgow.ihex") if args.action in ("build", "test", "tool"): pass elif args.action == "factory": device = GlasgowHardwareDevice(args.serial, firmware_filename, _factory_rev=args.factory_rev) else: device = GlasgowHardwareDevice(args.serial, firmware_filename) if args.action == "voltage": if args.voltage is not None: await device.reset_alert(args.ports) await device.poll_alert() # clear any remaining alerts try: await device.set_voltage(args.ports, args.voltage) except: await device.set_voltage(args.ports, 0.0) raise if args.set_alert and args.voltage != 0.0: await asyncio.sleep( 0.050) # let the output capacitor discharge a bit await device.set_alert_tolerance(args.ports, args.voltage, args.tolerance / 100) print("Port\tVio\tVlimit\tVsense\tMonitor") alerts = await device.poll_alert() for port in args.ports: vio = await device.get_voltage(port) vlimit = await device.get_voltage_limit(port) vsense = await device.measure_voltage(port) alert = await device.get_alert(port) notice = "" if port in alerts: notice += " (ALERT)" print("{}\t{:.2}\t{:.2}\t{:.3}\t{:.2}-{:.2}\t{}".format( port, vio, vlimit, vsense, alert[0], alert[1], notice)) if args.action == "safe": await device.reset_alert("AB") await device.set_voltage("AB", 0.0) await device.poll_alert() # clear any remaining alerts logger.info("all ports safe") if args.action == "voltage-limit": if args.voltage is not None: await device.set_voltage_limit(args.ports, args.voltage) print("Port\tVio\tVlimit") for port in args.ports: vio = await device.get_voltage(port) vlimit = await device.get_voltage_limit(port) print("{}\t{:.2}\t{:.2}".format(port, vio, vlimit)) if args.action in ("run", "run-repl", "run-prebuilt"): target, applet = _applet(device.revision, args) device.demultiplexer = DirectDemultiplexer( device, target.multiplexer.pipe_count) plan = target.build_plan() if args.action in ("run", "run-repl"): await device.download_target(plan, rebuild=args.rebuild) if args.action == "run-prebuilt": bitstream_file = args.bitstream or open( "{}.bin".format(args.applet), "rb") with bitstream_file: logger.warn("downloading prebuilt bitstream from %r", bitstream_file.name) await device.download_bitstream(bitstream_file.read()) do_trace = hasattr(args, "trace") and args.trace if do_trace: logger.info("starting applet analyzer") await device.write_register(target.analyzer.addr_done, 0) analyzer_iface = await device.demultiplexer.claim_interface( target.analyzer, target.analyzer.mux_interface, args=None) trace_decoder = TraceDecoder(target.analyzer.event_sources) vcd_writer = VCDWriter( args.trace, timescale="1 ns", check_values=False, comment='Generated by Glasgow for bitstream ID %s' % plan.bitstream_id.hex()) async def run_analyzer(): signals = {} strobes = set() for field_name, field_trigger, field_width in trace_decoder.events( ): if field_trigger == "throttle": var_type = "wire" var_init = 0 elif field_trigger == "change": var_type = "wire" var_init = "x" elif field_trigger == "strobe": if field_width > 0: var_type = "tri" var_init = "z" else: var_type = "event" var_init = "" else: assert False signals[field_name] = vcd_writer.register_var( scope="", name=field_name, var_type=var_type, size=field_width, init=var_init) if field_trigger == "strobe": strobes.add(field_name) init = True while not trace_decoder.is_done(): trace_decoder.process(await analyzer_iface.read()) for cycle, events in trace_decoder.flush(): if events == "overrun": target.analyzer.logger.error( "FIFO overrun, shutting down") for name in signals: vcd_writer.change(signals[name], next_timestamp, "x") timestamp += 1e3 # 1us break event_repr = " ".join("{}={}".format(n, v) for n, v in events.items()) target.analyzer.logger.trace("cycle %d: %s", cycle, event_repr) timestamp = 1e9 * (cycle + 0) // target.sys_clk_freq next_timestamp = 1e9 * (cycle + 1) // target.sys_clk_freq if init: init = False vcd_writer._timestamp = timestamp for name, value in events.items(): vcd_writer.change(signals[name], timestamp, value) for name, _value in events.items(): if name in strobes: vcd_writer.change(signals[name], next_timestamp, "z") vcd_writer.flush() vcd_writer.close(timestamp) async def run_applet(): logger.info("running handler for applet %r", args.applet) if applet.preview: logger.warn( "applet %r is PREVIEW QUALITY and may CORRUPT DATA", args.applet) try: iface = await applet.run(device, args) if args.action in ("run", "run-prebuilt"): await applet.interact(device, args, iface) if args.action == "run-repl": if applet.has_custom_repl: logger.warn( "applet provides customized REPL(s); consider using `run " "{} ...-repl` subcommands".format(applet.name)) logger.info( "dropping to REPL; use 'help(iface)' to see available APIs" ) await AsyncInteractiveConsole(locals={ "iface": iface }).interact() except GlasgowAppletError as e: applet.logger.error(str(e)) except asyncio.CancelledError: pass # terminate gracefully finally: await device.demultiplexer.flush() async def wait_for_sigint(): await wait_for_signal(signal.SIGINT) logger.debug("Ctrl+C pressed, terminating") if do_trace: analyzer_task = asyncio.ensure_future(run_analyzer()) applet_task = asyncio.ensure_future(run_applet()) sigint_task = asyncio.ensure_future(wait_for_sigint()) tasks = [applet_task, sigint_task] done, pending = await asyncio.wait( tasks, return_when=asyncio.FIRST_COMPLETED) for task in pending: task.cancel() for task in tasks: try: await task except asyncio.CancelledError: pass if do_trace: await device.write_register(target.analyzer.addr_done, 1) await analyzer_task await device.demultiplexer.cancel() if args.action == "tool": tool = GlasgowApplet.all_applets[args.applet].tool_cls() try: await tool.run(args) except GlasgowAppletError as e: tool.logger.error(e) raise SystemExit() if args.action == "flash": logger.info("reading device configuration") header = await device.read_eeprom("fx2", 0, 8 + 4 + GlasgowConfig.size) header[0] = 0xC2 # see below fx2_config = FX2Config.decode(header, partial=True) if (len(fx2_config.firmware) != 1 or fx2_config.firmware[0][0] != 0x4000 - GlasgowConfig.size or len(fx2_config.firmware[0][1]) != GlasgowConfig.size): raise SystemExit( "Unrecognized or corrupted configuration block") glasgow_config = GlasgowConfig.decode(fx2_config.firmware[0][1]) logger.info("device has serial %s-%s", glasgow_config.revision, glasgow_config.serial) if fx2_config.disconnect: logger.info("device has flashed firmware") else: logger.info("device does not have flashed firmware") if glasgow_config.bitstream_size: logger.info("device has flashed bitstream ID %s", glasgow_config.bitstream_id.hex()) else: logger.info("device does not have flashed bitstream") new_bitstream = b"" if args.remove_bitstream: logger.info("removing bitstream") glasgow_config.bitstream_size = 0 glasgow_config.bitstream_id = b"\x00" * 16 elif args.bitstream: logger.info("using bitstream from %s", args.bitstream.name) with args.bitstream as f: new_bitstream = f.read() glasgow_config.bitstream_size = len(new_bitstream) glasgow_config.bitstream_id = b"\xff" * 16 elif args.applet: logger.info("building bitstream for applet %s", args.applet) target, applet = _applet(device.revision, args) plan = target.build_plan() new_bitstream_id = plan.bitstream_id new_bitstream = plan.execute() # We always build and reflash the bitstream in case the one currently # in EEPROM is corrupted. If we only compared the ID, there would be # no easy way to recover from that case. There's also no point in # storing the bitstream hash (as opposed to Verilog hash) in the ID, # as building the bitstream takes much longer than flashing it. logger.info("built bitstream ID %s", new_bitstream_id.hex()) glasgow_config.bitstream_size = len(new_bitstream) glasgow_config.bitstream_id = new_bitstream_id fx2_config.firmware[0] = (0x4000 - GlasgowConfig.size, glasgow_config.encode()) if args.remove_firmware: logger.info("removing firmware") fx2_config.disconnect = False new_image = fx2_config.encode() new_image[0] = 0xC0 # see below else: logger.info( "using firmware from %r", args.firmware.name if args.firmware else firmware_filename) with (args.firmware or open(firmware_filename, "rb")) as f: for (addr, chunk) in input_data(f, fmt="ihex"): fx2_config.append(addr, chunk) fx2_config.disconnect = True new_image = fx2_config.encode() if new_bitstream: logger.info("programming bitstream") old_bitstream = await device.read_eeprom( "ice", 0, len(new_bitstream)) if old_bitstream != new_bitstream: for (addr, chunk) in diff_data(old_bitstream, new_bitstream): await device.write_eeprom("ice", addr, chunk) logger.info("verifying bitstream") if await device.read_eeprom( "ice", 0, len(new_bitstream)) != new_bitstream: logger.critical("bitstream programming failed") return 1 else: logger.info("bitstream identical") logger.info("programming configuration and firmware") old_image = await device.read_eeprom("fx2", 0, len(new_image)) if old_image != new_image: for (addr, chunk) in diff_data(old_image, new_image): await device.write_eeprom("fx2", addr, chunk) logger.info("verifying configuration and firmware") if await device.read_eeprom("fx2", 0, len(new_image)) != new_image: logger.critical( "configuration/firmware programming failed") return 1 else: logger.info("configuration and firmware identical") if args.action == "build": target, applet = _applet(args.rev, args) plan = target.build_plan() if args.type in ("il", "rtlil"): logger.info("building RTLIL for applet %r", args.applet) with open(args.filename or args.applet + ".il", "wt") as f: f.write(plan.rtlil) if args.type in ("bin", "bitstream"): logger.info("building bitstream for applet %r", args.applet) with open(args.filename or args.applet + ".bin", "wb") as f: f.write(plan.execute()) if args.type in ("zip", "archive"): logger.info("building archive for applet %r", args.applet) plan.archive(args.filename or args.applet + ".zip") if args.action == "test": logger.info("testing applet %r", args.applet) applet = GlasgowApplet.all_applets[args.applet]() loader = unittest.TestLoader() stream = unittest.runner._WritelnDecorator(sys.stderr) result = unittest.TextTestResult(stream=stream, descriptions=True, verbosity=2) result.failfast = True def startTest(test): unittest.TextTestResult.startTest(result, test) result.stream.write("\n") result.startTest = startTest if args.tests == []: suite = loader.loadTestsFromTestCase(applet.test_cls) suite.run(result) else: for test in args.tests: suite = loader.loadTestsFromName(test, module=applet.test_cls) suite.run(result) if not result.wasSuccessful(): for _, traceback in result.errors + result.failures: print(traceback, end="", file=sys.stderr) return 1 if args.action == "factory": logger.info("reading device configuration") header = await device.read_eeprom("fx2", 0, 8 + 4 + GlasgowConfig.size) if not re.match(rb"^\xff+$", header): if args.force: logger.warning( "device already factory-programmed, proceeding anyway") else: logger.error("device already factory-programmed") return 1 fx2_config = FX2Config(vendor_id=VID_QIHW, product_id=PID_GLASGOW, device_id=GlasgowConfig.encode_revision( args.rev), i2c_400khz=True) glasgow_config = GlasgowConfig(args.factory_rev, args.factory_serial) fx2_config.append(0x4000 - GlasgowConfig.size, glasgow_config.encode()) image = fx2_config.encode() # Let FX2 hardware enumerate. This won't load the configuration block # into memory automatically, but the firmware has code that does that # if it detects a C0 load. image[0] = 0xC0 logger.info("programming device configuration") await device.write_eeprom("fx2", 0, image) logger.info("verifying device configuration") if await device.read_eeprom("fx2", 0, len(image)) != image: logger.critical("factory programming failed") return 1 except GlasgowDeviceError as e: logger.error(e) return 1 except GatewareBuildError as e: applet.logger.error(e) return 1 finally: if device is not None: device.close() return 0
async def interact(self, device, args, nrf24lx1_iface): page_size = 512 if args.device == "LE1": memory_map = _nrf24le1_map buffer_size = 512 elif args.device == "LU1p32k": memory_map = _nrf24lu1p_32k_map buffer_size = 256 elif args.device == "LU1p16k": memory_map = _nrf24lu1p_16k_map buffer_size = 256 else: assert False try: await nrf24lx1_iface.reset_program() async def check_info_page(address): old_status = await nrf24lx1_iface.read_status() try: await nrf24lx1_iface.write_status(FSR_BIT_INFEN) fuse, = await nrf24lx1_iface.read(address, 1) return fuse != 0xff finally: await nrf24lx1_iface.write_status(old_status) async def check_read_protected(): if await check_info_page(0x23): raise ProgramNRF24Lx1Error("MCU is read protected; run `erase --info-page`") if args.operation == "read": await check_read_protected() chunks = [] for memory_area in memory_map: self.logger.info("reading %s memory", memory_area.name) if memory_area.spi_addr & 0x10000: await nrf24lx1_iface.write_status(FSR_BIT_INFEN) else: await nrf24lx1_iface.write_status(0) area_data = await nrf24lx1_iface.read(memory_area.spi_addr & 0xffff, memory_area.size) chunks.append((memory_area.mem_addr, area_data)) output_data(args.file, chunks, fmt="ihex") if args.operation == "program": await check_read_protected() area_index = 0 memory_area = memory_map[area_index] erased_pages = set() for chunk_mem_addr, chunk_data in sorted(input_data(args.file, fmt="ihex"), key=lambda c: c[0]): if len(chunk_data) == 0: continue if chunk_mem_addr < memory_area.mem_addr: raise ProgramNRF24Lx1Error("data outside of memory map at {:#06x}" .format(chunk_mem_addr)) while chunk_mem_addr >= memory_area.mem_addr + memory_area.size: area_index += 1 if area_index >= len(memory_area): raise ProgramNRF24Lx1Error("data outside of memory map at {:#06x}" .format(chunk_mem_addr)) memory_area = memory_map[area_index] if chunk_mem_addr + len(chunk_data) > memory_area.mem_addr + memory_area.size: raise ProgramNRF24Lx1Error("data outside of memory map at {:#06x}" .format(memory_area.mem_addr + memory_area.size)) if memory_area.spi_addr & 0x10000 and not args.info_page: self.logger.warn("data provided for info page, but info page programming " "is not enabled") continue chunk_spi_addr = (chunk_mem_addr - memory_area.mem_addr + memory_area.spi_addr) & 0xffff if memory_area.spi_addr & 0x10000: level = logging.WARN await nrf24lx1_iface.write_status(FSR_BIT_INFEN) else: level = logging.INFO await nrf24lx1_iface.write_status(0) overwrite_pages = set(range( (chunk_spi_addr // page_size), (chunk_spi_addr + len(chunk_data) + page_size - 1) // page_size)) need_erase_pages = overwrite_pages - erased_pages if need_erase_pages: for page in need_erase_pages: page_addr = (memory_area.spi_addr & 0x10000) | (page * page_size) self.logger.log(level, "erasing %s memory at %#06x+%#06x", memory_area.name, page_addr, page_size) await nrf24lx1_iface.write_enable() await nrf24lx1_iface.erase_page(page) await nrf24lx1_iface.wait_status() erased_pages.update(need_erase_pages) self.logger.log(level, "programming %s memory at %#06x+%#06x", memory_area.name, chunk_mem_addr, len(chunk_data)) while len(chunk_data) > 0: await nrf24lx1_iface.write_enable() await nrf24lx1_iface.program(chunk_spi_addr, chunk_data[:buffer_size]) await nrf24lx1_iface.wait_status() chunk_data = chunk_data[buffer_size:] chunk_spi_addr += buffer_size if args.operation == "erase": if args.info_page: await nrf24lx1_iface.write_status(FSR_BIT_INFEN) info_page = await nrf24lx1_iface.read(0x0000, 0x0100) self.logger.warn("backing up info page to %s", args.info_page) if os.path.isfile(args.info_page): raise ProgramNRF24Lx1Error("info page backup file already exists") with open(args.info_page, "wb") as f: output_data(f, [(0x10000, info_page)]) self.logger.warn("erasing code and data memory, and info page") else: await check_read_protected() await nrf24lx1_iface.write_status(0) self.logger.info("erasing code and data memory") try: await nrf24lx1_iface.write_enable() await nrf24lx1_iface.erase_all() await nrf24lx1_iface.wait_status() if args.info_page: self.logger.info("restoring info page DSYS area") await nrf24lx1_iface.write_enable() await nrf24lx1_iface.program(0, info_page[:32]) # DSYS only await nrf24lx1_iface.wait_status() except: if args.info_page: self.logger.error("IMPORTANT: programming failed; restore DSYS manually " "using `program --info-page %s`", args.info_page) raise if args.operation == "protect-read": if await check_info_page(0x23): raise ProgramNRF24Lx1Error("memory read protection is already enabled") self.logger.warn("protecting code and data memory from reads") await nrf24lx1_iface.write_enable() await nrf24lx1_iface.disable_read() await nrf24lx1_iface.wait_status() if args.operation == "enable-debug": if await check_info_page(0x24): raise ProgramNRF24Lx1Error("hardware debugging features already enabled") self.logger.info("enabling hardware debugging features") await nrf24lx1_iface.write_enable() await nrf24lx1_iface.enable_debug() await nrf24lx1_iface.wait_status() finally: await nrf24lx1_iface.reset_application()
async def _main(): args = get_argparser().parse_args() root_logger = logging.getLogger() root_logger.setLevel(logging.INFO + args.quiet * 10 - args.verbose * 10) handler = logging.StreamHandler() formatter_args = {"fmt": "[{levelname:>8s}] {name:s}: {message:s}", "style": "{"} if sys.stderr.isatty() and sys.platform != 'win32': handler.setFormatter(ANSIColorFormatter(**formatter_args)) else: handler.setFormatter(logging.Formatter(**formatter_args)) root_logger.addHandler(handler) try: firmware_file = os.path.join(os.path.dirname(__file__), "glasgow.ihex") if args.action in ("build", "test"): pass elif args.action == "factory": device = GlasgowHardwareDevice(firmware_file, VID_CYPRESS, PID_FX2) else: device = GlasgowHardwareDevice(firmware_file) if args.action == "voltage": if args.voltage is not None: await device.reset_alert(args.ports) await device.poll_alert() # clear any remaining alerts try: await device.set_voltage(args.ports, args.voltage) except: await device.set_voltage(args.ports, 0.0) raise if args.set_alert and args.voltage != 0.0: await asyncio.sleep(0.050) # let the output capacitor discharge a bit await device.set_alert_tolerance(args.ports, args.voltage, args.tolerance / 100) print("Port\tVio\tVlimit\tVsense\tMonitor") alerts = await device.poll_alert() for port in args.ports: vio = await device.get_voltage(port) vlimit = await device.get_voltage_limit(port) vsense = await device.measure_voltage(port) alert = await device.get_alert(port) notice = "" if port in alerts: notice += " (ALERT)" print("{}\t{:.2}\t{:.2}\t{:.3}\t{:.2}-{:.2}\t{}" .format(port, vio, vlimit, vsense, alert[0], alert[1], notice)) if args.action == "voltage-limit": if args.voltage is not None: await device.set_voltage_limit(args.ports, args.voltage) print("Port\tVio\tVlimit") for port in args.ports: vio = await device.get_voltage(port) vlimit = await device.get_voltage_limit(port) print("{}\t{:.2}\t{:.2}" .format(port, vio, vlimit)) if args.action == "run": if args.applet: target, applet = _applet(args) device.demultiplexer = DirectDemultiplexer(device) bitstream_id = target.get_bitstream_id() if await device.bitstream_id() == bitstream_id and not args.force: logger.info("device already has bitstream ID %s", bitstream_id.hex()) else: logger.info("building bitstream ID %s for applet %r", bitstream_id.hex(), args.applet) await device.download_bitstream(target.get_bitstream(debug=True), bitstream_id) logger.info("running handler for applet %r", args.applet) try: iface = await applet.run(device, args) await applet.interact(device, args, iface) except GlasgowAppletError as e: applet.logger.error(str(e)) # Work around bugs in python-libusb1 that cause segfaults on interpreter shutdown. await device.demultiplexer.flush() else: with args.bitstream as f: logger.info("downloading bitstream from %r", f.name) await device.download_bitstream(f.read()) if args.action == "flash": logger.info("reading device configuration") header = await device.read_eeprom("fx2", 0, 8 + 4 + GlasgowConfig.size) header[0] = 0xC2 # see below fx2_config = FX2Config.decode(header, partial=True) if (len(fx2_config.firmware) != 1 or fx2_config.firmware[0][0] != 0x4000 - GlasgowConfig.size or len(fx2_config.firmware[0][1]) != GlasgowConfig.size): raise SystemExit("Unrecognized or corrupted configuration block") glasgow_config = GlasgowConfig.decode(fx2_config.firmware[0][1]) logger.info("device has serial %s-%s", glasgow_config.revision, glasgow_config.serial) if fx2_config.disconnect: logger.info("device has flashed firmware") else: logger.info("device does not have flashed firmware") if glasgow_config.bitstream_size: logger.info("device has flashed bitstream ID %s", glasgow_config.bitstream_id.hex()) else: logger.info("device does not have flashed bitstream") new_bitstream = b"" if args.remove_bitstream: logger.info("removing bitstream") glasgow_config.bitstream_size = 0 glasgow_config.bitstream_id = b"\x00"*16 elif args.bitstream: logger.info("using bitstream from %s", args.bitstream.name) with args.bitstream as f: new_bitstream = f.read() glasgow_config.bitstream_size = len(new_bitstream) glasgow_config.bitstream_id = b"\xff"*16 elif args.applet: logger.info("building bitstream for applet %s", args.applet) target, applet = _applet(args) new_bitstream_id = target.get_bitstream_id() new_bitstream = target.get_bitstream(debug=True) # We always build and reflash the bitstream in case the one currently # in EEPROM is corrupted. If we only compared the ID, there would be # no easy way to recover from that case. There's also no point in # storing the bitstream hash (as opposed to Verilog hash) in the ID, # as building the bitstream takes much longer than flashing it. logger.info("built bitstream ID %s", new_bitstream_id.hex()) glasgow_config.bitstream_size = len(new_bitstream) glasgow_config.bitstream_id = new_bitstream_id fx2_config.firmware[0] = (0x4000 - GlasgowConfig.size, glasgow_config.encode()) if args.remove_firmware: logger.info("removing firmware") fx2_config.disconnect = False new_image = fx2_config.encode() new_image[0] = 0xC0 # see below else: logger.info("using firmware from %r", args.firmware.name if args.firmware else firmware_file) with (args.firmware or open(firmware_file, "rb")) as f: for (addr, chunk) in input_data(f, fmt="ihex"): fx2_config.append(addr, chunk) fx2_config.disconnect = True new_image = fx2_config.encode() if new_bitstream: logger.info("programming bitstream") old_bitstream = await device.read_eeprom("ice", 0, len(new_bitstream)) if old_bitstream != new_bitstream: for (addr, chunk) in diff_data(old_bitstream, new_bitstream): await device.write_eeprom("ice", addr, chunk) logger.info("verifying bitstream") if await device.read_eeprom("ice", 0, len(new_bitstream)) != new_bitstream: logger.critical("bitstream programming failed") return 1 else: logger.info("bitstream identical") logger.info("programming configuration and firmware") old_image = await device.read_eeprom("fx2", 0, len(new_image)) if old_image != new_image: for (addr, chunk) in diff_data(old_image, new_image): await device.write_eeprom("fx2", addr, chunk) logger.info("verifying configuration and firmware") if await device.read_eeprom("fx2", 0, len(new_image)) != new_image: logger.critical("configuration/firmware programming failed") return 1 else: logger.info("configuration and firmware identical") if args.action == "build": target, applet = _applet(args) logger.info("building bitstream for applet %r", args.applet) if args.type in ("v", "verilog"): target.get_verilog().write(args.filename or args.applet + ".v") if args.type in ("bin", "bitstream"): with open(args.filename or args.applet + ".bin", "wb") as f: f.write(target.get_bitstream(debug=True)) if args.type in ("zip", "archive"): with target.get_build_tree() as tree: if args.filename: basename, = os.path.splitext(args.filename) else: basename = args.applet shutil.make_archive(basename, format="zip", root_dir=tree, logger=logger) if args.action == "test": logger.info("testing applet %r", args.applet) applet = GlasgowApplet.all_applets[args.applet]() loader = unittest.TestLoader() stream = unittest.runner._WritelnDecorator(sys.stderr) result = unittest.TextTestResult(stream=stream, descriptions=True, verbosity=2) result.failfast = True def startTest(test): unittest.TextTestResult.startTest(result, test) result.stream.write("\n") result.startTest = startTest if args.tests == []: suite = loader.loadTestsFromTestCase(applet.test_cls) suite.run(result) else: for test in args.tests: suite = loader.loadTestsFromName(test, module=applet.test_cls) suite.run(result) if not result.wasSuccessful(): for _, traceback in result.errors + result.failures: print(traceback, end="", file=sys.stderr) return 1 if args.action == "internal-test": if args.mode == "toggle-io": await device.download_bitstream(TestToggleIO().get_bitstream(debug=True)) await device.set_voltage("AB", 3.3) if args.mode == "mirror-i2c": await device.download_bitstream(TestMirrorI2C().get_bitstream(debug=True)) await device.set_voltage("A", 3.3) if args.mode == "shift-out": await device.download_bitstream(TestShiftOut(is_async=args.is_async) .get_bitstream(debug=True)) await device.set_voltage("A", 3.3) if args.mode == "gen-seq": await device.download_bitstream(TestGenSeq().get_bitstream(debug=True)) if args.mode == "pll": await device.download_bitstream(TestPLL().get_bitstream(debug=True)) if args.mode == "registers": await device.download_bitstream(TestRegisters().get_bitstream(debug=True)) if args.action == "factory": logger.info("reading device configuration") header = await device.read_eeprom("fx2", 0, 8 + 4 + GlasgowConfig.size) if not re.match(rb"^\xff+$", header): logger.error("device already factory-programmed") return 1 fx2_config = FX2Config(vendor_id=VID_QIHW, product_id=PID_GLASGOW, device_id=1 + ord(args.revision) - ord('A'), i2c_400khz=True) glasgow_config = GlasgowConfig(args.revision, args.serial) fx2_config.append(0x4000 - GlasgowConfig.size, glasgow_config.encode()) image = fx2_config.encode() # Let FX2 hardware enumerate. This won't load the configuration block # into memory automatically, but the firmware has code that does that # if it detects a C0 load. image[0] = 0xC0 logger.info("programming device configuration") await device.write_eeprom("fx2", 0, image) logger.info("verifying device configuration") if await device.read_eeprom("fx2", 0, len(image)) != image: logger.critical("factory programming failed") return 1 except GlasgowDeviceError as e: logger.error(e) return 1 return 0
def __init__(self, serial=None, firmware_filename=None, *, _factory_rev=None): usb_context = usb1.USBContext() firmware = None handles = {} discover = True while discover: discover = False for device in usb_context.getDeviceIterator(): vendor_id = device.getVendorID() product_id = device.getProductID() device_id = device.getbcdDevice() if _factory_rev is None: if (vendor_id, product_id) != (VID_QIHW, PID_GLASGOW): continue revision = GlasgowConfig.decode_revision(device_id & 0xFF) else: if (vendor_id, product_id) != (VID_CYPRESS, PID_FX2): continue revision = _factory_rev if device_id & 0xFF00 in (0x0000, 0xA000): if firmware_filename is None: logger.warn( "found device without firmware, but no firmware is provided" ) continue elif firmware is None: logger.debug("loading firmware from %s", firmware_filename) with open(firmware_filename, "rb") as f: firmware = input_data(f, fmt="ihex") logger.debug("loading firmware to rev%s device", revision) handle = device.open() handle.controlWrite(usb1.REQUEST_TYPE_VENDOR, REQ_RAM, REG_CPUCS, 0, [1]) for address, data in firmware: while len(data) > 0: handle.controlWrite(usb1.REQUEST_TYPE_VENDOR, REQ_RAM, address, 0, data[:4096]) data = data[4096:] address += 4096 handle.controlWrite(usb1.REQUEST_TYPE_VENDOR, REQ_RAM, REG_CPUCS, 0, [0]) handle.close() # And rediscover the device after it reenumerates. discover = True else: handle = device.open() device_serial = handle.getASCIIStringDescriptor( device.getSerialNumberDescriptor()) if device_serial in handles: continue logger.debug("found rev%s device with serial %s", revision, device_serial) handles[device_serial] = (revision, handle) if discover: # Give every device we loaded firmware onto a bit of time to reenumerate. time.sleep(1.0) if len(handles) == 0: raise GlasgowDeviceError("device not found") if serial is None: if len(handles) > 1: raise GlasgowDeviceError( "found {} devices (serial numbers {}), but a serial " "number is not specified".format(len(handles), ", ".join( handles.keys()))) else: if serial not in handles: raise GlasgowDeviceError( "device with serial number {} not found".format(serial)) self.usb_context = usb_context self.usb_poller = _PollerThread(self.usb_context) self.usb_poller.start() if serial is None: self.revision, self.usb_handle = next(iter(handles.values())) else: self.revision, self.usb_handle = handles[serial] try: self.usb_handle.setAutoDetachKernelDriver(True) except usb1.USBErrorNotSupported: pass
def main(): args = get_argparser().parse_args() root_logger = logging.getLogger() root_logger.setLevel(logging.INFO + args.quiet * 10 - args.verbose * 10) handler = logging.StreamHandler() handler.setFormatter(logging.Formatter("[%(levelname)5s] %(name)s: %(message)s")) root_logger.addHandler(handler) try: firmware_file = os.path.join(os.path.dirname(__file__), "glasgow.ihex") if args.action in ("build",): pass elif args.action == "factory": device = GlasgowDevice(firmware_file, VID_CYPRESS, PID_FX2) else: device = GlasgowDevice(firmware_file) if args.action == "voltage": if args.voltage is not None: device.reset_alert(args.ports) device.poll_alert() # clear any remaining alerts try: device.set_voltage(args.ports, args.voltage) except: device.set_voltage(args.ports, 0.0) raise if args.set_alert and args.voltage != 0.0: time.sleep(0.050) # let the output capacitor discharge a bit device.set_alert_tolerance(args.ports, args.voltage, args.tolerance / 100) print("Port\tVio\tVsense\tRange") alerts = device.poll_alert() for port in args.ports: vio = device.get_voltage(port) vsense = device.measure_voltage(port) alert = device.get_alert(port) if port in alerts: notice = " (ALERT)" else: notice = "" print("{}\t{:.2}\t{:.3}\t{:.2}-{:.2}{}" .format(port, vio, vsense, alert[0], alert[1], notice)) if args.action == "run": if args.applet: applet, target = _applet(args) bitstream_id = target.get_bitstream_id() if device.bitstream_id() == bitstream_id and not args.force: logger.info("device already has bitstream ID %s", bitstream_id.hex()) else: logger.info("building bitstream ID %s for applet %s", bitstream_id.hex(), args.applet) device.download_bitstream(target.get_bitstream(debug=True), bitstream_id) logger.info("running handler for applet %s", args.applet) applet.run(device, args) else: with args.bitstream as f: logger.info("downloading bitstream from %s", f.name) device.download_bitstream(f.read()) if args.action == "flash": logger.info("reading device configuration") header = device.read_eeprom("fx2", 0, 8 + 4 + GlasgowConfig.size) header[0] = 0xC2 # see below fx2_config = FX2Config.decode(header, partial=True) if (len(fx2_config.firmware) != 1 or fx2_config.firmware[0][0] != 0x4000 - GlasgowConfig.size or len(fx2_config.firmware[0][1]) != GlasgowConfig.size): raise SystemExit("Unrecognized or corrupted configuration block") glasgow_config = GlasgowConfig.decode(fx2_config.firmware[0][1]) logger.info("device has serial %s-%s", glasgow_config.revision, glasgow_config.serial) if fx2_config.disconnect: logger.info("device has flashed firmware") else: logger.info("device does not have flashed firmware") if glasgow_config.bitstream_size: logger.info("device has flashed bitstream ID %s", glasgow_config.bitstream_id.hex()) else: logger.info("device does not have flashed bitstream") new_bitstream = b"" if args.remove_bitstream: logger.info("removing bitstream") glasgow_config.bitstream_size = 0 glasgow_config.bitstream_id = b"\x00"*16 elif args.bitstream: logger.info("using bitstream from %s", args.bitstream.name) with args.bitstream as f: new_bitstream = f.read() glasgow_config.bitstream_size = len(new_bitstream) glasgow_config.bitstream_id = b"\xff"*16 elif args.applet: logger.info("building bitstream for applet %s", args.applet) applet, target = _applet(args) new_bitstream_id = target.get_bitstream_id() new_bitstream = target.get_bitstream() # We always build and reflash the bitstream in case the one currently # in EEPROM is corrupted. If we only compared the ID, there would be # no easy way to recover from that case. There's also no point in # storing the bitstream hash (as opposed to Verilog hash) in the ID, # as building the bitstream takes much longer than flashing it. logger.info("built bitstream ID %s", new_bitstream_id.hex()) glasgow_config.bitstream_size = len(new_bitstream) glasgow_config.bitstream_id = new_bitstream_id fx2_config.firmware[0] = (0x4000 - GlasgowConfig.size, glasgow_config.encode()) if args.remove_firmware: logger.info("removing firmware") fx2_config.disconnect = False new_image = fx2_config.encode() new_image[0] = 0xC0 # see below else: logger.info("using firmware from %s", args.firmware.name if args.firmware else firmware_file) with (args.firmware or open(firmware_file, "rb")) as f: for (addr, chunk) in input_data(f, fmt="ihex"): fx2_config.append(addr, chunk) fx2_config.disconnect = True new_image = fx2_config.encode() if new_bitstream: logger.info("programming bitstream") old_bitstream = device.read_eeprom("ice", 0, len(new_bitstream)) if old_bitstream != new_bitstream: for (addr, chunk) in diff_data(old_bitstream, new_bitstream): device.write_eeprom("ice", addr, chunk) logger.info("verifying bitstream") if device.read_eeprom("ice", 0, len(new_bitstream)) != new_bitstream: raise SystemExit("Bitstream programming failed") else: logger.info("bitstream identical") logger.info("programming configuration and firmware") old_image = device.read_eeprom("fx2", 0, len(new_image)) if old_image != new_image: for (addr, chunk) in diff_data(old_image, new_image): device.write_eeprom("fx2", addr, chunk) logger.info("verifying configuration and firmware") if device.read_eeprom("fx2", 0, len(new_image)) != new_image: raise SystemExit("Configuration/firmware programming failed") else: logger.info("configuration and firmware identical") if args.action == "build": applet, target = _applet(args) logger.info("building bitstream for applet %s", args.applet) if args.type in ("v", "verilog"): target.get_verilog().write(args.filename or args.applet + ".v") if args.type in ("bin", "bitstream"): with open(args.filename or args.applet + ".bin", "wb") as f: f.write(target.get_bitstream(debug=True)) if args.action == "test": if args.mode == "toggle-io": device.download_bitstream(TestToggleIO().get_bitstream(debug=True)) device.set_voltage("AB", 3.3) if args.mode == "mirror-i2c": device.download_bitstream(TestMirrorI2C().get_bitstream(debug=True)) device.set_voltage("A", 3.3) if args.mode == "shift-out": device.download_bitstream(TestShiftOut(async=args.async) .get_bitstream(debug=True)) device.set_voltage("A", 3.3) if args.mode == "gen-seq": device.download_bitstream(TestGenSeq().get_bitstream(debug=True)) if args.mode == "pll": device.download_bitstream(TestPLL().get_bitstream(debug=True)) if args.action == "factory": logger.info("reading device configuration") header = device.read_eeprom("fx2", 0, 8 + 4 + GlasgowConfig.size) if not re.match(rb"^\xff+$", header): raise SystemExit("Device already factory-programmed") fx2_config = FX2Config(vendor_id=VID_QIHW, product_id=PID_GLASGOW, device_id=1 + ord(args.revision) - ord('A'), i2c_400khz=True) glasgow_config = GlasgowConfig(args.revision, args.serial) fx2_config.append(0x4000 - GlasgowConfig.size, glasgow_config.encode()) image = fx2_config.encode() # Let FX2 hardware enumerate. This won't load the configuration block # into memory automatically, but the firmware has code that does that # if it detects a C0 load. image[0] = 0xC0 logger.info("programming device configuration") device.write_eeprom("fx2", 0, image) logger.info("verifying device configuration") if device.read_eeprom("fx2", 0, len(image)) != image: raise SystemExit("Factory programming failed") except (ValueError, FX2DeviceError) as e: raise SystemExit(e)