Skip to content

Latest commit

 

History

History
868 lines (729 loc) · 54 KB

File metadata and controls

868 lines (729 loc) · 54 KB

Asm6502 User Guide

This document provides a small user guide for the Asm6502 library.


Table of Contents


Quick Start

Assembling 6502 Code

using Asm6502;
using static Asm6502.Mos6502Factory;

// Create an assembler 
using var asm = new Mos6502Assembler();

asm.Begin(0xC000);  // With a base address (e.g., $C000)

asm.LDX_Imm(0x00);      // LDX #$00
asm.LDY_Imm(0x10);      // LDY #$10
asm.LDA(0x0200, X); // LDA $0200,X
asm.STA(0x0200, X); // STA $0200,X
asm.RTS();          // RTS

asm.End(); // Finalize assembly (resolves labels)

// Get the assembled machine code
var buffer = asm.Buffer;

With a more fluent syntax:

using Asm6502;
using static Asm6502.Mos6502Factory;

// Create an assembler with a base address (e.g., $C000)
using var asm = new Mos6502Assembler(0xC000);

asm
    .Begin()        // Start assembling

    .LDX_Imm(0x00)      // LDX #$00
    .LDY_Imm(0x10)      // LDY #$10
    .LDA(0x0200, X) // LDA $0200,X
    .STA(0x0200, X) // STA $0200,X
    .RTS()          // RTS

    .End(); // Finalize assembly (resolves labels)

// Get the assembled machine code
var buffer = asm.Buffer;

Disassembling 6502 Code

using Asm6502;
using static Asm6502.Mos6502Factory;

var dis = new Mos6502Disassembler(new Mos6502DisassemblerOptions {
    PrintLabelBeforeFirstInstruction = false,
    PrintAddress = true,
    PrintAssemblyBytes = true,
});

string asmText = dis.Disassemble(buffer);
Console.WriteLine(asmText);

Assembling 6502 Code

The Mos6502Assembler class provides a fluent API for generating 6502 machine code. Each method corresponds to a 6502 instruction. Labels and branches are supported and resolved automatically.

Example: Loop with Labels

using var asm = new Mos6502Assembler();

asm
    .Org(0xC000);          // Start assembling at address $C000
asm.Label(out var start)
    .LDX_Imm(0x00)         // X = 0
    .LDY_Imm(0x10);        // Y = 16

asm.Label(out var loop)
    .LDA(0x0200, X)        // LDA $0200,X
    .CMP(0xFF)             // CMP #$FF
    .BEQ(out var skip)     // BEQ skip (forward label)

    .CLC()                 // CLC
    .ADC_Imm(0x01)         // ADC #$01
    .STA(0x0200, X);       // STA $0200,X

asm.Label(skip)
    .INX()                 // INX
    .DEY()                 // DEY
    .BNE(loop);            // BNE loop

asm.Label(out var endLabel)
    .JMP(endLabel)
    .End();

Appending Raw Bytes

You can append arbitrary bytes or fill memory regions:

asm
    .AppendBuffer([0x01, 0x02, 0x03])  // Appends 3 bytes
    .AppendBytes(5, 0xFF);             // Appends 5 bytes of value 0xFF

Disassembling 6502 Code

The Mos6502Disassembler class converts machine code back to readable assembly. Output formatting is highly customizable via Mos6502DisassemblerOptions.

Basic Disassembly

var dis = new Mos6502Disassembler();
string asmText = dis.Disassemble(buffer);
Console.WriteLine(asmText);

Customizing Output

Options include:

  • Show addresses and bytes
  • Label formatting
  • Indentation and padding
  • Custom comments

Example:

var options = new Mos6502DisassemblerOptions {
    PrintAddress = true,
    PrintAssemblyBytes = true,
    IndentSize = 4,
    InstructionTextPaddingLength = 20,
    PrintLabelBeforeFirstInstruction = false,
};
var dis = new Mos6502Disassembler(options);

Addressing Modes

The assembler supports all 6502 addressing modes. Below is a summary of the addressing modes with examples in both assembly and C# syntax:

Addressing Mode ASM Example C# Example Description
Immediate LDA #$10 asm.LDA_Imm(0x10) Load immediate value into A
Zero Page LDA $10 asm.LDA(0x10) Load from zero page address
Zero Page,X LDA $10,X asm.LDA(0x10, X) Load from zero page address + X
Zero Page,Y LDX $10,Y asm.LDX(0x10, Y) Load from zero page address + Y
Absolute LDA $1234 asm.LDA(0x1234) Load from absolute address
Absolute,X LDA $1234,X asm.LDA(0x1234, X) Load from absolute address + X
Absolute,Y LDA $1234,Y asm.LDA(0x1234, Y) Load from absolute address + Y
Indirect JMP ($1234) asm.JMP(_[0x1234]) Jump to address stored at $1234
(Indirect,X) LDA ($10,X) asm.LDA(_[0x10, X]) Load from address at (zero page + X)
(Indirect),Y LDA ($10),Y asm.LDA(_[0x10], Y) Load from address at (zero page) + Y

