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 Oct 11, 2023
1 parent 2a170e6 commit 30e5ce6
Show file tree
Hide file tree
Showing 4 changed files with 439 additions and 2 deletions.
51 changes: 49 additions & 2 deletions apollo_fpga/commands/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,21 @@
import logging
import argparse
from collections import namedtuple
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 apollo_fpga.gateware.flash_bridge import FlashBridge, FlashBridgeConnection
except ImportError:
pass


#
# Common JEDEC manufacturer IDs for SPI flash chips.
Expand Down Expand Up @@ -157,6 +166,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 @@ -293,7 +334,9 @@ def main():
Command("flash-erase", handler=erase_flash,
help="Erases the contents of the FPGA's flash memory."),
Command("flash-program", alias=["flash"], args=["file", "--offset"], handler=program_flash,
help="Programs the target bitstream onto the attached FPGA."),
help="Programs the target bitstream onto the FPGA's configuration flash."),
Command("flash-fast", args=["file", "--offset"], handler=program_flash_fast,
help="Programs a bitstream onto the FPGA's configuration flash using a SPI bridge"),
Command("flash-read", args=["file", "--offset", "--length"], handler=read_back_flash,
help="Reads the contents of the attached FPGA's configuration flash."),

Expand Down Expand Up @@ -341,6 +384,10 @@ def main():
parser.print_help()
return

# Add a special case where the platform information is needed
if args.command == "flash-fast":
args.func = partial(program_flash_fast, platform=get_appropriate_platform())

device = ApolloDebugger()

# Set up python's logging to act as a simple print, for now.
Expand Down
38 changes: 38 additions & 0 deletions apollo_fpga/ecp5.py
Original file line number Diff line number Diff line change
Expand Up @@ -1243,3 +1243,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)

Empty file.
Loading

0 comments on commit 30e5ce6

Please sign in to comment.