Exception processing, interrupts, registers.

master
Nabile Rahmani 2018-01-01 18:01:59 +01:00
parent 08853d495f
commit 158f01ccf9
17 changed files with 496 additions and 129 deletions

View File

@ -0,0 +1,119 @@
using System;
using System.Collections.Specialized;
namespace DotN64.CPU
{
public partial class VR4300
{
public partial class SystemControlUnit
{
/// <summary>
/// See: datasheet#6.3.6.
/// </summary>
public class CauseRegister : Register
{
#region Fields
private static readonly BitVector32.Section constant0 = BitVector32.CreateSection((1 << 2) - 1),
excCode = BitVector32.CreateSection((1 << 5) - 1, constant0),
constant1 = BitVector32.CreateSection(1, excCode),
ip = BitVector32.CreateSection((1 << 8) - 1, constant1),
constant2 = BitVector32.CreateSection((1 << 12) - 1, ip),
ce = BitVector32.CreateSection((1 << 2) - 1, constant2),
constant3 = BitVector32.CreateSection(1, ce),
bd = BitVector32.CreateSection(1, constant3);
#endregion
#region Properties
protected override RegisterIndex Index => RegisterIndex.Cause;
/// <summary>
/// Exception code field (refer to Table 6-2 for details.)
/// </summary>
public ExceptionCode ExcCode
{
get => (ExceptionCode)this[excCode];
set => this[excCode] = (byte)value;
}
/// <summary>
/// Indicates an interrupt is pending.
/// 1 → interrupt pending
/// 0 → no interrupt
/// IP(7) : Timer interrupt
/// IP(6:2) : External normal interrupts. Controlled by Int[4:0], or external write requests
/// IP(1:0) : Software interrupts. Only these bits can cause interrupt exception when they are set to 1 by software.
/// </summary>
public byte IP
{
get => (byte)this[ip];
set => this[ip] = value;
}
/// <summary>
/// Coprocessor unit number referenced when a Coprocessor Unusable exception has occurred.
/// If this exception does not occur, undefined.
/// </summary>
public byte CE
{
get => (byte)this[ce];
set => this[ce] = value;
}
/// <summary>
/// Indicates whether the last exception occurred has been executed in a branch delay slot.
/// 1 → delay slot
/// 0 → normal
/// </summary>
public bool BD
{
get => Convert.ToBoolean(this[bd]);
set => this[bd] = Convert.ToInt32(value);
}
#endregion
#region Constructors
public CauseRegister(SystemControlUnit cp0)
: base(cp0) { }
#endregion
#region Enumerations
public enum ExceptionCode : byte
{
/// <summary>Interrupt.</summary>
Int = 0,
/// <summary>TLB Modification exception.</summary>
Mod = 1,
/// <summary>TLB Miss exception (load or instruction fetch).</summary>
TLBL = 2,
/// <summary>TLB Miss exception (store).</summary>
TLBS = 3,
/// <summary>Address Error exception (load or instruction fetch).</summary>
AdEL = 4,
/// <summary>Address Error exception (store).</summary>
AdES = 5,
/// <summary>Bus Error exception (instruction fetch).</summary>
IBE = 6,
/// <summary>Bus Error exception (data reference: load or store).</summary>
DBE = 7,
/// <summary>Syscall exception.</summary>
Sys = 8,
/// <summary>Breakpoint exception.</summary>
Bp = 9,
/// <summary>Reserved Instruction exception.</summary>
RI = 10,
/// <summary>Coprocessor Unusable exception.</summary>
CpU = 11,
/// <summary>Arithmetic Overflow exception.</summary>
Ov = 12,
/// <summary>Trap exception.</summary>
Tr = 13,
/// <summary>Floating-Point exception.</summary>
FPE = 15,
/// <summary>Watch exception.</summary>
WATCH = 23
}
#endregion
}
}
}
}

View File

@ -12,6 +12,8 @@ namespace DotN64.CPU
public ConfigRegister Config { get; }
public StatusRegister Status { get; }
public CauseRegister Cause { get; }
#endregion
#region Constructors
@ -19,6 +21,7 @@ namespace DotN64.CPU
{
Config = new ConfigRegister(this);
Status = new StatusRegister(this);
Cause = new CauseRegister(this);
}
#endregion

View File

