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