diff --git a/src/entrypoints/Main/ExperimentHIL.py b/src/entrypoints/Main/ExperimentHIL.py
new file mode 100644
index 0000000000000000000000000000000000000000..adeb734810be57c0a942e9f0c96bae40646d3d36
--- /dev/null
+++ b/src/entrypoints/Main/ExperimentHIL.py
@@ -0,0 +1,344 @@
+from pwnlib import *
+from pwn import *
+from time import *
+from enum import Enum
+from intervaltree import IntervalTree
+import random as rn
+import pickle
+import os
+import subprocess as sp
+import threading
+
+maxInjectionTime = 15
+maxTestTime = 90
+nExperiments = 500
+
+cpu = [] # stores the cpu mean busy time and stdDev
+criticalFailure = ''
+apogee = 0
+
+GDB_PID = 0
+GDB_PORT = 0
+elfPath = ""
+experimentBase = ""
+
+injectable_addresses = IntervalTree()
+
+MatlabSimulatorFolder = "/home/streben/Documenti/SKYWARD/Repo/matlab-simulator-hil/simulator/"
+
+class FailureMode(Enum):
+    """
+        > 0 = golden_run
+        > 1 = undetected but crc wrong
+        > 2 = silent data corruption (undetected and crc correct)
+        > 3 = data corruption handler
+        > 4 = signature mismatch handler
+        > 5 = hardware fault (reboot)
+        > 6 = infinite loop
+        > 7 = other reason
+    """
+    GOLDEN = 0,
+    DATA_CORRUPTED = 1,
+    SILENT_CORRUPTION = 2,
+    DATA_HANDLER = 3,
+    SIGNATURE_HANDLER = 4,
+    HARDWARE_FAULT = 5,
+    INFINITE_LOOP = 6,
+    OTHER = 7
+
+def fromFailurModeEnumToStr(FM):
+    if FM == FailureMode.GOLDEN:
+        return "GOLDEN"
+    elif FM == FailureMode.DATA_CORRUPTED:
+        return "DATA_CORRUPTED"
+    elif FM == FailureMode.SILENT_CORRUPTION:
+        return "SILENT_CORRUPTION"
+    elif FM == FailureMode.DATA_HANDLER:
+        return "DATA_HANDLER"
+    elif FM == FailureMode.SIGNATURE_HANDLER:
+        return "SIGNATURE_HANDLER"
+    elif FM == FailureMode.HARDWARE_FAULT:
+        return "HARDWARE_FAULT"
+    elif FM == FailureMode.INFINITE_LOOP:
+        return "INFINITE_LOOP"
+    elif FM == FailureMode.OTHER:
+        return "OTHER"
+    else:
+        return "Invalid FailurMode enum!"
+
+
+def setup(s):
+    s.sendline(b"delete") # delete all breakpoints
+    s.sendline(b"b waitActuatorData") # breakpoint for injection
+    s.recvuntil(b"(gdb)")
+    s.sendline(b"b simulationEnd") # breakpoint for end of experiment (cutters actuated)
+    s.sendline(b"monitor reset halt")
+    s.recvuntil(b"(gdb)")
+    s.sendline(b"b DataCorruption_Handler") # breakpoint for fault handlers
+    s.clean()
+    s.sendline(b"b SigMismatch_Handler") # breakpoint for fault handlers
+    s.recvuntil(b"(gdb)")
+    sleep(0.5)
+    s.clean()
+
+
+def setupAfterInjection(s):
+    s.sendline(b"delete") # delete all breakpoints
+    s.recvuntil(b"(gdb)")
+    s.sendline(b"b main") # breakpoint for end of experiment (cutters actuated)
+    s.sendline(b"b simulationEnd") # breakpoint for end of experiment (cutters actuated)
+    s.recvuntil(b"(gdb)")
+    s.sendline(b"b DataCorruption_Handler") # breakpoint for fault handlers
+    s.sendline(b"b SigMismatch_Handler") # breakpoint for fault handlers
+    s.recvuntil(b"(gdb)")
+    s.clean()
+
+
+def save_run(name, result, injection, cpu):
+    """
+    Saves in a file the run:
+    - seed: seed number
+    - name: file path where to save the structure
+    - result: result of the experiment
+    - run_<bench>: list of couples (time, crc) of each cycle
+    """
+    data = {
+        "result": result,
+        "injection": injection,
+        "cpu" : cpu
+    }
+    with open(name+".pkl", 'wb') as f:
+        pickle.dump(data, f)
+
+
+def recover(s):
+    os.system(f"kill -2 {GDB_PID}")
+    s.recvuntil(b"(gdb)")
+    s.sendline(b"monitor reset halt")
+    s.recvuntil(b"(gdb)")
+    s.clean()
+    
+
+def inject(s, ada_start, ada_size):
+    # os.system(f"kill -2 {GDB_PID}")
+    
+    # Extract SEU in ada
+    mem = hex(rn.randrange(start=ada_start, stop=(ada_start + ada_size), step=4))
+
+    # Extract value
+    value = 0b1<<rn.randint(0,31)
+
+    s.recvuntil(b"(gdb)")
+    s.sendline(f"set *{mem}= *{mem}^{value}".encode())
+    s.recvuntil(b"(gdb)")
+    setupAfterInjection(s)
+    s.sendline(b"continue")
+    s.recvuntil(b"Continuing.")
+    return mem
+
+
+def simulatorThread():
+    global liftoff
+    global criticalFailure
+    global apogee
+    global cpu
+    liftoff = False
+    criticalFailure = ""
+    apogee = 0
+    cpu = []
+
+    try:
+        simu = process(["matlab", "-sd", MatlabSimulatorFolder, "-batch", "mainSimulator"])
+        line = simu.recvline(timeout=maxTestTime)
+        while line:
+            # get cpu data only from liftoff to apogee
+            if line.find(b"cpu:") != -1 and liftoff and apogee == 0:
+                split = line.split()
+                cpu.append(tuple([split[1], split[3]]))
+            elif line.find(b"Liftoff:") != -1:
+                liftoff = True
+            elif line.find(b"CRITICAL FAILURE:") != -1:
+                criticalFailure = line
+                print(line)
+                break
+            elif line.find(b"apogee: ") != -1:
+                apogee = float(line.split()[1])
+                break
+            # else:
+            #     print(line)
+            line = simu.recvline(timeout=maxTestTime)
+    except Exception:
+        pass
+
+    os.system(f"kill -2 {GDB_PID}")
+
+
+
+liftoff = False
+criticalFailure = ""
+apogee = 0
+cpu = []
+
+def makeExperiments(nExperiments, folderName, needToFlash):
+    global liftoff
+    global criticalFailure
+    global apogee
+    global cpu
+
+    # Extract paths for all needed 
+
+    elfPath = folderName + "/" + "main-entry.elf"
+    openOCDPath = folderName + "/" + "openocd.cfg"
+    experimentBase = folderName + "/" + folderName
+
+    BOARD_SERIAL = ""
+    with open(openOCDPath, 'r') as f:
+        for line in f:
+            if line.find("gdb_port") != -1:
+                print(line)
+                GDB_PORT = int(line.split()[1])
+                print("GDB_PORT: ", GDB_PORT)
+            elif line.find("hla_serial") != -1:
+                BOARD_SERIAL = line.split()[1]
+                print("BOARD_SERIAL: ", BOARD_SERIAL)
+
+    if(GDB_PORT == 0):
+        print("Port of gdbserver not found!")
+        exit(-1)
+
+    if(BOARD_SERIAL == ""):
+        print("Board serial not found!")
+        exit(-1)
+
+    files = next(os.walk(folderName))[2]
+    numbers = [int(f[len(folderName):-4].split('_')[0]) for f in files if f.startswith(folderName)]
+    
+    starting = 0
+    if(len(numbers) > 0):
+        starting = max(numbers)+1
+
+    # Reflash the boards if needed
+    if(needToFlash and starting < nExperiments):
+        print(f"Flashing {folderName} ({BOARD_SERIAL})")
+        os.system("pkill -SIGKILL openocd")
+        os.system("pkill -SIGKILL gdb")
+        if os.system(f"st-flash --serial {BOARD_SERIAL} write {folderName}/main-entry.bin 0x8000000 && st-flash --serial {BOARD_SERIAL} reset") != 0:
+            print("Couldn't flash!")
+            exit()
+
+    # Symbols definition
+    elf = ELF(elfPath)
+    ada_start = elf.symbols['ada']
+    ada_size = 272 # bytes
+
+    for i in range(starting, nExperiments):
+
+        result = FailureMode.OTHER
+
+        while(result == FailureMode.OTHER):
+
+            recover = False
+            os.system("pkill -SIGKILL openocd")
+            os.system("pkill -SIGKILL gdb")
+
+            os.system(f"st-flash --serial {BOARD_SERIAL} reset")
+            os.system(f"openocd -f {openOCDPath} --debug=0 > /dev/null 2> /dev/null &")
+            
+            s = process(["gdb", elfPath])
+            GDB_PID = s.proc.pid
+            print(f"gdb pid: {GDB_PID}")
+            s.sendline(f"target extended-remote :{GDB_PORT}".encode())
+            s.sendline(b"set style enabled off")
+            s.recvuntil(b"(gdb)")
+            sleep(0.5)
+            s.clean()
+
+            experimentFile = experimentBase + str(i)
+
+            print(f"{i+1}/{nExperiments} {experimentFile}", end="\t")
+            setup(s)
+
+            # Setup injection
+            s.sendline(b"continue")
+            if s.recvuntil(b"Continuing.", timeout=maxTestTime) == b'':
+                print("RECOVERING")
+                recover = True
+                continue
+            
+            simulator = threading.Thread(target=simulatorThread)
+            simulator.start()
+
+            while not liftoff:
+                s.sendline(b"continue")
+                if s.recvuntil(b"Continuing.", timeout=maxTestTime) == b'':
+                    print("RECOVERING")
+                    recover = True
+                    break
+                continue
+
+            if(recover):
+                continue
+
+            # Injecting after random amount of time (between 0 and 1.5 seconds)
+            injTime = rn.random() * maxInjectionTime
+            
+            # injection
+            injTs = time() + 5 + injTime
+            while time() < injTs:
+                s.sendline(b"continue")
+                if s.recvuntil(b"Continuing.", timeout=maxTestTime) == b'':
+                    print("RECOVERING")
+                    recover = True
+                    break
+            
+            if(recover):
+                continue
+
+            injection = inject(s, ada_start, ada_size)
+
+            # Here we check where we end up (endExperiment, fault handlers, start of main due to reboot, stuck in an infinite loop)
+            msg = s.recvuntil(b"(gdb)", timeout=maxTestTime)
+            s.sendline(b"continue")
+
+            simulator.join()
+
+            if criticalFailure != "":
+                result = FailureMode.DATA_CORRUPTED
+                criticalFailure = ""
+            elif(msg == b""):
+                result = FailureMode.INFINITE_LOOP
+                # recover(s)
+            elif msg.find(b"DataCorruption_Handler") != -1:
+                result = FailureMode.DATA_HANDLER
+            elif msg.find(b"SigMismatch_Handler") != -1:
+                result = FailureMode.SIGNATURE_HANDLER
+            elif msg.find(b"simulationEnd") != -1:
+                # Check results since the test reached its end
+                result = FailureMode.SILENT_CORRUPTION
+            elif msg.find(b"main") != -1:
+                result = FailureMode.HARDWARE_FAULT
+            else:
+                print("Didn't recognized this result: ")
+                print(msg)
+                result = FailureMode.OTHER
+
+            # print(check_results(), end="\t")
+            print(fromFailurModeEnumToStr(result), end="\t")
+            print(injection)
+
+            if(result != FailureMode.OTHER):
+                save_run(experimentFile, result, injection, cpu)
+
+
+    # server.close()
+
+"""
+    argv:
+    - 0: program name
+    - 1: Folder for openocd.cfg, main-entry.elf and main-entry.bin 
+"""
+if __name__ == "__main__":
+    context.terminal = ['bash', 'splitw', '-v']
+
+    makeExperiments(nExperiments, "reddi", True)
+    makeExperiments(nExperiments, "noProtection", True)
diff --git a/src/entrypoints/Main/PostprocessHIL.py b/src/entrypoints/Main/PostprocessHIL.py
new file mode 100644
index 0000000000000000000000000000000000000000..f689f51b5411037c403c0cf1be2fdaa77137f041
--- /dev/null
+++ b/src/entrypoints/Main/PostprocessHIL.py
@@ -0,0 +1,110 @@
+from pwnlib import *
+from pwn import *
+from time import *
+from enum import Enum
+from intervaltree import IntervalTree
+from statistics import mean, stdev
+import numpy as np
+import random as rn
+import pickle
+import os
+import subprocess as sp
+import matplotlib.pyplot as plt
+
+baseFolder = "."
+
+# Log IP, address/register injected and value injected
+
+class FailureMode(Enum):
+    """
+        > 0 = golden_run
+        > 1 = undetected but crc wrong
+        > 2 = silent data corruption (undetected and crc correct)
+        > 3 = data corruption handler
+        > 4 = signature mismatch handler
+        > 5 = hardware fault (reboot)
+        > 6 = infinite loop
+        > 7 = other reason
+    """
+    GOLDEN = 0,
+    DATA_CORRUPTED = 1,
+    SILENT_CORRUPTION = 2,
+    DATA_HANDLER = 3,
+    SIGNATURE_HANDLER = 4,
+    HARDWARE_FAULT = 5,
+    INFINITE_LOOP = 6,
+    OTHER = 7
+
+def fromFailurModeEnumToStr(FM):
+    if FM == FailureMode.GOLDEN:
+        return "GOLDEN"
+    elif FM == FailureMode.DATA_CORRUPTED:
+        return "DATA_CORRUPTED"
+    elif FM == FailureMode.SILENT_CORRUPTION:
+        return "SILENT_CORRUPTION"
+    elif FM == FailureMode.DATA_HANDLER:
+        return "DATA_HANDLER"
+    elif FM == FailureMode.SIGNATURE_HANDLER:
+        return "SIGNATURE_HANDLER"
+    elif FM == FailureMode.HARDWARE_FAULT:
+        return "HARDWARE_FAULT"
+    elif FM == FailureMode.INFINITE_LOOP:
+        return "INFINITE_LOOP"
+    elif FM == FailureMode.OTHER:
+        return "OTHER"
+    else:
+        return "Invalid FailurMode enum!"
+
+
+def postprocess_runs(folder):
+    """
+    Loads in a structure the previous run with given seed and name:
+    - name: the name of the file
+    - axs: axs[0, 0] acas;  axs[0, 1] jpeg;   axs[0, 2] latnav;  axs[1, 0] ann;    axs[1, 1] canny;    axs[1, 2] scaling
+
+    Returns
+    - list of the runs of different benchmarks (time, crc)
+    - result of the experiment as a FailureMode enum
+    """
+    files = next(os.walk(folder))[2]
+    dataFile = [f for f in files if f.endswith(".pkl")]
+    
+    outcomes = {
+        "GOLDEN" : 0,
+        "DATA_CORRUPTED" : 0,
+        "SILENT_CORRUPTION" : 0,
+        "DATA_HANDLER" : 0,
+        "SIGNATURE_HANDLER" : 0,
+        "HARDWARE_FAULT" : 0,
+        "INFINITE_LOOP" : 0,
+        "OTHER" : 0,
+    }
+
+    nFile = 0
+
+    cpuM = []
+    cpuV = []
+    for file in dataFile:
+        nFile += 1
+
+        print(f"{nFile}/{len(dataFile)}", end='\r')
+        with open(folder + "/" + file, 'rb') as f:
+            data = pickle.load(f) 
+            
+            outcomes[fromFailurModeEnumToStr(data["result"])] += 1
+            if(data["result"] == FailureMode.SILENT_CORRUPTION):
+                cpuM.append(mean([float(c[0]) for c in data["cpu"] ]))
+                cpuV.append(mean([float(c[1]) for c in data["cpu"] ]))
+
+    print(folder)
+    print(outcomes)
+    print(mean(cpuM), mean(cpuV))
+    plt.hist(cpuM, bins='auto', label=folder)
+
+
+if __name__ == "__main__":
+    postprocess_runs(f"{baseFolder}/noProtection")
+    postprocess_runs(f"{baseFolder}/reddi")
+    plt.legend(loc='upper right')
+    plt.show()
+