Something went wrong. Try again.
An assembler for the 6502 and 65SC02 written in Zig, in the dialect of Frank Kingswood's as65.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// 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 <zp>,x`, `bit <abs>,x`, `inc a`, /// `dec a` and `jmp (<abs>,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, /// `<bit>,<zp>` — the 65SC02 `rmb`/`smb` instructions. bit_zp, /// `<bit>,<zp>,<target>` — 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, /// `#<expr>` imm, /// `<expr>` — also how a branch target is written. direct, /// `<expr>,x` direct_x, /// `<expr>,y` direct_y, /// `(<expr>)` ind, /// `(<expr>,x)` ind_x, /// `(<expr>),y` ind_y, /// `<bit>,<expr>` bit, /// `<bit>,<expr>,<target>` 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 `<abs>,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 #<imm>` (`$89`)/// is a 65SC02 instruction that `as65` assembles without `-x`; `sty <abs>,x`/// assembles to `$9c`, which is `stz <abs>`; and `jsr (<abs>)` assembles to a/// plain `jsr <abs>`, 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));}