#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Implementation by Pedro Maat C. Massolino,
# hereby denoted as "the implementer".
#
# To the extent possible under law, the implementer has waived all copyright
# and related or neighboring rights to the source code in this file.
# http://creativecommons.org/publicdomain/zero/1.0/

import time
import random
import sys

from ultrascale_sidh import *
import sike_core_utils
import ecc_fpga_constants_v128
import ecc_fpga_constants_v256


tests_prom_folder = "../assembler/"

program_start_address_test_sike_kem_keygen = 1
program_start_address_test_sike_kem_enc = 3
program_start_address_test_sike_kem_dec = 5
program_start_address_test_sidh_keygen_alice = 7
program_start_address_test_sidh_keygen_bob = 9
program_start_address_test_sidh_shared_secret_alice = 11
program_start_address_test_sidh_shared_secret_bob = 13
program_start_address_test_ecc_scalar_multiplication = 15

sike_core_mac_ram_start_address =                           0x00000;
sike_core_mac_ram_last_address =                            0x07FFF;
sike_core_base_alu_ram_start_address =                      0x0C000;
sike_core_base_alu_ram_last_address =                       0x0C3FF;
sike_core_keccak_core_start_address =                       0x0D000;
sike_core_keccak_core_last_address =                        0x0D007;
sike_core_reg_program_counter_address =                     0x0E000;
sike_core_reg_status_address =                              0x0E001;
sike_core_reg_operands_size_address =                       0x0E002;
sike_core_reg_prime_line_equal_one_address =                0x0E003;
sike_core_reg_prime_address_address =                       0x0E004;
sike_core_reg_prime_plus_one_address_address =              0x0E005;
sike_core_reg_prime_line_address_address =                  0x0E006;
sike_core_reg_2prime_address_address =                      0x0E007;
sike_core_reg_initial_stack_address_address =               0x0E008;
sike_core_reg_flag_address =                                0x0E009;
sike_core_reg_scalar_address_address =                      0x0E00A;

sike_core_mac_ram_prime_address =                           0x00000;
sike_core_mac_ram_prime_plus_one_address =                  0x00001;
sike_core_mac_ram_prime_line_address =                      0x00002;
sike_core_mac_ram_2prime_address =                          0x00003;
sike_core_mac_ram_const_r_address =                         0x00004;
sike_core_mac_ram_const_r2_address =                        0x00005;
sike_core_mac_ram_const_1_address =                         0x00006;
sike_core_mac_ram_inv_4_mont_address =                      0x00007;
sike_core_mac_ram_sidh_xpa_mont_address =                   0x00008;
sike_core_mac_ram_sidh_xpai_mont_address =                  0x00009;
sike_core_mac_ram_sidh_xqa_mont_address =                   0x0000A;
sike_core_mac_ram_sidh_xqai_mont_address =                  0x0000B;
sike_core_mac_ram_sidh_xra_mont_address =                   0x0000C;
sike_core_mac_ram_sidh_xrai_mont_address =                  0x0000D;
sike_core_mac_ram_sidh_xpb_mont_address =                   0x0000E;
sike_core_mac_ram_sidh_xpbi_mont_address =                  0x0000F;
sike_core_mac_ram_sidh_xqb_mont_address =                   0x00010;
sike_core_mac_ram_sidh_xqbi_mont_address =                  0x00011;
sike_core_mac_ram_sidh_xrb_mont_address =                   0x00012;
sike_core_mac_ram_sidh_xrbi_mont_address =                  0x00013;

sike_core_base_alu_ram_sike_s_start_address =               0x000FB;
sike_core_base_alu_ram_sike_sk_start_address =              0x0011B;
sike_core_base_alu_ram_sike_m_start_address =               0x0013B;
sike_core_base_alu_ram_sike_ss_start_address =              0x0015B;
sike_core_base_alu_ram_sike_c1_start_address =              0x0017B;
sike_core_base_alu_ram_sike_message_length_address =        0x0019B;
sike_core_base_alu_ram_sike_shared_secret_length_address =  0x0019C;
sike_core_base_alu_ram_oa_mask_address =                    0x0019D;
sike_core_base_alu_ram_ob_mask_address =                    0x0019E;
sike_core_base_alu_ram_oa_bits_address =                    0x0019F;
sike_core_base_alu_ram_ob_bits_address =                    0x001A0;
sike_core_base_alu_ram_prime_size_bits_address =            0x001A1;
sike_core_base_alu_ram_splits_alice_start_address =         0x001A2;
sike_core_base_alu_ram_max_row_alice_address =              0x002D0;
sike_core_base_alu_ram_splits_bob_start_address =           0x002D1;
sike_core_base_alu_ram_max_row_bob_address =                0x003FF;

