Integrate kelvin_sim to Renode framework (Step 1)
This is the first step of using kelvin_sim as an external CPU in the
Renode framework.
As the first step, the KelvinCPU module can load the .bin file (same
blob format for FPGA and cantrip), run the program with
`sysbus.cpu2 IsHalted false`, and shows the program return. The control
block (ml_top registers and IRQs) will be added in the followup CLs.
Also, the 0x5A000000 ml_dmem is not used by kelvin_sim yet, so we can't
get the data out of the memory with the current MlCooridinator
functions. It will also be addressed in future CLs.
Change-Id: I6e127f581615cda4e625f26ecb1a81ec4721974b
diff --git a/kelvin_external_cpu.resc b/kelvin_external_cpu.resc
new file mode 100644
index 0000000..0f78c60
--- /dev/null
+++ b/kelvin_external_cpu.resc
@@ -0,0 +1,34 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Renode script for testing the Kelvin core using kelvin_sim external CPU library
+
+
+mach create "kelvin"
+
+include @sim/config/shodan_infrastructure/KelvinCPU.cs
+EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.KelvinCPU"
+
+$platformfile?=@sim/config/platforms/kelvin_ml_core_external_cpu.repl
+
+machine LoadPlatformDescription $platformfile
+
+$bin?=@out/kelvin/sw/bazel_out/hello_world.bin
+$cpuLibrary?=@out/kelvin/sim/librenode_kelvin.so
+
+sysbus.cpu2 CpuLibraryPath $cpuLibrary
+sysbus.cpu2 ExecutableFile $bin
+sysbus.cpu2 MemoryOffset 0
+
+logLevel -1 sysbus.cpu2
diff --git a/platforms/kelvin_ml_core_external_cpu.repl b/platforms/kelvin_ml_core_external_cpu.repl
new file mode 100644
index 0000000..f83ecdd
--- /dev/null
+++ b/platforms/kelvin_ml_core_external_cpu.repl
@@ -0,0 +1,21 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ***************************************************
+// ML Core that uses kelvin_sim as the external CPU library. WIP
+// ***************************************************
+
+cpu2: CPU.KelvinCPU @ sysbus
+ id: 1
+ cpuType: "Kelvin"
diff --git a/shodan_infrastructure/KelvinCPU.cs b/shodan_infrastructure/KelvinCPU.cs
new file mode 100644
index 0000000..b136900
--- /dev/null
+++ b/shodan_infrastructure/KelvinCPU.cs
@@ -0,0 +1,410 @@
+//
+// Copyright (c) 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Exceptions;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Peripherals.Bus;
+using Antmicro.Renode.Peripherals.CPU;
+using Antmicro.Renode.Peripherals.Timers;
+using Antmicro.Renode.Peripherals.CPU.Disassembler;
+using Antmicro.Renode.Peripherals.CPU.Registers;
+using Antmicro.Renode.Utilities;
+using Antmicro.Renode.Time;
+using Antmicro.Renode.Utilities.Binding;
+using ELFSharp.ELF;
+using ELFSharp.UImage;
+using Machine = Antmicro.Renode.Core.Machine;
+
+
+namespace Antmicro.Renode.Peripherals.CPU
+{
+
+ public class KelvinCPU : BaseCPU, ICPUWithRegisters, ITimeSink, IDisposable
+ {
+ [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Pack = 1)]
+ private struct RegInfo
+ {
+ [FieldOffset(0)]
+ public Int32 index;
+ [FieldOffset(4)]
+ public Int32 width;
+ [FieldOffset(8)]
+ public bool isGeneral;
+ [FieldOffset(9)]
+ public bool isReadOnly;
+ }
+
+ public KelvinCPU(uint id, string cpuType, Machine machine,
+ Endianess endianess = Endianess.LittleEndian,
+ CpuBitness bitness = CpuBitness.Bits32)
+ : base(id, cpuType, machine, endianess, bitness)
+ {
+ error_ptr = Marshal.AllocHGlobal(4);
+ value_ptr = Marshal.AllocHGlobal(8);
+ reg_info_ptr = Marshal.AllocHGlobal(Marshal.SizeOf<RegInfo>());
+ string_ptr = Marshal.AllocHGlobal(maxStringLen);
+ memoryOffset = 0;
+ }
+
+ ~KelvinCPU()
+ {
+ Marshal.FreeHGlobal(error_ptr);
+ Marshal.FreeHGlobal(value_ptr);
+ Marshal.FreeHGlobal(reg_info_ptr);
+ Marshal.FreeHGlobal(string_ptr);
+ }
+
+ public override void Start()
+ {
+ base.Start();
+ if (kelvin_id < 0)
+ {
+ LogAndThrowRE("Failed to create kelvin sim instance");
+ return;
+ }
+ Int32 result = load_image(kelvin_id, executableFile, memoryOffset);
+ if (result < 0)
+ {
+ LogAndThrowRE("Failed to load executable image");
+ }
+ this.NoisyLog("Load executable image " + executableFile);
+
+ // Start with halted CPU
+ IsHalted = true;
+ PC = 0;
+ }
+
+ public override void Reset()
+ {
+ base.Reset();
+ instructionsExecutedThisRound = 0;
+ totalExecutedInstructions = 0;
+ // Reset simulator.
+ var result = reset(kelvin_id);
+ if (result < 0)
+ {
+ LogAndThrowRE("Failed to reset kelvin");
+ }
+ this.NoisyLog("Reset kelvin");
+ }
+
+ public override void Dispose()
+ {
+ base.Dispose();
+ // Cleanup: simulator and any unmanaged resources.
+ destruct(kelvin_id);
+ }
+
+ public override ulong ExecutedInstructions => totalExecutedInstructions;
+
+ public override ExecutionMode ExecutionMode
+ {
+ get
+ {
+ return executionMode;
+ }
+ set
+ {
+ lock (singleStepSynchronizer.Guard)
+ {
+ if (executionMode == value)
+ {
+ return;
+ }
+
+ executionMode = value;
+
+ singleStepSynchronizer.Enabled = IsSingleStepMode;
+ UpdateHaltedState();
+ }
+ }
+ }
+
+ [Register]
+ public override RegisterValue PC
+ {
+ get
+ {
+ Int32 error = read_register(kelvin_id, PC_ID, value_ptr);
+ // TODO(hcindyl): Check different error types.
+ if (error < 0)
+ {
+ this.ErrorLog("Failed to read PC");
+ }
+ Int64 value = Marshal.ReadInt64(value_ptr);
+ return Convert.ToUInt64(value);
+ }
+
+ set
+ {
+ Int32 error = write_register(kelvin_id, PC_ID, value);
+ // TODO(hcindyl): Check different error types.
+ if (error < 0)
+ {
+ this.NoisyLog("Failed to write PC");
+ }
+ }
+ }
+
+ protected override ExecutionResult ExecuteInstructions(ulong numberOfInstructionsToExecute,
+ out ulong numberOfExecutedInstructions)
+ {
+ UInt64 instructionsExecutedThisRound = 0UL;
+ ExecutionResult result = ExecutionResult.Ok;
+ try
+ {
+ // Invoke simulator for the number of instructions.
+ instructionsExecutedThisRound = step(kelvin_id, numberOfInstructionsToExecute, error_ptr);
+ // Parse the result.
+ Int32 step_result = Marshal.ReadInt32(error_ptr);
+ switch (step_result)
+ {
+ case -1:
+ result = ExecutionResult.Aborted;
+ break;
+ case 0:
+ result = ExecutionResult.Ok;
+ break;
+ case 1:
+ result = ExecutionResult.Interrupted;
+ break;
+ case 2:
+ result = ExecutionResult.WaitingForInterrupt;
+ break;
+ case 3:
+ result = ExecutionResult.StoppedAtBreakpoint;
+ break;
+ case 4:
+ result = ExecutionResult.StoppedAtWatchpoint;
+ break;
+ case 5:
+ result = ExecutionResult.ExternalMmuFault;
+ break;
+ default:
+ LogAndThrowRE("Unknown return value from step - " + step_result);
+ break;
+ }
+ }
+ catch (Exception)
+ {
+ this.NoisyLog("CPU exception detected, halting.");
+ InvokeHalted(new HaltArguments(HaltReason.Abort, Id));
+ return ExecutionResult.Aborted;
+ }
+ finally
+ {
+ numberOfExecutedInstructions = instructionsExecutedThisRound;
+ totalExecutedInstructions += instructionsExecutedThisRound;
+ }
+
+ if (numberOfInstructionsToExecute > instructionsExecutedThisRound &&
+ result == ExecutionResult.Ok)
+ {
+ // TODO(hcindyl): Change to control block callbacks.
+ this.NoisyLog("CPU finish excution.");
+ this.NoisyLog("Total instruction count: {0}",
+ totalExecutedInstructions);
+ IsHalted = true;
+ }
+
+ return result;
+ }
+
+ // ICPUWithRegisters methods implementations.
+ public void SetRegisterUnsafe(int register, RegisterValue value)
+ {
+ var status = write_register((Int32)kelvin_id, (Int32)register, (UInt64)value);
+ if (status < 0)
+ {
+ LogAndThrowRE("Failed to write register " + register);
+ }
+ }
+
+ public RegisterValue GetRegisterUnsafe(int register)
+ {
+ var status = read_register(kelvin_id, (UInt32)register, value_ptr);
+ if (status < 0)
+ {
+ LogAndThrowRE("Failed to read register " + register);
+ }
+ Int64 value = Marshal.ReadInt64(value_ptr);
+ return (UInt64)value;
+ }
+
+ public IEnumerable<CPURegister> GetRegisters()
+ {
+ if (registerMap.Count == 0)
+ {
+ Int32 num_regs = get_reg_info_size(kelvin_id);
+ for (Int32 i = 0; i < num_regs; i++)
+ {
+ Int32 result = get_reg_info(kelvin_id, i, string_ptr, reg_info_ptr);
+ if (result < 0)
+ {
+ this.ErrorLog("Failed to get register info for index " + i);
+ continue;
+ }
+ var reg_info = Marshal.PtrToStructure<RegInfo>(reg_info_ptr);
+ var cpu_reg = new CPURegister(reg_info.index, reg_info.width, reg_info.isGeneral, reg_info.isReadOnly);
+ var reg_name = Marshal.PtrToStringAuto(string_ptr);
+ registerMap.Add(reg_info.index, cpu_reg);
+ registerNamesMap.Add(reg_name, reg_info.index);
+ }
+ }
+ return registerMap.Values.OrderBy(x => x.Index);
+ }
+
+ public string[,] GetRegistersValues()
+ {
+ if (registerMap.Count == 0)
+ {
+ GetRegisters();
+ }
+ var result = new Dictionary<string, ulong>();
+ foreach (var reg in registerNamesMap)
+ {
+ var status = read_register(kelvin_id, (UInt32)reg.Value, value_ptr);
+ if (status < 0) continue;
+ Int64 value = Marshal.ReadInt64(value_ptr);
+ result.Add(reg.Key, Convert.ToUInt64(value));
+ }
+ var table = new Table().AddRow("Name", "Value");
+ table.AddRows(result, x => x.Key, x => "0x{0:X}".FormatWith(x.Value));
+ return table.ToArray();
+ }
+
+ public string CpuLibraryPath
+ {
+ get
+ {
+ return cpuLibraryPath;
+ }
+ set
+ {
+ if (!String.IsNullOrWhiteSpace(value))
+ {
+ cpuLibraryPath = value;
+ try
+ {
+ binder = new NativeBinder(this, cpuLibraryPath);
+ }
+ catch (System.Exception e)
+ {
+ LogAndThrowRE("Failed to load CPU library: " + e.Message);
+ }
+
+ // Simulator initialization code goes here.
+ kelvin_id = construct(maxStringLen);
+ if (kelvin_id < 0)
+ {
+ LogAndThrowRE("Failed to create simulator instance");
+ return;
+ }
+ this.NoisyLog("Construct kelvin");
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+
+ public string ExecutableFile
+ {
+ get
+ {
+ return executableFile;
+ }
+ set
+ {
+ if (!String.IsNullOrWhiteSpace(value))
+ {
+ executableFile = value;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+
+ public UInt64 MemoryOffset
+ {
+ get
+ {
+ return memoryOffset;
+ }
+ set
+ {
+
+ memoryOffset = value;
+
+ }
+ }
+
+ private void LogAndThrowRE(string info)
+ {
+ this.Log(LogLevel.Error, info);
+ throw new RecoverableException(info);
+ }
+
+ private Dictionary<Int32, CPURegister> registerMap = new Dictionary<Int32, CPURegister>();
+ private Dictionary<string, Int32> registerNamesMap = new Dictionary<string, Int32>();
+
+ protected string cpuLibraryPath;
+ protected string executableFile;
+
+ protected UInt64 memoryOffset;
+ private NativeBinder binder;
+ private readonly object nativeLock;
+ private readonly Int32 maxStringLen = 32;
+ private IntPtr value_ptr { get; set; }
+ private IntPtr error_ptr { get; set; }
+ private IntPtr reg_info_ptr { get; set; }
+ private IntPtr string_ptr { get; set; }
+#pragma warning disable 649
+ [Import(UseExceptionWrapper = false)]
+ private FuncInt32Int32 construct; // Int32(Int32 max_string_lenth)
+ [Import(UseExceptionWrapper = false)]
+ private FuncInt32Int32 destruct; // Int32(Int32 id);
+ [Import(UseExceptionWrapper = false)]
+ private FuncInt32Int32 reset; // Int32(Int32 id);
+ [Import(UseExceptionWrapper = false)]
+ private FuncInt32Int32 get_reg_info_size; // Int32(Int32 id)
+ [Import(UseExceptionWrapper = false)]
+ private FuncInt32Int32Int32IntPtrIntPtr get_reg_info; // Int32(Int32 id, Int32 index, char *name, IntPtr *info);
+ [Import(UseExceptionWrapper = false)]
+ private FuncInt32Int32StringUInt64 load_image; // Int32(Int32 id, String file_name, Int64 address);
+ [Import(UseExceptionWrapper = false)]
+ private FuncUInt64Int32UInt64IntPtr step; // UInt64(Int32 id, UInt64 step, IntPtr *status);
+ [Import(UseExceptionWrapper = false)]
+ private FuncInt32Int32UInt32IntPtr read_register; // Int32(Int32 id, UInt32 reg_id, IntPtr *value);
+ [Import(UseExceptionWrapper = false)]
+ private FuncInt32Int32Int32UInt64 write_register; // Int32(Int32 id, Int32 reg_id, UInt64 value);
+#pragma warning restore 649
+
+ private Int32 kelvin_id { get; set; }
+ private ulong instructionsExecutedThisRound { get; set; }
+ private ulong totalExecutedInstructions { get; set; }
+ private const int PC_ID = 0x07b1; // PC magic ID
+ }
+}