Skip to content

Commit

Permalink
applet.program.ecp5_sram: initial ecp5 applet
Browse files Browse the repository at this point in the history
Tested on Butterstick, OrangeCrab.
This applet adds support for loading a bitstream file directly
to an ECP5, rather than create an SVF file.
  • Loading branch information
gregdavill committed Oct 11, 2020
1 parent 5307005 commit 96edd46
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 0 deletions.
1 change: 1 addition & 0 deletions software/glasgow/applet/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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
Expand Down
140 changes: 140 additions & 0 deletions software/glasgow/applet/program/ecp5_sram/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import argparse
import asyncio
import logging
import struct
import sys

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,
callback=lambda done, total, status: None):
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
await self.lower.enter_shift_dr()
chunk_size = 2048
address = 0
data = bytes(bitstream)
done, total = 0, len(data)
while len(data) > 0:
chunk = data[:chunk_size - address % chunk_size]
data = data[len(chunk):]

chunk_bits = bits()
for b in chunk:
chunk_bits += bits.from_int(b, 8).reversed()

callback(done, total, "bitstream loading {:#08x}".format(address))

await self.lower.shift_tdi(chunk_bits, last=False)
await self.lower.flush()

address += len(chunk)
done += len(chunk)

await self.lower.enter_update_dr()
callback(done, total, None)



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
"""

@staticmethod
def _show_progress(done, total, status):
if sys.stdout.isatty():
sys.stdout.write("\r\033[0K")
if done < total:
sys.stdout.write("{}/{} bytes done".format(done, total))
if status:
sys.stdout.write("; {}".format(status))
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 ProgramECP5SRAMInterface(jtag_iface, self.logger)

async def interact(self, device, args, ecp5_iface):
bitstream = args.bitstream.read()
await ecp5_iface.program(bitstream, callback=self._show_progress)
Empty file.
84 changes: 84 additions & 0 deletions software/glasgow/arch/lattice/ecp5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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_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.
29 changes: 29 additions & 0 deletions software/glasgow/database/lattice/ecp5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from collections import defaultdict, namedtuple


__all__ = ["devices", "devices_by_idcode", "devices_by_name"]


class ECP5Device(namedtuple("ECP5Device", ("name", "idcode"))):
def __repr__(self):
return f"ECP5Device(name='{self.name}' idcode={hex(self.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))

0 comments on commit 96edd46

Please sign in to comment.