Notice that immediate values are suffixed with _Imm, and indirect addressing uses the _[] syntax. The registers X and Y are used directly in the method calls.

In order to access the X and Y registers as well as the indirect addressing modes, the following syntax is used in C#:

using static Asm6502.Mos6502Factory;

Advanced Usage

Labels and Branches

Labels can be created and bound to addresses. Branch instructions (e.g., BEQ, BNE) can reference labels, even forward-declared ones. The assembler will resolve all label addresses when End() is called.

asm
    .Label(out var loop)
    .BNE(loop);    

Note that the name of the label is inferred from the variable name if not explicitly provided. In the example above, the label will be named "loop". You can also provide a custom name:

asm
    .Label("CUSTOM_LABEL", out var customLabel)
    .BNE(customLabel);

⚠️ The label name is only used when reporting errors when resolving label addresses.

You can also create forward labels that are resolved later:

asm.LabelForward(out var skipLabel)
    // ... some instructions ...
    .BEQ(skipLabel)    // Branch to SKIP if condition met
    .LDA_Imm(0xFF);    // Load accumulator with 0xFF

asm.Label(skipLabel);  // Bind SKIP label later

Or directly within the branch instruction:

asm
    .BEQ(out var skipLabel) // Branch to SKIP if condition met
    .LDA_Imm(0xFF);         // Load accumulator with 0xFF
asm.Label(skipLabel)        // Bind SKIP label later

Expressions

It is possible to use expressions for memory addresses and immediate values, such as:

  • Storing the low byte and high byte of an address
    asm
        .Begin()
        .LabelForward(out var forwardLabel)
        .LDA_Imm(forwardLabel.LowByte())
        .STA(0x1000)
        .LDA_Imm(forwardLabel.HighByte())
        .STA(0x1001)
        .RTS()
        .Label(forwardLabel)
        .End();
  • Storing the difference of value between two labels
    asm
      .Begin()
      .Label(out var startLabel)
      .LabelForward(out var endLabel)
      .LDA_Imm((endLabel - startLabel).LowByte()) // Store the size of this code
      .STA(0x1000)
      .RTS()
      .Label(endLabel)
      .End();
  • The address of label + a const value
    asm
      .Begin()
      .LabelForward(out var endLabel)
      .LDA(endLabel + 1) // Load A with the address of endLabel + 1
      .RTS()
      .Label(endLabel)
      .End();
  • Appending as raw data an address or the difference between two labels
    asm
      .Begin()
      .Label(out var startLabel)
      .LabelForward(out var endLabel)
      .RTS()
      .Label(endLabel)
      .Append(startLabel) // The address of the label
      .Append((endLabel - startLabel).LowByte()) // The size of the code
      .End();

Customizing Disassembly Output

The disassembler supports extensive customization:

  • Custom label formatting: Provide a delegate to format labels.
  • Instruction comments: Add comments per instruction.
  • Pre/Post instruction hooks: Inject text before/after each instruction.

Example:

var options = new Mos6502DisassemblerOptions {
    TryFormatLabel = (offset, span, out int charsWritten) => {
        // Custom label formatting logic
        charsWritten = $"LBL_{offset:X4}".AsSpan().CopyTo(span) ? 9 : 0;
        return charsWritten > 0;
    },
    TryFormatComment = (offset, inst, span, out int charsWritten) => {
        // Add a comment for each instruction
        charsWritten = $"; Offset {offset:X4}".AsSpan().CopyTo(span) ? 13 : 0;
        return charsWritten > 0;
    }
};

Assembler Debug Line Information

The assembler can generate debug line information that includes C# source file names and line numbers. It simply requires to add a derived class from IMos6502AssemblerDebugMap or use the default Mos6502AssemblerDebugMap implementation.

var debugMap = new Mos6502AssemblerDebugMap();
var asm = new Mos6502Assembler() 
{
    DebugMap = debugMap
};

var forwardLabel = new Mos6502Label();

asm
    .Begin(0xC000)
    .LDA(0x5) // Zero page load
    .STA(0x1000)
    .Label(out var label)
    .LDA(_[0x1, X])
    .LDA(_[0x2], Y)
    .BEQ(label)
    .BCC(forwardLabel)
    .RTS()
    .Label(forwardLabel)
    .End();

var toString = asm.DebugMap.ToString();
Console.WriteLine(toString);

will print something like:

$c000 ORG BEGIN: TestSimple
$c000 LINE: {ProjectDirectory}Mos6502AssemblerSpecialTests.cs:59
$c002 LINE: {ProjectDirectory}Mos6502AssemblerSpecialTests.cs:60
$c005 LINE: {ProjectDirectory}Mos6502AssemblerSpecialTests.cs:62
$c007 LINE: {ProjectDirectory}Mos6502AssemblerSpecialTests.cs:63
$c009 LINE: {ProjectDirectory}Mos6502AssemblerSpecialTests.cs:64
$c00b LINE: {ProjectDirectory}Mos6502AssemblerSpecialTests.cs:65
$c00d LINE: {ProjectDirectory}Mos6502AssemblerSpecialTests.cs:66
$c00e END

Org directive

The org directive allows to set the current assembly address to a specific value.

using var asm = new Mos6502Assembler();
asm.Begin(0xC000); // Resets the assembler state and sets the org to 0xC000
asm.LDA_Imm(0);
asm.Label(out var label1);
asm.LabelForward(out var label2);
asm.JMP(label2);

// Fill with NOPs to reach address 0xC010
asm.AppendBytes(0x10 - asm.SizeInBytes, (byte)Mos6502OpCode.NOP_Implied);
        
asm.Org(0xC010); // Usage of org directive to set the base address to 0xC010
asm.LDA_Imm(1);
asm.Label(label2);
asm.JMP(label1);
asm.End();

This will produce the following code:

C000  A9 00      LDA #$00

LL_02:
C002  4C 12 C0   JMP LL_01

C005  EA         NOP
C006  EA         NOP
C007  EA         NOP
C008  EA         NOP
C009  EA         NOP
C00A  EA         NOP
C00B  EA         NOP
C00C  EA         NOP
C00D  EA         NOP
C00E  EA         NOP
C00F  EA         NOP
C010  A9 01      LDA #$01

LL_01:
C012  4C 02 C0   JMP LL_02

Code Relocator

Asm6502 includes a code relocation analyzer, Asm6502.Relocator.CodeRelocator, that emulates your 6502/6510 program to:

  • discover accessed addresses and zero-page usage
  • determine which code bytes are self-modified or contribute to addressing
  • compute a safe relocation mapping and optionally remap zero-page usage

The relocator embeds its own 64 KiB RAM and a 6502/6510 CPU. It can trace memory reads/writes, branches, indirect accesses, and stack usage, and it can relocate your code to another base address while keeping it functional.

Key types:

  • CodeRelocator – main API to analyze and relocate a program buffer
  • CodeRelocationConfig – input buffer and configuration (program start, analysis range, ZP relocation)
  • CodeRelocationTarget – where to relocate (new base address and zero-page range)
  • CodeRelocationDiagnosticBag – diagnostics, warnings and errors produced during analysis/relocation

Minimal example:

using Asm6502.Relocator;

// Prepare configuration
var config = new CodeRelocationConfig
{
    ProgramAddress = 0xC000,
    ProgramBytes   = programBytes,   // your assembled bytes
    ZpRelocate     = true,           // enable zero-page relocation if needed (the default)
};

var relocator = new CodeRelocator(config);

// Analyze by running a subroutine from its entry point until RTS
// Optionally cap cycles to guard against infinite loops
relocator.RunSubroutineAt(0xC000, maxCycles: 500_000);

// Request relocation to a new address and ZP range
var relocated = relocator.Relocate(new CodeRelocationTarget
{
    Address = 0x2000,               // target base, same low-byte as original recommended
    ZpRange = new RamZpRange(0x80, 0x20) // relocate ZP usage to $80-$9F
});

// Diagnostics and mapping can be inspected or printed
if (relocator.Diagnostics.Messages.Count > 0)
{
    foreach (var m in relocator.Diagnostics.Messages) Console.WriteLine(m);
}

Notes and constraints:

  • Analysis range defaults to [ProgramAddress, ProgramAddress + ProgramBytes.Length - 1]. You can override it in CodeRelocationConfig.
  • The relocator emulates code paths you run via RunSubroutineAt. Unreached code won’t be analyzed.
  • Write-then-read self-modifying sequences are tracked, so absolute operands modified at runtime will be resolved correctly when relocating.
  • If ZpRelocate = true, a non-empty ZpRange must be provided at relocation time.

Recommended workflow:

  1. Initialize with original buffer (Initialize or constructor).
  2. Execute entry points with RunSubroutineAt(address, maxCycles) for init/play/IRQ/NMI.
  3. Call Relocate(target) once analysis is done.
  4. Optionally use SafeRamRanges to mark hardware or memory areas that must not be used during analysis (e.g., $D400 SID regs).

Advanced usage pattern (SID-like init/play/IRQ):

// High-level flow similar to a SID driver:
// - Run Init(A = subtune) once
// - Run Play many times; optionally detect IRQ/NMI vectors (e.g., $0314/$0318 or $FFFE/$FFFA)

relocator.Diagnostics.LogLevel = CodeRelocationDiagnosticKind.Info;

for (int song = 0; song < songs; song++)
{
    relocator.Cpu.A = (byte)song; // pass subtune in A
    relocator.RunSubroutineAt(initAddress, maxCycles: 1_000_000);

    for (int i = 0; i < playSteps; i++)
    {
        // Optionally probe vectors written by the code to discover dynamic play entry
        if (relocator.CheckIndirectAddressAndProbeIfRelocatable(0x0314, out var irqAddr) ||
            relocator.CheckIndirectAddressAndProbeIfRelocatable(0xFFFE, out irqAddr))
        {
            relocator.RunSubroutineAt(irqAddr, maxCycles: 20_000);
        }
        else
        {
            relocator.RunSubroutineAt(playAddress, maxCycles: 20_000);
        }

        // Optionally analyze an NMI “digi” routine if detected
        if (relocator.CheckIndirectAddressAndProbeIfRelocatable(0x0318, out var nmiAddr) ||
            relocator.CheckIndirectAddressAndProbeIfRelocatable(0xFFFA, out nmiAddr))
        {
            relocator.RunSubroutineAt(nmiAddr, maxCycles: 1_000);
        }
    }
}

// Once analyzed, relocate
var target = new CodeRelocationTarget
{
    Address = 0x2000,
    ZpRange = new RamZpRange(0x80, 0x10)
};
var relocatedBytes = relocator.Relocate(target);

// Optionally print relocation map and diagnostics
relocator.PrintRelocationMap(Console.Out);

CPU Emulator (6502/6510)

Asm6502 provides a cycle-accurate CPU core with two variants:

  • Mos6502Cpu implements the documented 6502 instruction set
  • Mos6510Cpu extends it with all undocumented opcodes (recommended)

Both cores use a pluggable 64 KiB memory bus defined by IMos6502CpuMemoryBus.

Quick start:

using Asm6502;

// Minimal 64 KiB RAM bus
public sealed class RamBus : IMos6502CpuMemoryBus
{
  private readonly byte[] _ram;
  public RamBus(byte[] ram) => _ram = ram;
  public byte Read(ushort address) => _ram[address];
  public void Write(ushort address, byte value) => _ram[address] = value;
}

// Create memory and a tiny program at $C000: LDA #$01; ADC #$01;
var mem = new byte[65536];
mem[0xC000] = 0xA9;
mem[0xC001] = 0x01; // LDA #$01
mem[0xC002] = 0x69;
mem[0xC003] = 0x01; // ADC #$01

// Set Reset vector to $C000
mem[0xFFFC] = 0x00;
mem[0xFFFD] = 0xC0;

var cpu = new Mos6510Cpu(new RamBus(mem));
cpu.Reset(); // fetch reset vector and begin executing
cpu.Steps(2); // Run 2 instructions (LDA and ADC)
var a = cpu.A; // 2
// cpu.PC is at 0xC004 (next instruction)

Useful members:

  • Step() / Steps(n): step by instruction; Cycle() / Cycles(n): step by bus cycle
  • InstructionCycles: cycles consumed by the last completed instruction
  • TimestampCounter: monotonically increasing bus-cycle counter
  • Nmi(), Irq(), Reset() convenience helpers; RaiseNmi/Irq/Reset() to request on next cycle

Memory-bus expectations:

  • Read-Modify-Write instructions perform read + two writes (read, write-back, write-modified)
  • Stack activity occurs in page $0100–$01FF; vectors are read from $FFFA/$FFFC/$FFFE
  • Unattached bus reads default to NOP (0xEA) internally

6510 Support

The library also supports the 6510 CPU, a 6502 variant used in the Commodore 64. In addition to the assembler and disassembler, you can emulate and run code with the CPU core:

  • Mos6510Assembler / Mos6510Disassembler: assembly and disassembly with undocumented opcodes
  • Mos6510Cpu: cycle-accurate execution with full opcode coverage

Use Mos6510Cpu when you need to execute code and measure exact cycle counts; if you only need documented opcodes, Mos6502Cpu is available as well.

Tips and Best Practices

  • Always call End() after assembling to resolve labels and finalize the buffer.
  • Instead of raw addresses you can use labels for all branch targets; the assembler will resolve them.
  • Customize disassembly output for integration with tools or documentation.
  • Use AppendBuffer and AppendBytes for embedding data or padding.
  • Dispose the assembler (using var asm = ...) to release internal buffers.

Supported instructions

6502 Instructions

The following instructions are supported by the Mos6502Assembler and Mos6510Assembler classes:

