| // |
| // 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.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 SpringbokRiscV32 : RiscV32 |
| { |
| public SpringbokRiscV32(Core.Machine machine, |
| uint hartId = 0, |
| PrivilegedArchitecture privilegedArchitecture = PrivilegedArchitecture.Priv1_11, |
| Endianess endianness = Endianess.LittleEndian, |
| string cpuType = "rv32imfv_zicsr") |
| : base(machine, cpuType, null, hartId, privilegedArchitecture, endianness) |
| { |
| RegisterCustomCSRs(); |
| |
| // funct7 ------- |
| // rs2 ----- |
| // rs1 ----- |
| // funct3 --- |
| // rd ----- |
| InstallCustomInstruction(pattern: "-------------------------1111011", handler: HandleSpringbokCustom3); // custom-3 |
| |
| Reset(); |
| // Placing these before reset results in them being reset (this is not intended behavior) |
| // These should be moved withing Tlib out of the reset function. |
| VectorRegisterLength = 512; |
| VectorElementMaxWidth = 32; |
| } |
| |
| public override void Reset() |
| { |
| base.Reset(); |
| |
| // This core comes out of reset paused. |
| this.IsHalted = true; |
| |
| if(ControlBlockRegistered) |
| { |
| ControlBlock.Reset(); |
| } |
| } |
| |
| public void RegisterControlBlock(SpringbokRiscV32_ControlBlock controlBlock) |
| { |
| ControlBlock = controlBlock; |
| ControlBlockRegistered = true; |
| } |
| |
| // A no-op API to match the KelvinCPU interface |
| public string CpuLibraryPath { get; set; } |
| |
| private SpringbokRiscV32_ControlBlock ControlBlock; |
| private bool ControlBlockRegistered = false; |
| |
| private void HandleSpringbokCustom3(UInt64 opcode) |
| { |
| int rd = (int)BitHelper.GetValue(opcode, 7, 5); |
| int funct3 = (int)BitHelper.GetValue(opcode, 12, 3); |
| int rs1 = (int)BitHelper.GetValue(opcode, 15, 5); |
| int rs2 = (int)BitHelper.GetValue(opcode, 20, 5); |
| // int funct7 = (int)BitHelper.GetValue(opcode, 25, 7); // Unused for now |
| |
| switch(funct3) |
| { |
| case 0: |
| // simprint |
| // rd is logging level |
| // rs1 is pointer to null-terminated string to print |
| // rs2 is number to print |
| int levelNum = (int)(X[rd].RawValue); |
| LogLevel level = LogLevel.Error; |
| |
| switch(levelNum) |
| { |
| case 0: |
| level = LogLevel.Error; |
| break; |
| case 1: |
| level = LogLevel.Warning; |
| break; |
| case 2: |
| level = LogLevel.Info; |
| break; |
| case 3: |
| level = LogLevel.Debug; |
| break; |
| case 4: |
| level = LogLevel.Noisy; |
| break; |
| default: |
| this.Log(LogLevel.Error, "Unrecognized logging level for simprint instruction! {0}: {1}", rd, levelNum); |
| return; |
| } |
| |
| uint messagePtr = (uint)this.TranslateAddress((uint)(X[rs1].RawValue), MpuAccess.Read); |
| uint number = (uint)(X[rs2].RawValue); |
| |
| byte[] messageArray = new byte[256]; |
| |
| for(int i = 0; i < 255; i++) |
| { |
| messageArray[i] = (byte)(ReadByteFromBus(messagePtr++) & 127); // Just in case we read garbage, let's restrict it to ASCII garbage. |
| if(messageArray[i] == 0) |
| { |
| break; |
| } |
| } |
| |
| String message = Encoding.ASCII.GetString(messageArray).TrimEnd((Char)0); |
| |
| this.Log(level, "simprint: \"{0}\", {1} (0x{1:X})", message, number); |
| |
| break; |
| case 1: |
| // xcount |
| switch(rs1) |
| { |
| case 0: |
| // icount |
| X[rd] = ExecutedInstructions; |
| break; |
| case 1: |
| // ccount |
| // Renode simulates one cycle per instruction |
| X[rd] = ExecutedInstructions; |
| break; |
| default: |
| this.Log(LogLevel.Error, "xcount: unrecognized source: {0} (0x{0:X})", rs1); |
| break; |
| } |
| break; |
| case 2: |
| // hostreq |
| if(ControlBlockRegistered) |
| { |
| ControlBlock.ExecHostReq(); |
| } |
| break; |
| case 3: |
| // finish |
| if(ControlBlockRegistered) |
| { |
| ControlBlock.ExecFinish(); |
| } |
| break; |
| default: |
| // Unrecognized |
| this.Log(LogLevel.Error, "custom-3: unrecognized funct3: {0} (0x{0:X})", funct3); |
| break; |
| } |
| } |
| |
| private void RegisterCustomCSRs() |
| { |
| // validate only privilege level when accessing CSRs |
| // do not validate rw bit as VexRiscv custom CSRs do not follow the standard |
| CSRValidation = CSRValidationLevel.None; |
| |
| RegisterCSR((ulong)CSRs.InstructionCount, () => InstructionCountCSRRead("InstructionCount"), value => { }); |
| RegisterCSR((ulong)CSRs.CycleCount, () => InstructionCountCSRRead("CycleCount"), value => { }); |
| } |
| |
| private ulong InstructionCountCSRRead(string name) |
| { |
| this.Log(LogLevel.Noisy, "Reading instruction count CSR {0} 0x{1:X}", name, ExecutedInstructions); |
| ulong count = ExecutedInstructions; |
| return count; |
| } |
| |
| private enum CSRs |
| { |
| InstructionCount = 0x7C0, |
| CycleCount = 0x7C1, |
| } |
| } |
| |
| public class SpringbokRiscV32_ControlBlock : |
| IDoubleWordPeripheral, |
| IProvidesRegisterCollection<DoubleWordRegisterCollection>, |
| IKnownSize |
| { |
| |
| public SpringbokRiscV32_ControlBlock(Machine machine, |
| SpringbokRiscV32 core, |
| uint mmuNumWindows, |
| ulong mmuMemorySize, |
| ulong mmuVirtualWindowSize, |
| ulong mmuRangeStart) |
| { |
| Machine = machine; |
| Core = core; |
| |
| this.mmuNumWindows = mmuNumWindows; |
| this.mmuRangeStart = mmuRangeStart; |
| this.mmuMemorySize = mmuMemorySize; |
| this.mmuVirtualWindowSize = mmuVirtualWindowSize; |
| |
| Mmu = InitMmu(mmuRangeStart, mmuMemorySize, mmuNumWindows, mmuVirtualWindowSize); |
| |
| HostReqIRQ = new GPIO(); |
| FinishIRQ = new GPIO(); |
| InstructionFaultIRQ = new GPIO(); |
| DataFaultIRQ = new GPIO(); |
| |
| Core.RegisterControlBlock(this); |
| |
| RegistersCollection = new DoubleWordRegisterCollection(this); |
| DefineRegisters(); |
| |
| Core.AddHookOnMmuFault(HandleFault); |
| |
| Reset(); |
| } |
| |
| public void Reset() |
| { |
| mode = Mode.Freeze | Mode.SwReset; |
| RegistersCollection.Reset(); |
| } |
| |
| private ExternalMmuBase InitMmu(ulong rangeStart, ulong memorySize, uint windowsCount, ulong windowSize) |
| { |
| var mmu = new ExternalMmuBase(this.Core, windowsCount); |
| |
| for(uint windowIndex = 0; windowIndex<windowsCount; windowIndex++) |
| { |
| var windowStart = rangeStart + windowIndex * windowSize; |
| var windowEnd = windowStart + windowSize; |
| mmu.SetWindowStart(windowIndex, windowStart); |
| mmu.SetWindowEnd(windowIndex, windowEnd); |
| } |
| return mmu; |
| } |
| |
| 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: 0x00000003) |
| .WithValueField(0, 19, 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.Mask) + mmuRangeStart; |
| 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(19, 32 - 19); |
| |
| // To-do: Not sure how to implement disablable memory banks. |
| Registers.MemoryBankControl.Define32(this) |
| .WithValueField(0, 4, 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(2, 4, out MemoryDisableAccess, name: "MEM_DISABLE_ACCESS") |
| .WithIgnoredBits(16, 32 - 16); |
| |
| Registers.InitStart.Define32(this) |
| .WithValueField(0, 22, out InitStartAddress, name: "ADDRESS") |
| .WithIgnoredBits(24, 32 - 24); |
| |
| 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, mmuRangeStart + (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) |
| { |
| switch (offset >> 10) |
| { |
| case 0: /* 0..1023 */ |
| return RegistersCollection.Read(offset); |
| case 1: /* 1024..2047 */ |
| if (Mmu != null) |
| { |
| offset -= 1024; |
| uint window = (uint)(offset >> 4); |
| if(window >= mmuNumWindows) |
| { |
| this.Log(LogLevel.Warning, "Invalid window number {0}", window); |
| return 0; |
| } |
| switch (offset & 15) |
| { |
| case 0: |
| return checked((uint)Mmu.GetWindowStart(window)); |
| case 4: |
| return checked((uint)(Mmu.GetWindowEnd(window) - Mmu.GetWindowStart(window))); // windowLength |
| case 8: |
| return checked((uint)Mmu.GetWindowPrivileges(window)); |
| default: |
| this.Log(LogLevel.Warning, "No Springbok Mmu register at address 0x{0:X}.", offset); |
| return 0; |
| } |
| } |
| this.Log(LogLevel.Warning, "Attempting to access Springbok Mmu when it was not included in system.", offset); |
| return 0; |
| default: /* 2048..4095 */ |
| return 0; |
| } |
| } |
| |
| public virtual void WriteDoubleWord(long offset, uint value) |
| { |
| switch(offset >> 10) |
| { |
| case 0: /* 0..1023 */ |
| RegistersCollection.Write(offset, value); |
| return; |
| case 1: /* 1024..2047 */ |
| if (Mmu != null) |
| { |
| offset -= 1024; |
| uint window = (uint)(offset >> 4); |
| if(window >= mmuNumWindows) |
| { |
| this.Log(LogLevel.Warning, "Invalid window number {0}", window); |
| return; |
| } |
| switch (offset & 15) |
| { |
| case 0: |
| var virtualWindowOffset = this.mmuRangeStart + window * this.mmuVirtualWindowSize; |
| Mmu.SetWindowAddend(window, value - virtualWindowOffset); |
| return; |
| case 4: |
| // This assumes that the window start has already been set |
| var windowEnd = Mmu.GetWindowStart(window) + value + 1; |
| Mmu.SetWindowEnd(window, windowEnd); // windowLength |
| return; |
| case 8: |
| Mmu.SetWindowPrivileges(window, value); |
| return; |
| } |
| } |
| return; |
| default: /* 2048..4095 */ |
| return; |
| } |
| } |
| |
| public void ExecHostReq() |
| { |
| // Pause the core and trigger a host interrupt signaling a request |
| if (mode == Mode.Run) |
| { |
| this.Log(LogLevel.Noisy, "Pausing core for host request."); |
| } |
| else |
| { |
| this.Log(LogLevel.Error, "Pausing core for host request, but core was not expected to be running. Did you clear IsHalted manually?"); |
| } |
| Core.IsHalted = true; |
| mode = Mode.Freeze; |
| irqsPending |= InterruptBits.HostReq; |
| IrqUpdate(); |
| } |
| |
| public void ExecFinish() |
| { |
| // 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(); |
| } |
| |
| private void ExecFault() |
| { |
| // Pause, reset the core (actual reset occurs when SwReset is cleared) and trigger a host interrupt indicating a fault |
| if (mode == Mode.Run) |
| { |
| this.Log(LogLevel.Noisy, "Pausing and resetting core for fault notification."); |
| } |
| else |
| { |
| this.Log(LogLevel.Error, "Pausing and resetting core for fault notification, but core was not expected to be running. Did you clear IsHalted manually?"); |
| } |
| Core.IsHalted = true; |
| mode = Mode.Freeze | Mode.SwReset; |
| irqsPending |= InterruptBits.DataFault; |
| IrqUpdate(); |
| return; |
| } |
| |
| public DoubleWordRegisterCollection RegistersCollection { get; private set; } |
| |
| private void HandleFault(ulong faultAddress, AccessType accessType, int windowIndex) |
| { |
| LogFaultType(Mmu, faultAddress, accessType, mmuRangeStart, mmuVirtualWindowSize, mmuNumWindows); |
| ExecFault(); |
| } |
| |
| private void LogFaultType(ExternalMmuBase mmu, ulong faultAddress, AccessType accessType, ulong rangeStart, ulong virtualWindowSize, uint numberOfWindows) |
| { |
| var mmuName = mmu.GetType().Name; |
| var virtualWindowIndex = (uint)((faultAddress - rangeStart) / virtualWindowSize); |
| |
| if(virtualWindowIndex >= numberOfWindows) |
| { |
| this.Log(LogLevel.Error, "{0}: Translation request for {1} at 0x{2:X}: failure, no window at address", mmuName, accessType, faultAddress); |
| } |
| else |
| { |
| var windowEnd = mmu.GetWindowEnd(virtualWindowIndex); |
| var windowPrivilege = mmu.GetWindowPrivileges(virtualWindowIndex); |
| |
| if(faultAddress > windowEnd) |
| { |
| this.Log(LogLevel.Error, "{0}: Translation request for {1} at 0x{2:X}: failure on window {3}, out of range, window ends at 0x{4:X}", |
| mmuName, accessType, faultAddress, virtualWindowIndex, windowEnd); |
| } |
| else if((windowPrivilege & (uint)accessType) == 0) |
| { |
| this.Log(LogLevel.Error, "{0}: Translation request for {1} at 0x{2:X}: failure on window {3}, no permission, window permissions are 0x{4:X}", |
| mmuName, accessType, faultAddress, virtualWindowIndex, windowPrivilege); |
| } |
| else |
| { |
| this.Log(LogLevel.Error, "{0}: Translation request for {1} at 0x{2:X}: failed for unknown reason. This should not occur", |
| mmuName, accessType, faultAddress); |
| } |
| } |
| } |
| |
| 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 SpringbokRiscV32 Core; |
| private readonly ExternalMmuBase Mmu; |
| private readonly uint mmuNumWindows; |
| private readonly ulong mmuRangeStart; |
| private readonly ulong mmuMemorySize; |
| private readonly ulong mmuVirtualWindowSize; |
| |
| // 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, |
| }; |
| private enum FaultType |
| { |
| InstructionFetch, |
| DataAccess, |
| }; |
| [Flags] |
| private enum InterruptBits |
| { |
| HostReq = 1, |
| Finish = 2, |
| InstructionFault = 4, |
| DataFault = 8, |
| Mask = 15, |
| }; |
| } |
| |
| } |