# ---------------------------------------------------------------------------------------------------------------------
# bitstream2mcs.py
# ---------------------------------------------------------------------------------------------------------------------
#
# Convert iCE40 bitstream into .mcs format suitable for programming using Xilinx iMPACT tool. Xilinx .mcs is in fact
# just plain Intel Hex. Since the beginning of our configuration memory is occupied by the main FPGA bitstream, we
# have to move iCE40 bitstream to the end of the configuration memory space.
#
# ---------------------------------------------------------------------------------------------------------------------
#
# 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 typing import Union
from intelhex import IntelHex
# ---------------------------------------------------------------------------------------------------------------------
# Settings
# ---------------------------------------------------------------------------------------------------------------------
LATTICE_MAGIC_MARKER = b'\x7E\xAA\x99\x7E'
PROM_SECTOR_SIZE = 256 * 256
PROM_NUM_SECTORS = 256
# ---------------------------------------------------------------------------------------------------------------------
def _load_bitstream(filename: str) -> Union[bytes, None]:
# 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))
# try to strip header
while not bitstream.startswith(LATTICE_MAGIC_MARKER):
bitstream = bitstream[1:]
if not bitstream:
print("Magic marker not found??")
return
# 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_mcs(offset: int, bitstream: bytes, filename: str) -> int:
# create Intel Hex class
ih = IntelHex()
# initialize memory from bitstream
ih.frombytes(bitstream, offset=offset)
# write memory to file
with open(filename, 'w') as f:
ih.write_hex_file(f)
# done
return 0
# ---------------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
def _bitstream2mcs(filename_in: str, filename_out: str) -> int:
# load bitstream
bitstream = _load_bitstream(filename_in)
if bitstream is None:
return 1
# determine number of sectors needed
num_sectors = math.ceil(len(bitstream) / PROM_SECTOR_SIZE)
print("Number of PROM sectors needed: %d" % num_sectors)
prom_offset = (PROM_NUM_SECTORS - num_sectors) * PROM_SECTOR_SIZE
print("Bitstream offset: 0x%x" % prom_offset)
# save mcs
return _save_mcs(prom_offset, bitstream, filename_out)
# ---------------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
def print_usage() -> int:
print("USAGE: %s <bitstream.bin> <bitstream.mcs>" % os.path.basename(sys.argv[0]))
return 2
# ---------------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
def main() -> int:
# check, that exactly two arguments were supplied
if len(sys.argv) != 3:
return print_usage()
# now run the script
return _bitstream2mcs(sys.argv[1], sys.argv[2])
# ---------------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
if __name__ == '__main__':
sys.exit(main())
# ---------------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# End-of-File
# ---------------------------------------------------------------------------------------------------------------------