sike_core_mac_ram_input_function_start_address =            0x00014;
sike_core_mac_ram_output_function_start_address =           0x00024;

sike_core_ecc_base_alu_ram_scalar_max_size_address =        0x000FA;

def scalar_multiplication_weierstrass(prime, const_a, const_a2, const_b3, scalar, elliptic_curve_point):
    
    r0x = 0
    r0y = 1
    r0z = 0
    
    r1x = elliptic_curve_point[0]
    r1y = elliptic_curve_point[1]
    r1z = elliptic_curve_point[2]
    # Scalar position 0 is the most significant bit.
    scalar_list = [int(x) for x in (bin(scalar)[2:])]
    for i, scalar_bit in enumerate(scalar_list):
        if(scalar_bit == 1):
            #
            # Point Addition
            #
            # Step 1
            #
            # t0 := X1+Y1;
            # t1 := X2+Y2;
            # t2 := Y1+Z1;
            # t3 := Y2+Z2;
            t0 = (r0x+r0y) % prime
            t1 = (r1x+r1y) % prime
            t2 = (r0y+r0z) % prime
            t3 = (r1y+r1z) % prime
            #
            # t4 := X1+Z1;
            # t5 := X2+Z2;
            t4 = (r0x+r0z) % prime
            t5 = (r1x+r1z) % prime

            # Step 2
            #
            # t6  := t0*t1;
            # t7  := t2*t3;
            # t8  := t4*t5;
            # t9  := X1*X2;
            # t10 := Y1*Y2;
            # t11 := Z1*Z2;
            t6  = (t0*t1) % prime
            t7  = (t2*t3) % prime
            t8  = (t4*t5) % prime
            t9  = (r1x*r0x) % prime
            t10 = (r1y*r0y) % prime
            t11 = (r1z*r0z) % prime
            
            # Step 3
            #
            # t3 := t6-t9;
            # t4 := t7-t10;
            # t5 := t8-t11;
            t3 = (t6-t9) % prime
            t4 = (t7-t10) % prime
            t5 = (t8-t11) % prime
            
            # Step 4
            #
            # t0 := t3-t10;
            # t1 := t4-t11;
            # t2 := t5-t9;
            t0 = (t3-t10) % prime
            t1 = (t4-t11) % prime
            t2 = (t5-t9) % prime
            
            # Step 5
            #
            # t3 := b3*t11;
            # t4 := a*t11;
            # t5 := a*t2;
            # t6 := b3*t2;
            # t7 := a*t9;
            # t8 := a^2*t11;
            t3  = (const_b3*t11) % prime
            t4  = (const_a*t11) % prime
            t5  = (const_a*t2) % prime
            t6  = (const_b3*t2) % prime
            t7  = (const_a*t9) % prime
            t8  = (const_a2*t11) % prime
            
            # Step 6
            #
            # t2  := t3+t5;
            # t11 := t9+t4;
            # t12 := t9+t9;
            # t13 := t6+t7;
            t2  = (t3+t5) % prime
            t11 = (t9+t4) % prime
            t12 = (t9+t9) % prime
            t13 = (t6+t7) % prime
            
            # Step 7
            #
            # t3 := t13-t8;
            # t4 := t12+t11;
            # t5 := t10-t2;
            # t6 := t10+t2;
            t3 = (t13-t8) % prime
            t4 = (t12+t11) % prime
            t5 = (t10-t2) % prime
            t6 = (t10+t2) % prime
            
            # Step 8
            #
            # t2 := t0*t5;
            # t7 := t0*t4;
            # t8 := t1*t3;
            # t9 := t4*t3;
            # t10 := t6*t5;
            # t11 := t1*t6;
            t2  = (t0*t5) % prime
            t7  = (t0*t4) % prime
            t8  = (t1*t3) % prime
            t9  = (t4*t3) % prime
            t10 = (t6*t5) % prime
            t11 = (t1*t6) % prime
            
            # Step 9
            #
            # X3 := t2-t8;
            # Y3 := t10+t9;
            # Z3 := t11+t7;
            r0x = (t2-t8) % prime
            r0y = (t10+t9) % prime
            r0z = (t11+t7) % prime
            
            #
            # Point Doubling with Addition
            #
            # Step 1
            #
            # t14 := X1+Y1;
            # t15 := X2+Y2;
            # t16 := Y1+Z1;
            # t17 := Y2+Z2;
            t14 = (r1x+r1y) % prime
            t15 = (r1x+r1y) % prime
            t16 = (r1y+r1z) % prime
            t17 = (r1y+r1z) % prime
            #
            # t18 := X1+Z1;
            # t19 := X2+Z2;
            t18 = (r1x+r1z) % prime
            t19 = (r1x+r1z) % prime
            
            # Step 2
            #
            # t20 := t14*t15;
            # t21 := t16*t17;
            # t22 := t18*t19;
            # t23 := X1*X2;
            # t24 := Y1*Y2;
            # t25 := Z1*Z2;
            t20 = (t14*t15) % prime
            t21 = (t16*t17) % prime
            t22 = (t18*t19) % prime
            t23 = (r1x*r1x) % prime
            t24 = (r1y*r1y) % prime
            t25 = (r1z*r1z) % prime
            
            # Step 3
            #
            # t17 := t20-t23;
            # t18 := t21-t24;
            # t19 := t22-t25;
            t17 = (t20-t23) % prime
            t18 = (t21-t24) % prime
            t19 = (t22-t25) % prime
            
            # Step 4
            #
            # t14 := t17-t24;
            # t15 := t18-t25;
            # t16 := t19-t23;
            t14 = (t17-t24) % prime
            t15 = (t18-t25) % prime
            t16 = (t19-t23) % prime
            
            # Step 5
            #
            # t17 := b3*t25;
            # t18 := a*t25;
            # t19 := a*t16;
            # t20 := b3*t16;
            # t21 := a*t23;
            # t22 := a^2*t25;
            t17 = (const_b3*t25) % prime
            t18 = (const_a*t25) % prime
            t19 = (const_a*t16) % prime
            t20 = (const_b3*t16) % prime
            t21 = (const_a*t23) % prime
            t22 = (const_a2*t25) % prime
            
            # Step 6
            #
            # t16 := t17+t19;
            # t25 := t23+t18;
            # t26 := t23+t23;
            # t27 := t20+t21;
            t16 = (t17+t19) % prime
            t25 = (t23+t18) % prime
            t26 = (t23+t23) % prime
            t27 = (t20+t21) % prime
            
            # Step 7
            #
            # t17 := t27-t22;
            # t18 := t26+t25;
            # t19 := t24-t16;
            # t20 := t24+t16;
            t17 = (t27-t22) % prime
            t18 = (t26+t25) % prime
            t19 = (t24-t16) % prime
            t20 = (t24+t16) % prime
            
            # Step 8
            #
            # t16 := t14*t19;
            # t21 := t14*t18;
            # t22 := t15*t17;
            # t23 := t18*t17;
            # t24 := t20*t19;
            # t25 := t15*t20;
            t16  = (t14*t19) % prime
            t21  = (t14*t18) % prime
            t22  = (t15*t17) % prime
            t23  = (t18*t17) % prime
            t24  = (t20*t19) % prime
            t25  = (t15*t20) % prime
            
            # Step 9
            #
            # X3 := t16-t22;
            # Y3 := t24+t23;
            # Z3 := t25+t21;
            r1x = (t16-t22) % prime
            r1y = (t24+t23) % prime
            r1z = (t25+t21) % prime
        else:
            #
            # Point Addition
            #
            # Step 1
            #
            # t0 := X1+Y1;
            # t1 := X2+Y2;
            # t2 := Y1+Z1;
            # t3 := Y2+Z2;
            t0 = (r0x+r0y) % prime
            t1 = (r1x+r1y) % prime
            t2 = (r0y+r0z) % prime
            t3 = (r1y+r1z) % prime
            #
            # t4 := X1+Z1;
            # t5 := X2+Z2;
            t4 = (r0x+r0z) % prime
            t5 = (r1x+r1z) % prime
            
            # Step 2
            #
            # t6  := t0*t1;
            # t7  := t2*t3;
            # t8  := t4*t5;
            # t9  := X1*X2;
            # t10 := Y1*Y2;
            # t11 := Z1*Z2;
            t6  = (t0*t1) % prime
            t7  = (t2*t3) % prime
            t8  = (t4*t5) % prime
            t9  = (r1x*r0x) % prime
            t10 = (r1y*r0y) % prime
            t11 = (r1z*r0z) % prime
            
            # Step 3
            #
            # t3 := t6-t9;
            # t4 := t7-t10;
            # t5 := t8-t11;
            t3 = (t6-t9) % prime
            t4 = (t7-t10) % prime
            t5 = (t8-t11) % prime
            
            # Step 4
            #
            # t0 := t3-t10;
            # t1 := t4-t11;
            # t2 := t5-t9;
            t0 = (t3-t10) % prime
            t1 = (t4-t11) % prime
            t2 = (t5-t9) % prime
            
            # Step 5
            #
            # t3 := b3*t11;
            # t4 := a*t11;
            # t5 := a*t2;
            # t6 := b3*t2;
            # t7 := a*t9;
            # t8 := a^2*t11;
            t3  = (const_b3*t11) % prime
            t4  = (const_a*t11) % prime
            t5  = (const_a*t2) % prime
            t6  = (const_b3*t2) % prime
            t7  = (const_a*t9) % prime
            t8  = (const_a2*t11) % prime
            
            # Step 6
            #
            # t2  := t3+t5;
            # t11 := t9+t4;
            # t12 := t9+t9;
            # t13 := t6+t7;
            t2  = (t3+t5) % prime
            t11 = (t9+t4) % prime
            t12 = (t9+t9) % prime
            t13 = (t6+t7) % prime
            
            # Step 7
            #
            # t3 := t13-t8;
            # t4 := t12+t11;
            # t5 := t10-t2;
            # t6 := t10+t2;
            t3 = (t13-t8) % prime
            t4 = (t12+t11) % prime
            t5 = (t10-t2) % prime
            t6 = (t10+t2) % prime
            
            # Step 8
            #
            # t2 := t0*t5;
            # t7 := t0*t4;
            # t8 := t1*t3;
            # t9 := t4*t3;
            # t10 := t6*t5;
            # t11 := t1*t6;
            t2  = (t0*t5) % prime
            t7  = (t0*t4) % prime
            t8  = (t1*t3) % prime
            t9  = (t4*t3) % prime
            t10 = (t6*t5) % prime
            t11 = (t1*t6) % prime
            
            # Step 9
            #
            # X3 := t2-t8;
            # Y3 := t10+t9;
            # Z3 := t11+t7;
            r1x = (t2-t8) % prime
            r1y = (t10+t9) % prime
            r1z = (t11+t7) % prime
            
            #
            # Point Doubling with Addition
            #
            # Step 1
            #
            # t14 := X1+Y1;
            # t15 := X2+Y2;
            # t16 := Y1+Z1;
            # t17 := Y2+Z2;
            t14 = (r0x+r0y) % prime
            t15 = (r0x+r0y) % prime
            t16 = (r0y+r0z) % prime
            t17 = (r0y+r0z) % prime
            #
            # t18 := X1+Z1;
            # t19 := X2+Z2;
            t18 = (r0x+r0z) % prime
            t19 = (r0x+r0z) % prime
            
            # Step 2
            #
            # t20 := t14*t15;
            # t21 := t16*t17;
            # t22 := t18*t19;
            # t23 := X1*X2;
            # t24 := Y1*Y2;
            # t25 := Z1*Z2;
            t20 = (t14*t15) % prime
            t21 = (t16*t17) % prime
            t22 = (t18*t19) % prime
            t23 = (r0x*r0x) % prime
            t24 = (r0y*r0y) % prime
            t25 = (r0z*r0z) % prime
            
            # Step 3
            #
            # t17 := t20-t23;
            # t18 := t21-t24;
            # t19 := t22-t25;
            t17 = (t20-t23) % prime
            t18 = (t21-t24) % prime
            t19 = (t22-t25) % prime
            
            # Step 4
            #
            # t14 := t17-t24;
            # t15 := t18-t25;
            # t16 := t19-t23;
            t14 = (t17-t24) % prime
            t15 = (t18-t25) % prime
            t16 = (t19-t23) % prime
            
            # Step 5
            #
            # t17 := b3*t25;
            # t18 := a*t25;
            # t19 := a*t16;
            # t20 := b3*t16;
            # t21 := a*t23;
            # t22 := a^2*t25;
            t17 = (const_b3*t25) % prime
            t18 = (const_a*t25) % prime
            t19 = (const_a*t16) % prime
            t20 = (const_b3*t16) % prime
            t21 = (const_a*t23) % prime
            t22 = (const_a2*t25) % prime
            
            # Step 6
            #
            # t16 := t17+t19;
            # t25 := t23+t18;
            # t26 := t23+t23;
            # t27 := t20+t21;
            t16 = (t17+t19) % prime
            t25 = (t23+t18) % prime
            t26 = (t23+t23) % prime
            t27 = (t20+t21) % prime
            
            # Step 7
            #
            # t17 := t27-t22;
            # t18 := t26+t25;
            # t19 := t24-t16;
            # t20 := t24+t16;
            t17 = (t27-t22) % prime
            t18 = (t26+t25) % prime
            t19 = (t24-t16) % prime
            t20 = (t24+t16) % prime
            
            # Step 8
            #
            # t16 := t14*t19;
            # t21 := t14*t18;
            # t22 := t15*t17;
            # t23 := t18*t17;
            # t24 := t20*t19;
            # t25 := t15*t20;
            t16  = (t14*t19) % prime
            t21  = (t14*t18) % prime
            t22  = (t15*t17) % prime
            t23  = (t18*t17) % prime
            t24  = (t20*t19) % prime
            t25  = (t15*t20) % prime
            
            # Step 9
            #
            # X3 := t16-t22;
            # Y3 := t24+t23;
            # Z3 := t25+t21;
            r0x = (t16-t22) % prime
            r0y = (t24+t23) % prime
            r0z = (t25+t21) % prime
    
    inv_r0z  = pow(r0z, prime-2, prime)
    inv_r1z  = pow(r1z, prime-2, prime)
    
    r0x = (r0x*inv_r0z) % prime
    r0y = (r0y*inv_r0z) % prime
    r0z = 1
    r1x = (r1x*inv_r1z) % prime
    r1y = (r1y*inv_r1z) % prime
    r1z = 1
    
    return r0x, r0y
    
    
