diff --git a/software/glasgow/applet/all.py b/software/glasgow/applet/all.py index 928f25de4..d8007b217 100644 --- a/software/glasgow/applet/all.py +++ b/software/glasgow/applet/all.py @@ -24,6 +24,8 @@ from .program.avr.spi import ProgramAVRSPIApplet from .program.ice40_flash import ProgramICE40FlashApplet from .program.ice40_sram import ProgramICE40SRAMApplet +from .program.ecp5_sram import ProgramECP5SRAMApplet +from .program.ecp5_flash import ProgramECP5FLASHApplet from .program.m16c import ProgramM16CApplet from .program.mec16xx import ProgramMEC16xxApplet from .program.nrf24lx1 import ProgramNRF24Lx1Applet diff --git a/software/glasgow/applet/program/ecp5_flash/__init__.py b/software/glasgow/applet/program/ecp5_flash/__init__.py new file mode 100644 index 000000000..2d23b42bc --- /dev/null +++ b/software/glasgow/applet/program/ecp5_flash/__init__.py @@ -0,0 +1,99 @@ +import argparse +import asyncio +import logging +import struct + +from ....arch.jtag import * +from ....arch.lattice.ecp5 import * +from ....support.bits import * +from ....support.logging import * +from ....database.jedec import * +from ....database.lattice.ecp5 import * +from ... import * +from ...interface.jtag_probe import JTAGProbeApplet, JTAGProbeStateTransitionError +from ..ecp5_sram import ProgramECP5SRAMInterface +from ....applet.memory._25x import Memory25xInterface, Memory25xApplet, Memory25xSFDPParser + + +class ProgramECP5FLASHInterface(Memory25xInterface, ProgramECP5SRAMInterface): + def __init__(self, interface, logger): + self.lower = interface + self._logger = logger + self._level = logging.DEBUG if self._logger.name == __name__ else logging.TRACE + + # Overwrite _command, to direct bits out into a JTAG-DR. + # The ECP5 then takes care of outputing data over SPI + async def _command(self, cmd, arg=[], dummy=0, ret=0): + arg = bytes(arg) + + self._log("cmd=%02X arg=<%s> dummy=%d ret=%d", cmd, dump_hex(arg), dummy, ret) + + # CS will remain LOW while we are in SHIFT_DR state + await self.lower.enter_shift_dr() + + input_bytes = bytearray([cmd, *arg, *[0 for _ in range(dummy)]]) + bits_to_send = bits() + for b in input_bytes: + bits_to_send += bits.from_int(b, 8).reversed() + + await self.lower.shift_tdi(bits_to_send, last=(ret == 0)) + + if ret > 0: + tdo_bits = await self.lower.shift_tdo(ret*8) + + # Release CS pin + await self.lower.enter_pause_dr() + + # Reverse bits in every byte to fix LSB bit order of JTAG + if ret > 0: + tdo_bytes = [tdo_bits[i:i + 8] for i in range(0, len(tdo_bits), 8)] + result = [] + + for b in tdo_bytes: + result.append(b.reversed().to_int()) + + self._log("result=<%s>", dump_hex(result)) + + return bytearray(result) + return None + + async def _enter_spi_background_mode(self): + # Erase currently configured bitstream + await self.lower.write_ir(IR_ISC_ENABLE) + await self.lower.run_test_idle(100) + + await self.lower.write_ir(IR_ISC_ERASE) + await self.lower.write_dr(bits.from_int(0,8)) + await self.lower.run_test_idle(100) + + await self.lower.write_ir(IR_ISC_DISABLE) + await self.lower.run_test_idle(100) + + # Enable background SPI + await self.lower.write_ir(IR_LSC_BACKGROUD_SPI) + await self.lower.write_dr(bits.from_int(0x68FE,16)) + await self.lower.run_test_idle(100) + + +class ProgramECP5FLASHApplet(JTAGProbeApplet, name="program-ecp5-flash"): + logger = logging.getLogger(__name__) + help = "Program ECP5 configuration SPI FLASH via JTAG" + description = """ + Program the non-volatile configuration memory of a SPI FLASH chip connected to a ECP5 FPGAs + """ + + @classmethod + def add_interact_arguments(cls, parser): + Memory25xApplet.add_interact_arguments(parser) + + # TODO:: Add options for SRAM erase before programming, toggle REFRESH ofter complete, check status, etc. + + async def run(self, device, args): + ecp5_iface = await self.run_lower(ProgramECP5FLASHApplet, device, args) + return ProgramECP5FLASHInterface(ecp5_iface, self.logger) + + async def interact(self, device, args, ecp5_iface): + #bitstream = args.bitstream.read() + await ecp5_iface.identify() + await ecp5_iface._enter_spi_background_mode() + await Memory25xApplet().interact(device, args, ecp5_iface) \ No newline at end of file diff --git a/software/glasgow/applet/program/ecp5_sram/__init__.py b/software/glasgow/applet/program/ecp5_sram/__init__.py new file mode 100644 index 000000000..3a534abe6 --- /dev/null +++ b/software/glasgow/applet/program/ecp5_sram/__init__.py @@ -0,0 +1,114 @@ +import argparse +import asyncio +import logging +import struct + +from ....arch.jtag import * +from ....arch.lattice.ecp5 import * +from ....support.bits import * +from ....support.logging import * +from ....database.jedec import * +from ....database.lattice.ecp5 import * +from ... import * +from ...interface.jtag_probe import JTAGProbeApplet, JTAGProbeStateTransitionError + + +class ProgramECP5SRAMInterface: + def __init__(self, interface, logger): + self.lower = interface + self._logger = logger + self._level = logging.DEBUG if self._logger.name == __name__ else logging.TRACE + + async def _read_idcode(self): + await self.lower.test_reset() + await self.lower.write_ir(IR_READ_ID) + raw_bits = await self.lower.read_dr(32) + idcode = struct.unpack("> 7, + idcode.mfg_id & 0x7f) or \ + "unknown" + self._logger.info("manufacturer=%#05x (%s) part=%#06x version=%#03x", + idcode.mfg_id, mfg_name, idcode.part_id, idcode.version) + + # Decode to actual ECP5 devices + device = devices_by_idcode[idcode_value] or None + if device is None: + raise GlasgowAppletError("IDCODE does not mtach ECP5 device", hex(idcode_value)) + self._logger.info("Found Device: %s", device) + + async def _check_status(self, status): + self._logger.info("Status Register: 0x%#08x", status.to_int()) + self._logger.info(" %s", status) + + async def program(self, bitstream): + await self.identify() + + # perform programming + await self.lower.write_ir(IR_ISC_ENABLE) + await self.lower.run_test_idle(10) + # Device can now accept a new bitstream + await self.lower.write_ir(IR_LSC_BITSTREAM_BURST) + + # Send entire bitstream data into DR, + # Bytes are expected MSB by the ECP5, so need to be reversed + # Slit bitstream up into chunks just for improving JTAG throughput + chunk_size = 128 + bitstream_chunks = [bitstream[i:i + chunk_size] for i in range(0, len(bitstream), chunk_size)] + + await self.lower.enter_shift_dr() + for chunk in bitstream_chunks: + chunk_bits = bits() + for b in chunk: + chunk_bits += bits.from_int(b, 8).reversed() + + await self.lower.shift_tdi(chunk_bits, last=False) + await self.lower.enter_update_dr() + + + await self.lower.write_ir(IR_ISC_DISABLE) + await self.lower.run_test_idle(10) + + # Check status + # Note ECP5 will not release until STATUS is read + status = await self._read_status() + + if status.DONE: + self._logger.info("Configuration Done") + else: + await self._check_status(status) + raise GlasgowAppletError("Configuration error. DONE not set", status.BSEErrorCode()) + + + +class ProgramECP5SRAMApplet(JTAGProbeApplet, name="program-ecp5-sram"): + logger = logging.getLogger(__name__) + help = "Program ECP5 configuration sram via JTAG" + description = """ + Program the volatile configuration memory of ECP5 FPGAs + """ + + @classmethod + def add_interact_arguments(cls, parser): + parser.add_argument( + "bitstream", metavar="BITSTREAM", type=argparse.FileType("rb"), + help="bitstream file") + + async def run(self, device, args): + jtag_iface = await self.run_lower(ProgramECP5SRAMApplet, device, args) + return ProgramECP5SRAMInterface(jtag_iface, self._logger) + + async def interact(self, device, args, ecp5_iface): + bitstream = args.bitstream.read() + await ecp5_iface.program(bitstream) \ No newline at end of file diff --git a/software/glasgow/arch/lattice/__init__.py b/software/glasgow/arch/lattice/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/software/glasgow/arch/lattice/ecp5.py b/software/glasgow/arch/lattice/ecp5.py new file mode 100644 index 000000000..97094922c --- /dev/null +++ b/software/glasgow/arch/lattice/ecp5.py @@ -0,0 +1,85 @@ +from ...support.bits import * +from ...support.bitstruct import * +from collections import namedtuple + + +# IR Values +IR_READ_ID = bits("11100000") +IR_LSC_READ_STATUS = bits("00111100") +IR_ISC_ENABLE = bits("11000110") +IR_ISC_DISABLE = bits("00100110") +IR_ISC_ERASE = bits("00001110") +IR_LSC_BITSTREAM_BURST = bits("01111010") +IR_LSC_BACKGROUD_SPI = bits("00111010") + +LSC_Status_bits = bitstruct("LSC_STATUS", 32, [ + ("Transparent_Mode", 1), + ("Config_Target", 3), + ("JTAG_Active", 1), + ("PWD_Protection", 1), + (None, 1), # Not used + ("Decrypt_Enable", 1), + ("DONE", 1), + ("ISC_Enable", 1), + ("Write_Enable", 1), + ("Read_Enable", 1), + ("Busy_Flag", 1), + ("Fail_Flag", 1), + ("FEA_OTP", 1), + ("Decrypt_Only", 1), + ("PWD_Enable", 1), + (None, 3), # Not used + ("Encrypt_Preamble", 1), + ("Std_Preamble", 1), + ("SPIm_Fail_1", 1), + ("BSE_Error_Code", 3), + ("Execution_Error", 1), + ("ID_Error", 1), + ("Invalid_Command", 1), + ("SED_Error", 1), + ("Bypass_Mode", 1), + ("Flow_Through_Mode",1), +]) + +BSEErrorCode = namedtuple("BSEErrorCode", ("code","error","error_info")) + +bse_error_code = [ + BSEErrorCode(0b000, "No Error", None ), + BSEErrorCode(0b001, "ID Error", None ), + BSEErrorCode(0b010, "CMD Error", "illegal command" ), + BSEErrorCode(0b011, "CRC Error", None ), + BSEErrorCode(0b100, "PRMB Error", "preamble error" ), + BSEErrorCode(0b101, "ABRT Error", "configuration aborted by the user" ), + BSEErrorCode(0b110, "OVFL Error", "data overflow error" ), + BSEErrorCode(0b111, "SDM Error", "bitstream pass the size of SRAM array"), +] + +ConfigTargetCode = namedtuple("ConfigTargetCode", ("code","target")) + +config_target_code = [ + ConfigTargetCode(0b000, "SRAM"), + ConfigTargetCode(0b001, "eFuse"), +] + +class LSC_Status(LSC_Status_bits): + def __init__(self): + ... + + def __iter__(self): + properties = {} + properties["Config Target"] = "{}".format(config_target_code[self.Config_Target]) + properties["BSE Error Code"] = "{}".format(bse_error_code[self.BSE_Error_Code]) + + return iter(properties.items()) + + def BSEErrorCode(self): + return bse_error_code[self.BSE_Error_Code] + + def flags_repl(self): + s = "" + for i in self: + s += " {}".format(i) + return s + + def __repr__(self): + return "<{}.{} {}{}>".format(self.__module__, self.__class__.__name__, self.bits_repr(), self.flags_repl()) \ No newline at end of file diff --git a/software/glasgow/database/lattice/__init__.py b/software/glasgow/database/lattice/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/software/glasgow/database/lattice/ecp5.py b/software/glasgow/database/lattice/ecp5.py new file mode 100644 index 000000000..561dcb8af --- /dev/null +++ b/software/glasgow/database/lattice/ecp5.py @@ -0,0 +1,27 @@ +from collections import defaultdict, namedtuple + + +__all__ = ["devices", "devices_by_idcode", "devices_by_name"] + + +ECP5Device = namedtuple("ECP5Device", ("name", "idcode")) + + +devices = [ + ECP5Device("LFE5U-12", idcode=0x21111043), + ECP5Device("LFE5U-25", idcode=0x41111043), + ECP5Device("LFE5U-45", idcode=0x41112043), + ECP5Device("LFE5U-85", idcode=0x41113043), + ECP5Device("LFE5UM-25", idcode=0x01111043), + ECP5Device("LFE5UM-45", idcode=0x01112043), + ECP5Device("LFE5UM-85", idcode=0x01113043), + ECP5Device("LFE5UM5G-25", idcode=0x81111043), + ECP5Device("LFE5UM5G-45", idcode=0x81112043), + ECP5Device("LFE5UM5G-85", idcode=0x81113043), +] + +devices_by_idcode = defaultdict(lambda: None, + ((device.idcode, device) for device in devices)) + +devices_by_name = defaultdict(lambda: None, + ((device.name, device) for device in devices))