Add Kelvin core definition
This new core supports Kelvin's MPAUSE instruction, but is otherwise
a standard RV32IM core. In particular, Kelvin's SIMD extension is NOT
supported.
The top-level control register for reset, halt and initial PC should
function as expected. The interrupt management control registers are
also implemented. All of the other control registers are placeholders.
Change-Id: I537873eb3f2d01fb37d38c96f9016b65617677ce
diff --git a/kelvin.resc b/kelvin.resc
new file mode 100644
index 0000000..b3ee710
--- /dev/null
+++ b/kelvin.resc
@@ -0,0 +1,38 @@
+# Copyright 2022 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 Vector Core
+
+mach create "kelvin"
+
+EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.RiscV32"
+include @sim/config/shodan_infrastructure/KelvinRiscV32.cs
+EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.KelvinRiscV32"
+EnsureTypeIsLoaded "Antmicro.Renode.Peripherals.CPU.KelvinRiscV32_ControlBlock"
+$platformfile?=@sim/config/platforms/kelvin.repl
+
+machine LoadPlatformDescription $platformfile
+
+$bin?=@out/kelvin/sw/bazel_out/hello_world.bin
+
+sysbus.cpu2 EnableRiscvOpcodesCounting
+
+macro reset
+"""
+ sysbus LoadBIN $bin 0x5A000000
+ # Start the vector core at address 0 of its TCM.
+ sysbus.cpu2 IsHalted true
+ sysbus.cpu2 PC 0x5A000000
+"""
+runMacro $reset
diff --git a/platforms/kelvin.repl b/platforms/kelvin.repl
new file mode 100644
index 0000000..0aa30ae
--- /dev/null
+++ b/platforms/kelvin.repl
@@ -0,0 +1,23 @@
+// Copyright 2022 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.
+
+// ***************************************************
+// Kelvin single test core
+// ***************************************************
+
+using "sim/config/platforms/ml_core.repl"
+
+vec_controlblock : CPU.KelvinRiscV32_ControlBlock @ sysbus 0x5C000000
+ core: cpu2
+ memoryRangeStart: 0x5A000000
diff --git a/shodan_infrastructure/KelvinRiscV32.cs b/shodan_infrastructure/KelvinRiscV32.cs
new file mode 100644
index 0000000..1b7772d
--- /dev/null
+++ b/shodan_infrastructure/KelvinRiscV32.cs
@@ -0,0 +1,357 @@
+//
+// Copyright (c) 2021 Google LLC
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+using System;
+using System.Linq;
+using System.Text;
+
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Core.Structure.Registers;
+using Antmicro.Renode.Exceptions;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Peripherals.Miscellaneous;
+using Antmicro.Renode.Peripherals.Bus;
+using Antmicro.Renode.Utilities;
+using Antmicro.Renode.Debugging;
+
+using Endianess = ELFSharp.ELF.Endianess;
+
+namespace Antmicro.Renode.Peripherals.CPU
+{
+ public class KelvinRiscV32 : RiscV32
+ {
+ public KelvinRiscV32(Core.Machine machine,
+ uint hartId = 0,
+ PrivilegeArchitecture privilegeArchitecture = PrivilegeArchitecture.Priv1_11,
+ Endianess endianness = Endianess.LittleEndian,
+ string cpuType = "rv32im")
+ : base(null, cpuType, machine, hartId, privilegeArchitecture, endianness)
+ {
+ InstallCustomInstruction(pattern: "00001000000000000000000001110011", handler: HandleKelvinMPause);
+ InstallCustomInstruction(pattern: "00000000000100000000000001110011", handler: HandleKelvinEBreak); // Kelvin doesn't implement rv32i ebreak correctly
+
+ Reset();
+ }
+
+ public override void Reset()
+ {
+ base.Reset();
+
+ // This core comes out of reset paused.
+ this.IsHalted = true;
+
+ if(ControlBlockRegistered)
+ {
+ ControlBlock.Reset();
+ }
+ }
+
+ public void RegisterControlBlock(KelvinRiscV32_ControlBlock controlBlock)
+ {
+ ControlBlock = controlBlock;
+ ControlBlockRegistered = true;
+ }
+
+ private KelvinRiscV32_ControlBlock ControlBlock;
+ private bool ControlBlockRegistered = false;
+
+ private void HandleKelvinMPause(UInt64 opcode)
+ {
+ ControlBlock.ExecMPause();
+ }
+
+ private void HandleKelvinEBreak(UInt64 opcode)
+ {
+ ControlBlock.ExecEBreak();
+ }
+ }
+
+ public class KelvinRiscV32_ControlBlock :
+ IDoubleWordPeripheral,
+ IProvidesRegisterCollection<DoubleWordRegisterCollection>,
+ IKnownSize
+ {
+
+ public KelvinRiscV32_ControlBlock(Machine machine,
+ KelvinRiscV32 core,
+ ulong memoryRangeStart)
+ {
+ Machine = machine;
+ Core = core;
+
+ this.memoryRangestart = memoryRangeStart;
+
+ HostReqIRQ = new GPIO();
+ FinishIRQ = new GPIO();
+ InstructionFaultIRQ = new GPIO();
+ DataFaultIRQ = 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.Log(LogLevel.Noisy, "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.Log(LogLevel.Noisy, "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.Log(LogLevel.Noisy, "Got {0} to set IRQ pending bits", value);
+ irqsPending = irqsPending | ((InterruptBits)value & InterruptBits.Mask);
+ IrqUpdate();
+ })
+ ;
+
+ Registers.Control.Define32(this, resetValue: 0x00000002)
+ .WithValueField(0, 24, name: "FREEZE_VC_RESET_PC_START",
+ 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.Log(LogLevel.Noisy, "Pausing core.");
+ Core.IsHalted = true;
+ }
+
+ // Trigger the core's reset when SwReset is deasserted.
+ if (((mode & Mode.SwReset) != 0) && ((newMode & Mode.SwReset) == 0))
+ {
+ this.Log(LogLevel.Noisy, "Resetting core.");
+ Core.Reset();
+ ulong startAddress = (val >> (ulong)Mode.NumBits) + memoryRangeStart;
+ this.Log(LogLevel.Noisy, "Setting PC to 0x{0:X}.", startAddress);
+ Core.PC = startAddress;
+ }
+
+ // Unpause the core when both freeze and SwReset are deasserted.
+ if ((mode != Mode.Run) && (newMode == Mode.Run))
+ {
+ this.Log(LogLevel.Noisy, "Resuming core.");
+ Core.IsHalted = false;
+
+ Core.Resume();
+ }
+
+ this.mode = newMode;
+ })
+ .WithIgnoredBits(24, 32 - 24)
+ ;
+
+ // To-do: Not sure how to implement disablable memory banks.
+ Registers.MemoryBankControl.Define32(this)
+ .WithValueField(0, 16, out MemoryEnable, name: "MEM_ENABLE")
+ .WithIgnoredBits(16, 32 - 16)
+ ;
+
+ // To-do: Not sure how to implement memory access range checks.
+ Registers.ErrorStatus.Define32(this)
+ .WithFlag(0, name: "MEM_OUT_OF_RANGE")
+ .WithValueField(1, 9, out MemoryDisableAccess, name: "MEM_DISABLE_ACCESS")
+ .WithIgnoredBits(9, 32 - 9)
+ ;
+
+ Registers.InitStart.Define32(this)
+ .WithValueField(0, 22, out InitStartAddress, name: "ADDRESS")
+ .WithIgnoredBits(22, 32 - 22)
+ ;
+
+ Registers.InitEnd.Define32(this)
+ .WithValueField(0, 22, out InitEndAddress, name: "ADDRESS")
+ .WithFlag(22, name: "VALID", mode: FieldMode.Read | FieldMode.Write,
+ writeCallback: (_, val) =>
+ {
+ // If valid, do the memory clear.
+ if (val)
+ {
+ var dataPageMask = ~((ulong)(DataPageSize - 1));
+ InitStatusPending.Value = true;
+ InitStatusDone.Value = false;
+ Machine.LocalTimeSource.ExecuteInNearestSyncedState( __ =>
+ {
+ for(ulong writeAddress = InitStartAddress.Value & dataPageMask;
+ writeAddress < ((InitEndAddress.Value + DataPageSize - 1) & dataPageMask);
+ writeAddress += DataPageSize)
+ {
+ Machine.SystemBus.WriteBytes(DataErasePattern,
+ memoryRangeStart +
+ (ulong)writeAddress,
+ (uint)DataPageSize, true);
+ }
+ InitStatusPending.Value = false;
+ InitStatusDone.Value = true;
+ });
+ }
+ })
+ .WithIgnoredBits(23, 32 - 23)
+ ;
+
+ Registers.InitStatus.Define32(this)
+ .WithFlag(0, out InitStatusPending, name: "INIT_PENDING")
+ .WithFlag(1, out InitStatusDone, name: "INIT_DONE")
+ .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, reset the core (actual reset occurs when SwReset is cleared) and trigger a host interrupt indicating completion
+ if (mode == Mode.Run)
+ {
+ this.Log(LogLevel.Noisy, "Pausing and resetting core for host completion notification.");
+ }
+ else
+ {
+ this.Log(LogLevel.Error, "Pausing and resetting core for host completion notification, but core was not expected to be running. Did you clear IsHalted manually?");
+ }
+ Core.IsHalted = true;
+ mode = Mode.Freeze | Mode.SwReset;
+ irqsPending |= InterruptBits.Finish;
+ IrqUpdate();
+ }
+
+ public void ExecEBreak()
+ {
+ // Pause and trigger a host interrupt indicating completion with fault
+ if (mode == Mode.Run)
+ {
+ this.Log(LogLevel.Noisy, "Core executed ebreak.");
+ }
+ else
+ {
+ this.Log(LogLevel.Error, "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; }
+ public GPIO DataFaultIRQ { 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);
+ DataFaultIRQ.Set((irqsPassed & InterruptBits.DataFault) != 0);
+ }
+
+ // To-do: Set the erase pattern to what the hardware actually does. 0x5A is
+ // only for debugging purposes.
+ private const int DataPageSize = 64;
+ private readonly byte[] DataErasePattern = (byte[])Enumerable.Repeat((byte)0x5A, DataPageSize).ToArray();
+
+ // Disable unused variable warnings. These warnings will go away on their
+ // their own when each register's behavior is implemented.
+#pragma warning disable 414
+ private IValueRegisterField MemoryEnable;
+ private IValueRegisterField MemoryDisableAccess;
+ private IValueRegisterField InitStartAddress;
+ private IValueRegisterField InitEndAddress;
+ private IFlagRegisterField InitStatusPending;
+ private IFlagRegisterField InitStatusDone;
+#pragma warning restore 414
+
+ private Mode mode;
+ private readonly Machine Machine;
+ private readonly KelvinRiscV32 Core;
+ private readonly ulong memoryRangeStart;
+
+ // Length of register space.
+ 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,
+ DataFault = 8,
+ Mask = 15,
+ };
+ }
+
+}