Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: add flash-fast command #25

Merged
merged 3 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading