├── .gitignore ├── tests ├── test.bin ├── test-brk.bin ├── smoketest.sh ├── speedrun.sh ├── packages.lisp ├── jit.lisp ├── fixtures.lisp ├── perf.lisp ├── disassembler.lisp ├── opcodes.lisp ├── parser.lisp └── assembler.lisp ├── gen-docs.sh ├── src ├── utils.lisp ├── utils.md ├── conditions.md ├── outro.md ├── packages.md ├── conditions.lisp ├── Makefile ├── parser.md ├── jit.lisp ├── assemble.md ├── jit.md ├── disassemble.md ├── packages.lisp ├── doc │ ├── genbook.sh │ └── template.latex ├── cpu.md ├── opcodes.md ├── addressing.md ├── disassemble.lisp ├── lessons.md ├── addressing.lisp ├── intro.md ├── parser.lisp ├── assemble.lisp ├── cpu.lisp └── opcodes.lisp ├── TODO ├── .travis.yml ├── LICENSE ├── cl-6502.asd ├── NEWS.md ├── README.md └── docs ├── cl-6502.html └── 6502.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | src/doc/* -------------------------------------------------------------------------------- /tests/test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcons/cl-6502/HEAD/tests/test.bin -------------------------------------------------------------------------------- /tests/test-brk.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcons/cl-6502/HEAD/tests/test-brk.bin -------------------------------------------------------------------------------- /tests/smoketest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sbcl --eval "(asdf:oos 'asdf:test-op 'cl-6502)" \ 3 | --eval "(sb-ext:quit)" 4 | -------------------------------------------------------------------------------- /tests/speedrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sbcl --eval "(ql:quickload 'cl-6502-tests)" \ 3 | --eval "(in-package :6502-tests)" \ 4 | --eval "(progn (speedrun) (sb-ext:quit))" 5 | -------------------------------------------------------------------------------- /gen-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sbcl --eval "(ql:quickload '(cl-6502 sb-introspect cl-api))" \ 3 | --eval "(cl-api:api-gen :cl-6502 \"docs/cl-6502.html\")" \ 4 | --eval "(progn (terpri) (sb-ext:quit))" 5 | -------------------------------------------------------------------------------- /tests/packages.lisp: -------------------------------------------------------------------------------- 1 | (defpackage :6502-tests 2 | (:use :cl :fiveam :6502) 3 | (:import-from :6502-conf #:app-path) 4 | (:import-from :alexandria #:read-file-into-byte-vector) 5 | (:export #:run! #:6502-tests)) 6 | -------------------------------------------------------------------------------- /tests/jit.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502-tests) 2 | 3 | (def-suite jit :in 6502-tests) 4 | (in-suite jit) 5 | 6 | (deftest jit-passes-klaus-test 7 | "The JIT should pass Klaus Dorfmann's test suite." 8 | (klaus-init) 9 | (let ((cycles (* 78 (expt 2 20)))) 10 | (loop until (> (cpu-cc *cpu*) cycles) 11 | do (6502::jit-step *cpu* (cpu-pc *cpu*)))) 12 | (is (eql (cpu-pc *cpu*) #x3c37))) 13 | -------------------------------------------------------------------------------- /tests/fixtures.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502-tests) 2 | 3 | (def-suite 6502-tests) 4 | (in-suite 6502-tests) 5 | 6 | (def-fixture cpu () 7 | (let ((*ram* (bytevector #x10000)) 8 | (*cpu* (make-cpu))) 9 | (declare (special *ram*)) 10 | (symbol-macrolet ((cpu *cpu*)) 11 | (&body)))) 12 | 13 | (defmacro deftest (name docstring &body body) 14 | `(test ,name 15 | ,docstring 16 | (with-fixture cpu () 17 | ,@body))) 18 | -------------------------------------------------------------------------------- /src/utils.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502) 2 | 3 | (defun execute (cpu) 4 | "Step the CPU until a BRK instruction." 5 | (loop for opcode of-type u8 = (get-byte (cpu-pc cpu)) 6 | do (handler-case (step-cpu cpu opcode) 7 | (undefined-function () 8 | (error 'illegal-opcode :opcode opcode))) 9 | until (zerop opcode))) 10 | 11 | (defun step-cpu (cpu opcode) 12 | "Step the CPU through the next OPCODE." 13 | (funcall (aref *opcode-funs* opcode) cpu)) 14 | -------------------------------------------------------------------------------- /src/utils.md: -------------------------------------------------------------------------------- 1 | ## `utils.lisp`: Bringing it all Together 2 | 3 | Now we need to actually connect the dots and get our CPU emulating! An `EXECUTE` 4 | function that emulates code in a loop until a halt instruction and a `STEP-CPU` 5 | function to execute a single opcode suit nicely. Here we see that our opcodes 6 | array finally gets put to good use as `STEP-CPU` just grabs the appropriate 7 | lambda and calls it, leaving `EXECUTE` to handle errors if necessary. 8 | 9 | ## Source Code 10 | -------------------------------------------------------------------------------- /src/conditions.md: -------------------------------------------------------------------------------- 1 | ## `conditions.lisp`: Just in Case 2 | 3 | There's really not much to say about our 4 | [conditions](http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html). 5 | The primary illegal state our emulator can encounter is trying to execute an 6 | invalid opcode, i.e. a byte for which no opcode definition exists. For our 7 | assembler, we have also defined a condition for invalid syntax. Finally, 8 | there is a condition for invalid addressing modes, mostly to provide better 9 | debugging in case a new opcode definition was fat-fingered. 10 | 11 | ## Source Code 12 | -------------------------------------------------------------------------------- /src/outro.md: -------------------------------------------------------------------------------- 1 | ## Next Steps 2 | 3 | Once you've got a CPU Emulator there's a plethora of options for further exploration: 4 | 5 | * You could write a [whole-system emulator](https://github.com/redline6561/famiclom) using that CPU. 6 | * You could write a simple compiler targeting that CPU. 7 | * You could write a [static analyzer](https://github.com/redline6561/trowel) to compute the 8 | [CFG](http://en.wikipedia.org/wiki/Control_flow_graph) of binaries for that CPU. 9 | * You could [port it to the web](https://github.com/redline6561/cljs-6502) to bring your code to a wider audience. 10 | 11 | Whatever you do, I hope you enjoyed reading through **cl-6502**. Happy Hacking! 12 | -------------------------------------------------------------------------------- /src/packages.md: -------------------------------------------------------------------------------- 1 | ## `packages.lisp`: Exposed Functionality 2 | 3 | Finally, we'll want to define packages for our emulator to provide a public API. 4 | Who knows, maybe somebody out there is just dying for a drag and drop 6502 5 | emulator in Lisp with a function to single-step instructions. :) 6 | 7 | The `6502` package is for use by emulator writers, the test suite, etc. It 8 | exposes all the types and interesting high-level functionality. It also shadows 9 | Common Lisp's built-in `and` and `bit` symbols since they name 6502 opcodes. 10 | 11 | The `cl-6502` package is more limited and designed for public consumption, 12 | hiding the Addressing Modes and their protocol, the CPU slot accessors, and 13 | other helpers. 14 | 15 | ## Source Code 16 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Switch back to using *CPU* so we don't have to pass to opcodes/addr-modes? 2 | Eliminate generic addition. 3 | Unit tests. 4 | 5 | Ideas: 6 | Write a high-level macro-assembler! (using defmacro and sexps, of course) 7 | Define restarts for step? 8 | -- Define the instruction. 9 | -- Hand over a lambda to funcall/form to eval. 10 | Cleanup the CI integration once sionescu and co improve cl-travis. 11 | 12 | PERF NOTES: 13 | lib6502 -> 00.1 seconds 14 | sbcl -> 02.2 seconds 15 | ccl -> 05.0 seconds 16 | 17 | ; Or from 3.5x to >20x the speed of the NES. 18 | eecfe7 - 03.8 mhz (0.9.1, baseline) 19 | 94b49f - 06.5 mhz (0.9.3, the book) 20 | fb29da - 10.0 mhz (defasm rewrite) 21 | 53e3ee - 11.5 mhz (+new defaddress) 22 | b729e8 - 28.0 mhz (+new status-bit) 23 | b40300 - 48.1 mhz (0.9.5, +declaim) 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: common-lisp 2 | sudo: false 3 | 4 | env: 5 | global: 6 | - PATH=~/.roswell/bin:$PATH 7 | - ROSWELL_INSTALL_DIR=$HOME/.roswell 8 | matrix: 9 | - LISP=sbcl-bin 10 | - LISP=ccl-bin 11 | 12 | install: 13 | - curl -L https://raw.githubusercontent.com/snmsts/roswell/release/scripts/install-for-ci.sh | sh 14 | 15 | cache: 16 | directories: 17 | - $HOME/.roswell 18 | - $HOME/.config/common-lisp 19 | 20 | script: 21 | - ros -s fiveam 22 | -e '(setf fiveam:*debug-on-error* t 23 | fiveam:*debug-on-failure* t)' 24 | -e '(setf *debugger-hook* 25 | (lambda (c h) 26 | (declare (ignore c h)) 27 | (uiop:quit -1)))' 28 | -e "(ql:quickload '(:cl-6502 :cl-6502-test))" 29 | -e "(6502-tests:run! '6502-tests:6502-tests)" 30 | -------------------------------------------------------------------------------- /src/conditions.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502) 2 | 3 | (define-condition illegal-opcode () 4 | ((opcode :initarg :opcode :reader opcode)) 5 | (:report (lambda (condition stream) 6 | (format stream "~X is not a legal opcode." (opcode condition)))) 7 | (:documentation "Illegal opcodes are not currently implemented.")) 8 | 9 | (define-condition invalid-syntax () 10 | ((line :initarg :line :reader line)) 11 | (:report (lambda (condition stream) 12 | (format stream "Syntax for line ~S is invalid." (line condition)))) 13 | (:documentation "Assembly must conform to the syntax in the README.")) 14 | 15 | (define-condition invalid-mode () 16 | ((mode :initarg :mode :reader mode)) 17 | (:report (lambda (condition stream) 18 | (format stream "~A is not a valid addressing mode." (mode condition)))) 19 | (:documentation "Only the 6502 addressing modes have readers and printers.")) 20 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | LISPSRC := $(wildcard *.lisp) 2 | MDOBJS := $(patsubst %, doc/obj/%.md, $(LISPSRC)) $(wildcard *.md) 3 | 4 | book: doc/cl-6502.pdf doc/cl-6502.epub 5 | 6 | doc/obj/%.lisp.md : %.lisp Makefile 7 | awk '/^ *[^;]{3}/ {if (last~/^ *;;;/) printf("\n")} {print} {last=$$0}' < $< | sed -E -e 's/^/ /g' -e 's/^ ;;; ?//g' > $@ 8 | 9 | doc/cl-6502.md: $(MDOBJS) Makefile doc/genbook.sh 10 | (cd doc; ./genbook.sh) > $@ 11 | 12 | doc/cl-6502.pdf: doc/cl-6502.md 13 | pandoc --template=doc/template.latex --latex-engine=lualatex -V fontsize=10pt -V monofont=droidsansmono -V monoscale=.70 -V verbatimspacing=.85 -V mainfont=droidserif -V sansfont=droidsans -V documentclass:book -V geometry:top=1.0in -V geometry:bottom=0.75in -S --toc --chapters -o $@ $< 14 | 15 | doc/cl-6502.epub: doc/cl-6502.md 16 | pandoc -S --toc --chapters -o $@ $< 17 | 18 | clean: 19 | -rm doc/cl-6502.* 20 | -rm doc/obj/*.md 21 | 22 | clean-md: 23 | -rm doc/obj/*.md 24 | -------------------------------------------------------------------------------- /src/parser.md: -------------------------------------------------------------------------------- 1 | ## `parser.lisp`: String processing 2 | 3 | ## References: 4 | [ca65 syntax](http://www.cc65.org/doc/ca65-4.html) 5 | 6 | The parser converts source code in string format to intermediate instructions. 7 | The syntax is simple, following the standard 6502 style, except without any 8 | directives. A single instruction can contain any of the following: a label, an 9 | opcode, an operand, and a comment. 10 | 11 | Operands have different syntax depending upon the addressing modes as defined 12 | in addressing.lisp. The actual value from the operand is still handled by the 13 | parser, and can be represented using decimal, or hexadecimal (if preceded by 14 | "$" a dollar sign), or binary (if preceded by "%" a percent sign), or can be 15 | a label, or a simple expression consisting of some other term plus ("+") 16 | another expression. 17 | 18 | The parser returns a list of instructions, with each slot set to the appropriate 19 | fields from the assembly statement. There may be more than one address-mode in 20 | such instructions, since the syntax is ambiguous. 21 | 22 | ## Source Code 23 | -------------------------------------------------------------------------------- /src/jit.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502) 2 | 3 | (defvar *jit-cache* (make-hash-table) 4 | "The JIT's hot code cache. Currently never invalidated.") 5 | 6 | (defun get-basic-block (cpu) 7 | "Get the opcodes from the current PC to the next branch." 8 | (flet ((op-length (x) (fourth (aref *opcode-meta* x)))) 9 | (loop for pc = (cpu-pc cpu) then (+ pc (op-length op)) 10 | for op = (get-byte pc) collect op 11 | until (member op '(#x90 #xb0 #xf0 #x30 #xd0 #x10 #x50 #x70 12 | #x00 #x4c #x6c #x20 #x40 #x60))))) 13 | 14 | (defun jit-block (opcodes) 15 | "Given a list of opcodes, JIT compile an equivalent function." 16 | (flet ((jit-op (x) `(funcall ,(aref *opcode-funs* x) cpu))) 17 | (compile nil `(lambda (cpu) ,@(mapcar #'jit-op opcodes))))) 18 | 19 | (defun jit-step (cpu pc) 20 | "If the current block has been JIT compiled, run it, otherwise JIT compile it." 21 | (alexandria:if-let (fn (gethash pc *jit-cache*)) 22 | (funcall fn cpu) 23 | (let ((code (jit-block (get-basic-block cpu)))) 24 | (setf (gethash pc *jit-cache*) code) 25 | (funcall code cpu)))) 26 | -------------------------------------------------------------------------------- /tests/perf.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502-tests) 2 | 3 | (eval-when (:compile-toplevel :load-toplevel :execute) 4 | (require 'sb-sprof)) 5 | 6 | (def-suite performance :in 6502-tests) 7 | (in-suite performance) 8 | 9 | (defun profile! (&key (mode :cpu)) 10 | "MODE may be :TIME, :CPU, or :ALLOC. Load Klaus' test suite and use SBCL's 11 | statistical profiler to observe performance while running the test suite." 12 | (klaus-init) 13 | (sb-sprof:with-profiling (:sample-interval 0.001 14 | :max-samples 1000 15 | :show-progress t 16 | :report :graph 17 | :mode mode 18 | :reset t) 19 | (loop until (> (cpu-cc *cpu*) (* 78 (expt 2 20))) 20 | do (step-cpu *cpu* (get-byte (immediate *cpu*)))))) 21 | 22 | (deftest keep-it-fast 23 | "We should not have deteriorating performance. 5 seconds at most." 24 | ;; NOTE: This test based on 64-bit SBCL 1.1.4 on my Debian Thinkpad X200. 25 | (klaus-init) 26 | (let ((start (get-internal-real-time))) 27 | (klaus-test) 28 | (is (< (- (get-internal-real-time) start) #x1400)))) 29 | -------------------------------------------------------------------------------- /tests/disassembler.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502-tests) 2 | 3 | (def-suite disassembler :in 6502-tests) 4 | (in-suite disassembler) 5 | 6 | (deftest disassemble-implied 7 | "Implied mode instructions should disassemble correctly." 8 | (setf (get-byte 0) 0) 9 | (is (search "BRK" (disasm-to-str 0 1))) 10 | (setf (get-byte 0) 234) 11 | (is (search "NOP" (disasm-to-str 0 1)))) 12 | 13 | (deftest disasm-to-list 14 | "We should be able to disassemble code to a sexp-based format." 15 | (setf (get-byte 0) 0) 16 | (is (equalp (disasm-to-list 0 0) '((:brk)))) 17 | (setf (get-byte 0) 234) 18 | (is (equalp (disasm-to-list 0 0) '((:nop))))) 19 | 20 | (deftest disasm-to-list-with-args 21 | "We should be able to disassemble code with args in a sexp-based format." 22 | (setf (get-range 0) #(160 0 200 208 253 237 1 0)) 23 | (is (equalp (disasm-to-list 0 7) 24 | '((:ldy :#$00) (:iny) (:bne :&fd) (:sbc :$0001))))) 25 | 26 | (deftest current-instruction 27 | "We should be able to inspect the current instruction." 28 | (setf (get-range #x3fa5) #(76 165 63) 29 | (cpu-pc cpu) #x3fa5) 30 | (is (equalp (current-instruction cpu) '(:jmp :$3fa5)))) 31 | -------------------------------------------------------------------------------- /src/assemble.md: -------------------------------------------------------------------------------- 1 | ## `assemble.lisp`: This is only the Beginning 2 | 3 | ### References: 4 | * [A port of Henry Baker's comfy-6502](http://josephoswald.nfshost.com/comfy/summary.html) 5 | * [Fun with Lisp: Programming the NES](http://ahefner.livejournal.com/20528.html) 6 | 7 | Since our disassembler can disassemble to either a string or sexp format, we'd 8 | like our assembler to be similarly versatile. Therefore, we'll define a Generic 9 | Function `asm` that works on lists or strings. In addition it takes an optional 10 | environment as a hash table, and a starting address. 11 | 12 | The assembler works by looping over its input, recording any labels, and 13 | assembling each statement one at a time, using delayed functions for any 14 | statements that use a label. Then finally, delayed functions are resolved, and 15 | all the results are concatenated together. Both lists and strings are converted 16 | into an intermediary format, a struct called instruction, which represents 17 | a single statement. 18 | 19 | The parser determines which possible address modes can match the given syntax, 20 | which may be ambiguous due to labels, and matches these modes against what the 21 | given opcode can use. 22 | 23 | ## Source Code 24 | -------------------------------------------------------------------------------- /tests/opcodes.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502-tests) 2 | 3 | (def-suite opcodes :in 6502-tests) 4 | (in-suite opcodes) 5 | 6 | (defun klaus-init (&optional (file "tests/test.bin")) 7 | (let ((test-rom (read-file-into-byte-vector (app-path file)))) 8 | (setf (get-range #x0a) test-rom 9 | (cpu-pc *cpu*) #x1000))) 10 | 11 | (defun klaus-test () 12 | (let ((cycles (* 78 (expt 2 20)))) 13 | (loop until (> (cpu-cc *cpu*) cycles) 14 | for opcode = (get-byte (cpu-pc *cpu*)) 15 | do (step-cpu *cpu* opcode)))) 16 | 17 | (defun speedrun () 18 | (klaus-init "tests/test-brk.bin") 19 | (time (6502::execute *cpu*))) 20 | 21 | (deftest pass-klaus-test-suite 22 | "We should pass Klaus Dorfmann's test suite." 23 | (klaus-init) 24 | (klaus-test) 25 | (destructuring-bind (op addr) (current-instruction cpu) 26 | (let* ((addr-text (symbol-name addr)) 27 | (hex-num (cl-ppcre:scan-to-strings "[0-9a-fA-F]{2,4}" addr-text)) 28 | (pc (parse-integer hex-num :radix 16))) 29 | (is (and (eql op :jmp) (eql pc (cpu-pc cpu)))) 30 | ;; There are multiple traps in the code that are jump-to-self. 31 | ;; Only 0x3c37 is the 'success' macro. Disasm surrounding code to verify. 32 | (is (eql (cpu-pc cpu) #x3c37))))) 33 | -------------------------------------------------------------------------------- /src/jit.md: -------------------------------------------------------------------------------- 1 | ## `jit.lisp`: "JIT" is a bit generous, isn't it? 2 | 3 | Proper JITs involve much careful thinking and engineering to get good results. 4 | This "jit" was more of a quick, fun Sunday evening hack. The whole point of a 5 | compiler is to rewrite code into a faster form that preserves the original 6 | semantics. That entails doing analysis to understand the code and 7 | transformations to improve the parts the compiler understands. As 8 | [littledan says](http://useless-factor.blogspot.com/2009/10/bitfields-in-factor-structs-and-special.html), 9 | every compiler has a "special style" that it knows how to optimize. Thus, 10 | speeding up a language really corresponds to increasing the parts of the 11 | language the compiler understands, growing the special style. 12 | All this JIT does is eliminate the opcode dispatch between branches. It never 13 | invalidates cached code which means that if a program 14 | [modifies itself while running](http://en.wikipedia.org/wiki/Self-modifying_code) 15 | we ignore it, producing incorrect results. As 16 | [Mike Pall has written](http://lambda-the-ultimate.org/node/3851#comment-57646), 17 | a fast interpreter can outrun a naive compiler. **cl-6502** 18 | sticks with interpretation. I just think the JIT is too cute to remove. 19 | 20 | ## Source Code 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Brit Butler 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /src/disassemble.md: -------------------------------------------------------------------------------- 1 | ## `disassemble.lisp`: When one is Lost 2 | 3 | Disassembly actually isn't very tricky...at least on the 6502. Conceptually, you 4 | just want to specify a range of bytes to be viewed as code rather than data and 5 | print its representation. Our storage of metadata in the opcodes array makes it 6 | trivial. We simply loop from start to end, always incrementing by the length of 7 | the opcode rather than 1. 8 | 9 | Disassembling a single instruction at a given index works by reading the byte, 10 | retrieving the matching entry in the opcodes array, and either printing it using 11 | the Addressing Mode's writer or returning a list representing the instruction. 12 | 13 | We use keywords rather than symbols for the lispy syntax since `#$` is a 14 | standard token in 6502 assembly. There is no way to support the standard syntax 15 | for indirect-addressed code in lisp (without readtable hacks) because it uses 16 | parens which are illegal in keywords and symbols, so we use an `@` sign instead. 17 | 18 | Thus, we can disassemble to either a lispy syntax or the standard 6502 syntax in 19 | only a few dozen lines of code. Since we've factored out disassembling a single 20 | opcode, its easy to add a `current-instruction` helper to disassemble whatever 21 | the CPU is about to execute as well. 22 | 23 | ## Source Code 24 | -------------------------------------------------------------------------------- /src/packages.lisp: -------------------------------------------------------------------------------- 1 | (defpackage :6502 2 | (:use :cl) 3 | (:import-from :alexandria #:compose #:emptyp #:flatten #:make-keyword #:named-lambda) 4 | (:export ;; Public API 5 | #:execute #:step-cpu #:asm #:disasm #:disasm-to-str #:disasm-to-list 6 | #:current-instruction #:get-byte #:get-word #:get-range #:*cpu* #:cpu 7 | #:nmi #:reset #:jit-step #:*opcode-meta* 8 | ;; CPU struct 9 | #:make-cpu #:cpu-ar #:cpu-xr #:cpu-yr #:cpu-sr #:cpu-sp #:cpu-pc #:cpu-cc #:u8 #:u16 10 | ;; Addr modes 11 | #:implied #:accumulator #:immediate #:zero-page #:zero-page-x #:zero-page-y 12 | #:absolute #:absolute-x #:absolute-y #:indirect #:indirect-x #:indirect-y #:relative 13 | #:reader #:writer 14 | ;; Helpers 15 | #:wrap-byte #:wrap-word #:status-bit #:defenum #:bytevector)) 16 | 17 | (defpackage :cl-6502 18 | (:documentation "Homepage: Github") 19 | (:use :cl) 20 | (:import-from :6502 #:execute #:step-cpu #:asm #:disasm #:disasm-to-str #:disasm-to-list 21 | #:current-instruction #:get-byte #:get-word #:get-range #:*cpu* #:cpu 22 | #:nmi #:reset #:jit-step) 23 | (:export #:execute #:step-cpu #:asm #:disasm #:disasm-to-str #:disasm-to-list 24 | #:current-instruction #:get-byte #:get-word #:get-range #:*cpu* #:cpu 25 | #:nmi #:reset #:jit-step)) 26 | -------------------------------------------------------------------------------- /src/doc/genbook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This shell scripts generates the top-level Markdown structure of the 4 | # cl-6502 book. 5 | # 6 | # The authors list is automatically generated from Git history, 7 | # ordered from most to least commits. 8 | 9 | cat < 1 { printf("; ") } { printf("%s", $0) } END { print("") }') 15 | 16 | # Introduction 17 | $(cat ../intro.md) 18 | 19 | # Addressing Modes 20 | $(cat ../addressing.md) 21 | $(cat obj/addressing.lisp.md) 22 | 23 | # CPU 24 | $(cat ../cpu.md) 25 | $(cat obj/cpu.lisp.md) 26 | 27 | # Opcode Emulation 28 | $(cat ../opcodes.md) 29 | $(cat obj/opcodes.lisp.md) 30 | 31 | # Exceptional Conditions 32 | $(cat ../conditions.md) 33 | $(cat obj/conditions.lisp.md) 34 | 35 | # Stepping and Execution 36 | $(cat ../utils.md) 37 | $(cat obj/utils.lisp.md) 38 | 39 | # A Naive JIT 40 | $(cat ../jit.md) 41 | $(cat obj/jit.lisp.md) 42 | 43 | # Taking Apart Old Code 44 | $(cat ../disassemble.md) 45 | $(cat obj/disassemble.lisp.md) 46 | 47 | # Parsing Assembly 48 | $(cat ../parser.md) 49 | $(cat obj/parser.lisp.md) 50 | 51 | # Creating New Code 52 | $(cat ../assemble.md) 53 | $(cat obj/assemble.lisp.md) 54 | 55 | # Wrap it with a Bow 56 | $(cat ../packages.md) 57 | $(cat obj/packages.lisp.md) 58 | 59 | $(cat ../lessons.md) 60 | 61 | # Conclusion 62 | $(cat ../outro.md) 63 | 64 | EOF 65 | -------------------------------------------------------------------------------- /cl-6502.asd: -------------------------------------------------------------------------------- 1 | (defsystem #:cl-6502 2 | :name "cl-6502" 3 | :description "An emulator for the MOS 6502 CPU" 4 | :version "0.9.8-dev" 5 | :license "BSD" 6 | :author "Brit Butler " 7 | :pathname "src/" 8 | :depends-on (:alexandria :cl-ppcre) 9 | :serial t 10 | :components ((:file "packages") 11 | (:file "conditions") 12 | (:file "addressing") 13 | (:file "cpu") 14 | (:file "disassemble") 15 | (:file "parser") 16 | (:file "assemble") 17 | (:file "opcodes") 18 | (:file "jit") 19 | (:file "utils")) 20 | :in-order-to ((test-op (test-op cl-6502/test)))) 21 | 22 | (defsystem #:cl-6502/test 23 | :description "A test suite for cl-6502." 24 | :license "BSD" 25 | :author "Brit Butler " 26 | :depends-on (:cl-6502 :fiveam) 27 | :pathname "tests/" 28 | :serial t 29 | :components ((:file "packages") 30 | (:file "fixtures") 31 | (:file "assembler") 32 | (:file "disassembler") 33 | (:file "parser") 34 | (:file "opcodes") 35 | (:file "jit") 36 | #+sbcl (:file "perf")) 37 | :perform (test-op (op c) 38 | (uiop:symbol-call 'fiveam 'run! 39 | (uiop:find-symbol* '6502-tests '6502-tests)))) 40 | 41 | (defpackage #:6502-conf (:export #:app-path)) 42 | (defvar 6502-conf::*basedir* 43 | (make-pathname :defaults *load-truename* :name nil :type nil)) 44 | (defun 6502-conf:app-path (path &rest args) 45 | (merge-pathnames (apply 'format nil path args) 6502-conf::*basedir*)) 46 | -------------------------------------------------------------------------------- /src/cpu.md: -------------------------------------------------------------------------------- 1 | ## `cpu.lisp`: The 6502 VM 2 | 3 | ### References: 4 | * [General](http://nesdev.parodius.com/6502.txt) 5 | * [Registers](http://www.obelisk.demon.co.uk/6502/registers.html) 6 | 7 | Loosely, the idea of `cpu.lisp` is to define a simple VM the rest of the emulator's 8 | behavior is defined in terms of. The first thing to do is construct a model 9 | of the CPU's state. Like all CPUs, the 6502 has a Program Counter pointing 10 | to the next instruction to execute. The Program Counter (or PC) is 16 bits, 11 | meaning the 6502 can address 64k of RAM. It also has Stack Pointer, Accumulator, 12 | X, and Y registers each of which is a single byte. There is a Status Register 13 | with information about the result of the last computation. For emulation purposes, 14 | we'll also add a cycle counter to track CPU execution time. 15 | 16 | Once data structures for the CPU and RAM are defined, we'll want helper functions 17 | for the rest of the emulator to transform them. Common operations we'll want to 18 | support include: 19 | 20 | * Getting and setting a byte/word in RAM. 21 | * Pushing and popping items off the stack. 22 | * Getting and setting individual bits in the status register. 23 | * Resetting the state of the system. 24 | * Executing an [NMI](http://en.wikipedia.org/wiki/Non_maskable_interrupt). 25 | 26 | We'll also add a few things to help ensure the correctness of our emulator: 27 | 28 | * Wrappers to ensure that bytes and words don't overflow. 29 | * Type definitions for a byte (u8) and machine word (u16). 30 | * A macro to simplify the definition of opcodes. 31 | 32 | Most other things we'll need to define our opcodes will be built in language 33 | primitives like arithmetic, conditionals, etc. 34 | 35 | ## Source Code 36 | -------------------------------------------------------------------------------- /src/opcodes.md: -------------------------------------------------------------------------------- 1 | ## `opcodes.lisp`: Enter defasm 2 | 3 | ### References: 4 | * [Opcodes](http://www.obelisk.demon.co.uk/6502/reference.html) 5 | * [Py65](https://github.com/mnaberez/py65/blob/master/src/py65/devices/mpu6502.py) 6 | 7 | Now comes the core of the project, the CPU opcode definitions. The 6502 has 56 8 | instructions, each with several addressing modes. We'll use our `defasm` macro 9 | to define each instruction with all its variants in one place. The variants are 10 | given as a list of lists where each variant is specified like so: 11 | 12 | `(opcode-byte cycles bytes addressing-mode)` 13 | 14 | The `opcode-byte` is the how the opcode is stored in memory, the `cycles` are 15 | how long it takes the CPU to execute, and the `bytes` are how many bytes in RAM 16 | the opcode *and* its arguments use. 17 | 18 | You might have wondered why in `defasm` we increment the Program Counter by 1, 19 | then *later* check `track-pc` before incrementing the rest. Imagining the opcode's 20 | execution makes it obvious. The PC points at the opcode, then we increment the 21 | program counter to point at the first argument. The instruction BODY can run 22 | without worrying about argument offsets. Afterwards, if there were no arguments, 23 | we're already pointing at the next instruction. Otherwise, we just bump the PC 24 | past the arguments. 25 | 26 | If you were reading carefully earlier, you noticed that we wrap the body of 27 | `defasm` in a FLET that defines the `getter` and `setter` functions to access 28 | memory for the opcode's addressing mode. It's advantageous for performance to 29 | compute as much as possible at compile-time, so we have `make-getter` compute 30 | a custom body for the GETTER in `defasm`. 31 | 32 | `make-getter` is there only because, unlike **all** the other instructions, 33 | shifts and rotations (i.e. ASL, LSR, ROL, and ROR), use "raw" addressing in 34 | their accumulator mode but "normal" addressing everywhere else. `make-getter` 35 | takes the instruction mnemonic and addressing mode and factors out the special 36 | casing. Aren't you glad I already hunted those bugs down? 37 | 38 | ## Source Code 39 | -------------------------------------------------------------------------------- /src/addressing.md: -------------------------------------------------------------------------------- 1 | ## `addressing.lisp`: The Addressing Mode Protocol 2 | 3 | ### References: 4 | * [Addressing Modes](http://www.obelisk.demon.co.uk/6502/addressing.html) 5 | 6 | Addressing Modes are perhaps *the* major difference between high-level languages 7 | and assembly. In practically every high-level language, you define named 8 | variables to store your data in and then access your data by name. In assembly, 9 | you store data at a memory address and interact with the address only. To 10 | interact with a given address, you use Addressing Modes. 11 | 12 | Conceptually, an addressing mode is just a function that takes a number and 13 | returns an index into memory. Each addressing mode indexes into memory 14 | differently and comes with a unique syntax which we'll define readers and 15 | writers for to aid with assembly and disassembly. 16 | 17 | The 6502 has 13 addressing modes that can be roughly divided into 5 groups based 18 | on what part of memory they access: 19 | 20 | 1. Basic: CPU registers - implied, immediate, accumulator 21 | 2. Zero-Page: The bottom 256 bytes of RAM - zero-page, zero-page-x, zero-page-y 22 | 3. Absolute: Any address in RAM - absolute, absolute-x, absolute-y 23 | 4. Indirect: The address stored at another address - indirect, indirect-x, indirect-y 24 | 5. Relative: Conditionally go forwards or backwards - relative 25 | 26 | To make things more complicated, you can't just pass any address to any CPU 27 | instruction. Each CPU instruction supports a limited number of addressing modes. 28 | You'll note when we start defining opcodes that certain addressing modes take 29 | more CPU time than others. On many older systems, a large part of writing fast 30 | assembly code is figuring out how to layout your program's data in memory so 31 | you can use the fastest addressing mode possible to access it. 32 | 33 | A functioning emulator needs to know how to parse assembly, print disassebly, get data, 34 | and set data for each addressing mode. **cl-6502** uses 35 | [methods on unique symbols](http://cl-cookbook.sourceforge.net/clos-tutorial/#section-4.5) 36 | for reading and printing, and functions for getting and setting. The reader 37 | is a regular expression, with a placeholder character (underscore) from which 38 | a number, label or expression is extracted. The printer is a lisp format string 39 | that effectively inverts the process. Since the getter and setter need to work 40 | on the same address in memory, and use the same code to compute that address, 41 | we'll use a `defaddress` macro to factor out their shared code. 42 | 43 | ## Source Code 44 | -------------------------------------------------------------------------------- /src/disassemble.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502) 2 | 3 | (defmacro with-disasm ((start end &key op) &body body) 4 | "Loop from START to END, passing each instruction to OP and execute BODY. 5 | OP is PRINT-INSTRUCTION by default. Within BODY, the return value of OP is 6 | bound to RESULT and the length of the instruction in bytes is bound to STEP." 7 | `(loop with index = ,start while (<= index ,end) 8 | for (step result) = (disasm-ins index ,@(when op (list op))) 9 | do (incf index step) ,@body)) 10 | 11 | (defun disasm (start end) 12 | "Disassemble memory from START to END." 13 | (with-disasm (start end))) 14 | 15 | (defun disasm-to-list (start end) 16 | "Disassemble a given region of memory into a sexp-based format." 17 | (with-disasm (start end :op #'sexpify-instruction) collect result)) 18 | 19 | (defun disasm-to-str (start end) 20 | "Call DISASM with the provided args and return its output as a string." 21 | (with-output-to-string (*standard-output*) (disasm start end))) 22 | 23 | (defun disasm-ins (index &optional (disasm-op #'print-instruction)) 24 | "Lookup the metadata for the instruction at INDEX and pass it to 25 | DISASM-OP for formatting and display, returning the instruction length." 26 | (destructuring-bind (name docs cycles bytes mode) 27 | (aref *opcode-meta* (get-byte index)) 28 | (declare (ignore cycles)) 29 | (let ((code-block (coerce (get-range index (+ index bytes)) 'list))) 30 | (list bytes (funcall disasm-op code-block index name docs mode))))) 31 | 32 | (defun print-instruction (bytes index name docs mode) 33 | "Format the instruction at INDEX and its operands for display." 34 | (let ((byte-str (format nil "~{~2,'0x ~}" bytes)) 35 | (args-str (format nil "~A ~A" name (arg-formatter (rest bytes) mode)))) 36 | (format t "$~4,'0x ~9A ;; ~14A ~A~%" index byte-str args-str docs))) 37 | 38 | (defun sexpify-instruction (bytes index name docs mode) 39 | "Given BYTES and metadata, return a sexp-format representation of it." 40 | (declare (ignore index docs)) 41 | (alexandria:if-let ((args (rest bytes)) 42 | (args-str (bytes-to-keyword-syntax bytes mode))) 43 | (mapcar #'make-keyword (list name args-str)) 44 | (mapcar #'make-keyword (list name)))) 45 | 46 | (defun arg-formatter (arg mode) 47 | "Given an instruction's ARG, format it for display using the MODE's WRITER." 48 | (if (member mode '(absolute absolute-x absolute-y indirect)) 49 | (format nil (writer mode) (reverse arg)) 50 | (format nil (writer mode) arg))) 51 | 52 | (defun bytes-to-keyword-syntax (bytes mode) 53 | "Take BYTES and a MODE and return our assembly representation of the arguments." 54 | (let ((result (arg-formatter (rest bytes) mode))) 55 | (flet ((munge-indirect (str) 56 | (cl-ppcre:regex-replace "\\(\\$(.*)\\)(.*)?" str "@\\1\\2"))) 57 | (cl-ppcre:regex-replace ", " (munge-indirect result) ".")))) 58 | 59 | (defun current-instruction (cpu &optional print-p) 60 | "Return a list representing the current instruction. If PRINT-P is non-nil, 61 | print the current address and instruction and return NIL." 62 | (let ((fn (if print-p #'print-instruction #'sexpify-instruction))) 63 | (second (disasm-ins (cpu-pc cpu) fn)))) 64 | -------------------------------------------------------------------------------- /src/lessons.md: -------------------------------------------------------------------------------- 1 | # Lessons Learned - Emulation 2 | 3 | ## High-level emulation is viable 4 | 5 | High-level emulation *can* work and the result is great. On the other hand, it 6 | entails _extra_ work mapping the low-level hardware to your high-level language 7 | back to reasonably fast code. This is a time-consuming process. 8 | 9 | ## What must be fast 10 | 11 | 3 things absolutely must be fast: opcode dispatch, memory reading/writing, and 12 | status bit handling. CPUs only do a few things and most of them your language 13 | already knows how to make fast; arithmetic and assignment, in particular. 14 | Practically every CPU instruction deals with memory somehow, updates the status 15 | register, or both. Think carefully about how to map these operations onto your 16 | language. They should happen a few million times a second. :) 17 | 18 | ## Profile with intuition 19 | 20 | Your language profiler can't show you architectural issues, only hotspots. I 21 | used SBCL's statistical profiler a good bit on this project and regard it highly. 22 | It tracks both CPU usage and memory allocation/consing very well. However, most 23 | of the really big gains came from some mixture of intuition, experimentation, 24 | and good old hard thinking. 25 | 26 | ## Get the API right first 27 | 28 | Consequently, getting the API right is the hard part and the most important. If 29 | you've defined your interfaces correctly, you can rewrite the underlying data 30 | representations or execution strategies to achieve good performance. 31 | 32 | # Lessons Learned - Common Lisp 33 | 34 | ## Structures can be preferable to classes 35 | 36 | Structures are much more static than classes. They also enforce their slot types. 37 | When you have a solid idea of the layout of your data and really need speed, 38 | they're ideal. 39 | 40 | ## CLOS is fast enough 41 | 42 | CLOS, for single-dispatch at least, is really quite fast. When I redesigned the 43 | emulator to avoid a method call for every memory read/write, my benchmark only 44 | ran ~10% faster. I eventually chose to stick with the new scheme for several 45 | reasons, performance was only a minor factor. 46 | 47 | ## Destructuring is more expensive than you think 48 | 49 | My second big speedup came, indirectly, from changing the arguments to the 50 | opcode lambdas. By having the opcode only take a single argument, the CPU, I 51 | avoided the need to destructure the opcode metadata in `step-cpu`. You **don't** 52 | want to destructure a list in your inner loop, no matter how readable it is! 53 | 54 | ## Eval-when is about *data* more than code 55 | 56 | That is, the times I found myself using it always involved computing data at 57 | compile-time that would be stored or accessed at load-time or later. E.g. I used 58 | it to ensure that the status-bit enum was created for use by later macros like 59 | `set-flags-if`. Regardless, [try to go without it](http://fare.livejournal.com/146698.html) if possible. 60 | 61 | ## Use DECLAIM (and DECLARE) wisely 62 | 63 | *DECLAIM* is for global declarations and *DECLARE* is for local ones. Once you've 64 | eked out as many algorithmic gains as possible and figured out your hotspots with 65 | the profiler, recompile your code with `(declaim (optimize speed))` to see what 66 | notes the compiler gives you. Letting the compiler know the *FTYPE* of your most 67 | called functions and inlining a few things can make a big difference. 68 | -------------------------------------------------------------------------------- /tests/parser.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502-tests) 2 | 3 | (def-suite parser :in 6502-tests) 4 | (in-suite parser) 5 | 6 | (deftest parse-just-opcode 7 | (let ((expect (6502::make-instruction 8 | :opcode :brk 9 | :address-mode '(6502::implied)))) 10 | (is (equalp (list expect) (6502::parse-code "brk"))))) 11 | 12 | (deftest parse-label 13 | (let ((expect (6502::make-instruction 14 | :label "start" 15 | :address-mode '(6502::implied)))) 16 | (is (equalp (list expect) (6502::parse-code "start:"))))) 17 | 18 | (deftest parse-opcode-with-operand 19 | (let ((expect (6502::make-instruction 20 | :opcode :lda 21 | :value 123 22 | :address-mode '(6502::immediate)))) 23 | (is (equalp (list expect) (6502::parse-code "lda #123"))))) 24 | 25 | (deftest parse-operand-zeropage 26 | (let ((expect (6502::make-instruction 27 | :opcode :lda 28 | :value 0 29 | :address-mode '(6502::relative 6502::absolute 6502::zero-page)))) 30 | (is (equalp (list expect) (6502::parse-code "lda $00"))))) 31 | 32 | (deftest parse-operand-address 33 | (let ((expect (6502::make-instruction 34 | :opcode :lda 35 | :value 8194 36 | :address-mode '(6502::relative 6502::absolute 6502::zero-page)))) 37 | (is (equalp (list expect) (6502::parse-code "lda $2002"))))) 38 | 39 | (deftest parse-operand-accumulator 40 | (let ((expect (6502::make-instruction 41 | :opcode :lsr 42 | :value "a" 43 | :address-mode '(6502::accumulator)))) 44 | (is (equalp (list expect) (6502::parse-code "lsr a"))))) 45 | 46 | (deftest parse-operand-variable 47 | (let ((expect (6502::make-instruction 48 | :opcode :lda 49 | :value "some_label" 50 | :address-mode '(6502::relative 6502::absolute 6502::zero-page)))) 51 | (is (equalp (list expect) (6502::parse-code "lda some_label"))))) 52 | 53 | (deftest parse-operand-indexing 54 | (let ((expect (6502::make-instruction 55 | :opcode :lda 56 | :value 8194 57 | :address-mode '(6502::absolute-x 6502::zero-page-x)))) 58 | (is (equalp (list expect) (6502::parse-code "lda $2002,x"))))) 59 | 60 | (deftest parse-operand-add-addresses 61 | (let ((expect (6502::make-instruction 62 | :opcode :lda 63 | :value '(+ 700 2) 64 | :address-mode '(6502::relative 6502::absolute 6502::zero-page)))) 65 | (is (equalp (list expect) (6502::parse-code "lda 700+2"))))) 66 | 67 | (deftest parse-operand-add-variable 68 | (let ((expect (6502::make-instruction 69 | :opcode :lda 70 | :value '(+ "some_label" 2) 71 | :address-mode '(6502::relative 6502::absolute 6502::zero-page)))) 72 | (is (equalp (list expect) (6502::parse-code "lda some_label+2"))))) 73 | 74 | (deftest parse-operand-label-indexing 75 | (let ((expect (6502::make-instruction 76 | :opcode :sta 77 | :value "some_label" 78 | :address-mode '(6502::absolute-x 6502::zero-page-x)))) 79 | (is (equalp (list expect) (6502::parse-code "sta some_label,x"))))) 80 | 81 | (deftest parse-operand-label-indirect-ptr 82 | (let ((expect (6502::make-instruction 83 | :opcode :sta 84 | :value "memory_ptr" 85 | :address-mode '(6502::indirect)))) 86 | (is (equalp (list expect) (6502::parse-code "sta (memory_ptr)"))))) 87 | 88 | (deftest parse-operand-label-indirect-indexing 89 | (let ((expect (6502::make-instruction 90 | :opcode :sta 91 | :value "memory_ptr" 92 | :address-mode '(6502::indirect-y)))) 93 | (is (equalp (list expect) (6502::parse-code "sta (memory_ptr),y"))))) 94 | -------------------------------------------------------------------------------- /src/addressing.lisp: -------------------------------------------------------------------------------- 1 | ;;; ### The Protocol 2 | 3 | (in-package :6502) 4 | 5 | (defparameter *address-modes* nil 6 | "A list of all the 6502 Address Modes.") 7 | 8 | (defgeneric reader (mode) 9 | (:documentation "Return a Perl-compatible regex suitable for parsing MODE.") 10 | (:method (mode) (error 'invalid-mode :mode mode))) 11 | 12 | (defgeneric writer (mode) 13 | (:documentation "Return a format string suitable for printing MODE.") 14 | (:method (mode) (error 'invalid-mode :mode mode))) 15 | 16 | (defmacro defaddress (name (&key reader writer cpu-reg) &body body) 17 | "Define an Addressing Mode, NAME, with READER and WRITER methods that take 18 | NAME as a symbol and return a regex or format expression, respectively, 19 | and a function and setf function to get and set the data pointed to by the 20 | given mode." 21 | `(progn 22 | (defmethod reader ((mode (eql ',name))) 23 | ,(cl-ppcre:regex-replace-all "_" reader "([^,()#&]+)")) 24 | (defmethod writer ((mode (eql ',name))) ,writer) 25 | (push ',name *address-modes*) 26 | (defun ,name (cpu) ,@body) 27 | (defun (setf ,name) (value cpu) 28 | ,(if cpu-reg 29 | `(setf ,@body value) 30 | `(setf (get-byte ,@body) value))))) 31 | 32 | (defun make-getter (name mode raw-p) 33 | "Generate an appropriate GETTER for NAME based on RAW-P 34 | and whether or not it is a register shift operation." 35 | (let ((register-shift-op-p (and (member name '(asl lsr rol ror)) 36 | (eql mode 'accumulator)))) 37 | (if (or raw-p register-shift-op-p) 38 | `(,mode cpu) 39 | `(get-byte (,mode cpu))))) 40 | 41 | ;;; ### Addressing Modes 42 | 43 | (defaddress implied (:reader "^$" 44 | :writer "") 45 | nil) 46 | 47 | (defaddress accumulator (:reader "^[aA]$" 48 | :writer "A" 49 | :cpu-reg t) 50 | (cpu-ar cpu)) 51 | 52 | (defaddress immediate (:reader "^#_$" 53 | :writer "~{#$~2,'0x~}" 54 | :cpu-reg t) 55 | (cpu-pc cpu)) 56 | 57 | (defaddress zero-page (:reader "^_$" 58 | :writer "~{$~2,'0x~}") 59 | (get-byte (cpu-pc cpu))) 60 | 61 | (defaddress zero-page-x (:reader "^_,\\s*[xX]$" 62 | :writer "$~{~2,'0x~}, X") 63 | (wrap-byte (+ (get-byte (cpu-pc cpu)) (cpu-xr cpu)))) 64 | 65 | (defaddress zero-page-y (:reader "^_,\\s*[yY]$" 66 | :writer "$~{~2,'0x~}, Y") 67 | (wrap-byte (+ (get-byte (cpu-pc cpu)) (cpu-yr cpu)))) 68 | 69 | (defaddress absolute (:reader "^_$" 70 | :writer "$~{~2,'0x~}") 71 | (get-word (cpu-pc cpu))) 72 | 73 | (defaddress absolute-x (:reader "^_,\\s*[xX]$" 74 | :writer "$~{~2,'0x~}, X") 75 | (let ((result (wrap-word (+ (get-word (cpu-pc cpu)) (cpu-xr cpu))))) 76 | (maybe-update-cycle-count cpu result) 77 | result)) 78 | 79 | (defaddress absolute-y (:reader "^_,\\s*[yY]$" 80 | :writer "$~{~2,'0x~}, Y") 81 | (let ((result (wrap-word (+ (get-word (cpu-pc cpu)) (cpu-yr cpu))))) 82 | (maybe-update-cycle-count cpu result) 83 | result)) 84 | 85 | (defaddress indirect (:reader "^\\(_\\)$" 86 | :writer "($~{~2,'0x~})") 87 | (get-word (get-word (cpu-pc cpu)) t)) 88 | 89 | (defaddress indirect-x (:reader "^\\(_\\),\\s*[xX]$" 90 | :writer "($~{~2,'0x~}), X") 91 | (get-word (wrap-byte (+ (get-byte (cpu-pc cpu)) (cpu-xr cpu))) t)) 92 | 93 | (defaddress indirect-y (:reader "^\\(_\\),\\s*[yY]$" 94 | :writer "($~{~2,'0x~}), Y") 95 | (let* ((addr (get-word (get-byte (cpu-pc cpu)) t)) 96 | (result (wrap-word (+ addr (cpu-yr cpu))))) 97 | (maybe-update-cycle-count cpu result addr) 98 | result)) 99 | 100 | (defaddress relative (:reader "^(&?_)$" 101 | :writer "&~{~2,'0x~}") 102 | (let ((offset (get-byte (cpu-pc cpu)))) 103 | (incf (cpu-cc cpu)) 104 | (let ((result (if (logbitp 7 offset) 105 | (wrap-word (- (cpu-pc cpu) (- #xff offset))) 106 | (wrap-word (+ (cpu-pc cpu) (1+ offset)))))) 107 | (maybe-update-cycle-count cpu result (1+ (cpu-pc cpu))) 108 | result))) 109 | -------------------------------------------------------------------------------- /src/intro.md: -------------------------------------------------------------------------------- 1 | ## High-Level Emulation by Example 2 | 3 | I started working on **cl-6502** to develop a better mental model of assembly 4 | and how CPUs work. The project has evolved into a highly correct, concise 6502 5 | emulator. Its nearest neighbors, and inspirations, are 6 | [lib6502](http://piumarta.com/software/lib6502/) and 7 | [py65](https://github.com/mnaberez/py65) which also make for pretty good 8 | reading if you prefer C or Python to Lisp. :) 9 | 10 | ## The MOS 6502 11 | 12 | The [MOS 6502](http://en.wikipedia.org/wiki/MOS_Technology_6502), as noted in the README, 13 | is famous for its usage in: 14 | 15 | * the [Apple II](http://en.wikipedia.org/wiki/Apple_II_series), 16 | * the [Nintendo](http://en.wikipedia.org/wiki/Nintendo_Entertainment_System), 17 | * the [Commodore 64](http://en.wikipedia.org/wiki/Commodore_64), 18 | * the [BBC Micro](http://en.wikipedia.org/wiki/BBC_Micro), 19 | * Bender, the Terminator, and elsewhere. 20 | 21 | It was, therefore, a large component of the microcomputer boom in the 1980s and 22 | helped usher in the Modern PC era we enjoy today. The 6502 also comes from a 23 | time when assembly programming was necessary for fast, responsive programs. 24 | Consequently, the assembly language was designed with the intent that it would 25 | be used by humans at least as often as it would be generated by a compiler for 26 | a higher-level language. This makes it ideal for education purposes especially 27 | relative to the complex x86 assembly that is prevalent today. 28 | 29 | ## Explicit Goals 30 | 31 | **cl-6502** does have some explicit technical goals: 32 | 33 | * Code size excluding tests should be < 1000 loc. (**0.9.7**: 949 lines) 34 | * Able to run at 8 mhz or faster using a single core on an Intel Core CPU. (**0.9.7**: ~50 mhz) 35 | * Cycle-accurate emulation suitable for use in a full NES emulator. 36 | * Readable as a 6502 introduction for someone with or without a copy of [CLHS](http://www.lispworks.com/documentation/HyperSpec/). 37 | 38 | ## A Word on Performance 39 | 40 | Performance is **not** an explicit goal of the **cl-6502** project. Currently, 41 | it emulates about 15-20x slower than lib6502, a very fast C implementation by 42 | [Ian Piumarta](http://piumarta.com/cv/bio.html), and is 25x faster than the 43 | original NES. This has less to do with the choice of language than with my 44 | naivete about emulation techniques and the emphasis on readable, safe code. 45 | For example, **cl-6502** raises an exception upon encountering illegal 46 | opcodes while lib6502 prints a message and continues. 47 | 48 | ## Why Lisp? 49 | 50 | Common Lisp was chosen for several reasons: 51 | 52 | * It is the language the [primary author](http://redlinernotes.com/) is most familiar with. 53 | * It has strong support for [metaprogramming](http://lists.warhead.org.uk/pipermail/iwe/2005-July/000130.html). 54 | * It supports several [paradigms](http://en.wikipedia.org/wiki/Programming_paradigm) and has good facilities to describe low-level behavior. 55 | * It has [native code compilers](http://www.sbcl.org/) capable of generating fast code. 56 | 57 | ## Emulation Basics 58 | 59 | Emulation isn't particularly different from any other kind of programming. You 60 | need to store the state of the hardware you're emulating and provide functions 61 | to transform that state based on user input to produce correct results. 62 | In **cl-6502**'s case, that means: 63 | 64 | * a CPU object, 65 | * functions for assembly instructions and interrupts the CPU can execute, 66 | * some RAM (represented as an array of bytes) to store the emulated program in, 67 | * functions to make the CPU run a single instruction or run until the program halts. 68 | 69 | More complete coverage of emulation, including whole-system emulation, 70 | can be found in Victor Moya del Barrio's excellent 71 | [Study of the Techniques for Emulation Programming](http://personals.ac.upc.edu/vmoya/docs/emuprog.pdf). 72 | 73 | ## Design Decisions 74 | 75 | These are the current major design decisions in **cl-6502**: 76 | 77 | * Compile-time macros for all status register handling. 78 | * Type declarations for core data structures and memory access functions. 79 | * Storage of opcode metadata and opcode lambdas in separate arrays. 80 | * A naive interpreter loop rather than Context-Threaded Dispatch, JIT, etc. 81 | * Ignore decimal mode support in ADC/SBC since the NES doesn't require it. 82 | 83 | If you get stumped by a macro, I'd encourage you to macroexpand one of its callsites. Don't be afraid to skim something and come back to it later, either. Most importantly, I am _very open_ to feedback of any kind on this text or the code itself. If you find something confusing, feel free to email and ask why I wrote it that way or suggest alternatives. Patches welcome! 84 | 85 | With all that out of the way, let's dive in! 86 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | ## Changes for 0.9.7 (2014-03-31): 2 | 3 | * Rewrite the assembler, adding support for labels, compile-time expressions, 4 | and literals in decimal and binary. Huge thanks go to @dustmop for this 5 | fantastic work. 6 | 7 | ## Changes for 0.9.6 (2013-12-12): 8 | 9 | * Rewrite DEFADDRESS again and stop inlining get-byte. The new implementation 10 | is easier to read and allows upstream clients to monkey patch get-byte 11 | to use a given platform's memory map. 12 | * Reduce consing on SBCL > 1.1.8 due to modarith/type checking changes. 13 | * Make debugging easier by using named-lambdas for the opcodes. 14 | * Minor code cleanups and book clarifications. 15 | 16 | ## Changes for 0.9.5 (2013-07-05): 17 | 18 | * Further performance improvements from type hints: 19 | Emulates the 6502 at ~50 mhz with SBCL 1.1.4.debian on an old Core 2 Duo. 20 | 21 | ## Changes for 0.9.4 (2013-06-19): 22 | 23 | * Major performance improvements from rewriting: 24 | DEFADDRESS, DEFASM, and status-bit handling, again. 25 | Results in a 75% reduction in runtime on Klaus' testsuite! 26 | * Two new chapters in the book: Lessons Learned and A Naive JIT. 27 | * *Incompatible change*: Renamed 6502-STEP to STEP-CPU. 28 | * *Incompatible change*: JIT-EXECUTE was removed. 29 | 30 | ## Changes for 0.9.3 (2013-06-10): 31 | 32 | * Code written in a more literate style with a [readable book](http://redlinernotes.com/docs/cl-6502.pdf)! 33 | 34 | ## Changes for 0.9.2 (2013-05-14): 35 | 36 | * Substantial performance improvements from rewriting: 37 | overflow handling for arithmetic ops (overflow-p), 38 | storage handling for %status-bit (defenum). 39 | Results in a 33% reduction in runtime and an over 90% 40 | reduction in garbage generated for Klaus' testsuite. 41 | * Greatly simplified defasm macro replaces defopcode+defins. 42 | * New Addressing Modes implementation. 43 | 44 | ## Changes for 0.9.1 (2013-04-01): 45 | 46 | * Add a **very** naive JIT compiler. Don't expect big speedups. Do expect bugs. 47 | * Improve error reporting on incorrect assembly. 48 | * Remove comment and label support from assembler. 49 | * Fix symbolic disassembly for certain addressing modes. 50 | 51 | ## Changes for 0.9.0 (2013-03-16): 52 | 53 | * Perfect run on Klaus Dorfmann's test suite aside from decimal mode ADC/SBC. 54 | * Fix bugs in new symbolic assembler. Indirect and *-x *-y addressing modes 55 | were previously not working. Extended test suite to cover these cases. 56 | * Added and exported CURRENT-INSTRUCTION helper. 57 | * Fix stack wraparound in PHP, PLA, etc. 58 | 59 | ## Changes for 0.8.9 (2013-03-03): 60 | 61 | * Add symbolic assembly and disassembly, i.e. sexp-format support. 62 | 63 | ## Changes for 0.8.8 (2013-02-23): 64 | 65 | * Massive package overhaul and docs update. 66 | * Minor dead code removal (i.e. toy assembly progs). 67 | 68 | ## Changes for 0.8.7 (2013-02-21): 69 | 70 | * Tweaked DEFOPCODE to fix addressing mode handling of ASL, LSR, ROL, and ROR. 71 | * Rewrote ROL and ROR, fixing bugs. 72 | * Emulate the infamous 6502 indirect JMP page wrapping bug. 73 | * Fixed incorrect break and unused bit handling in PHP, PLP, and RTI. 74 | * Fixed incorrect overflow bit handling in ADC. 75 | * Fixed incorrect result in LSR due to extra fetch. 76 | * Fixed carry bit handling in SBC, CMP, CPX, and CPY. Previous fix didn't correctly handle all cases. 77 | 78 | ## Changes for 0.8.6 (2013-02-20): 79 | 80 | * Added NMI support. 81 | * Improved assembler error reporting. 82 | * Improved readability of generated code from defopcode/defins. 83 | * Fixed CPU initial state based on values in NESdev wiki. 84 | * Fixed incorrect carry bit handling in CMP,CPX,CPY. 85 | * Fixed incorrect jumps when using JSR opcode. 86 | * Fixed assembler PC tracking bug when using labels. 87 | * General refactoring and code cleanups. 88 | 89 | ## Changes for 0.8.5 (2012-07-22): 90 | 91 | * Assembler supports forward references/jumps. 92 | * Assembler detects addressing mode of a label based on call site, not definition. 93 | * Fixed PC tracking in assembler. 94 | * Improved assembler unit tests. 95 | 96 | ## Changes for 0.8 (2012-07-14): 97 | 98 | * Added assembler with comment and label/var support. 99 | * Miscellaneous improvements to docs and unit tests. 100 | 101 | ## Changes for 0.7.5 (2012-07-11): 102 | 103 | * Huge refactor to status bit handling, correctness improved. 104 | * Improved correctness of ADC and SBC instructions. 105 | 106 | ## Changes for 0.7.2 (2012-06-24): 107 | 108 | * Switched to BSD license. 109 | * Completed addressing mode unit tests. 110 | * Fixed load-time defopcode bug with eval-when. 111 | * Fleshed out README. 112 | 113 | ## Changes for 0.7.1 (2012-06-17): 114 | 115 | * Disassembler correctly formats output based on addressing mode. 116 | * Added API docs. 117 | 118 | ## Changes for 0.7 (2012-06-16): 119 | 120 | * Export 6502-step, execute. 121 | * Improved cycle counting. 122 | * Bugfixes to PC tracking (jsr, jmp, rts, relative mode). 123 | 124 | ## Changes for 0.6.5 (2012-06-11): 125 | 126 | * Added disassembler. 127 | * Bugfixes and cleanups. 128 | 129 | ## Changes for 0.5 (2012-06-10): 130 | 131 | * Initial release. 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cl-6502 - A Readable CPU Emulator 2 | 3 | [![Build Status](https://travis-ci.org/kingcons/cl-6502.svg?branch=master)](https://travis-ci.org/kingcons/cl-6502) 4 | [![Quicklisp](http://quickdocs.org/badge/cl-6502.svg)](http://quickdocs.org/cl-6502/) 5 | 6 | 7 | cl-6502 is a Common Lisp emulator, assembler and disassembler for the 8 | [MOS 6502 processor](http://en.wikipedia.org/wiki/MOS_Technology_6502). 9 | In case that sounds weird to you, the MOS 6502 is famous for its use in... 10 | 11 | * the [Apple II](http://en.wikipedia.org/wiki/Apple_II_series), 12 | * the [original NES](http://en.wikipedia.org/wiki/Nintendo_Entertainment_System), 13 | * the [Commodore 64](http://en.wikipedia.org/wiki/Commodore_64), 14 | * the [BBC Micro](http://en.wikipedia.org/wiki/BBC_Micro), 15 | * and [Michael Steil's phenomenal talk](http://media.ccc.de/browse/congress/2010/27c3-4159-en-reverse_engineering_mos_6502.html) at 27C3. 16 | 17 | I gave a talk on cl-6502 called 'On Programmer Archaeology'. You can watch it [on Vimeo](http://vimeo.com/47364930) or grab [the slides](http://redlinernotes.com/docs/talks/opa.html). A few notes on why I'm writing it are [here](http://blog.redlinernotes.com/posts/On-Interactive-Retrocomputing.html) and minor notes on the design are [here](http://blog.redlinernotes.com/posts/An-Emulator-Design-Pattern.html). 18 | 19 | ## Reading 20 | 21 | Inspired by Luke Gorrie's call for [Readable Programs](http://lukego.github.io/blog/2012/10/24/readable-programs/), there is a readable [PDF book](http://redlinernotes.com/docs/cl-6502.pdf) of the source. You can also produce it from the git repo with: `cd repo/src && make book`. You'll need make, pandoc, and some latex packages (texlive-luatex, texlive-xetex, and texlive-latex-extra on debian) installed to build it yourself. 22 | 23 | ## Install 24 | You are strongly encouraged to use this library via [Quicklisp](http://quicklisp.org/). Simply start your lisp and run: ```(ql:quickload 'cl-6502)```. 25 | 26 | ## Getting Started 27 | * Check out the docs for the [*cl-6502*](http://redlinernotes.com/docs/cl-6502.html) package or have a look on [quickdocs](http://quickdocs.org/cl-6502). 28 | * Play around at the REPL! 29 | * Use it to create your own wacky code artifacts. 30 | * There is also a lower-level *6502* package if you really want to get your hands dirty. NOTE: The 6502 package shadows `BIT` and `AND` so you likely don't want to `:use` it in your own packages. 31 | 32 | In particular, [asm](http://redlinernotes.com/docs/cl-6502.html#asm_func), [disasm](http://redlinernotes.com/docs/cl-6502.html#disasm_func), [execute](http://redlinernotes.com/docs/cl-6502.html#execute_func), [step-cpu](http://redlinernotes.com/docs/cl-6502.html#step-cpu_func), and [reset](http://redlinernotes.com/docs/cl-6502.html#reset_func) are likely of interest. 33 | 34 | ### A simple example: 35 | 36 | 1. Load cl-6502 and switch to the `cl-6502` package. 37 | 2. Write some 6502 code and run it through ```asm``` (e.g. ```(asm "brk")```) to get a bytevector to execute. 38 | 3. Load it into memory with ```(setf (get-range 0) *my-bytevector*)```. 39 | 4. Set the program counter to 0 with ```(setf (6502:cpu-pc *cpu*) 0)```. 40 | 5. Run it with ```(run *cpu*)``` or manually step through it with ```(step-cpu *cpu* (get-byte (cpu-pc *cpu*)))```. 41 | 6. ```(reset)``` the CPU as necessary and keep hacking! :) 42 | 43 | ### Supported Assembler Syntax 44 | There are sexp-based and string-based assemblers, both invoked via `asm`. The string-based assembler expects statements to be separated by newlines. The sexp-based assembler expects each statement to be in its own list. Disassembling to both formats is supported via `disasm` and `disasm-to-list`. Semicolons are treated as "comment to end-of-line" in the string assembler. 45 | 46 | ``` 47 | | Addressing Mode | SEXP-based format | String format | 48 | |-----------------|-------------------|----------------| 49 | | Implied | (:brk) | "brk" | 50 | | Immediate | (:lda :#$00) | "lda #$00" | 51 | | Accumulator | (:rol :a) | "rol a" | 52 | | Zero-page | (:lda :$03) | "lda $03" | 53 | | Zero-page, X | (:lda :$03.x) | "lda $03, x" | 54 | | Zero-page, Y | (:ldx :$03.y) | "ldx $03, y" | 55 | | Absolute | (:sbc :$0001) | "sbc $0001" | 56 | | Absolute, X | (:lda :$1234.x) | "lda $1234, x" | 57 | | Absolute, Y | (:lda :$1234.y) | "lda $1234, y" | 58 | | Indirect | (:jmp :@1234) | "jmp ($1234) | 59 | | Indirect, X | (:lda :@12.x) | "lda ($12), x" | 60 | | Indirect, Y | (:lda :@34.y) | "lda ($34), y" | 61 | | Relative | (:bne :&fd) | "bne &fd" | 62 | ``` 63 | 64 | ## Hacking 65 | 66 | * Using Quicklisp: For local development, git clone this repository into the ```local-projects``` subdirectory of quicklisp. 67 | 68 | To run the tests, after you've loaded *cl-6502* just run ```(asdf:oos 'asdf:test-op 'cl-6502)```. You may need to ```(ql:quickload 'cl-6502-tests)``` to ensure that the fiveam dependency is satisfied first. 69 | 70 | ## License 71 | 72 | The code is under a BSD license except for docs/6502.txt and tests/6502_functional_test.a65 which are only present by 'mere aggregation' and not strictly part of my sources. 73 | -------------------------------------------------------------------------------- /src/parser.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502) 2 | 3 | (defun make-stream (text) 4 | "Make a string displaced onto the given text." 5 | (make-array (length text) :element-type 'character :adjustable t 6 | :displaced-to text :displaced-index-offset 0)) 7 | 8 | (defun try-fetch (stream regex) 9 | "If the stream begins with a regex match, returns the matched text and move 10 | the stream past it. Otherwise, returns nil." 11 | (let ((result (cl-ppcre:scan-to-strings regex stream))) 12 | (when result 13 | (multiple-value-bind (original index-offset) (array-displacement stream) 14 | (adjust-array stream (- (length stream) (length result)) 15 | :displaced-to original 16 | :displaced-index-offset (+ index-offset (length result)))) 17 | result))) 18 | 19 | (defun substream-advance (stream start end) 20 | "Set the stream to a substream at START positions ahead and finishing 21 | at END position." 22 | (multiple-value-bind (original index-offset) (array-displacement stream) 23 | (unless original 24 | (error "substream-advance called with a string, not a stream")) 25 | (adjust-array stream (- end start) :displaced-to original 26 | :displaced-index-offset (+ index-offset start)))) 27 | 28 | (defun skip-white-space (stream) 29 | "Fetches white space from the stream, ignores it and returns the stream." 30 | (try-fetch stream "^\\s+") 31 | stream) 32 | 33 | (defun fetch-label (stream) 34 | "Fetches a label from the stream, or returns nil." 35 | (let ((result (try-fetch stream "^[a-zA-Z][a-zA-Z0-9_]*:"))) 36 | (when result (string-right-trim ":" result)))) 37 | 38 | (defun fetch-opcode (stream) 39 | "Fetches an opcode from the stream as a keyword, or returns nil." 40 | (let ((result (try-fetch stream "^[a-zA-Z]{3}"))) 41 | (when result (intern (string-upcase result) :keyword)))) 42 | 43 | (defun fetch-literal (stream) 44 | "Fetches a literal value from the stream and returns it as an alist 45 | containing the integer value and address mode, or returns nil." 46 | (let ((result (try-fetch stream "^((\\$|\\&)[a-fA-F0-9]+|%[0-1]+|[0-9]+)"))) 47 | (cond 48 | ((not result) nil) 49 | ((find (aref result 0) "$&") (parse-integer (subseq result 1) :radix 16)) 50 | ((char= (aref result 0) #\%) (parse-integer (subseq result 1) :radix 2)) 51 | (t (parse-integer result :radix 10))))) 52 | 53 | (defun fetch-name (stream) 54 | "Fetches a name from the stream, or returns nil." 55 | (try-fetch stream "^[a-zA-Z][a-zA-Z0-9_]*")) 56 | 57 | (defun fetch-term (stream) 58 | "Fetches a literal or name from the stream, or returns nil." 59 | (or (fetch-literal stream) (fetch-name stream))) 60 | 61 | (defun match-operand-address-modes (stream) 62 | "Matches the stream against all address-mode readers, returning those that 63 | match, as well as the positions where the match occurs." 64 | (let ((value-match (list nil nil))) 65 | (list (loop for address-mode in *address-modes* 66 | when (multiple-value-bind (start end match-start match-end) 67 | (cl-ppcre:scan (reader address-mode) stream) 68 | (when start 69 | (setf value-match (list match-start match-end)))) 70 | collect address-mode) value-match))) 71 | 72 | (defun operand-possible-modes-and-value (stream) 73 | "Returns all matching address-modes for the operand, along with positions 74 | where the match occurs." 75 | (destructuring-bind (address-modes (match-starts match-ends)) 76 | (match-operand-address-modes stream) 77 | (cond 78 | ((find 'implied address-modes) (list (list 'implied) 0 0)) 79 | ((find 'accumulator address-modes) (list (list 'accumulator) 0 1)) 80 | (t (list address-modes (aref match-starts 0) (aref match-ends 0)))))) 81 | 82 | (defun fetch-expression (stream) 83 | "Fetches an expression from the stream, either a term or a term plus another." 84 | (let ((term-1 (fetch-term stream))) 85 | (if (try-fetch (skip-white-space stream) "^\\+") 86 | (list '+ term-1 (fetch-expression stream)) 87 | term-1))) 88 | 89 | (defun fetch-operand (stream) 90 | "Fetches the operand, returning its numerical value and possible 91 | address-modes." 92 | (destructuring-bind (possible-modes value-start value-end) 93 | (operand-possible-modes-and-value stream) 94 | (substream-advance stream value-start value-end) 95 | (list (fetch-expression stream) possible-modes))) 96 | 97 | (defun parse-line (text) 98 | "Converts a line of text into an instruction representing the assembly code." 99 | (let* ((stream (make-stream text)) 100 | (label (fetch-label (skip-white-space stream))) 101 | (opcode (fetch-opcode (skip-white-space stream))) 102 | (operand (fetch-operand (skip-white-space stream))) 103 | (value (first operand)) 104 | (address-modes (second operand))) 105 | (make-instruction :label label :opcode opcode :value value 106 | :address-mode address-modes))) 107 | 108 | (defun strip-comment (text) 109 | "Removes comment and white space from end of string." 110 | (let ((pos (position #\; text))) 111 | (when pos (setf text (subseq text 0 pos)))) 112 | (string-right-trim " " text)) 113 | 114 | (defun parse-code (text) 115 | "Parses the assembly source text and returns the assembled code as a list of 116 | alists." 117 | (loop for line in (cl-ppcre:split "\\n" text) 118 | when (parse-line (strip-comment line)) collect it)) 119 | -------------------------------------------------------------------------------- /tests/assembler.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502-tests) 2 | 3 | (def-suite assembler :in 6502-tests) 4 | (in-suite assembler) 5 | 6 | (defmacro bvec (&rest args) 7 | "Construct a byte vector from ARGS where ARGS are symbols representing hex." 8 | (let ((nums (cl-ppcre:split " " (format nil "~{~A~^ ~}" args)))) 9 | `(vector ,@(mapcar (lambda (x) (parse-integer x :radix 16)) nums)))) 10 | 11 | (deftest assemble-ignores-case 12 | "Case shouldn't come into play in assembly." 13 | (is (equalp (asm "brk") #(0))) 14 | (is (equalp (asm "BRK") #(0))) 15 | (is (equalp (asm "bRK") #(0))) 16 | (is (equalp (asm "LSR a") #(#x4a))) 17 | (is (equalp (asm "lsr A") #(#x4a)))) 18 | 19 | (deftest assemble-implied 20 | "Implied mode instructions should be assembled correctly." 21 | (is (equalp (asm "nop") #(#xea))) 22 | (is (equalp (asm '(:nop)) #(#xea)))) 23 | 24 | (deftest assemble-accumulator 25 | "Accumulator mode instructions should be assembled correctly." 26 | (is (equalp (asm "rol a") #(#x2a))) 27 | (is (equalp (asm '(:rol :a)) #(#x2a)))) 28 | 29 | (deftest assemble-immediate 30 | "Immediate mode instructions should be assembled correctly." 31 | (let ((expected (bvec a9 12))) 32 | (is (equalp (asm "lda #$12") expected)) 33 | (is (equalp (asm '(:lda :#$12)) expected)))) 34 | 35 | (deftest assemble-immediate-decimal 36 | "Immediate mode using decimal should be assembled correctly." 37 | (let ((expected (bvec a9 0c))) 38 | (is (equalp (asm "lda #12") expected)) 39 | (is (equalp (asm '(:lda :#12)) expected)))) 40 | 41 | (deftest assemble-zero-page 42 | "Zero-page mode instructions should be assembled correctly." 43 | (let ((expected (bvec a5 03))) 44 | (is (equalp (asm "lda $03") expected)) 45 | (is (equalp (asm '(:lda :$03)) expected)))) 46 | 47 | (deftest assemble-zero-page-x 48 | "Zero-page-x mode instructions should be assembled correctly." 49 | (let ((expected (bvec b5 03))) 50 | (is (equalp (asm "lda $03, x") expected)) 51 | (is (equalp (asm '(:lda :$03.x)) expected)))) 52 | 53 | (deftest assemble-zero-page-y 54 | "Zero-page-y mode instructions should be assembled correctly." 55 | (let ((expected (bvec b6 03))) 56 | (is (equalp (asm "ldx $03, y") expected)) 57 | (is (equalp (asm '(:ldx :$03.y)) expected)))) 58 | 59 | (deftest assemble-absolute 60 | "Absolute mode instructions should be assembled correctly." 61 | (let ((expected (bvec ed 11 1))) 62 | (is (equalp (asm "sbc $0111") expected)) 63 | (is (equalp (asm '(:sbc :$0111)) expected)))) 64 | 65 | (deftest assemble-absolute-x 66 | "Absolute-x mode instructions should be assembled correctly." 67 | (let ((expected (bvec bd 34 12))) 68 | (is (equalp (asm "lda $1234, x") expected)) 69 | (is (equalp (asm '(:lda :$1234.x)) expected)))) 70 | 71 | (deftest assemble-absolute-y 72 | "Absolute-y mode instructions should be assembled correctly." 73 | (let ((expected (bvec b9 34 12))) 74 | (is (equalp (asm "lda $1234, y") expected)) 75 | (is (equalp (asm '(:lda :$1234.y)) expected)))) 76 | 77 | (deftest assemble-indirect 78 | "Indirect mode instructions should be assembled correctly." 79 | (let ((expected (bvec 6c 34 12))) 80 | (is (equalp (asm "jmp ($1234)") expected)) 81 | (is (equalp (asm '(:jmp :@1234)) expected)))) 82 | 83 | (deftest assemble-indirect-x 84 | "Indirect-x mode instructions should be assembled correctly." 85 | (let ((expected (bvec a1 12))) 86 | (is (equalp (asm "lda ($12), x") expected)) 87 | (is (equalp (asm '((:lda :@12.x))) expected)))) 88 | 89 | (deftest assemble-indirect-y 90 | "Indirect-y mode instructions should be assembled correctly." 91 | (let ((expected (bvec b1 34))) 92 | (is (equalp (asm "lda ($34), y") expected)) 93 | (is (equalp (asm '(:lda :@34.y)) expected)))) 94 | 95 | (deftest assemble-relative 96 | "Relative mode instructions should be assembled correctly." 97 | (let ((expected (bvec d0 fd))) 98 | (is (equalp (asm "bne &fd") expected)) 99 | (is (equalp (asm '(:bne :&fd)) expected)))) 100 | 101 | (deftest assemble-comment 102 | "Comments (;) should be ignored. Code before comments should not be ignored." 103 | (is (equalp (asm " ; blah blah blah") #())) 104 | (is (equalp (asm " BRK ; foo bar baz") #(0)))) 105 | 106 | (deftest assemble-program 107 | "A basic program should assemble correctly." 108 | (let ((code (format nil "CLC~% LDA #$00~% LDY #$00~% 109 | INY~% bne &fd~% sbc $0001~% brk"))) 110 | (is (equalp (asm code) #(24 169 0 160 0 200 208 253 229 1 0))) 111 | (setf (get-range 0) (asm code) (cpu-pc cpu) 0) 112 | (cl-6502:execute cpu) 113 | (is (eql (cpu-ar cpu) 86)))) 114 | 115 | (deftest assemble-forward-relative 116 | "A program with a forward relative jump should assemble correctly." 117 | (let ((code (format nil "CLC~% LDA #$00~% LDY #$00~% bne &02~% nop~% 118 | nop~% INY~% bne &fd~% sbc $0001~% brk"))) 119 | (setf (get-range 0) (asm code) (cpu-pc cpu) 0) 120 | (cl-6502:execute cpu) 121 | (is (eql (cpu-ar cpu) 86)))) 122 | 123 | (deftest assemble-symbolic 124 | "A sexp-format program should assemble correctly." 125 | (is (equalp (asm '(:brk)) #(0))) 126 | (is (equalp (asm '(:nop)) #(234)))) 127 | 128 | (deftest assemble-symbolic-with-args 129 | "A sexp-format program with args should assemble correctly." 130 | (let ((code '((:ldy :#$00) 131 | (:iny) 132 | (:bne :&fd) 133 | (:sbc :$0123)))) 134 | (is (equalp (asm code) #(160 0 200 208 253 237 35 1))))) 135 | 136 | (deftest assemble-back-label-absolute 137 | "A program with a back reference label in an absolute jump should 138 | assemble correctly." 139 | (let ((code (format nil "nop~%start:~%lda $1000~%jmp start")) 140 | (expected (bvec ea ad 00 10 4c 01 00))) 141 | (is (equalp (asm code) expected)))) 142 | 143 | (deftest assemble-back-label-relative 144 | "A program with a back reference label in a relative jump should 145 | assemble correctly." 146 | (let ((code (format nil "nop~%start:~%lda $1000~%bne start")) 147 | (expected (bvec ea ad 00 10 d0 fb))) 148 | (is (equalp (asm code) expected)))) 149 | 150 | ; (deftest assemble-pc "*" nil)? 151 | -------------------------------------------------------------------------------- /src/doc/template.latex: -------------------------------------------------------------------------------- 1 | \documentclass[oneside,$if(fontsize)$$fontsize$,$endif$$if(lang)$$lang$,$endif$]{$documentclass$} 2 | \usepackage[T1]{fontenc} 3 | \usepackage{lmodern} 4 | % Use sans for sections, serif for main 5 | \usepackage{sectsty} 6 | \allsectionsfont{\sffamily} 7 | % Nicer chapter headings 8 | \usepackage{titlesec,color} 9 | \definecolor{gray75}{gray}{0.75} 10 | \definecolor{gray30}{gray}{0.30} 11 | \newcommand{\hsp}{\hspace{20pt}} 12 | \titleformat{\chapter}[hang]{\Huge\sffamily}{\textcolor{gray30}{\thechapter}\hsp\textcolor{gray75}{|}\hsp}{0pt}{\Huge\sffamily\textcolor{gray30}} 13 | % Convert footnotes to margin notes 14 | \usepackage{setspace} 15 | \usepackage{cprotect} 16 | \let\oldfootnote\footnote 17 | \renewcommand\footnote[1]{\marginpar{#1}} 18 | \let\oldmarginpar\marginpar 19 | \renewcommand\marginpar[1]{\-\oldmarginpar{\setlength{\parindent}{0pt}\setlength{\parskip}{6pt plus 2pt minus 1pt}\raggedright\itshape\small\leavevmode\color{gray30}#1}} 20 | %\renewcommand\marginpar[1]{\-\oldmarginpar{\setstretch{1.0}\raggedright\itshape\small #1}} 21 | % Make nice title 22 | \usepackage{titling} 23 | \renewcommand{\maketitlehooka}{\sffamily} 24 | \pretitle{\begin{center}\Huge} 25 | \posttitle{\par\end{center}\vskip 0.5em} 26 | % Make nicer headers 27 | \usepackage{fancyhdr} 28 | \pagestyle{fancy} 29 | \renewcommand{\chaptermark}[1]{\markboth{#1}{}} 30 | \renewcommand{\headrulewidth}{0pt} 31 | \renewcommand{\footrulewidth}{0pt} 32 | \lhead{\nouppercase{\textcolor{gray30}{\leftmark}\ {\bfseries\textcolor{gray75}|} \textcolor{gray30}\rightmark}} 33 | \rhead{\nouppercase{\textcolor{gray30}\thepage}} 34 | \cfoot{} 35 | \fancypagestyle{plain}{\fancyhf{}} 36 | \fancyheadoffset[R]{0.0in} 37 | % Smaller spacing with bullets 38 | %\usepackage{enumitem} 39 | %\setlist{nolistsep} 40 | % Smaller line spacing with verbatim code listings 41 | $if(verbatimspacing)$ 42 | \usepackage{etoolbox} 43 | \preto{\verbatim}{\edef\tempstretch{\baselinestretch}\par\setstretch{$verbatimspacing$}} 44 | \appto{\endverbatim}{\vspace{-\tempstretch\baselineskip}\vspace{\baselineskip}} 45 | $endif$ 46 | \usepackage{amssymb,amsmath} 47 | \usepackage{ifxetex,ifluatex} 48 | \usepackage{fixltx2e} % provides \textsubscript 49 | % use microtype if available 50 | \IfFileExists{microtype.sty}{\usepackage{microtype}}{} 51 | \ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex 52 | \usepackage[utf8]{inputenc} 53 | $if(euro)$ 54 | \usepackage{eurosym} 55 | $endif$ 56 | \else % if luatex or xelatex 57 | \usepackage{fontspec} 58 | \ifxetex 59 | \usepackage{xltxtra,xunicode} 60 | \fi 61 | \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase} 62 | \newcommand{\euro}{€} 63 | $if(mainfont)$ 64 | \setmainfont{$mainfont$} 65 | $endif$ 66 | $if(sansfont)$ 67 | \setsansfont{$sansfont$} 68 | $endif$ 69 | $if(monofont)$ 70 | $if(monoscale)$ 71 | \setmonofont[Scale=$monoscale$]{$monofont$} 72 | $else$ 73 | \setmonofont{$monofont$} 74 | $endif$ 75 | $endif$ 76 | $if(mathfont)$ 77 | \setmathfont{$mathfont$} 78 | $endif$ 79 | \fi 80 | $if(geometry)$ 81 | \usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} 82 | $endif$ 83 | $if(natbib)$ 84 | \usepackage{natbib} 85 | \bibliographystyle{plainnat} 86 | $endif$ 87 | $if(biblatex)$ 88 | \usepackage{biblatex} 89 | $if(biblio-files)$ 90 | \bibliography{$biblio-files$} 91 | $endif$ 92 | $endif$ 93 | $if(listings)$ 94 | \usepackage{listings} 95 | $endif$ 96 | $if(lhs)$ 97 | \lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} 98 | $endif$ 99 | $if(highlighting-macros)$ 100 | $highlighting-macros$ 101 | $endif$ 102 | $if(verbatim-in-note)$ 103 | \usepackage{fancyvrb} 104 | $endif$ 105 | $if(fancy-enums)$ 106 | % Redefine labelwidth for lists; otherwise, the enumerate package will cause 107 | % markers to extend beyond the left margin. 108 | \makeatletter\AtBeginDocument{% 109 | \renewcommand{\@listi} 110 | {\setlength{\labelwidth}{4em}} 111 | }\makeatother 112 | \usepackage{enumerate} 113 | $endif$ 114 | $if(tables)$ 115 | \usepackage{ctable} 116 | \usepackage{float} % provides the H option for float placement 117 | $endif$ 118 | $if(graphics)$ 119 | \usepackage{graphicx} 120 | % We will generate all images so they have a width \maxwidth. This means 121 | % that they will get their normal width if they fit onto the page, but 122 | % are scaled down if they would overflow the margins. 123 | \makeatletter 124 | \def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth 125 | \else\Gin@nat@width\fi} 126 | \makeatother 127 | \let\Oldincludegraphics\includegraphics 128 | \renewcommand{\includegraphics}[1]{\Oldincludegraphics[width=\maxwidth]{#1}} 129 | $endif$ 130 | \ifxetex 131 | \usepackage[setpagesize=false, % page size defined by xetex 132 | unicode=false, % unicode breaks when used with xetex 133 | xetex]{hyperref} 134 | \else 135 | \usepackage[unicode=true]{hyperref} 136 | \fi 137 | \hypersetup{breaklinks=true, 138 | bookmarks=true, 139 | pdfauthor={$author-meta$}, 140 | pdftitle={$title-meta$}, 141 | colorlinks=true, 142 | urlcolor=$if(urlcolor)$$urlcolor$$else$blue$endif$, 143 | linkcolor=$if(linkcolor)$$linkcolor$$else$magenta$endif$, 144 | pdfborder={0 0 0}} 145 | $if(links-as-notes)$ 146 | % Make links footnotes instead of hotlinks: 147 | \renewcommand{\href}[2]{#2\footnote{\url{#1}}} 148 | $endif$ 149 | $if(strikeout)$ 150 | \usepackage[normalem]{ulem} 151 | % avoid problems with \sout in headers with hyperref: 152 | \pdfstringdefDisableCommands{\renewcommand{\sout}{}} 153 | $endif$ 154 | \setlength{\parindent}{0pt} 155 | \setlength{\parskip}{6pt plus 2pt minus 1pt} 156 | \setlength{\emergencystretch}{3em} % prevent overfull lines 157 | $if(numbersections)$ 158 | $else$ 159 | \setcounter{secnumdepth}{0} 160 | $endif$ 161 | $if(verbatim-in-note)$ 162 | \VerbatimFootnotes % allows verbatim text in footnotes 163 | $endif$ 164 | $if(lang)$ 165 | \ifxetex 166 | \usepackage{polyglossia} 167 | \setmainlanguage{$mainlang$} 168 | \else 169 | \usepackage[$lang$]{babel} 170 | \fi 171 | $endif$ 172 | $for(header-includes)$ 173 | $header-includes$ 174 | $endfor$ 175 | 176 | $if(title)$ 177 | \title{$title$} 178 | $endif$ 179 | \author{$for(author)$$author$$sep$ \and $endfor$} 180 | \date{$date$} 181 | 182 | \begin{document} 183 | $if(title)$ 184 | \maketitle 185 | $endif$ 186 | 187 | \newgeometry{bottom=1.0in,left=1.0in,right=0.5in,includemp=true,marginparwidth=1.5in,marginparsep=0.25in} 188 | 189 | $for(include-before)$ 190 | $include-before$ 191 | 192 | $endfor$ 193 | $if(toc)$ 194 | { 195 | \hypersetup{linkcolor=black} 196 | \pagestyle{plain} 197 | \tableofcontents 198 | \cleardoublepage 199 | } 200 | $endif$ 201 | $body$ 202 | 203 | $if(natbib)$ 204 | $if(biblio-files)$ 205 | $if(biblio-title)$ 206 | $if(book-class)$ 207 | \renewcommand\bibname{$biblio-title$} 208 | $else$ 209 | \renewcommand\refname{$biblio-title$} 210 | $endif$ 211 | $endif$ 212 | \bibliography{$biblio-files$} 213 | 214 | $endif$ 215 | $endif$ 216 | $if(biblatex)$ 217 | \printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ 218 | 219 | $endif$ 220 | $for(include-after)$ 221 | $include-after$ 222 | 223 | $endfor$ 224 | \end{document} 225 | -------------------------------------------------------------------------------- /src/assemble.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502) 2 | 3 | (defconstant +relative-branch-size-byte+ 2) 4 | (defconstant +max-byte+ 256) 5 | (defparameter +absolute-modes+ '(absolute absolute-x absolute-y)) 6 | (defparameter +zero-page-modes+ '(zero-page zero-page-x zero-page-y)) 7 | 8 | (defgeneric asm (source &optional init-env org-start) 9 | (:documentation "Assemble SOURCE into a bytevector and return it.")) 10 | 11 | (defmethod asm ((source list) &optional init-env org-start) 12 | (assemble-code-block (list-to-instructions source) init-env org-start)) 13 | 14 | (defmethod asm ((source string) &optional init-env org-start) 15 | (assemble-code-block (parse-code source) init-env org-start)) 16 | 17 | (defstruct instruction 18 | "Represents a single line of code." 19 | (label nil :type (or null string)) 20 | (opcode nil :type (or null symbol)) 21 | (address-mode nil :type (or null symbol list)) 22 | (value nil :type (or null u16 list string))) 23 | 24 | (defun list-to-instructions (instructions) 25 | "Given a list of assembly tuples, convert them to instructions." 26 | (unless (listp (first instructions)) 27 | (setf instructions (list instructions))) 28 | (loop for tuple in instructions collect (apply #'tuple-to-instruction tuple))) 29 | 30 | (defun tuple-to-instruction (opcode &optional operand) 31 | "Given an opcode and value, as symbols, convert them to an instruction." 32 | (unless operand 33 | (return-from tuple-to-instruction 34 | (make-instruction :opcode opcode :address-mode 'implied))) 35 | (let ((token (transform-sexp-syntax operand))) 36 | (destructuring-bind (possible-modes value-start value-end) 37 | (operand-possible-modes-and-value token) 38 | (let ((stream (make-stream (coerce (subseq token value-start value-end) 39 | '(vector character))))) 40 | (make-instruction :opcode opcode :value (fetch-literal stream) 41 | :address-mode possible-modes))))) 42 | 43 | (defun transform-sexp-syntax (sexp-token) 44 | "Given a SEXP-token using an indirect, *.x or *.y addressing mode, transform 45 | it to use the classic string assembly syntax." 46 | (substitute #\, #\. (cl-ppcre:regex-replace "\@([^.]*)(.*)?" 47 | (string sexp-token) "($\\1)\\2"))) 48 | 49 | (defmacro resolve-byte (place env) 50 | "Given a place and an environment, if that place is a function, call that 51 | function with the environment and assign the result to the place." 52 | (let ((byte-name (gensym))) 53 | `(when (functionp ,place) 54 | (let ((,byte-name (funcall ,place ,env))) 55 | (setf ,place ,byte-name))))) 56 | 57 | (defun assemble-code-block (code-block &optional init-env org-start) 58 | "Given a list of instructions, assemble each to a byte vector." 59 | (let ((env (or init-env (make-hash-table :test 'equal))) 60 | (output (make-array 0 :fill-pointer 0 :adjustable t)) 61 | (pc-start (or org-start 0))) 62 | ; Build byte vector, without labels. 63 | (loop for instruction in code-block 64 | do (let ((bytes (assemble-instruction instruction 65 | (+ pc-start (length output)) env))) 66 | (loop for b in bytes 67 | do (vector-push-extend b output)))) 68 | ; Resolve labels in the byte vector. 69 | (loop for i from 0 below (length output) 70 | do (resolve-byte (aref output i) env)) 71 | output)) 72 | 73 | (defun assemble-instruction (instruction pc env) 74 | "Given an instruction, and the current program counter, fill the environment 75 | with any labels and assemble instruction to a list of bytes." 76 | (with-slots (opcode value label) instruction 77 | (when label 78 | (setf (gethash label env) pc)) 79 | (when opcode 80 | (let ((mode (decide-address-mode instruction env))) 81 | (list* (find-opcode opcode mode) (process-args value mode pc)))))) 82 | 83 | (defun find-opcode (opcode mode) 84 | "Finds an opcode matching OPCODE and MODE, raising ILLEGAL-OPCODE otherwise." 85 | (let ((match (position-if #'(lambda (e) (match-opcode-data e opcode mode)) 86 | *opcode-meta*))) 87 | (or match (error 'illegal-opcode :opcode (list opcode mode))))) 88 | 89 | (defun process-args (value address-mode pc) 90 | "Given the operand value, address-mode, and program counter, return a list of 91 | assembled bytes, using delayed functions for labels or expressions." 92 | (case address-mode 93 | ((absolute absolute-x absolute-y indirect) 94 | (list (make-byte value pc :low) (make-byte value pc :high))) 95 | ((implied accumulator) nil) 96 | (relative (list (make-byte value pc :relative))) 97 | (otherwise (list (make-byte value pc :low))))) 98 | 99 | (defun decide-address-mode (instruction env) 100 | "Finds the desired address mode, matching what the opcode allows to what was 101 | parsed from the operand's syntax." 102 | (with-slots (opcode address-mode value) instruction 103 | (let ((modes (if (listp address-mode) address-mode (list address-mode))) 104 | (opcode-modes (get-opcode-address-modes opcode))) 105 | (if (and (zero-page-address value env) 106 | (intersection opcode-modes +zero-page-modes+)) 107 | (setf modes (set-difference modes +absolute-modes+)) 108 | (setf modes (set-difference modes +zero-page-modes+))) 109 | (first (intersection modes opcode-modes))))) 110 | 111 | (defun get-opcode-address-modes (opcode) 112 | "Given an opcode, return the possible address modes for that operation." 113 | (loop for e across *opcode-meta* 114 | when (match-opcode-data e opcode :any) collect (fifth e))) 115 | 116 | (defun match-opcode-data (data opcode &optional (address-mode :any)) 117 | "Returns whether the asm metadata matches the given opcode, and address-mode 118 | if it is provided." 119 | (and (eq (first data) (intern (symbol-name opcode) :6502)) 120 | (or (eq address-mode :any) (eq address-mode (fifth data))))) 121 | 122 | (defun zero-page-address (addr env) 123 | "Returns whether the address is a zero page access." 124 | (cond 125 | ((numberp addr) (< addr +max-byte+)) 126 | ((stringp addr) 127 | (let ((addr (gethash addr env))) 128 | (and (numberp addr) (< addr +max-byte+)))) 129 | ((listp addr) nil) 130 | (t (error "Invalid address" :argument addr)))) 131 | 132 | (defun make-byte (value pc type) 133 | "Given an integer, return a single byte for the required type. Given a label, 134 | return a delayed function to calculate the same, once labels are defined." 135 | (cond 136 | ((stringp value) 137 | (lambda (env) 138 | (let ((addr (or (gethash value env) 139 | (error "Undefined label" :argument value)))) 140 | (when (eq type :relative) 141 | (setf addr (- addr pc +relative-branch-size-byte+))) 142 | (make-byte addr pc type)))) 143 | ((and (listp value) (eq (first value) '+)) 144 | (lambda (env) 145 | (destructuring-bind (unused-plus operand-1 operand-2) value 146 | (declare (ignore unused-plus)) 147 | (+ (make-and-resolve-byte operand-1 pc type env) 148 | (make-and-resolve-byte operand-2 pc type env))))) 149 | ((numberp value) 150 | (if (eq type :high) (floor (/ value +max-byte+)) (mod value +max-byte+))) 151 | (t (error "Cannot make-byte" :argument value)))) 152 | 153 | (defun make-and-resolve-byte (operand pc type env) 154 | "Given an operand, convert it to a byte, resolving any delayed functions." 155 | (let ((value (make-byte operand pc type))) 156 | (resolve-byte value env) 157 | value)) 158 | -------------------------------------------------------------------------------- /src/cpu.lisp: -------------------------------------------------------------------------------- 1 | ;;; ### Core Data Types 2 | 3 | (in-package :6502) 4 | 5 | (deftype u8 () '(unsigned-byte 8)) 6 | (deftype u16 () '(unsigned-byte 16)) 7 | 8 | (defstruct cpu 9 | "A 6502 CPU with an extra slot for tracking the cycle count/clock ticks." 10 | (pc #xfffc :type u16) ;; program counter 11 | (sp #xfd :type u8) ;; stack pointer 12 | (sr #x24 :type u8) ;; status register 13 | (xr 0 :type u8) ;; x register 14 | (yr 0 :type u8) ;; y register 15 | (ar 0 :type u8) ;; accumulator 16 | (cc 0 :type fixnum)) ;; cycle counter 17 | 18 | (defmethod initialize-instance :after ((cpu cpu) &key) 19 | (setf (cpu-pc cpu) (absolute cpu))) 20 | 21 | (defun bytevector (size) 22 | "Return an array of the given SIZE with element-type u8." 23 | (make-array size :element-type 'u8)) 24 | 25 | ;;; ### Tasty Globals 26 | 27 | (declaim (type (simple-array u8 (#x10000)) *ram*)) 28 | (defparameter *ram* (bytevector #x10000) 29 | "A lovely hunk of bytes.") 30 | 31 | (defparameter *cpu* (make-cpu) 32 | "The 6502 instance used by default during execution.") 33 | 34 | (declaim (type (simple-vector 256) *opcode-funs*)) 35 | (defparameter *opcode-funs* (make-array #x100 :element-type '(or function null)) 36 | "The opcode lambdas used during emulation.") 37 | 38 | (defparameter *opcode-meta* (make-array #x100 :initial-element nil) 39 | "A mapping of opcodes to metadata lists.") 40 | 41 | ;;; ### Helpers 42 | 43 | (defgeneric reset (obj) 44 | (:documentation "Reset the OBJ to an initial state.") 45 | (:method (obj) (initialize-instance obj))) 46 | 47 | (defgeneric nmi (obj) 48 | (:documentation "Generate a non-maskable interrupt. Used for vblanking in NES.") 49 | (:method (obj) 50 | (stack-push-word (cpu-pc obj) obj) 51 | (stack-push (cpu-sr obj) obj) 52 | (setf (cpu-pc obj) (get-word #xfffa)))) 53 | 54 | (declaim (inline wrap-byte wrap-word wrap-page) 55 | (ftype (function (fixnum) u8) wrap-byte)) 56 | (defun wrap-byte (value) 57 | "Wrap VALUE so it conforms to (typep value 'u8), i.e. a single byte." 58 | (logand value #xff)) 59 | 60 | (declaim (ftype (function (fixnum) u16) wrap-word)) 61 | (defun wrap-word (value) 62 | "Wrap VALUE so it conforms to (typep value 'u16), i.e. a machine word." 63 | (logand value #xffff)) 64 | 65 | (defun wrap-page (address) 66 | "Wrap the given ADDRESS, ensuring that we don't cross a page boundary. 67 | e.g. If we (get-word address)." 68 | (+ (logand address #xff00) (logand (1+ address) #xff))) 69 | 70 | (declaim (ftype (function (u16) u8) get-byte)) 71 | (defun get-byte (address) 72 | "Get a byte from RAM at the given ADDRESS." 73 | (aref *ram* address)) 74 | 75 | (defun (setf get-byte) (new-val address) 76 | "Set ADDRESS in *ram* to NEW-VAL." 77 | (setf (aref *ram* address) new-val)) 78 | 79 | (defun get-word (address &optional wrap-p) 80 | "Get a word from RAM starting at the given ADDRESS." 81 | (+ (get-byte address) 82 | (ash (get-byte (if wrap-p (wrap-page address) (1+ address))) 8))) 83 | 84 | (defun (setf get-word) (new-val address) 85 | "Set ADDRESS and (1+ ADDRESS) in *ram* to NEW-VAL, little endian ordering." 86 | (setf (get-byte address) (wrap-byte (ash new-val -8)) 87 | (get-byte (1+ address)) (wrap-byte new-val))) 88 | 89 | (defun get-range (start &optional end) 90 | "Get a range of bytes from RAM, starting from START and stopping at END if 91 | provided." 92 | (subseq *ram* start end)) 93 | 94 | (defun (setf get-range) (bytevector start) 95 | "Replace the contents of RAM, starting from START with BYTEVECTOR." 96 | (setf (subseq *ram* start (+ start (length bytevector))) bytevector)) 97 | 98 | (declaim (inline stack-push stack-pop)) 99 | (defun stack-push (value cpu) 100 | "Push the byte VALUE on the stack and decrement the SP." 101 | (setf (get-byte (+ (cpu-sp cpu) #x100)) (wrap-byte value)) 102 | (setf (cpu-sp cpu) (wrap-byte (1- (cpu-sp cpu))))) 103 | 104 | (defun stack-push-word (value cpu) 105 | "Push the 16-bit word VALUE onto the stack." 106 | (stack-push (wrap-byte (ash value -8)) cpu) 107 | (stack-push (wrap-byte value) cpu)) 108 | 109 | (defun stack-pop (cpu) 110 | "Pop the value pointed to by the SP and increment the SP." 111 | (setf (cpu-sp cpu) (wrap-byte (1+ (cpu-sp cpu)))) 112 | (get-byte (+ (cpu-sp cpu) #x100))) 113 | 114 | (defun stack-pop-word (cpu) 115 | "Pop a 16-bit word off the stack." 116 | (+ (stack-pop cpu) (ash (stack-pop cpu) 8))) 117 | 118 | (defmacro defenum (name (&rest keys)) 119 | "Define a function named %NAME, that takes KEY as an arg and returns the 120 | index of KEY. KEYS should be scalar values." 121 | (let ((enum (make-hash-table))) 122 | (loop for i = 0 then (1+ i) 123 | for key in keys 124 | do (setf (gethash key enum) i)) 125 | `(defun ,(intern (format nil "%~:@(~A~)" name)) (key) 126 | (let ((enum ,enum)) 127 | (gethash key enum))))) 128 | 129 | (eval-when (:compile-toplevel :load-toplevel :execute) 130 | (defenum status-bit (:carry :zero :interrupt :decimal 131 | :break :unused :overflow :negative))) 132 | 133 | (defmacro status-bit (key) 134 | "Test if KEY is set in the status register. KEY should be a keyword." 135 | `(logand (cpu-sr cpu) ,(ash 1 (%status-bit key)))) 136 | 137 | (defmacro set-status-bit (key new-val) 138 | "Set bit KEY in the status reg to NEW-VAL. KEY should be a keyword." 139 | `(setf (ldb (byte 1 ,(%status-bit key)) (cpu-sr cpu)) ,new-val)) 140 | 141 | (defmacro set-flags-if (&rest flag-preds) 142 | "Takes any even number of arguments where the first is a keyword denoting a 143 | status bit and the second is a funcallable predicate that takes no arguments. 144 | It will set each flag to 1 if its predicate is true, otherwise 0." 145 | `(progn 146 | ,@(loop for (flag pred . nil) on flag-preds by #'cddr 147 | collecting `(set-status-bit ,flag (if ,pred 1 0))))) 148 | 149 | (defun overflow-p (result reg mem) 150 | "Checks whether the sign of RESULT is found in the signs of REG or MEM." 151 | (flet ((sign-of (x) (logbitp 7 x))) 152 | (not (or (eql (sign-of result) (sign-of reg)) 153 | (eql (sign-of result) (sign-of mem)))))) 154 | 155 | (defun maybe-update-cycle-count (cpu address &optional start) 156 | "If ADDRESS crosses a page boundary, add an extra cycle to CPU's count. If 157 | START is provided, test that against ADDRESS. Otherwise, use the absolute address." 158 | (let ((operand (or start (absolute cpu)))) 159 | (declare (type u16 operand) 160 | (type u16 address) 161 | (type (or null u16) start)) 162 | (when (not (= (logand operand #xff00) 163 | (logand address #xff00))) 164 | (incf (cpu-cc cpu))))) 165 | 166 | (defmacro branch-if (predicate) 167 | "Take a Relative branch if PREDICATE is true, otherwise increment the PC." 168 | `(if ,predicate 169 | (setf (cpu-pc cpu) (relative cpu)) 170 | (incf (cpu-pc cpu)))) 171 | 172 | (defun rotate-byte (integer count cpu) 173 | "Rotate the bits of INTEGER by COUNT. If COUNT is negative, rotate right." 174 | (let ((result (ash integer count))) 175 | (if (plusp (status-bit :carry)) 176 | (ecase count 177 | (01 (logior result #x01)) 178 | (-1 (logior result #x80))) 179 | result))) 180 | 181 | ;;; ### Opcode Macrology 182 | 183 | (defmacro defasm (name (&key (docs "") raw-p (track-pc t)) 184 | modes &body body) 185 | "Define a 6502 instruction NAME, storing its DOCS and metadata in *opcode-meta*, 186 | and a lambda that executes BODY in *opcode-funs*. Within BODY, the functions 187 | GETTER and SETTER can be used to get and set values for the current addressing 188 | mode, respectively. TRACK-PC can be passed nil to disable program counter updates 189 | for branching/jump operations. If RAW-P is true, GETTER will return the mode's 190 | address directly, otherwise it will return the byte at that address. MODES is a 191 | list of opcode metadata lists: (opcode cycles bytes mode)." 192 | `(progn 193 | ,@(loop for (op cycles bytes mode) in modes collect 194 | `(setf (aref *opcode-meta* ,op) ',(list name docs cycles bytes mode))) 195 | ,@(loop for (op cycles bytes mode) in modes collect 196 | `(setf (aref *opcode-funs* ,op) 197 | (named-lambda ,(intern (format nil "~A-~X" name op)) (cpu) 198 | (incf (cpu-pc cpu)) 199 | (flet ((getter () 200 | ,(make-getter name mode raw-p)) 201 | (setter (x) 202 | (setf (,mode cpu) x))) 203 | ,@body) 204 | ,@(when track-pc 205 | `((incf (cpu-pc cpu) ,(1- bytes)))) 206 | (incf (cpu-cc cpu) ,cycles)))))) 207 | -------------------------------------------------------------------------------- /docs/cl-6502.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | API for CL-6502 6 | 39 | 53 | 54 |
55 |
API for package: 56 | cl-6502
57 |
58 |
Homepage: Github
59 |
60 |
61 | -Variables
62 |
63 |
64 |
65 | *cpu*: 66 | #S(|6502|:CPU :PC 65532 :SP 253 :SR 36 :XR 0 :YR 0 :AR 0 :CC 0) 67 | variable
68 |
69 |
The 6502 instance used by default during execution.
70 |
71 |
72 | -Conditions
73 |
74 |
75 |
76 | cpu 77 | (structure-object) 78 | class
79 |
80 |
A 6502 CPU with an extra slot for tracking the cycle count/clock ticks.
81 |
82 |
83 | -Functions
84 |
85 |
86 |
87 | asm 88 | source 89 | standard-generic-function
90 |
91 |
Assemble SOURCE into a bytevector and return it.
92 |
93 |
94 | current-instruction 95 | cpu &optional print-p 96 | function
97 |
98 |
Return a list representing the current instruction. If PRINT-P is non-nil,
 99 | print the current address and instruction and return NIL.
100 |
101 |
102 | disasm 103 | start end 104 | function
105 |
106 |
Disassemble memory from START to END.
107 |
108 |
109 | disasm-to-list 110 | start end 111 | function
112 |
113 |
Disassemble a given region of memory into a sexp-based format.
114 |
115 |
116 | disasm-to-str 117 | start end 118 | function
119 |
120 |
Call DISASM with the provided args and return its output as a string.
121 |
122 |
123 | execute 124 | cpu 125 | function
126 |
127 |
Step the CPU until a BRK instruction.
128 |
129 |
130 | get-byte 131 | address 132 | function
133 |
134 |
Get a byte from RAM at the given ADDRESS.
135 |
136 |
137 | (setf get-byte) 138 | new-val address 139 | function
140 |
141 |
Set ADDRESS in *ram* to NEW-VAL.
142 |
143 |
144 | get-range 145 | start &optional end 146 | function
147 |
148 |
Get a range of bytes from RAM, starting from START and stopping at END if
149 | provided.
150 |
151 |
152 | (setf get-range) 153 | bytevector start 154 | function
155 |
156 |
Replace the contents of RAM, starting from START with BYTEVECTOR.
157 |
158 |
159 | get-word 160 | address &optional wrap-p 161 | function
162 |
163 |
Get a word from RAM starting at the given ADDRESS.
164 |
165 |
166 | (setf get-word) 167 | new-val address 168 | function
169 |
170 |
Set ADDRESS and (1+ ADDRESS) in *ram* to NEW-VAL, little endian ordering.
171 |
172 |
173 | jit-step 174 | cpu pc 175 | function
176 |
177 |
If the current block has been JIT compiled, run it, otherwise JIT compile it.
178 |
179 |
180 | nmi 181 | obj 182 | standard-generic-function
183 |
184 |
Generate a non-maskable interrupt. Used for vblanking in NES.
185 |
186 |
187 | reset 188 | obj 189 | standard-generic-function
190 |
191 |
Reset the OBJ to an initial state.
192 |
193 |
194 | step-cpu 195 | cpu opcode 196 | function
197 |
198 |
Step the CPU through the next OPCODE.
199 | -------------------------------------------------------------------------------- /src/opcodes.lisp: -------------------------------------------------------------------------------- 1 | (in-package :6502) 2 | 3 | (defasm adc (:docs "Add to Accumulator with Carry") 4 | ((#x61 6 2 indirect-x) 5 | (#x65 3 2 zero-page) 6 | (#x69 2 2 immediate) 7 | (#x6d 4 3 absolute) 8 | (#x71 5 2 indirect-y) 9 | (#x75 4 2 zero-page-x) 10 | (#x79 4 3 absolute-y) 11 | (#x7d 4 3 absolute-x)) 12 | (let ((result (+ (cpu-ar cpu) (getter) (status-bit :carry)))) 13 | (set-flags-if :carry (> result #xff) 14 | :overflow (overflow-p result (cpu-ar cpu) (getter)) 15 | :negative (logbitp 7 result) 16 | :zero (zerop (wrap-byte result))) 17 | (setf (cpu-ar cpu) (wrap-byte result)))) 18 | 19 | (defasm and (:docs "And with Accumulator") 20 | ((#x21 6 2 indirect-x) 21 | (#x25 3 2 zero-page) 22 | (#x29 2 2 immediate) 23 | (#x2d 4 3 absolute) 24 | (#x31 5 2 indirect-y) 25 | (#x35 4 2 zero-page-x) 26 | (#x39 4 3 absolute-y) 27 | (#x3d 4 3 absolute-x)) 28 | (let ((result (setf (cpu-ar cpu) (logand (cpu-ar cpu) (getter))))) 29 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 30 | 31 | (defasm asl (:docs "Arithmetic Shift Left") 32 | ((#x06 5 2 zero-page) 33 | (#x0a 2 1 accumulator) 34 | (#x0e 6 3 absolute) 35 | (#x16 6 2 zero-page-x) 36 | (#x1e 7 3 absolute-x)) 37 | (let* ((operand (getter)) 38 | (result (wrap-byte (ash operand 1)))) 39 | (set-flags-if :carry (logbitp 7 operand) 40 | :zero (zerop result) 41 | :negative (logbitp 7 result)) 42 | (setter result))) 43 | 44 | (defasm bcc (:docs "Branch on Carry Clear" :track-pc nil) 45 | ((#x90 2 2 relative)) 46 | (branch-if (zerop (status-bit :carry)))) 47 | 48 | (defasm bcs (:docs "Branch on Carry Set" :track-pc nil) 49 | ((#xb0 2 2 relative)) 50 | (branch-if (plusp (status-bit :carry)))) 51 | 52 | (defasm beq (:docs "Branch if Equal" :track-pc nil) 53 | ((#xf0 2 2 relative)) 54 | (branch-if (plusp (status-bit :zero)))) 55 | 56 | (defasm bit (:docs "Test Bits in Memory with Accumulator") 57 | ((#x24 3 2 zero-page) 58 | (#x2c 4 3 absolute)) 59 | (let ((operand (getter))) 60 | (set-flags-if :zero (zerop (logand (cpu-ar cpu) operand)) 61 | :negative (logbitp 7 operand) 62 | :overflow (logbitp 6 operand)))) 63 | 64 | (defasm bmi (:docs "Branch on Negative Result" :track-pc nil) 65 | ((#x30 2 2 relative)) 66 | (branch-if (plusp (status-bit :negative)))) 67 | 68 | (defasm bne (:docs "Branch if Not Equal" :track-pc nil) 69 | ((#xd0 2 2 relative)) 70 | (branch-if (zerop (status-bit :zero)))) 71 | 72 | (defasm bpl (:docs "Branch on Positive Result" :track-pc nil) 73 | ((#x10 2 2 relative)) 74 | (branch-if (zerop (status-bit :negative)))) 75 | 76 | (defasm brk (:docs "Force Break") 77 | ((#x00 7 1 implied)) 78 | (let ((pc (wrap-word (1+ (cpu-pc cpu))))) 79 | (stack-push-word pc cpu) 80 | (set-status-bit :break 1) 81 | (stack-push (cpu-sr cpu) cpu) 82 | (set-status-bit :interrupt 1) 83 | (setf (cpu-pc cpu) (get-word #xfffe)))) 84 | 85 | (defasm bvc (:docs "Branch on Overflow Clear" :track-pc nil) 86 | ((#x50 2 2 relative)) 87 | (branch-if (zerop (status-bit :overflow)))) 88 | 89 | (defasm bvs (:docs "Branch on Overflow Set" :track-pc nil) 90 | ((#x70 2 2 relative)) 91 | (branch-if (plusp (status-bit :overflow)))) 92 | 93 | (defasm clc (:docs "Clear Carry Flag") 94 | ((#x18 2 1 implied)) 95 | (set-status-bit :carry 0)) 96 | 97 | (defasm cld (:docs "Clear Decimal Flag") 98 | ((#xd8 2 1 implied)) 99 | (set-status-bit :decimal 0)) 100 | 101 | (defasm cli (:docs "Clear Interrupt Flag") 102 | ((#x58 2 1 implied)) 103 | (set-status-bit :interrupt 0)) 104 | 105 | (defasm clv (:docs "Clear Overflow Flag") 106 | ((#xb8 2 1 implied)) 107 | (set-status-bit :overflow 0)) 108 | 109 | (defasm cmp (:docs "Compare Memory with Accumulator") 110 | ((#xc1 6 2 indirect-x) 111 | (#xc5 3 2 zero-page) 112 | (#xc9 2 2 immediate) 113 | (#xcd 4 3 absolute) 114 | (#xd1 5 2 indirect-y) 115 | (#xd5 4 2 zero-page-x) 116 | (#xd9 4 3 absolute-y) 117 | (#xdd 4 3 absolute-x)) 118 | (let ((result (- (cpu-ar cpu) (getter)))) 119 | (set-flags-if :carry (not (minusp result)) 120 | :zero (zerop result) 121 | :negative (logbitp 7 result)))) 122 | 123 | (defasm cpx (:docs "Compare Memory with X register") 124 | ((#xe0 2 2 immediate) 125 | (#xe4 3 2 zero-page) 126 | (#xec 4 3 absolute)) 127 | (let ((result (- (cpu-xr cpu) (getter)))) 128 | (set-flags-if :carry (not (minusp result)) 129 | :zero (zerop result) 130 | :negative (logbitp 7 result)))) 131 | 132 | (defasm cpy (:docs "Compare Memory with Y register") 133 | ((#xc0 2 2 immediate) 134 | (#xc4 3 2 zero-page) 135 | (#xcc 4 3 absolute)) 136 | (let ((result (- (cpu-yr cpu) (getter)))) 137 | (set-flags-if :carry (not (minusp result)) 138 | :zero (zerop result) 139 | :negative (logbitp 7 result)))) 140 | 141 | (defasm dec (:docs "Decrement Memory") 142 | ((#xc6 5 2 zero-page) 143 | (#xce 6 3 absolute) 144 | (#xd6 6 2 zero-page-x) 145 | (#xde 7 3 absolute-x)) 146 | (let ((result (wrap-byte (1- (getter))))) 147 | (setter result) 148 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 149 | 150 | (defasm dex (:docs "Decrement X register") 151 | ((#xca 2 1 implied)) 152 | (let ((result (setf (cpu-xr cpu) (wrap-byte (1- (cpu-xr cpu)))))) 153 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 154 | 155 | (defasm dey (:docs "Decrement Y register") 156 | ((#x88 2 1 implied)) 157 | (let ((result (setf (cpu-yr cpu) (wrap-byte (1- (cpu-yr cpu)))))) 158 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 159 | 160 | (defasm eor (:docs "Exclusive OR with Accumulator") 161 | ((#x41 6 2 indirect-x) 162 | (#x45 3 2 zero-page) 163 | (#x49 2 2 immediate) 164 | (#x4d 4 3 absolute) 165 | (#x51 5 2 indirect-y) 166 | (#x55 4 2 zero-page-x) 167 | (#x59 4 3 absolute-y) 168 | (#x5d 4 3 absolute-x)) 169 | (let ((result (setf (cpu-ar cpu) (logxor (getter) (cpu-ar cpu))))) 170 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 171 | 172 | (defasm inc (:docs "Increment Memory") 173 | ((#xe6 5 2 zero-page) 174 | (#xee 6 3 absolute) 175 | (#xf6 6 2 zero-page-x) 176 | (#xfe 7 3 absolute-x)) 177 | (let ((result (wrap-byte (1+ (getter))))) 178 | (setter result) 179 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 180 | 181 | (defasm inx (:docs "Increment X register") 182 | ((#xe8 2 1 implied)) 183 | (let ((result (setf (cpu-xr cpu) (wrap-byte (1+ (cpu-xr cpu)))))) 184 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 185 | 186 | (defasm iny (:docs "Increment Y register") 187 | ((#xc8 2 1 implied)) 188 | (let ((result (setf (cpu-yr cpu) (wrap-byte (1+ (cpu-yr cpu)))))) 189 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 190 | 191 | (defasm jmp (:docs "Jump Unconditionally" :raw-p t :track-pc nil) 192 | ((#x4c 3 3 absolute) 193 | (#x6c 5 3 indirect)) 194 | (setf (cpu-pc cpu) (getter))) 195 | 196 | (defasm jsr (:docs "Jump to Subroutine" :raw-p t :track-pc nil) 197 | ((#x20 6 3 absolute)) 198 | (stack-push-word (wrap-word (1+ (cpu-pc cpu))) cpu) 199 | (setf (cpu-pc cpu) (getter))) 200 | 201 | (defasm lda (:docs "Load Accumulator from Memory") 202 | ((#xa1 6 2 indirect-x) 203 | (#xa5 3 2 zero-page) 204 | (#xa9 2 2 immediate) 205 | (#xad 4 3 absolute) 206 | (#xb1 5 2 indirect-y) 207 | (#xb5 4 2 zero-page-x) 208 | (#xb9 4 3 absolute-y) 209 | (#xbd 4 3 absolute-x)) 210 | (let ((result (setf (cpu-ar cpu) (getter)))) 211 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 212 | 213 | (defasm ldx (:docs "Load X register from Memory") 214 | ((#xa2 2 2 immediate) 215 | (#xa6 3 2 zero-page) 216 | (#xae 4 3 absolute) 217 | (#xb6 4 2 zero-page-y) 218 | (#xbe 4 3 absolute-y)) 219 | (let ((result (setf (cpu-xr cpu) (getter)))) 220 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 221 | 222 | (defasm ldy (:docs "Load Y register from Memory") 223 | ((#xa0 2 2 immediate) 224 | (#xa4 3 2 zero-page) 225 | (#xac 4 3 absolute) 226 | (#xbc 4 3 absolute-x) 227 | (#xb4 4 2 zero-page-x)) 228 | (let ((result (setf (cpu-yr cpu) (getter)))) 229 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 230 | 231 | (defasm lsr (:docs "Logical Shift Right") 232 | ((#x46 5 2 zero-page) 233 | (#x4a 2 1 accumulator) 234 | (#x4e 6 3 absolute) 235 | (#x56 6 2 zero-page-x) 236 | (#x5e 7 3 absolute-x)) 237 | (let* ((operand (getter)) 238 | (result (ash operand -1))) 239 | (set-flags-if :carry (logbitp 0 operand) 240 | :zero (zerop result) 241 | :negative (logbitp 7 result)) 242 | (setter result))) 243 | 244 | (defasm nop (:docs "No Operation") 245 | ((#xea 2 1 implied)) 246 | nil) 247 | 248 | (defasm ora (:docs "Bitwise OR with Accumulator") 249 | ((#x01 6 2 indirect-x) 250 | (#x05 3 2 zero-page) 251 | (#x09 2 2 immediate) 252 | (#x0d 4 3 absolute) 253 | (#x11 5 2 indirect-y) 254 | (#x15 4 2 zero-page-x) 255 | (#x19 4 3 absolute-y) 256 | (#x1d 4 3 absolute-x)) 257 | (let ((result (setf (cpu-ar cpu) (logior (cpu-ar cpu) (getter))))) 258 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 259 | 260 | (defasm pha (:docs "Push Accumulator") 261 | ((#x48 3 1 implied)) 262 | (stack-push (cpu-ar cpu) cpu)) 263 | 264 | (defasm php (:docs "Push Processor Status") 265 | ((#x08 3 1 implied)) 266 | (stack-push (logior (cpu-sr cpu) #x10) cpu)) 267 | 268 | (defasm pla (:docs "Pull Accumulator from Stack") 269 | ((#x68 4 1 implied)) 270 | (let ((result (setf (cpu-ar cpu) (stack-pop cpu)))) 271 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 272 | 273 | (defasm plp (:docs "Pull Processor Status from Stack") 274 | ((#x28 4 1 implied)) 275 | (let ((result (logior (stack-pop cpu) #x20))) 276 | (setf (cpu-sr cpu) (logandc2 result #x10)))) 277 | 278 | (defasm rol (:docs "Rotate Left") 279 | ((#x2a 2 1 accumulator) 280 | (#x26 5 2 zero-page) 281 | (#x2e 6 3 absolute) 282 | (#x36 6 2 zero-page-x) 283 | (#x3e 7 3 absolute-x)) 284 | (let* ((operand (getter)) 285 | (result (wrap-byte (rotate-byte operand 1 cpu)))) 286 | (setter result) 287 | (set-flags-if :carry (logbitp 7 operand) 288 | :zero (zerop result) 289 | :negative (logbitp 7 result)))) 290 | 291 | (defasm ror (:docs "Rotate Right") 292 | ((#x66 5 2 zero-page) 293 | (#x6a 2 1 accumulator) 294 | (#x6e 6 3 absolute) 295 | (#x76 6 2 zero-page-x) 296 | (#x7e 7 3 absolute-x)) 297 | (let* ((operand (getter)) 298 | (result (wrap-byte (rotate-byte operand -1 cpu)))) 299 | (setter result) 300 | (set-flags-if :carry (logbitp 0 operand) 301 | :zero (zerop result) 302 | :negative (logbitp 7 result)))) 303 | 304 | (defasm rti (:docs "Return from Interrupt") 305 | ((#x40 6 1 implied)) 306 | (setf (cpu-sr cpu) (logior (stack-pop cpu) #x20)) 307 | (setf (cpu-pc cpu) (stack-pop-word cpu))) 308 | 309 | (defasm rts (:docs "Return from Subroutine" :track-pc nil) 310 | ((#x60 6 1 implied)) 311 | (setf (cpu-pc cpu) (1+ (stack-pop-word cpu)))) 312 | 313 | (defasm sbc (:docs "Subtract from Accumulator with Carry") 314 | ((#xe1 6 2 indirect-x) 315 | (#xe5 3 2 zero-page) 316 | (#xe9 2 2 immediate) 317 | (#xed 4 3 absolute) 318 | (#xf1 5 2 indirect-y) 319 | (#xf5 4 2 zero-page-x) 320 | (#xf9 4 3 absolute-y) 321 | (#xfd 4 3 absolute-x)) 322 | (flet ((flip-bit (position x) (logxor (expt 2 position) x))) 323 | (let* ((operand (getter)) 324 | (carry-bit (flip-bit 0 (status-bit :carry))) 325 | (result (- (cpu-ar cpu) operand carry-bit))) 326 | (set-flags-if :zero (zerop (wrap-byte result)) 327 | :overflow (overflow-p result (cpu-ar cpu) (flip-bit 7 operand)) 328 | :negative (logbitp 7 result) 329 | :carry (not (minusp result))) 330 | (setf (cpu-ar cpu) (wrap-byte result))))) 331 | 332 | (defasm sec (:docs "Set Carry Flag") 333 | ((#x38 2 1 implied)) 334 | (set-status-bit :carry 1)) 335 | 336 | (defasm sed (:docs "Set Decimal Flag") 337 | ((#xf8 2 1 implied)) 338 | (set-status-bit :decimal 1)) 339 | 340 | (defasm sei (:docs "Set Interrupt Flag") 341 | ((#x78 2 1 implied)) 342 | (set-status-bit :interrupt 1)) 343 | 344 | (defasm sta (:docs "Store Accumulator") 345 | ((#x81 6 2 indirect-x) 346 | (#x85 3 2 zero-page) 347 | (#x8d 4 3 absolute) 348 | (#x91 6 2 indirect-y) 349 | (#x95 4 2 zero-page-x) 350 | (#x99 5 3 absolute-y) 351 | (#x9d 5 3 absolute-x)) 352 | (setter (cpu-ar cpu))) 353 | 354 | (defasm stx (:docs "Store X register") 355 | ((#x86 3 2 zero-page) 356 | (#x8e 4 3 absolute) 357 | (#x96 4 2 zero-page-y)) 358 | (setter (cpu-xr cpu))) 359 | 360 | (defasm sty (:docs "Store Y register") 361 | ((#x84 3 2 zero-page) 362 | (#x8c 4 3 absolute) 363 | (#x94 4 2 zero-page-x)) 364 | (setter (cpu-yr cpu))) 365 | 366 | (defasm tax (:docs "Transfer Accumulator to X register") 367 | ((#xaa 2 1 implied)) 368 | (let ((result (setf (cpu-xr cpu) (cpu-ar cpu)))) 369 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 370 | 371 | (defasm tay (:docs "Transfer Accumulator to Y register") 372 | ((#xa8 2 1 implied)) 373 | (let ((result (setf (cpu-yr cpu) (cpu-ar cpu)))) 374 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 375 | 376 | (defasm tsx (:docs "Transfer Stack Pointer to X register") 377 | ((#xba 2 1 implied)) 378 | (let ((result (setf (cpu-xr cpu) (cpu-sp cpu)))) 379 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 380 | 381 | (defasm txa (:docs "Transfer X register to Accumulator") 382 | ((#x8a 2 1 implied)) 383 | (let ((result (setf (cpu-ar cpu) (cpu-xr cpu)))) 384 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 385 | 386 | (defasm txs (:docs "Transfer X register to Stack Pointer") 387 | ((#x9a 2 1 implied)) 388 | (setf (cpu-sp cpu) (cpu-xr cpu))) 389 | 390 | (defasm tya (:docs "Transfer Y register to Accumulator") 391 | ((#x98 2 1 implied)) 392 | (let ((result (setf (cpu-ar cpu) (cpu-yr cpu)))) 393 | (set-flags-if :zero (zerop result) :negative (logbitp 7 result)))) 394 | -------------------------------------------------------------------------------- /docs/6502.txt: -------------------------------------------------------------------------------- 1 | 6502 Microprocessor 2 | 3 | Most of the following information has been taking out of the "Commodore 64 4 | Programmers Reference Manual" simply because it was available in electronic 5 | form and there appears to be no difference between this documentation and 6 | the 6502 documentation, they are both from the 6500 family after all. I've 7 | made changes and additions where appropriate. 8 | 9 | In theory you should be able to use any code you can find for emulating 10 | the 6510 (the C64 processor). 11 | 12 | 13 | 14 | THE REGISTERS INSIDE THE 6502 MICROPROCESSOR 15 | 16 | Almost all calculations are done in the microprocessor. Registers are 17 | special pieces of memory in the processor which are used to carry out, and 18 | store information about calculations. The 6502 has the following registers: 19 | 20 | 21 | THE ACCUMULATOR 22 | 23 | This is THE most important register in the microprocessor. Various ma- 24 | chine language instructions allow you to copy the contents of a memory 25 | location into the accumulator, copy the contents of the accumulator into 26 | a memory location, modify the contents of the accumulator or some other 27 | register directly, without affecting any memory. And the accumulator is 28 | the only register that has instructions for performing math. 29 | 30 | 31 | THE X INDEX REGISTER 32 | 33 | This is a very important register. There are instructions for nearly 34 | all of the transformations you can make to the accumulator. But there are 35 | other instructions for things that only the X register can do. Various 36 | machine language instructions allow you to copy the contents of a memory 37 | location into the X register, copy the contents of the X register into a 38 | memory location, and modify the contents of the X, or some other register 39 | directly. 40 | 41 | 42 | THE Y INDEX REGISTER 43 | 44 | This is a very important register. There are instructions for nearly 45 | all of the transformations you can make to the accumulator, and the X 46 | register. But there are other instructions for things that only the Y 47 | register can do. Various machine language instructions allow you to copy 48 | the contents of a memory location into the Y register, copy the contents 49 | of the Y register into a memory location, and modify the contents of the 50 | Y, or some other register directly. 51 | 52 | 53 | THE STATUS REGISTER 54 | 55 | This register consists of eight "flags" (a flag = something that indi- 56 | cates whether something has, or has not occurred). Bits of this register 57 | are altered depending on the result of arithmetic and logical operations. 58 | These bits are described below: 59 | 60 | Bit No. 7 6 5 4 3 2 1 0 61 | S V B D I Z C 62 | 63 | Bit0 - C - Carry flag: this holds the carry out of the most significant 64 | bit in any arithmetic operation. In subtraction operations however, this 65 | flag is cleared - set to 0 - if a borrow is required, set to 1 - if no 66 | borrow is required. The carry flag is also used in shift and rotate 67 | logical operations. 68 | 69 | Bit1 - Z - Zero flag: this is set to 1 when any arithmetic or logical 70 | operation produces a zero result, and is set to 0 if the result is 71 | non-zero. 72 | 73 | Bit 2 - I: this is an interrupt enable/disable flag. If it is set, 74 | interrupts are disabled. If it is cleared, interrupts are enabled. 75 | 76 | Bit 3 - D: this is the decimal mode status flag. When set, and an Add with 77 | Carry or Subtract with Carry instruction is executed, the source values are 78 | treated as valid BCD (Binary Coded Decimal, eg. 0x00-0x99 = 0-99) numbers. 79 | The result generated is also a BCD number. 80 | 81 | Bit 4 - B: this is set when a software interrupt (BRK instruction) is 82 | executed. 83 | 84 | Bit 5: not used. Supposed to be logical 1 at all times. 85 | 86 | Bit 6 - V - Overflow flag: when an arithmetic operation produces a result 87 | too large to be represented in a byte, V is set. 88 | 89 | Bit 7 - S - Sign flag: this is set if the result of an operation is 90 | negative, cleared if positive. 91 | 92 | The most commonly used flags are C, Z, V, S. 93 | 94 | 95 | 96 | THE PROGRAM COUNTER 97 | 98 | This contains the address of the current machine language instruction 99 | being executed. Since the operating system is always "RUN"ning in the 100 | Commodore VIC-20 (or, for that matter, any computer), the program counter 101 | is always changing. It could only be stopped by halting the microprocessor 102 | in some way. 103 | 104 | 105 | THE STACK POINTER 106 | 107 | This register contains the location of the first empty place on the 108 | stack. The stack is used for temporary storage by machine language pro- 109 | grams, and by the computer. 110 | 111 | 112 | 113 | 114 | ADDRESSING MODES 115 | 116 | Instructions need operands to work on. There are various ways of 117 | indicating where the processor is to get these operands. The different 118 | methods used to do this are called addressing modes. The 6502 offers 11 119 | modes, as described below. 120 | 121 | 1) Immediate 122 | In this mode the operand's value is given in the instruction itself. In 123 | assembly language this is indicated by "#" before the operand. 124 | eg. LDA #$0A - means "load the accumulator with the hex value 0A" 125 | In machine code different modes are indicated by different codes. So LDA 126 | would be translated into different codes depending on the addressing mode. 127 | In this mode, it is: $A9 $0A 128 | 129 | 2 & 3) Absolute and Zero-page Absolute 130 | In these modes the operands address is given. 131 | eg. LDA $31F6 - (assembler) 132 | $AD $31F6 - (machine code) 133 | If the address is on zero page - i.e. any address where the high byte is 134 | 00 - only 1 byte is needed for the address. The processor automatically 135 | fills the 00 high byte. 136 | eg. LDA $F4 137 | $A5 $F4 138 | Note the different instruction codes for the different modes. 139 | Note also that for 2 byte addresses, the low byte is store first, eg. 140 | LDA $31F6 is stored as three bytes in memory, $AD $F6 $31. 141 | Zero-page absolute is usually just called zero-page. 142 | 143 | 4) Implied 144 | No operand addresses are required for this mode. They are implied by the 145 | instruction. 146 | eg. TAX - (transfer accumulator contents to X-register) 147 | $AA 148 | 149 | 5) Accumulator 150 | In this mode the instruction operates on data in the accumulator, so no 151 | operands are needed. 152 | eg. LSR - logical bit shift right 153 | $4A 154 | 155 | 6 & 7) Indexed and Zero-page Indexed 156 | In these modes the address given is added to the value in either the X or 157 | Y index register to give the actual address of the operand. 158 | eg. LDA $31F6, Y 159 | $D9 $31F6 160 | LDA $31F6, X 161 | $DD $31F6 162 | Note that the different operation codes determine the index register used. 163 | In the zero-page version, you should note that the X and Y registers are 164 | not interchangeable. Most instructions which can be used with zero-page 165 | indexing do so with X only. 166 | eg. LDA $20, X 167 | $B5 $20 168 | 169 | 8) Indirect 170 | This mode applies only to the JMP instruction - JuMP to new location. It is 171 | indicated by parenthesis around the operand. The operand is the address of 172 | the bytes whose value is the new location. 173 | eg. JMP ($215F) 174 | Assume the following - byte value 175 | $215F $76 176 | $2160 $30 177 | This instruction takes the value of bytes $215F, $2160 and uses that as the 178 | address to jump to - i.e. $3076 (remember that addresses are stored with 179 | low byte first). 180 | 181 | 9) Pre-indexed indirect 182 | In this mode a zer0-page address is added to the contents of the X-register 183 | to give the address of the bytes holding the address of the operand. The 184 | indirection is indicated by parenthesis in assembly language. 185 | eg. LDA ($3E, X) 186 | $A1 $3E 187 | Assume the following - byte value 188 | X-reg. $05 189 | $0043 $15 190 | $0044 $24 191 | $2415 $6E 192 | 193 | Then the instruction is executed by: 194 | (i) adding $3E and $05 = $0043 195 | (ii) getting address contained in bytes $0043, $0044 = $2415 196 | (iii) loading contents of $2415 - i.e. $6E - into accumulator 197 | 198 | Note a) When adding the 1-byte address and the X-register, wrap around 199 | addition is used - i.e. the sum is always a zero-page address. 200 | eg. FF + 2 = 0001 not 0101 as you might expect. 201 | DON'T FORGET THIS WHEN EMULATING THIS MODE. 202 | b) Only the X register is used in this mode. 203 | 204 | 10) Post-indexed indirect 205 | In this mode the contents of a zero-page address (and the following byte) 206 | give the indirect addressm which is added to the contents of the Y-register 207 | to yield the actual address of the operand. Again, inassembly language, 208 | the instruction is indicated by parenthesis. 209 | eg. LDA ($4C), Y 210 | Note that the parenthesis are only around the 2nd byte of the instruction 211 | since it is the part that does the indirection. 212 | Assume the following - byte value 213 | $004C $00 214 | $004D $21 215 | Y-reg. $05 216 | $2105 $6D 217 | Then the instruction above executes by: 218 | (i) getting the address in bytes $4C, $4D = $2100 219 | (ii) adding the contents of the Y-register = $2105 220 | (111) loading the contents of the byte $2105 - i.e. $6D into the 221 | accumulator. 222 | Note: only the Y-register is used in this mode. 223 | 224 | 11) Relative 225 | This mode is used with Branch-on-Condition instructions. It is probably 226 | the mode you will use most often. A 1 byte value is added to the program 227 | counter, and the program continues execution from that address. The 1 228 | byte number is treated as a signed number - i.e. if bit 7 is 1, the number 229 | given byt bits 0-6 is negative; if bit 7 is 0, the number is positive. This 230 | enables a branch displacement of up to 127 bytes in either direction. 231 | eg bit no. 7 6 5 4 3 2 1 0 signed value unsigned value 232 | value 1 0 1 0 0 1 1 1 -39 $A7 233 | value 0 0 1 0 0 1 1 1 +39 $27 234 | Instruction example: 235 | BEQ $A7 236 | $F0 $A7 237 | This instruction will check the zero status bit. If it is set, 39 decimal 238 | will be subtracted from the program counter and execution continues from 239 | that address. If the zero status bit is not set, execution continues from 240 | the following instruction. 241 | Notes: a) The program counter points to the start of the instruction 242 | after the branch instruction before the branch displacement is added. 243 | Remember to take this into account when calculating displacements. 244 | b) Branch-on-condition instructions work by checking the relevant 245 | status bits in the status register. Make sure that they have been set or 246 | unset as you want them. This is often done using a CMP instruction. 247 | c) If you find you need to branch further than 127 bytes, use the 248 | opposite branch-on-condition and a JMP. 249 | 250 | 251 | +------------------------------------------------------------------------ 252 | | 253 | | MCS6502 MICROPROCESSOR INSTRUCTION SET - ALPHABETIC SEQUENCE 254 | | 255 | +------------------------------------------------------------------------ 256 | | 257 | | ADC Add Memory to Accumulator with Carry 258 | | AND "AND" Memory with Accumulator 259 | | ASL Shift Left One Bit (Memory or Accumulator) 260 | | 261 | | BCC Branch on Carry Clear 262 | | BCS Branch on Carry Set 263 | | BEQ Branch on Result Zero 264 | | BIT Test Bits in Memory with Accumulator 265 | | BMI Branch on Result Minus 266 | | BNE Branch on Result not Zero 267 | | BPL Branch on Result Plus 268 | | BRK Force Break 269 | | BVC Branch on Overflow Clear 270 | | BVS Branch on Overflow Set 271 | | 272 | | CLC Clear Carry Flag 273 | | CLD Clear Decimal Mode 274 | | CLI Clear interrupt Disable Bit 275 | | CLV Clear Overflow Flag 276 | | CMP Compare Memory and Accumulator 277 | | CPX Compare Memory and Index X 278 | | CPY Compare Memory and Index Y 279 | | 280 | | DEC Decrement Memory by One 281 | | DEX Decrement Index X by One 282 | | DEY Decrement Index Y by One 283 | | 284 | | EOR "Exclusive-Or" Memory with Accumulator 285 | | 286 | | INC Increment Memory by One 287 | | INX Increment Index X by One 288 | | INY Increment Index Y by One 289 | | 290 | | JMP Jump to New Location 291 | | 292 | +------------------------------------------------------------------------ 293 | 294 | 295 | ------------------------------------------------------------------------+ 296 | | 297 | MCS6502 MICROPROCESSOR INSTRUCTION SET - ALPHABETIC SEQUENCE | 298 | | 299 | ------------------------------------------------------------------------+ 300 | | 301 | JSR Jump to New Location Saving Return Address | 302 | | 303 | LDA Load Accumulator with Memory | 304 | LDX Load Index X with Memory | 305 | LDY Load Index Y with Memory | 306 | LSR Shift Right One Bit (Memory or Accumulator) | 307 | | 308 | NOP No Operation | 309 | | 310 | ORA "OR" Memory with Accumulator | 311 | | 312 | PHA Push Accumulator on Stack | 313 | PHP Push Processor Status on Stack | 314 | PLA Pull Accumulator from Stack | 315 | PLP Pull Processor Status from Stack | 316 | | 317 | ROL Rotate One Bit Left (Memory or Accumulator) | 318 | ROR Rotate One Bit Right (Memory or Accumulator) | 319 | RTI Return from Interrupt | 320 | RTS Return from Subroutine | 321 | | 322 | SBC Subtract Memory from Accumulator with Borrow | 323 | SEC Set Carry Flag | 324 | SED Set Decimal Mode | 325 | SEI Set Interrupt Disable Status | 326 | STA Store Accumulator in Memory | 327 | STX Store Index X in Memory | 328 | STY Store Index Y in Memory | 329 | | 330 | TAX Transfer Accumulator to Index X | 331 | TAY Transfer Accumulator to Index Y | 332 | TSX Transfer Stack Pointer to Index X | 333 | TXA Transfer Index X to Accumulator | 334 | TXS Transfer Index X to Stack Pointer | 335 | TYA Transfer Index Y to Accumulator | 336 | ------------------------------------------------------------------------+ 337 | 338 | 339 | The following notation applies to this summary: 340 | 341 | 342 | A Accumulator EOR Logical Exclusive Or 343 | 344 | X, Y Index Registers fromS Transfer from Stack 345 | 346 | M Memory toS Transfer to Stack 347 | 348 | P Processor Status Register -> Transfer to 349 | 350 | S Stack Pointer <- Transfer from 351 | 352 | / Change V Logical OR 353 | 354 | _ No Change PC Program Counter 355 | 356 | + Add PCH Program Counter High 357 | 358 | /\ Logical AND PCL Program Counter Low 359 | 360 | - Subtract OPER OPERAND 361 | 362 | # IMMEDIATE ADDRESSING MODE 363 | 364 | 365 | 366 | Note: At the top of each table is located in parentheses a reference 367 | number (Ref: XX) which directs the user to that Section in the 368 | MCS6500 Microcomputer Family Programming Manual in which the 369 | instruction is defined and discussed. 370 | 371 | 372 | 373 | 374 | ADC Add memory to accumulator with carry ADC 375 | 376 | Operation: A + M + C -> A, C N Z C I D V 377 | / / / _ _ / 378 | (Ref: 2.2.1) 379 | +----------------+-----------------------+---------+---------+----------+ 380 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 381 | +----------------+-----------------------+---------+---------+----------+ 382 | | Immediate | ADC #Oper | 69 | 2 | 2 | 383 | | Zero Page | ADC Oper | 65 | 2 | 3 | 384 | | Zero Page,X | ADC Oper,X | 75 | 2 | 4 | 385 | | Absolute | ADC Oper | 60 | 3 | 4 | 386 | | Absolute,X | ADC Oper,X | 70 | 3 | 4* | 387 | | Absolute,Y | ADC Oper,Y | 79 | 3 | 4* | 388 | | (Indirect,X) | ADC (Oper,X) | 61 | 2 | 6 | 389 | | (Indirect),Y | ADC (Oper),Y | 71 | 2 | 5* | 390 | +----------------+-----------------------+---------+---------+----------+ 391 | * Add 1 if page boundary is crossed. 392 | 393 | 394 | AND "AND" memory with accumulator AND 395 | 396 | Operation: A /\ M -> A N Z C I D V 397 | / / _ _ _ _ 398 | (Ref: 2.2.3.0) 399 | +----------------+-----------------------+---------+---------+----------+ 400 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 401 | +----------------+-----------------------+---------+---------+----------+ 402 | | Immediate | AND #Oper | 29 | 2 | 2 | 403 | | Zero Page | AND Oper | 25 | 2 | 3 | 404 | | Zero Page,X | AND Oper,X | 35 | 2 | 4 | 405 | | Absolute | AND Oper | 2D | 3 | 4 | 406 | | Absolute,X | AND Oper,X | 3D | 3 | 4* | 407 | | Absolute,Y | AND Oper,Y | 39 | 3 | 4* | 408 | | (Indirect,X) | AND (Oper,X) | 21 | 2 | 6 | 409 | | (Indirect,Y) | AND (Oper),Y | 31 | 2 | 5 | 410 | +----------------+-----------------------+---------+---------+----------+ 411 | * Add 1 if page boundary is crossed. 412 | 413 | 414 | ASL ASL Shift Left One Bit (Memory or Accumulator) ASL 415 | +-+-+-+-+-+-+-+-+ 416 | Operation: C <- |7|6|5|4|3|2|1|0| <- 0 417 | +-+-+-+-+-+-+-+-+ N Z C I D V 418 | / / / _ _ _ 419 | (Ref: 10.2) 420 | +----------------+-----------------------+---------+---------+----------+ 421 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 422 | +----------------+-----------------------+---------+---------+----------+ 423 | | Accumulator | ASL A | 0A | 1 | 2 | 424 | | Zero Page | ASL Oper | 06 | 2 | 5 | 425 | | Zero Page,X | ASL Oper,X | 16 | 2 | 6 | 426 | | Absolute | ASL Oper | 0E | 3 | 6 | 427 | | Absolute, X | ASL Oper,X | 1E | 3 | 7 | 428 | +----------------+-----------------------+---------+---------+----------+ 429 | 430 | 431 | BCC BCC Branch on Carry Clear BCC 432 | N Z C I D V 433 | Operation: Branch on C = 0 _ _ _ _ _ _ 434 | (Ref: 4.1.1.3) 435 | +----------------+-----------------------+---------+---------+----------+ 436 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 437 | +----------------+-----------------------+---------+---------+----------+ 438 | | Relative | BCC Oper | 90 | 2 | 2* | 439 | +----------------+-----------------------+---------+---------+----------+ 440 | * Add 1 if branch occurs to same page. 441 | * Add 2 if branch occurs to different page. 442 | 443 | 444 | BCS BCS Branch on carry set BCS 445 | 446 | Operation: Branch on C = 1 N Z C I D V 447 | _ _ _ _ _ _ 448 | (Ref: 4.1.1.4) 449 | +----------------+-----------------------+---------+---------+----------+ 450 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 451 | +----------------+-----------------------+---------+---------+----------+ 452 | | Relative | BCS Oper | B0 | 2 | 2* | 453 | +----------------+-----------------------+---------+---------+----------+ 454 | * Add 1 if branch occurs to same page. 455 | * Add 2 if branch occurs to next page. 456 | 457 | 458 | BEQ BEQ Branch on result zero BEQ 459 | N Z C I D V 460 | Operation: Branch on Z = 1 _ _ _ _ _ _ 461 | (Ref: 4.1.1.5) 462 | +----------------+-----------------------+---------+---------+----------+ 463 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 464 | +----------------+-----------------------+---------+---------+----------+ 465 | | Relative | BEQ Oper | F0 | 2 | 2* | 466 | +----------------+-----------------------+---------+---------+----------+ 467 | * Add 1 if branch occurs to same page. 468 | * Add 2 if branch occurs to next page. 469 | 470 | 471 | BIT BIT Test bits in memory with accumulator BIT 472 | 473 | Operation: A /\ M, M7 -> N, M6 -> V 474 | 475 | Bit 6 and 7 are transferred to the status register. N Z C I D V 476 | If the result of A /\ M is zero then Z = 1, otherwise M7/ _ _ _ M6 477 | Z = 0 478 | (Ref: 4.2.1.1) 479 | +----------------+-----------------------+---------+---------+----------+ 480 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 481 | +----------------+-----------------------+---------+---------+----------+ 482 | | Zero Page | BIT Oper | 24 | 2 | 3 | 483 | | Absolute | BIT Oper | 2C | 3 | 4 | 484 | +----------------+-----------------------+---------+---------+----------+ 485 | 486 | 487 | BMI BMI Branch on result minus BMI 488 | 489 | Operation: Branch on N = 1 N Z C I D V 490 | _ _ _ _ _ _ 491 | (Ref: 4.1.1.1) 492 | +----------------+-----------------------+---------+---------+----------+ 493 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 494 | +----------------+-----------------------+---------+---------+----------+ 495 | | Relative | BMI Oper | 30 | 2 | 2* | 496 | +----------------+-----------------------+---------+---------+----------+ 497 | * Add 1 if branch occurs to same page. 498 | * Add 1 if branch occurs to different page. 499 | 500 | 501 | BNE BNE Branch on result not zero BNE 502 | 503 | Operation: Branch on Z = 0 N Z C I D V 504 | _ _ _ _ _ _ 505 | (Ref: 4.1.1.6) 506 | +----------------+-----------------------+---------+---------+----------+ 507 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 508 | +----------------+-----------------------+---------+---------+----------+ 509 | | Relative | BMI Oper | D0 | 2 | 2* | 510 | +----------------+-----------------------+---------+---------+----------+ 511 | * Add 1 if branch occurs to same page. 512 | * Add 2 if branch occurs to different page. 513 | 514 | 515 | BPL BPL Branch on result plus BPL 516 | 517 | Operation: Branch on N = 0 N Z C I D V 518 | _ _ _ _ _ _ 519 | (Ref: 4.1.1.2) 520 | +----------------+-----------------------+---------+---------+----------+ 521 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 522 | +----------------+-----------------------+---------+---------+----------+ 523 | | Relative | BPL Oper | 10 | 2 | 2* | 524 | +----------------+-----------------------+---------+---------+----------+ 525 | * Add 1 if branch occurs to same page. 526 | * Add 2 if branch occurs to different page. 527 | 528 | 529 | BRK BRK Force Break BRK 530 | 531 | Operation: Forced Interrupt PC + 2 toS P toS N Z C I D V 532 | _ _ _ 1 _ _ 533 | (Ref: 9.11) 534 | +----------------+-----------------------+---------+---------+----------+ 535 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 536 | +----------------+-----------------------+---------+---------+----------+ 537 | | Implied | BRK | 00 | 1 | 7 | 538 | +----------------+-----------------------+---------+---------+----------+ 539 | 1. A BRK command cannot be masked by setting I. 540 | 541 | 542 | BVC BVC Branch on overflow clear BVC 543 | 544 | Operation: Branch on V = 0 N Z C I D V 545 | _ _ _ _ _ _ 546 | (Ref: 4.1.1.8) 547 | +----------------+-----------------------+---------+---------+----------+ 548 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 549 | +----------------+-----------------------+---------+---------+----------+ 550 | | Relative | BVC Oper | 50 | 2 | 2* | 551 | +----------------+-----------------------+---------+---------+----------+ 552 | * Add 1 if branch occurs to same page. 553 | * Add 2 if branch occurs to different page. 554 | 555 | 556 | BVS BVS Branch on overflow set BVS 557 | 558 | Operation: Branch on V = 1 N Z C I D V 559 | _ _ _ _ _ _ 560 | (Ref: 4.1.1.7) 561 | +----------------+-----------------------+---------+---------+----------+ 562 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 563 | +----------------+-----------------------+---------+---------+----------+ 564 | | Relative | BVS Oper | 70 | 2 | 2* | 565 | +----------------+-----------------------+---------+---------+----------+ 566 | * Add 1 if branch occurs to same page. 567 | * Add 2 if branch occurs to different page. 568 | 569 | 570 | CLC CLC Clear carry flag CLC 571 | 572 | Operation: 0 -> C N Z C I D V 573 | _ _ 0 _ _ _ 574 | (Ref: 3.0.2) 575 | +----------------+-----------------------+---------+---------+----------+ 576 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 577 | +----------------+-----------------------+---------+---------+----------+ 578 | | Implied | CLC | 18 | 1 | 2 | 579 | +----------------+-----------------------+---------+---------+----------+ 580 | 581 | 582 | CLD CLD Clear decimal mode CLD 583 | 584 | Operation: 0 -> D N A C I D V 585 | _ _ _ _ 0 _ 586 | (Ref: 3.3.2) 587 | +----------------+-----------------------+---------+---------+----------+ 588 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 589 | +----------------+-----------------------+---------+---------+----------+ 590 | | Implied | CLD | D8 | 1 | 2 | 591 | +----------------+-----------------------+---------+---------+----------+ 592 | 593 | 594 | CLI CLI Clear interrupt disable bit CLI 595 | 596 | Operation: 0 -> I N Z C I D V 597 | _ _ _ 0 _ _ 598 | (Ref: 3.2.2) 599 | +----------------+-----------------------+---------+---------+----------+ 600 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 601 | +----------------+-----------------------+---------+---------+----------+ 602 | | Implied | CLI | 58 | 1 | 2 | 603 | +----------------+-----------------------+---------+---------+----------+ 604 | 605 | 606 | CLV CLV Clear overflow flag CLV 607 | 608 | Operation: 0 -> V N Z C I D V 609 | _ _ _ _ _ 0 610 | (Ref: 3.6.1) 611 | +----------------+-----------------------+---------+---------+----------+ 612 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 613 | +----------------+-----------------------+---------+---------+----------+ 614 | | Implied | CLV | B8 | 1 | 2 | 615 | +----------------+-----------------------+---------+---------+----------+ 616 | 617 | 618 | CMP CMP Compare memory and accumulator CMP 619 | 620 | Operation: A - M N Z C I D V 621 | / / / _ _ _ 622 | (Ref: 4.2.1) 623 | +----------------+-----------------------+---------+---------+----------+ 624 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 625 | +----------------+-----------------------+---------+---------+----------+ 626 | | Immediate | CMP #Oper | C9 | 2 | 2 | 627 | | Zero Page | CMP Oper | C5 | 2 | 3 | 628 | | Zero Page,X | CMP Oper,X | D5 | 2 | 4 | 629 | | Absolute | CMP Oper | CD | 3 | 4 | 630 | | Absolute,X | CMP Oper,X | DD | 3 | 4* | 631 | | Absolute,Y | CMP Oper,Y | D9 | 3 | 4* | 632 | | (Indirect,X) | CMP (Oper,X) | C1 | 2 | 6 | 633 | | (Indirect),Y | CMP (Oper),Y | D1 | 2 | 5* | 634 | +----------------+-----------------------+---------+---------+----------+ 635 | * Add 1 if page boundary is crossed. 636 | 637 | CPX CPX Compare Memory and Index X CPX 638 | N Z C I D V 639 | Operation: X - M / / / _ _ _ 640 | (Ref: 7.8) 641 | +----------------+-----------------------+---------+---------+----------+ 642 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 643 | +----------------+-----------------------+---------+---------+----------+ 644 | | Immediate | CPX *Oper | E0 | 2 | 2 | 645 | | Zero Page | CPX Oper | E4 | 2 | 3 | 646 | | Absolute | CPX Oper | EC | 3 | 4 | 647 | +----------------+-----------------------+---------+---------+----------+ 648 | 649 | CPY CPY Compare memory and index Y CPY 650 | N Z C I D V 651 | Operation: Y - M / / / _ _ _ 652 | (Ref: 7.9) 653 | +----------------+-----------------------+---------+---------+----------+ 654 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 655 | +----------------+-----------------------+---------+---------+----------+ 656 | | Immediate | CPY *Oper | C0 | 2 | 2 | 657 | | Zero Page | CPY Oper | C4 | 2 | 3 | 658 | | Absolute | CPY Oper | CC | 3 | 4 | 659 | +----------------+-----------------------+---------+---------+----------+ 660 | 661 | 662 | DEC DEC Decrement memory by one DEC 663 | 664 | Operation: M - 1 -> M N Z C I D V 665 | / / _ _ _ _ 666 | (Ref: 10.7) 667 | +----------------+-----------------------+---------+---------+----------+ 668 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 669 | +----------------+-----------------------+---------+---------+----------+ 670 | | Zero Page | DEC Oper | C6 | 2 | 5 | 671 | | Zero Page,X | DEC Oper,X | D6 | 2 | 6 | 672 | | Absolute | DEC Oper | CE | 3 | 6 | 673 | | Absolute,X | DEC Oper,X | DE | 3 | 7 | 674 | +----------------+-----------------------+---------+---------+----------+ 675 | 676 | 677 | DEX DEX Decrement index X by one DEX 678 | 679 | Operation: X - 1 -> X N Z C I D V 680 | / / _ _ _ _ 681 | (Ref: 7.6) 682 | +----------------+-----------------------+---------+---------+----------+ 683 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 684 | +----------------+-----------------------+---------+---------+----------+ 685 | | Implied | DEX | CA | 1 | 2 | 686 | +----------------+-----------------------+---------+---------+----------+ 687 | 688 | 689 | DEY DEY Decrement index Y by one DEY 690 | 691 | Operation: X - 1 -> Y N Z C I D V 692 | / / _ _ _ _ 693 | (Ref: 7.7) 694 | +----------------+-----------------------+---------+---------+----------+ 695 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 696 | +----------------+-----------------------+---------+---------+----------+ 697 | | Implied | DEY | 88 | 1 | 2 | 698 | +----------------+-----------------------+---------+---------+----------+ 699 | 700 | 701 | EOR EOR "Exclusive-Or" memory with accumulator EOR 702 | 703 | Operation: A EOR M -> A N Z C I D V 704 | / / _ _ _ _ 705 | (Ref: 2.2.3.2) 706 | +----------------+-----------------------+---------+---------+----------+ 707 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 708 | +----------------+-----------------------+---------+---------+----------+ 709 | | Immediate | EOR #Oper | 49 | 2 | 2 | 710 | | Zero Page | EOR Oper | 45 | 2 | 3 | 711 | | Zero Page,X | EOR Oper,X | 55 | 2 | 4 | 712 | | Absolute | EOR Oper | 40 | 3 | 4 | 713 | | Absolute,X | EOR Oper,X | 50 | 3 | 4* | 714 | | Absolute,Y | EOR Oper,Y | 59 | 3 | 4* | 715 | | (Indirect,X) | EOR (Oper,X) | 41 | 2 | 6 | 716 | | (Indirect),Y | EOR (Oper),Y | 51 | 2 | 5* | 717 | +----------------+-----------------------+---------+---------+----------+ 718 | * Add 1 if page boundary is crossed. 719 | 720 | INC INC Increment memory by one INC 721 | N Z C I D V 722 | Operation: M + 1 -> M / / _ _ _ _ 723 | (Ref: 10.6) 724 | +----------------+-----------------------+---------+---------+----------+ 725 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 726 | +----------------+-----------------------+---------+---------+----------+ 727 | | Zero Page | INC Oper | E6 | 2 | 5 | 728 | | Zero Page,X | INC Oper,X | F6 | 2 | 6 | 729 | | Absolute | INC Oper | EE | 3 | 6 | 730 | | Absolute,X | INC Oper,X | FE | 3 | 7 | 731 | +----------------+-----------------------+---------+---------+----------+ 732 | 733 | INX INX Increment Index X by one INX 734 | N Z C I D V 735 | Operation: X + 1 -> X / / _ _ _ _ 736 | (Ref: 7.4) 737 | +----------------+-----------------------+---------+---------+----------+ 738 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 739 | +----------------+-----------------------+---------+---------+----------+ 740 | | Implied | INX | E8 | 1 | 2 | 741 | +----------------+-----------------------+---------+---------+----------+ 742 | 743 | 744 | INY INY Increment Index Y by one INY 745 | 746 | Operation: X + 1 -> X N Z C I D V 747 | / / _ _ _ _ 748 | (Ref: 7.5) 749 | +----------------+-----------------------+---------+---------+----------+ 750 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 751 | +----------------+-----------------------+---------+---------+----------+ 752 | | Implied | INY | C8 | 1 | 2 | 753 | +----------------+-----------------------+---------+---------+----------+ 754 | 755 | 756 | JMP JMP Jump to new location JMP 757 | 758 | Operation: (PC + 1) -> PCL N Z C I D V 759 | (PC + 2) -> PCH (Ref: 4.0.2) _ _ _ _ _ _ 760 | (Ref: 9.8.1) 761 | +----------------+-----------------------+---------+---------+----------+ 762 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 763 | +----------------+-----------------------+---------+---------+----------+ 764 | | Absolute | JMP Oper | 4C | 3 | 3 | 765 | | Indirect | JMP (Oper) | 6C | 3 | 5 | 766 | +----------------+-----------------------+---------+---------+----------+ 767 | 768 | 769 | JSR JSR Jump to new location saving return address JSR 770 | 771 | Operation: PC + 2 toS, (PC + 1) -> PCL N Z C I D V 772 | (PC + 2) -> PCH _ _ _ _ _ _ 773 | (Ref: 8.1) 774 | +----------------+-----------------------+---------+---------+----------+ 775 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 776 | +----------------+-----------------------+---------+---------+----------+ 777 | | Absolute | JSR Oper | 20 | 3 | 6 | 778 | +----------------+-----------------------+---------+---------+----------+ 779 | 780 | 781 | LDA LDA Load accumulator with memory LDA 782 | 783 | Operation: M -> A N Z C I D V 784 | / / _ _ _ _ 785 | (Ref: 2.1.1) 786 | +----------------+-----------------------+---------+---------+----------+ 787 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 788 | +----------------+-----------------------+---------+---------+----------+ 789 | | Immediate | LDA #Oper | A9 | 2 | 2 | 790 | | Zero Page | LDA Oper | A5 | 2 | 3 | 791 | | Zero Page,X | LDA Oper,X | B5 | 2 | 4 | 792 | | Absolute | LDA Oper | AD | 3 | 4 | 793 | | Absolute,X | LDA Oper,X | BD | 3 | 4* | 794 | | Absolute,Y | LDA Oper,Y | B9 | 3 | 4* | 795 | | (Indirect,X) | LDA (Oper,X) | A1 | 2 | 6 | 796 | | (Indirect),Y | LDA (Oper),Y | B1 | 2 | 5* | 797 | +----------------+-----------------------+---------+---------+----------+ 798 | * Add 1 if page boundary is crossed. 799 | 800 | 801 | LDX LDX Load index X with memory LDX 802 | 803 | Operation: M -> X N Z C I D V 804 | / / _ _ _ _ 805 | (Ref: 7.0) 806 | +----------------+-----------------------+---------+---------+----------+ 807 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 808 | +----------------+-----------------------+---------+---------+----------+ 809 | | Immediate | LDX #Oper | A2 | 2 | 2 | 810 | | Zero Page | LDX Oper | A6 | 2 | 3 | 811 | | Zero Page,Y | LDX Oper,Y | B6 | 2 | 4 | 812 | | Absolute | LDX Oper | AE | 3 | 4 | 813 | | Absolute,Y | LDX Oper,Y | BE | 3 | 4* | 814 | +----------------+-----------------------+---------+---------+----------+ 815 | * Add 1 when page boundary is crossed. 816 | 817 | 818 | LDY LDY Load index Y with memory LDY 819 | N Z C I D V 820 | Operation: M -> Y / / _ _ _ _ 821 | (Ref: 7.1) 822 | +----------------+-----------------------+---------+---------+----------+ 823 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 824 | +----------------+-----------------------+---------+---------+----------+ 825 | | Immediate | LDY #Oper | A0 | 2 | 2 | 826 | | Zero Page | LDY Oper | A4 | 2 | 3 | 827 | | Zero Page,X | LDY Oper,X | B4 | 2 | 4 | 828 | | Absolute | LDY Oper | AC | 3 | 4 | 829 | | Absolute,X | LDY Oper,X | BC | 3 | 4* | 830 | +----------------+-----------------------+---------+---------+----------+ 831 | * Add 1 when page boundary is crossed. 832 | 833 | 834 | LSR LSR Shift right one bit (memory or accumulator) LSR 835 | 836 | +-+-+-+-+-+-+-+-+ 837 | Operation: 0 -> |7|6|5|4|3|2|1|0| -> C N Z C I D V 838 | +-+-+-+-+-+-+-+-+ 0 / / _ _ _ 839 | (Ref: 10.1) 840 | +----------------+-----------------------+---------+---------+----------+ 841 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 842 | +----------------+-----------------------+---------+---------+----------+ 843 | | Accumulator | LSR A | 4A | 1 | 2 | 844 | | Zero Page | LSR Oper | 46 | 2 | 5 | 845 | | Zero Page,X | LSR Oper,X | 56 | 2 | 6 | 846 | | Absolute | LSR Oper | 4E | 3 | 6 | 847 | | Absolute,X | LSR Oper,X | 5E | 3 | 7 | 848 | +----------------+-----------------------+---------+---------+----------+ 849 | 850 | 851 | NOP NOP No operation NOP 852 | N Z C I D V 853 | Operation: No Operation (2 cycles) _ _ _ _ _ _ 854 | 855 | +----------------+-----------------------+---------+---------+----------+ 856 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 857 | +----------------+-----------------------+---------+---------+----------+ 858 | | Implied | NOP | EA | 1 | 2 | 859 | +----------------+-----------------------+---------+---------+----------+ 860 | 861 | 862 | ORA ORA "OR" memory with accumulator ORA 863 | 864 | Operation: A V M -> A N Z C I D V 865 | / / _ _ _ _ 866 | (Ref: 2.2.3.1) 867 | +----------------+-----------------------+---------+---------+----------+ 868 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 869 | +----------------+-----------------------+---------+---------+----------+ 870 | | Immediate | ORA #Oper | 09 | 2 | 2 | 871 | | Zero Page | ORA Oper | 05 | 2 | 3 | 872 | | Zero Page,X | ORA Oper,X | 15 | 2 | 4 | 873 | | Absolute | ORA Oper | 0D | 3 | 4 | 874 | | Absolute,X | ORA Oper,X | 1D | 3 | 4* | 875 | | Absolute,Y | ORA Oper,Y | 19 | 3 | 4* | 876 | | (Indirect,X) | ORA (Oper,X) | 01 | 2 | 6 | 877 | | (Indirect),Y | ORA (Oper),Y | 11 | 2 | 5 | 878 | +----------------+-----------------------+---------+---------+----------+ 879 | * Add 1 on page crossing 880 | 881 | 882 | PHA PHA Push accumulator on stack PHA 883 | 884 | Operation: A toS N Z C I D V 885 | _ _ _ _ _ _ 886 | (Ref: 8.5) 887 | +----------------+-----------------------+---------+---------+----------+ 888 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 889 | +----------------+-----------------------+---------+---------+----------+ 890 | | Implied | PHA | 48 | 1 | 3 | 891 | +----------------+-----------------------+---------+---------+----------+ 892 | 893 | 894 | PHP PHP Push processor status on stack PHP 895 | 896 | Operation: P toS N Z C I D V 897 | _ _ _ _ _ _ 898 | (Ref: 8.11) 899 | +----------------+-----------------------+---------+---------+----------+ 900 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 901 | +----------------+-----------------------+---------+---------+----------+ 902 | | Implied | PHP | 08 | 1 | 3 | 903 | +----------------+-----------------------+---------+---------+----------+ 904 | 905 | 906 | PLA PLA Pull accumulator from stack PLA 907 | 908 | Operation: A fromS N Z C I D V 909 | _ _ _ _ _ _ 910 | (Ref: 8.6) 911 | +----------------+-----------------------+---------+---------+----------+ 912 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 913 | +----------------+-----------------------+---------+---------+----------+ 914 | | Implied | PLA | 68 | 1 | 4 | 915 | +----------------+-----------------------+---------+---------+----------+ 916 | 917 | 918 | PLP PLP Pull processor status from stack PLA 919 | 920 | Operation: P fromS N Z C I D V 921 | From Stack 922 | (Ref: 8.12) 923 | +----------------+-----------------------+---------+---------+----------+ 924 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 925 | +----------------+-----------------------+---------+---------+----------+ 926 | | Implied | PLP | 28 | 1 | 4 | 927 | +----------------+-----------------------+---------+---------+----------+ 928 | 929 | 930 | ROL ROL Rotate one bit left (memory or accumulator) ROL 931 | 932 | +------------------------------+ 933 | | M or A | 934 | | +-+-+-+-+-+-+-+-+ +-+ | 935 | Operation: +-< |7|6|5|4|3|2|1|0| <- |C| <-+ N Z C I D V 936 | +-+-+-+-+-+-+-+-+ +-+ / / / _ _ _ 937 | (Ref: 10.3) 938 | +----------------+-----------------------+---------+---------+----------+ 939 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 940 | +----------------+-----------------------+---------+---------+----------+ 941 | | Accumulator | ROL A | 2A | 1 | 2 | 942 | | Zero Page | ROL Oper | 26 | 2 | 5 | 943 | | Zero Page,X | ROL Oper,X | 36 | 2 | 6 | 944 | | Absolute | ROL Oper | 2E | 3 | 6 | 945 | | Absolute,X | ROL Oper,X | 3E | 3 | 7 | 946 | +----------------+-----------------------+---------+---------+----------+ 947 | 948 | 949 | ROR ROR Rotate one bit right (memory or accumulator) ROR 950 | 951 | +------------------------------+ 952 | | | 953 | | +-+ +-+-+-+-+-+-+-+-+ | 954 | Operation: +-> |C| -> |7|6|5|4|3|2|1|0| >-+ N Z C I D V 955 | +-+ +-+-+-+-+-+-+-+-+ / / / _ _ _ 956 | (Ref: 10.4) 957 | +----------------+-----------------------+---------+---------+----------+ 958 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 959 | +----------------+-----------------------+---------+---------+----------+ 960 | | Accumulator | ROR A | 6A | 1 | 2 | 961 | | Zero Page | ROR Oper | 66 | 2 | 5 | 962 | | Zero Page,X | ROR Oper,X | 76 | 2 | 6 | 963 | | Absolute | ROR Oper | 6E | 3 | 6 | 964 | | Absolute,X | ROR Oper,X | 7E | 3 | 7 | 965 | +----------------+-----------------------+---------+---------+----------+ 966 | 967 | Note: ROR instruction is available on MCS650X microprocessors after 968 | June, 1976. 969 | 970 | 971 | RTI RTI Return from interrupt RTI 972 | N Z C I D V 973 | Operation: P fromS PC fromS From Stack 974 | (Ref: 9.6) 975 | +----------------+-----------------------+---------+---------+----------+ 976 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 977 | +----------------+-----------------------+---------+---------+----------+ 978 | | Implied | RTI | 4D | 1 | 6 | 979 | +----------------+-----------------------+---------+---------+----------+ 980 | 981 | 982 | RTS RTS Return from subroutine RTS 983 | N Z C I D V 984 | Operation: PC fromS, PC + 1 -> PC _ _ _ _ _ _ 985 | (Ref: 8.2) 986 | +----------------+-----------------------+---------+---------+----------+ 987 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 988 | +----------------+-----------------------+---------+---------+----------+ 989 | | Implied | RTS | 60 | 1 | 6 | 990 | +----------------+-----------------------+---------+---------+----------+ 991 | 992 | 993 | SBC SBC Subtract memory from accumulator with borrow SBC 994 | - 995 | Operation: A - M - C -> A N Z C I D V 996 | - / / / _ _ / 997 | Note:C = Borrow (Ref: 2.2.2) 998 | +----------------+-----------------------+---------+---------+----------+ 999 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1000 | +----------------+-----------------------+---------+---------+----------+ 1001 | | Immediate | SBC #Oper | E9 | 2 | 2 | 1002 | | Zero Page | SBC Oper | E5 | 2 | 3 | 1003 | | Zero Page,X | SBC Oper,X | F5 | 2 | 4 | 1004 | | Absolute | SBC Oper | ED | 3 | 4 | 1005 | | Absolute,X | SBC Oper,X | FD | 3 | 4* | 1006 | | Absolute,Y | SBC Oper,Y | F9 | 3 | 4* | 1007 | | (Indirect,X) | SBC (Oper,X) | E1 | 2 | 6 | 1008 | | (Indirect),Y | SBC (Oper),Y | F1 | 2 | 5 | 1009 | +----------------+-----------------------+---------+---------+----------+ 1010 | * Add 1 when page boundary is crossed. 1011 | 1012 | 1013 | SEC SEC Set carry flag SEC 1014 | 1015 | Operation: 1 -> C N Z C I D V 1016 | _ _ 1 _ _ _ 1017 | (Ref: 3.0.1) 1018 | +----------------+-----------------------+---------+---------+----------+ 1019 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1020 | +----------------+-----------------------+---------+---------+----------+ 1021 | | Implied | SEC | 38 | 1 | 2 | 1022 | +----------------+-----------------------+---------+---------+----------+ 1023 | 1024 | 1025 | SED SED Set decimal mode SED 1026 | N Z C I D V 1027 | Operation: 1 -> D _ _ _ _ 1 _ 1028 | (Ref: 3.3.1) 1029 | +----------------+-----------------------+---------+---------+----------+ 1030 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1031 | +----------------+-----------------------+---------+---------+----------+ 1032 | | Implied | SED | F8 | 1 | 2 | 1033 | +----------------+-----------------------+---------+---------+----------+ 1034 | 1035 | 1036 | SEI SEI Set interrupt disable status SED 1037 | N Z C I D V 1038 | Operation: 1 -> I _ _ _ 1 _ _ 1039 | (Ref: 3.2.1) 1040 | +----------------+-----------------------+---------+---------+----------+ 1041 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1042 | +----------------+-----------------------+---------+---------+----------+ 1043 | | Implied | SEI | 78 | 1 | 2 | 1044 | +----------------+-----------------------+---------+---------+----------+ 1045 | 1046 | 1047 | STA STA Store accumulator in memory STA 1048 | 1049 | Operation: A -> M N Z C I D V 1050 | _ _ _ _ _ _ 1051 | (Ref: 2.1.2) 1052 | +----------------+-----------------------+---------+---------+----------+ 1053 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1054 | +----------------+-----------------------+---------+---------+----------+ 1055 | | Zero Page | STA Oper | 85 | 2 | 3 | 1056 | | Zero Page,X | STA Oper,X | 95 | 2 | 4 | 1057 | | Absolute | STA Oper | 80 | 3 | 4 | 1058 | | Absolute,X | STA Oper,X | 90 | 3 | 5 | 1059 | | Absolute,Y | STA Oper, Y | 99 | 3 | 5 | 1060 | | (Indirect,X) | STA (Oper,X) | 81 | 2 | 6 | 1061 | | (Indirect),Y | STA (Oper),Y | 91 | 2 | 6 | 1062 | +----------------+-----------------------+---------+---------+----------+ 1063 | 1064 | 1065 | STX STX Store index X in memory STX 1066 | 1067 | Operation: X -> M N Z C I D V 1068 | _ _ _ _ _ _ 1069 | (Ref: 7.2) 1070 | +----------------+-----------------------+---------+---------+----------+ 1071 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1072 | +----------------+-----------------------+---------+---------+----------+ 1073 | | Zero Page | STX Oper | 86 | 2 | 3 | 1074 | | Zero Page,Y | STX Oper,Y | 96 | 2 | 4 | 1075 | | Absolute | STX Oper | 8E | 3 | 4 | 1076 | +----------------+-----------------------+---------+---------+----------+ 1077 | 1078 | 1079 | STY STY Store index Y in memory STY 1080 | 1081 | Operation: Y -> M N Z C I D V 1082 | _ _ _ _ _ _ 1083 | (Ref: 7.3) 1084 | +----------------+-----------------------+---------+---------+----------+ 1085 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1086 | +----------------+-----------------------+---------+---------+----------+ 1087 | | Zero Page | STY Oper | 84 | 2 | 3 | 1088 | | Zero Page,X | STY Oper,X | 94 | 2 | 4 | 1089 | | Absolute | STY Oper | 8C | 3 | 4 | 1090 | +----------------+-----------------------+---------+---------+----------+ 1091 | 1092 | 1093 | TAX TAX Transfer accumulator to index X TAX 1094 | 1095 | Operation: A -> X N Z C I D V 1096 | / / _ _ _ _ 1097 | (Ref: 7.11) 1098 | +----------------+-----------------------+---------+---------+----------+ 1099 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1100 | +----------------+-----------------------+---------+---------+----------+ 1101 | | Implied | TAX | AA | 1 | 2 | 1102 | +----------------+-----------------------+---------+---------+----------+ 1103 | 1104 | 1105 | TAY TAY Transfer accumulator to index Y TAY 1106 | 1107 | Operation: A -> Y N Z C I D V 1108 | / / _ _ _ _ 1109 | (Ref: 7.13) 1110 | +----------------+-----------------------+---------+---------+----------+ 1111 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1112 | +----------------+-----------------------+---------+---------+----------+ 1113 | | Implied | TAY | A8 | 1 | 2 | 1114 | +----------------+-----------------------+---------+---------+----------+ 1115 | 1116 | 1117 | TSX TSX Transfer stack pointer to index X TSX 1118 | 1119 | Operation: S -> X N Z C I D V 1120 | / / _ _ _ _ 1121 | (Ref: 8.9) 1122 | +----------------+-----------------------+---------+---------+----------+ 1123 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1124 | +----------------+-----------------------+---------+---------+----------+ 1125 | | Implied | TSX | BA | 1 | 2 | 1126 | +----------------+-----------------------+---------+---------+----------+ 1127 | 1128 | TXA TXA Transfer index X to accumulator TXA 1129 | N Z C I D V 1130 | Operation: X -> A / / _ _ _ _ 1131 | (Ref: 7.12) 1132 | +----------------+-----------------------+---------+---------+----------+ 1133 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1134 | +----------------+-----------------------+---------+---------+----------+ 1135 | | Implied | TXA | 8A | 1 | 2 | 1136 | +----------------+-----------------------+---------+---------+----------+ 1137 | 1138 | TXS TXS Transfer index X to stack pointer TXS 1139 | N Z C I D V 1140 | Operation: X -> S _ _ _ _ _ _ 1141 | (Ref: 8.8) 1142 | +----------------+-----------------------+---------+---------+----------+ 1143 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1144 | +----------------+-----------------------+---------+---------+----------+ 1145 | | Implied | TXS | 9A | 1 | 2 | 1146 | +----------------+-----------------------+---------+---------+----------+ 1147 | 1148 | TYA TYA Transfer index Y to accumulator TYA 1149 | 1150 | Operation: Y -> A N Z C I D V 1151 | / / _ _ _ _ 1152 | (Ref: 7.14) 1153 | +----------------+-----------------------+---------+---------+----------+ 1154 | | Addressing Mode| Assembly Language Form| OP CODE |No. Bytes|No. Cycles| 1155 | +----------------+-----------------------+---------+---------+----------+ 1156 | | Implied | TYA | 98 | 1 | 2 | 1157 | +----------------+-----------------------+---------+---------+----------+ 1158 | 1159 | 1160 | 1161 | +------------------------------------------------------------------------ 1162 | | INSTRUCTION ADDRESSING MODES AND RELATED EXECUTION TIMES 1163 | | (in clock cycles) 1164 | +------------------------------------------------------------------------ 1165 | 1166 | A A A B B B B B B B B B B C 1167 | D N S C C E I M N P R V V L 1168 | C D L C S Q T I E L K C S C 1169 | Accumulator | . . 2 . . . . . . . . . . . 1170 | Immediate | 2 2 . . . . . . . . . . . 1171 | Zero Page | 3 3 5 . . . 3 . . . . . . . 1172 | Zero Page,X | 4 4 6 . . . . . . . . . . . 1173 | Zero Page,Y | . . . . . . . . . . . . . . 1174 | Absolute | 4 4 6 . . . 4 . . . . . . . 1175 | Absolute,X | 4* 4* 7 . . . . . . . . . . . 1176 | Absolute,Y | 4* 4* . . . . . . . . . . . . 1177 | Implied | . . . . . . . . . . . . . 2 1178 | Relative | . . . 2** 2** 2** . 2** 2** 2** 7 2** 2** . 1179 | (Indirect,X) | 6 6 . . . . . . . . . . . . 1180 | (Indirect),Y | 5* 5* . . . . . . . . . . . . 1181 | Abs. Indirect| . . . . . . . . . . . . . . 1182 | +----------------------------------------------------------- 1183 | C C C C C C D D D E I I I J 1184 | L L L M P P E E E O N N N M 1185 | D I V P X Y C X Y R C X Y P 1186 | Accumulator | . . . . . . . . . . . . . . 1187 | Immediate | . . . 2 2 2 . . . 2 . . . . 1188 | Zero Page | . . . 3 3 3 5 . . 3 5 . . . 1189 | Zero Page,X | . . . 4 . . 6 . . 4 6 . . . 1190 | Zero Page,Y | . . . . . . . . . . . . . . 1191 | Absolute | . . . 4 4 4 6 . . 4 6 . . 3 1192 | Absolute,X | . . . 4* . . 7 . . 4* 7 . . . 1193 | Absolute,Y | . . . 4* . . . . . 4* . . . . 1194 | Implied | 2 2 2 . . . . 2 2 . . 2 2 . 1195 | Relative | . . . . . . . . . . . . . . 1196 | (Indirect,X) | . . . 6 . . . . . 6 . . . . 1197 | (Indirect),Y | . . . 5* . . . . . 5* . . . . 1198 | Abs. Indirect| . . . . . . . . . . . . . 5 1199 | +----------------------------------------------------------- 1200 | * Add one cycle if indexing across page boundary 1201 | ** Add one cycle if branch is taken, Add one additional if branching 1202 | operation crosses page boundary 1203 | 1204 | 1205 | ------------------------------------------------------------------------+ 1206 | INSTRUCTION ADDRESSING MODES AND RELATED EXECUTION TIMES | 1207 | (in clock cycles) | 1208 | ------------------------------------------------------------------------+ 1209 | 1210 | J L L L L N O P P P P R R R 1211 | S D D D S O R H H L L O O T 1212 | R A X Y R P A A P A P L R I 1213 | Accumulator | . . . . 2 . . . . . . 2 2 . 1214 | Immediate | . 2 2 2 . . 2 . . . . . . . 1215 | Zero Page | . 3 3 3 5 . 3 . . . . 5 5 . 1216 | Zero Page,X | . 4 . 4 6 . 4 . . . . 6 6 . 1217 | Zero Page,Y | . . 4 . . . . . . . . . . . 1218 | Absolute | 6 4 4 4 6 . 4 . . . . 6 6 . 1219 | Absolute,X | . 4* . 4* 7 . 4* . . . . 7 7 . 1220 | Absolute,Y | . 4* 4* . . . 4* . . . . . . . 1221 | Implied | . . . . . 2 . 3 3 4 4 . . 6 1222 | Relative | . . . . . . . . . . . . . . 1223 | (Indirect,X) | . 6 . . . . 6 . . . . . . . 1224 | (Indirect),Y | . 5* . . . . 5* . . . . . . . 1225 | Abs. Indirect| . . . . . . . . . . . . . . 1226 | +----------------------------------------------------------- 1227 | R S S S S S S S T T T T T T 1228 | T B E E E T T T A A S X X Y 1229 | S C C D I A X Y X Y X A S A 1230 | Accumulator | . . . . . . . . . . . . . . 1231 | Immediate | . 2 . . . . . . . . . . . . 1232 | Zero Page | . 3 . . . 3 3 3 . . . . . . 1233 | Zero Page,X | . 4 . . . 4 . 4 . . . . . . 1234 | Zero Page,Y | . . . . . . 4 . . . . . . . 1235 | Absolute | . 4 . . . 4 4 4 . . . . . . 1236 | Absolute,X | . 4* . . . 5 . . . . . . . . 1237 | Absolute,Y | . 4* . . . 5 . . . . . . . . 1238 | Implied | 6 . 2 2 2 . . . 2 2 2 2 2 2 1239 | Relative | . . . . . . . . . . . . . . 1240 | (Indirect,X) | . 6 . . . 6 . . . . . . . . 1241 | (Indirect),Y | . 5* . . . 6 . . . . . . . . 1242 | Abs. Indirect| . . . . . . . . . . . . . . 1243 | +----------------------------------------------------------- 1244 | * Add one cycle if indexing across page boundary 1245 | ** Add one cycle if branch is taken, Add one additional if branching 1246 | operation crosses page boundary 1247 | 1248 | 1249 | 1250 | 00 - BRK 20 - JSR 1251 | 01 - ORA - (Indirect,X) 21 - AND - (Indirect,X) 1252 | 02 - Future Expansion 22 - Future Expansion 1253 | 03 - Future Expansion 23 - Future Expansion 1254 | 04 - Future Expansion 24 - BIT - Zero Page 1255 | 05 - ORA - Zero Page 25 - AND - Zero Page 1256 | 06 - ASL - Zero Page 26 - ROL - Zero Page 1257 | 07 - Future Expansion 27 - Future Expansion 1258 | 08 - PHP 28 - PLP 1259 | 09 - ORA - Immediate 29 - AND - Immediate 1260 | 0A - ASL - Accumulator 2A - ROL - Accumulator 1261 | 0B - Future Expansion 2B - Future Expansion 1262 | 0C - Future Expansion 2C - BIT - Absolute 1263 | 0D - ORA - Absolute 2D - AND - Absolute 1264 | 0E - ASL - Absolute 2E - ROL - Absolute 1265 | 0F - Future Expansion 2F - Future Expansion 1266 | 10 - BPL 30 - BMI 1267 | 11 - ORA - (Indirect),Y 31 - AND - (Indirect),Y 1268 | 12 - Future Expansion 32 - Future Expansion 1269 | 13 - Future Expansion 33 - Future Expansion 1270 | 14 - Future Expansion 34 - Future Expansion 1271 | 15 - ORA - Zero Page,X 35 - AND - Zero Page,X 1272 | 16 - ASL - Zero Page,X 36 - ROL - Zero Page,X 1273 | 17 - Future Expansion 37 - Future Expansion 1274 | 18 - CLC 38 - SEC 1275 | 19 - ORA - Absolute,Y 39 - AND - Absolute,Y 1276 | 1A - Future Expansion 3A - Future Expansion 1277 | 1B - Future Expansion 3B - Future Expansion 1278 | 1C - Future Expansion 3C - Future Expansion 1279 | 1D - ORA - Absolute,X 3D - AND - Absolute,X 1280 | 1E - ASL - Absolute,X 3E - ROL - Absolute,X 1281 | 1F - Future Expansion 3F - Future Expansion 1282 | 1283 | 40 - RTI 60 - RTS 1284 | 41 - EOR - (Indirect,X) 61 - ADC - (Indirect,X) 1285 | 42 - Future Expansion 62 - Future Expansion 1286 | 43 - Future Expansion 63 - Future Expansion 1287 | 44 - Future Expansion 64 - Future Expansion 1288 | 45 - EOR - Zero Page 65 - ADC - Zero Page 1289 | 46 - LSR - Zero Page 66 - ROR - Zero Page 1290 | 47 - Future Expansion 67 - Future Expansion 1291 | 48 - PHA 68 - PLA 1292 | 49 - EOR - Immediate 69 - ADC - Immediate 1293 | 4A - LSR - Accumulator 6A - ROR - Accumulator 1294 | 4B - Future Expansion 6B - Future Expansion 1295 | 4C - JMP - Absolute 6C - JMP - Indirect 1296 | 4D - EOR - Absolute 6D - ADC - Absolute 1297 | 4E - LSR - Absolute 6E - ROR - Absolute 1298 | 4F - Future Expansion 6F - Future Expansion 1299 | 50 - BVC 70 - BVS 1300 | 51 - EOR - (Indirect),Y 71 - ADC - (Indirect),Y 1301 | 52 - Future Expansion 72 - Future Expansion 1302 | 53 - Future Expansion 73 - Future Expansion 1303 | 54 - Future Expansion 74 - Future Expansion 1304 | 55 - EOR - Zero Page,X 75 - ADC - Zero Page,X 1305 | 56 - LSR - Zero Page,X 76 - ROR - Zero Page,X 1306 | 57 - Future Expansion 77 - Future Expansion 1307 | 58 - CLI 78 - SEI 1308 | 59 - EOR - Absolute,Y 79 - ADC - Absolute,Y 1309 | 5A - Future Expansion 7A - Future Expansion 1310 | 5B - Future Expansion 7B - Future Expansion 1311 | 5C - Future Expansion 7C - Future Expansion 1312 | 50 - EOR - Absolute,X 70 - ADC - Absolute,X 1313 | 5E - LSR - Absolute,X 7E - ROR - Absolute,X 1314 | 5F - Future Expansion 7F - Future Expansion 1315 | 1316 | 80 - Future Expansion A0 - LDY - Immediate 1317 | 81 - STA - (Indirect,X) A1 - LDA - (Indirect,X) 1318 | 82 - Future Expansion A2 - LDX - Immediate 1319 | 83 - Future Expansion A3 - Future Expansion 1320 | 84 - STY - Zero Page A4 - LDY - Zero Page 1321 | 85 - STA - Zero Page A5 - LDA - Zero Page 1322 | 86 - STX - Zero Page A6 - LDX - Zero Page 1323 | 87 - Future Expansion A7 - Future Expansion 1324 | 88 - DEY A8 - TAY 1325 | 89 - Future Expansion A9 - LDA - Immediate 1326 | 8A - TXA AA - TAX 1327 | 8B - Future Expansion AB - Future Expansion 1328 | 8C - STY - Absolute AC - LDY - Absolute 1329 | 80 - STA - Absolute AD - LDA - Absolute 1330 | 8E - STX - Absolute AE - LDX - Absolute 1331 | 8F - Future Expansion AF - Future Expansion 1332 | 90 - BCC B0 - BCS 1333 | 91 - STA - (Indirect),Y B1 - LDA - (Indirect),Y 1334 | 92 - Future Expansion B2 - Future Expansion 1335 | 93 - Future Expansion B3 - Future Expansion 1336 | 94 - STY - Zero Page,X B4 - LDY - Zero Page,X 1337 | 95 - STA - Zero Page,X BS - LDA - Zero Page,X 1338 | 96 - STX - Zero Page,Y B6 - LDX - Zero Page,Y 1339 | 97 - Future Expansion B7 - Future Expansion 1340 | 98 - TYA B8 - CLV 1341 | 99 - STA - Absolute,Y B9 - LDA - Absolute,Y 1342 | 9A - TXS BA - TSX 1343 | 9B - Future Expansion BB - Future Expansion 1344 | 9C - Future Expansion BC - LDY - Absolute,X 1345 | 90 - STA - Absolute,X BD - LDA - Absolute,X 1346 | 9E - Future Expansion BE - LDX - Absolute,Y 1347 | 9F - Future Expansion BF - Future Expansion 1348 | 1349 | C0 - Cpy - Immediate E0 - CPX - Immediate 1350 | C1 - CMP - (Indirect,X) E1 - SBC - (Indirect,X) 1351 | C2 - Future Expansion E2 - Future Expansion 1352 | C3 - Future Expansion E3 - Future Expansion 1353 | C4 - CPY - Zero Page E4 - CPX - Zero Page 1354 | C5 - CMP - Zero Page E5 - SBC - Zero Page 1355 | C6 - DEC - Zero Page E6 - INC - Zero Page 1356 | C7 - Future Expansion E7 - Future Expansion 1357 | C8 - INY E8 - INX 1358 | C9 - CMP - Immediate E9 - SBC - Immediate 1359 | CA - DEX EA - NOP 1360 | CB - Future Expansion EB - Future Expansion 1361 | CC - CPY - Absolute EC - CPX - Absolute 1362 | CD - CMP - Absolute ED - SBC - Absolute 1363 | CE - DEC - Absolute EE - INC - Absolute 1364 | CF - Future Expansion EF - Future Expansion 1365 | D0 - BNE F0 - BEQ 1366 | D1 - CMP (Indirect@,Y F1 - SBC - (Indirect),Y 1367 | D2 - Future Expansion F2 - Future Expansion 1368 | D3 - Future Expansion F3 - Future Expansion 1369 | D4 - Future Expansion F4 - Future Expansion 1370 | D5 - CMP - Zero Page,X F5 - SBC - Zero Page,X 1371 | D6 - DEC - Zero Page,X F6 - INC - Zero Page,X 1372 | D7 - Future Expansion F7 - Future Expansion 1373 | D8 - CLD F8 - SED 1374 | D9 - CMP - Absolute,Y F9 - SBC - Absolute,Y 1375 | DA - Future Expansion FA - Future Expansion 1376 | DB - Future Expansion FB - Future Expansion 1377 | DC - Future Expansion FC - Future Expansion 1378 | DD - CMP - Absolute,X FD - SBC - Absolute,X 1379 | DE - DEC - Absolute,X FE - INC - Absolute,X 1380 | DF - Future Expansion FF - Future Expansion 1381 | 1382 | 1383 | INSTRUCTION OPERATION 1384 | 1385 | The following code has been taken from VICE for the purposes of showing 1386 | how each instruction operates. No particular addressing mode is used since 1387 | we only wish to see the operation of the instruction itself. 1388 | 1389 | src : the byte of data that is being addressed. 1390 | SET_SIGN : sets\resets the sign flag depending on bit 7. 1391 | SET_ZERO : sets\resets the zero flag depending on whether the result 1392 | is zero or not. 1393 | SET_CARRY(condition) : if the condition has a non-zero value then the 1394 | carry flag is set, else it is reset. 1395 | SET_OVERFLOW(condition) : if the condition is true then the overflow 1396 | flag is set, else it is reset. 1397 | SET_INTERRUPT : } 1398 | SET_BREAK : } As for SET_CARRY and SET_OVERFLOW. 1399 | SET_DECIMAL : } 1400 | REL_ADDR(PC, src) : returns the relative address obtained by adding 1401 | the displacement src to the PC. 1402 | SET_SR : set the Program Status Register to the value given. 1403 | GET_SR : get the value of the Program Status Register. 1404 | PULL : Pull a byte off the stack. 1405 | PUSH : Push a byte onto the stack. 1406 | LOAD : Get a byte from the memory address. 1407 | STORE : Store a byte in a memory address. 1408 | IF_CARRY, IF_OVERFLOW, IF_SIGN, IF_ZERO etc : Returns true if the 1409 | relevant flag is set, otherwise returns false. 1410 | clk : the number of cycles an instruction takes. This is shown below 1411 | in situations where the number of cycles changes depending 1412 | on the result of the instruction (eg. Branching instructions). 1413 | 1414 | AC = Accumulator 1415 | XR = X register 1416 | YR = Y register 1417 | PC = Program Counter 1418 | SP = Stack Pointer 1419 | 1420 | 1421 | /* ADC */ 1422 | unsigned int temp = src + AC + (IF_CARRY() ? 1 : 0); 1423 | SET_ZERO(temp & 0xff); /* This is not valid in decimal mode */ 1424 | if (IF_DECIMAL()) { 1425 | if (((AC & 0xf) + (src & 0xf) + (IF_CARRY() ? 1 : 0)) > 9) temp += 6; 1426 | SET_SIGN(temp); 1427 | SET_OVERFLOW(!((AC ^ src) & 0x80) && ((AC ^ temp) & 0x80)); 1428 | if (temp > 0x99) temp += 96; 1429 | SET_CARRY(temp > 0x99); 1430 | } else { 1431 | SET_SIGN(temp); 1432 | SET_OVERFLOW(!((AC ^ src) & 0x80) && ((AC ^ temp) & 0x80)); 1433 | SET_CARRY(temp > 0xff); 1434 | } 1435 | AC = ((BYTE) temp); 1436 | 1437 | /* AND */ 1438 | src &= AC; 1439 | SET_SIGN(src); 1440 | SET_ZERO(src); 1441 | AC = src; 1442 | 1443 | /* ASL */ 1444 | SET_CARRY(src & 0x80); 1445 | src <<= 1; 1446 | src &= 0xff; 1447 | SET_SIGN(src); 1448 | SET_ZERO(src); 1449 | STORE src in memory or accumulator depending on addressing mode. 1450 | 1451 | /* BCC */ 1452 | if (!IF_CARRY()) { 1453 | clk += ((PC & 0xFF00) != (REL_ADDR(PC, src) & 0xFF00) ? 2 : 1); 1454 | PC = REL_ADDR(PC, src); 1455 | } 1456 | 1457 | /* BCS */ 1458 | if (IF_CARRY()) { 1459 | clk += ((PC & 0xFF00) != (REL_ADDR(PC, src) & 0xFF00) ? 2 : 1); 1460 | PC = REL_ADDR(PC, src); 1461 | } 1462 | 1463 | /* BEQ */ 1464 | if (IF_ZERO()) { 1465 | clk += ((PC & 0xFF00) != (REL_ADDR(PC, src) & 0xFF00) ? 2 : 1); 1466 | PC = REL_ADDR(PC, src); 1467 | } 1468 | 1469 | /* BIT */ 1470 | SET_SIGN(src); 1471 | SET_OVERFLOW(0x40 & src); /* Copy bit 6 to OVERFLOW flag. */ 1472 | SET_ZERO(src & AC); 1473 | 1474 | /* BMI */ 1475 | if (IF_SIGN()) { 1476 | clk += ((PC & 0xFF00) != (REL_ADDR(PC, src) & 0xFF00) ? 2 : 1); 1477 | PC = REL_ADDR(PC, src); 1478 | } 1479 | 1480 | /* BNE */ 1481 | if (!IF_ZERO()) { 1482 | clk += ((PC & 0xFF00) != (REL_ADDR(PC, src) & 0xFF00) ? 2 : 1); 1483 | PC = REL_ADDR(PC, src); 1484 | } 1485 | 1486 | /* BPL */ 1487 | if (!IF_SIGN()) { 1488 | clk += ((PC & 0xFF00) != (REL_ADDR(PC, src) & 0xFF00) ? 2 : 1); 1489 | PC = REL_ADDR(PC, src); 1490 | } 1491 | 1492 | /* BRK */ 1493 | PC++; 1494 | PUSH((PC >> 8) & 0xff); /* Push return address onto the stack. */ 1495 | PUSH(PC & 0xff); 1496 | SET_BREAK((1)); /* Set BFlag before pushing */ 1497 | PUSH(SR); 1498 | SET_INTERRUPT((1)); 1499 | PC = (LOAD(0xFFFE) | (LOAD(0xFFFF) << 8)); 1500 | 1501 | /* BVC */ 1502 | if (!IF_OVERFLOW()) { 1503 | clk += ((PC & 0xFF00) != (REL_ADDR(PC, src) & 0xFF00) ? 2 : 1); 1504 | PC = REL_ADDR(PC, src); 1505 | } 1506 | 1507 | /* BVS */ 1508 | if (IF_OVERFLOW()) { 1509 | clk += ((PC & 0xFF00) != (REL_ADDR(PC, src) & 0xFF00) ? 2 : 1); 1510 | PC = REL_ADDR(PC, src); 1511 | } 1512 | 1513 | /* CLC */ 1514 | SET_CARRY((0)); 1515 | 1516 | /* CLD */ 1517 | SET_DECIMAL((0)); 1518 | 1519 | /* CLI */ 1520 | SET_INTERRUPT((0)); 1521 | 1522 | /* CLV */ 1523 | SET_OVERFLOW((0)); 1524 | 1525 | /* CMP */ 1526 | src = AC - src; 1527 | SET_CARRY(src < 0x100); 1528 | SET_SIGN(src); 1529 | SET_ZERO(src &= 0xff); 1530 | 1531 | /* CPX */ 1532 | src = XR - src; 1533 | SET_CARRY(src < 0x100); 1534 | SET_SIGN(src); 1535 | SET_ZERO(src &= 0xff); 1536 | 1537 | /* CPY */ 1538 | src = YR - src; 1539 | SET_CARRY(src < 0x100); 1540 | SET_SIGN(src); 1541 | SET_ZERO(src &= 0xff); 1542 | 1543 | /* DEC */ 1544 | src = (src - 1) & 0xff; 1545 | SET_SIGN(src); 1546 | SET_ZERO(src); 1547 | STORE(address, (src)); 1548 | 1549 | /* DEX */ 1550 | unsigned src = XR; 1551 | src = (src - 1) & 0xff; 1552 | SET_SIGN(src); 1553 | SET_ZERO(src); 1554 | XR = (src); 1555 | 1556 | /* DEY */ 1557 | unsigned src = YR; 1558 | src = (src - 1) & 0xff; 1559 | SET_SIGN(src); 1560 | SET_ZERO(src); 1561 | YR = (src); 1562 | 1563 | /* EOR */ 1564 | src ^= AC; 1565 | SET_SIGN(src); 1566 | SET_ZERO(src); 1567 | AC = src; 1568 | 1569 | /* INC */ 1570 | src = (src + 1) & 0xff; 1571 | SET_SIGN(src); 1572 | SET_ZERO(src); 1573 | STORE(address, (src)); 1574 | 1575 | /* INX */ 1576 | unsigned src = XR; 1577 | src = (src + 1) & 0xff; 1578 | SET_SIGN(src); 1579 | SET_ZERO(src); 1580 | XR = (src); 1581 | 1582 | /* INY */ 1583 | unsigned src = YR; 1584 | src = (src + 1) & 0xff; 1585 | SET_SIGN(src); 1586 | SET_ZERO(src); 1587 | YR = (src); 1588 | 1589 | /* JMP */ 1590 | PC = (src); 1591 | 1592 | /* JSR */ 1593 | PC--; 1594 | PUSH((PC >> 8) & 0xff); /* Push return address onto the stack. */ 1595 | PUSH(PC & 0xff); 1596 | PC = (src); 1597 | 1598 | /* LDA */ 1599 | SET_SIGN(src); 1600 | SET_ZERO(src); 1601 | AC = (src); 1602 | 1603 | /* LDX */ 1604 | SET_SIGN(src); 1605 | SET_ZERO(src); 1606 | XR = (src); 1607 | 1608 | /* LDY */ 1609 | SET_SIGN(src); 1610 | SET_ZERO(src); 1611 | YR = (src); 1612 | 1613 | /* LSR */ 1614 | SET_CARRY(src & 0x01); 1615 | src >>= 1; 1616 | SET_SIGN(src); 1617 | SET_ZERO(src); 1618 | STORE src in memory or accumulator depending on addressing mode. 1619 | 1620 | /* NOP */ 1621 | Nothing. 1622 | 1623 | /* ORA */ 1624 | src |= AC; 1625 | SET_SIGN(src); 1626 | SET_ZERO(src); 1627 | AC = src; 1628 | 1629 | /* PHA */ 1630 | src = AC; 1631 | PUSH(src); 1632 | 1633 | /* PHP */ 1634 | src = GET_SR; 1635 | PUSH(src); 1636 | 1637 | /* PLA */ 1638 | src = PULL(); 1639 | SET_SIGN(src); /* Change sign and zero flag accordingly. */ 1640 | SET_ZERO(src); 1641 | 1642 | /* PLP */ 1643 | src = PULL(); 1644 | SET_SR((src)); 1645 | 1646 | /* ROL */ 1647 | src <<= 1; 1648 | if (IF_CARRY()) src |= 0x1; 1649 | SET_CARRY(src > 0xff); 1650 | src &= 0xff; 1651 | SET_SIGN(src); 1652 | SET_ZERO(src); 1653 | STORE src in memory or accumulator depending on addressing mode. 1654 | 1655 | /* ROR */ 1656 | if (IF_CARRY()) src |= 0x100; 1657 | SET_CARRY(src & 0x01); 1658 | src >>= 1; 1659 | SET_SIGN(src); 1660 | SET_ZERO(src); 1661 | STORE src in memory or accumulator depending on addressing mode. 1662 | 1663 | /* RTI */ 1664 | src = PULL(); 1665 | SET_SR(src); 1666 | src = PULL(); 1667 | src |= (PULL() << 8); /* Load return address from stack. */ 1668 | PC = (src); 1669 | 1670 | /* RTS */ 1671 | src = PULL(); 1672 | src += ((PULL()) << 8) + 1; /* Load return address from stack and add 1. */ 1673 | PC = (src); 1674 | 1675 | /* SBC */ 1676 | unsigned int temp = AC - src - (IF_CARRY() ? 0 : 1); 1677 | SET_SIGN(temp); 1678 | SET_ZERO(temp & 0xff); /* Sign and Zero are invalid in decimal mode */ 1679 | SET_OVERFLOW(((AC ^ temp) & 0x80) && ((AC ^ src) & 0x80)); 1680 | if (IF_DECIMAL()) { 1681 | if ( ((AC & 0xf) - (IF_CARRY() ? 0 : 1)) < (src & 0xf)) /* EP */ temp -= 6; 1682 | if (temp > 0x99) temp -= 0x60; 1683 | } 1684 | SET_CARRY(temp < 0x100); 1685 | AC = (temp & 0xff); 1686 | 1687 | /* SEC */ 1688 | SET_CARRY((1)); 1689 | 1690 | /* SED */ 1691 | SET_DECIMAL((1)); 1692 | 1693 | /* SEI */ 1694 | SET_INTERRUPT((1)); 1695 | 1696 | /* STA */ 1697 | STORE(address, (src)); 1698 | 1699 | /* STX */ 1700 | STORE(address, (src)); 1701 | 1702 | /* STY */ 1703 | STORE(address, (src)); 1704 | 1705 | /* TAX */ 1706 | unsigned src = AC; 1707 | SET_SIGN(src); 1708 | SET_ZERO(src); 1709 | XR = (src); 1710 | 1711 | /* TAY */ 1712 | unsigned src = AC; 1713 | SET_SIGN(src); 1714 | SET_ZERO(src); 1715 | YR = (src); 1716 | 1717 | /* TSX */ 1718 | unsigned src = SP; 1719 | SET_SIGN(src); 1720 | SET_ZERO(src); 1721 | XR = (src); 1722 | 1723 | /* TXA */ 1724 | unsigned src = XR; 1725 | SET_SIGN(src); 1726 | SET_ZERO(src); 1727 | AC = (src); 1728 | 1729 | /* TXS */ 1730 | unsigned src = XR; 1731 | SP = (src); 1732 | 1733 | /* TYA */ 1734 | unsigned src = YR; 1735 | SET_SIGN(src); 1736 | SET_ZERO(src); 1737 | AC = (src); 1738 | 1739 | --------------------------------------------------------------------------------