- Reduced memory allocation and CPU usage by not using LINQ, foreach in hot paths and unsafe writes instead of array copies.

- 64-bit addressing in mapping entries instead of down-casting.
- Fixed cartridge field length.
- Adjusted ops.
master
Nabile Rahmani 2017-10-13 21:02:42 +02:00
parent f938e58c2e
commit 9da7b68741
16 changed files with 150 additions and 144 deletions

View File

@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;
namespace DotN64.AI
{
using Extensions;
public class AudioInterface
{
#region Fields
@ -46,9 +47,9 @@ namespace DotN64.AI
#endregion
#region Methods
public uint ReadWord(ulong address) => memoryMaps.First(e => e.Contains(address)).ReadWord(address);
public uint ReadWord(ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.First(e => e.Contains(address)).WriteWord(address, value);
public void WriteWord(ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}

View File

@ -6,105 +6,55 @@
{
public enum RegisterIndex : byte
{
/// <summary>
/// Programmable pointer into TLB array.
/// </summary>
/// <summary>Programmable pointer into TLB array.</summary>
Index = 0,
/// <summary>
/// Pseudorandom pointer into TLB array (read only).
/// </summary>
/// <summary>Pseudorandom pointer into TLB array (read only).</summary>
Random = 1,
/// <summary>
/// Low half of TLB entry for even virtual address (VPN).
/// </summary>
/// <summary>Low half of TLB entry for even virtual address (VPN).</summary>
EntryLo0 = 2,
/// <summary>
/// Low half of TLB entry for odd virtual address (VPN).
/// </summary>
/// <summary>Low half of TLB entry for odd virtual address (VPN).</summary>
EntryLo1 = 3,
/// <summary>
/// Pointer to kernel virtual page table entry (PTE) in 32-bit mode.
/// </summary>
/// <summary>Pointer to kernel virtual page table entry (PTE) in 32-bit mode.</summary>
Context = 4,
/// <summary>
/// Page size specification.
/// </summary>
/// <summary>Page size specification.</summary>
PageMask = 5,
/// <summary>
/// Number of wired TLB entries.
/// </summary>
/// <summary>Number of wired TLB entries.</summary>
Wired = 6,
/// <summary>
/// Display of virtual address that occurred an error last.
/// </summary>
/// <summary>Display of virtual address that occurred an error last.</summary>
BadVAddr = 8,
/// <summary>
/// Timer Count.
/// </summary>
/// <summary>Timer Count.</summary>
Count = 9,
/// <summary>
/// High half of TLB entry (including ASID).
/// </summary>
/// <summary>High half of TLB entry (including ASID).</summary>
EntryHi = 10,
/// <summary>
/// Timer Compare Value.
/// </summary>
/// <summary>Timer Compare Value.</summary>
Compare = 11,
/// <summary>
/// Operation status setting.
/// </summary>
/// <summary>Operation status setting.</summary>
Status = 12,
/// <summary>
/// Display of cause of last exception.
/// </summary>
/// <summary>Display of cause of last exception.</summary>
Cause = 13,
/// <summary>
/// Exception Program Counter.
/// </summary>
/// <summary>Exception Program Counter.</summary>
EPC = 14,
/// <summary>
/// Processor Revision Identifier.
/// </summary>
/// <summary>Processor Revision Identifier.</summary>
PRId = 15,
/// <summary>
/// Memory system mode setting.
/// </summary>
/// <summary>Memory system mode setting.</summary>
Config = 16,
/// <summary>
/// Load Linked instruction address display.
/// </summary>
/// <summary>Load Linked instruction address display.</summary>
LLAddr = 17,
/// <summary>
/// Memory reference trap address low bits.
/// </summary>
/// <summary>Memory reference trap address low bits.</summary>
WatchLo = 18,
/// <summary>
/// Memory reference trap address high bits.
/// </summary>
/// <summary>Memory reference trap address high bits.</summary>
WatchHi = 19,
/// <summary>
/// Pointer to Kernel virtual PTE table in 64-bit mode.
/// </summary>
/// <summary>Pointer to Kernel virtual PTE table in 64-bit mode.</summary>
XContext = 20,
/// <summary>
/// Cache parity bits.
/// </summary>
/// <summary>Cache parity bits.</summary>
ParityError = 26,
/// <summary>
/// Cache Error and Status register.
/// </summary>
/// <summary>Cache Error and Status register.</summary>
CacheError = 27,
/// <summary>
/// Cache Tag register low.
/// </summary>
/// <summary>Cache Tag register low.</summary>
TagLo = 28,
/// <summary>
/// Cache Tag register high.
/// </summary>
/// <summary>Cache Tag register high.</summary>
TagHi = 29,
/// <summary>
/// Error Exception Program Counter.
/// </summary>
/// <summary>Error Exception Program Counter.</summary>
ErrorEPC = 30
}
}

View File

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace DotN64.CPU
{
using Extensions;
public partial class VR4300
{
public partial class SystemControlUnit
@ -56,16 +57,7 @@ namespace DotN64.CPU
throw new Exception($"Unknown memory map segment for location 0x{address:X}.");
}
try
{
var value = address;
return memoryMaps.First(e => e.Contains(value));
}
catch (InvalidOperationException e)
{
throw new Exception($"Unknown physical address: 0x{address:X}.", e);
}
return memoryMaps.GetEntry(address);
}
#endregion
}

View File

@ -4,14 +4,19 @@
{
public enum GPRIndex : byte
{
r0,
/// <summary>Hardwired to zero.</summary>
zero,
/// <summary>Assembler temporary (used for pseudo-ops).</summary>
at,
/// <summary>Function return values.</summary>
v0,
v1,
/// <summary>Function arguments.</summary>
a0,
a1,
a2,
a3,
/// <summary>Temporaries.</summary>
t0,
t1,
t2,
@ -20,6 +25,7 @@
t5,
t6,
t7,
/// <summary>Saved temporaries.</summary>
s0,
s1,
s2,
@ -28,13 +34,19 @@
s5,
s6,
s7,
/// <summary>Temporaries.</summary>
t8,
t9,
/// <summary>Reserved for kernel.</summary>
k0,
k1,
/// <summary>Global pointer.</summary>
gp,
/// <summary>Stack pointer.</summary>
sp,
s8,
/// <summary>Frame pointer or saved temporary.</summary>
fp,
/// <summary>Return address.</summary>
ra
}
}

View File

@ -82,38 +82,38 @@ namespace DotN64.CPU
[OpCode.LUI] = i => GPR[i.RT] = (ulong)(i.Immediate << 16),
[OpCode.MTC0] = i => CP0.Registers[i.RD] = GPR[i.RT],
[OpCode.ORI] = i => GPR[i.RT] = GPR[i.RS] | i.Immediate,
[OpCode.LW] = i => GPR[i.RT] = (ulong)(int)(ReadWord((ulong)(short)i.Immediate + GPR[i.RS])),
[OpCode.ANDI] = i => GPR[i.RT] = GPR[i.RS] & i.Immediate,
[OpCode.LW] = i => GPR[i.RT] = (ulong)(int)ReadWord((ulong)(short)i.Immediate + GPR[i.RS]),
[OpCode.ANDI] = i => GPR[i.RT] = (ulong)(i.Immediate & (ushort)GPR[i.RS]),
[OpCode.BEQL] = i => BranchLikely(i, (rs, rt) => rs == rt),
[OpCode.ADDIU] = i => GPR[i.RT] = GPR[i.RS] + (ulong)(short)i.Immediate,
[OpCode.ADDIU] = i => GPR[i.RT] = (ulong)(int)(GPR[i.RS] + (ulong)(short)i.Immediate),
[OpCode.SW] = i => WriteWord((ulong)(short)i.Immediate + GPR[i.RS], (uint)GPR[i.RT]),
[OpCode.BNEL] = i => BranchLikely(i, (rs, rt) => rs != rt),
[OpCode.BNE] = i => Branch(i, (rs, rt) => rs != rt),
[OpCode.BEQ] = i => Branch(i, (rs, rt) => rs == rt),
[OpCode.ADDI] = i => GPR[i.RT] = GPR[i.RS] + (ulong)(short)i.Immediate
[OpCode.ADDI] = i => GPR[i.RT] = (ulong)(int)(GPR[i.RS] + (ulong)(short)i.Immediate)
};
specialOperations = new Dictionary<SpecialOpCode, Action<Instruction>>
{
[SpecialOpCode.ADD] = i => GPR[i.RD] = GPR[i.RS] + GPR[i.RT], // Should we discard the upper word and extend the lower one ?
[SpecialOpCode.ADD] = i => GPR[i.RD] = (ulong)((int)GPR[i.RS] + (int)GPR[i.RT]),
[SpecialOpCode.JR] = i =>
{
delaySlot = PC;
PC = GPR[i.RS];
},
[SpecialOpCode.SRL] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] >> i.SA),
[SpecialOpCode.SRL] = i => GPR[i.RD] = (ulong)((int)GPR[i.RT] >> i.SA),
[SpecialOpCode.OR] = i => GPR[i.RD] = GPR[i.RS] | GPR[i.RT],
[SpecialOpCode.MULTU] = i =>
{
var result = (uint)GPR[i.RS] * (uint)GPR[i.RT];
var result = (ulong)(uint)GPR[i.RS] * (ulong)(uint)GPR[i.RT];
LO = (ulong)(int)result;
HI = (ulong)(int)(result >> 32);
},
[SpecialOpCode.MFLO] = i => GPR[i.RD] = LO,
[SpecialOpCode.SLL] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] << i.SA),
[SpecialOpCode.SLL] = i => GPR[i.RD] = (ulong)((int)GPR[i.RT] << i.SA),
[SpecialOpCode.SUBU] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RS] - GPR[i.RT]),
[SpecialOpCode.XOR] = i => GPR[i.RD] = GPR[i.RS] ^ GPR[i.RT],
[SpecialOpCode.MFHI] = i => GPR[i.RD] = HI,
[SpecialOpCode.ADDU] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RS] + GPR[i.RT]),
[SpecialOpCode.ADDU] = i => GPR[i.RD] = (ulong)((int)GPR[i.RS] + (int)GPR[i.RT]),
[SpecialOpCode.SLTU] = i => GPR[i.RD] = (ulong)(GPR[i.RS] < GPR[i.RT] ? 1 : 0),
[SpecialOpCode.SLLV] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] << (int)(GPR[i.RS] & ((1 << 5) - 1))),
[SpecialOpCode.SRLV] = i => GPR[i.RD] = (ulong)(int)(GPR[i.RT] >> (int)(GPR[i.RS] & ((1 << 5) - 1))),

