-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
applet.program.ecp5_sram: initial ecp5 applet
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
1 parent
5307005
commit 96edd46
Showing
6 changed files
with
254 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |