Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/home/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ The `Build & run tests` workflow sets both variables (with the ROM directory cac

`tools/vice-testprogs/Highbyte.DotNet6502.ViceTestprogs` runs the VIC-II test programs from the [VICE test programs](https://sourceforge.net/p/vice-emu/code/HEAD/tree/testprogs/) repository against the C64 emulation and compares the picture with the reference screenshots that ship with them. It is a console tool, not part of the test suite: the programs and their references are not in this repository (fetch a suite directory, for example `testprogs/VICII/border`, from the Subversion repository at `https://svn.code.sf.net/p/vice-emu/code/testprogs/VICII/`), and many of the pictures are not expected to match yet.

The tool boots the real ROMs to the `READY.` prompt, loads a `.prg`, runs it from its BASIC `SYS` line and stops at the first write to `$D7FF`, which is how the programs report their result (`$00` passed, `$FF` failed) and end. The frame completed before that write is compared with the reference, colour index by colour index, over the part of the frame both pictures cover. For each program it writes a picture with the reference, the emulator's frame and the differing pixels side by side, and a `results.md` table with the exit code and the number of differing pixels.
The tool boots the real ROMs to the `READY.` prompt, loads a `.prg`, runs it from its BASIC `SYS` line (with the CPU's `FullUnofficial` opcode profile, as the programs are written for the real chip) and stops at the first write to `$D7FF` made with the I/O area banked in, which is how the programs report their result (`$00` passed, `$FF` failed) and end. The frame completed before that write is compared with the reference, colour index by colour index, over the part of the frame both pictures cover. For each program it writes a picture with the reference, the emulator's frame and the differing pixels side by side, and a `results.md` table with the exit code and the number of differing pixels.

```sh
dotnet run --project tools/vice-testprogs/Highbyte.DotNet6502.ViceTestprogs -c Release -- \
Expand Down
2 changes: 1 addition & 1 deletion docs/libraries/core/dotnet6502.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Higher profiles include everything from lower profiles.
| Profile | Meaning |
| ------- | ------- |
| `OfficialOnly` | Only documented MOS 6502 opcodes are available. |
| `StableUnofficial` | Also enables the more predictable undocumented NMOS opcodes commonly used on real 6502/6510 hardware. |
| `StableUnofficial` | Also enables the more predictable undocumented NMOS opcodes commonly used on real 6502/6510 hardware, and `LXA` ($AB) and `ANE` ($8B), whose result depends on a chip-specific value ORed into A: the common `$EE` is used. |
| `ExperimentalUnofficial` | Also enables the currently implemented but less reliable undocumented opcodes used for targeted compatibility testing. |
| `FullUnofficial` | Also enables halt-style unofficial opcodes such as `JAM` / `KIL` that can intentionally jam the CPU until reset. |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public record CpuCompatibilityProfileOption(CpuCompatibilityProfile Profile, str
new CpuCompatibilityProfileOption(
CpuCompatibilityProfile.StableUnofficial,
"Stable unofficial",
"Also enables the predictable NMOS unofficial opcodes commonly used on real 6502/6510 hardware."),
"Also enables the predictable NMOS unofficial opcodes commonly used on real 6502/6510 hardware, and LXA/ANE with the common chip-specific value."),
new CpuCompatibilityProfileOption(
CpuCompatibilityProfile.ExperimentalUnofficial,
"Experimental unofficial",
Expand Down
9 changes: 9 additions & 0 deletions src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ public class C64 : ISystem, ISystemMonitor, ISystemState, ISystemCleanup, ISyste
public byte[] RAM { get; set; } = default!;
public byte[] IO { get; set; } = default!;
public byte CurrentBank { get; private set; }

/// <summary>
/// Whether the I/O area (the VIC-II, SID, colour RAM, CIAs and cartridge I/O at
/// $D000-$DFFF) is visible to the CPU in the current memory configuration, as the processor
/// port and the cartridge lines select it.
/// </summary>
public bool IsIOVisible => _configurationMapsIO[Mem.CurrentConfiguration];
private readonly bool[] _configurationMapsIO = new bool[32];
public Vic2 Vic2 { get; set; } = default!;
public Cia1 Cia1 { get; set; } = default!;
public Cia2 Cia2 { get; set; } = default!;
Expand Down Expand Up @@ -487,6 +495,7 @@ private void SetCurrentAudioProvider(Type? audioProviderType)

private void MapLocationsOnCurrentCPUBank(Memory mem, bool mapIO)
{
_configurationMapsIO[mem.CurrentConfiguration] = mapIO;
// Address 0x00: 6510 CPU data direction register.
mem.MapReader(0x00, IoPortDirectionLoad);
mem.MapWriter(0x00, IoPortDirectionStore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,12 @@ public static void ApplyNmosUndocumented(OpCodeDescriptor?[] table, CpuCompatibi
Read(table, 0x4B, "ALR", AddrMode.I, 2, 2, InstructionCores.Alr, false, indexedDummyReads, documented: false);
Read(table, 0xCB, "AXS", AddrMode.I, 2, 2, InstructionCores.Axs, false, indexedDummyReads, documented: false);

// The two immediate opcodes whose result depends on a chip-specific value, with the
// common one: closer to any real chip than running them as one-byte instructions,
// which derails programs that use them (FLI displayers use LXA #0).
Read(table, 0xAB, "LXA", AddrMode.I, 2, 2, InstructionCores.Lxa, false, indexedDummyReads, documented: false);
Read(table, 0x8B, "ANE", AddrMode.I, 2, 2, InstructionCores.Ane, false, indexedDummyReads, documented: false);

// $EB: undocumented alias of SBC #imm — same core as the official byte.
Read(table, 0xEB, "SBC", AddrMode.I, 2, 2, InstructionCores.SbcNmos, false, indexedDummyReads, documented: false);
}
Expand Down
25 changes: 25 additions & 0 deletions src/libraries/Highbyte.DotNet6502/CpuModels/InstructionCores.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,31 @@ public static ulong Axs(CPU cpu, byte value)
return 0;
}

/// <summary>
/// The value the unstable immediate opcodes LXA and ANE OR into A before their AND. It
/// differs between chips (and with temperature); $EE is the common one, and the one every
/// LXA and ANE vector of the SingleStepTests 6502 corpus shows.
/// </summary>
public const byte UnstableImmediateMagic = 0xEE;

/// <summary>LXA (LAX #imm): A = X = (A OR magic) AND value.</summary>
public static ulong Lxa(CPU cpu, byte value)
{
byte result = (byte)((cpu.A | UnstableImmediateMagic) & value);
cpu.A = result;
cpu.X = result;
BinaryArithmeticHelpers.SetFlagsAfterRegisterLoadIncDec(result, ref cpu.ProcessorStatus);
return 0;
}

/// <summary>ANE (XAA #imm): A = (A OR magic) AND X AND value.</summary>
public static ulong Ane(CPU cpu, byte value)
{
cpu.A = (byte)((cpu.A | UnstableImmediateMagic) & cpu.X & value);
BinaryArithmeticHelpers.SetFlagsAfterRegisterLoadIncDec(cpu.A, ref cpu.ProcessorStatus);
return 0;
}

/// <summary>LAS: A = X = SP = value AND SP.</summary>
public static ulong Las(CPU cpu, byte value)
{
Expand Down
6 changes: 6 additions & 0 deletions src/libraries/Highbyte.DotNet6502/OpCodeId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,12 @@ public enum OpCodeId: byte
// LAS — A = X = SP = memory & SP
LAS_ABS_Y = 0xBB,

// LXA — A = X = (A | magic) & immediate
LXA_I = 0xAB,

// ANE — A = (A | magic) & X & immediate
ANE_I = 0x8B,

// SBC duplicate (identical to SBC_I 0xE9)
SBC_I_EB = 0xEB,
}
6 changes: 6 additions & 0 deletions tests/Highbyte.DotNet6502.Tests/OpCodeInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public void OfficialOnly_Profile_Excludes_Unofficial_OpCodes()
Assert.True(cpu.IsOpCodeDefined((byte)OpCodeId.NOP));
Assert.False(cpu.IsOpCodeDefined((byte)OpCodeId.NOP_ILL_1A));
Assert.False(cpu.IsOpCodeDefined((byte)OpCodeId.ARR_I));
Assert.False(cpu.IsOpCodeDefined((byte)OpCodeId.LXA_I));
Assert.False(cpu.IsOpCodeDefined((byte)OpCodeId.ANE_I));
}

[Fact]
Expand All @@ -42,6 +44,8 @@ public void StableUnofficial_Profile_Includes_Stable_But_Not_Experimental_OpCode
Assert.True(cpu.IsOpCodeDefined((byte)OpCodeId.SBC_I_EB));
Assert.False(cpu.IsOpCodeDefined((byte)OpCodeId.ARR_I));
Assert.False(cpu.IsOpCodeDefined((byte)OpCodeId.LAS_ABS_Y));
Assert.True(cpu.IsOpCodeDefined((byte)OpCodeId.LXA_I));
Assert.True(cpu.IsOpCodeDefined((byte)OpCodeId.ANE_I));
}

[Fact]
Expand All @@ -51,6 +55,8 @@ public void ExperimentalUnofficial_Profile_Includes_Experimental_OpCodes()

Assert.True(cpu.IsOpCodeDefined((byte)OpCodeId.ARR_I));
Assert.True(cpu.IsOpCodeDefined((byte)OpCodeId.LAS_ABS_Y));
Assert.True(cpu.IsOpCodeDefined((byte)OpCodeId.LXA_I));
Assert.True(cpu.IsOpCodeDefined((byte)OpCodeId.ANE_I));
Assert.False(cpu.IsOpCodeDefined((byte)OpCodeId.JAM_02));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public SingleStepVectorTests(Harness harness, ITestOutputHelper output)
}

private const string UnstableNmos =
"unstable undocumented opcode (result depends on a chip-specific 'magic' value and bus timing); not implemented in any profile, the corpus encodes one silicon's behavior";
"unstable undocumented opcode (result depends on bus timing and the chip); not implemented in any profile, the corpus encodes one silicon's behavior";
private const string RockwellBitOps =
"Rockwell/WDC bit instruction (RMB/SMB/BBR/BBS); the emulated NCR 65C02 executes these bytes as 1-cycle NOPs";

Expand All @@ -44,13 +44,11 @@ public SingleStepVectorTests(Harness harness, ITestOutputHelper output)
{
["6502"] = new Dictionary<byte, string>
{
[0x8B] = UnstableNmos, // ANE
[0x93] = UnstableNmos, // SHA (zp),Y
[0x9B] = UnstableNmos, // TAS
[0x9C] = UnstableNmos, // SHY
[0x9E] = UnstableNmos, // SHX
[0x9F] = UnstableNmos, // SHA abs,Y
[0xAB] = UnstableNmos, // LXA
},
["wdc65c02"] = BuildWdcDeviations(),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ private static TestResult RunOne(string suite, string name, string prgPath, stri
var frameHeight = rasterizer.NativeSize.Height;

// The debug register: the test writes its exit code there when its picture is complete.
// Hooked in every bank configuration (the C64 has 32, for the processor port and the
// cartridge lines); the value also goes to RAM, as it would in a configuration without I/O.
// It is in the I/O area, so it is hooked in every bank configuration that shows I/O (the
// C64 has 32, for the processor port and the cartridge lines); in the others a write to
// $D7FF is a write to RAM, as a test that clears memory with I/O banked out makes.
int? exitCode = null;
var exitFrame = -1;
var frame = 0;
void OnDebugWrite(ushort address, byte value)
{
c64.RAM[address] = value;
if (exitCode == null)
{
exitCode = value;
Expand All @@ -119,7 +119,8 @@ void OnDebugWrite(ushort address, byte value)
for (var configuration = 0; configuration < c64.Mem.NumberOfConfigurations; configuration++)
{
c64.Mem.SetMemoryConfiguration(configuration);
c64.Mem.MapWriter(DebugRegister, OnDebugWrite);
if (c64.IsIOVisible)
c64.Mem.MapWriter(DebugRegister, OnDebugWrite);
}
c64.Mem.SetMemoryConfiguration(currentConfiguration);

Expand Down Expand Up @@ -264,6 +265,8 @@ private static C64 BuildC64(string model, string romDir)
AudioEnabled = false,
RenderProviderType = typeof(Vic2Rasterizer),
Vic2RasterizerPerLineSprites = true,
// The test programs are written for the real chip: every opcode it executes.
CpuCompatibilityProfile = CpuCompatibilityProfile.FullUnofficial,
ROMs =
[
new ROM { Name = "kernal", File = "kernal.901227-03.bin" },
Expand Down
Loading