def load_program(ultrascale, prom_file_name, base_word_size, base_word_size_signed_number_words):
    prom_file = open(prom_file_name, 'r')
    program = []
    prom_file.seek(0, 2)
    prom_file_size = prom_file.tell()
    prom_file.seek(0)
    while (prom_file.tell() != prom_file_size):
        program += [sike_core_utils.load_list_value_VHDL_MAC_memory_as_integer(prom_file, base_word_size, base_word_size_signed_number_words, 1, False)]
    print("Loading program into SIKE core:" + str(prom_file_name))
    ultrascale.write_program_prom(0, program)
    print("Reading program uploaded into SIKE core")
    program_written = ultrascale.read_program_prom(0, len(program))
    print("Verifying program uploaded into SIKE core")
    if(program_written == program):
        return True
    print(program)
    print(program_written)
    return False

def test_single_scalar_multiplication(ultrascale, prime, base_word_size, extended_word_size, number_of_words, scalar, elliptic_curve_point, const_a, const_a2, const_b3, const_a_mont, const_a2_mont, const_b3_mont, debug_mode=False):

    scalar_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, scalar)
    
    elliptic_curve_point_x_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, elliptic_curve_point[0])
    elliptic_curve_point_y_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, elliptic_curve_point[1])
    elliptic_curve_point_z_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, elliptic_curve_point[2])
    const_a_mont_list  = sike_core_utils.integer_to_list(extended_word_size, number_of_words, const_a_mont)
    const_a2_mont_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, const_a2_mont)
    const_b3_mont_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, const_b3_mont)
    
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_start_address + sike_core_mac_ram_input_function_start_address + 0, scalar_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_start_address + sike_core_mac_ram_input_function_start_address + 1, elliptic_curve_point_x_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_start_address + sike_core_mac_ram_input_function_start_address + 2, elliptic_curve_point_y_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_start_address + sike_core_mac_ram_input_function_start_address + 3, elliptic_curve_point_z_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_start_address + sike_core_mac_ram_input_function_start_address + 4, const_a_mont_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_start_address + sike_core_mac_ram_input_function_start_address + 5, const_a2_mont_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_start_address + sike_core_mac_ram_input_function_start_address + 6, const_b3_mont_list, number_of_words)
    
    ultrascale.write_package(sike_core_reg_program_counter_address, program_start_address_test_ecc_scalar_multiplication)
    
    true_ecc_r0x, true_ecc_r0y = scalar_multiplication_weierstrass(prime, const_a, const_a2, const_b3, scalar, elliptic_curve_point)
    
    #time.sleep(0.1)
    
    while(not ultrascale.isFree()):
        time.sleep(0.1)
    
    computed_test_ecc_r0x_list = ultrascale.read_mac_ram_operand(sike_core_mac_ram_start_address + sike_core_mac_ram_output_function_start_address + 0, number_of_words)
    computed_test_ecc_r0y_list = ultrascale.read_mac_ram_operand(sike_core_mac_ram_start_address + sike_core_mac_ram_output_function_start_address + 1, number_of_words)
    
    computed_test_ecc_r0x = sike_core_utils.list_to_integer(extended_word_size, number_of_words, computed_test_ecc_r0x_list)
    computed_test_ecc_r0y = sike_core_utils.list_to_integer(extended_word_size, number_of_words, computed_test_ecc_r0y_list)
    
    if((debug_mode) or ((computed_test_ecc_r0x != true_ecc_r0x) or (computed_test_ecc_r0y != true_ecc_r0y))):
        print("Error in ECC scalar multiplication ")
        print("ECC Point")
        print('{:0x}'.format(elliptic_curve_point[0]))
        print('{:0x}'.format(elliptic_curve_point[1]))
        print('{:0x}'.format(elliptic_curve_point[2]))
        print("ECC scalar")
        print(scalar)
        print("ECC True scalar*Point")
        print('{:0x}'.format(true_ecc_r0x))
        print('{:0x}'.format(true_ecc_r0y))
        print("ECC Computed scalar*Point")
        print('{:0x}'.format(computed_test_ecc_r0x))
        print('{:0x}'.format(computed_test_ecc_r0y))
        return True
    return False

