[fpga] Add loader tool for ChipWhipserer CW310 FPGA board

This tool enables the loading of OpenTitan bistreams and application
binaries to the CW310 FPGA board.

Signed-off-by: Pirmin Vogel <vogelpi@lowrisc.org>
diff --git a/util/fpga/cw310_loader.py b/util/fpga/cw310_loader.py
new file mode 100755
index 0000000..3d16747
--- /dev/null
+++ b/util/fpga/cw310_loader.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+
+import cw_spiflash
+
+import chipwhisperer as cw
+from chipwhisperer.capture.targets.CW310 import CW310
+
+
+def main():
+
+    parser = argparse.ArgumentParser(
+        description=
+        'Load OpenTitan Bitstream & Software to ChipWhisperer CW310 FPGA Board'
+    )
+
+    parser.add_argument('--bitstream',
+                        '-b',
+                        metavar='bitstream',
+                        type=str,
+                        help='Path to the FPGA .bit file to load',
+                        default=None)
+
+    parser.add_argument('--firmware',
+                        '-f',
+                        metavar='firmware',
+                        type=str,
+                        help='Path to the software .bin file to load',
+                        default=None)
+
+    args = parser.parse_args()
+
+    print("CW310 Loader: Attemping to find CW310 FPGA Board:")
+    if args.bitstream:
+        print("    Using bitstream :{}".format(args.bitstream))
+    else:
+        print("    No bitstream specified")
+    target = cw.target(None, CW310, bsfile=args.bitstream, slurp=False)
+
+    print("Board found, setting PLL2 to 100 MHz")
+
+    target.pll.pll_enable_set(True)
+    target.pll.pll_outenable_set(False, 0)
+    # Note: 1 and 2 seem to be reversed.
+    target.pll.pll_outenable_set(True, 1)
+    target.pll.pll_outenable_set(False, 2)
+
+    # Note: both 1 and 2 need to be set, even if only 1 is enabled.
+    target.pll.pll_outfreq_set(100E6, 1)
+    target.pll.pll_outfreq_set(100E6, 2)
+
+    if args.firmware:
+        print("INFO: Programming firmware file: {}".format(args.firmware))
+        prog = cw_spiflash.SPIProgrammer(args.firmware, "CW310")
+        prog.run(target)
+
+    print("Loading done.")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/util/fpga/cw_spiflash.py b/util/fpga/cw_spiflash.py
index c710719..032169d 100644
--- a/util/fpga/cw_spiflash.py
+++ b/util/fpga/cw_spiflash.py
@@ -6,6 +6,7 @@
 import time
 import hashlib
 from functools import partial
+from collections import namedtuple
 
 
 def _uint_to_le(val, length):
@@ -122,21 +123,40 @@
     >>>   ack = bootstrap.transfer(frame)
     >>>   ...
     """
-    # Default pin mapping for CW305 board.
-    _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'
+    # Pin mappings for CW305/CW310 boards.
+    PinMapping = namedtuple('PinMapping', ['PIN_SCK',
+                                           'PIN_SDI',
+                                           'PIN_SDO',
+                                           'PIN_CS',
+                                           'PIN_TRST',
+                                           'PIN_SRST',
+                                           'PIN_JTAG_SPI',
+                                           'PIN_BOOTSTRAP'])
+    _PIN_MAPPINGS = {}
+    _PIN_MAPPINGS['CW305'] = PinMapping('USB_A13',
+                                        'USB_A14',
+                                        'USB_A15',
+                                        'USB_A16',
+                                        'USB_A17',
+                                        'USB_A18',
+                                        'USB_A19',
+                                        'USB_A20')
+    _PIN_MAPPINGS['CW310'] = PinMapping('USB_SPI_SCK',
+                                        'USB_SPI_COPI',
+                                        'USB_SPI_CIPO',
+                                        'USB_SPI_CS',
+                                        'USB_A17',
+                                        'USB_A18',
+                                        'USB_A19',
+                                        'USB_A16')
+    _board = 'CW305'
+
     # Delays below are in seconds.
     _BOOTSTRAP_DELAY = 0.1
     _SECOND_FRAME_DELAY = 0.2
     _INTER_FRAME_DELAY = 0.02
 
-    def __init__(self, fpga, board="CW305"):
+    def __init__(self, fpga, board='CW305'):
         """Inits a _Bootstrap with a CW310/305.
 
         Args:
@@ -144,53 +164,48 @@
           board: can be ``CW310`` or ``CW305`` to distinguish the different board types.
         """
 
-        # Change pin mapping to CW310 board.
-        if board == "CW310":
-            self._PIN_SCK = 'USB_SPI_SCK'
-            self._PIN_SDI = 'USB_SPI_COPI'
-            self._PIN_SDO = 'USB_SPI_CIPO'
-            self._PIN_CS = 'USB_SPI_CS'
-            self._PIN_TRST = 'USB_A17'
-            self._PIN_SRST = 'USB_A18'
-            self._PIN_JTAG_SPI = 'USB_A19'
-            self._PIN_BOOTSTRAP = 'USB_A16'
+        # Check board type.
+        if board in ['CW305', 'CW310']:
+            self._board = board
+        else:
+            raise ValueError('board must be either CW305 or CW310, got ' + board)
 
         # 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)
+        self._fpga_io.pin_set_output(self._PIN_MAPPINGS[self._board].PIN_TRST)
+        self._fpga_io.pin_set_output(self._PIN_MAPPINGS[self._board].PIN_SRST)
+        self._fpga_io.pin_set_output(self._PIN_MAPPINGS[self._board].PIN_JTAG_SPI)
+        self._fpga_io.pin_set_output(self._PIN_MAPPINGS[self._board].PIN_BOOTSTRAP)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_TRST, 1)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_SRST, 1)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_JTAG_SPI, 1)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].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_setpins(sck=self._PIN_MAPPINGS[self._board].PIN_SCK,
+                                   sdo=self._PIN_MAPPINGS[self._board].PIN_SDI,
+                                   sdi=self._PIN_MAPPINGS[self._board].PIN_SDO,
+                                   cs=self._PIN_MAPPINGS[self._board].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)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_JTAG_SPI, 1)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_SRST, 0)
         time.sleep(self._BOOTSTRAP_DELAY)
-        self._fpga_io.pin_set_state(self._PIN_SRST, 1)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_SRST, 1)
         time.sleep(self._BOOTSTRAP_DELAY)
 
     def __enter__(self):
         """Starts bootstrapping."""
-        self._fpga_io.pin_set_state(self._PIN_BOOTSTRAP, 1)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_BOOTSTRAP, 1)
         self._reset_opentitan()
-        self._fpga_io.pin_set_state(self._PIN_JTAG_SPI, 0)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].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)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_BOOTSTRAP, 0)
+        self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_JTAG_SPI, 1)
         time.sleep(self._BOOTSTRAP_DELAY)
 
     def transfer(self, frame):
@@ -209,7 +224,7 @@
 
 class SPIProgrammer:
     """Class for programming OpenTitan over the SPI interface of SAM3X/U on CW310/305."""
-    def __init__(self, firmware_path, board="CW305"):
+    def __init__(self, firmware_path, board='CW305'):
         """Inits SPIProgrammer with the path of a firmware image and board name."""
         self._firmware_path = firmware_path
         self._board = board