Byte Instruction C# Syntax Description
0x6d ADC address asm.ADC(address); Add with carry
0x7d ADC address, X asm.ADC(address, X); Add with carry
0x79 ADC address, Y asm.ADC(address, Y); Add with carry
0x69 ADC #value asm.ADC_Imm(value); Add with carry
0x61 ADC (zp, X) asm.ADC(_[zp, X]); Add with carry
0x71 ADC (zp), Y asm.ADC(_[zp], Y); Add with carry
0x65 ADC zp asm.ADC(zp); Add with carry
0x75 ADC zp, X asm.ADC(zp, X); Add with carry
0x2d AND address asm.AND(address); Logical AND
0x3d AND address, X asm.AND(address, X); Logical AND
0x39 AND address, Y asm.AND(address, Y); Logical AND
0x29 AND #value asm.AND_Imm(value); Logical AND
0x21 AND (zp, X) asm.AND(_[zp, X]); Logical AND
0x31 AND (zp), Y asm.AND(_[zp], Y); Logical AND
0x25 AND zp asm.AND(zp); Logical AND
0x35 AND zp, X asm.AND(zp, X); Logical AND
0x0e ASL address asm.ASL(address); Arithmetic shift left
0x1e ASL address, X asm.ASL(address, X); Arithmetic shift left
0x0a ASL A asm.ASL(A); Arithmetic shift left
0x06 ASL zp asm.ASL(zp); Arithmetic shift left
0x16 ASL zp, X asm.ASL(zp, X); Arithmetic shift left
0x90 BCC label asm.BCC(label); Branch if carry clear
0xb0 BCS label asm.BCS(label); Branch if carry set
0xf0 BEQ label asm.BEQ(label); Branch if equal
0x2c BIT address asm.BIT(address); Bit test
0x24 BIT zp asm.BIT(zp); Bit test
0x30 BMI label asm.BMI(label); Branch if minus
0xd0 BNE label asm.BNE(label); Branch if not equal
0x10 BPL label asm.BPL(label); Branch if positive
0x00 BRK asm.BRK(); Break / Software Interrupt
0x50 BVC label asm.BVC(label); Branch if overflow clear
0x70 BVS label asm.BVS(label); Branch if overflow set
0x18 CLC asm.CLC(); Clear carry
0xd8 CLD asm.CLD(); Clear decimal mode
0x58 CLI asm.CLI(); Clear interrupt disable
0xb8 CLV asm.CLV(); Clear overflow flag
0xcd CMP address asm.CMP(address); Compare
0xdd CMP address, X asm.CMP(address, X); Compare
0xd9 CMP address, Y asm.CMP(address, Y); Compare
0xc9 CMP #value asm.CMP_Imm(value); Compare
0xc1 CMP (zp, X) asm.CMP(_[zp, X]); Compare
0xd1 CMP (zp), Y asm.CMP(_[zp], Y); Compare
0xc5 CMP zp asm.CMP(zp); Compare
0xd5 CMP zp, X asm.CMP(zp, X); Compare
0xec CPX address asm.CPX(address); Compare X register
0xe0 CPX #value asm.CPX_Imm(value); Compare X register
0xe4 CPX zp asm.CPX(zp); Compare X register
0xcc CPY address asm.CPY(address); Compare Y register
0xc0 CPY #value asm.CPY_Imm(value); Compare Y register
0xc4 CPY zp asm.CPY(zp); Compare Y register
0xce DEC address asm.DEC(address); Decrement memory
0xde DEC address, X asm.DEC(address, X); Decrement memory
0xc6 DEC zp asm.DEC(zp); Decrement memory
0xd6 DEC zp, X asm.DEC(zp, X); Decrement memory
0xca DEX asm.DEX(); Decrement X register
0x88 DEY asm.DEY(); Decrement Y register
0x4d EOR address asm.EOR(address); Logical Exclusive OR (XOR)
0x5d EOR address, X asm.EOR(address, X); Logical Exclusive OR (XOR)
0x59 EOR address, Y asm.EOR(address, Y); Logical Exclusive OR (XOR)
0x49 EOR #value asm.EOR_Imm(value); Logical Exclusive OR (XOR)
0x41 EOR (zp, X) asm.EOR(_[zp, X]); Logical Exclusive OR (XOR)
0x51 EOR (zp), Y asm.EOR(_[zp], Y); Logical Exclusive OR (XOR)
0x45 EOR zp asm.EOR(zp); Logical Exclusive OR (XOR)
0x55 EOR zp, X asm.EOR(zp, X); Logical Exclusive OR (XOR)
0xee INC address asm.INC(address); Increment memory
0xfe INC address, X asm.INC(address, X); Increment memory
0xe6 INC zp asm.INC(zp); Increment memory
0xf6 INC zp, X asm.INC(zp, X); Increment memory
0xe8 INX asm.INX(); Increment X register
0xc8 INY asm.INY(); Increment Y register
0x4c JMP address asm.JMP(address); Unconditional Jump
0x6c JMP (address) asm.JMP(_[address]); Unconditional Jump
0x20 JSR address asm.JSR(address); Jump to subroutine
0xad LDA address asm.LDA(address); Load accumulator
0xbd LDA address, X asm.LDA(address, X); Load accumulator
0xb9 LDA address, Y asm.LDA(address, Y); Load accumulator
0xa9 LDA #value asm.LDA_Imm(value); Load accumulator
0xa1 LDA (zp, X) asm.LDA(_[zp, X]); Load accumulator
0xb1 LDA (zp), Y asm.LDA(_[zp], Y); Load accumulator
0xa5 LDA zp asm.LDA(zp); Load accumulator
0xb5 LDA zp, X asm.LDA(zp, X); Load accumulator
0xae LDX address asm.LDX(address); Load X register
0xbe LDX address, Y asm.LDX(address, Y); Load X register
0xa2 LDX #value asm.LDX_Imm(value); Load X register
0xa6 LDX zp asm.LDX(zp); Load X register
0xb6 LDX zp, Y asm.LDX(zp, Y); Load X register
0xac LDY address asm.LDY(address); Load Y register
0xbc LDY address, X asm.LDY(address, X); Load Y register
0xa0 LDY #value asm.LDY_Imm(value); Load Y register
0xa4 LDY zp asm.LDY(zp); Load Y register
0xb4 LDY zp, X asm.LDY(zp, X); Load Y register
0x4e LSR address asm.LSR(address); Logical shift right
0x5e LSR address, X asm.LSR(address, X); Logical shift right
0x4a LSR A asm.LSR(A); Logical shift right
0x46 LSR zp asm.LSR(zp); Logical shift right
0x56 LSR zp, X asm.LSR(zp, X); Logical shift right
0xea NOP asm.NOP(); No operation
0x0d ORA address asm.ORA(address); Logical Inclusive OR
0x1d ORA address, X asm.ORA(address, X); Logical Inclusive OR
0x19 ORA address, Y asm.ORA(address, Y); Logical Inclusive OR
0x09 ORA #value asm.ORA_Imm(value); Logical Inclusive OR
0x01 ORA (zp, X) asm.ORA(_[zp, X]); Logical Inclusive OR
0x11 ORA (zp), Y asm.ORA(_[zp], Y); Logical Inclusive OR
0x05 ORA zp asm.ORA(zp); Logical Inclusive OR
0x15 ORA zp, X asm.ORA(zp, X); Logical Inclusive OR
0x48 PHA asm.PHA(); Push accumulator
0x08 PHP asm.PHP(); Push processor status
0x68 PLA asm.PLA(); Pull accumulator
0x28 PLP asm.PLP(); Pull processor status
0x2e ROL address asm.ROL(address); Rotate left
0x3e ROL address, X asm.ROL(address, X); Rotate left
0x2a ROL A asm.ROL(A); Rotate left
0x26 ROL zp asm.ROL(zp); Rotate left
0x36 ROL zp, X asm.ROL(zp, X); Rotate left
0x6e ROR address asm.ROR(address); Rotate right
0x7e ROR address, X asm.ROR(address, X); Rotate right
0x6a ROR A asm.ROR(A); Rotate right
0x66 ROR zp asm.ROR(zp); Rotate right
0x76 ROR zp, X asm.ROR(zp, X); Rotate right
0x40 RTI asm.RTI(); Return from interrupt
0x60 RTS asm.RTS(); Return from subroutine
0xed SBC address asm.SBC(address); Subtract with carry
0xfd SBC address, X asm.SBC(address, X); Subtract with carry
0xf9 SBC address, Y asm.SBC(address, Y); Subtract with carry
0xe9 SBC #value asm.SBC_Imm(value); Subtract with carry
0xe1 SBC (zp, X) asm.SBC(_[zp, X]); Subtract with carry
0xf1 SBC (zp), Y asm.SBC(_[zp], Y); Subtract with carry
0xe5 SBC zp asm.SBC(zp); Subtract with carry
0xf5 SBC zp, X asm.SBC(zp, X); Subtract with carry
0x38 SEC asm.SEC(); Set carry
0xf8 SED asm.SED(); Set decimal flag
0x78 SEI asm.SEI(); Set interrupt disable
0x8d STA address asm.STA(address); Store accumulator
0x9d STA address, X asm.STA(address, X); Store accumulator
0x99 STA address, Y asm.STA(address, Y); Store accumulator
0x81 STA (zp, X) asm.STA(_[zp, X]); Store accumulator
0x91 STA (zp), Y asm.STA(_[zp], Y); Store accumulator
0x85 STA zp asm.STA(zp); Store accumulator
0x95 STA zp, X asm.STA(zp, X); Store accumulator
0x8e STX address asm.STX(address); Store X register
0x86 STX zp asm.STX(zp); Store X register
0x96 STX zp, Y asm.STX(zp, Y); Store X register
0x8c STY address asm.STY(address); Store Y register
0x84 STY zp asm.STY(zp); Store Y register
0x94 STY zp, X asm.STY(zp, X); Store Y register
0xaa TAX asm.TAX(); Transfer acc to X
0xa8 TAY asm.TAY(); Transfer acc to Y
0xba TSX asm.TSX(); Transfer stack pointer to X
0x8a TXA asm.TXA(); Transfer X to acc
0x9a TXS asm.TXS(); Transfer X to SP
0x98 TYA asm.TYA(); Transfer Y to acc

