# --------------------------------------------------------------------------------------------------------------------- # link_bitstreams.py # --------------------------------------------------------------------------------------------------------------------- # # Glue iCE40 bitstream to the main FPGA bitstream. The MKM bitstream is aligned on a sector boundary. # # --------------------------------------------------------------------------------------------------------------------- # # Copyright 2021 The Commons Conservancy Cryptech Project # # SPDX-License-Identifier: BSD-3-Clause # # 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. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # 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. # # --------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------- # Imports # --------------------------------------------------------------------------------------------------------------------- import os import sys import math # --------------------------------------------------------------------------------------------------------------------- # More Imports # --------------------------------------------------------------------------------------------------------------------- from enum import Enum from typing import Optional # --------------------------------------------------------------------------------------------------------------------- # Markers # --------------------------------------------------------------------------------------------------------------------- MARKER_XILINX = b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" \ b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" \ b"\x00\x00\x00\xBB\x11\x22\x00\x44\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" \ b"\xAA\x99\x55\x66" MARKER_LATTICE = b"\x7E\xAA\x99\x7E" # --------------------------------------------------------------------------------------------------------------------- class FPGA(Enum): Artix7 = 'artix7' iCE40 = 'ice40' # --------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------- # Settings # --------------------------------------------------------------------------------------------------------------------- PROM_PAGE_BYTES = 256 PROM_SECTOR_BYTES = 256 * PROM_PAGE_BYTES MARKER_DICT = {FPGA.Artix7: MARKER_XILINX, FPGA.iCE40: MARKER_LATTICE} # --------------------------------------------------------------------------------------------------------------------- def _load_bitstream(filename: str, fpga: FPGA) -> Optional[bytes]: # try to load input bitstream try: with open(filename, 'rb') as f: bitstream = f.read() except FileNotFoundError: print("File '%s' not found." % filename) return finally: len_bitstream_orig = len(bitstream) print("File '%s' loaded, %d bytes." % (filename, len_bitstream_orig)) # check magic marker and strip leading bytes bitstream = _check_marker(bitstream, fpga) # done return bitstream # --------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------- def _check_marker(bitstream: bytes, fpga: FPGA) -> Optional[bytes]: len_bitstream_orig = len(bitstream) marker = MARKER_DICT[fpga] # check, that marker is present if marker not in bitstream: print(" Magic marker for device '%s' not found, did you specify the right filename?" % fpga.value) return # try to strip leading redundant bytes while not bitstream.startswith(marker): bitstream = bitstream[1:] # print how many bytes we stripped num_stripped_bytes = len_bitstream_orig - len(bitstream) print(" Stripped %d bytes before the magic marker." % num_stripped_bytes) # done return bitstream # --------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------- def _save_bit(bitstream: bytes, filename: str) -> int: # write memory to file with open(filename, 'wb') as f: f.write(bitstream) print("Output bitstream written.") # done return 0 # --------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------- def _link_bitstreams(filename_artix7: str, filename_ice40: str, filename_output: str) -> int: # load bitstream bitstream_artix7 = _load_bitstream(filename_artix7, FPGA.Artix7) bitstream_ice40 = _load_bitstream(filename_ice40, FPGA.iCE40) if bitstream_artix7 is None or bitstream_ice40 is None: return 1 # grow main bitstream to sector boundary num_sectors_artix7 = math.ceil(len(bitstream_artix7) / PROM_SECTOR_BYTES) num_padding_bytes = num_sectors_artix7 * PROM_SECTOR_BYTES - len(bitstream_artix7) print("Added %d byte(s) after the main bitstream to align the MKM bitmap on sector boundary." % num_padding_bytes) padding_bytes = bytes(0xFF for _ in range(num_padding_bytes)) bitstream_artix7_padded = bitstream_artix7 + padding_bytes # glue two bitstreams bitstream_output = bitstream_artix7_padded + bitstream_ice40 # fill bitstream with 0xFF's up to page boundary num_padding_bytes = PROM_PAGE_BYTES - (len(bitstream_output) % 256) print("Added %d byte(s) after the mkm bitmap to align output on page boundary." % num_padding_bytes) padding_bytes = bytes(0xFF for _ in range(num_padding_bytes)) bitstream_output += padding_bytes # save bit return _save_bit(bitstream_output, filename_output) # --------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------- def print_usage() -> int: print("USAGE: %s " % os.path.basename(sys.argv[0])) return 2 # --------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------- def main() -> int: # check, that exactly three arguments were supplied if len(sys.argv) != 4: return print_usage() # now run the script return _link_bitstreams(*sys.argv[1:4]) # --------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------- if __name__ == '__main__': sys.exit(main()) # --------------------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------------------- # End-of-File # ---------------------------------------------------------------------------------------------------------------------