View File

@ -22,7 +22,7 @@ namespace DotN64
(uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(ROM, 0x14))
};
public string ImageName => Encoding.ASCII.GetString(ROM, 0x20, 0x33 - 0x20);
public string ImageName => Encoding.ASCII.GetString(ROM, 0x20, 0x34 - 0x20);
public byte ManufacturerID => ROM[0x3B];

View File

@ -16,11 +16,13 @@
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@ -55,9 +57,9 @@
<Compile Include="MI\MIPSInterface.cs" />
<Compile Include="CPU\VR4300.RegImmOpCode.cs" />
<Compile Include="PI\PeripheralInterface.CICStatus.cs" />
<Compile Include="Extensions\MappingEntryExtensions.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Documentation\" />
<Folder Include="CPU\" />
<Folder Include="RCP\" />
<Folder Include="CPU\CP0\" />
@ -66,9 +68,7 @@
<Folder Include="SI\" />
<Folder Include="VI\" />
<Folder Include="MI\" />
</ItemGroup>
<ItemGroup>
<None Include="Documentation\U10504EJ7V0UMJ1.pdf" />
<Folder Include="Extensions\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace DotN64.Extensions
{
public static class MappingEntryExtensions
{
#region Methods
public static MappingEntry GetEntry(this IReadOnlyList<MappingEntry> memoryMaps, ulong address)
{
for (int i = 0; i < memoryMaps.Count; i++)
{
var entry = memoryMaps[i];
if (entry.Contains(address))
return entry;
}
throw new Exception($"Unknown physical address: 0x{address:X}.");
}
#endregion
}
}

