blob: 81c2f322b71243db5870c3b89e5809fc950e2c53 [file] [log] [blame]
//
// 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 uint 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");
}
}
}
}
public 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, this));
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 SetRegister(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 GetRegister(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 void SetRegisterUnsafe(int register, RegisterValue value)
{
// This is obsolete API, left here only for compatibility
this.Log(LogLevel.Warning, "Using `SetRegisterUnsafe` API is " +
"obsolete. Please change to `SetRegister`.");
SetRegister(register, value);
}
public RegisterValue GetRegisterUnsafe(int register)
{
// This is obsolete API, left here only for compatibility
this.Log(LogLevel.Warning, "Using `GetRegisterUnsafe` API is " +
"obsolete. Please change to `GetRegister`.");
return GetRegister(register);
}
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,
};
}
}