Add tool to program an FPGA
To program FPGAs with a bitstream, we currently either need to rely on
fusesoc, or open the Vivado GUI. This commit adds a command line tool to
do the same, based on the TCL code available in fusesoc/edalize.
Signed-off-by: Philipp Wagner <phw@lowrisc.org>
diff --git a/util/licence-checker.hjson b/util/licence-checker.hjson
index 206f8e3..f800022 100644
--- a/util/licence-checker.hjson
+++ b/util/licence-checker.hjson
@@ -58,6 +58,7 @@
# Site Assets
'site/**/assets/scss/**',
'site/landing/static/js/tiny-slider.js',
+ 'util/opentitan-pgm-fpga/vivado_pgm.tcl',
# Code taken from Chromium, so covered by the BSD licence
'sw/otbn/code-snippets/modexp.S',
diff --git a/util/opentitan-pgm-fpga/LICENSE.edalize b/util/opentitan-pgm-fpga/LICENSE.edalize
new file mode 100644
index 0000000..ffa88a7
--- /dev/null
+++ b/util/opentitan-pgm-fpga/LICENSE.edalize
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2018, Olof Kindgren
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/util/opentitan-pgm-fpga/opentitan-pgm-fpga b/util/opentitan-pgm-fpga/opentitan-pgm-fpga
new file mode 100755
index 0000000..fbefd4e
--- /dev/null
+++ b/util/opentitan-pgm-fpga/opentitan-pgm-fpga
@@ -0,0 +1,64 @@
+#!/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
+"""Program an FPGA with a bitstream
+"""
+import argparse
+import os
+import subprocess
+import sys
+
+
+def program_bitstream_with_vivado(part, bitstream, hw_target, jtag_freq):
+ tcl_file = os.path.join(os.path.dirname(__file__), 'vivado_pgm.tcl')
+ if not os.path.isfile(tcl_file):
+ raise FileNotFoundError('{} not found.'.format(tcl_file))
+
+ cmd_env = os.environ.copy()
+ if hw_target is not None:
+ cmd_env['HW_TARGET'] = hw_target
+ if jtag_freq is not None:
+ cmd_env['JTAG_FREQ'] = jtag_freq
+
+ cmd = [
+ 'vivado', '-quiet', '-nolog', '-notrace', '-mode', 'batch', '-source',
+ str(tcl_file), '-tclargs', part, bitstream
+ ]
+ try:
+ subprocess.run(cmd, check=True, env=cmd_env)
+ except FileNotFoundError as e:
+ print("Unable to run Vivado: %s. Is Vivado in your PATH?" % str(e),
+ file=sys.stderr)
+ return 1
+ except subprocess.CalledProcessError as e:
+ print(str(e), file=sys.stderr)
+ return 1
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--hw-target',
+ '-t',
+ required=False,
+ default=None,
+ help='Hardware target to use. Use the TCL command '
+ 'get_hw_targets in Vivado to get a list of available '
+ 'targets.')
+ parser.add_argument('--jtag-freq',
+ '-f',
+ required=False,
+ default=None,
+ help='JTAG frequency (in Hz)')
+ parser.add_argument('part', help='Device (part) to flash')
+ parser.add_argument('bitstream', help='Path to the bitstream file')
+ args = parser.parse_args()
+
+ return program_bitstream_with_vivado(args.part,
+ args.bitstream,
+ hw_target=args.hw_target,
+ jtag_freq=args.jtag_freq)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/util/opentitan-pgm-fpga/vivado_pgm.tcl b/util/opentitan-pgm-fpga/vivado_pgm.tcl
new file mode 100644
index 0000000..e10ab6e
--- /dev/null
+++ b/util/opentitan-pgm-fpga/vivado_pgm.tcl
@@ -0,0 +1,111 @@
+# Copyright lowRISC contributors
+# Copyright edalize contributors
+# Licensed under the 2-Clause BSD License, see LICENSE.edalize for details.
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# This file was produced by edalize from a template at
+# https://github.com/olofk/edalize/blob/592154c50ac40ec86e3d954dd2d300b0573b2c37/edalize/templates/vivado/vivado-program.tcl.j2
+
+set part [lindex $argv 0]
+set bitstream [lindex $argv 1]
+
+
+if {[info exists env(HW_TARGET)]} {
+ set explicit_hw_target $env(HW_TARGET)
+} else {
+ set explicit_hw_target ""
+}
+if {[info exists env(JTAG_FREQ)]} {
+ set jtag_freq $env(JTAG_FREQ)
+} else {
+ set jtag_freq ""
+}
+
+puts "OpenTitan Xilinx FPGA Programming Tool"
+puts "======================================"
+puts ""
+puts "INFO: Programming part $part with bitstream $bitstream"
+if { $explicit_hw_target != "" } {
+ puts "INFO: Programming target $explicit_hw_target"
+}
+
+# Connect to Xilinx Hardware Server
+if { [ catch { open_hw_manager } ] } { open_hw }
+connect_hw_server
+
+if { $explicit_hw_target == "" } {
+ set hw_targets [get_hw_targets]
+} else {
+ set hw_targets [get_hw_targets $explicit_hw_target]
+}
+
+if { [llength $hw_targets] == 0 } {
+ if { $explicit_hw_target == "" } {
+ puts "ERROR: Failed to find any targets"
+ } else {
+ puts "ERROR: Failed to find target: $target"
+ }
+}
+
+# Find the first target and device that contains a FPGA $part.
+set hw_device_found 0
+foreach hw_target $hw_targets {
+ puts "INFO: Trying to use hardware target $hw_target"
+ current_hw_target $hw_target
+
+ # Open hardware target
+ # The Vivado hardware server isn't always able to reliably open a target.
+ # Try three times before giving up.
+ set hw_target_opened 0
+ for {set open_hw_target_try 1} {$open_hw_target_try <= 3} {incr open_hw_target_try} {
+ if {[catch {open_hw_target} res_open_hw_target] == 0} {
+ set hw_target_opened 1
+ break
+ }
+ }
+ if { $hw_target_opened == 0 } {
+ puts "WARNING: Unable to open hardware target $hw_target after " \
+ "$open_hw_target_try tries. Skipping."
+ continue
+ }
+ puts "INFO: Opened hardware target $hw_target on try $open_hw_target_try."
+
+ # Iterate through all devices and find one which contains $part
+ foreach { hw_device } [get_hw_devices] {
+ if { [string first [get_property PART $hw_device] $part] == 0 } {
+ puts "INFO: Found $part as part of $hw_device."
+ current_hw_device $hw_device
+ set hw_device_found 1
+ break
+ }
+ }
+
+ if { $hw_device_found == 1 } {
+ break
+ } else {
+ # Close currently tried device, and try with next one.
+ puts "INFO: Part not found as part of $hw_target. Trying next device."
+ close_hw_target
+ }
+}
+if { $hw_device_found == 0 } {
+ puts "ERROR: None of the hardware targets included a $part FPGA part. \
+ Check cables and ensure that jumpers are correct for JTAG programming."
+ exit 1
+}
+puts "INFO: Programming bitstream to device $hw_device on target $hw_target."
+
+# Do the programming
+current_hw_device $hw_device
+set_property PROGRAM.FILE $bitstream [current_hw_device]
+if {$jtag_freq != ""} {
+ set_property PARAM.FREQUENCY $jtag_freq [get_hw_targets $hw_target]
+}
+program_hw_devices [current_hw_device]
+
+# Disconnect from Xilinx Hardware Server
+close_hw_target
+disconnect_hw_server
+
+puts ""
+puts "INFO: SUCCESS! FPGA $part successfully programmed with bitstream $bitstream."