Skip to content

Commit

Permalink
Merge pull request #25 from mndza/flash-fast
Browse files Browse the repository at this point in the history
cli: add flash-fast command
  • Loading branch information
martinling authored Mar 27, 2024
2 parents d497b06 + bde009c commit d00d0d5
Show file tree
Hide file tree
Showing 5 changed files with 451 additions and 2 deletions.
4 changes: 4 additions & 0 deletions apollo_fpga/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class ApolloDebugger:
APOLLO_USB_IDS = [(0x1d50, 0x615c)]
LUNA_USB_IDS = [(0x1d50, 0x615b)]

# Add pid.codes VID/PID pairs with PID from 0x0001 to 0x0010
for i in range(16):
LUNA_USB_IDS += [(0x1209, i+1)]

# If we have a LUNA_USB_IDS variable, we can use it to find the LUNA device.
if os.getenv("LUNA_USB_IDS"):
LUNA_USB_IDS += [tuple([int(x, 16) for x in os.getenv("LUNA_USB_IDS").split(":")])]
Expand Down
62 changes: 60 additions & 2 deletions apollo_fpga/commands/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,23 @@
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 *

try:
from amaranth.build.run import LocalBuildProducts
from luna.gateware.platform import get_appropriate_platform
from apollo_fpga.gateware.flash_bridge import FlashBridge, FlashBridgeConnection
except ImportError:
flash_fast_enable = False
else:
flash_fast_enable = True


#
# Common JEDEC manufacturer IDs for SPI flash chips.
Expand Down Expand Up @@ -161,6 +172,44 @@ def program_flash(device, args):
programmer.flash(bitstream, offset=offset)



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.file, "rb") as f:
bitstream = f.read()
programmer.flash(bitstream)


def program_flash_fast_unavailable(device, args):
logging.error("`flash-fast` requires the `luna` package in the Python environment.\n"
"Install `luna` or use `flash` instead.")
sys.exit(-1)


def read_back_flash(device, args):
ensure_unconfigured(device)

Expand Down Expand Up @@ -298,7 +347,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 @@ -346,6 +397,13 @@ def main():
parser.print_help()
return

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

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 @@ -1260,3 +1260,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)

Loading

0 comments on commit d00d0d5

Please sign in to comment.