def test_scalar_multiplication(ultrascale, param, number_of_tests, debug_mode=False):

    base_word_size = param[1]
    extended_word_size = param[2]
    number_of_words = param[4]
    prime = param[5]
    prime_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, param[5])
    prime_plus_one_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, param[7])
    prime_line_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, param[8])
    prime2 = param[10]
    prime2_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, param[10])
    r_mod_prime_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, param[11])
    r2_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, param[12])
    constant_1_list = sike_core_utils.integer_to_list(extended_word_size, number_of_words, param[13])
    
    error_computation = False
    
    prime_size = param[6]
    scalar_max = param[14]
    scalar_max_size = param[15]
    
    starting_position_stack_sidh_core = param[25]
    
    enable_special_prime_line_arithmetic = param[9]
    
    const_a_mont  = param[16]
    const_a2_mont = param[17]
    const_b3_mont = param[18] 
    
    const_a  = param[19]
    const_a2 = param[20]
    const_b3 = param[21] 
    elliptic_curve_point = param[22:25]
    
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_prime_address, prime_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_prime_plus_one_address, prime_plus_one_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_prime_line_address, prime_line_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_2prime_address, prime2_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_const_r_address, r_mod_prime_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_const_r2_address, r2_list, number_of_words)
    ultrascale.write_mac_ram_operand(sike_core_mac_ram_const_1_address, constant_1_list, number_of_words)
    
    ultrascale.write_package(sike_core_base_alu_ram_start_address + sike_core_base_alu_ram_prime_size_bits_address, prime_size)
    ultrascale.write_package(sike_core_base_alu_ram_start_address + sike_core_ecc_base_alu_ram_scalar_max_size_address, scalar_max_size)
    
    ultrascale.write_package(sike_core_reg_operands_size_address, number_of_words - 1)
    ultrascale.write_package(sike_core_reg_prime_line_equal_one_address, enable_special_prime_line_arithmetic)
    ultrascale.write_package(sike_core_reg_prime_address_address, 0)
    ultrascale.write_package(sike_core_reg_prime_plus_one_address_address, 1)
    ultrascale.write_package(sike_core_reg_prime_line_address_address, 2)
    ultrascale.write_package(sike_core_reg_2prime_address_address, 3)
    ultrascale.write_package(sike_core_reg_scalar_address_address, 0)
    ultrascale.write_package(sike_core_reg_initial_stack_address_address, starting_position_stack_sidh_core)
    
    # Fixed test
    tests_already_performed = 0
    fixed_tests = [1, scalar_max-2]
    for test in fixed_tests:
        scalar = test
        error_computation = test_single_scalar_multiplication(ultrascale, prime, base_word_size, extended_word_size, number_of_words, scalar, elliptic_curve_point, const_a, const_a2, const_b3, const_a_mont, const_a2_mont, const_b3_mont, debug_mode)
        tests_already_performed += 1
        if(error_computation):
            break
    
    # Random tests
    if(not error_computation):
        for i in range(tests_already_performed, number_of_tests):
            if(((i %(1000)) == 0)):
                print(i)
            scalar = random.randint(0, scalar_max-1)
            error_computation = test_single_scalar_multiplication(ultrascale, prime, base_word_size, extended_word_size, number_of_words, scalar, elliptic_curve_point, const_a, const_a2, const_b3, const_a_mont, const_a2_mont, const_b3_mont, debug_mode)
        
            if(error_computation):
                break
    
    return error_computation

