From 60736372e62405ff001e2a1df4aa82760d5a9f3e Mon Sep 17 00:00:00 2001 From: Catherine Date: Sat, 28 Oct 2023 15:06:18 +0000 Subject: [PATCH] applet.program.ecp5_sram: new applet. (WIP) --- docs/archive | 2 +- .../applet/program/ecp5_sram/__init__.py | 128 ++++++++++++++++++ .../glasgow/applet/program/ecp5_sram/test.py | 8 ++ software/glasgow/arch/lattice/__init__.py | 0 software/glasgow/arch/lattice/ecp5.py | 91 +++++++++++++ software/glasgow/database/lattice/__init__.py | 0 software/glasgow/database/lattice/ecp5.py | 27 ++++ software/pyproject.toml | 1 + 8 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 software/glasgow/applet/program/ecp5_sram/__init__.py create mode 100644 software/glasgow/applet/program/ecp5_sram/test.py create mode 100644 software/glasgow/arch/lattice/__init__.py create mode 100644 software/glasgow/arch/lattice/ecp5.py create mode 100644 software/glasgow/database/lattice/__init__.py create mode 100644 software/glasgow/database/lattice/ecp5.py diff --git a/docs/archive b/docs/archive index 6e45b9a2f..bc5bc920c 160000 --- a/docs/archive +++ b/docs/archive @@ -1 +1 @@ -Subproject commit 6e45b9a2f57b59e663b39b8a14582bc8a310b55b +Subproject commit bc5bc920caa891bf8913de2a7f076bb51dbf7582 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..6e65e4890 --- /dev/null +++ b/software/glasgow/applet/program/ecp5_sram/__init__.py @@ -0,0 +1,128 @@ +# XXX: where is JTAG programming described? + +import sys +import logging +import argparse + +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 + + +class ECP5JTAGError(GlasgowAppletError): + pass + + +class ECP5JTAGInterface: + def __init__(self, interface, logger): + self.lower = interface + self._logger = logger + self._level = logging.DEBUG if self._logger.name == __name__ else logging.TRACE + + def _log(self, message, *args): + self._logger.log(self._level, f"ECP5: " + message, *args) + + async def identify(self): + await self.lower.test_reset() + await self.lower.write_ir(IR_IDCODE) + idcode = DR_IDCODE.from_bits(await self.lower.read_dr(32)) + self._log("read id mfg-id=%03x part-id=%04x version=%01x", + idcode.mfg_id, idcode.part_id, idcode.version) + return idcode, devices_by_idcode[idcode.to_int()] + + async def read_status(self): + await self.lower.write_ir(IR_LSC_READ_STATUS) + status = LSC_Status.from_bits(await self.lower.read_dr(32)) + self._log("status %s", status.bits_repr()) + return status + + async def programming_enable(self): + self._log("programming enable") + await self.lower.write_ir(IR_ISC_ENABLE) + await self.lower.run_test_idle(10) # XXX verify timing + + async def programming_disable(self): + self._log("programing disable") + await self.lower.write_ir(IR_ISC_DISABLE) + await self.lower.run_test_idle(10) # XXX verify timing + + async def load_bitstream(self, bitstream, + callback=lambda done, total: None): + bitstream = bits(bitstream) + self._log("load bitstream bit-length=%d", len(bitstream) * 8) + + # The FPGA expects bitstream bytes to be shifted in MSB-first. + bitstream = bitstream.byte_reversed() + + # Enter bitstream burst load mode. + await self.lower.write_ir(IR_LSC_BITSTREAM_BURST) + + # Send bitstream in medium sized chunks. This is faster because the JTAG probe currently + # doesn't optimize the case of sending very large `bits` values well. + await self.lower.enter_shift_dr() + chunk_size = 4096 + for chunk_start in range(0, len(bitstream), chunk_size): + callback(chunk_start, len(bitstream)) + await self.lower.shift_tdi(bitstream[chunk_start:chunk_start + chunk_size], last=False) + await self.lower.flush() + callback(len(bitstream), len(bitstream)) + await self.lower.shift_tdi(bits("00000000"), last=True) # dummy write to exit Shift-DR + await self.lower.enter_update_dr() + + async def program_bitstream(self, bitstream, + callback=lambda done, total: None): + await self.programming_enable() + await self.load_bitstream(bitstream, callback=callback) + await self.programming_disable() + + status = await self.read_status() + if status.DONE: + self._logger.info("FPGA successfully configured") + else: + error_code = BSE_Error_Code(status.BSE_Error_Code).explanation + raise GlasgowAppletError(f"FPGA failed to configure: {error_code}") + + +class ProgramECP5SRAMApplet(JTAGProbeApplet): + logger = logging.getLogger(__name__) + help = "Program SRAM of ECP5 FPGAs via JTAG" + description = """ + Program the volatile configuration memory of ECP5 FPGAs. + """ + + @staticmethod + def _show_progress(done, total): + if sys.stdout.isatty(): + sys.stdout.write("\r\033[0K") + if done < total: + sys.stdout.write(f"{done / total * 100:.0f}% complete") + sys.stdout.flush() + + @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 ECP5JTAGInterface(jtag_iface, self.logger) + + async def interact(self, device, args, ecp5_iface): + idcode, ecp5_device = await ecp5_iface.identify() + if ecp5_device is None: + raise ECP5JTAGError("cannot operate on unknown device with IDCODE={:#10x}" + .format(idcode.to_int())) + self.logger.info("found device %s", ecp5_device.name) + + await ecp5_iface.program_bitstream(args.bitstream.read(), callback=self._show_progress) + + @classmethod + def tests(cls): + from . import test + return test.ProgramECP5SRAMAppletTestCase diff --git a/software/glasgow/applet/program/ecp5_sram/test.py b/software/glasgow/applet/program/ecp5_sram/test.py new file mode 100644 index 000000000..e4e18b423 --- /dev/null +++ b/software/glasgow/applet/program/ecp5_sram/test.py @@ -0,0 +1,8 @@ +from ... import * +from . import ProgramECP5SRAMApplet + + +class ProgramECP5SRAMAppletTestCase(GlasgowAppletTestCase, applet=ProgramECP5SRAMApplet): + @synthesis_test + def test_build(self): + self.assertBuilds() 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..bd96cfcc7 --- /dev/null +++ b/software/glasgow/arch/lattice/ecp5.py @@ -0,0 +1,91 @@ +# Ref: FPGA-TN-02039 ECP5 and ECP5-5G sysCONFIG Usage Guide +# Ref: https://www.latticesemi.com/-/media/LatticeSemi/Documents/ApplicationNotes/EH/FPGA-TN-02039-1-7-ECP5-and-ECP5-5G-sysCONFIG.pdf +# Accession: G00087 + +import enum + +from ...support.bits import * +from ...support.bitstruct import * + + +__all__ = [ + # IR values + "IR_IDCODE", "IR_LSC_READ_STATUS", "IR_ISC_ENABLE", "IR_ISC_DISABLE", "IR_ISC_ERASE", + "IR_LSC_BITSTREAM_BURST", + # DR structures + "Config_Target", "BSE_Error_Code", "LSC_Status", +] + + +# IR values (ascending numeric order) +# XXX: where did these values come from? +IR_ISC_ERASE = bits("00001110") +IR_ISC_DISABLE = bits("00100110") +IR_LSC_READ_STATUS = bits("00111100") +IR_LSC_BITSTREAM_BURST = bits("01111010") +IR_ISC_ENABLE = bits("11000110") +IR_IDCODE = bits("11100000") + + +# Lattice status register +class Config_Target(enum.IntEnum): + SRAM = 0b000 + eFuse = 0b001 + + +class BSE_Error_Code(enum.IntEnum): + No_error = 0b000 + ID_error = 0b001 + CMD_error = 0b010 + CRC_error = 0b011 + PRMB_error = 0b100 + ABRT_error = 0b101 + OVFL_error = 0b110 + SDM_error = 0b111 + + @property + def explanation(self): + if self == self.No_error: + return "success" + if self == self.ID_error: + return "IDCODE mismatch" + if self == self.CMD_error: + return "illegal command" + if self == self.CRC_error: + return "checksum error" + if self == self.ABRT_error: + return "configuration aborted" + if self == self.OVFL_error: + return "data overflow error" + if self == self.SDM_error: + return "bitstream past the size of SRAM array" + + +LSC_Status = 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), +]) 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)) diff --git a/software/pyproject.toml b/software/pyproject.toml index d00c68c7f..62fd0df04 100644 --- a/software/pyproject.toml +++ b/software/pyproject.toml @@ -97,6 +97,7 @@ debug-arm = "glasgow.applet.debug.arm.jtag:DebugARMJTAGApplet" debug-mips = "glasgow.applet.debug.mips:DebugMIPSApplet" program-avr-spi = "glasgow.applet.program.avr.spi:ProgramAVRSPIApplet" +program-ecp5-sram = "glasgow.applet.program.ecp5_sram:ProgramECP5SRAMApplet" program-ice40-flash = "glasgow.applet.program.ice40_flash:ProgramICE40FlashApplet" program-ice40-sram = "glasgow.applet.program.ice40_sram:ProgramICE40SRAMApplet" program-m16c = "glasgow.applet.program.m16c:ProgramM16CApplet"