@ -0,0 +1,68 @@
namespace DotN64.CPU
{
public partial class VR4300
{
/// <summary>
/// See: datasheet#6.4.
/// </summary>
private static class ExceptionProcessing
{
#region Fields
private const ulong ResetVector = 0xFFFFFFFFBFC00000,
GeneralVector = 0xFFFFFFFF80000000,
GeneralVectorBEV = 0xFFFFFFFFBFC00200;
private const ushort GeneralVectorOffset = 0x0180;
#endregion
#region Methods
private static void HandleReset(VR4300 cpu)
{
cpu.CP0.Registers[(int)SystemControlUnit.RegisterIndex.ErrorEPC] = cpu.PC;
cpu.PC = ResetVector;
}
private static void HandleGeneral(VR4300 cpu, SystemControlUnit.CauseRegister.ExceptionCode excCode, byte? ce = null)
{
cpu.CP0.Cause.ExcCode = excCode;
if (ce.HasValue)
cpu.CP0.Cause.CE = ce.Value;
cpu.CP0.Registers[(int)SystemControlUnit.RegisterIndex.BadVAddr] = cpu.PC;
if (!cpu.CP0.Status.EXL)
{
if (cpu.CP0.Cause.BD = cpu.DelaySlot.HasValue) // FIXME: Step() nullifies the delay slot before executing it.
cpu.CP0.Registers[(int)SystemControlUnit.RegisterIndex.EPC] = cpu.PC - Instruction.Size;
else
cpu.CP0.Registers[(int)SystemControlUnit.RegisterIndex.EPC] = cpu.PC;
}
cpu.CP0.Status.EXL = true;
cpu.PC = (cpu.CP0.Status.DS.BEV ? GeneralVectorBEV : GeneralVector) + GeneralVectorOffset;
}
public static void ColdReset(VR4300 cpu)
{
var ds = cpu.CP0.Status.DS;
ds.TS = ds.SR = cpu.CP0.Status.RP = false;
cpu.CP0.Config.EP = 0;
cpu.CP0.Status.ERL = ds.BEV = true;
cpu.CP0.Config.BE = (SystemControlUnit.ConfigRegister.Endianness)1;
cpu.CP0.Registers[(int)SystemControlUnit.RegisterIndex.Random] = 31;
cpu.CP0.Config.EC = cpu.DivMode;
cpu.CP0.Status.DS = ds;
HandleReset(cpu);
}
public static void Interrupt(VR4300 cpu) => HandleGeneral(cpu, SystemControlUnit.CauseRegister.ExceptionCode.Int);
#endregion
}
}
}

View File

