From ec226416a65af704663e3c383c5fb55029304985 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Fri, 24 Apr 2020 23:56:40 +0930 Subject: [PATCH 1/9] applet.program.ecp5_sram: initial ecp5 applet --- software/glasgow/applet/all.py | 1 + .../applet/program/ecp5_sram/__init__.py | 167 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 software/glasgow/applet/program/ecp5_sram/__init__.py diff --git a/software/glasgow/applet/all.py b/software/glasgow/applet/all.py index 928f25de4..6f1c84120 100644 --- a/software/glasgow/applet/all.py +++ b/software/glasgow/applet/all.py @@ -24,6 +24,7 @@ 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.m16c import ProgramM16CApplet from .program.mec16xx import ProgramMEC16xxApplet from .program.nrf24lx1 import ProgramNRF24Lx1Applet 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..a97e230ae --- /dev/null +++ b/software/glasgow/applet/program/ecp5_sram/__init__.py @@ -0,0 +1,167 @@ +import argparse +import asyncio +import logging +import struct +from bitarray import bitarray +from nmigen.compat import * + +from ....arch.jtag import * +from ....support.bits import * +from ....support.logging import * +from ....protocol.jtag_svf import * +from ....database.jedec import * +from ... import * +from ...interface.jtag_probe import JTAGProbeApplet, JTAGProbeStateTransitionError + +READ_ID = bits.from_int(0xE0,8) +LSC_READ_STATUS = bits.from_int(0x3C,8) +ISC_ENABLE = bits.from_int(0xC6,8) +ISC_DISABLE = bits.from_int(0x26,8) +ISC_ERASE = bits.from_int(0x0E,8) +LSC_BITSTREAM_BURST = bits.from_int(0x7A,8) + + +ECP5_FAMILY_IDCODES = { + 0x21111043 : "LFE5U-12" , + 0x41111043 : "LFE5U-25" , + 0x41112043 : "LFE5U-45" , + 0x41113043 : "LFE5U-85" , + 0x01111043 : "LFE5UM-25" , + 0x01112043 : "LFE5UM-45" , + 0x01113043 : "LFE5UM-85" , + 0x81111043 : "LFE5UM5G-25", + 0x81112043 : "LFE5UM5G-45", + 0x81113043 : "LFE5UM5G-85" +} + +class ProgramECP5SRAMInterface: + def __init__(self, interface, logger, frequency): + 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(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 + try: + device = ECP5_FAMILY_IDCODES[idcode_value] + self.logger.info("Found Device: %s", device) + except: + self.logger.error("IDCODE 0x%08X does not mtach ECP5 device", idcode_value) + + + + async def read_STATUS(self): + await self.lower.write_ir(LSC_READ_STATUS) + raw_bits = await self.lower.read_dr(32) + status = struct.unpack(" 0]) + self.logger.debug(" Read Enable: %s", {True : "Readable", False : "Not Readable"}[status & (1 << 11) != 0] ) + self.logger.debug(" Write Enable: %s", {True : "Writable", False : "Not Writable"}[status & (1 << 10) != 0] ) + self.logger.debug(" JTAG Active: %s", {True: "Yes", False: "No"}[status & (1 << 4) != 0] ) + self.logger.debug(" PWD Protection: %s", {True: "Yes", False: "No"}[status & (1 << 5) != 0] ) + self.logger.debug(" Decrypt Enable: %s", {True: "Yes", False: "No"}[status & (1 << 7) != 0] ) + self.logger.debug(" DONE: %s", {True: "Yes", False: "No"}[status & (1 << 8) != 0] ) + self.logger.debug(" ISC Enable: %s", {True: "Yes", False: "No"}[status & (1 << 9) != 0] ) + self.logger.debug(" Busy Flag: %s", {True: "Yes", False: "No"}[status & (1 << 12) != 0] ) + self.logger.debug(" Fail Flag: %s", {True: "Yes", False: "No"}[status & (1 << 13) != 0] ) + self.logger.debug(" Feature OTP: %s", {True: "Yes", False: "No"}[status & (1 << 14) != 0] ) + self.logger.debug(" Decrypt Only: %s", {True: "Yes", False: "No"}[status & (1 << 15) != 0] ) + self.logger.debug(" PWD Enable: %s", {True: "Yes", False: "No"}[status & (1 << 16) != 0] ) + self.logger.debug(" Encrypt Preamble: %s", {True: "Yes", False: "No"}[status & (1 << 20) != 0] ) + self.logger.debug(" Std Preamble: %s", {True: "Yes", False: "No"}[status & (1 << 21) != 0] ) + self.logger.debug(" SPIm Fail 1: %s", {True: "Yes", False: "No"}[status & (1 << 22) != 0] ) + self.logger.debug(" Execution Error: %s", {True: "Yes", False: "No"}[status & (1 << 26) != 0] ) + self.logger.debug(" ID Error: %s", {True: "Yes", False: "No"}[status & (1 << 27) != 0] ) + self.logger.debug(" Invalid Command: %s", {True: "Yes", False: "No"}[status & (1 << 28) != 0] ) + self.logger.debug(" SED Error: %s", {True: "Yes", False: "No"}[status & (1 << 29) != 0] ) + self.logger.debug(" Bypass Mode: %s", {True: "Yes", False: "No"}[status & (1 << 30) != 0] ) + self.logger.debug(" Flow Through Mode: %s", {True: "Yes", False: "No"}[status & (1 << 31) != 0] ) + + bse_error = (status & (7 << 23)) >> 23 + self.logger.debug(" Flow Through Mode: %s",{ + 0b000: "No Error (0b000)", + 0b001: "ID Error (0b001)", + 0b010: "CMD Error - illegal command (0b010)", + 0b011: "CRC Error (0b011)", + 0b100: "PRMB Error - preamble error (0b100)", + 0b101: "ABRT Error - configuration aborted by the user (0b101)", + 0b110: "OVFL Error - data overflow error (0b110)", + 0b111: "SDM Error - bitstream pass the size of SRAM array (0b111)", + }[bse_error]) + + def reverse(self, a,size): + b = 0 + for i in range(size): + b <<= 1 + b |= a >> i & 1 + return b + + async def program(self, bitstream): + await self.check_IDCODE() + await self.check_STATUS() + + # perform programming + await self.lower.write_ir(ISC_ENABLE) + await self.lower.run_test_idle(10) + await self.lower.write_ir(LSC_BITSTREAM_BURST) + + # Send entire bitstream data into DR, + # Bytes are expected MSB by the ECP5, so need to be reversed + await self.lower.enter_shift_dr() + for b in bitstream: + b = self.reverse(b, 8) + await self.lower.shift_tdi(bits.from_int(b, 8), last=False) + await self.lower.enter_update_dr() + + + await self.lower.write_ir(ISC_DISABLE) + await self.lower.run_test_idle(10) + + # Check status + # Note ECP5 will not release until STATUS is read + await self.check_STATUS() + + + + + +class ProgramECP5SRAMApplet(JTAGProbeApplet, name="program-ecp5-sram"): + logger = logging.getLogger(__name__) + help = "Program ECP5 configuration sram via JTAG" + description = """ + TODO + """ + + @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, args.frequency * 1000) + + async def interact(self, device, args, ecp5_iface): + bitstream = args.bitstream.read() + await ecp5_iface.program(bitstream) \ No newline at end of file From 14f60b4012b54feb7c96bb99bcc8c82ba6eadcd8 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 25 Apr 2020 09:33:43 +0930 Subject: [PATCH 2/9] applet.program.ecp5_sram: rework applet Moved jtag constants into arch.lattice.ecp5 Moved device idodes into database.lattice.ecp5 --- .../applet/program/ecp5_sram/__init__.py | 108 +++++------------- software/glasgow/arch/lattice/__init__.py | 0 software/glasgow/arch/lattice/ecp5.py | 54 +++++++++ software/glasgow/database/lattice/__init__.py | 0 software/glasgow/database/lattice/ecp5.py | 27 +++++ 5 files changed, 109 insertions(+), 80 deletions(-) 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/software/glasgow/applet/program/ecp5_sram/__init__.py b/software/glasgow/applet/program/ecp5_sram/__init__.py index a97e230ae..f8d02afe0 100644 --- a/software/glasgow/applet/program/ecp5_sram/__init__.py +++ b/software/glasgow/applet/program/ecp5_sram/__init__.py @@ -2,37 +2,16 @@ import asyncio import logging import struct -from bitarray import bitarray -from nmigen.compat import * from ....arch.jtag import * +from ....arch.lattice.ecp5 import * from ....support.bits import * from ....support.logging import * -from ....protocol.jtag_svf import * from ....database.jedec import * +from ....database.lattice.ecp5 import * from ... import * from ...interface.jtag_probe import JTAGProbeApplet, JTAGProbeStateTransitionError -READ_ID = bits.from_int(0xE0,8) -LSC_READ_STATUS = bits.from_int(0x3C,8) -ISC_ENABLE = bits.from_int(0xC6,8) -ISC_DISABLE = bits.from_int(0x26,8) -ISC_ERASE = bits.from_int(0x0E,8) -LSC_BITSTREAM_BURST = bits.from_int(0x7A,8) - - -ECP5_FAMILY_IDCODES = { - 0x21111043 : "LFE5U-12" , - 0x41111043 : "LFE5U-25" , - 0x41112043 : "LFE5U-45" , - 0x41113043 : "LFE5U-85" , - 0x01111043 : "LFE5UM-25" , - 0x01112043 : "LFE5UM-45" , - 0x01113043 : "LFE5UM-85" , - 0x81111043 : "LFE5UM5G-25", - 0x81112043 : "LFE5UM5G-45", - 0x81113043 : "LFE5UM5G-85" -} class ProgramECP5SRAMInterface: def __init__(self, interface, logger, frequency): @@ -40,15 +19,22 @@ def __init__(self, interface, logger, frequency): self.logger = logger self._level = logging.DEBUG if self.logger.name == __name__ else logging.TRACE - async def read_IDCODE(self): + async def _read_IDCODE(self): await self.lower.test_reset() - await self.lower.write_ir(READ_ID) + 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 \ @@ -57,57 +43,16 @@ async def check_IDCODE(self): idcode.mfg_id, mfg_name, idcode.part_id, idcode.version) # Decode to actual ECP5 devices try: - device = ECP5_FAMILY_IDCODES[idcode_value] + device = devices_by_idcode[idcode_value] self.logger.info("Found Device: %s", device) except: self.logger.error("IDCODE 0x%08X does not mtach ECP5 device", idcode_value) - - async def read_STATUS(self): - await self.lower.write_ir(LSC_READ_STATUS) - raw_bits = await self.lower.read_dr(32) - status = struct.unpack(" 0]) - self.logger.debug(" Read Enable: %s", {True : "Readable", False : "Not Readable"}[status & (1 << 11) != 0] ) - self.logger.debug(" Write Enable: %s", {True : "Writable", False : "Not Writable"}[status & (1 << 10) != 0] ) - self.logger.debug(" JTAG Active: %s", {True: "Yes", False: "No"}[status & (1 << 4) != 0] ) - self.logger.debug(" PWD Protection: %s", {True: "Yes", False: "No"}[status & (1 << 5) != 0] ) - self.logger.debug(" Decrypt Enable: %s", {True: "Yes", False: "No"}[status & (1 << 7) != 0] ) - self.logger.debug(" DONE: %s", {True: "Yes", False: "No"}[status & (1 << 8) != 0] ) - self.logger.debug(" ISC Enable: %s", {True: "Yes", False: "No"}[status & (1 << 9) != 0] ) - self.logger.debug(" Busy Flag: %s", {True: "Yes", False: "No"}[status & (1 << 12) != 0] ) - self.logger.debug(" Fail Flag: %s", {True: "Yes", False: "No"}[status & (1 << 13) != 0] ) - self.logger.debug(" Feature OTP: %s", {True: "Yes", False: "No"}[status & (1 << 14) != 0] ) - self.logger.debug(" Decrypt Only: %s", {True: "Yes", False: "No"}[status & (1 << 15) != 0] ) - self.logger.debug(" PWD Enable: %s", {True: "Yes", False: "No"}[status & (1 << 16) != 0] ) - self.logger.debug(" Encrypt Preamble: %s", {True: "Yes", False: "No"}[status & (1 << 20) != 0] ) - self.logger.debug(" Std Preamble: %s", {True: "Yes", False: "No"}[status & (1 << 21) != 0] ) - self.logger.debug(" SPIm Fail 1: %s", {True: "Yes", False: "No"}[status & (1 << 22) != 0] ) - self.logger.debug(" Execution Error: %s", {True: "Yes", False: "No"}[status & (1 << 26) != 0] ) - self.logger.debug(" ID Error: %s", {True: "Yes", False: "No"}[status & (1 << 27) != 0] ) - self.logger.debug(" Invalid Command: %s", {True: "Yes", False: "No"}[status & (1 << 28) != 0] ) - self.logger.debug(" SED Error: %s", {True: "Yes", False: "No"}[status & (1 << 29) != 0] ) - self.logger.debug(" Bypass Mode: %s", {True: "Yes", False: "No"}[status & (1 << 30) != 0] ) - self.logger.debug(" Flow Through Mode: %s", {True: "Yes", False: "No"}[status & (1 << 31) != 0] ) - - bse_error = (status & (7 << 23)) >> 23 - self.logger.debug(" Flow Through Mode: %s",{ - 0b000: "No Error (0b000)", - 0b001: "ID Error (0b001)", - 0b010: "CMD Error - illegal command (0b010)", - 0b011: "CRC Error (0b011)", - 0b100: "PRMB Error - preamble error (0b100)", - 0b101: "ABRT Error - configuration aborted by the user (0b101)", - 0b110: "OVFL Error - data overflow error (0b110)", - 0b111: "SDM Error - bitstream pass the size of SRAM array (0b111)", - }[bse_error]) + async def check_STATUS(self, status): + self.logger.info("Status Register: 0x%08X", status.to_int()) + self.logger.info(" %s", status) + def reverse(self, a,size): b = 0 @@ -117,13 +62,12 @@ def reverse(self, a,size): return b async def program(self, bitstream): - await self.check_IDCODE() - await self.check_STATUS() + await self.identify() # perform programming - await self.lower.write_ir(ISC_ENABLE) + await self.lower.write_ir(IR_ISC_ENABLE) await self.lower.run_test_idle(10) - await self.lower.write_ir(LSC_BITSTREAM_BURST) + 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 @@ -134,15 +78,19 @@ async def program(self, bitstream): await self.lower.enter_update_dr() - await self.lower.write_ir(ISC_DISABLE) + 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 - await self.check_STATUS() - + status = await self._read_STATUS() + if status.DONE: + self.logger.info("Configuration Done") + else: + self.check_STATUS(status) + class ProgramECP5SRAMApplet(JTAGProbeApplet, name="program-ecp5-sram"): 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..0bdde6930 --- /dev/null +++ b/software/glasgow/arch/lattice/ecp5.py @@ -0,0 +1,54 @@ +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") + +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), +]) + +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"), +] \ 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)) From 0d1a164a7cf3a1f2bdbc3fb6d60a5db827846100 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 25 Apr 2020 10:43:46 +0930 Subject: [PATCH 3/9] applet.program.ecp5_sram: speed improvements Split bitstream into chunks to reduce overhead and improve speed Removed local reversal funcitons, switched to bits.reversed() --- .../applet/program/ecp5_sram/__init__.py | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/software/glasgow/applet/program/ecp5_sram/__init__.py b/software/glasgow/applet/program/ecp5_sram/__init__.py index f8d02afe0..2e42d112e 100644 --- a/software/glasgow/applet/program/ecp5_sram/__init__.py +++ b/software/glasgow/applet/program/ecp5_sram/__init__.py @@ -14,11 +14,11 @@ class ProgramECP5SRAMInterface: - def __init__(self, interface, logger, frequency): + 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) @@ -49,17 +49,9 @@ async def identify(self): self.logger.error("IDCODE 0x%08X does not mtach ECP5 device", idcode_value) - async def check_STATUS(self, status): + async def _check_STATUS(self, status): self.logger.info("Status Register: 0x%08X", status.to_int()) self.logger.info(" %s", status) - - - def reverse(self, a,size): - b = 0 - for i in range(size): - b <<= 1 - b |= a >> i & 1 - return b async def program(self, bitstream): await self.identify() @@ -67,16 +59,24 @@ async def program(self, bitstream): # 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 b in bitstream: - b = self.reverse(b, 8) - await self.lower.shift_tdi(bits.from_int(b, 8), last=False) + 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) @@ -88,16 +88,15 @@ async def program(self, bitstream): if status.DONE: self.logger.info("Configuration Done") else: - self.check_STATUS(status) + await self._check_STATUS(status) - class ProgramECP5SRAMApplet(JTAGProbeApplet, name="program-ecp5-sram"): logger = logging.getLogger(__name__) help = "Program ECP5 configuration sram via JTAG" description = """ - TODO + Program the volatile configuration memory of ECP5 FPGAs """ @classmethod @@ -108,7 +107,7 @@ def add_interact_arguments(cls, parser): async def run(self, device, args): jtag_iface = await self.run_lower(ProgramECP5SRAMApplet, device, args) - return ProgramECP5SRAMInterface(jtag_iface, self.logger, args.frequency * 1000) + return ProgramECP5SRAMInterface(jtag_iface, self.logger) async def interact(self, device, args, ecp5_iface): bitstream = args.bitstream.read() From c505abea8ce23437f8582bdd5fb6b7ab887d2608 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 25 Apr 2020 11:03:03 +0930 Subject: [PATCH 4/9] applet.program.ecp5_sram: style changes --- .../glasgow/applet/program/ecp5_sram/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/software/glasgow/applet/program/ecp5_sram/__init__.py b/software/glasgow/applet/program/ecp5_sram/__init__.py index 2e42d112e..307375122 100644 --- a/software/glasgow/applet/program/ecp5_sram/__init__.py +++ b/software/glasgow/applet/program/ecp5_sram/__init__.py @@ -19,14 +19,14 @@ def __init__(self, interface, logger): self.logger = logger self._level = logging.DEBUG if self.logger.name == __name__ else logging.TRACE - async def _read_IDCODE(self): + 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 \ @@ -49,7 +49,7 @@ async def identify(self): self.logger.error("IDCODE 0x%08X does not mtach ECP5 device", idcode_value) - async def _check_STATUS(self, status): + async def _check_status(self, status): self.logger.info("Status Register: 0x%08X", status.to_int()) self.logger.info(" %s", status) @@ -83,12 +83,12 @@ async def program(self, bitstream): # Check status # Note ECP5 will not release until STATUS is read - status = await self._read_STATUS() + status = await self._read_status() if status.DONE: self.logger.info("Configuration Done") else: - await self._check_STATUS(status) + await self._check_status(status) From 58b7459a62fd3ff05b386cd0ccd4517a59650679 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 25 Apr 2020 11:25:08 +0930 Subject: [PATCH 5/9] applet.program.ecp5_sram: use GlasgowAppletError --- .../applet/program/ecp5_sram/__init__.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/software/glasgow/applet/program/ecp5_sram/__init__.py b/software/glasgow/applet/program/ecp5_sram/__init__.py index 307375122..0620ae9b5 100644 --- a/software/glasgow/applet/program/ecp5_sram/__init__.py +++ b/software/glasgow/applet/program/ecp5_sram/__init__.py @@ -41,16 +41,15 @@ async def identify(self): "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 - try: - device = devices_by_idcode[idcode_value] - self.logger.info("Found Device: %s", device) - except: - self.logger.error("IDCODE 0x%08X does not mtach ECP5 device", idcode_value) - + 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("Status Register: 0x%#08x", status.to_int()) self.logger.info(" %s", status) async def program(self, bitstream): @@ -67,7 +66,7 @@ async def program(self, bitstream): # 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() @@ -89,6 +88,8 @@ async def program(self, bitstream): self.logger.info("Configuration Done") else: await self._check_status(status) + raise GlasgowAppletError("Configuration error. DONE not set", + bse_error_code[status.BSE_Error_Code]) From 7184483e9e9b7a5005a5994889c3099f440a9896 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 25 Apr 2020 12:34:11 +0930 Subject: [PATCH 6/9] applet.program.ecp5_sram: add method to decode errors --- .../applet/program/ecp5_sram/__init__.py | 3 +- software/glasgow/arch/lattice/ecp5.py | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/software/glasgow/applet/program/ecp5_sram/__init__.py b/software/glasgow/applet/program/ecp5_sram/__init__.py index 0620ae9b5..806925446 100644 --- a/software/glasgow/applet/program/ecp5_sram/__init__.py +++ b/software/glasgow/applet/program/ecp5_sram/__init__.py @@ -88,8 +88,7 @@ async def program(self, bitstream): self.logger.info("Configuration Done") else: await self._check_status(status) - raise GlasgowAppletError("Configuration error. DONE not set", - bse_error_code[status.BSE_Error_Code]) + raise GlasgowAppletError("Configuration error. DONE not set", status.BSEErrorCode()) diff --git a/software/glasgow/arch/lattice/ecp5.py b/software/glasgow/arch/lattice/ecp5.py index 0bdde6930..a082f7ad6 100644 --- a/software/glasgow/arch/lattice/ecp5.py +++ b/software/glasgow/arch/lattice/ecp5.py @@ -11,7 +11,7 @@ IR_ISC_ERASE = bits("00001110") IR_LSC_BITSTREAM_BURST = bits("01111010") -LSC_Status = bitstruct("LSC_STATUS", 32, [ +LSC_Status_bits = bitstruct("LSC_STATUS", 32, [ ("Transparent_Mode", 1), ("Config_Target", 3), ("JTAG_Active", 1), @@ -51,4 +51,34 @@ 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"), -] \ No newline at end of file +] + +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 From f4f12616b84436a7321a23168a39970779df4fd4 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 25 Apr 2020 16:09:32 +0930 Subject: [PATCH 7/9] applet.program.ecp5_sram: fix private _logger --- .../glasgow/applet/program/ecp5_sram/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/software/glasgow/applet/program/ecp5_sram/__init__.py b/software/glasgow/applet/program/ecp5_sram/__init__.py index 806925446..3a534abe6 100644 --- a/software/glasgow/applet/program/ecp5_sram/__init__.py +++ b/software/glasgow/applet/program/ecp5_sram/__init__.py @@ -16,8 +16,8 @@ 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 + 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() @@ -39,18 +39,18 @@ async def identify(self): mfg_name = jedec_mfg_name_from_bank_num(idcode.mfg_id >> 7, idcode.mfg_id & 0x7f) or \ "unknown" - self.logger.info("manufacturer=%#05x (%s) part=%#06x version=%#03x", + 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) + 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) + self._logger.info("Status Register: 0x%#08x", status.to_int()) + self._logger.info(" %s", status) async def program(self, bitstream): await self.identify() @@ -85,7 +85,7 @@ async def program(self, bitstream): status = await self._read_status() if status.DONE: - self.logger.info("Configuration Done") + self._logger.info("Configuration Done") else: await self._check_status(status) raise GlasgowAppletError("Configuration error. DONE not set", status.BSEErrorCode()) @@ -107,7 +107,7 @@ def add_interact_arguments(cls, parser): async def run(self, device, args): jtag_iface = await self.run_lower(ProgramECP5SRAMApplet, device, args) - return ProgramECP5SRAMInterface(jtag_iface, self.logger) + return ProgramECP5SRAMInterface(jtag_iface, self._logger) async def interact(self, device, args, ecp5_iface): bitstream = args.bitstream.read() From 952f599ea943d5d5a69f5644d6037a4a8b5bddf2 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 25 Apr 2020 16:11:20 +0930 Subject: [PATCH 8/9] applet.program.ecp5_flash: new applet --- software/glasgow/applet/all.py | 1 + .../applet/program/ecp5_flash/__init__.py | 87 +++++++++++++++++++ software/glasgow/arch/lattice/ecp5.py | 1 + 3 files changed, 89 insertions(+) create mode 100644 software/glasgow/applet/program/ecp5_flash/__init__.py diff --git a/software/glasgow/applet/all.py b/software/glasgow/applet/all.py index 6f1c84120..d8007b217 100644 --- a/software/glasgow/applet/all.py +++ b/software/glasgow/applet/all.py @@ -25,6 +25,7 @@ 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..813450f99 --- /dev/null +++ b/software/glasgow/applet/program/ecp5_flash/__init__.py @@ -0,0 +1,87 @@ +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): + 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/arch/lattice/ecp5.py b/software/glasgow/arch/lattice/ecp5.py index a082f7ad6..97094922c 100644 --- a/software/glasgow/arch/lattice/ecp5.py +++ b/software/glasgow/arch/lattice/ecp5.py @@ -10,6 +10,7 @@ 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), From ca10bd30d67e729a1a92247e0bdf247fa2a2ccc8 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 25 Apr 2020 16:30:33 +0930 Subject: [PATCH 9/9] applet.program.ecp5_flash: erase old SRAM data --- .../glasgow/applet/program/ecp5_flash/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/software/glasgow/applet/program/ecp5_flash/__init__.py b/software/glasgow/applet/program/ecp5_flash/__init__.py index 813450f99..2d23b42bc 100644 --- a/software/glasgow/applet/program/ecp5_flash/__init__.py +++ b/software/glasgow/applet/program/ecp5_flash/__init__.py @@ -58,6 +58,18 @@ async def _command(self, cmd, arg=[], dummy=0, ret=0): 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)