| // |
| // 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: "00000000000100000000000001110011", handler: HandleKelvinEBreak); // Kelvin doesn't implement rv32i ebreak correctly |
| InstallCustomInstruction(pattern: "00000010000000000000000001110011", handler: HandleKelvinEExit); |
| InstallCustomInstruction(pattern: "00000100000000000000000001110011", handler: HandleKelvinEYield); |
| InstallCustomInstruction(pattern: "00000110000000000000000001110011", handler: HandleKelvinECtxSw); |
| InstallCustomInstruction(pattern: "00001000000000000000000001110011", handler: HandleKelvinMPause); |
| InstallCustomInstruction(pattern: "011110000000-----000000001110111", handler: HandleKelvinFLog); |
| InstallCustomInstruction(pattern: "011110000000-----001000001110111", handler: HandleKelvinSLog); |
| InstallCustomInstruction(pattern: "011110000000-----010000001110111", handler: HandleKelvinCLog); |
| InstallCustomInstruction(pattern: "011110000000-----011000001110111", handler: HandleKelvinKLog); |
| InstallCustomInstruction(pattern: "00100110000000000000000001110111", handler: HandleKelvinFlushAll); |
| InstallCustomInstruction(pattern: "001001100000-----000000001110111", handler: HandleKelvinFlushAt); |
| |
| 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 HandleKelvinEBreak(UInt64 opcode) |
| { |
| ControlBlock.ExecEBreak(); |
| } |
| |
| private void HandleKelvinEExit(UInt64 opcode) |
| { |
| ControlBlock.ExecEBreak(); |
| } |
| |
| private void HandleKelvinEYield(UInt64 opcode) |
| { |
| // To be implemented |
| ControlBlock.ExecEBreak(); |
| } |
| |
| private void HandleKelvinECtxSw(UInt64 opcode) |
| { |
| // To be implemented |
| ControlBlock.ExecEBreak(); |
| } |
| |
| private void HandleKelvinMPause(UInt64 opcode) |
| { |
| ControlBlock.ExecMPause(); |
| } |
| |
| private void HandleKelvinFLog(UInt64 opcode) |
| { |
| this.Log(LogLevel.Noisy, "FLog dropped (unimplemented)"); |
| // To be implemented |
| } |
| |
| private void HandleKelvinSLog(UInt64 opcode) |
| { |
| this.Log(LogLevel.Noisy, "SLog dropped (unimplemented)"); |
| // To be implemented |
| } |
| |
| private void HandleKelvinCLog(UInt64 opcode) |
| { |
| this.Log(LogLevel.Noisy, "CLog dropped (unimplemented)"); |
| // To be implemented |
| } |
| |
| private void HandleKelvinKLog(UInt64 opcode) |
| { |
| this.Log(LogLevel.Noisy, "KLog dropped (unimplemented)"); |
| // To be implemented |
| } |
| |
| private void HandleKelvinFlushAll(UInt64 opcode) |
| { |
| // No-op in simulator |
| } |
| |
| private void HandleKelvinFlushAt(UInt64 opcode) |
| { |
| // No-op in simulator |
| } |
| } |
| |
| public class KelvinRiscV32_ControlBlock : |
| IDoubleWordPeripheral, |
| IProvidesRegisterCollection<DoubleWordRegisterCollection>, |
| IKnownSize |
| { |
| |
| public KelvinRiscV32_ControlBlock(Machine machine, |
| KelvinRiscV32 core, |
| ulong tcmSize, |
| ulong tcmRangeStart) |
| { |
| Machine = machine; |
| Core = core; |
| |
| this.tcmRangeStart = tcmRangeStart; |
| this.tcmSize = tcmSize; |
| |
| Mmu = InitMmu(tcmRangeStart, tcmSize); |
| |
| 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 tcmSize) |
| { |
| var mmu = new ExternalMmuBase(this.Core, 1); |
| |
| mmu.SetWindowStart(0, 0); |
| mmu.SetWindowEnd(0, tcmSize); |
| mmu.SetWindowAddend(0, tcmRangeStart); |
| mmu.SetWindowPrivileges(0, 7); // Read / write / execute |
| |
| 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: 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(); |
| } |
| |
| if ((mode & Mode.SwReset) != 0) |
| { |
| // Always set the PC on assignment to help with GDB. |
| ulong startAddress = (val >> (int)Mode.NumBits); |
| if ((Core.PC != startAddress)) |
| { |
| 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, 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, |
| (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; } |
| |
| |
| private void HandleFault(ulong faultAddress, AccessType accessType, int windowIndex) |
| { |
| LogFaultType(Mmu, faultAddress, accessType); |
| ExecEBreak(); |
| } |
| |
| private void LogFaultType(ExternalMmuBase mmu, ulong faultAddress, AccessType accessType) |
| { |
| var mmuName = mmu.GetType().Name; |
| var virtualWindowIndex = (uint)(faultAddress / tcmSize); |
| |
| if(virtualWindowIndex >= 1) |
| { |
| 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 KelvinRiscV32 Core; |
| private readonly ExternalMmuBase Mmu; |
| private readonly ulong tcmSize; |
| private readonly ulong tcmRangeStart; |
| |
| // 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, |
| }; |
| } |
| |
| } |