blob: bef2b2f6565b4f2e927359b93193a54ec1ebfde4 [file] [log] [blame]
//
// 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,
};
}
}