* VR4300.Exceptions.cs: Added a custom exception.

* VR4300.cs: Fixed SLLV/SRLV not downcasting a register source.

* Debugger.InstructionFormat.cs: Added some more formatting
  exceptions.

* Program.cs:
* Cartridge.cs:
* DotN64.csproj:
* RDRAM.cs:
* PeripheralInterface.cs:
* RDRAM.ConfigIndex.cs:
* RDRAM.ConfigRegister.cs:
* PeripheralInterface.CIC.cs:
* PeripheralInterface.CICStatus.cs:
* PeripheralInterface.DeviceState.cs:
* RealityCoprocessor.RDRAMInterface.cs:
* RealityCoprocessor.PeripheralInterface.cs:

* Nintendo64.cs: Added peripherals and rewiring.

* RealityCoprocessor.PeripheralInterface.StatusRegister.cs: Moved
  write register constants to an enum, too.

* BitHelper.cs: Better method signatures.
master
Nabile Rahmani 2017-12-18 08:50:25 +01:00
parent 23b06e69b5
commit 17dbe71cef
21 changed files with 510 additions and 239 deletions

View File

@ -0,0 +1,28 @@
using System;
namespace DotN64.CPU
{
public partial class VR4300
{
public class UnimplementedOperationException : Exception
{
#region Properties
public Instruction Instruction { get; }
#endregion
#region Constructors
public UnimplementedOperationException(Instruction instruction)
: base($"Unimplemented opcode ({GetOpCodeType(instruction).Name} {FormatOpCodeBits(instruction)}) from instruction 0x{(uint)instruction:X8}.")
{
Instruction = instruction;
}
#endregion
#region Methods
private static Type GetOpCodeType(Instruction instruction) => instruction.Special?.GetType() ?? instruction.RegImm?.GetType() ?? instruction.OP.GetType();
private static string FormatOpCodeBits(Instruction instruction) => "0b" + Convert.ToString((byte?)instruction.Special ?? (byte?)instruction.RegImm ?? (byte)instruction.OP, 2);
#endregion
}
}
}

View File

@ -94,7 +94,7 @@ namespace DotN64.CPU
[Instruction.FromOpCode(OpCode.MTC0)] = i => CP0.Registers[i.RD] = GPR[i.RT],
[Instruction.FromOpCode(OpCode.ORI)] = i => GPR[i.RT] = GPR[i.RS] | i.Immediate,
[Instruction.FromOpCode(OpCode.LW)] = i => GPR[i.RT] = (ulong)(int)ReadWord((ulong)(short)i.Immediate + GPR[i.RS]),
[Instruction.FromOpCode(OpCode.ANDI)] = i => GPR[i.RT] = (ulong)(i.Immediate & (ushort)GPR[i.RS]),
[Instruction.FromOpCode(OpCode.ANDI)] = i => GPR[i.RT] = i.Immediate & GPR[i.RS],
[Instruction.FromOpCode(OpCode.BEQL)] = i => BranchLikely(i, (rs, rt) => rs == rt),
[Instruction.FromOpCode(OpCode.ADDIU)] = i => GPR[i.RT] = (ulong)(int)(GPR[i.RS] + (ulong)(short)i.Immediate),
[Instruction.FromOpCode(OpCode.SW)] = i => WriteWord((ulong)(short)i.Immediate + GPR[i.RS], (uint)GPR[i.RT]),
@ -140,8 +140,8 @@ namespace DotN64.CPU
[Instruction.FromOpCode(SpecialOpCode.MFHI)] = i => GPR[i.RD] = HI,
[Instruction.FromOpCode(SpecialOpCode.ADDU)] = i => GPR[i.RD] = (ulong)((int)GPR[i.RS] + (int)GPR[i.RT]),
[Instruction.FromOpCode(SpecialOpCode.SLTU)] = i => GPR[i.RD] = (ulong)(GPR[i.RS] < GPR[i.RT] ? 1 : 0),
[Instruction.FromOpCode(SpecialOpCode.SLLV)] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] << (int)(GPR[i.RS] & ((1 << 5) - 1))),
[Instruction.FromOpCode(SpecialOpCode.SRLV)] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] >> (int)(GPR[i.RS] & ((1 << 5) - 1))),
[Instruction.FromOpCode(SpecialOpCode.SLLV)] = i => GPR[i.RD] = (ulong)(int)((uint)GPR[i.RT] << (int)(GPR[i.RS] & ((1 << 5) - 1))),
[Instruction.FromOpCode(SpecialOpCode.SRLV)] = i => GPR[i.RD] = (ulong)(int)((uint)GPR[i.RT] >> (int)(GPR[i.RS] & ((1 << 5) - 1))),
[Instruction.FromOpCode(SpecialOpCode.AND)] = i => GPR[i.RD] = GPR[i.RS] & GPR[i.RT],
[Instruction.FromOpCode(SpecialOpCode.SLT)] = i => GPR[i.RD] = GPR[i.RS] < GPR[i.RT] ? (ulong)1 : 0,
[Instruction.FromOpCode(RegImmOpCode.BGEZAL)] = i => Branch(i, (rs, rt) => rs >= 0, true),
@ -179,7 +179,7 @@ namespace DotN64.CPU
if (operations.TryGetValue(instruction.ToOpCode(), out var operation))
operation(instruction);
else
throw new Exception($"Unknown opcode from instruction 0x{(uint)instruction:X8}.");
throw new UnimplementedOperationException(instruction);
}
public void Step()