6510 Additional Undocumented Instructions

The following instructions are supported by the Mos6510Assembler class:

Byte Instruction C# Syntax Aliases Description Unstable
0x4b ALR #value asm.ALR_Imm(value); ALR, ASR AND then LSR
0x0b ANC #value asm.ANC_Imm(value); ANC AND then set carry
0x2b ANC #value asm.ANC_2B_Imm(value); ANC, ANC2 AND then set carry
0x8b ANE #value asm.ANE_Imm(value); ANE, XAA Undocumented: AND with X then AND operand
0x6b ARR #value asm.ARR_Imm(value); ARR AND then ROR
0xcf DCP address asm.DCP(address); DCP, DCM DEC then CMP
0xdf DCP address, X asm.DCP(address, X); DCP, DCM DEC then CMP
0xdb DCP address, Y asm.DCP(address, Y); DCP, DCM DEC then CMP
0xc3 DCP (zp, X) asm.DCP(_[zp, X]); DCP, DCM DEC then CMP
0xd3 DCP (zp), Y asm.DCP(_[zp], Y); DCP, DCM DEC then CMP
0xc7 DCP zp asm.DCP(zp); DCP, DCM DEC then CMP
0xd7 DCP zp, X asm.DCP(zp, X); DCP, DCM DEC then CMP
0xef ISC address asm.ISC(address); ISC, ISB, INS INC then SBC
0xff ISC address, X asm.ISC(address, X); ISC, ISB, INS INC then SBC
0xfb ISC address, Y asm.ISC(address, Y); ISC, ISB, INS INC then SBC
0xe3 ISC (zp, X) asm.ISC(_[zp, X]); ISC, ISB, INS INC then SBC
0xf3 ISC (zp), Y asm.ISC(_[zp], Y); ISC, ISB, INS INC then SBC
0xe7 ISC zp asm.ISC(zp); ISC, ISB, INS INC then SBC
0xf7 ISC zp, X asm.ISC(zp, X); ISC, ISB, INS INC then SBC
0x02 JAM asm.JAM(); JAM, KIL, HLT Jam the CPU (halt)
0x12 JAM asm.JAM_12(); JAM, KIL, HLT Jam the CPU (halt)
0x22 JAM asm.JAM_22(); JAM, KIL, HLT Jam the CPU (halt)
0x32 JAM asm.JAM_32(); JAM, KIL, HLT Jam the CPU (halt)
0x42 JAM asm.JAM_42(); JAM, KIL, HLT Jam the CPU (halt)
0x52 JAM asm.JAM_52(); JAM, KIL, HLT Jam the CPU (halt)
0x62 JAM asm.JAM_62(); JAM, KIL, HLT Jam the CPU (halt)
0x72 JAM asm.JAM_72(); JAM, KIL, HLT Jam the CPU (halt)
0x92 JAM asm.JAM_92(); JAM, KIL, HLT Jam the CPU (halt)
0xb2 JAM asm.JAM_B2(); JAM, KIL, HLT Jam the CPU (halt)
0xd2 JAM asm.JAM_D2(); JAM, KIL, HLT Jam the CPU (halt)
0xf2 JAM asm.JAM_F2(); JAM, KIL, HLT Jam the CPU (halt)
0xbb LAS address, Y asm.LAS(address, Y); LAS, LAR Load accumulator and transfer SP to X
0xaf LAX address asm.LAX(address); LAX LDA then LDX
0xbf LAX address, Y asm.LAX(address, Y); LAX LDA then LDX
0xa3 LAX (zp, X) asm.LAX(_[zp, X]); LAX LDA then LDX
0xb3 LAX (zp), Y asm.LAX(_[zp], Y); LAX LDA then LDX
0xa7 LAX zp asm.LAX(zp); LAX LDA then LDX
0xb7 LAX zp, Y asm.LAX(zp, Y); LAX LDA then LDX
0xab LXA #value asm.LXA_Imm(value); LXA, LAX, immediate LDA then LDX
0x0c NOP address asm.NOP(address); NOP No operation
0x1c NOP address, X asm.NOP(address, X); NOP No operation
0x3c NOP address, X asm.NOP_3C(address, X); NOP No operation
0x5c NOP address, X asm.NOP_5C(address, X); NOP No operation
0x7c NOP address, X asm.NOP_7C(address, X); NOP No operation
0xdc NOP address, X asm.NOP_DC(address, X); NOP No operation
0xfc NOP address, X asm.NOP_FC(address, X); NOP No operation
0x80 NOP #value asm.NOP_Imm(value); NOP No operation
0x82 NOP #value asm.NOP_82_Imm(value); NOP No operation
0x89 NOP #value asm.NOP_89_Imm(value); NOP No operation
0xc2 NOP #value asm.NOP_C2_Imm(value); NOP No operation
0xe2 NOP #value asm.NOP_E2_Imm(value); NOP No operation
0x1a NOP asm.NOP_1A(); NOP No operation
0x3a NOP asm.NOP_3A(); NOP No operation
0x5a NOP asm.NOP_5A(); NOP No operation
0x7a NOP asm.NOP_7A(); NOP No operation
0xda NOP asm.NOP_DA(); NOP No operation
0xfa NOP asm.NOP_FA(); NOP No operation
0x04 NOP zp asm.NOP(zp); NOP No operation
0x44 NOP zp asm.NOP_44(zp); NOP No operation
0x64 NOP zp asm.NOP_64(zp); NOP No operation
0x14 NOP zp, X asm.NOP(zp, X); NOP No operation
0x34 NOP zp, X asm.NOP_34(zp, X); NOP No operation
0x54 NOP zp, X asm.NOP_54(zp, X); NOP No operation
0x74 NOP zp, X asm.NOP_74(zp, X); NOP No operation
0xd4 NOP zp, X asm.NOP_D4(zp, X); NOP No operation
0xf4 NOP zp, X asm.NOP_F4(zp, X); NOP No operation
0x2f RLA address asm.RLA(address); RLA ROL then AND
0x3f RLA address, X asm.RLA(address, X); RLA ROL then AND
0x3b RLA address, Y asm.RLA(address, Y); RLA ROL then AND
0x23 RLA (zp, X) asm.RLA(_[zp, X]); RLA ROL then AND
0x33 RLA (zp), Y asm.RLA(_[zp], Y); RLA ROL then AND
0x27 RLA zp asm.RLA(zp); RLA ROL then AND
0x37 RLA zp, X asm.RLA(zp, X); RLA ROL then AND
0x6f RRA address asm.RRA(address); RRA ROR then ADC
0x7f RRA address, X asm.RRA(address, X); RRA ROR then ADC
0x7b RRA address, Y asm.RRA(address, Y); RRA ROR then ADC
0x63 RRA (zp, X) asm.RRA(_[zp, X]); RRA ROR then ADC
0x73 RRA (zp), Y asm.RRA(_[zp], Y); RRA ROR then ADC
0x67 RRA zp asm.RRA(zp); RRA ROR then ADC
0x77 RRA zp, X asm.RRA(zp, X); RRA ROR then ADC
0x8f SAX address asm.SAX(address); SAX, AXS, AAX Store accumulator AND X
0x83 SAX (zp, X) asm.SAX(_[zp, X]); SAX, AXS, AAX Store accumulator AND X
0x87 SAX zp asm.SAX(zp); SAX, AXS, AAX Store accumulator AND X
0x97 SAX zp, Y asm.SAX(zp, Y); SAX, AXS, AAX Store accumulator AND X
0xcb SBX #value asm.SBX_Imm(value); SBX, AXS, SAX Compute (A AND X) then subtract with carry
0x9f SHA address, Y asm.SHA(address, Y); SHA, AHX, AXA Store A AND X AND (high address + 1)
0x93 SHA (zp), Y asm.SHA(_[zp], Y); SHA, AHX, AXA Store A AND X AND (high address + 1)
0x9e SHX address, Y asm.SHX(address, Y); SHX, A11, SXA, XAS Store A AND X AND (high address + 1)
0x9c SHY address, X asm.SHY(address, X); SHY, A11, SYA, SAY Store Y AND (high address + 1)
0x0f SLO address asm.SLO(address); SLO, ASO ASL then ORA
0x1f SLO address, X asm.SLO(address, X); SLO, ASO ASL then ORA
0x1b SLO address, Y asm.SLO(address, Y); SLO, ASO ASL then ORA
0x03 SLO (zp, X) asm.SLO(_[zp, X]); SLO, ASO ASL then ORA
0x13 SLO (zp), Y asm.SLO(_[zp], Y); SLO, ASO ASL then ORA
0x07 SLO zp asm.SLO(zp); SLO, ASO ASL then ORA
0x17 SLO zp, X asm.SLO(zp, X); SLO, ASO ASL then ORA
0x4f SRE address asm.SRE(address); SRE, LSE LSR then EOR
0x5f SRE address, X asm.SRE(address, X); SRE, LSE LSR then EOR
0x5b SRE address, Y asm.SRE(address, Y); SRE, LSE LSR then EOR
0x43 SRE (zp, X) asm.SRE(_[zp, X]); SRE, LSE LSR then EOR
0x53 SRE (zp), Y asm.SRE(_[zp], Y); SRE, LSE LSR then EOR
0x47 SRE zp asm.SRE(zp); SRE, LSE LSR then EOR
0x57 SRE zp, X asm.SRE(zp, X); SRE, LSE LSR then EOR
0x9b TAS address, Y asm.TAS(address, Y); TAS, XAS, SHS Transfer A AND X to SP, store A AND X AND (high address + 1)
0xeb USBC #value asm.USBC_Imm(value); USBC, SBC SBC with NOP behavior