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