| // |
| // 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.Core.Structure.Registers; |
| using Antmicro.Renode.Exceptions; |
| using Antmicro.Renode.Logging; |
| using Antmicro.Renode.Peripherals.Bus; |
| using Antmicro.Renode.Peripherals.CPU; |
| using Antmicro.Renode.Peripherals.Memory; |
| 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, |
| MappedMemory memory, |
| 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); |
| dataMemory = memory; |
| |
| // Touch all the memory segments to ensure all of segments are |
| // allocated with valid pointers. |
| dataMemory.TouchAllSegments(); |
| cpuLibraryRegistered = false; |
| } |
| |
| ~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; |
| } |
| PC = 0; |
| } |
| |
| public void RegisterControlBlock(MlTopControlBlock controlBlock) |
| { |
| this.controlBlock = controlBlock; |
| controlBlockRegistered = true; |
| } |
| |
| private void HandleKelvinEBreak() |
| { |
| if (controlBlockRegistered) |
| { |
| controlBlock.ExecEBreak(); |
| } |
| else |
| { |
| IsHalted = true; |
| } |
| } |
| |
| private void HandleKelvinMPause() |
| { |
| if (controlBlockRegistered) |
| { |
| controlBlock.ExecMPause(); |
| } |
| else |
| { |
| IsHalted = true; |
| } |
| } |
| |
| 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. |
| lock(nativeLock) |
| { |
| destruct(kelvin_id); |
| } |
| } |
| |
| // No-op public API to match RiscVCPU |
| public Int32 PerformanceInMips { get; set; } |
| |
| public override string Architecture { get { return "kelvin"; } } |
| |
| 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 |
| { |
| Int64 value = 0; |
| lock(nativeLock) |
| { |
| 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"); |
| } |
| value = Marshal.ReadInt64(value_ptr); |
| |
| } |
| return Convert.ToUInt64(value); |
| } |
| |
| set |
| { |
| lock(nativeLock) |
| { |
| 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 |
| { |
| lock(nativeLock) |
| { |
| // 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) |
| { |
| this.DebugLog("CPU finish execution."); |
| this.DebugLog("Total instruction count: {0}", |
| totalExecutedInstructions); |
| HandleKelvinMPause(); |
| } |
| |
| if (numberOfInstructionsToExecute > instructionsExecutedThisRound && |
| result == ExecutionResult.Aborted) |
| { |
| this.DebugLog("CPU finish execution."); |
| HandleKelvinEBreak(); |
| } |
| |
| return result; |
| } |
| |
| // ICPUWithRegisters methods implementations. |
| public void SetRegisterUnsafe(int register, RegisterValue value) |
| { |
| lock(nativeLock) |
| { |
| 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) |
| { |
| Int64 value = 0; |
| lock(nativeLock) |
| { |
| var status = read_register(kelvin_id, (UInt32)register, value_ptr); |
| if (status < 0) |
| { |
| LogAndThrowRE("Failed to read register " + register); |
| } |
| value = Marshal.ReadInt64(value_ptr); |
| } |
| return (UInt64)value; |
| } |
| |
| public IEnumerable<CPURegister> GetRegisters() |
| { |
| if (registerMap.Count == 0) |
| { |
| lock(nativeLock) |
| { |
| 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) |
| { |
| Int64 value = 0; |
| lock(nativeLock) |
| { |
| var status = read_register(kelvin_id, (UInt32)reg.Value, value_ptr); |
| if (status < 0) continue; |
| 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)) |
| { |
| if (cpuLibraryRegistered) |
| { |
| this.WarningLog("cpu library already registerd and " + |
| "should not be updated again."); |
| return; |
| } |
| cpuLibraryPath = value; |
| try |
| { |
| binder = new NativeBinder(this, cpuLibraryPath); |
| } |
| catch (System.Exception e) |
| { |
| LogAndThrowRE("Failed to load CPU library: " + e.Message); |
| } |
| cpuLibraryRegistered = true; |
| |
| this.NoisyLog("Memory block size: {0}, total size: {1}", |
| dataMemory.SegmentSize, dataMemory.Size); |
| IntPtr[] memoryBlocks = new IntPtr[dataMemory.SegmentCount]; |
| for (int i = 0; i < dataMemory.SegmentCount; i++) |
| { |
| this.NoisyLog("Segment {0}: pointer {1:8X}, size {2}", |
| i, dataMemory.GetSegment(i), |
| dataMemory.SegmentSize); |
| memoryBlocks[i] = dataMemory.GetSegment(i); |
| } |
| |
| // Simulator initialization code goes here. |
| IntPtr memoryBlocksPtr = Marshal.AllocHGlobal( |
| dataMemory.SegmentCount * Marshal.SizeOf<IntPtr>()); |
| Marshal.Copy(memoryBlocks, 0, memoryBlocksPtr, memoryBlocks.Length); |
| lock(nativeLock) |
| { |
| // Initiate a kelvin_sim simulator. For memory access |
| // inside the kelvin_sim, it refers to the memory block |
| // pointers provided by the dataMemory here. |
| // |
| // Note we don't preempt the memory access from other |
| // cores (e.g., MlCoordinator) with the nativeLock. The |
| // control flow we have in CantripOS should not cause |
| // the conflict memory access. |
| kelvin_id = construct_with_memory(maxStringLen, |
| (ulong)dataMemory.SegmentSize, |
| (ulong)dataMemory.Size, |
| memoryBlocksPtr); |
| } |
| Marshal.FreeHGlobal(memoryBlocksPtr); |
| |
| if (kelvin_id < 0) |
| { |
| LogAndThrowRE("Failed to create simulator instance"); |
| return; |
| } |
| this.NoisyLog("Construct kelvin"); |
| } |
| else |
| { |
| return; |
| } |
| } |
| } |
| |
| private void LogAndThrowRE(string info) |
| { |
| this.Log(LogLevel.Error, info); |
| throw new RecoverableException(info); |
| } |
| |
| protected string cpuLibraryPath; |
| private bool cpuLibraryRegistered; |
| |
| private readonly MappedMemory dataMemory; |
| private NativeBinder binder; |
| // lock to ensure only one binding function call is running. |
| private readonly object nativeLock = new object(); |
| 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; } |
| |
| // Functions from cpuLibrary |
| [Import(UseExceptionWrapper = false)] |
| // Int32(Int32 max_string_lenth, UInt64 mem_block_size, |
| // UInt64 mem_size, uint8_t ** mem_block_ptr_list); |
| private FuncInt32Int32UInt64UInt64IntPtr construct_with_memory; |
| [Import(UseExceptionWrapper = false)] |
| // Int32(Int32 id); |
| private FuncInt32Int32 destruct; |
| [Import(UseExceptionWrapper = false)] |
| // Int32(Int32 id); |
| private FuncInt32Int32 reset; |
| [Import(UseExceptionWrapper = false)] |
| // Int32(Int32 id) |
| private FuncInt32Int32 get_reg_info_size; |
| [Import(UseExceptionWrapper = false)] |
| // Int32(Int32 id, Int32 index, char *name, IntPtr *info); |
| private FuncInt32Int32Int32IntPtrIntPtr get_reg_info; |
| [Import(UseExceptionWrapper = false)] |
| // UInt64(Int32 id, UInt64 step, IntPtr *status); |
| private FuncUInt64Int32UInt64IntPtr step; |
| [Import(UseExceptionWrapper = false)] |
| // Int32(Int32 id, UInt32 reg_id, IntPtr *value); |
| private FuncInt32Int32UInt32IntPtr read_register; |
| [Import(UseExceptionWrapper = false)] |
| // Int32(Int32 id, Int32 reg_id, UInt64 value); |
| private FuncInt32Int32Int32UInt64 write_register; |
| |
| // Register access. |
| private Dictionary<Int32, CPURegister> registerMap = new Dictionary<Int32, CPURegister>(); |
| private Dictionary<string, Int32> registerNamesMap = new Dictionary<string, Int32>(); |
| |
| // PC magic ID. Defined in |
| // https://github.com/riscv/riscv-opcodes/blob/d752f193cec46cfd33300abba2a3a0d020c36755/constants.py#L317 |
| // It is used to set/get PC by passing this register ID via `read_register` |
| // and `write_register`. |
| private const int PC_ID = 0x07b1; |
| |
| private ulong instructionsExecutedThisRound { get; set; } |
| private ulong totalExecutedInstructions { get; set; } |
| |
| private Int32 kelvin_id { get; set; } |
| |
| // MlTopControlBlock |
| private MlTopControlBlock controlBlock; |
| private bool controlBlockRegistered = false; |
| } |
| |
| |
| public class MlTopControlBlock : |
| IDoubleWordPeripheral, |
| IProvidesRegisterCollection<DoubleWordRegisterCollection>, |
| IKnownSize |
| { |
| |
| public MlTopControlBlock(Machine machine, |
| KelvinCPU core) |
| { |
| Machine = machine; |
| Core = core; |
| |
| HostReqIRQ = new GPIO(); |
| FinishIRQ = new GPIO(); |
| InstructionFaultIRQ = new GPIO(); |
| |
| Core.RegisterControlBlock(this); |
| |
| RegistersCollection = new DoubleWordRegisterCollection(this); |
| DefineRegisters(); |
| |
| Reset(); |
| } |
| |
| public void Reset() |
| { |
| mode = Mode.Freeze | Mode.SwReset; |
| RegistersCollection.Reset(); |
| } |
| |
| private void DefineRegisters() |
| { |
| Registers.IntrState.Define32(this) |
| .WithValueField(0, 4, |
| writeCallback: (_, value) => |
| { |
| this.NoisyLog("Got {0} to clear IRQ pending bits", value); |
| irqsPending = irqsPending & ~(InterruptBits)value; |
| IrqUpdate(); |
| }, |
| valueProviderCallback: (_) => |
| { |
| return (uint)irqsPending; |
| }); |
| |
| Registers.IntrEnable.Define32(this) |
| .WithValueField(0, 4, |
| writeCallback: (_, value) => |
| { |
| this.NoisyLog("Got {0} to write IRQ enable bits", value); |
| irqsEnabled = (InterruptBits)value & InterruptBits.Mask; |
| IrqUpdate(); |
| }, |
| valueProviderCallback: (_) => |
| { |
| return (uint)irqsEnabled; |
| }); |
| |
| Registers.IntrTest.Define32(this) |
| .WithValueField(0, 4, |
| writeCallback: (_, value) => |
| { |
| this.NoisyLog("Got {0} to set IRQ pending bits", value); |
| irqsPending |= ((InterruptBits)value & InterruptBits.Mask); |
| IrqUpdate(); |
| }); |
| |
| Registers.Control.Define32(this, resetValue: 0x00000002) |
| .WithValueField(0, 24, name: "FREEZE_RESET_PC", |
| writeCallback: (_, val) => |
| { |
| Mode newMode = (Mode)val & Mode.Mask; |
| // Pause the core when either freeze or swreset is asserted. |
| if ((mode == Mode.Run) && (newMode != Mode.Run)) |
| { |
| this.NoisyLog("Pausing core."); |
| Core.IsHalted = true; |
| } |
| |
| // Trigger the core's reset when SwReset is deasserted. |
| if (((mode & Mode.SwReset) != 0) && |
| ((newMode & Mode.SwReset) == 0)) |
| { |
| this.NoisyLog("Resetting core."); |
| // Reset PC |
| ulong startAddress = (val >> (int)Mode.NumBits); |
| if (Core.PC != startAddress) |
| { |
| this.DebugLog("Setting PC to 0x{0:X8}.", |
| startAddress); |
| Core.PC = startAddress; |
| } |
| Core.Reset(); |
| } |
| |
| // Raise reset bit, but the core will not be |
| // reset until SwReset is deasserted. |
| if ((mode & Mode.SwReset) == 0 && |
| ((newMode & Mode.SwReset) != 0)) |
| { |
| this.NoisyLog("Raise reset bit"); |
| } |
| |
| // Unpause the core when both freeze and |
| // SwReset are deasserted. |
| if ((mode != Mode.Run) && (newMode == Mode.Run)) |
| { |
| this.NoisyLog("Resuming core."); |
| Core.IsHalted = false; |
| |
| Core.Resume(); |
| } |
| |
| this.mode = newMode; |
| }) |
| .WithTaggedFlag("VOLT_SEL", 24) |
| .WithIgnoredBits(25, 32 - 25); |
| |
| // TODO: No implementation to memory bank control. |
| Registers.MemoryBankControl.Define32(this) |
| .WithTag("MEM_ENABLE", 0, 16) |
| .WithIgnoredBits(16, 32 - 16); |
| |
| // TODO: No implementation of memory access error. |
| Registers.ErrorStatus.Define32(this) |
| .WithTaggedFlag("MEM_OUT_OF_RANGE", 0) |
| .WithTag("MEM_DISABLE_ACCESS", 1, 8) |
| .WithIgnoredBits(9, 32 - 9); |
| |
| Registers.InitStart.Define32(this) |
| .WithTag("ADDRESS", 0, 22) |
| .WithIgnoredBits(22, 32 - 22); |
| |
| Registers.InitEnd.Define32(this) |
| .WithTag("ADDRESS", 0, 22) |
| .WithTaggedFlag("VALID", 22) |
| .WithIgnoredBits(23, 32 - 23); |
| |
| Registers.InitStatus.Define32(this) |
| .WithTaggedFlag("INIT_PENDING", 0) |
| .WithTaggedFlag("INIT_DONE", 1) |
| .WithIgnoredBits(2, 32 - 2); |
| |
| } |
| |
| public virtual uint ReadDoubleWord(long offset) |
| { |
| return RegistersCollection.Read(offset); |
| } |
| |
| public virtual void WriteDoubleWord(long offset, uint value) |
| { |
| RegistersCollection.Write(offset, value); |
| } |
| |
| public void ExecMPause() |
| { |
| // Pause and trigger a host interrupt indicating completion |
| if (mode == Mode.Run) |
| { |
| this.NoisyLog("Pausing the core for host completion notification."); |
| } |
| else |
| { |
| this.ErrorLog("Pausing the core for host completion notification, " + |
| "but core was not expected to be running. Did you " + |
| "clear IsHalted manually?"); |
| } |
| Core.IsHalted = true; |
| mode = Mode.Freeze; |
| irqsPending |= InterruptBits.Finish; |
| IrqUpdate(); |
| } |
| |
| public void ExecEBreak() |
| { |
| // Pause and trigger a host interrupt indicating completion with |
| // fault |
| if (mode == Mode.Run) |
| { |
| this.NoisyLog("Core executed ebreak."); |
| } |
| else |
| { |
| this.ErrorLog("Core executed ebreak, but core was not expected " + |
| "to be running. Did you clear IsHalted manually?"); |
| } |
| Core.IsHalted = true; |
| mode = Mode.Freeze; |
| irqsPending |= InterruptBits.Finish | InterruptBits.InstructionFault; |
| IrqUpdate(); |
| } |
| |
| public DoubleWordRegisterCollection RegistersCollection { get; private set; } |
| |
| public GPIO HostReqIRQ { get; } |
| public GPIO FinishIRQ { get; } |
| public GPIO InstructionFaultIRQ { get; } |
| |
| private InterruptBits irqsEnabled; |
| private InterruptBits irqsPending; |
| |
| private void IrqUpdate() |
| { |
| InterruptBits irqsPassed = irqsEnabled & irqsPending; |
| HostReqIRQ.Set((irqsPassed & InterruptBits.HostReq) != 0); |
| FinishIRQ.Set((irqsPassed & InterruptBits.Finish) != 0); |
| InstructionFaultIRQ.Set((irqsPassed & InterruptBits.InstructionFault) != 0); |
| } |
| |
| private Mode mode; |
| private readonly Machine Machine; |
| private readonly KelvinCPU Core; |
| // Length of register space. Required by IKnowSize. |
| public long Size => 0x1000; |
| private enum Registers |
| { |
| IntrState = 0x00, |
| IntrEnable = 0x04, |
| IntrTest = 0x08, |
| Control = 0x0C, |
| MemoryBankControl = 0x10, |
| ErrorStatus = 0x14, |
| InitStart = 0x18, |
| InitEnd = 0x1C, |
| InitStatus = 0x20, |
| }; |
| [Flags] |
| private enum Mode |
| { |
| Run = 0x00, |
| Freeze = 0x01, |
| SwReset = 0x02, |
| Mask = 0x03, |
| NumBits = 2, |
| }; |
| [Flags] |
| private enum InterruptBits |
| { |
| HostReq = 1, |
| Finish = 2, |
| InstructionFault = 4, |
| Mask = 7, |
| }; |
| } |
| |
| } |