Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: applet.program.ecp5_flash #192

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions software/glasgow/applet/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
99 changes: 99 additions & 0 deletions software/glasgow/applet/program/ecp5_flash/__init__.py
Original file line number Diff line number Diff line change
@@ -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)
114 changes: 114 additions & 0 deletions software/glasgow/applet/program/ecp5_sram/__init__.py
Original file line number Diff line number Diff line change
@@ -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("<I", raw_bits.to_bytes())[0]
return idcode

async def _read_status(self):
await self.lower.write_ir(IR_LSC_READ_STATUS)
raw_bits = await self.lower.read_dr(32)
status_value = struct.unpack("<I", raw_bits.to_bytes())[0]
status = LSC_Status.from_int(status_value)
return status

async def identify(self):
idcode_value = await self._read_idcode()
idcode = DR_IDCODE.from_int(idcode_value)
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",
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)
Empty file.
85 changes: 85 additions & 0 deletions software/glasgow/arch/lattice/ecp5.py
Original file line number Diff line number Diff line change
@@ -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())
Empty file.
27 changes: 27 additions & 0 deletions software/glasgow/database/lattice/ecp5.py
Original file line number Diff line number Diff line change
@@ -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))