Skip to content

Commit

Permalink
cli: add flash-fast command
Browse files Browse the repository at this point in the history
This command uses a gateware Flash Bridge to access the configuration
SPI memory behind the FPGA.
  • Loading branch information
mndza committed Sep 8, 2023
1 parent 72e239e commit 84bf805
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
52 changes: 50 additions & 2 deletions apollo_fpga/commands/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,21 @@
import errno
import logging
import argparse
import xdg.BaseDirectory
from functools import partial

from apollo_fpga import ApolloDebugger
from apollo_fpga.jtag import JTAGChain, JTAGPatternError
from apollo_fpga.ecp5 import ECP5_JTAGProgrammer
from apollo_fpga.ecp5 import ECP5_JTAGProgrammer, ECP5FlashBridgeProgrammer
from apollo_fpga.onboard_jtag import *

from amaranth.build.run import LocalBuildProducts
try:
from luna.gateware.platform import get_appropriate_platform
from luna.gateware.applets.flash_bridge import FlashBridge, FlashBridgeConnection
except ImportError:
pass


COMMAND_HELP_TEXT = \
"""configure -- Uploads a bitstream to the device's FPGA over JTAG.
Expand Down Expand Up @@ -171,6 +180,38 @@ def program_flash(device, args):

device.soft_reset()


def program_flash_fast(device, args, *, platform):

# Retrieve a FlashBridge cached bitstream or build it
plan = platform.build(FlashBridge(), do_build=False)
cache_dir = os.path.join(
xdg.BaseDirectory.save_cache_path('apollo'), 'build', plan.digest().hex()
)
if os.path.exists(cache_dir):
products = LocalBuildProducts(cache_dir)
else:
products = plan.execute_local(cache_dir)

# Configure flash bridge
with device.jtag as jtag:
programmer = device.create_jtag_programmer(jtag)
programmer.configure(products.get("top.bit"))

# Let the LUNA gateware take over in devices with shared USB port
device.honor_fpga_adv()

# Wait for flash bridge enumeration
time.sleep(2)

# Program SPI flash memory using the configured bridge
bridge = FlashBridgeConnection()
programmer = ECP5FlashBridgeProgrammer(bridge=bridge)
with open(args.argument, "rb") as f:
bitstream = f.read()
programmer.flash(bitstream)


def read_back_flash(device, args):

# XXX abstract this?
Expand Down Expand Up @@ -298,6 +339,7 @@ def main():
'flash-erase': erase_flash,
'flash': program_flash,
'flash-program': program_flash,
'flash-fast': program_flash_fast,
'flash-read': read_back_flash,

# JTAG commands
Expand Down Expand Up @@ -331,13 +373,19 @@ def main():
help='the value to a register write command, or the length for flash read')

args = parser.parse_args()

# Add a special case where the platform information is needed
if args.command == 'flash-fast':
command = partial(program_flash_fast, platform=get_appropriate_platform())
else:
command = commands[args.command]

device = ApolloDebugger()

# Set up python's logging to act as a simple print, for now.
logging.basicConfig(level=logging.INFO, format="%(message)-s")

# Execute the relevant command.
command = commands[args.command]
command(device, args)


Expand Down
38 changes: 38 additions & 0 deletions apollo_fpga/ecp5.py
Original file line number Diff line number Diff line change
Expand Up @@ -1233,3 +1233,41 @@ def reverse_bits(num):
# Bit-reverse the data we capture in response, compensating for MSB-first ordering.
response = [reverse_bits(b) for b in bytes(response)]
return bytes(response)


class ECP5FlashBridgeProgrammer(ECP5CommandBasedProgrammer):
""" Class that enables programming the configuration SPI flash using the FPGA as
a SPI bridge (needs companion gateware).
This programmer is only used for flashing the SPI memory.
"""

# Only useful for flashing operation

def __init__(self, bridge, *args, **kwargs):
""" Creates a new ECP5 Flash Bridge Programmer interface.
Parameters:
bridge -- The connection object to operate with the gateware bridge.
See ECP5Programmer.__init__ for additional accepted arguments.
"""

# Store a reference to our SPI bridge.
self.bridge = bridge

# And run the parent configuration.
super(ECP5FlashBridgeProgrammer, self).__init__(*args, **kwargs)

def trigger_reconfiguration(self):
""" Triggers the target FPGA to reconfigure itself from its flash chip. """
return self.bridge.trigger_reconfiguration()

def _enter_background_spi(self, reset_flash=True):
""" Places the FPGA into background SPI mode; for e.g. programming a connected flash. """
pass

def _background_spi_transfer(self, data, reverse=False, ignore_response=False):
""" Performs a SPI transfer, targeting the configuration flash."""
return self.bridge.transfer(data)

0 comments on commit 84bf805

Please sign in to comment.