View File

@ -5,21 +5,23 @@ using System.Text;
namespace DotN64
{
using Helpers;
public class Cartridge
{
#region Properties
public byte[] ROM { get; set; }
public uint ClockRate => (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(ROM, 0x04));
public uint ClockRate => BitHelper.FromBigEndian(BitConverter.ToUInt32(ROM, 0x04));
public uint BootAddressOffset => (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(ROM, 0x08));
public uint BootAddressOffset => BitHelper.FromBigEndian(BitConverter.ToUInt32(ROM, 0x08));
public uint ReleaseOffset => (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(ROM, 0x0C));
public uint ReleaseOffset => BitHelper.FromBigEndian(BitConverter.ToUInt32(ROM, 0x0C));
public uint[] CRC => new[]
{
(uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(ROM, 0x10)),
(uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(ROM, 0x14))
BitHelper.FromBigEndian(BitConverter.ToUInt32(ROM, 0x10)),
BitHelper.FromBigEndian(BitConverter.ToUInt32(ROM, 0x14))
};
public string ImageName => Encoding.ASCII.GetString(ROM, 0x20, 0x34 - 0x20);

View File

@ -60,9 +60,17 @@
{
case VR4300.SpecialOpCode.JR:
return Format(instruction, FormatRegister(instruction.RS, cpu));
case VR4300.SpecialOpCode.MFHI:
case VR4300.SpecialOpCode.MFLO:
return Format(instruction, FormatRegister(instruction.RD, cpu));
case VR4300.SpecialOpCode.MULTU:
return Format(instruction, FormatRegister(instruction.RS, cpu), FormatRegister(instruction.RT, cpu));
case VR4300.SpecialOpCode.SLLV:
case VR4300.SpecialOpCode.SRLV:
return Format(instruction, FormatRegister(instruction.RD, cpu), FormatRegister(instruction.RT, cpu), FormatRegister(instruction.RS, cpu));
case VR4300.SpecialOpCode.SLL:
case VR4300.SpecialOpCode.SRL:
return Format(instruction, FormatRegister(instruction.RD, cpu), FormatRegister(instruction.RT, cpu), (sbyte)instruction.SA);
}
switch (instruction.OP)

View File

@ -43,7 +43,6 @@
<Compile Include="RCP\MI\RealityCoprocessor.MIPSInterface.cs" />
<Compile Include="RCP\MI\RealityCoprocessor.MIPSInterface.InitModeRegister.cs" />
<Compile Include="RCP\PI\RealityCoprocessor.PeripheralInterface.cs" />
<Compile Include="RCP\PI\RealityCoprocessor.PeripheralInterface.CICStatus.cs" />
<Compile Include="RCP\PI\RealityCoprocessor.PeripheralInterface.Domain.cs" />
<Compile Include="RCP\PI\RealityCoprocessor.PeripheralInterface.StatusRegister.cs" />
<Compile Include="RCP\RI\RealityCoprocessor.RDRAMInterface.cs" />
@ -72,8 +71,14 @@
<Compile Include="Helpers\BitHelper.cs" />
<Compile Include="RCP\DP\RealityCoprocessor.DisplayProcessor.StatusRegister.cs" />
<Compile Include="RCP\SP\RealityCoprocessor.SignalProcessor.StatusRegister.cs" />
<Compile Include="RCP\RI\RealityCoprocessor.RDRAMInterface.RDRAMConfigRegister.cs" />
<Compile Include="RCP\RI\RealityCoprocessor.RDRAMInterface.RDRAMConfigIndex.cs" />
<Compile Include="CPU\VR4300\VR4300.Exceptions.cs" />
<Compile Include="PIF\PeripheralInterface.cs" />
<Compile Include="PIF\PeripheralInterface.CICStatus.cs" />
<Compile Include="PIF\PeripheralInterface.DeviceState.cs" />
<Compile Include="RDRAM\RDRAM.cs" />
<Compile Include="RDRAM\RDRAM.ConfigIndex.cs" />
<Compile Include="RDRAM\RDRAM.ConfigRegister.cs" />
<Compile Include="PIF\PeripheralInterface.CIC.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="CPU\" />
@ -91,6 +96,8 @@
<Folder Include="CPU\VR4300\CP0\" />
<Folder Include="Diagnostics\" />
<Folder Include="Helpers\" />
<Folder Include="PIF\" />
<Folder Include="RDRAM\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using System.Net;
using System.Runtime.CompilerServices;
namespace DotN64.Helpers
{
@ -25,6 +26,7 @@ namespace DotN64.Helpers
data |= (value & size) << shift;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Write(byte[] data, int index, uint value)
{
fixed (byte* pointer = &data[index])
@ -32,6 +34,12 @@ namespace DotN64.Helpers
*(uint*)pointer = value;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint FromBigEndian(uint value) => (uint)IPAddress.NetworkToHostOrder((int)value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ToBigEndian(uint value) => (uint)IPAddress.HostToNetworkOrder((int)value);
#endregion
}
}

View File

@ -15,7 +15,9 @@ namespace DotN64
public RealityCoprocessor RCP { get; }
public byte[] RAM { get; } = new byte[0x00400000]; // The base system has 4 MB of RAM installed.
public RDRAM RAM { get; } = new RDRAM(new byte[0x00400000]); // The base system has 4 MB of RAM installed.
public PeripheralInterface PIF { get; }
public Cartridge Cartridge { get; set; }
#endregion
@ -24,17 +26,18 @@ namespace DotN64
public Nintendo64()
{
RCP = new RealityCoprocessor(this);
PIF = new PeripheralInterface(this);
MemoryMaps = new[]
{
new MappingEntry(0x1FC00000, 0x1FC007BF, false) // PIF Boot ROM.
{
Read = RCP.PI.MemoryMaps.ReadWord,
Write = RCP.PI.MemoryMaps.WriteWord
Read = PIF.MemoryMaps.ReadWord,
Write = PIF.MemoryMaps.WriteWord
},
new MappingEntry(0x1FC007C0, 0x1FC007FF, false) // PIF (JoyChannel) RAM.
{
Read = RCP.PI.MemoryMaps.ReadWord,
Write = RCP.PI.MemoryMaps.WriteWord
Read = PIF.MemoryMaps.ReadWord,
Write = PIF.MemoryMaps.WriteWord
},
new MappingEntry(0x04600000, 0x046FFFFF, false) // Peripheral interface (PI) registers.
{
@ -82,13 +85,13 @@ namespace DotN64
},
new MappingEntry(0x00000000, 0x03EFFFFF, false) // RDRAM memory.
{
Read = RCP.RI.MemoryMaps.ReadWord,
Write = RCP.RI.MemoryMaps.WriteWord
Read = RAM.MemoryMaps.ReadWord,
Write = RAM.MemoryMaps.WriteWord
},
new MappingEntry(0x03F00000, 0x03FFFFFF, false) // RDRAM registers.
{
Read = RCP.RI.MemoryMaps.ReadWord,
Write = RCP.RI.MemoryMaps.WriteWord
Read = RAM.MemoryMaps.ReadWord,
Write = RAM.MemoryMaps.WriteWord
}
};
CPU = new VR4300
@ -104,9 +107,7 @@ namespace DotN64
public void PowerOn()
{
CPU.Reset();
if (RCP.PI.BootROM == null)
RCP.PI.EmulateBootROM();
PIF.Reset();
}
public void Run()

View File

@ -0,0 +1,62 @@
namespace DotN64
{
public partial class PeripheralInterface
{
public static class CIC
{
#region Methods
private static uint ComputeCRC32(byte[] data, int offset, int length)
{
var table = new uint[256];
uint c;
for (uint i = 0; i < table.Length; i++)
{
c = i;
for (uint j = 0; j < 8; j++)
{
if ((c & 1) != 0)
c = 0xEDB88320 ^ (c >> 1);
else
c >>= 1;
}
table[i] = c;
}
c = 0 ^ 0xFFFFFFFF;
for (int i = 0; i < length; i++)
{
c = table[(c ^ (data[offset + i])) & 0xFF] ^ (c >> 8);
}
return c ^ 0xFFFFFFFF;
}
public static uint GetSeed(byte[] data, int offset = 0x40, int length = 0x1000 - 0x40)
{
switch (ComputeCRC32(data, offset, length))
{
case 0x6170A4A1: // NUS-6101.
case 0x009E9EA3: // NUS-7102.
return 0x00043F3F;
case 0x90BB6CB5: // NUS-6102.
return 0x00003F3F;
case 0x0B050EE0: // NUS-6103.
return 0x0000783F;
case 0x98BC2C86: // NUS-6105.
return 0x0000913F;
case 0xACC8580A: // NUS-6106.
return 0x0000853F;
case 0x0E018159: // NUS-8303.
return 0x0000DD00;
default:
return 0;
}
}
#endregion
}
}
}

View File

@ -0,0 +1,22 @@
namespace DotN64
{
public partial class PeripheralInterface
{
private enum CICStatus : byte
{
/// <summary>
/// The boot ROM copies the cartridge's bootstrap code [0x40, 0x1000[ into the RSP's DMEM while computing the CIC algorithm on the data.
/// When done, it stores the result in the PIF RAM at [48, 56[.
/// </summary>
Computing = 16,
/// <summary>
/// The CPU waits for the PIF CPU to validate that the cartridge's CIC computed the same result stored in the PIF RAM.
/// </summary>
Waiting = 48,
/// <summary>
/// The CIC check matched, continue booting.
/// </summary>
OK = 128
}
}
}

View File

@ -0,0 +1,72 @@
using System.Collections.Specialized;
namespace DotN64
{
public partial class PeripheralInterface
{
private struct DeviceState
{
#region Fields
private BitVector32 bits;
private static readonly BitVector32.Section ipl2SeedSection = BitVector32.CreateSection(0xFF),
ipl3SeedSection = BitVector32.CreateSection(0xFF, ipl2SeedSection),
resetSection = BitVector32.CreateSection(0x02, ipl3SeedSection),
versionSection = BitVector32.CreateSection(0x02, resetSection),
romSection = BitVector32.CreateSection(0x04, versionSection);
#endregion
#region Properties
public byte IPL2Seed
{
get => (byte)bits[ipl2SeedSection];
set => bits[ipl2SeedSection] = value;
}
public byte IPL3Seed
{
get => (byte)bits[ipl3SeedSection];
set => bits[ipl3SeedSection] = value;
}
public ResetType Reset
{
get => (ResetType)bits[resetSection];
set => bits[resetSection] = (byte)value;
}
public byte Version
{
get => (byte)bits[versionSection];
set => bits[versionSection] = value;
}
public ROMType ROM
{
get => (ROMType)bits[romSection];
set => bits[romSection] = (byte)value;
}
#endregion
#region Operators
public static implicit operator DeviceState(uint data) => new DeviceState { bits = new BitVector32((int)data) };
public static implicit operator uint(DeviceState state) => (uint)state.bits.Data;
#endregion
#region Enumerations
public enum ResetType : byte
{
ColdReset = 0,
NMI = 1
}
public enum ROMType : byte
{
Cartridge = 0,
DiskDrive = 1
}
#endregion
}
}
}

View File

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
namespace DotN64
{
using CPU;
using Extensions;
using Helpers;
public partial class PeripheralInterface
{
#region Fields
private readonly Nintendo64 nintendo64;
private const byte CICStatusOffset = 0x3C, DeviceStateOffset = 0x24;
#endregion
#region Properties
public IReadOnlyList<MappingEntry> MemoryMaps { get; }
public byte[] BootROM { get; set; }
public byte[] RAM { get; } = new byte[64];
private DeviceState DeviceStateFlags
{
get => BitConverter.ToUInt32(RAM, DeviceStateOffset);
set => BitHelper.Write(RAM, DeviceStateOffset, value);
}
#endregion
#region Constructors
public PeripheralInterface(Nintendo64 nintendo64)
{
this.nintendo64 = nintendo64;
MemoryMaps = new[]
{
new MappingEntry(0x1FC00000, 0x1FC007BF) // PIF Boot ROM.
{
Read = o => BitHelper.FromBigEndian(BitConverter.ToUInt32(BootROM, (int)o))
},
new MappingEntry(0x1FC007C0, 0x1FC007FF) // PIF (JoyChannel) RAM.
{
Read = o => BitConverter.ToUInt32(RAM, (int)o),
Write = (o, v) =>
{
BitHelper.Write(RAM, (int)o, v);
OnRAMWritten((int)o);
}
}
};
}
#endregion
#region Methods
/// <summary>
/// Called when a word is written at a specified index in the RAM.
/// Handles actions sent through memory writes.
/// </summary>
private void OnRAMWritten(int index)
{
switch (index)
{
case CICStatusOffset:
if (RAM[index] == (byte)CICStatus.Waiting) // The boot ROM waits for the PIF's CIC check to be OK.
RAM[index] = (byte)CICStatus.OK; // We tell it it's OK by having the loaded word that gets ANDI'd match the immediate value 128, storing non-zero which allows us to exit the BEQL loop.
break;
}
}
private void EmulateBootROM()
{
// Replicating the memory writes to properly initialise the subsystems.
var writes = new uint[,]
{
{ 0x4040010, 0xA },
{ 0x4600010, 0x3 },
{ 0x440000C, 0x3FF },
{ 0x4400024, 0x0 },
{ 0x4400010, 0x0 },
{ 0x4500000, 0x0 },
{ 0x4500004, 0x0 },
{ 0x4600014, 0x40 }, // These four are likely cartridge-specific (PI domain 1 values).
{ 0x4600018, 0xFF803712 },
{ 0x460001C, 0xFFFF8037 },
{ 0x4600020, 0xFFFFF803 },
// Omitted the CIC results.
{ 0x1FC007FC, 0xC0 }
};
for (int i = 0; i < writes.GetLength(0); i++)
{
nintendo64.MemoryMaps.WriteWord(writes[i, 0], writes[i, 1]);
}
if (DeviceStateFlags.ROM == DeviceState.ROMType.Cartridge && nintendo64.Cartridge != null)
{
for (int i = 0x40; i < 0x1000; i += sizeof(uint)) // Copying the bootstrap code from the cartridge to the RSP's DMEM.
{
nintendo64.MemoryMaps.WriteWord((ulong)(0x04000000 + i), BitHelper.FromBigEndian(BitConverter.ToUInt32(nintendo64.Cartridge.ROM, i)));
}
}
nintendo64.CPU.GPR[(int)VR4300.GPRIndex.s3] = (ulong)DeviceStateFlags.ROM;
// TODO: $s4 is the video mode (0 being PAL settings). This value is hard-coded into regional boot ROMs.
nintendo64.CPU.GPR[(int)VR4300.GPRIndex.s5] = (ulong)DeviceStateFlags.Reset;
nintendo64.CPU.GPR[(int)VR4300.GPRIndex.s6] = DeviceStateFlags.IPL3Seed;
nintendo64.CPU.GPR[(int)VR4300.GPRIndex.s7] = DeviceStateFlags.Version;
// Restoring CPU state.
nintendo64.CPU.CP0.Registers[12] = 0x34000000;
nintendo64.CPU.CP0.Registers[16] = 0x6E463;
nintendo64.CPU.GPR[6] = 0xFFFFFFFFA4001F0C;
nintendo64.CPU.GPR[7] = 0xFFFFFFFFA4001F08;
nintendo64.CPU.GPR[8] = 0xC0;
nintendo64.CPU.GPR[10] = 0x40;
nintendo64.CPU.GPR[11] = 0xFFFFFFFFA4000040;
nintendo64.CPU.GPR[20] = 0x1;
nintendo64.CPU.GPR[29] = 0xFFFFFFFFA4001FF0;
nintendo64.CPU.GPR[31] = 0xFFFFFFFFA4001550;
nintendo64.CPU.PC = 0xFFFFFFFFA4000040;
}
public void Reset()
{
if (nintendo64.Cartridge?.ROM.Length >= 0x1000)
DeviceStateFlags = CIC.GetSeed(nintendo64.Cartridge.ROM);
if (BootROM == null)
EmulateBootROM();
}
#endregion
}
}

View File

@ -18,7 +18,7 @@ namespace DotN64
switch (arg)
{
case "--pif-rom":
nintendo64.RCP.PI.BootROM = File.ReadAllBytes(args[++i]);
nintendo64.PIF.BootROM = File.ReadAllBytes(args[++i]);
break;
case "--debug":
case "-d":

View File

@ -1,25 +0,0 @@
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class PeripheralInterface
{
private enum CICStatus : byte
{
/// <summary>
/// The boot ROM copies the cartridge's bootstrap code [0x40, 0x1000[ into the RSP's DMEM while computing the CIC algorithm on the data.
/// When done, it stores the result in the PIF RAM at [48, 56[.
/// </summary>
Computing = 16,
/// <summary>
/// The CPU waits for the PIF CPU to validate that the cartridge's CIC computed the same result stored in the PIF RAM.
/// </summary>
Waiting = 48,
/// <summary>
/// The CIC check matched, continue booting.
/// </summary>
OK = 128
}
}
}
}

View File

@ -1,16 +1,25 @@
namespace DotN64.RCP
using System;
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class PeripheralInterface
{
[System.Flags]
[Flags]
public enum StatusRegister
{
DMABusy = 1 << 0,
IOBusy = 1 << 1,
Error = 1 << 2
}
[Flags]
private enum WriteStatusRegister : byte
{
ResetController = 1 << 0,
ClearInterrupt = 1 << 1
}
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Net;
namespace DotN64.RCP
{
@ -10,18 +9,9 @@ namespace DotN64.RCP
{
public partial class PeripheralInterface : Interface
{
#region Fields
private const byte CICStatusOffset = 60;
private const byte ResetControllerStatus = 1 << 0, ClearInterruptStatus = 1 << 1;
#endregion
#region Properties
public StatusRegister Status { get; set; }
public byte[] BootROM { get; set; }
public byte[] RAM { get; } = new byte[64];
public Domain[] Domains { get; } = new[]
{
new Domain(),
@ -50,29 +40,16 @@ namespace DotN64.RCP
{
MemoryMaps = new[]
{
new MappingEntry(0x1FC00000, 0x1FC007BF) // PIF Boot ROM.
{
Read = o => (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(BootROM, (int)o))
},
new MappingEntry(0x1FC007C0, 0x1FC007FF) // PIF (JoyChannel) RAM.
{
Read = o => BitConverter.ToUInt32(RAM, (int)o),
Write = (o, v) =>
{
BitHelper.Write(RAM, (int)o, v);
if (o == CICStatusOffset && RAM[o] == (byte)CICStatus.Waiting) // The boot ROM waits for the PIF's CIC check to be OK.
RAM[o] = (byte)CICStatus.OK; // We tell it it's OK by having the loaded word that gets ANDI'd match the immediate value 128, storing non-zero which allows us to exit the BEQL loop.
}
},
new MappingEntry(0x04600010, 0x04600013) // PI status.
{
Read = o => (uint)Status,
Write = (o, v) =>
{
if ((v & ResetControllerStatus) != 0) { /* TODO. */ }
var status = (WriteStatusRegister)v;
if ((v & ClearInterruptStatus) != 0) { /* TODO. */ }
if ((status & WriteStatusRegister.ResetController) != 0) { /* TODO. */ }
if ((status & WriteStatusRegister.ClearInterrupt) != 0) { /* TODO. */ }
}
},
new MappingEntry(0x04600014, 0x04600017) // PI dom1 latency.
@ -118,61 +95,11 @@ namespace DotN64.RCP
},
new MappingEntry(0x10000000, 0x1FBFFFFF) // Cartridge Domain 1 Address 2.
{
Read = o => (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(rcp.Nintendo64.Cartridge.ROM, (int)o))
Read = o => BitHelper.FromBigEndian(BitConverter.ToUInt32(rcp.Nintendo64.Cartridge.ROM, (int)o))
}
};
}
#endregion
#region Methods
public void EmulateBootROM()
{
// Replicating the memory writes to properly initialise the subsystems.
var writes = new uint[,]
{
{ 0x4040010, 0xA },
{ 0x4600010, 0x3 },
{ 0x440000C, 0x3FF },
{ 0x4400024, 0x0 },
{ 0x4400010, 0x0 },
{ 0x4500000, 0x0 },
{ 0x4500004, 0x0 },
{ 0x4600014, 0x40 }, // These four are likely cartridge-specific (PI domain 1 values).
{ 0x4600018, 0xFF803712 },
{ 0x460001C, 0xFFFF8037 },
{ 0x4600020, 0xFFFFF803 },
// Omitted the CIC results.
{ 0x1FC007FC, 0xC0 }
};
for (int i = 0; i < writes.GetLength(0); i++)
{
var address = (ulong)writes[i, 0];
rcp.Nintendo64.MemoryMaps.WriteWord(address, writes[i, 1]);
}
for (int i = 0x40; i < 0x1000; i += sizeof(uint)) // Copying the bootstrap code from the cartridge to the RSP's DMEM.
{
var address = (ulong)(0x04000000 + i);
rcp.Nintendo64.MemoryMaps.WriteWord(address, (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(rcp.Nintendo64.Cartridge.ROM, i)));
}
// Restoring CPU state.
rcp.Nintendo64.CPU.CP0.Registers[12] = 0x34000000;
rcp.Nintendo64.CPU.CP0.Registers[16] = 0x6E463;
rcp.Nintendo64.CPU.GPR[6] = 0xFFFFFFFFA4001F0C;
rcp.Nintendo64.CPU.GPR[7] = 0xFFFFFFFFA4001F08;
rcp.Nintendo64.CPU.GPR[8] = 0xC0;
rcp.Nintendo64.CPU.GPR[10] = 0x40;
rcp.Nintendo64.CPU.GPR[11] = 0xFFFFFFFFA4000040;
rcp.Nintendo64.CPU.GPR[20] = 0x1;
rcp.Nintendo64.CPU.GPR[29] = 0xFFFFFFFFA4001FF0;
rcp.Nintendo64.CPU.GPR[31] = 0xFFFFFFFFA4001550;
rcp.Nintendo64.CPU.PC = 0xFFFFFFFFA4000040;
}
#endregion
}
}
}

View File

@ -1,15 +0,0 @@
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class RDRAMInterface
{
public enum RDRAMConfigIndex : byte
{
Zero,
One,
Global
}
}
}
}

View File

@ -1,27 +0,0 @@
using System.Runtime.InteropServices;
namespace DotN64.RCP
{
public partial class RealityCoprocessor
{
public partial class RDRAMInterface
{
[StructLayout(LayoutKind.Sequential)]
public struct RDRAMConfigRegister
{
#region Fields
public uint DeviceType;
public uint DeviceID;
public uint Delay;
public uint Mode;
public uint RefInterval;
public uint RefRow;
public uint RasInterval;
public uint MinInterval;
public uint AddressSelect;
public uint DeviceManufacturer;
#endregion
}
}
}
}

View File

@ -1,9 +1,5 @@
using System;
namespace DotN64.RCP
namespace DotN64.RCP
{
using Helpers;
public partial class RealityCoprocessor
{
public partial class RDRAMInterface : Interface
@ -16,8 +12,6 @@ namespace DotN64.RCP
public ModeRegister Mode { get; set; }
public RefreshRegister Refresh { get; set; }
public RDRAMConfigRegister[] RDRAMConfigs { get; } = new RDRAMConfigRegister[Enum.GetNames(typeof(RDRAMConfigIndex)).Length];
#endregion
#region Constructors
@ -47,69 +41,10 @@ namespace DotN64.RCP
{
Read = a => Refresh,
Write = (a, v) => Refresh = v
},
new MappingEntry(0x00000000, 0x03EFFFFF) // RDRAM memory.
{
Read = o => BitConverter.ToUInt32(rcp.Nintendo64.RAM, (int)o),
Write = (o, v) => BitHelper.Write(rcp.Nintendo64.RAM, (int)o, v)
},
new MappingEntry(0x03F00000, 0x03FFFFFF) // RDRAM registers.
{
Read = o =>
{
if (!GetRDRAMRegisterInfo((uint)o, out var register, out var index))
return 0;
unsafe
{
fixed (void* pointer = &RDRAMConfigs[index.Value])
{
return *((uint*)pointer + register / sizeof(uint));
}
}
},
Write = (o, v) =>
{
if (!GetRDRAMRegisterInfo((uint)o, out var register, out var index))
return;
unsafe
{
fixed (void* pointer = &RDRAMConfigs[index.Value])
{
*((uint*)pointer + register / sizeof(uint)) = v;
}
}
}
}
};
}
#endregion
#region Methods
private bool GetRDRAMRegisterInfo(uint offset, out uint register, out int? index)
{
register = offset & ((1 << 8) - 1);
switch (offset >> 8)
{
case 0x0000:
index = (int)RDRAMConfigIndex.Zero;
break;
case 0x0004:
index = (int)RDRAMConfigIndex.One;
break;
case 0x0800:
index = (int)RDRAMConfigIndex.Global;
break;
default:
index = null;
return false;
}
return true;
}
#endregion
}
}
}