View File

@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;
namespace DotN64.MI
{
using Extensions;
public class MIPSInterface
{
#region Fields
@ -23,9 +24,9 @@ namespace DotN64.MI
#endregion
#region Methods
public uint ReadWord(ulong address) => memoryMaps.First(e => e.Contains(address)).ReadWord(address);
public uint ReadWord(ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.First(e => e.Contains(address)).WriteWord(address, value);
public void WriteWord(ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}

View File

@ -5,9 +5,9 @@ namespace DotN64
public struct MappingEntry
{
#region Properties
public uint StartAddress { get; set; }
public ulong StartAddress { get; set; }
public uint EndAddress { get; set; }
public ulong EndAddress { get; set; }
public Func<ulong, uint> Read { get; set; }
@ -27,7 +27,7 @@ namespace DotN64
#endregion
#region Methods
public bool Contains(ulong address) => (uint)address >= StartAddress && (uint)address <= EndAddress;
public bool Contains(ulong address) => address >= StartAddress && address <= EndAddress;
public uint ReadWord(ulong address) => Read(OffsetAddress ? address - StartAddress : address);

View File

@ -28,8 +28,6 @@ namespace DotN64
public MIPSInterface MI { get; } = new MIPSInterface();
public byte[] RAM { get; } = new byte[4 * 1024 * 1024]; // 4 MB of base memory (excludes the expansion pack).
public Cartridge Cartridge { get; set; }
#endregion
@ -123,9 +121,9 @@ namespace DotN64
CPU.CP0.Map(ref address).WriteWord(address, writes[i, 1]);
}
for (int i = 0; i < 0x1000; i += sizeof(uint)) // Copying the bootstrap code from the cartridge to the RSP's DMEM.
for (int i = 0x40; i < 0x1000; i += sizeof(uint)) // Copying the bootstrap code from the cartridge to the RSP's DMEM.
{
var dmemAddress = (ulong)(0xA4000000 + i);
var dmemAddress = 0xFFFFFFFFA4000000 + (uint)i;
CPU.CP0.Map(ref dmemAddress).WriteWord(dmemAddress, (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(Cartridge.ROM, i)));
}

View File

@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
namespace DotN64.PI
{
using Extensions;
public partial class PeripheralInterface
{
#region Fields
@ -42,7 +43,13 @@ namespace DotN64.PI
Read = o => BitConverter.ToUInt32(RAM, (int)o),
Write = (o, v) =>
{
Array.Copy(BitConverter.GetBytes(v), 0, RAM, (int)o, sizeof(uint));
unsafe
{
fixed (byte* data = &RAM[(int)o])
{
*(uint*)data = 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.
@ -87,9 +94,9 @@ namespace DotN64.PI
private void ResetController() { /* TODO: Implement. */ }
public uint ReadWord(ulong address) => memoryMaps.First(e => e.Contains(address)).ReadWord(address);
public uint ReadWord(ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.First(e => e.Contains(address)).WriteWord(address, value);
public void WriteWord(ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}

View File

@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;
namespace DotN64.RCP
{
using Extensions;
public partial class RealityCoprocessor
{
public class DisplayProcessor
@ -24,10 +25,10 @@ namespace DotN64.RCP
}
#endregion
#region Methods
public uint ReadWord(ulong address) => memoryMaps.First(e => e.Contains(address)).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.First(e => e.Contains(address)).WriteWord(address, value);
#region Methods
public uint ReadWord(ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}

View File

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace DotN64.RCP
{
using Extensions;
public partial class RealityCoprocessor
{
public class SignalProcessor
@ -13,9 +14,9 @@ namespace DotN64.RCP
#endregion
#region Properties
public uint StatusRegister { get; set; } = 1;
public uint Status { get; set; } = 1;
public uint DMABusyRegister { get; set; }
public uint DMABusy { get; set; }
public byte[] IMEM { get; } = new byte[0x1000];
@ -30,30 +31,48 @@ namespace DotN64.RCP
new MappingEntry(0x04001000, 0x04001FFF) // SP_IMEM read/write.
{
Read = o => BitConverter.ToUInt32(IMEM, (int)o),
Write = (o, v) => Array.Copy(BitConverter.GetBytes(v), 0, IMEM, (int)o, sizeof(uint))
Write = (o, v) =>
{
unsafe
{
fixed (byte* data = &IMEM[(int)o])
{
*(uint*)data = v;
}
}
}
},
new MappingEntry(0x04040010, 0x04040013) // SP status.
{
Read = o => StatusRegister,
Write = (o, v) => StatusRegister = v
Read = o => Status,
Write = (o, v) => Status = v
},
new MappingEntry(0x04040018, 0x0404001B) // SP DMA busy.
{
Read = o => DMABusyRegister
Read = o => DMABusy
},
new MappingEntry(0x04000000, 0x04000FFF) // SP_DMEM read/write.
{
Read = o => BitConverter.ToUInt32(DMEM, (int)o),
Write = (o, v) => Array.Copy(BitConverter.GetBytes(v), 0, DMEM, (int)o, sizeof(uint))
Write = (o, v) =>
{
unsafe
{
fixed (byte* data = &DMEM[(int)o])
{
*(uint*)data = v;
}
}
}
}
};
}
#endregion
#region Methods
public uint ReadWord(ulong address) => memoryMaps.First(e => e.Contains(address)).ReadWord(address);
public uint ReadWord(ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.First(e => e.Contains(address)).WriteWord(address, value);
public void WriteWord(ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}

View File

@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;
namespace DotN64.SI
{
using Extensions;
public partial class SerialInterface
{
#region Fields
@ -27,10 +28,10 @@ namespace DotN64.SI
}
#endregion
#region Methods
public uint ReadWord(ulong address) => memoryMaps.First(e => e.Contains(address)).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.First(e => e.Contains(address)).WriteWord(address, value);
#region Methods
public uint ReadWord(ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}

View File

@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;
namespace DotN64.VI
{
using Extensions;
public partial class VideoInterface
{
#region Fields
@ -59,10 +60,10 @@ namespace DotN64.VI
}
#endregion
#region Methods
public uint ReadWord(ulong address) => memoryMaps.First(e => e.Contains(address)).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.First(e => e.Contains(address)).WriteWord(address, value);
#region Methods
public uint ReadWord(ulong address) => memoryMaps.GetEntry(address).ReadWord(address);
public void WriteWord(ulong address, uint value) => memoryMaps.GetEntry(address).WriteWord(address, value);
#endregion
}
}