| // | |
| // 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 SpringbokRiscV32 : RiscV32 | |
| { | |
| public SpringbokRiscV32(Core.Machine machine, | |
| uint hartId = 0, | |
| PrivilegeArchitecture privilegeArchitecture = PrivilegeArchitecture.Priv1_11, | |
| Endianess endianness = Endianess.LittleEndian, | |
| string cpuType = "rv32imfv") | |
| : base(null, cpuType, machine, hartId, privilegeArchitecture, 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; | |
| } | |
| 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) | |
| { | |
| InitStatusPending.Value = true; | |
| InitStatusDone.Value = false; | |
| Machine.LocalTimeSource.ExecuteInNearestSyncedState( __ => { | |
| for(long writeAddress = InitStartAddress.Value & ~(DataPageSize - 1); | |
| writeAddress < ((InitEndAddress.Value + DataPageSize - 1) & ~(DataPageSize - 1)); | |
| 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, | |
| }; | |
| } | |
| } |