def test_all_scalar_multiplication(ultrascale, version, only_one_parameter=None):
    sike_base_word_size = 16
    if(version == '256'):
        sike_extended_word_size = 256
        ecc_fpga_constants = ecc_fpga_constants_v256.ecc_fpga_constants_v256
    elif(version == '128'):
        sike_extended_word_size = 128
        ecc_fpga_constants = ecc_fpga_constants_v128.ecc_fpga_constants_v128
    #if(load_program(ultrascale, tests_prom_folder + "test_ecc_scalar_multiplication_v" + str(sike_extended_word_size)+ ".dat", sike_base_word_size, 4)):
    if(load_program(ultrascale, tests_prom_folder + "test_sike_sidh_ecc_functions_v" + str(sike_extended_word_size)+ ".dat", sike_base_word_size, 4)):
        print("Program loaded correctly into SIKE core")
        number_of_tests = 100
        error_computation = False
        if(only_one_parameter != None):
            all_testing_parameters = ecc_fpga_constants[only_one_parameter:only_one_parameter+1]
        else:
            all_testing_parameters = ecc_fpga_constants
        for param in all_testing_parameters:
            print("Testing ECC scalar multiplication " +  param[0])
            error_computation = test_scalar_multiplication(ultrascale, param, number_of_tests, debug_mode=False)
            if(error_computation):
                break
    else:
        print('Program loading failed')

ultrascale = ultrascale('COM4', sys.argv[1])
#ultrascale.read_initial_message(34)
while(not ultrascale.isFree()):
    time.sleep(0.01)
ultrascale.flush()

test_all_scalar_multiplication(ultrascale, sys.argv[1])

ultrascale.disconnect()