| // |
| // 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 |
| } |
| } |