From fe9055ea23a90052a29372480bee3b6682aa610d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Th=C3=B6rnblad?= Date: Mon, 25 Nov 2024 15:28:13 +0100 Subject: [PATCH] Add script to split app into simulation ram MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikael Ă…gren --- .../tools/app_bin_to_spram_hex.py | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 hw/application_fpga/tools/app_bin_to_spram_hex.py diff --git a/hw/application_fpga/tools/app_bin_to_spram_hex.py b/hw/application_fpga/tools/app_bin_to_spram_hex.py new file mode 100644 index 0000000..a71c8f0 --- /dev/null +++ b/hw/application_fpga/tools/app_bin_to_spram_hex.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 + +# Copyright 2024 Tillitis AB +# SPDX-License-Identifier: BSD-2-Clause + +import argparse +import sys + +arg_parser = argparse.ArgumentParser( + description=( + "Converts one TKey app binary into four files suitable to load into SPRAM" + " during simulation." + ) +) + +arg_parser.add_argument("app_bin") +arg_parser.add_argument("output_spram0_hex") +arg_parser.add_argument("output_spram1_hex") +arg_parser.add_argument("output_spram2_hex") +arg_parser.add_argument("output_spram3_hex") + +args = arg_parser.parse_args() + + +def abort(msg: str, exitcode: int): + sys.stderr.write(msg + "\n") + sys.exit(exitcode) + + +with ( + open(args.app_bin, "rb") as app, + open(args.output_spram0_hex, "w") as spram0, + open(args.output_spram1_hex, "w") as spram1, + open(args.output_spram2_hex, "w") as spram2, + open(args.output_spram3_hex, "w") as spram3, +): + + # For debug + # app = open("app.bin", "rb") + # spram0 = open("output_spram0_hex", "w") + # spram1 = open("output_spram1_hex", "w") + # spram2 = open("output_spram2_hex", "w") + # spram3 = open("output_spram3_hex", "w") + + SPRAM_SIZE_BYTES = int(256 * 1024 / 8) + RAM_SIZE_BYTES = SPRAM_SIZE_BYTES * 4 + + rows, cols = int(SPRAM_SIZE_BYTES / 2), 2 + ram0 = [[0] * cols for _ in range(rows)] + ram1 = [[0] * cols for _ in range(rows)] + ram2 = [[0] * cols for _ in range(rows)] + ram3 = [[0] * cols for _ in range(rows)] + + # Input data is little endian + input_data_le = app.read() + + if len(input_data_le) % 2 != 0: + abort("Error: File size not a multiple of 2 bytes", -1) + + if len(input_data_le) > RAM_SIZE_BYTES: + abort("Error: File does not fit in RAM", -1) + + # Zero-pad input to RAM size + input_data_le += b"\x00" * (RAM_SIZE_BYTES - len(input_data_le)) + + # Set ram_addr_rand and ram_data_rand to the values the simulated TRNG + # will deliver. + ram_addr_rand = 0xDEADBEEF + ram_data_rand = 0xBD5B7DDE + + # Convert RAM + cpu_addr_data_le = input_data_le[0:RAM_SIZE_BYTES] + + for cpu_addr in range(0, int(RAM_SIZE_BYTES / 4), 4): + + # Data is little endian + word_data_le = cpu_addr_data_le[cpu_addr : cpu_addr + 4] + + # Get ram address + ram_addr = cpu_addr >> 2 + + # Convert ram_addr to little endian for calculation + ram_addr_le = ram_addr.to_bytes(4, byteorder="little", signed=False) + + # Convert ram_data_rand to little endian for calculation + ram_data_rand_le = ram_data_rand.to_bytes(4, byteorder="little", signed=False) + + # Concatenate two ram_addr and convert to little endian for calculation + ram_addr_double = ((ram_addr << 16) & 0xFFFF0000) | (ram_addr & 0x0000FFFF) + ram_addr_double_le = ram_addr_double.to_bytes( + 4, byteorder="little", signed=False + ) + + # XOR word_data_le, ram_data_rand_le and ram_addr_le + # Explanation: + # 1. Zip the byte strings: zip(word_data_le, ram_data_rand_le, ram_addr_double_le) creates + # an iterator of tuples with corresponding elements from the byte strings. + # 2. Apply XOR: Use a generator expression (a ^ b ^ c for a, b, c in ...) to XOR the values in each tuple. + # 3. Convert back to bytes: The bytes constructor collects the XORed values into a new byte string. + scrambled_word_data_le = bytes( + a ^ b ^ c + for a, b, c in zip(word_data_le, ram_data_rand_le, ram_addr_double_le) + ) + + # Convert ram_addr_rand to little endian for calculation + ram_addr_rand_le = ram_addr_rand.to_bytes(4, byteorder="little", signed=False) + + # XOR ram_addr_le and ram_addr_rand_le + scrambled_ram_addr_le = bytes( + a ^ b for a, b in zip(ram_addr_le, ram_addr_rand_le) + ) + + # Get scrambled_ram_addr as int + scrambled_ram_addr = int.from_bytes( + scrambled_ram_addr_le, byteorder="little", signed=False + ) + + # In hardware the highest bits are discarded since the memory is only 128K bytes + scrambled_ram_addr = scrambled_ram_addr & 0x7FFF + + # Check bit 14 for which pair of ram blocks the data should be stored in + if not (scrambled_ram_addr & 0x4000): + ram0[scrambled_ram_addr] = ( + scrambled_word_data_le[0], + scrambled_word_data_le[1], + ) + ram1[scrambled_ram_addr] = ( + scrambled_word_data_le[2], + scrambled_word_data_le[3], + ) + else: + ram2[scrambled_ram_addr] = ( + scrambled_word_data_le[0], + scrambled_word_data_le[1], + ) + ram3[scrambled_ram_addr] = ( + scrambled_word_data_le[2], + scrambled_word_data_le[3], + ) + + for line in range(0, rows, 1): + spram0.write(f"{ram0[line][1]:02x}") + spram0.write(f"{ram0[line][0]:02x}\n") + spram1.write(f"{ram1[line][1]:02x}") + spram1.write(f"{ram1[line][0]:02x}\n") + spram2.write(f"{ram2[line][1]:02x}") + spram2.write(f"{ram2[line][0]:02x}\n") + spram3.write(f"{ram3[line][1]:02x}") + spram3.write(f"{ram3[line][0]:02x}\n")