cljs-65020.1.0A 6502 Emulator, Assembler, and Disassembler in pure Clojurescript. dependencies
| (this space intentionally left almost blank) | |||||||||
(ns clj-6502.macros (:refer-clojure :exclude [bytes])) | ||||||||||
Define an Enum: a function %NAME that can be called with a keyword to retrieve the corresponding index. | (defmacro defenum
[name keys]
`(defn ~(symbol (str "%" name)) [~'key]
(let [enum# ~(zipmap keys (range))]
(get enum# ~'key)))) | |||||||||
Take a Relative branch if PREDICATE is true, otherwise increment the PC. | (defmacro branch-if
[predicate]
`(if ~predicate
(set-register :pc (getter (Relative.)))
(swap! cpu update-in [:pc] inc))) | |||||||||
Special-case the handling of accumulator mode in ASL/LSR/ROL/ROR. | (defmacro getter-mixed
[]
`(if (= (type mode) ~'accumulator)
(getter mode raw?)
(get-byte (getter mode raw?)))) | |||||||||
Define an Addressing Mode: a type NAME and an implementation of the AddressingMode protocol for that type. BODY should compute an address and will be used by getter and setter. If CPU-REG is non-nil, BODY will be wrapped in a get-byte call for setting. READER should be a regex that can read assembly in the mode. PRINTER should be the format string desired for disassembly of the mode. | (defmacro defaddress
[name {:keys [reader writer cpu-reg]} & body]
(let [getter-body (if cpu-reg
`((cljs-6502.cpu/get-register ~@body))
body)])
`(do
(deftype ~name [])
(extend-protocol cljs-6502.addressing/AddressingMode
~name
(~'getter [mode# raw?#] `(if raw?#
~@getter-body
(get-byte ~@getter-body)))
(~'setter [mode# val#] ~(if cpu-reg
`(cljs-6502.cpu/set-register ~@body val#)
`(cljs-6502.cpu/set-byte ~@body val#)))
(~'reader [mode#] ~reader)
(~'writer [mode#] ~writer)))) | |||||||||
Define an Assembly Mnemonic: a function NAME that takes an array of opcode metadata and executes BODY. DOCS is the documentation for the opcode. TRACK-PC? can be passed nil to short-circuit PC incrementing for opcodes that modify the PC. RAW? controls whether the addressing mode's GETTER returns an address directly or the byte at that address. | (defmacro defasm
[name {:keys [docs raw? track-pc?]} modes & body]
`(do
`(defn ~name
~docs
[~'cycles ~'bytes ~'mode ~'raw?]
(swap! cljs-6502.cpu/cpu update-in [:pc] inc)
~@body
(when (clojure.core/and ~track-pc? (> bytes 1))
(swap! cljs-6502.cpu/cpu update-in [:pc] #(+ % (dec bytes))))
(swap! cljs-6502.cpu/cpu update-in [:cc] #(+ % ~'cycles)))
(doseq [[op# cycles# bytes# mode#] ~modes]
(swap! cljs-6502.cpu/opcodes update-in [op#]
(fn [x#] [~name cycles# bytes# (new mode#) ~raw?]))))) | |||||||||
(ns cljs-6502.addressing
(:use [cljs-6502.cpu :only [cpu get-register set-register
get-byte get-word wrap-byte wrap-word
maybe-update-cycle-count]])
(:use-macros [clj-6502.macros :only [defaddress]])) | ||||||||||
(defprotocol AddressingMode (getter [mode raw?] "Get the value at MODE or the address if RAW? is non-nil.") (setter [mode val] "Set the value at MODE to VAL.") (reader [mode] "Return a Regular Expression for parsing MODE.") (writer [mode] "Return a Format Expression for printing MODE.")) | ||||||||||
(defaddress Implied
{:reader #"^$"
:writer }
nil) | ||||||||||
(defaddress Accumulator
{:reader #"^[aA]$"
:writer "A"
:cpu-reg t}
:ar) | ||||||||||
(defaddress Immediate
{:reader #"^#\\$[0-9a-fA-F]{2}$"
:writer "#$%02x"
:cpu-reg t}
:pc) | ||||||||||
(defaddress ZeroPage
{:reader #"^\\$[0-9a-fA-F]{2}$"
:writer "$%02x"}
(get-byte (:pc @cpu))) | ||||||||||
(defaddress ZeroPageX
{:reader #"^\\$[0-9a-fA-F]{2},[xX]$"
:writer "$%02x, X"}
(wrap-byte (+ (:pc @cpu) (:xr @cpu)))) | ||||||||||
(defaddress ZeroPageY
{:reader #"^\\$[0-9a-fA-F]{2},[yY]$"
:writer "$%02x, Y"}
(wrap-byte (+ (:pc @cpu) (:xr @cpu)))) | ||||||||||
(defaddress Absolute
{:reader #"^\\$[0-9a-fA-F]{4}$"
:writer "$%02x%02x"}
(get-word (:pc @cpu))) | ||||||||||
(defaddress AbsoluteX
{:reader #"^\\$[0-9a-fA-F]{4},[xX]$"
:writer "$%02x%02x, X"}
;; TODO: Either have M-U-C-C return result or use a threading macro.
(let [result (wrap-word (+ (get-word (:pc @cpu)) (:xr @cpu)))]
(maybe-update-cycle-count result nil))) | ||||||||||
(defaddress AbsoluteY
{:reader #"^\\$[0-9a-fA-F]{4},[yY]$"
:writer "$%02x%02x, Y"}
(let [result (wrap-word (+ (get-word (:pc @cpu)) (:yr @cpu)))]
(maybe-update-cycle-count result nil))) | ||||||||||
(defaddress Indirect
{:reader #"^\\(\\$[0-9a-fA-F]{4}\\)$"
:writer "($%02x%02x)"}
(get-word (get-word (:pc @cpu)) t)) | ||||||||||
(defaddress IndirectX
{:reader #"^\\(\\$[0-9a-fA-F]{2}\\),[xX]$"
:writer "($%02x), X"}
(get-word (wrap-byte (+ (get-byte (:pc @cpu)) (:xr @cpu))) t)) | ||||||||||
(defaddress IndirectY
{:reader #"^\\(\\$[0-9a-fA-F]{2}\\),[yY]$"
:writer "($%02x), Y"}
(let [addr (get-word (get-byte (:pc @cpu)) t)
result (wrap-word (+ addr (:yr @cpu)))]
(maybe-update-cycle-count result nil))) | ||||||||||
(defaddress Relative
{:reader #"^&[0-9a-fA-F]{2}$"
:writer "&%02x"}
(swap! cpu update-in [:cc] inc)
(let [pc (:pc @cpu)
offset (get-byte pc)
result (if (bit-test offset 7)
(wrap-word (- pc (- 0xff offset)))
(wrap-word (+ pc (inc offset))))]
(maybe-update-cycle-count result (inc pc)))) | ||||||||||
(ns cljs-6502.core
(:use [cljs-6502.cpu :only [get-byte cpu opcodes]]
;[cljs-6502.opcodes :only []])) | ||||||||||
Step the CPU until a BRK. | (defn execute
[]
(loop [opcode (get-byte (:pc @cpu))]
(let [result (cpu-step opcode)]
(if (= result :done)
@cpu
(recur (get-byte (:pc @cpu))))))) | |||||||||
Step the CPU through the next instruction, returning the CPU or :done. | (defn cpu-step
[opcode]
(let [[multimethod _ _ mode] (get @opcodes opcode)
result (multimethod opcode mode)]
(if (zero? opcode)
:done
result))) | |||||||||
(ns cljs-6502.cpu (:use-macros [clj-6502.macros :only [defenum]])) | ||||||||||
(defprotocol Resettable (reset [obj] "Reset the given object to an initial state.")) | ||||||||||
(defrecord CPU [pc sp sr xr yr ar cc]) | ||||||||||
(defn make-cpu [] (CPU. 0xfffc 0xfd 0x24 0 0 0 0)) | ||||||||||
The 6502 instance used by opcodes in the package. | (def cpu (atom (make-cpu))) | |||||||||
A lovely hunk of bytes. | (def ram (atom (vec (repeat 0x10000 0)))) | |||||||||
A mapping of opcodes to instruction metadata vectors. | (def opcodes (atom (vec (repeat 0x100 [nil nil nil nil])))) | |||||||||
Get the value of REGISTER. | (defn get-register [register] (get @cpu register)) | |||||||||
Set REGISTER to VALUE. | (defn set-register [register value] (swap! cpu update-in [register] (fn [x] value)) value) | |||||||||
Get a byte from ram at the given ADDRESS. | (defn get-byte [address] (get @ram address)) | |||||||||
Set ADDRESS in ram to VALUE. | (defn set-byte [address value] (swap! ram update-in [address] (fn [x] value)) value) | |||||||||
Get the word starting at ADDRESS. | (defn get-word
[address]
(+ (get-byte address)
(bit-shift-left (get-byte (inc address)) 8))) | |||||||||
Get the range of bytes from START to END from ram. | (defn get-range [start end] (subvec @ram start end)) | |||||||||
Wrap VALUE to ensure it fits in a single byte. | (defn wrap-byte [value] (bit-and value 0xff)) | |||||||||
Wrap VALUE to ensure it fits in a single word. | (defn wrap-word [value] (bit-and value 0xffff)) | |||||||||
Wrap VALUE to ensure it doesn't cross a page boundary. | (defn wrap-page
[value]
(+ (bit-and value 0xff00)
(bit-and (inc value) 0xff))) | |||||||||
If ADDRESS crosses a page boundary, add an extra cycle to CPU's count. If START is provided, test that against ADDRESS. Otherwise, use absolute address. | (defn maybe-update-cycle-count
[address start]
(when (not (= (bit-and (or start (get-word (:pc @cpu))) 0xff00)
(bit-and address 0xff00)))
(swap! cpu update-in [:cc] inc))) | |||||||||
Push the byte VALUE on the stack, decrementing the stack pointer. | (defn stack-push
[value]
(let [address (+ (:sp @cpu) 0x100)]
(swap! ram update-in [address] (fn [x] (wrap-byte value)))
(swap! cpu update-in [:sp] (fn [x] (wrap-byte (dec x)))))) | |||||||||
Push the 16-bit word VALUE onto the stack, decrementing the stack pointer. | (defn stack-push-word [value] (stack-push (wrap-byte (bit-shift-right value 8))) (stack-push (wrap-byte value))) | |||||||||
Pop the top byte off the stack, incrementing the stack pointer. | (defn stack-pop [] (swap! cpu update-in [:sp] (fn [x] (wrap-byte (inc x)))) (get-byte (+ (:sp @cpu) 0x100))) | |||||||||
Pop the top word off the stack, incrementing the stack pointer. | (defn stack-pop-word [] (+ (stack-pop) (bit-shift-left (stack-pop) 8))) | |||||||||
(defenum status-bit [:carry :zero :interrupt :decimal
:break :unused :overflow :negative]) | ||||||||||
Retrieve the bit corresponding to keyword NAME from the CPU status register. | (defn status-bit [name] (bit-and (:sr @cpu) (Math/pow 2 (%status-bit name)))) | |||||||||
Set the bit corresponding to NAME from the CPU status register to VALUE. | (defn set-status-bit
[name value]
(let [setter (if (zero? value) bit-clear bit-set)
index (%status-bit name)]
(swap! cpu update-in [:sr] (fn [x] (apply setter [x index]))))) | |||||||||
Takes any even number of arguments where the first is a keyword denoting a status bit and the second is a predicate function that takes no arguments. It will set each flag to 1 if its predicate is true, otherwise 0. | (defn set-flags-if
[& flag-preds]
(doseq [[flag predicate] (apply hash-map flag-preds)]
(set-status-bit flag (if (predicate) 1 0)))) | |||||||||
Set the zero and negative bits of CPU's status-register based on VALUE. | (defn set-flags-nz [value] (set-flags-if :zero #(zero? value) :negative #(bit-test value 7))) | |||||||||
Checks whether the sign of RESULT is found in the signs of REG or MEM. | (defn overflow?
[result reg mem]
(not (or (= (bit-test result 7) (bit-test reg 7))
(= (bit-test result 7) (bit-test mem 7))))) | |||||||||
Rotate the bits of INTEGER by COUNT. If COUNT is negative, rotate right. | (defn rotate-byte
[integer count]
(let [shifter (if (pos? count) bit-shift-left bit-shift-right)
result (apply shifter [integer count])]
(cond
(not (pos? (status-bit :carry))) result
(= 01 count) (bit-or result 0x01)
(= -1 count) (bit-or result 0x80)))) | |||||||||
Generate a non-maskable interrupt. Used for vblanking in NES. | (defn nmi [] (stack-push-word (:pc @cpu)) (stack-push (:sr @cpu)) (set-register :pc (get-word 0xfffa))) | |||||||||
(extend-protocol Resettable CPU (reset [obj] (swap! cpu (fn [x] (make-cpu))))) | ||||||||||
(ns cljs-6502.opcodes
(:refer-clojure :exclude [and inc dec])
(:use-macros [clj-6502.macros :only [defasm branch-if getter-mixed]])
(:use [cljs-6502.addressing :only [Implied Immediate Accumulator
ZeroPage ZeroPageX ZeroPageY
Absolute AbsoluteX AbsoluteY
Indirect IndirectX IndirectY
Relative getter setter]]
[cljs-6502.cpu :only [get-register set-register get-byte set-byte
get-word wrap-byte wrap-word rotate-byte
stack-push stack-pop stack-push-word stack-pop-word
status-bit set-status-bit set-flags-if set-flags-nz
overflow?]])) | ||||||||||
(defasm adc {:docs "Add to Accumulator with Carry"}
[[0x61 6 2 IndirectX]
[0x65 3 2 ZeroPage]
[0x69 2 2 Immediate]
[0x6d 4 3 Absolute]
[0x71 5 2 IndirectY]
[0x75 4 2 ZeroPageX]
[0x79 4 3 AbsoluteY]
[0x7d 4 3 AbsoluteX]]
(let [value (getter mode raw?)
result (+ (get-register :ar) value (status-bit :carry))]
(set-flags-if :carry #(> result 0xff)
:overflow #(overflow? result (get-register :ar) value)
:negative #(bit-test result 7)
:zero #(zero? (wrap-byte result)))
(set-register :ar (wrap-byte result)))) | ||||||||||
(defasm and {:docs "And with Accumulator"}
[[0x21 6 2 IndirectX]
[0x25 3 2 ZeroPage]
[0x29 2 2 Immediate]
[0x2d 4 3 Absolute]
[0x31 5 2 IndirectY]
[0x35 4 2 ZeroPageX]
[0x39 4 3 AbsoluteY]
[0x3d 4 3 AbsoluteX]]
(let [value (getter mode raw?)
result (set-register :ar (bit-and (get-register :ar) value))]
(set-flags-nz result))) | ||||||||||
(defasm asl {:docs "Arithmetic Shift Left"}
[[0x06 5 2 ZeroPage]
[0x0a 2 1 Accumulator]
[0x0e 6 3 Absolute]
[0x16 6 2 ZeroPageX]
[0x1e 7 3 AbsoluteX]]
(let [value (getter-mixed)
result (wrap-byte (bit-shift-left value 1))]
(set-flags-if :carry #(bit-test value 7))
(set-flags-nz result)
(setter mode result))) | ||||||||||
(defasm bcc {:docs "Branch on Carry Clear" :track-pc? nil}
[[0x90 2 2 Relative]]
(branch-if #(zero? (status-bit :carry)))) | ||||||||||
(defasm bcs {:docs "Branch on Carry Set" :track-pc? nil}
[[0xb0 2 2 Relative]]
(branch-if #(pos? (status-bit :carry)))) | ||||||||||
(defasm beq {:docs "Branch if Equal" :track-pc? nil}
[[0xf0 2 2 Relative]]
(branch-if #(pos? (status-bit :zero)))) | ||||||||||
(defasm bit {:docs "Test Bits in Memory with Accumulator"}
[[0x24 3 2 ZeroPage]
[0x2c 4 3 Absolute]]
(let [result (getter mode raw?)]
(set-flags-if :zero #(zero? (bit-and (get-register :ar) result))
:negative #(bit-test result 7)
:overflow #(bit-test result 6)))) | ||||||||||
(defasm bmi {:docs "Branch on Negative Result" :track-pc? nil}
[[0x30 2 2 Relative]]
(branch-if #(pos? (status-bit :negative)))) | ||||||||||
(defasm bne {:docs "Branch if Not Equal" :track-pc? nil}
[[0xd0 2 2 Relative]]
(branch-if #(zero? (status-bit :zero)))) | ||||||||||
(defasm bpl {:docs "Branch on Positive Result" :track-pc? nil}
[[0x10 2 2 Relative]]
(branch-if #(zero? (status-bit :negative)))) | ||||||||||
(defasm brk {:docs "Force Break"}
[[0x00 7 1 Implied]]
(let [pc (wrap-word (+ 1 (get-register :pc)))]
(stack-push-word pc)
(set-status-bit :break 1)
(stack-push (get-register :sr))
(set-status-bit :interrupt 1)
(set-register :pc (get-word 0xfffe)))) | ||||||||||
(defasm bvc {:docs "Branch on Overflow Clear" :track-pc? nil}
[[0x50 2 2 Relative]]
(branch-if #(zero? (status-bit :overflow)))) | ||||||||||
(defasm bvs {:docs "Branch on Overflow Set" :track-pc? nil}
[[0x70 2 2 Relative]]
(branch-if #(pos? (status-bit :overflow)))) | ||||||||||
(defasm clc {:docs "Clear Carry Flag"}
[[0x18 2 1 Implied]]
(set-status-bit :carry 0)) | ||||||||||
(defasm cld {:docs "Clear Decimal Flag"}
[[0xd8 2 1 Implied]]
(set-status-bit :decimal 0)) | ||||||||||
(defasm cli {:docs "Clear Interrupt Flag"}
[[0x58 2 1 Implied]]
(set-status-bit :interrupt 0)) | ||||||||||
(defasm clv {:docs "Clear Overflow Flag"}
[[0xb8 2 1 Implied]]
(set-status-bit :overflow 0)) | ||||||||||
(defasm cmp {:docs "Compare Memory with Accumulator"}
[[0xc1 6 2 IndirectX]
[0xc5 3 2 ZeroPage]
[0xc9 2 2 Immediate]
[0xcd 4 3 Absolute]
[0xd1 5 2 IndirectY]
[0xd5 4 2 ZeroPageX]
[0xd9 4 3 AbsoluteY]
[0xdd 4 3 AbsoluteX]]
(let [result (- (get-register :ar) (getter mode raw?))]
(set-flags-if :carry (not (neg? result)))
(set-flags-nz result))) | ||||||||||
(defasm cpx {:docs "Compare Memory with X register"}
[[0xe0 2 2 Immediate]
[0xe4 3 2 ZeroPage]
[0xec 4 3 Absolute]]
(let [result (- (get-register :xr) (getter mode raw?))]
(set-flags-if :carry #(not (neg? result)))
(set-flags-nz result))) | ||||||||||
(defasm cpy {:docs "Compare Memory with Y register"}
[[0xc0 2 2 Immediate]
[0xc4 3 2 ZeroPage]
[0xcc 4 3 Absolute]]
(let [result (- (get-register :yr) (getter mode raw?))]
(set-flags-if :carry #(not (neg? result)))
(set-flags-nz result))) | ||||||||||
(defasm dec {:docs "Decrement Memory"}
[[0xc6 5 2 ZeroPage]
[0xce 6 3 Absolute]
[0xd6 6 2 ZeroPageX]
[0xde 7 3 AbsoluteX]]
(let [result (wrap-byte (- (getter mode raw?) 1))]
(setter mode result)
(set-flags-nz result))) | ||||||||||
(defasm dex {:docs "Decrement X register"}
[[0xca 2 1 Implied]]
(let [result (set-register :xr (wrap-byte (- (get-register :xr) 1)))]
(set-flags-nz result))) | ||||||||||
(defasm dey {:docs "Decrement Y register"}
[[0x88 2 1 Implied]]
(let [result (set-register :yr (wrap-byte (- (get-register :yr) 1)))]
(set-flags-nz result))) | ||||||||||
(defasm eor {:docs "Exclusive OR with Accumulator"}
[[0x41 6 2 IndirectX]
[0x45 3 2 ZeroPage]
[0x49 2 2 Immediate]
[0x4d 4 3 Absolute]
[0x51 5 2 IndirectY]
[0x55 4 2 ZeroPageX]
[0x59 4 3 AbsoluteY]
[0x5d 4 3 AbsoluteX]]
(let [result (set-register :ar (bit-xor (getter mode raw?) (get-register :ar)))]
(set-flags-nz result))) | ||||||||||
(defasm inc {:docs "Increment Memory"}
[[0xe6 5 2 ZeroPage]
[0xee 6 3 Absolute]
[0xf6 6 2 ZeroPageX]
[0xfe 7 3 AbsoluteX]]
(let [result (wrap-byte (+ 1 (getter mode raw?)))]
(setter mode result)
(set-flags-nz result))) | ||||||||||
(defasm inx {:docs "Increment X register"}
[[0xe8 2 1 Implied]]
(let [result (set-register :xr (wrap-byte (+ 1 (get-register :xr))))]
(set-flags-nz result))) | ||||||||||
(defasm iny {:docs "Increment Y register"}
[[0xc8 2 1 Implied]]
(let [result (set-register :yr (wrap-byte (+ 1 (get-register :yr))))]
(set-flags-nz result))) | ||||||||||
(defasm jmp {:docs "Jump Unconditionally" :raw? t :track-pc? nil}
[[0x4c 3 3 Absolute]
[0x6c 5 3 Indirect]]
(set-register :pc (getter mode raw?))) | ||||||||||
(defasm jsr {:docs "Jump to Subroutine" :raw? t :track-pc? nil}
[[0x20 6 3 Absolute]]
(stack-push-word (wrap-word (+ 1 (get-register :pc))))
(set-register :pc (getter mode raw?))) | ||||||||||
(defasm lda {:docs "Load Accumulator from Memory"}
[[0xa1 6 2 IndirectX]
[0xa5 3 2 ZeroPage]
[0xa9 2 2 Immediate]
[0xad 4 3 Absolute]
[0xb1 5 2 IndirectY]
[0xb5 4 2 ZeroPageX]
[0xb9 4 3 AbsoluteY]
[0xbd 4 3 AbsoluteX]]
(let [result (set-register :ar (getter mode raw?))]
(set-flags-nz result))) | ||||||||||
(defasm ldx {:docs "Load X register from Memory"}
[[0xa2 2 2 Immediate]
[0xa6 3 2 ZeroPage]
[0xae 4 3 Absolute]
[0xb6 4 2 ZeroPageY]
[0xbe 4 3 AbsoluteY]]
(let [result (set-register :xr (getter mode raw?))]
(set-flags-nz result))) | ||||||||||
(defasm ldy {:docs "Load Y register from Memory"}
[[0xa0 2 2 Immediate]
[0xa4 3 2 ZeroPage]
[0xac 4 3 Absolute]
[0xbc 4 3 AbsoluteX]
[0xb4 4 2 ZeroPageX]]
(let [result (set-register :yr (getter mode raw?))]
(set-flags-nz result))) | ||||||||||
(defasm lsr {:docs "Logical Shift Right"}
[[0x46 5 2 ZeroPage]
[0x4a 2 1 Accumulator]
[0x4e 6 3 Absolute]
[0x56 6 2 ZeroPageX]
[0x5e 7 3 AbsoluteX]]
(let [value (getter-mixed)
result (bit-shift-right value 1)]
(set-flags-if :carry #(bit-test value 0))
(setter mode result)
(set-flags-nz result))) | ||||||||||
(defasm nop {:docs "No Operation"}
[[0xea 2 1 Implied]]
nil) | ||||||||||
(defasm ora {:docs "Bitwise OR with Accumulator"}
[[0x01 6 2 IndirectX]
[0x05 3 2 ZeroPage]
[0x09 2 2 Immediate]
[0x0d 4 3 Absolute]
[0x11 5 2 IndirectY]
[0x15 4 2 ZeroPageX]
[0x19 4 3 AbsoluteY]
[0x1d 4 3 AbsoluteX]]
(let [result (set-register :ar (bit-or (get-register :ar) (getter mode raw?)))]
(set-flags-nz result))) | ||||||||||
(defasm pha {:docs "Push Accumulator"}
[[0x48 3 1 Implied]]
(stack-push (get-register :ar))) | ||||||||||
(defasm php {:docs "Push Processor Status"}
[[0x08 3 1 Implied]]
(stack-push (bit-or (get-register :sp) 0x10))) | ||||||||||
(defasm pla {:docs "Pull Accumulator from Stack"}
[[0x68 4 1 Implied]]
(let [result (set-register :ar (stack-pop))]
(set-flags-nz result))) | ||||||||||
(defasm plp {:docs "Pull Processor Status from Stack"}
[[0x28 4 1 Implied]]
(let [result (bit-or (stack-pop) 0x20)]
(set-register :sr result)
(set-status-bit :break 0))) | ||||||||||
(defasm rol {:docs "Rotate Left"}
[[0x2a 2 1 Accumulator]
[0x26 5 2 ZeroPage]
[0x2e 6 3 Absolute]
[0x36 6 2 ZeroPageX]
[0x3e 7 3 AbsoluteX]]
(let [value (getter-mixed)
result (wrap-byte (rotate-byte value 1))]
(setter mode result)
(set-flags-if :carry #(bit-test value 7))
(set-flags-nz result))) | ||||||||||
(defasm ror {:docs "Rotate Right"}
[[0x66 5 2 ZeroPage]
[0x6a 2 1 Accumulator]
[0x6e 6 3 Absolute]
[0x76 6 2 ZeroPageX]
[0x7e 7 3 AbsoluteX]]
(let [value (getter-mixed)
result (wrap-byte (rotate-byte value -1))]
(setter mode result)
(set-flags-if :carry #(bit-test value 0))
(set-flags-nz result))) | ||||||||||
(defasm rti {:docs "Return from Interrupt"}
[[0x40 6 1 Implied]]
(set-register :sr (bit-or (stack-pop) 0x20))
(set-register :pc (stack-pop-word))) | ||||||||||
(defasm rts {:docs "Return from Subroutine" :track-pc? nil}
[[0x60 6 1 Implied]]
(set-register :pc (+ 1 (stack-pop-word)))) | ||||||||||
(defasm sbc {:docs "Subtract from Accumulator with Carry"}
[[0xe1 6 2 IndirectX]
[0xe5 3 2 ZeroPage]
[0xe9 2 2 Immediate]
[0xed 4 3 Absolute]
[0xf1 5 2 IndirectY]
[0xf5 4 2 ZeroPageX]
[0xf9 4 3 AbsoluteY]
[0xfd 4 3 AbsoluteX]]
(let [value (getter mode raw?)
result (- (get-register :ar) value
(bit-flip (status-bit :carry) 0))]
(set-flags-if :zero #(zero? (wrap-byte result))
:overflow #(overflow? result (bit-flip value 7)
(get-register :ar))
:negative #(bit-test result 7)
:carry #(not (neg? result)))
(set-register :ar (wrap-byte result)))) | ||||||||||
(defasm sec {:docs "Set Carry Flag"}
[[0x38 2 1 Implied]]
(set-status-bit :carry 1)) | ||||||||||
(defasm sed {:docs "Set Decimal Flag"}
[[0xf8 2 1 Implied]]
(set-status-bit :decimal 1)) | ||||||||||
(defasm sei {:docs "Set Interrupt Flag"}
[[0x78 2 1 Implied]]
(set-status-bit :interrupt 1)) | ||||||||||
(defasm sta {:docs "Store Accumulator" :raw? t}
[[0x81 6 2 IndirectX]
[0x85 3 2 ZeroPage]
[0x8d 4 3 Absolute]
[0x91 6 2 IndirectY]
[0x95 4 2 ZeroPageX]
[0x99 5 3 AbsoluteY]
[0x9d 5 3 AbsoluteX]]
(setter mode (get-register :ar))) | ||||||||||
(defasm stx {:docs "Store X register" :raw? t}
[[0x86 3 2 ZeroPage]
[0x8e 4 3 Absolute]
[0x96 4 2 ZeroPageY]]
(setter mode (get-register :xr))) | ||||||||||
(defasm sty {:docs "Store Y register" :raw? t}
[[0x84 3 2 ZeroPage]
[0x8c 4 3 Absolute]
[0x94 4 2 ZeroPageX]]
(setter mode (get-register :yr))) | ||||||||||
(defasm tax {:docs "Transfer Accumulator to X register"}
[[0xaa 2 1 Implied]]
(let [result (set-register :xr (get-register :ar))]
(set-flags-nz result))) | ||||||||||
(defasm tay {:docs "Transfer Accumulator to Y register"}
[[0xa8 2 1 Implied]]
(let [result (set-register :yr (get-register :ar))]
(set-flags-nz result))) | ||||||||||
(defasm tsx {:docs "Transfer Stack Pointer to X register"}
[[0xba 2 1 Implied]]
(let [result (set-register :xr (get-register :sp))]
(set-flags-nz result))) | ||||||||||
(defasm txa {:docs "Transfer X register to Accumulator"}
[[0x8a 2 1 Implied]]
(let [result (set-register :ar (get-register :xr))]
(set-flags-nz result))) | ||||||||||
(defasm txs {:docs "Transfer X register to Stack Pointer"}
[[0x9a 2 1 Implied]]
(set-register :sp (get-register :xr))) | ||||||||||
(defasm tya {:docs "Transfer Y register to Accumulator"}
[[0x98 2 1 Implied]]
(let [result (set-register :ar (get-register :yr))]
(set-flags-nz result))) | ||||||||||