View File

@ -0,0 +1,12 @@
namespace DotN64
{
public partial class RDRAM
{
public enum ConfigIndex : byte
{
Zero,
One,
Global
}
}
}

View File

@ -0,0 +1,24 @@
using System.Runtime.InteropServices;
namespace DotN64
{
public partial class RDRAM
{
[StructLayout(LayoutKind.Sequential)]
public struct ConfigRegister
{
#region Fields
public uint DeviceType;
public uint DeviceID;
public uint Delay;
public uint Mode;
public uint RefInterval;
public uint RefRow;
public uint RasInterval;
public uint MinInterval;
public uint AddressSelect;
public uint DeviceManufacturer;
#endregion
}
}
}

87
DotN64/RDRAM/RDRAM.cs Normal file
View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
namespace DotN64
{
using Helpers;
public partial class RDRAM
{
#region Properties
public IReadOnlyList<MappingEntry> MemoryMaps { get; }
public byte[] Memory { get; }
public ConfigRegister[] Configs { get; } = new ConfigRegister[Enum.GetNames(typeof(ConfigIndex)).Length];
#endregion
#region Constructors
public RDRAM(byte[] memory)
{
Memory = memory;
MemoryMaps = new[]
{
new MappingEntry(0x00000000, 0x03EFFFFF) // RDRAM memory.
{
Read = o => BitConverter.ToUInt32(Memory, (int)o),
Write = (o, v) => BitHelper.Write(Memory, (int)o, v)
},
new MappingEntry(0x03F00000, 0x03FFFFFF) // RDRAM registers.
{
Read = o =>
{
if (!GetRegisterInfo((uint)o, out var register, out var index))
return 0;
unsafe
{
fixed (void* pointer = &Configs[index.Value])
{
return *((uint*)pointer + register);
}
}
},
Write = (o, v) =>
{
if (!GetRegisterInfo((uint)o, out var register, out var index))
return;
unsafe
{
fixed (void* pointer = &Configs[index.Value])
{
*((uint*)pointer + register) = v;
}
}
}
}
};
}
#endregion
#region Methods
private bool GetRegisterInfo(uint offset, out uint register, out int? index)
{
register = (offset & ((1 << 8) - 1)) / sizeof(uint);
switch (offset >> 8)
{
case 0x0000:
index = (int)ConfigIndex.Zero;
break;
case 0x0004:
index = (int)ConfigIndex.One;
break;
case 0x0800:
index = (int)ConfigIndex.Global;
break;
default:
index = null;
return false;
}
return true;
}
#endregion
}
}