[fpga] Carry over ChipWhisperer spiflash tool form ot-sca repo

This commit carries over the spiflash tool for ChipWhisperer FPGA boards
from the ot-sca repository. This tool allows flashing firmware images
into OpenTitan using the SAM3X/U MCUs on the ChipWhisperer FPGA boards.

Signed-off-by: Pirmin Vogel <vogelpi@lowrisc.org>
diff --git a/util/fpga/cw_spiflash.py b/util/fpga/cw_spiflash.py
new file mode 100644
index 0000000..09fbae8
--- /dev/null
+++ b/util/fpga/cw_spiflash.py
@@ -0,0 +1,244 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+"""Utilities used to download firmware images into the OpenTitan system."""
+
+import subprocess
+import time
+import hashlib
+from functools import partial
+
+def _uint_to_le(val, length):
+  """Returns a byte array that represents an unsigned integer in little-endian format.
+
+  Args:
+    val: Unsigned integer to convert.
+    length: Number of bytes.
+
+  Returns:
+    A byte array of ``length`` bytes that represents ``val`` in little-endian format.
+  """
+  return val.to_bytes(length=length, byteorder='little')
+
+def _le_to_uint(val):
+  """Returns the unsigned integer represented by the given byte array in little-endian format.
+
+  Args:
+    val: Byte array in little-endian format that represents an unsigned integer.
+
+  Returns:
+    The unsigned integer represented by the byte array ``val``.
+  """
+  return int.from_bytes(val, byteorder='little')
+
+class _SpiFlashFrame:
+  """A frame used for programming OpenTitan over the SPI interface.
+
+  In order to program OpenTitan with a binary image, the image is broken into ``PAYLOAD_SIZE``
+  byte chunks, each of which is transmitted as a frame over the SPI interface. A frame consists
+  of four parts:
+  * Digest: A ``DIGEST_SIZE`` byte digest of the rest of the frame,
+  * Frame number: A ``FRAME_NUMBER_SIZE`` byte little-endian representation of the frame number.
+    MSB of the last frame's frame number is set to 1 to indicate the end of the image.
+  * Flash offset: A ``FLASH_OFFSET_SIZE`` byte little-endian representation of the flash offset
+    to start writing the payload of the frame.
+  * Payload: ``PAYLOAD_SIZE`` byte actual binary payload in little-endian format.
+
+  OpenTitan signals correct transmission of frame N-1 by transmitting its digest (computed over
+  the entire frame, i.e. including the digest field of the frame) while it receives frame N.
+
+  Attributes:
+    HASH_FUNCTION: Hash function to use for digest computations.
+    DIGEST_SIZE: Size of digests (inferred from ``HASH_FUNCTION``).
+    FRAME_NUMBER_SIZE: Size of the frame number field in bytes.
+    FLASH_OFFSET_SIZE: Size of the flash offset field in bytes.
+    PAYLOAD_SIZE: Size of the frame payload in bytes.
+    is_first_frame: Indicates if this is the first frame.
+    expected_ack: Expected acknowledgement value for a frame.
+  """
+  HASH_FUNCTION = hashlib.sha256
+  DIGEST_SIZE = HASH_FUNCTION().digest_size
+  FRAME_NUMBER_SIZE = 4
+  FLASH_OFFSET_SIZE = 4
+  PAYLOAD_SIZE = 2048 - DIGEST_SIZE - FRAME_NUMBER_SIZE - FLASH_OFFSET_SIZE
+
+  def __init__(self, frame_number, flash_offset, payload):
+    """Inits a _SpiFlashFrame.
+
+    Also pads frame payload to ``PAYLOAD_SIZE`` bytes if needed.
+
+    Args:
+      frame_numer: Frame number.
+      flash_offset: Flash offset.
+      payload: Frame payload.
+    """
+    self._frame_number = _uint_to_le(frame_number, self.FRAME_NUMBER_SIZE)
+    self._flash_offset = _uint_to_le(flash_offset, self.FLASH_OFFSET_SIZE)
+    self._payload = payload
+    padding = self.PAYLOAD_SIZE - len(self._payload)
+    if padding > 0:
+      self._payload = b''.join([self._payload, b'\xFF'*padding])
+    self._digest = self.HASH_FUNCTION(b''.join([
+      self._frame_number,
+      self._flash_offset,
+      self._payload,
+    ])).digest()
+
+  def __bytes__(self):
+    return b''.join([
+        self._digest,
+        self._frame_number,
+        self._flash_offset,
+        self._payload,
+    ])
+
+  def __repr__(self):
+    return f'frame 0x{_le_to_uint(self._frame_number):08X} @ 0x{_le_to_uint(self._flash_offset):08X}'
+
+  @property
+  def is_first_frame(self):
+    """Indicates if this is the first frame."""
+    return _le_to_uint(self._frame_number) == 0
+
+  @property
+  def expected_ack(self):
+    """Expected acknowledgement value for the frame."""
+    return self.HASH_FUNCTION(bytes(self)).digest()
+
+class _Bootstrap:
+  """Handles low-level details during programming OpenTitan using SAM3U on CW305.
+
+  Initializes pins, resets OpenTitan, transmits frames, and receives acknowledgements.
+
+  To use:
+  >>> with _Bootstrap(fpga) as bootstrap:
+  >>>   ...
+  >>>   ack = bootstrap.transfer(frame)
+  >>>   ...
+  """
+  _PIN_SCK='USB_A13'
+  _PIN_SDI='USB_A14'
+  _PIN_SDO='USB_A15'
+  _PIN_CS='USB_A16'
+  _PIN_TRST='USB_A17'
+  _PIN_SRST='USB_A18'
+  _PIN_JTAG_SPI='USB_A19'
+  _PIN_BOOTSTRAP='USB_A20'
+  # Delays below are in seconds.
+  _BOOTSTRAP_DELAY=0.1
+  _FIRST_FRAME_DELAY=0.2
+  _INTER_FRAME_DELAY=0.02
+
+  def __init__(self, fpga):
+    """Inits a _Bootstrap with a CW305.
+
+    Args:
+      fpga: CW305 to be programmed, a ``cw.capture.targets.CW305`` instance.
+    """
+    # Configure JTAG and bootstrap pins.
+    self._fpga_io = fpga.gpio_mode()
+    self._fpga_io.pin_set_output(self._PIN_TRST)
+    self._fpga_io.pin_set_output(self._PIN_SRST)
+    self._fpga_io.pin_set_output(self._PIN_JTAG_SPI)
+    self._fpga_io.pin_set_output(self._PIN_BOOTSTRAP)
+    self._fpga_io.pin_set_state(self._PIN_TRST, 1)
+    self._fpga_io.pin_set_state(self._PIN_SRST, 1)
+    self._fpga_io.pin_set_state(self._PIN_JTAG_SPI, 1)
+    self._fpga_io.pin_set_state(self._PIN_BOOTSTRAP, 0)
+    # Initialize SPI pins.
+    self._fpga_io.spi1_setpins(sck=self._PIN_SCK, sdo=self._PIN_SDI, sdi=self._PIN_SDO, cs=self._PIN_CS)
+    self._fpga_io.spi1_enable(True)
+
+  def _reset_opentitan(self):
+    self._fpga_io.pin_set_state(self._PIN_JTAG_SPI, 1)
+    self._fpga_io.pin_set_state(self._PIN_SRST, 0)
+    time.sleep(self._BOOTSTRAP_DELAY)
+    self._fpga_io.pin_set_state(self._PIN_SRST, 1)
+    time.sleep(self._BOOTSTRAP_DELAY)
+
+  def __enter__(self):
+    """Starts bootstrapping."""
+    self._fpga_io.pin_set_state(self._PIN_BOOTSTRAP, 1)
+    self._reset_opentitan()
+    self._fpga_io.pin_set_state(self._PIN_JTAG_SPI, 0)
+    time.sleep(self._BOOTSTRAP_DELAY)
+    return self
+
+  def __exit__(self, exc_type, exc_value, traceback):
+    """Ends bootstrapping."""
+    self._fpga_io.pin_set_state(self._PIN_BOOTSTRAP, 0)
+    self._fpga_io.pin_set_state(self._PIN_JTAG_SPI, 1)
+    time.sleep(self._BOOTSTRAP_DELAY)
+
+  def transfer(self, frame):
+    """Transmits a frame over SPI and receives the acknowledgement for the previous frame."""
+    # Wait longer after the first frame since it triggers a flash erase operation.
+    if frame.is_first_frame:
+      time.sleep(self._FIRST_FRAME_DELAY)
+    else:
+      time.sleep(self._INTER_FRAME_DELAY)
+    print(f'Transferring {frame}.')
+    # Acknowledgement is the same size as digests.
+    return bytes(self._fpga_io.spi1_transfer(bytes(frame))[:_SpiFlashFrame.DIGEST_SIZE])
+
+
+class SAM3UProgrammer:
+  """Class for programming OpenTitan over the SPI interface of SAM3U on CW305."""
+  def __init__(self, firmware_path):
+    """Inits SAM3UProgrammer with the path of a firmware image."""
+    self._firmware_path = firmware_path
+
+  def run(self, fpga):
+    """Programs OpenTitan over the SPI interface of SAM3U on CW305.
+
+    This implementation has two improvements over the `spiflash` tool in
+    lowRISC/opentitan:
+    * Optimizes the happy path by checking the acknowledgement of frame N-1
+      after transmitting frame N instead of transmitting an extra frame to check
+      each frame's acknowledgement.
+    * Sends an all 0xFF frame as the last frame to avoid cases where the last
+      frame gets corrupted and halts the bootstrapping process.
+
+    Args:
+      fpga: CW305 to be programmed, a ``cw.capture.targets.CW305`` instance.
+    """
+    with _Bootstrap(fpga) as bootstrap:
+      frame_number = 0
+      flash_offset = 0
+      prev_frame = None
+      print(f'Programming OpenTitan with "{self._firmware_path}"...')
+      with open(self._firmware_path, mode='rb') as fw:
+        # Read fixed-size blocks from the firmware image.
+        # Note: The second argument ``b''`` to ``iter`` below is the sentinel value that
+        # ends the loop, i.e. the value returned by ``fw.read`` at EOF.
+        for payload in iter(partial(fw.read, _SpiFlashFrame.PAYLOAD_SIZE), b''):
+          frame = _SpiFlashFrame(frame_number, flash_offset, payload)
+          # We need to make sure that the previous frame is transmitted correctly before
+          # proceeding with the next frame.
+          while True:
+            # Transmit frame N, receive acknowledgement for frame N-1.
+            ack = bootstrap.transfer(frame)
+            if prev_frame and prev_frame.expected_ack != ack:
+              # When OpenTitan encounters a transmission error or an out of
+              # sequence frame, it ignores the current frame and sends the ack of
+              # the last successfully received frame. Since transmission errors
+              # can occur in both directions, i.e. the previous frame sent by
+              # SAM3U or the ack sent by OpenTitan, and we can't tell them
+              # apart, we should re-transmit the previous frame and check the
+              # ack sent by OpenTitan.
+              print(f'Error transferring {prev_frame}.')
+              ack = bootstrap.transfer(prev_frame)
+              if ack == frame.expected_ack:
+                # Ack sent by OpenTitan was corrupted. Proceed with the next frame.
+                break
+              else:
+                # Previous frame sent by SAM3U was corrupted. Send current frame again.
+                pass
+            else:
+              # Correct acknowledgement received or this is the first frame.
+              break
+          prev_frame = frame
+          frame_number += 1
+          flash_offset += _SpiFlashFrame.PAYLOAD_SIZE
+      # Transmit an all 0xFF frame with the expected frame number to finish bootstrapping.
+      bootstrap.transfer(_SpiFlashFrame(frame_number | (1<<31), flash_offset, b''))