@ -4,6 +4,8 @@ using System.Runtime.CompilerServices;
namespace DotN64.CPU
{
using Helpers;
/// <summary>
/// The NEC VR4300 CPU, designed by MIPS Technologies.
/// Datasheet: http://datasheets.chipdb.org/NEC/Vr-Series/Vr43xx/U10504EJ7V0UMJ1.pdf
@ -12,8 +14,14 @@ namespace DotN64.CPU
{
#region Fields
private readonly IReadOnlyDictionary<uint, Action<Instruction>> operations;
private readonly IReadOnlyDictionary<byte, float> divModeMultipliers = new Dictionary<byte, float>
{
[0b01] = 1.5f,
[0b10] = 2.0f,
[0b11] = 3.0f
};
private const ulong ResetVector = 0xFFFFFFFFBFC00000;
private const byte IntShift = 2, IntSize = (1 << 5) - 1;
private delegate bool BranchCondition(ulong rs, ulong rt);
#endregion
@ -70,6 +78,39 @@ namespace DotN64.CPU
set => divMode = (byte)(value & ((1 << 2) - 1));
}
public double MasterClock { get; set; }
/// <summary>
/// Pipeline clock.
/// </summary>
private double PClock => MasterClock * divModeMultipliers[DivMode] * (CP0.Status.RP ? 0.25 : 1.0);
/// <summary>
/// System interface clock.
/// </summary>
private double SClock => PClock / divModeMultipliers[DivMode];
/// <summary>
/// Transmit/receive clock.
/// </summary>
public double TClock => SClock;
private double TicksPerCycle => 1.0 / PClock * TimeSpan.TicksPerSecond;
/// <summary>
/// Interrupt request acknowledge.
/// </summary>
public byte Int
{
get => (byte)BitHelper.Get(CP0.Cause.IP, IntShift, IntSize);
set
{
var ip = (uint)CP0.Cause.IP;
BitHelper.Set(ref ip, IntShift, IntSize, value);
CP0.Cause.IP = (byte)ip;
}
}
/// <summary>
/// System address/data bus.
/// </summary>
@ -155,25 +196,9 @@ namespace DotN64.CPU
/// Cold reset.
/// See: datasheet#6.4.4.
/// </summary>
public void Reset()
{
PC = ResetVector;
var ds = CP0.Status.DS;
ds.TS = ds.SR = CP0.Status.RP = false;
CP0.Config.EP = 0;
CP0.Status.ERL = ds.BEV = true;
CP0.Config.BE = (SystemControlUnit.ConfigRegister.Endianness)1;
CP0.Registers[(int)SystemControlUnit.RegisterIndex.Random] = 31;
CP0.Config.EC = DivMode;
CP0.Status.DS = ds;
}
public void Reset() => ExceptionProcessing.ColdReset(this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Run(Instruction instruction)
{
if (operations.TryGetValue(instruction.ToOpCode(), out var operation))
@ -182,7 +207,8 @@ namespace DotN64.CPU
throw new UnimplementedOperationException(instruction);
}
public void Step()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Step()
{
Instruction instruction;
@ -200,6 +226,19 @@ namespace DotN64.CPU
Run(instruction);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Cycle()
{
if ((uint)++CP0.Registers[(int)SystemControlUnit.RegisterIndex.Count] == (uint)CP0.Registers[(int)SystemControlUnit.RegisterIndex.Compare])
CP0.Cause.IP |= 1 << 7; // Set the Timer interrupt.
if (CP0.Status.IE && !CP0.Status.EXL && !CP0.Status.ERL && (CP0.Status.IM & CP0.Cause.IP) != 0)
ExceptionProcessing.Interrupt(this);
Step();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool Branch(Instruction instruction, BranchCondition condition, bool storeLink = false)
{
var result = condition(GPR[instruction.RS], GPR[instruction.RT]);
@ -216,6 +255,7 @@ namespace DotN64.CPU
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BranchLikely(Instruction instruction, BranchCondition condition)
{
if (!Branch(instruction, condition))

View File

@ -75,7 +75,7 @@ namespace DotN64.Diagnostics
for (var i = BigInteger.Zero; i < count; i++)
{
Disassemble();
nintendo64.CPU.Step();
nintendo64.CPU.Cycle();
}
}),
new Command(new[] { "goto", "g" }, "Sets the CPU's PC to the specified address.", args => nintendo64.CPU.PC = ulong.Parse(args.First(), NumberStyles.HexNumber)),
@ -275,7 +275,7 @@ namespace DotN64.Diagnostics
try
{
nintendo64.CPU.Step();
nintendo64.CPU.Cycle();
}
catch (Exception e)
{

View File

@ -44,7 +44,7 @@
<Compile Include="RCP\MI\RealityCoprocessor.MIPSInterface.InitModeRegister.cs" />
<Compile Include="RCP\PI\RealityCoprocessor.ParallelInterface.cs" />
<Compile Include="RCP\PI\RealityCoprocessor.ParallelInterface.Domain.cs" />
<Compile Include="RCP\PI\RealityCoprocessor.ParallelInterface.StatusRegister.cs" />
<Compile Include="RCP\PI\RealityCoprocessor.ParallelInterface.Statuses.cs" />
<Compile Include="RCP\RI\RealityCoprocessor.RDRAMInterface.cs" />
<Compile Include="RCP\RI\RealityCoprocessor.RDRAMInterface.ConfigRegister.cs" />
<Compile Include="RCP\RI\RealityCoprocessor.RDRAMInterface.ModeRegister.cs" />
@ -70,7 +70,7 @@
<Compile Include="Diagnostics\Debugger.InstructionFormat.cs" />
<Compile Include="Helpers\BitHelper.cs" />
<Compile Include="RCP\DP\RealityCoprocessor.DisplayProcessor.StatusRegister.cs" />
<Compile Include="RCP\SP\RealityCoprocessor.SignalProcessor.StatusRegister.cs" />
<Compile Include="RCP\SP\RealityCoprocessor.SignalProcessor.Statuses.cs" />
<Compile Include="CPU\VR4300\VR4300.Exceptions.cs" />
<Compile Include="PIF\PeripheralInterface.cs" />
<Compile Include="PIF\PeripheralInterface.CICStatus.cs" />
@ -79,6 +79,9 @@
<Compile Include="RDRAM\RDRAM.ConfigIndex.cs" />
<Compile Include="RDRAM\RDRAM.ConfigRegister.cs" />
<Compile Include="PIF\PeripheralInterface.CIC.cs" />
<Compile Include="RCP\MI\RealityCoprocessor.MIPSInterface.Interrupts.cs" />
<Compile Include="CPU\VR4300\CP0\VR4300.SystemControlUnit.CauseRegister.cs" />
<Compile Include="CPU\VR4300\VR4300.ExceptionProcessing.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="CPU\" />

View File

@ -1,4 +1,6 @@
namespace DotN64
using System;
namespace DotN64
{
using CPU;
using Extensions;
@ -25,7 +27,8 @@
RCP = new RealityCoprocessor(this);
CPU = new VR4300
{
DivMode = 0b01, // Assuming this value as the CPU is clocked at 93.75 MHz, and the RCP would be clocked at 93.75 / 3 * 2 = 62.5 MHz.
DivMode = 0b01,
MasterClock = 62.5 * Math.Pow(10, 6),
ReadSysAD = RCP.MemoryMaps.ReadWord,
WriteSysAD = RCP.MemoryMaps.WriteWord
};
@ -43,7 +46,7 @@
{
while (true)
{
CPU.Step();
CPU.Cycle();
}
}
#endregion

View File

@ -36,6 +36,10 @@
new MappingEntry(0x04500004, 0x04500007) // AI length.
{
Write = (o, v) => TransferLength = v
},
new MappingEntry(0x0450000C, 0x0450000F) // AI status.
{
Write = (o, v) => rcp.MI.Interrupt &= ~MIPSInterface.Interrupts.AI
}
};
}

View File

@ -6,6 +6,10 @@ namespace DotN64.RCP
{
public partial class DisplayProcessor
{
#region Fields
private readonly RealityCoprocessor rcp;
#endregion
#region Properties
public IReadOnlyList<MappingEntry> MemoryMaps { get; }
@ -13,8 +17,9 @@ namespace DotN64.RCP
#endregion
#region Constructors
public DisplayProcessor()
public DisplayProcessor(RealityCoprocessor rcp)
{
this.rcp = rcp;
MemoryMaps = new[]
{
new MappingEntry(0x0410000C, 0x0410000F) // DP CMD status.

View File

@ -0,0 +1,38 @@
using System;
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class MIPSInterface
{
[Flags]
public enum Interrupts : byte
{
SP = 1 << 0,
SI = 1 << 1,
AI = 1 << 2,
VI = 1 << 3,
PI = 1 << 4,
DP = 1 << 5
}
[Flags]
private enum InterruptMaskWrites : ushort
{
ClearSP = 1 << 0,
SetSP = 1 << 1,
ClearSI = 1 << 2,
SetSI = 1 << 3,
ClearAI = 1 << 4,
SetAI = 1 << 5,
ClearVI = 1 << 6,
SetVI = 1 << 7,
ClearPI = 1 << 8,
SetPI = 1 << 9,
ClearDP = 1 << 10,
SetDP = 1 << 11
}
}
}
}

View File

@ -1,14 +1,35 @@
using System;
using System.Collections.Specialized;
using System.Collections.Specialized;
namespace DotN64.RCP
{
using CPU;
public partial class RealityCoprocessor
{
public partial class MIPSInterface : Interface
{
#region Fields
private static readonly byte interruptPin = 1 << 0;
#endregion
#region Properties
private VR4300 CPU => rcp.nintendo64.CPU;
public InitModeRegister InitMode { get; set; }
public Interrupts InterruptMask { get; set; }
private Interrupts interrupt;
public Interrupts Interrupt
{
get => interrupt;
set
{
interrupt = value;
UpdateInterrupt();
}
}
#endregion
#region Constructors
@ -33,7 +54,7 @@ namespace DotN64.RCP
mode.EBusTestMode |= bits[InitModeRegister.SetEBusTestModeSection] != 0;
if (bits[InitModeRegister.ClearDPInterruptSection] != 0)
throw new NotImplementedException("Clear DP interrupt.");
Interrupt &= ~Interrupts.DP;
mode.RDRAMRegMode &= bits[InitModeRegister.ClearRDRAMRegSection] == 0;
mode.RDRAMRegMode |= bits[InitModeRegister.SetRDRAMRegModeSection] != 0;
@ -44,10 +65,65 @@ namespace DotN64.RCP
new MappingEntry(0x04300004, 0x04300007) // MI version.
{
Read = o => 0 // TODO.
},
new MappingEntry(0x0430000C, 0x0430000F) // MI interrupt mask.
{
Write = (o, v) =>
{
var mask = (InterruptMaskWrites)v;
if ((mask & InterruptMaskWrites.ClearSP) != 0)
InterruptMask &= ~Interrupts.SP;
if ((mask & InterruptMaskWrites.SetSP) != 0)
InterruptMask |= Interrupts.SP;
if ((mask & InterruptMaskWrites.ClearSI) != 0)
InterruptMask &= ~Interrupts.SI;
if ((mask & InterruptMaskWrites.SetSI) != 0)
InterruptMask |= Interrupts.SI;
if ((mask & InterruptMaskWrites.ClearAI) != 0)
InterruptMask &= ~Interrupts.AI;
if ((mask & InterruptMaskWrites.SetAI) != 0)
InterruptMask |= Interrupts.AI;
if ((mask & InterruptMaskWrites.ClearVI) != 0)
InterruptMask &= ~Interrupts.VI;
if ((mask & InterruptMaskWrites.SetVI) != 0)
InterruptMask |= Interrupts.VI;
if ((mask & InterruptMaskWrites.ClearPI) != 0)
InterruptMask &= ~Interrupts.PI;
if ((mask & InterruptMaskWrites.SetPI) != 0)
InterruptMask |= Interrupts.PI;
if ((mask & InterruptMaskWrites.ClearDP) != 0)
InterruptMask &= ~Interrupts.DP;
if ((mask & InterruptMaskWrites.SetDP) != 0)
InterruptMask |= Interrupts.DP;
UpdateInterrupt();
}
}
};
}
#endregion
#region Methods
private void UpdateInterrupt()
{
if ((Interrupt & InterruptMask) != 0)
CPU.Int |= interruptPin;
else
CPU.Int &= (byte)~interruptPin;
}
#endregion
}
}
}

View File

@ -7,7 +7,7 @@ namespace DotN64.RCP
public partial class ParallelInterface
{
[Flags]
public enum StatusRegister
public enum Statuses
{
DMABusy = 1 << 0,
IOBusy = 1 << 1,
@ -15,7 +15,7 @@ namespace DotN64.RCP
}
[Flags]
private enum WriteStatusRegister : byte
private enum StatusWrites : byte
{
ResetController = 1 << 0,
ClearInterrupt = 1 << 1

View File

@ -10,7 +10,7 @@ namespace DotN64.RCP
public partial class ParallelInterface : Interface
{
#region Properties
public StatusRegister Status { get; set; }
public Statuses Status { get; set; }
public Domain[] Domains { get; } = new[]
{
@ -45,11 +45,12 @@ namespace DotN64.RCP
Read = o => (uint)Status,
Write = (o, v) =>
{
var status = (WriteStatusRegister)v;
var status = (StatusWrites)v;
if ((status & WriteStatusRegister.ResetController) != 0) { /* TODO. */ }
if ((status & StatusWrites.ResetController) != 0) { /* TODO. */ }
if ((status & WriteStatusRegister.ClearInterrupt) != 0) { /* TODO. */ }
if ((status & StatusWrites.ClearInterrupt) != 0)
rcp.MI.Interrupt &= ~MIPSInterface.Interrupts.PI;
}
},
new MappingEntry(0x04600014, 0x04600017) // PI dom1 latency.
@ -81,7 +82,7 @@ namespace DotN64.RCP
Write = (o, v) =>
{
WriteLength = v & ((1 << 24) - 1);
Status |= StatusRegister.DMABusy;
Status |= Statuses.DMABusy;
var maps = rcp.MemoryMaps;
for (uint i = 0; i < WriteLength + 1; i += sizeof(uint))
@ -89,13 +90,13 @@ namespace DotN64.RCP
maps.WriteWord(DRAMAddress + i, maps.ReadWord(PBusAddress + i));
}
Status &= ~StatusRegister.DMABusy;
// TODO: Set interrupt.
Status &= ~Statuses.DMABusy;
rcp.MI.Interrupt |= MIPSInterface.Interrupts.PI;
}
},
new MappingEntry(0x10000000, 0x1FBFFFFF) // Cartridge Domain 1 Address 2.
{
Read = o => BitHelper.FromBigEndian(BitConverter.ToUInt32(rcp.Nintendo64.Cartridge.ROM, (int)o))
Read = o => BitHelper.FromBigEndian(BitConverter.ToUInt32(rcp.nintendo64.Cartridge.ROM, (int)o))
}
};
}

View File

@ -6,14 +6,16 @@ namespace DotN64.RCP
public partial class RealityCoprocessor
{
#region Fields
private readonly Nintendo64 nintendo64;
#endregion
#region Properties
public IReadOnlyList<MappingEntry> MemoryMaps { get; }
public Nintendo64 Nintendo64 { get; }
public SignalProcessor SP { get; }
public SignalProcessor SP { get; } = new SignalProcessor();
public DisplayProcessor DP { get; } = new DisplayProcessor();
public DisplayProcessor DP { get; }
public ParallelInterface PI { get; }
@ -31,7 +33,9 @@ namespace DotN64.RCP
#region Constructors
public RealityCoprocessor(Nintendo64 nintendo64)
{
Nintendo64 = nintendo64;
this.nintendo64 = nintendo64;
SP = new SignalProcessor(this);
DP = new DisplayProcessor(this);
PI = new ParallelInterface(this);
SI = new SerialInterface(this);
AI = new AudioInterface(this);
@ -42,13 +46,13 @@ namespace DotN64.RCP
{
new MappingEntry(0x1FC00000, 0x1FC007BF, false) // PIF Boot ROM.
{
Read = Nintendo64.PIF.MemoryMaps.ReadWord,
Write = Nintendo64.PIF.MemoryMaps.WriteWord
Read = nintendo64.PIF.MemoryMaps.ReadWord,
Write = nintendo64.PIF.MemoryMaps.WriteWord
},
new MappingEntry(0x1FC007C0, 0x1FC007FF, false) // PIF (JoyChannel) RAM.
{
Read = Nintendo64.PIF.MemoryMaps.ReadWord,
Write = Nintendo64.PIF.MemoryMaps.WriteWord
Read = nintendo64.PIF.MemoryMaps.ReadWord,
Write = nintendo64.PIF.MemoryMaps.WriteWord
},
new MappingEntry(0x04600000, 0x046FFFFF, false) // Peripheral interface (PI) registers.
{
@ -96,13 +100,13 @@ namespace DotN64.RCP
},
new MappingEntry(0x00000000, 0x03EFFFFF, false) // RDRAM memory.
{
Read = Nintendo64.RAM.MemoryMaps.ReadWord,
Write = Nintendo64.RAM.MemoryMaps.WriteWord
Read = nintendo64.RAM.MemoryMaps.ReadWord,
Write = nintendo64.RAM.MemoryMaps.WriteWord
},
new MappingEntry(0x03F00000, 0x03FFFFFF, false) // RDRAM registers.
{
Read = Nintendo64.RAM.MemoryMaps.ReadWord,
Write = Nintendo64.RAM.MemoryMaps.WriteWord
Read = nintendo64.RAM.MemoryMaps.ReadWord,
Write = nintendo64.RAM.MemoryMaps.WriteWord
}
};
}

View File

@ -7,7 +7,7 @@ namespace DotN64.RCP
public partial class SignalProcessor
{
[Flags]
public enum StatusRegister : ushort
public enum Statuses : ushort
{
Halt = 1 << 0,
Broke = 1 << 1,
@ -27,7 +27,7 @@ namespace DotN64.RCP
}
[Flags]
private enum WriteStatusRegister
private enum StatusWrites
{
ClearHalt = 1 << 0,
SetHalt = 1 << 1,

View File

@ -9,10 +9,14 @@ namespace DotN64.RCP
{
public partial class SignalProcessor
{
#region Fields
private readonly RealityCoprocessor rcp;
#endregion
#region Properties
public IReadOnlyList<MappingEntry> MemoryMaps { get; }
public StatusRegister Status { get; set; } = StatusRegister.Halt;
public Statuses Status { get; set; } = Statuses.Halt;
public bool DMABusy { get; set; }
@ -25,11 +29,17 @@ namespace DotN64.RCP
/// Data memory.
/// </summary>
public byte[] DMEM { get; } = new byte[0x1000];
/// <summary>
/// Program counter.
/// </summary>
public ushort PC { get; set; }
#endregion
#region Constructors
public SignalProcessor()
public SignalProcessor(RealityCoprocessor rcp)
{
this.rcp = rcp;
MemoryMaps = new[]
{
new MappingEntry(0x04001000, 0x04001FFF) // SP_IMEM read/write.
@ -42,80 +52,82 @@ namespace DotN64.RCP
Read = o => (uint)Status,
Write = (o, v) =>
{
var status = (WriteStatusRegister)v;
var status = (StatusWrites)v;
if (status.HasFlag(WriteStatusRegister.ClearHalt))
Status &= ~StatusRegister.Halt;
if ((status & StatusWrites.ClearHalt) != 0)
Status &= ~Statuses.Halt;
if (status.HasFlag(WriteStatusRegister.SetHalt))
Status |= StatusRegister.Halt;
if ((status & StatusWrites.SetHalt) != 0)
Status |= Statuses.Halt;
if (status.HasFlag(WriteStatusRegister.ClearBroke))
Status &= ~StatusRegister.Broke;
if ((status & StatusWrites.ClearBroke) != 0)
Status &= ~Statuses.Broke;
if (status.HasFlag(WriteStatusRegister.ClearInterrupt)) { /* TODO. */ }
if ((status & StatusWrites.ClearInterrupt) != 0)
rcp.MI.Interrupt &= ~MIPSInterface.Interrupts.SP;
if (status.HasFlag(WriteStatusRegister.SetInterrupt)) { /* TODO. */ }
if ((status & StatusWrites.SetInterrupt) != 0)
rcp.MI.Interrupt |= MIPSInterface.Interrupts.SP;
if (status.HasFlag(WriteStatusRegister.ClearSingleStep))
Status &= ~StatusRegister.SingleStep;
if ((status & StatusWrites.ClearSingleStep) != 0)
Status &= ~Statuses.SingleStep;
if (status.HasFlag(WriteStatusRegister.SetSingleStep))
Status |= StatusRegister.SingleStep;
if ((status & StatusWrites.SetSingleStep) != 0)
Status |= Statuses.SingleStep;
if (status.HasFlag(WriteStatusRegister.ClearInterruptOnBreak))
Status &= ~StatusRegister.InterruptOnBreak;
if ((status & StatusWrites.ClearInterruptOnBreak) != 0)
Status &= ~Statuses.InterruptOnBreak;
if (status.HasFlag(WriteStatusRegister.SetInterruptOnBreak))
Status |= StatusRegister.InterruptOnBreak;
if ((status & StatusWrites.SetInterruptOnBreak) != 0)
Status |= Statuses.InterruptOnBreak;
if (status.HasFlag(WriteStatusRegister.ClearSignal0))
Status &= ~StatusRegister.Signal0;
if ((status & StatusWrites.ClearSignal0) != 0)
Status &= ~Statuses.Signal0;
if (status.HasFlag(WriteStatusRegister.SetSignal0))
Status |= StatusRegister.Signal0;
if ((status & StatusWrites.SetSignal0) != 0)
Status |= Statuses.Signal0;
if (status.HasFlag(WriteStatusRegister.ClearSignal1))
Status &= ~StatusRegister.Signal1;
if ((status & StatusWrites.ClearSignal1) != 0)
Status &= ~Statuses.Signal1;
if (status.HasFlag(WriteStatusRegister.SetSignal1))
Status |= StatusRegister.Signal1;
if ((status & StatusWrites.SetSignal1) != 0)
Status |= Statuses.Signal1;
if (status.HasFlag(WriteStatusRegister.ClearSignal2))
Status &= ~StatusRegister.Signal2;
if ((status & StatusWrites.ClearSignal2) != 0)
Status &= ~Statuses.Signal2;
if (status.HasFlag(WriteStatusRegister.SetSignal2))
Status |= StatusRegister.Signal2;
if ((status & StatusWrites.SetSignal2) != 0)
Status |= Statuses.Signal2;
if (status.HasFlag(WriteStatusRegister.ClearSignal3))
Status &= ~StatusRegister.Signal3;
if ((status & StatusWrites.ClearSignal3) != 0)
Status &= ~Statuses.Signal3;
if (status.HasFlag(WriteStatusRegister.SetSignal3))
Status |= StatusRegister.Signal3;
if ((status & StatusWrites.SetSignal3) != 0)
Status |= Statuses.Signal3;
if (status.HasFlag(WriteStatusRegister.ClearSignal4))
Status &= ~StatusRegister.Signal4;
if ((status & StatusWrites.ClearSignal4) != 0)
Status &= ~Statuses.Signal4;
if (status.HasFlag(WriteStatusRegister.SetSignal4))
Status |= StatusRegister.Signal4;
if ((status & StatusWrites.SetSignal4) != 0)
Status |= Statuses.Signal4;
if (status.HasFlag(WriteStatusRegister.ClearSignal5))
Status &= ~StatusRegister.Signal5;
if ((status & StatusWrites.ClearSignal5) != 0)
Status &= ~Statuses.Signal5;
if (status.HasFlag(WriteStatusRegister.SetSignal5))
Status |= StatusRegister.Signal5;
if ((status & StatusWrites.SetSignal5) != 0)
Status |= Statuses.Signal5;
if (status.HasFlag(WriteStatusRegister.ClearSignal6))
Status &= ~StatusRegister.Signal6;
if ((status & StatusWrites.ClearSignal6) != 0)
Status &= ~Statuses.Signal6;
if (status.HasFlag(WriteStatusRegister.SetSignal6))
Status |= StatusRegister.Signal6;
if ((status & StatusWrites.SetSignal6) != 0)
Status |= Statuses.Signal6;
if (status.HasFlag(WriteStatusRegister.ClearSignal7))
Status &= ~StatusRegister.Signal7;
if ((status & StatusWrites.ClearSignal7) != 0)
Status &= ~Statuses.Signal7;
if (status.HasFlag(WriteStatusRegister.SetSignal7))
Status |= StatusRegister.Signal7;
if ((status & StatusWrites.SetSignal7) != 0)
Status |= Statuses.Signal7;
}
},
new MappingEntry(0x04040018, 0x0404001B) // SP DMA busy.
@ -126,6 +138,10 @@ namespace DotN64.RCP
{
Read = o => BitConverter.ToUInt32(DMEM, (int)o),
Write = (o, v) => BitHelper.Write(DMEM, (int)o, v)
},
new MappingEntry(0x04080000, 0x04080003) // SP PC.
{
Read = o => PC
}
};
}

View File

@ -1,38 +1,21 @@
using System.Collections.Generic;
namespace DotN64.RCP
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class VideoInterface : Interface
{
#region Properties
private ushort verticalInterrupt;
/// <summary>
/// Interrupt when current half-line = V_INTR.
/// </summary>
public ushort VerticalInterrupt
{
get => verticalInterrupt;
set => verticalInterrupt = (ushort)(value & ((1 << 10) - 1));
}
public ushort VerticalInterrupt { get; set; }
public HorizontalVideoRegister HorizontalVideo { get; set; }
private ushort currentVerticalLine;
/// <summary>
/// Current half line, sampled once per line (the lsb of V_CURRENT is constant within a field, and in interlaced modes gives the field number - which is constant for non-interlaced modes).
/// </summary>
public ushort CurrentVerticalLine
{
get => currentVerticalLine;
set
{
currentVerticalLine = (ushort)(value & ((1 << 10) - 1));
// TODO: Clear interrupt line.
}
}
public ushort CurrentVerticalLine { get; set; }
#endregion
#region Constructors
@ -43,7 +26,7 @@ namespace DotN64.RCP
{
new MappingEntry(0x0440000C, 0x0440000F) // VI vertical intr.
{
Write = (o, v) => VerticalInterrupt = (ushort)v
Write = (o, v) => VerticalInterrupt = (ushort)(v & ((1 << 10) - 1))
},
new MappingEntry(0x04400024, 0x04400027) // VI horizontal video.
{
@ -51,7 +34,11 @@ namespace DotN64.RCP
},
new MappingEntry(0x04400010, 0x04400013) // VI current vertical line.
{
Write = (o, v) => CurrentVerticalLine = (ushort)v
Write = (o, v) =>
{
CurrentVerticalLine = (ushort)(v & ((1 << 10) - 1));
rcp.MI.Interrupt &= ~MIPSInterface.Interrupts.VI;
}
}
};
}