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,
+        };
+    }
+
+}