// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie // SPDX-License-Identifier: MIT //! The instruction set as `as65` understands it. //! //! Every opcode and cycle count in this table was read back out of Frank //! Kingswood's `as65` itself, one mnemonic and addressing mode at a //! time, rather than copied from a data sheet. That matters because the goal //! is byte-identical output, and `as65` has entries that no 6502 data sheet //! would agree with; those are called out where they appear. const std = @import("std"); /// Which processor the assembler is targeting. /// /// `-x1` selects `mos6502x`, which adds the undocumented NMOS opcodes; /// `-x2` — or plain `-x` — selects `g65sc02`. They are alternatives, not /// flags to be combined: the last one on the command line wins. pub const Cpu = enum { mos6502, mos6502x, g65sc02 }; /// Which processors an encoding exists on. pub const Availability = enum { /// Every processor the assembler knows. all, /// The 65SC02 only. sc02, /// The 65SC02 and, oddly, `-x1`: `bit ,x`, `bit ,x`, `inc a`, /// `dec a` and `jmp (,x)` are all accepted under the undocumented /// 6502 set as well as the 65SC02. extended, /// The undocumented NMOS opcodes, reachable only through `-x1`. undocumented, pub fn includes(self: Availability, cpu: Cpu) bool { return switch (self) { .all => true, .sc02 => cpu == .g65sc02, .extended => cpu == .g65sc02 or cpu == .mos6502x, .undocumented => cpu == .mos6502x, }; } }; /// A machine addressing mode: one row of the encoding table. /// /// These are the modes an *instruction* can be encoded in, which is not the /// same set as the shapes an operand can be *written* in. `$12,x` is written /// one way but encodes as either `zp_x` or `abs_x` depending on the value. pub const Mode = enum { implied, acc, imm, zp, zp_x, zp_y, abs, abs_x, abs_y, ind_x, ind_y, ind_zp, ind_abs, ind_abs_x, rel, /// `,` — the 65SC02 `rmb`/`smb` instructions. bit_zp, /// `,,` — the 65SC02 `bbr`/`bbs` instructions. bit_rel, /// Total length of an instruction in this mode, opcode included. pub fn size(self: Mode) u8 { return switch (self) { .implied, .acc => 1, .imm, .zp, .zp_x, .zp_y, .ind_x, .ind_y, .ind_zp, .rel, .bit_zp => 2, .abs, .abs_x, .abs_y, .ind_abs, .ind_abs_x, .bit_rel => 3, }; } }; /// The shape an operand was written in, before its value is known. pub const Syntax = enum { none, /// A bare `a`. acc, /// `#` imm, /// `` — also how a branch target is written. direct, /// `,x` direct_x, /// `,y` direct_y, /// `()` ind, /// `(,x)` ind_x, /// `(),y` ind_y, /// `,` bit, /// `,,` bit_rel, /// The mode to use when the operand fits in a byte, if the instruction has /// one, and the mode to fall back to when it does not. /// /// `as65` always prefers the short form when the value is known to fit and /// the instruction offers it, and reaches for the long form otherwise — /// which is why `adc $12,y` assembles as three bytes of `abs_y`: `adc` has /// no `zp_y` row for the short form to land in. pub fn modes(self: Syntax) struct { short: ?Mode, long: ?Mode } { return switch (self) { .none => .{ .short = .implied, .long = null }, .acc => .{ .short = .acc, .long = null }, .imm => .{ .short = .imm, .long = null }, .direct => .{ .short = .zp, .long = .abs }, .direct_x => .{ .short = .zp_x, .long = .abs_x }, .direct_y => .{ .short = .zp_y, .long = .abs_y }, .ind => .{ .short = .ind_zp, .long = .ind_abs }, .ind_x => .{ .short = .ind_x, .long = .ind_abs_x }, .ind_y => .{ .short = .ind_y, .long = null }, .bit => .{ .short = .bit_zp, .long = null }, .bit_rel => .{ .short = .bit_rel, .long = null }, }; } }; /// One cell of the encoding table. pub const Encoding = struct { op: u8, /// Cycle count `as65` prints in a `-c` listing. cycles: u8, /// Cycle count for the 65SC02, where it differs. The only instructions /// that differ are the read-modify-write ones in `,x`, which `as65` /// gives seven cycles on the NMOS parts and six on the 65SC02. cycles_sc02: u8 = 0, available: Availability = .all, pub fn cycleCount(self: Encoding, cpu: Cpu) u8 { if (cpu == .g65sc02 and self.cycles_sc02 != 0) return self.cycles_sc02; return self.cycles; } }; /// One row of the encoding table: a mnemonic and what it can be encoded as. pub const Instruction = struct { name: []const u8, encodings: []const struct { Mode, Encoding }, /// True for `rmb`, whose name `as65` also gives to a pseudo-instruction; /// which one is meant depends on whether a bit number and a comma follow. bit_form_only: bool = false, /// The encoding for `mode`, if this instruction has one that `cpu` can /// execute. pub fn lookup(self: Instruction, mode: Mode, cpu: Cpu) ?Encoding { for (self.encodings) |entry| { if (entry[0] == mode and entry[1].available.includes(cpu)) return entry[1]; } return null; } /// True when `cpu` cannot execute a single one of this instruction's /// encodings, which is how `as65` comes to reject `bra` without `-x`. pub fn unavailable(self: Instruction, cpu: Cpu) bool { for (self.encodings) |entry| { if (entry[1].available.includes(cpu)) return false; } return true; } }; fn e(op: u8, cycles: u8) Encoding { return .{ .op = op, .cycles = cycles }; } /// The encoding table, kept sorted by name so it can be binary-searched. /// /// The `rmb`, `smb`, `bbr` and `bbs` rows hold the opcode for bit zero; the /// bit number is multiplied by sixteen and added when the instruction is /// assembled. /// /// Three rows here are `as65`'s and not the processor's. `bit #` (`$89`) /// is a 65SC02 instruction that `as65` assembles without `-x`; `sty ,x` /// assembles to `$9c`, which is `stz `; and `jsr ()` assembles to a /// plain `jsr `, parentheses and all. Reproducing them is the point of /// the exercise, so they are written out here as ordinary rows. pub const table = [_]Instruction{ .{ .name = "adc", .encodings = &.{ .{ .imm, e(0x69, 2) }, .{ .zp, e(0x65, 3) }, .{ .zp_x, e(0x75, 4) }, .{ .abs, e(0x6d, 4) }, .{ .abs_x, e(0x7d, 4) }, .{ .abs_y, e(0x79, 4) }, .{ .ind_x, e(0x61, 6) }, .{ .ind_y, e(0x71, 5) }, .{ .ind_zp, .{ .op = 0x72, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "alr", .encodings = &.{.{ .imm, .{ .op = 0x4b, .cycles = 2, .available = .undocumented } }} }, .{ .name = "and", .encodings = &.{ .{ .imm, e(0x29, 2) }, .{ .zp, e(0x25, 3) }, .{ .zp_x, e(0x35, 4) }, .{ .abs, e(0x2d, 4) }, .{ .abs_x, e(0x3d, 4) }, .{ .abs_y, e(0x39, 4) }, .{ .ind_x, e(0x21, 6) }, .{ .ind_y, e(0x31, 5) }, .{ .ind_zp, .{ .op = 0x32, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "arr", .encodings = &.{.{ .imm, .{ .op = 0x6b, .cycles = 2, .available = .undocumented } }} }, .{ .name = "asl", .encodings = &.{ .{ .acc, e(0x0a, 2) }, .{ .zp, e(0x06, 5) }, .{ .zp_x, e(0x16, 6) }, .{ .abs, e(0x0e, 6) }, .{ .abs_x, .{ .op = 0x1e, .cycles = 7, .cycles_sc02 = 6, .available = .all } }, } }, .{ .name = "aso", .encodings = &.{ .{ .imm, .{ .op = 0x0b, .cycles = 2, .available = .undocumented } }, .{ .zp, .{ .op = 0x07, .cycles = 3, .available = .undocumented } }, .{ .zp_x, .{ .op = 0x17, .cycles = 4, .available = .undocumented } }, .{ .abs, .{ .op = 0x0f, .cycles = 4, .available = .undocumented } }, .{ .abs_x, .{ .op = 0x1f, .cycles = 4, .available = .undocumented } }, .{ .abs_y, .{ .op = 0x1b, .cycles = 4, .available = .undocumented } }, .{ .ind_x, .{ .op = 0x03, .cycles = 6, .available = .undocumented } }, .{ .ind_y, .{ .op = 0x13, .cycles = 5, .available = .undocumented } }, } }, .{ .name = "axs", .encodings = &.{ .{ .zp, .{ .op = 0x87, .cycles = 2, .available = .undocumented } }, .{ .zp_y, .{ .op = 0x97, .cycles = 3, .available = .undocumented } }, .{ .abs, .{ .op = 0x8f, .cycles = 3, .available = .undocumented } }, .{ .ind_x, .{ .op = 0x83, .cycles = 5, .available = .undocumented } }, } }, .{ .name = "bbr", .encodings = &.{.{ .bit_rel, .{ .op = 0x0f, .cycles = 5, .available = .sc02 } }} }, .{ .name = "bbs", .encodings = &.{.{ .bit_rel, .{ .op = 0x8f, .cycles = 5, .available = .sc02 } }} }, .{ .name = "bcc", .encodings = &.{.{ .rel, e(0x90, 3) }} }, .{ .name = "bcs", .encodings = &.{.{ .rel, e(0xb0, 3) }} }, .{ .name = "beq", .encodings = &.{.{ .rel, e(0xf0, 3) }} }, .{ .name = "bit", .encodings = &.{ .{ .imm, e(0x89, 2) }, .{ .zp, e(0x24, 3) }, .{ .zp_x, .{ .op = 0x34, .cycles = 4, .available = .extended } }, .{ .abs, e(0x2c, 4) }, .{ .abs_x, .{ .op = 0x3c, .cycles = 4, .available = .extended } }, } }, .{ .name = "bmi", .encodings = &.{.{ .rel, e(0x30, 3) }} }, .{ .name = "bne", .encodings = &.{.{ .rel, e(0xd0, 3) }} }, .{ .name = "bpl", .encodings = &.{.{ .rel, e(0x10, 3) }} }, .{ .name = "bra", .encodings = &.{.{ .rel, .{ .op = 0x80, .cycles = 3, .available = .sc02 } }} }, .{ .name = "brk", .encodings = &.{.{ .implied, e(0x00, 7) }} }, .{ .name = "bvc", .encodings = &.{.{ .rel, e(0x50, 3) }} }, .{ .name = "bvs", .encodings = &.{.{ .rel, e(0x70, 3) }} }, .{ .name = "clc", .encodings = &.{.{ .implied, e(0x18, 2) }} }, .{ .name = "cld", .encodings = &.{.{ .implied, e(0xd8, 2) }} }, .{ .name = "cli", .encodings = &.{.{ .implied, e(0x58, 2) }} }, .{ .name = "clr", .encodings = &.{ .{ .zp, .{ .op = 0x64, .cycles = 3, .available = .sc02 } }, .{ .zp_x, .{ .op = 0x74, .cycles = 4, .available = .sc02 } }, .{ .abs, .{ .op = 0x9c, .cycles = 4, .available = .sc02 } }, .{ .abs_x, .{ .op = 0x9e, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "clv", .encodings = &.{.{ .implied, e(0xb8, 2) }} }, .{ .name = "cmp", .encodings = &.{ .{ .imm, e(0xc9, 2) }, .{ .zp, e(0xc5, 3) }, .{ .zp_x, e(0xd5, 4) }, .{ .abs, e(0xcd, 4) }, .{ .abs_x, e(0xdd, 4) }, .{ .abs_y, e(0xd9, 4) }, .{ .ind_x, e(0xc1, 6) }, .{ .ind_y, e(0xd1, 5) }, .{ .ind_zp, .{ .op = 0xd2, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "cpx", .encodings = &.{ .{ .imm, e(0xe0, 2) }, .{ .zp, e(0xe4, 3) }, .{ .abs, e(0xec, 4) }, } }, .{ .name = "cpy", .encodings = &.{ .{ .imm, e(0xc0, 2) }, .{ .zp, e(0xc4, 3) }, .{ .abs, e(0xcc, 4) }, } }, .{ .name = "dcm", .encodings = &.{ .{ .zp, .{ .op = 0xc7, .cycles = 3, .available = .undocumented } }, .{ .zp_x, .{ .op = 0xd7, .cycles = 4, .available = .undocumented } }, .{ .abs, .{ .op = 0xcf, .cycles = 4, .available = .undocumented } }, .{ .abs_x, .{ .op = 0xdf, .cycles = 4, .available = .undocumented } }, .{ .abs_y, .{ .op = 0xdb, .cycles = 4, .available = .undocumented } }, .{ .ind_x, .{ .op = 0xc3, .cycles = 6, .available = .undocumented } }, .{ .ind_y, .{ .op = 0xd3, .cycles = 5, .available = .undocumented } }, } }, .{ .name = "dec", .encodings = &.{ .{ .acc, .{ .op = 0x3a, .cycles = 2, .available = .extended } }, .{ .zp, e(0xc6, 5) }, .{ .zp_x, e(0xd6, 6) }, .{ .abs, e(0xce, 6) }, .{ .abs_x, .{ .op = 0xde, .cycles = 7, .cycles_sc02 = 6, .available = .all } }, } }, .{ .name = "dex", .encodings = &.{.{ .implied, e(0xca, 2) }} }, .{ .name = "dey", .encodings = &.{.{ .implied, e(0x88, 2) }} }, .{ .name = "disable", .encodings = &.{.{ .implied, e(0x78, 2) }} }, .{ .name = "enable", .encodings = &.{.{ .implied, e(0x58, 2) }} }, .{ .name = "eor", .encodings = &.{ .{ .imm, e(0x49, 2) }, .{ .zp, e(0x45, 3) }, .{ .zp_x, e(0x55, 4) }, .{ .abs, e(0x4d, 4) }, .{ .abs_x, e(0x5d, 4) }, .{ .abs_y, e(0x59, 4) }, .{ .ind_x, e(0x41, 6) }, .{ .ind_y, e(0x51, 5) }, .{ .ind_zp, .{ .op = 0x52, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "inc", .encodings = &.{ .{ .acc, .{ .op = 0x1a, .cycles = 2, .available = .extended } }, .{ .zp, e(0xe6, 5) }, .{ .zp_x, e(0xf6, 6) }, .{ .abs, e(0xee, 6) }, .{ .abs_x, .{ .op = 0xfe, .cycles = 7, .cycles_sc02 = 6, .available = .all } }, } }, .{ .name = "ins", .encodings = &.{ .{ .zp, .{ .op = 0xe7, .cycles = 3, .available = .undocumented } }, .{ .zp_x, .{ .op = 0xf7, .cycles = 4, .available = .undocumented } }, .{ .abs, .{ .op = 0xef, .cycles = 4, .available = .undocumented } }, .{ .abs_x, .{ .op = 0xff, .cycles = 4, .available = .undocumented } }, .{ .abs_y, .{ .op = 0xfb, .cycles = 4, .available = .undocumented } }, .{ .ind_x, .{ .op = 0xe3, .cycles = 6, .available = .undocumented } }, .{ .ind_y, .{ .op = 0xf3, .cycles = 5, .available = .undocumented } }, } }, .{ .name = "inx", .encodings = &.{.{ .implied, e(0xe8, 2) }} }, .{ .name = "iny", .encodings = &.{.{ .implied, e(0xc8, 2) }} }, .{ .name = "jmp", .encodings = &.{ .{ .abs, e(0x4c, 3) }, .{ .ind_abs, e(0x6c, 6) }, .{ .ind_abs_x, .{ .op = 0x7c, .cycles = 6, .available = .extended } }, } }, .{ .name = "jsr", .encodings = &.{ .{ .abs, e(0x20, 6) }, .{ .ind_abs, e(0x20, 6) }, } }, .{ .name = "lax", .encodings = &.{ .{ .zp, .{ .op = 0xa7, .cycles = 2, .available = .undocumented } }, .{ .zp_x, .{ .op = 0xb7, .cycles = 3, .available = .undocumented } }, .{ .abs, .{ .op = 0xaf, .cycles = 3, .available = .undocumented } }, .{ .abs_y, .{ .op = 0xbf, .cycles = 3, .available = .undocumented } }, .{ .ind_x, .{ .op = 0xa3, .cycles = 5, .available = .undocumented } }, .{ .ind_y, .{ .op = 0xb3, .cycles = 4, .available = .undocumented } }, } }, .{ .name = "lda", .encodings = &.{ .{ .imm, e(0xa9, 2) }, .{ .zp, e(0xa5, 3) }, .{ .zp_x, e(0xb5, 4) }, .{ .abs, e(0xad, 4) }, .{ .abs_x, e(0xbd, 4) }, .{ .abs_y, e(0xb9, 4) }, .{ .ind_x, e(0xa1, 6) }, .{ .ind_y, e(0xb1, 5) }, .{ .ind_zp, .{ .op = 0xb2, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "ldx", .encodings = &.{ .{ .imm, e(0xa2, 2) }, .{ .zp, e(0xa6, 3) }, .{ .zp_y, e(0xb6, 4) }, .{ .abs, e(0xae, 4) }, .{ .abs_y, e(0xbe, 4) }, } }, .{ .name = "ldy", .encodings = &.{ .{ .imm, e(0xa0, 2) }, .{ .zp, e(0xa4, 3) }, .{ .zp_x, e(0xb4, 4) }, .{ .abs, e(0xac, 4) }, .{ .abs_x, e(0xbc, 4) }, } }, .{ .name = "lse", .encodings = &.{ .{ .zp, .{ .op = 0x47, .cycles = 3, .available = .undocumented } }, .{ .zp_x, .{ .op = 0x57, .cycles = 4, .available = .undocumented } }, .{ .abs, .{ .op = 0x4f, .cycles = 4, .available = .undocumented } }, .{ .abs_x, .{ .op = 0x5f, .cycles = 4, .available = .undocumented } }, .{ .abs_y, .{ .op = 0x5b, .cycles = 4, .available = .undocumented } }, .{ .ind_x, .{ .op = 0x43, .cycles = 6, .available = .undocumented } }, .{ .ind_y, .{ .op = 0x53, .cycles = 5, .available = .undocumented } }, } }, .{ .name = "lsr", .encodings = &.{ .{ .acc, e(0x4a, 2) }, .{ .zp, e(0x46, 5) }, .{ .zp_x, e(0x56, 6) }, .{ .abs, e(0x4e, 6) }, .{ .abs_x, .{ .op = 0x5e, .cycles = 7, .cycles_sc02 = 6, .available = .all } }, } }, .{ .name = "nop", .encodings = &.{.{ .implied, e(0xea, 2) }} }, .{ .name = "oal", .encodings = &.{.{ .imm, .{ .op = 0xab, .cycles = 2, .available = .undocumented } }} }, .{ .name = "ora", .encodings = &.{ .{ .imm, e(0x09, 2) }, .{ .zp, e(0x05, 3) }, .{ .zp_x, e(0x15, 4) }, .{ .abs, e(0x0d, 4) }, .{ .abs_x, e(0x1d, 4) }, .{ .abs_y, e(0x19, 4) }, .{ .ind_x, e(0x01, 6) }, .{ .ind_y, e(0x11, 5) }, .{ .ind_zp, .{ .op = 0x12, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "pha", .encodings = &.{.{ .implied, e(0x48, 3) }} }, .{ .name = "php", .encodings = &.{.{ .implied, e(0x08, 3) }} }, .{ .name = "phx", .encodings = &.{.{ .implied, .{ .op = 0xda, .cycles = 3, .available = .sc02 } }} }, .{ .name = "phy", .encodings = &.{.{ .implied, .{ .op = 0x5a, .cycles = 3, .available = .sc02 } }} }, .{ .name = "pla", .encodings = &.{.{ .implied, e(0x68, 4) }} }, .{ .name = "plp", .encodings = &.{.{ .implied, e(0x28, 4) }} }, .{ .name = "plx", .encodings = &.{.{ .implied, .{ .op = 0xfa, .cycles = 4, .available = .sc02 } }} }, .{ .name = "ply", .encodings = &.{.{ .implied, .{ .op = 0x7a, .cycles = 4, .available = .sc02 } }} }, .{ .name = "rla", .encodings = &.{ .{ .imm, .{ .op = 0x2b, .cycles = 2, .available = .undocumented } }, .{ .zp, .{ .op = 0x27, .cycles = 3, .available = .undocumented } }, .{ .zp_x, .{ .op = 0x37, .cycles = 4, .available = .undocumented } }, .{ .abs, .{ .op = 0x2f, .cycles = 4, .available = .undocumented } }, .{ .abs_x, .{ .op = 0x3f, .cycles = 4, .available = .undocumented } }, .{ .abs_y, .{ .op = 0x3b, .cycles = 4, .available = .undocumented } }, .{ .ind_x, .{ .op = 0x23, .cycles = 6, .available = .undocumented } }, .{ .ind_y, .{ .op = 0x33, .cycles = 5, .available = .undocumented } }, } }, .{ .name = "rmb", .encodings = &.{.{ .bit_zp, .{ .op = 0x07, .cycles = 5, .available = .sc02 } }}, .bit_form_only = true }, .{ .name = "rol", .encodings = &.{ .{ .acc, e(0x2a, 2) }, .{ .zp, e(0x26, 5) }, .{ .zp_x, e(0x36, 6) }, .{ .abs, e(0x2e, 6) }, .{ .abs_x, .{ .op = 0x3e, .cycles = 7, .cycles_sc02 = 6, .available = .all } }, } }, .{ .name = "ror", .encodings = &.{ .{ .acc, e(0x6a, 2) }, .{ .zp, e(0x66, 5) }, .{ .zp_x, e(0x76, 6) }, .{ .abs, e(0x6e, 6) }, .{ .abs_x, .{ .op = 0x7e, .cycles = 7, .cycles_sc02 = 6, .available = .all } }, } }, .{ .name = "rra", .encodings = &.{ .{ .zp, .{ .op = 0x67, .cycles = 3, .available = .undocumented } }, .{ .zp_x, .{ .op = 0x77, .cycles = 4, .available = .undocumented } }, .{ .abs, .{ .op = 0x6f, .cycles = 4, .available = .undocumented } }, .{ .abs_x, .{ .op = 0x7f, .cycles = 4, .available = .undocumented } }, .{ .abs_y, .{ .op = 0x7b, .cycles = 4, .available = .undocumented } }, .{ .ind_x, .{ .op = 0x63, .cycles = 6, .available = .undocumented } }, .{ .ind_y, .{ .op = 0x73, .cycles = 5, .available = .undocumented } }, } }, .{ .name = "rti", .encodings = &.{.{ .implied, e(0x40, 6) }} }, .{ .name = "rts", .encodings = &.{.{ .implied, e(0x60, 6) }} }, .{ .name = "sax", .encodings = &.{.{ .imm, .{ .op = 0xcb, .cycles = 2, .available = .undocumented } }} }, .{ .name = "sbc", .encodings = &.{ .{ .imm, e(0xe9, 2) }, .{ .zp, e(0xe5, 3) }, .{ .zp_x, e(0xf5, 4) }, .{ .abs, e(0xed, 4) }, .{ .abs_x, e(0xfd, 4) }, .{ .abs_y, e(0xf9, 4) }, .{ .ind_x, e(0xe1, 6) }, .{ .ind_y, e(0xf1, 5) }, .{ .ind_zp, .{ .op = 0xf2, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "sec", .encodings = &.{.{ .implied, e(0x38, 2) }} }, .{ .name = "sed", .encodings = &.{.{ .implied, e(0xf8, 2) }} }, .{ .name = "sei", .encodings = &.{.{ .implied, e(0x78, 2) }} }, .{ .name = "skb", .encodings = &.{.{ .implied, .{ .op = 0x04, .cycles = 2, .available = .undocumented } }} }, .{ .name = "skw", .encodings = &.{.{ .implied, .{ .op = 0x0c, .cycles = 3, .available = .undocumented } }} }, .{ .name = "smb", .encodings = &.{.{ .bit_zp, .{ .op = 0x87, .cycles = 5, .available = .sc02 } }} }, .{ .name = "sta", .encodings = &.{ .{ .zp, e(0x85, 3) }, .{ .zp_x, e(0x95, 4) }, .{ .abs, e(0x8d, 4) }, .{ .abs_x, e(0x9d, 5) }, .{ .abs_y, e(0x99, 5) }, .{ .ind_x, e(0x81, 6) }, .{ .ind_y, e(0x91, 5) }, .{ .ind_zp, .{ .op = 0x92, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "stc", .encodings = &.{.{ .implied, e(0x38, 2) }} }, .{ .name = "std", .encodings = &.{.{ .implied, e(0xf8, 2) }} }, .{ .name = "sti", .encodings = &.{.{ .implied, e(0x78, 2) }} }, .{ .name = "stx", .encodings = &.{ .{ .zp, e(0x86, 3) }, .{ .zp_y, e(0x96, 4) }, .{ .abs, e(0x8e, 4) }, } }, .{ .name = "sty", .encodings = &.{ .{ .zp, e(0x84, 3) }, .{ .zp_x, e(0x94, 4) }, .{ .abs, e(0x8c, 4) }, .{ .abs_x, e(0x9c, 4) }, } }, .{ .name = "stz", .encodings = &.{ .{ .zp, .{ .op = 0x64, .cycles = 3, .available = .sc02 } }, .{ .zp_x, .{ .op = 0x74, .cycles = 4, .available = .sc02 } }, .{ .abs, .{ .op = 0x9c, .cycles = 4, .available = .sc02 } }, .{ .abs_x, .{ .op = 0x9e, .cycles = 5, .available = .sc02 } }, } }, .{ .name = "tax", .encodings = &.{.{ .implied, e(0xaa, 2) }} }, .{ .name = "tay", .encodings = &.{.{ .implied, e(0xa8, 2) }} }, .{ .name = "trb", .encodings = &.{ .{ .zp, .{ .op = 0x14, .cycles = 2, .available = .sc02 } }, .{ .abs, .{ .op = 0x1c, .cycles = 3, .available = .sc02 } }, } }, .{ .name = "tsb", .encodings = &.{ .{ .zp, .{ .op = 0x04, .cycles = 2, .available = .sc02 } }, .{ .abs, .{ .op = 0x0c, .cycles = 3, .available = .sc02 } }, } }, .{ .name = "tsx", .encodings = &.{.{ .implied, e(0xba, 2) }} }, .{ .name = "txa", .encodings = &.{.{ .implied, e(0x8a, 2) }} }, .{ .name = "txs", .encodings = &.{.{ .implied, e(0x9a, 2) }} }, .{ .name = "tya", .encodings = &.{.{ .implied, e(0x98, 2) }} }, .{ .name = "xaa", .encodings = &.{.{ .imm, .{ .op = 0x8b, .cycles = 2, .available = .undocumented } }} }, }; pub fn find(name: []const u8) ?*const Instruction { var lo: usize = 0; var hi: usize = table.len; while (lo < hi) { const mid = lo + (hi - lo) / 2; switch (std.mem.order(u8, table[mid].name, name)) { .lt => lo = mid + 1, .gt => hi = mid, .eq => return &table[mid], } } return null; } test "table is sorted and searchable" { for (table[1..], table[0 .. table.len - 1]) |next, prev| { try std.testing.expect(std.mem.order(u8, prev.name, next.name) == .lt); } for (&table) |*instr| { try std.testing.expectEqual(instr, find(instr.name).?); } try std.testing.expectEqual(@as(?*const Instruction, null), find("psscht")); } test "the 65SC02 mnemonics are absent from the plain 6502" { for ([_][]const u8{ "bra", "stz", "phx", "trb", "smb", "bbr", "clr" }) |name| { try std.testing.expect(find(name).?.unavailable(.mos6502)); try std.testing.expect(!find(name).?.unavailable(.g65sc02)); } } test "the undocumented opcodes need -x1 and nothing else" { const aso = find("aso").?; try std.testing.expect(aso.unavailable(.mos6502)); try std.testing.expect(aso.unavailable(.g65sc02)); try std.testing.expectEqual(@as(u8, 0x07), aso.lookup(.zp, .mos6502x).?.op); } test "the rows as65 has that the processor does not" { try std.testing.expectEqual(@as(u8, 0x89), find("bit").?.lookup(.imm, .mos6502).?.op); try std.testing.expectEqual(@as(u8, 0x9c), find("sty").?.lookup(.abs_x, .mos6502).?.op); try std.testing.expectEqual(@as(u8, 0x20), find("jsr").?.lookup(.ind_abs, .mos6502).?.op); } test "read-modify-write on abs,x costs a cycle less on the 65SC02" { const asl = find("asl").?.lookup(.abs_x, .g65sc02).?; try std.testing.expectEqual(@as(u8, 7), asl.cycleCount(.mos6502)); try std.testing.expectEqual(@as(u8, 6), asl.cycleCount(.g65sc02)); }