├── resources
├── renderer.cljs.edn
├── nestest.nes
├── main.cljs.edn
├── package.json
└── index.html
├── boot.properties
├── src
├── cljsnes
│ ├── interrupts.cljs
│ ├── emulator.cljs
│ ├── assembler.cljs
│ ├── state.cljs
│ ├── arith.cljs
│ ├── display.cljs
│ ├── spec.cljs
│ ├── cartridge.cljs
│ ├── memory.cljs
│ ├── opcodes.cljs
│ ├── ppu.cljs
│ └── cpu.cljs
├── demos
│ ├── notifications.cljs
│ ├── shell.cljs
│ ├── filesystem.cljs
│ └── badge.cljs
└── app
│ ├── main.cljs
│ └── renderer.cljs
├── .gitignore
├── result.txt
├── README.md
└── test
└── cljsnes
└── all_test.cljs
/resources/renderer.cljs.edn:
--------------------------------------------------------------------------------
1 | {:require [app.renderer]
2 | :init-fns [app.renderer/init]}
3 |
--------------------------------------------------------------------------------
/resources/nestest.nes:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angusiguess/cljs-nes/HEAD/resources/nestest.nes
--------------------------------------------------------------------------------
/resources/main.cljs.edn:
--------------------------------------------------------------------------------
1 | {:require [app.main]
2 | :init-fns [app.main/init]
3 | :compiler-options {:target :nodejs}}
4 |
--------------------------------------------------------------------------------
/resources/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "electron-cljs",
3 | "version" : "0.1.0",
4 | "main" : "main.js"
5 | }
6 |
--------------------------------------------------------------------------------
/boot.properties:
--------------------------------------------------------------------------------
1 | #https://github.com/boot-clj/boot
2 | #Fri Jul 31 00:07:27 CEST 2015
3 | BOOT_CLOJURE_VERSION=1.9.0-alpha14
4 | BOOT_VERSION=2.7.1
5 |
--------------------------------------------------------------------------------
/src/cljsnes/interrupts.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.interrupts
2 | (:require [cljsnes.spec :as spec]))
3 |
4 | (defn check-interrupt [state]
5 | (get state :interrupt nil))
6 |
--------------------------------------------------------------------------------
/src/demos/notifications.cljs:
--------------------------------------------------------------------------------
1 | (ns demos.notifications)
2 |
3 | (comment
4 | (js/Notification. "Hello ClojuTRE!" (clj->js {:body "It's great to be here."}))
5 |
6 | )
7 |
--------------------------------------------------------------------------------
/src/demos/shell.cljs:
--------------------------------------------------------------------------------
1 | (ns demos.shell)
2 |
3 | (comment
4 | (def cp (js/require "child_process"))
5 |
6 | (.spawn cp "screencapture" #js ["-i" "screenshot.png"])
7 |
8 | )
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | pom.xml
2 | pom.xml.asc
3 | *jar
4 | /lib/
5 | /classes/
6 | /target/
7 | /checkouts/
8 | .lein-deps-sum
9 | .lein-repl-history
10 | .lein-plugins/
11 | .lein-failures
12 | .nrepl-port
13 | *.DS_Store
14 | /out/
15 |
--------------------------------------------------------------------------------
/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ClojureScript & Electron
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/cljsnes/emulator.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.emulator
2 | (:require [cljsnes.cartridge :as cart]
3 | [cljsnes.memory :as memory]
4 | [cljsnes.state :as state]
5 | [cljsnes.cpu :as cpu]))
6 |
7 | (defn init [path]
8 | (let [rom (cart/load-rom path)
9 | memory (memory/init-mem rom)]
10 | (state/init! memory)))
11 |
--------------------------------------------------------------------------------
/result.txt:
--------------------------------------------------------------------------------
1 |
2 | ;; ======================================================================
3 | ;; Testing with Node:
4 |
5 |
6 | Testing cljsnes.all-test
7 | Success!
8 |
9 | Ran 3 tests containing 3 assertions.
10 | 0 failures, 0 errors.
11 |
12 | Testing cljsnes.all-test
13 | Success!
14 |
15 | Ran 3 tests containing 3 assertions.
16 | 0 failures, 0 errors.
17 |
--------------------------------------------------------------------------------
/src/demos/filesystem.cljs:
--------------------------------------------------------------------------------
1 | (ns demos.filesystem)
2 |
3 | (comment
4 |
5 | (def fs (js/require "fs"))
6 |
7 | (def path (js/require "path"))
8 |
9 | (def current-dir (.resolve path "."))
10 |
11 | (.writeFile fs
12 | (str current-dir "/written-from-cljs.txt")
13 | "I didn't expect it to be so warm in Finland.")
14 |
15 | )
16 |
--------------------------------------------------------------------------------
/src/cljsnes/assembler.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.assembler
2 | (:require [clojure.string :as str]
3 | [clojure.spec :as s]))
4 |
5 | (s/def ::opcode #{"LDA"})
6 |
7 | (defn drop-comments [s]
8 | (take-while (fn [x] (not= ";" x)) s))
9 |
10 | (defn assemble [s]
11 | (let [lines (->> s
12 | str/split-lines
13 | (map (fn [s] (str/split s #"\s+"))))]
14 | (map drop-comments lines)))
15 |
--------------------------------------------------------------------------------
/src/demos/badge.cljs:
--------------------------------------------------------------------------------
1 | (ns demos.dock)
2 |
3 | (comment
4 | ;; Code for main process
5 | (def app (js/require "app"))
6 | (def ipc (js/require "ipc"))
7 |
8 | (.on ipc "bounce-dock" (fn [event arg]
9 | (.. app -dock bounce)))
10 | (.on ipc "set-badge" (fn [event arg]
11 | (.. app -dock (setBadge arg))))
12 |
13 | )
14 |
15 | (comment
16 | (def ipc (js/require "ipc"))
17 |
18 | (.send ipc "bounce-dock")
19 |
20 | (.send ipc "set-badge" "122")
21 |
22 | )
23 |
--------------------------------------------------------------------------------
/src/app/main.cljs:
--------------------------------------------------------------------------------
1 | (ns app.main)
2 |
3 | (def electron (js/require "electron"))
4 | (def app (.-app electron))
5 | (def BrowserWindow (.-BrowserWindow electron))
6 |
7 | (goog-define dev? false)
8 |
9 | (defn load-page
10 | "When compiling with `:none` the compiled JS that calls .loadURL is
11 | in a different place than it would be when compiling with optimizations
12 | that produce a single artifact (`:whitespace, :simple, :advanced`).
13 |
14 | Because of this we need to dispatch the loading based on the used
15 | optimizations, for this we defined `dev?` above that we can override
16 | at compile time using the `:clojure-defines` compiler option."
17 | [window]
18 | (if dev?
19 | (.loadURL window (str "file://" js/__dirname "/../../index.html"))
20 | (.loadURL window (str "file://" js/__dirname "/index.html"))))
21 |
22 | (def main-window (atom nil))
23 |
24 | (defn mk-window [w h frame? show?]
25 | (BrowserWindow. #js {:width w :height h :frame frame? :show show?}))
26 |
27 | (defn init-browser []
28 | (reset! main-window (mk-window 800 600 true true))
29 | (load-page @main-window)
30 | (if dev? (.openDevTools @main-window))
31 | (.on @main-window "closed" #(reset! main-window nil)))
32 |
33 | (defn init []
34 | (.on app "window-all-closed" #(when-not (= js/process.platform "darwin") (.quit app)))
35 | (.on app "ready" init-browser)
36 | (set! *main-cli-fn* (fn [] nil)))
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Electron & ClojureScript
2 |
3 | This repository contains an Electron app written in ClojureScript. In
4 | contrast to other examples out there this one uses ClojureScript for
5 | both parts of the Electron app: the main process and the renderer.
6 |
7 | #### Development
8 |
9 | For development you can start the compiler with `boot watch dev-build`.
10 | This will incrementally compile ClojureScript sources, push changes to your
11 | running Electron app and provide a REPL connection
12 | ([docs](https://github.com/adzerk-oss/boot-cljs-repl))into the app.
13 |
14 | You can start the electron process using
15 | [electron-prebuilt](https://github.com/mafintosh/electron-prebuilt) or
16 | using a downloaded `Electron.app` package:
17 |
18 | ```
19 | electron target/ # Do not omit the trailing '/'
20 | ```
21 |
22 | The `build.boot` file is annotated so you can exactly understand
23 | what's happening. When you make changes to the main process (the
24 | `app.main` namespace) you will need to restart the
25 | application. Probably automatic reloading could be added here too but
26 | changes are the main process are not as frequent so I didn't bother
27 | too much.
28 |
29 | #### Packaging the app
30 |
31 | The easiest way to package an electron app is by using
32 | [`electron-packager`](https://github.com/maxogden/electron-packager):
33 |
34 | ```
35 | electron-packager target/ MyApp --platform=darwin --arch=x64 --version=0.31.2
36 | ```
37 |
--------------------------------------------------------------------------------
/src/cljsnes/state.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.state
2 | (:require [cljsnes.spec :as spec]
3 | [reagent.ratom :as r]
4 | [cljsnes.cpu :as cpu]
5 | [cljsnes.ppu :as ppu]
6 | [clojure.spec :as s]))
7 |
8 | (defonce state (r/atom nil))
9 |
10 | (defonce save-state (r/atom nil))
11 |
12 | (defn make-buffer
13 | ([]
14 | (make-buffer 0x00))
15 | ([colour]
16 | (let [row (into [] (repeat 257 colour))]
17 | (into [] (repeat 225 row)))))
18 |
19 | (defn init-state [memory]
20 | {:order [:ppu :ppu :ppu :cpu]
21 | :cpu {:a 0
22 | :x 0
23 | :y 0
24 | :pc 0
25 | :s 0xFD
26 | :c 0
27 | :z 0
28 | :i 1
29 | :d 0
30 | :b 0
31 | :u 0
32 | :v 0
33 | :n 0
34 | :cycles 0
35 | :ticks 0
36 | :nmi 0
37 | :irq 0
38 | :reset 0}
39 | :memory memory
40 | :ppu {:cycle 0
41 | :line 0
42 | :write-address-low 0
43 | :write-address-high 0
44 | :write-started false
45 | :nmi-enable true}
46 | :display {:front (make-buffer 0x26)
47 | :back (make-buffer 0x27)}})
48 |
49 | (defn get-order [state]
50 | (get state :order))
51 |
52 | (defn step-order [state]
53 | (update state :order #(into []
54 | (take 4 (drop 1 (cycle %))))))
55 |
56 | (defn init-vectors [state]
57 | (let [cpu-mem (get state :memory)]
58 | (-> state
59 | (assoc-in [:cpu :nmi] (cpu/get-address cpu-mem 0xFFFA))
60 | (assoc-in [:cpu :reset] (cpu/get-address cpu-mem 0xFFFC))
61 | (assoc-in [:cpu :irq] (cpu/get-address cpu-mem 0xFFFE))
62 | (assoc-in [:cpu :pc] (cpu/get-address cpu-mem 0xFFFC)))))
63 |
64 | (defn init! [memory]
65 | (reset! state (init-vectors
66 | (init-state memory))))
67 |
68 | (defn step! []
69 | (let [next (first (get-order @state))]
70 | (if (= :ppu next) (swap! state (comp step-order ppu/step))
71 | (swap! state (comp step-order cpu/step)))))
72 |
73 | (defn save-state! []
74 | (reset! save-state @state))
75 |
76 | (defn load-state! []
77 | (reset! state @save-state))
78 |
--------------------------------------------------------------------------------
/src/cljsnes/arith.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.arith
2 | (:require [cljs.spec :as s])
3 | (:refer-clojure :exclude [inc dec]))
4 |
5 | (s/def ::byte (s/int-in 0 256))
6 |
7 | (s/def ::address (s/int-in 0 65536))
8 |
9 | (s/def ::carry-bit #{0 1})
10 |
11 | ;; Some addition fns
12 | (defn add
13 | ([] [0 0])
14 | ([x] [x 0])
15 | ([x y]
16 | (let [sum (+ x y)
17 | masked-sum (bit-and 0xFF sum)
18 | carry (if (bit-test sum 8) 1 0)]
19 | [masked-sum carry]))
20 | ([x y z]
21 | (let [[sum carry] (add x y)]
22 | (add sum z))))
23 |
24 | (defn inc [x]
25 | (add x 1))
26 |
27 | (defn sub
28 | ([] [0 0])
29 | ([x] [x])
30 | ([x y]
31 | (let [negated-y (bit-and 0xFF (+ 1 (bit-not y)))]
32 | (add x negated-y)))
33 | ([x y z]
34 | (let [[diff carry] (sub x y)]
35 | (sub diff z))))
36 |
37 | (defn dec [x]
38 | (sub x 1))
39 |
40 | (defn make-address [lower upper]
41 | (+ lower (bit-shift-left upper 8)))
42 |
43 | (defn address->bytes [address]
44 | (let [mask-high 0xFF00
45 | mask-low 0xFF]
46 | [(bit-and mask-low address)
47 | (bit-shift-right (bit-and mask-high address) 8)]))
48 |
49 | (s/fdef make-address
50 | :args (s/cat :lower ::byte :upper ::byte)
51 | :ret ::address)
52 |
53 | (s/fdef add
54 | :args (s/cat :x (s/? ::byte) :y (s/? ::byte) :z (s/? ::byte))
55 | :ret (s/cat :sum ::byte :carry ::carry-bit))
56 |
57 | (s/fdef inc
58 | :args (s/cat :x ::byte)
59 | :ret (s/cat :sum ::byte :carry ::carry-bit))
60 |
61 | ;; Negative?
62 |
63 | (defn neg-byte? [x]
64 | (bit-test x 7))
65 |
66 | (s/fdef neg-byte?
67 | :args (s/cat :x nat-int?)
68 | :ret boolean?)
69 |
70 |
71 |
72 | ;; Logical Shifts
73 |
74 | (defn asl [x]
75 | (let [shifted (bit-shift-left x 1)
76 | masked (bit-and shifted 255)
77 | carry (if (bit-test shifted 8) 1 0)]
78 | [masked carry]))
79 |
80 | (s/fdef asl
81 | :args (s/cat :x ::byte)
82 | :fn #(or (>= (-> % :ret :shifted) (-> % :args :x))
83 | (= (-> % :ret :carry) 1))
84 | :ret (s/cat :shifted ::byte :carry ::carry-bit))
85 |
86 | (defn lsr [x]
87 | (let [shifted (bit-shift-right x 1)
88 | carry (if (bit-test x 0) 1 0)]
89 | [shifted carry]))
90 |
91 | (s/fdef lsr
92 | :args (s/cat :x ::byte)
93 | :fn #(or (<= (-> % :ret :shifted) (-> % :args :x))
94 | (= (-> % :ret :carry) 1))
95 | :ret (s/cat :shifted ::byte :carry ::carry-bit))
96 |
97 | (defn unsigned->signed [x]
98 | (let [mask 128]
99 | (+ (- (bit-and x mask))
100 | (bit-and x (bit-not mask)))))
101 |
102 | ;; Logical ops
103 |
104 | (defn l-and [x y]
105 | (bit-and x y))
106 |
107 | (s/fdef l-and
108 | :args (s/cat :x ::byte :y ::byte)
109 | :ret ::byte)
110 |
111 | (defn l-or [x y]
112 | (bit-or x y))
113 |
114 | (s/fdef l-or
115 | :args (s/cat :x ::byte :y ::byte)
116 | :ret ::byte)
117 |
118 | (defn l-xor [x y]
119 | (bit-xor x y))
120 |
121 | (s/fdef l-xor
122 | :args (s/cat :x ::byte :y ::byte)
123 | :ret ::byte)
124 |
--------------------------------------------------------------------------------
/src/cljsnes/display.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.display
2 | (:require [clojure.pprint :as pprint]))
3 |
4 | (enable-console-print!)
5 |
6 | (def palette [#_0x00 [0x75 0x75 0x75]
7 | #_0x01 [0x27 0x1B 0x8F]
8 | #_0x02 [0x00 0x00 0xAB]
9 | #_0x03 [0x47 0x00 0x9F]
10 | #_0x04 [0x8F 0x00 0x77]
11 | #_0x05 [0xAB 0x00 0x13]
12 | #_0x06 [0xA7 0x00 0x00]
13 | #_0x07 [0x7F 0x0B 0x00]
14 | #_0x08 [0x43 0x2F 0x00]
15 | #_0x09 [0x00 0x47 0x00]
16 | #_0x0A [0x00 0x51 0x00]
17 | #_0x0B [0x00 0x3F 0x17]
18 | #_0x0C [0x1B 0x3F 0x5F]
19 | #_0x0D [0x00 0x00 0x00]
20 | #_0x0E [0x00 0x00 0x00]
21 | #_0x0F [0x00 0x00 0x00]
22 | #_0x10 [0xBC 0xBC 0xBC]
23 | #_0x11 [0x00 0x73 0xEF]
24 | #_0x12 [0x23 0x3B 0xEF]
25 | #_0x13 [0x83 0x00 0xF3]
26 | #_0x14 [0xBF 0x00 0xBF]
27 | #_0x15 [0xE7 0x00 0x5B]
28 | #_0x16 [0xDB 0x2B 0x00]
29 | #_0x17 [0xCB 0x4F 0x0F]
30 | #_0x18 [0x8B 0x73 0x00]
31 | #_0x19 [0x00 0x97 0x00]
32 | #_0x1A [0x00 0xAB 0x00]
33 | #_0x1B [0x00 0x93 0x3B]
34 | #_0x1C [0x00 0x83 0x8B]
35 | #_0x1D [0x00 0x00 0x00]
36 | #_0x1E [0x00 0x00 0x00]
37 | #_0x1F [0x00 0x00 0x00]
38 | #_0x20 [0xFF 0xFF 0xFF]
39 | #_0x21 [0x3F 0xBF 0xFF]
40 | #_0x22 [0x5F 0x97 0xFF]
41 | #_0x23 [0xA7 0x8B 0xFD]
42 | #_0x24 [0xF7 0x7B 0xFF]
43 | #_0x25 [0xFF 0x77 0xB7]
44 | #_0x26 [0xFF 0x77 0x63]
45 | #_0x27 [0xFF 0x9B 0x3B]
46 | #_0x28 [0xF3 0xBF 0x3F]
47 | #_0x29 [0x83 0xD3 0x13]
48 | #_0x2A [0x4F 0xDF 0x4B]
49 | #_0x2B [0x58 0xF8 0x98]
50 | #_0x2C [0x00 0xEB 0xDB]
51 | #_0x2D [0x00 0x00 0x00]
52 | #_0x2E [0x00 0x00 0x00]
53 | #_0x2F [0x00 0x00 0x00]
54 | #_0x30 [0xFF 0xFF 0xFF]
55 | #_0x31 [0xAB 0xE7 0xFF]
56 | #_0x32 [0xC7 0xD7 0xFF]
57 | #_0x33 [0xD7 0xCB 0xFF]
58 | #_0x34 [0xFF 0xC7 0xFF]
59 | #_0x35 [0xFF 0xC7 0xDB]
60 | #_0x36 [0xFF 0xBF 0xB3]
61 | #_0x37 [0xFF 0xDB 0xAB]
62 | #_0x38 [0xFF 0xE7 0xA3]
63 | #_0x39 [0xE3 0xFF 0xA3]
64 | #_0x3A [0xAB 0xF3 0xBF]
65 | #_0x3B [0xB3 0xFF 0xCF]
66 | #_0x3C [0x9F 0xFF 0xF3]
67 | #_0x3D [0x00 0x00 0x00]
68 | #_0x3E [0x00 0x00 0x00]
69 | #_0x3F [0x00 0x00 0x00]])
70 |
71 | (def palette-a (apply array (map #(apply array %) palette)))
72 |
73 | (defn render-frame [buffer ctx]
74 | (when ctx
75 | (let [image-data (.createImageData ctx 256 224)
76 | data (.-data image-data)]
77 | (loop [x 0
78 | y 0]
79 | (let [rgb (aget palette-a (get-in buffer [y x]))
80 | r (aget rgb 0)
81 | g (aget rgb 1)
82 | b (aget rgb 2)
83 | idx (* 4 (+ x (* y 256)))]
84 | (aset data idx r)
85 | (aset data (+ idx 1) g)
86 | (aset data (+ idx 2) b)
87 | (aset data (+ idx 3) 0xFF))
88 | (if (not (and (= x 256) (= y 224)))
89 | (recur (if (= x 256) 0 (inc x)) (if (do (= x 256)) (inc y) y))))
90 | (.putImageData ctx image-data 0 0))))
91 |
--------------------------------------------------------------------------------
/src/cljsnes/spec.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.spec
2 | (:require [clojure.spec :as s]
3 | [cljsnes.memory :as memory]
4 | [clojure.test.check.generators :as gen]))
5 |
6 | (s/def ::bit #{0 1})
7 |
8 | (s/def ::byte (s/int-in 0 256))
9 |
10 | (s/def ::address (s/int-in 0 65536))
11 |
12 | ;; CPU
13 |
14 | (s/def :cpu/a ::byte)
15 |
16 | (s/def :cpu/x ::byte)
17 |
18 | (s/def :cpu/y ::byte)
19 |
20 | (s/def :cpu/pc ::address)
21 |
22 | (s/def :cpu/s ::byte)
23 |
24 | (s/def :cpu/n ::bit)
25 |
26 | (s/def :cpu/c ::bit)
27 |
28 | (s/def :cpu/z ::bit)
29 |
30 | (s/def :cpu/i ::bit)
31 |
32 | (s/def :cpu/d ::bit)
33 |
34 | (s/def :cpu/u ::bit)
35 |
36 | (s/def :cpu/v ::bit)
37 |
38 | (s/def :cpu/n ::bit)
39 |
40 | (s/def :cpu/cycles (s/int-in 0 1000000000))
41 |
42 | (s/def :cpu/ticks (s/int-in 0 8192))
43 |
44 | (s/def :cpu/nmi ::byte)
45 |
46 | (s/def :cpu/irq ::byte)
47 |
48 | (s/def :cpu/reset ::byte)
49 |
50 | (s/def :cpu/status (s/keys :req-un [:cpu/n :cpu/v :cpu/d
51 | :cpu/i :cpu/z :cpu/c]))
52 |
53 | (s/def :state/cpu (s/keys :req-un [:cpu/a :cpu/x :cpu/pc :cpu/s :cpu/c
54 | :cpu/z :cpu/i :cpu/d :cpu/u
55 | :cpu/v :cpu/n :cpu/cycles :cpu/ticks
56 | :cpu/memory]))
57 |
58 | ;; PPU
59 |
60 | (s/def :ppu/frame-parity boolean?)
61 |
62 | (s/def :ppu/cycle (s/int-in 0 341))
63 |
64 | (s/def :ppu/scan-line (s/int-in 0 262))
65 |
66 | (s/def :ppu/frame nat-int?)
67 |
68 | ;; PPU CTRL $2000
69 |
70 | (s/def :ppu/nmi-enable boolean?)
71 |
72 | (s/def :ppu/ppu-master boolean?)
73 |
74 | (s/def :ppu/sprite-height boolean?)
75 |
76 | (s/def :ppu/background-tile-select boolean?)
77 |
78 | (s/def :ppu/increment-mode boolean?)
79 |
80 | (s/def :ppu/nametable-select #{0 1 2 3})
81 |
82 | ;; PPU MASK $2001
83 |
84 | (s/def :ppu/colour-emphasis-red boolean?)
85 |
86 | (s/def :ppu/colour-emphasis-green boolean?)
87 |
88 | (s/def :ppu/colour-emphasis-blue boolean?)
89 |
90 | (s/def :ppu/sprite-enable boolean?)
91 |
92 | (s/def :ppu/background-enable boolean?)
93 |
94 | (s/def :ppu/sprite-column-left boolean?)
95 |
96 | (s/def :ppu/background-left-column-enable boolean?)
97 |
98 | (s/def :ppu/greyscale boolean?)
99 |
100 | ;; PPU STATUS $2002
101 |
102 | (s/def :ppu/vblank boolean?)
103 |
104 | (s/def :ppu/sprite-zero-hit boolean?)
105 |
106 | (s/def :ppu/sprite-overflow boolean?)
107 |
108 | (s/def :ppu/oam-addr ::byte)
109 |
110 | (s/def :ppu/oam-data ::byte)
111 |
112 | (s/def :ppu/ppu-scroll ::byte)
113 |
114 | (s/def :ppu/ppu-addr ::byte)
115 |
116 | (s/def :ppu/ppu-data ::byte)
117 |
118 | (s/def :ppu/oam-data ::byte)
119 |
120 | (s/def :ppu/rgb (s/int-in 0x000000 0x1000000))
121 |
122 | (s/def :ppu/display (s/coll-of :ppu/rgb
123 | :count 61440
124 | :into []))
125 |
126 | (s/def ::ppu (s/keys :req-un [:ppu/cycle :ppu/scan-line :ppu/frame
127 | :ppu/nmi-enable :ppu/master :ppu/sprite-height
128 | :ppu/background-tile-select :ppu/increment-mode
129 | :ppu/colour-emphasis-red :ppu/colour-emphasis-green
130 | :ppu/colour-emphasis-blue :ppu/sprite-column-left
131 | :ppu/background-left-column-enable :ppu/vblank
132 | :ppu/sprite-zero-hit :ppu/sprite-overflow
133 | :ppu/oam-addr :ppu/oam-data :ppu/ppu-scroll
134 | :ppu/ppu-addr :ppu/ppu-data :ppu/oam-data]))
135 |
136 | ;; Interrupts
137 |
138 | (s/def :interrupt/interrupt #{:nmi :reset :irq})
139 |
140 | ;; State
141 |
142 | (s/def ::state (s/keys :req-un [:state/cpu :state/ppu]))
143 |
--------------------------------------------------------------------------------
/src/app/renderer.cljs:
--------------------------------------------------------------------------------
1 | (ns app.renderer
2 | (:require [reagent.core :as r]
3 | [cljs.spec :as s]
4 | [cljs.core.async :as a]
5 | [cljsnes.state :as state]
6 | [cljsnes.spec :as spec]
7 | [clojure.pprint :as pprint]
8 | [cljsnes.cpu :as cpu]
9 | [cljsnes.display :as display]
10 | [cljsnes.emulator :as emulator]
11 | [cljsnes.assembler :as assembler]
12 | [cljsnes.cartridge :as cartridge]
13 | [cljsnes.memory :as memory]
14 | [cljsnes.opcodes :as opcodes])
15 | (:require-macros [cljs.core.async.macros :refer [go go-loop]]))
16 |
17 | (enable-console-print!)
18 |
19 | (def front (r/cursor state/state [:display :front]))
20 |
21 | (def buffer (r/cursor state/state [:display]))
22 |
23 | (def ppu-cycles (r/cursor state/state [:ppu :cycle]))
24 |
25 | (def ppu-line (r/cursor state/state [:ppu :line]))
26 |
27 | (def cpu-cycles (r/cursor state/state [:cpu :cycles]))
28 |
29 | (def zero-flag (r/cursor state/state [:cpu :z]))
30 |
31 | (defonce stop-chan (a/chan))
32 |
33 | (defn swap-buffers []
34 | (swap! state/state update :display (fn [{:keys [front back] :as state}]
35 | (assoc state :front back
36 | :back front))))
37 |
38 | (defn swap-button []
39 | [:div [:input {:type "button"
40 | :value "Swap buffer!"
41 | :on-click #(swap-buffers)}]])
42 |
43 | (defn init-button []
44 | [:div [:input {:type "button"
45 | :value "Init State!"
46 | :on-click #(emulator/init "/Users/angusiguess/Downloads/Super Mario Bros. (Japan, USA).nes")}]])
47 |
48 | (defn step-button []
49 | [:div [:input {:type "button"
50 | :value "Step State!"
51 | :on-click #(state/step!)}]])
52 |
53 | (defn stop-button []
54 | [:div [:input {:type "button"
55 | :value "Stop state!"
56 | :on-click #(a/put! stop-chan :stop)}]])
57 |
58 | (defn run-button []
59 | [:div [:input {:type "button"
60 | :value "Run state!"
61 | :on-click #(go-loop []
62 | (let [[v ch] (a/alts! [stop-chan (go 1)])]
63 | (when (not= ch stop-chan)
64 | (state/step!)
65 | (recur))))}]])
66 |
67 | (defn save-state []
68 | [:div [:input {:type "button"
69 | :value "Save state"
70 | :on-click #(state/save-state!)}]])
71 |
72 | (defn load-state []
73 | [:div [:input {:type "button"
74 | :value "Load state"
75 | :on-click #(state/load-state!)}]])
76 |
77 | (defn cpu-cycle []
78 | [:div [:p "CPU Cycles: " @cpu-cycles]])
79 |
80 | (defn ppu-cycle []
81 | [:div [:p "PPU Cycles: " @ppu-cycles]
82 | [:p "PPU Line: " @ppu-line]])
83 |
84 | (defn cpu-pc []
85 | (let [cursor (r/cursor state/state [:cpu :pc])]
86 | [:div [:p "Program Counter: " (pprint/cl-format nil "~x" @cursor)]]))
87 |
88 | (defn cpu-registers []
89 | (let [a (r/cursor state/state [:cpu :a])
90 | x (r/cursor state/state [:cpu :x])
91 | y (r/cursor state/state [:cpu :y])
92 | i (r/cursor state/state [:cpu :i])
93 | s (r/cursor state/state [:cpu :s])
94 | z (r/cursor state/state [:cpu :z])]
95 | [:div [:p "A: " @a " X: " @x " Y: " @y " I: " @i]
96 | [:p "Stack Pointer: " @s]
97 | [:z "Zero? " @z]]))
98 |
99 | (defn display-component []
100 | @front
101 | (when-let [elem (.getElementById js/document "display")]
102 | (let [context (.getContext elem "2d")]
103 | (display/render-frame @front context)))
104 | [:div
105 | [:canvas {:width 256 :height 224 :id :display}]])
106 |
107 | (defn container []
108 | [:div
109 | (display-component)
110 | (swap-button)
111 | (init-button)
112 | (step-button)
113 | (save-state)
114 | (load-state)
115 | (run-button)
116 | (stop-button)
117 | (ppu-cycle)
118 | (cpu-cycle)
119 | (cpu-pc)
120 | (cpu-registers)])
121 |
122 | (defn init []
123 | (r/render-component [container]
124 | (.getElementById js/document "container"))
125 | (js/console.log "Starting Application"))
126 |
--------------------------------------------------------------------------------
/src/cljsnes/cartridge.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.cartridge
2 | (:require [cljs.nodejs :as node]
3 | [clojure.string :as str]
4 | [cljs.reader :as reader]
5 | [clojure.spec :as s]))
6 |
7 | (def fs (node/require "fs"))
8 |
9 | (def buffer (node/require "buffer"))
10 |
11 | (def util (node/require "util"))
12 |
13 | (def Buffer (.-Buffer buffer))
14 |
15 | (s/def :cart/rom-banks (s/int-in 2 9))
16 |
17 | (s/def :cart/vrom-banks (s/int-in 1 11))
18 |
19 | (s/def :cart/vrom-bank-bytes (s/coll-of (s/coll-of
20 | (s/int-in 0 256) :into [])
21 | :into []))
22 |
23 | (s/def :cart/rom-bank-bytes (s/coll-of (s/coll-of (s/int-in 0 256)
24 | :into [])
25 | :into []))
26 |
27 | (s/def :cart/vertical-mirroring boolean?)
28 |
29 |
30 |
31 |
32 | (defn stat-file [path]
33 | (.statSync fs path))
34 |
35 | (defn get-size [stat]
36 | (aget stat "size"))
37 |
38 | (defn open-file [path]
39 | (.openSync fs path "r"))
40 |
41 | (defn read-file-sync [path]
42 | (let []
43 | (.readFileSync fs path "utf-8")))
44 |
45 | (defn read-file [path]
46 | (let [fd (open-file path)
47 | size (-> path
48 | stat-file
49 | get-size)
50 | buf (.alloc Buffer size)]
51 | (.readSync fs fd buf 0 size)
52 | (into [] (es6-iterator-seq (.values buf)))))
53 |
54 | (defn get-opts-one [byte]
55 | (let [bits (map #(if (bit-test byte %) 1 0) (range 8))
56 | [vertical-mirroring battery-ram trainer four-screen
57 | & mapper-lower] bits]
58 | {:vertical-mirroring (pos? vertical-mirroring)
59 | :battery-ram (pos? battery-ram)
60 | :trainer (pos? trainer)
61 | :four-screen (pos? four-screen)
62 | :mapper-lower (into [] mapper-lower)}))
63 |
64 | (defn get-opts-two [byte]
65 | (let [bits (map #(if (bit-test byte %) 1 0) (range 8))
66 | [vs-system _ _ _ & mapper-upper] bits]
67 | {:vs-system (pos? vs-system)
68 | :mapper-upper (into [] mapper-upper)}))
69 |
70 | (defn pal? [byte]
71 | (bit-test byte 0))
72 |
73 | (defn get-mapper [cart]
74 | (let [lower (get cart :mapper-lower)
75 | upper (get cart :mapper-upper)]
76 | (assoc cart :mapper (reader/read-string (str
77 | "2r"
78 | (str/join upper)
79 | (str/join lower))))))
80 |
81 | (defn parse-headers
82 | "This table was cribbed from http://fms.komkon.org/EMUL8/NES.html#LABM
83 | Byte Contents
84 | ---------------------------------------------------------------------------
85 | 0-3 String \"NES^Z\" used to recognize .NES files.
86 | 4 Number of 16kB ROM banks.
87 | 5 Number of 8kB VROM banks.
88 | 6 bit 0 1 for vertical mirroring, 0 for horizontal mirroring.
89 | bit 1 1 for battery-backed RAM at $6000-$7FFF.
90 | bit 2 1 for a 512-byte trainer at $7000-$71FF.
91 | bit 3 1 for a four-screen VRAM layout.
92 | bit 4-7 Four lower bits of ROM Mapper Type.
93 | 7 bit 0 1 for VS-System cartridges.
94 | bit 1-3 Reserved, must be zeroes!
95 | bit 4-7 Four higher bits of ROM Mapper Type.
96 | 8 Number of 8kB RAM banks. For compatibility with the previous
97 | versions of the .NES format, assume 1x8kB RAM page when this
98 | byte is zero.
99 | 9 bit 0 1 for PAL cartridges, otherwise assume NTSC.
100 | bit 1-7 Reserved, must be zeroes!
101 | 10-15 Reserved, must be zeroes!
102 | 16-... ROM banks, in ascending order. If a trainer is present, its
103 | 512 bytes precede the ROM bank contents.
104 | ...-EOF VROM banks, in ascending order."
105 | [rom]
106 | (let [header (subvec rom 0 4)
107 | rom-banks (get rom 4)
108 | chr-banks (get rom 5)
109 | opts-one (get rom 6)
110 | opts-two (get rom 7)
111 | ram-banks (get rom 8)
112 | pal (pal? (get rom 9))
113 | rom-offset (+ 16 (* 16384 rom-banks))
114 | chr-offset (+ rom-offset (* 8192 chr-banks))]
115 | (assert (= header [78 69 83 26]))
116 | (-> {:header header
117 | :rom-banks rom-banks
118 | :chr-banks chr-banks
119 | :ram-banks ram-banks
120 | :pal pal
121 | :rom-bank-bytes (mapv (partial into [])
122 | (partition 16384
123 | (subvec rom 16 rom-offset)))
124 | :chr-bank-bytes (mapv (partial into [])
125 | (partition 8192
126 | (subvec rom
127 | rom-offset
128 | chr-offset)))
129 | :count (count rom)}
130 | (merge (get-opts-one opts-one)
131 | (get-opts-two opts-two))
132 | get-mapper)))
133 |
134 | (defn load-rom [path]
135 | (-> (read-file path)
136 | parse-headers))
137 |
--------------------------------------------------------------------------------
/test/cljsnes/all_test.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.all-test
2 | (:require [cljs.test :refer [deftest is testing run-tests]]
3 | [cljs.spec :as s]
4 | [cljs.pprint :as pprint]
5 | [cljs.spec.test :as stest]
6 | [cljsnes.state :as state]
7 | [cljsnes.cartridge :as cart]
8 | [cljsnes.memory :as memory]
9 | [cljsnes.arith :as arith]
10 | [cljsnes.opcodes :as opcodes]
11 | [cljsnes.ppu :as ppu]
12 | [cljsnes.cpu :as cpu]))
13 |
14 | (enable-console-print!)
15 |
16 | (defn summarize-results' [spec-check]
17 | (map (comp #(pprint/write % :stream nil) stest/abbrev-result) spec-check))
18 |
19 | (defn check' [spec-check]
20 | (is (empty? (->> spec-check
21 | (filter :failure))) (summarize-results' spec-check)))
22 |
23 |
24 | (deftest byte-arithmetic
25 | (check' (stest/check [`arith/add
26 | `arith/inc
27 | `arith/neg-byte?
28 | `arith/asl
29 | `arith/lsr
30 | `arith/l-and
31 | `arith/l-or
32 | `arith/l-xor
33 | `arith/make-address])))
34 |
35 | (deftest cpu-ops
36 | (check' (stest/check [#_`cpu/push-8
37 | #_`cpu/pop-8
38 | #_`cpu/push-16
39 | #_`cpu/status->byte
40 | `cpu/byte->status])))
41 |
42 | (deftest opcodes-conform
43 | (is (nil? (s/explain :opcode/ops opcodes/ops))))
44 |
45 | (deftest ppu-ticks-cycle
46 | (testing "cycle increment"
47 | (let [state (ppu/step {:ppu {:cycle 0 :line 0}})]
48 | (is (= 1 (get-in state [:ppu :cycle]))))
49 | (testing "cycle wrap"
50 | (let [state (ppu/step {:ppu {:cycle 340 :line 10}})]
51 | (is (= 0 (get-in state [:ppu :cycle])))
52 | (is (= 11 (get-in state [:ppu :line])))))
53 | (testing "cycle-and-line-wrap"
54 | (let [state (ppu/step {:ppu {:cycle 340 :line 261}})]
55 | (is (= 0 (get-in state [:ppu :cycle]))
56 | (= 0 (get-in state [:ppu :line])))))))
57 |
58 | (deftest cpu-stack-test
59 | (let [memory (memory/make-nrom [[]] [[]] [[]] [[]] false)
60 | state {:cpu {:s 0xFF}
61 | :memory memory}]
62 | (testing "push-8 pop-8"
63 | (let [pushed-state (cpu/push-8 state 0xBB)]
64 | (is (= 0xFE (get-in pushed-state [:cpu :s])))
65 | (is (= 0xBB (first (cpu/pop-8 pushed-state))))
66 | (is (= 0xFF (-> (cpu/pop-8 pushed-state)
67 | last
68 | (get-in [:cpu :s]))))))
69 | (testing "push-16 pop-16"
70 | (let [pushed-state (cpu/push-16 state 0xBBCC)]
71 | (is (= 0xFD (get-in pushed-state [:cpu :s])))
72 | (is (= 0xBBCC (first (cpu/pop-16 pushed-state))))
73 | (is (= 0xFF (-> (cpu/pop-16 pushed-state)
74 | last
75 | (get-in [:cpu :s]))))))))
76 |
77 | (deftest ppu-registers-shared
78 | (let [memory (memory/make-nrom [[]] [[]] [[]] [[]] false)]
79 | (testing "Shared registers"
80 | (doseq [address (range 0x2000 0x2008)]
81 | (is (= 0xBB (-> memory
82 | (memory/cpu-write address 0xBB)
83 | (memory/ppu-read address))))
84 | (is (= 0xBB (-> memory
85 | (memory/ppu-write address 0xBB)
86 | (memory/cpu-read address))))))))
87 |
88 | (deftest status-read
89 | (is (cpu/ppu-status-read? {:resolved-address 0x2002}))
90 | (is (false? (cpu/ppu-status-read? {}))))
91 |
92 | (deftest control-write
93 | (let [state {:memory (memory/make-nrom [[]] [[]] [[]] [[]] false)
94 | :ppu {:t 0}}]
95 | (is (cpu/ppu-control-write? {:resolved-address 0x2000}))
96 | (is (= 3072 (get-in (ppu/write-control state 0xFF) [:ppu :t])))))
97 |
98 | (deftest ppu-read-status
99 | (let [state {:memory (memory/make-nrom [[]] [[]] [[]] [[]] false)
100 | :ppu {:write-address-low 0xBB
101 | :write-address-high 0xCC
102 | :w true}}
103 | after-read (ppu/read-status state)]
104 | (is (= false (get-in after-read [:ppu :w])))))
105 |
106 | (deftest ppu-write-scroll
107 | (let [state {:memory (memory/make-nrom [[]] [[]] [[]] [[]] false)
108 | :ppu {:t 0
109 | :x 0
110 | :w false}}
111 | first-write (ppu/write-register-scroll state 0xFF)
112 | second-write (ppu/write-register-scroll first-write 0xFF)]
113 | (is (get-in first-write [:ppu :w]))
114 | (is (= 0x07 (get-in first-write [:ppu :x])))
115 | (is (= 31 (get-in first-write [:ppu :t])))
116 | (is (not (get-in second-write [:ppu :w])))
117 | (is (= 2r111001111111111 (get-in second-write [:ppu :t])))))
118 |
119 | (deftest ppu-write-address
120 | (let [state {:memory (memory/make-nrom [[]] [[]] [[]] [[]] false)
121 | :ppu {:t 0
122 | :x 0
123 | :w false}}
124 | first-write (ppu/write-register-address state 0xFF)
125 | second-write (ppu/write-register-address first-write 0xFF)]
126 | (is (get-in first-write [:ppu :w]))
127 | (is (= 2r011111100000000 (get-in first-write [:ppu :t])))
128 | (is (not (get-in second-write [:ppu :w])))
129 | (is (= 2r011111111111111 (get-in second-write [:ppu :t])))
130 | (is (= 2r011111111111111 (get-in second-write [:ppu :v])))))
131 |
132 | (deftest ppu-write-data
133 | (let [state {:memory (memory/make-nrom [[]] [[]] [[]] [[]] false)
134 | :ppu {:t 0
135 | :v 0x2009
136 | :x 0
137 | :w false}}
138 | first-write (ppu/write-register-data state 0xFF)
139 | memory (get first-write :memory)]
140 | (is (= 0x200A (get-in first-write [:ppu :v])))
141 | (is (= 0xFF (memory/ppu-read memory 0x2009)))))
142 |
143 | (deftest coarse-x
144 | (let [v 0]
145 | (testing "regular increment"
146 | (let [first-increment (ppu/coarse-x-increment v)]
147 | (is (= 1 first-increment))))
148 | (testing "tile increment"
149 | (let [tile-increment (ppu/coarse-x-increment 31)]
150 | (is (= 1024 tile-increment))))))
151 |
152 | (deftest nes-test-rom
153 | (let [rom (cart/load-rom "/Users/angusiguess/code/cljsnes/resources/nestest.nes")
154 | log (clojure.string/split-lines (cart/read-file-sync "/Users/angusiguess/code/cljsnes/resources/nestest.log"))
155 | memory (memory/init-mem rom)
156 | state (assoc-in (state/init-vectors (state/init-state memory)) [:cpu :pc] 0xC000)]
157 | (loop [log log
158 | state state
159 | line-no 1]
160 | (when log
161 | (let [log-line (-> log
162 | first
163 | (clojure.string/replace #"CYC:.*$" "")
164 | (clojure.string/split #"\s+"))
165 | my-line (clojure.string/split (cpu/log-step state) #"\s+")]
166 | (println line-no)
167 | (println log-line)
168 | (println my-line)
169 | (when (is (= log-line my-line))
170 | (recur (rest log) (cpu/test-step state) (inc line-no))))))))
171 |
--------------------------------------------------------------------------------
/src/cljsnes/memory.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.memory
2 | (:require [clojure.pprint :as pprint]))
3 |
4 | (defprotocol CPUMemory
5 | (cpu-read [_ addr])
6 | (cpu-write [_ addr byte]))
7 |
8 | (defprotocol PPUMemory
9 | (ppu-read [_ addr])
10 | (ppu-write [_ addr byte]))
11 |
12 | ;; CPU ROM mappers
13 |
14 | (defrecord Nrom [ram ppu-registers apu-io test-mode
15 | lower-bank upper-bank prg-ram chr vram mirroring palette-ram]
16 | CPUMemory
17 | (cpu-read [_ addr]
18 | (cond (<= 0x0 addr 0x1FFF) (get ram (mod addr 0x800))
19 | (<= 0x2000 addr 0x3FFF) (get ppu-registers (-> addr
20 | (- 0x2000)
21 | (mod 0x08)))
22 | (<= 0x4000 addr 0x4017) (get apu-io (- addr 0x4000))
23 | (<= 0x4018 addr 0x401F) (get test-mode (- addr 0x4018))
24 | (<= 0x6000 addr 0x7FFF) (get prg-ram (-> addr
25 | (- 0x6000)
26 | (mod 0x800)))
27 | (<= 0x8000 addr 0xBFFF) (get lower-bank (- addr 0x8000))
28 | (<= 0xC000 addr 0xFFFF) (get upper-bank (- addr 0xC000))
29 | :else (throw (js/Error (pprint/cl-format nil
30 | "Address ~x out of range" addr)))))
31 | (cpu-write [_ addr byte]
32 | (cond (<= 0x0 addr 0x1FFF) (Nrom.
33 | (assoc ram (mod addr 0x800) byte)
34 | ppu-registers
35 | apu-io
36 | test-mode
37 | lower-bank
38 | upper-bank
39 | prg-ram
40 | chr
41 | vram
42 | mirroring
43 | palette-ram)
44 | (<= 0x2000 addr 0x3FFF) (Nrom. ram
45 | (assoc ppu-registers
46 | (-> addr
47 | (- 0x2000)
48 | (mod 0x08))
49 | byte)
50 | apu-io
51 | test-mode
52 | lower-bank
53 | upper-bank
54 | prg-ram
55 | chr
56 | vram
57 | mirroring
58 | palette-ram)
59 | (<= 0x4000 addr 0x4017) (Nrom. ram
60 | ppu-registers
61 | (assoc apu-io (- addr 0x4000) byte)
62 | test-mode
63 | lower-bank
64 | upper-bank
65 | prg-ram
66 | chr
67 | vram
68 | mirroring
69 | palette-ram)
70 | (<= 0x4018 addr 0x401F) (Nrom. ram
71 | ppu-registers
72 | apu-io
73 | (assoc test-mode (- addr 0x4018) byte)
74 | lower-bank
75 | upper-bank
76 | prg-ram
77 | chr
78 | vram
79 | mirroring
80 | palette-ram)
81 | (<= 0x6000 addr 0x7FFF) (Nrom. ram
82 | ppu-registers
83 | apu-io
84 | test-mode
85 | lower-bank
86 | upper-bank
87 | (assoc prg-ram
88 | (-> addr
89 | (- 0x6000)
90 | (mod 0x800))
91 | byte)
92 | chr
93 | vram
94 | mirroring
95 | palette-ram)
96 | :else (throw (js/Error (pprint/cl-format nil
97 | "Address ~x out of range" addr)))))
98 | PPUMemory
99 | (ppu-read [_ addr]
100 | (let [vram-offset 0x2000
101 | palette-offset 0x3000]
102 | (cond (<= 0x0000 addr 0x1FFF) (get chr addr)
103 | (<= 0x2000 addr 0x2007) (get ppu-registers (- addr vram-offset))
104 | (<= 0x2008 addr 0x2FFF) (get vram (- addr vram-offset))
105 | (<= 0x3F00 addr 0x3FFF) (get palette-ram (mod (- addr palette-offset) 0x20))
106 | :else (throw (js/Error (pprint/cl-format nil
107 | "Address ~x out of range" addr))))))
108 | (ppu-write [_ addr byte]
109 | (let [vram-offset 0x2000
110 | palette-offset 0x3000]
111 | (cond
112 | (<= 0x2000 addr 0x2007) (Nrom. ram
113 | (assoc ppu-registers (- addr vram-offset) byte)
114 | apu-io
115 | test-mode
116 | lower-bank
117 | upper-bank
118 | prg-ram
119 | chr
120 | vram
121 | mirroring
122 | palette-ram)
123 | (<= 0x2008 addr 0x2FFF) (Nrom. ram
124 | ppu-registers
125 | apu-io
126 | test-mode
127 | lower-bank
128 | upper-bank
129 | prg-ram
130 | chr
131 | (assoc vram
132 | (- addr vram-offset)
133 | byte)
134 | mirroring
135 | palette-ram)
136 | (<= 0x3000 addr 0x3FFF) (Nrom. ram
137 | ppu-registers
138 | apu-io
139 | test-mode
140 | lower-bank
141 | upper-bank
142 | prg-ram
143 | chr
144 | vram
145 | mirroring
146 | (assoc palette-ram
147 | (mod (- addr palette-offset) 0x20)
148 | byte))
149 | :else (throw (js/Error (pprint/cl-format nil
150 | "Address ~x out of range" addr)))))))
151 |
152 | (defn make-nrom [lower-bank upper-bank prg-ram chr mirroring]
153 | (map->Nrom {:ram (into [] (repeat 0x2000 0))
154 | :ppu-registers (into [] (repeat 0x08 0))
155 | :apu-io (into [] (repeat 0x18 0))
156 | :test-mode (into [] (repeat 0x08 0))
157 | :lower-bank lower-bank
158 | :upper-bank upper-bank
159 | :prg-ram prg-ram
160 | :chr chr
161 | :vram (into [] (repeat 0xFFF 0))
162 | :mirroring mirroring
163 | :palette-ram (into [] (repeat 0x1F 0))}))
164 |
165 | (defn init-mem [rom]
166 | (case (:mapper rom)
167 | 0 (make-nrom (get-in rom [:rom-bank-bytes 0])
168 | (or (get-in rom [:rom-bank-bytes 1])
169 | (get-in rom [:rom-bank-bytes 0]))
170 | (get-in rom [:vrom-bank-bytes 0])
171 | (get-in rom [:chr-bank-bytes 0])
172 | (get rom :vertical-mirroring))))
173 |
--------------------------------------------------------------------------------
/src/cljsnes/opcodes.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.opcodes
2 | (:require [clojure.spec :as s]))
3 |
4 | (s/def :opcode/code (s/int-in 0x00 0xFF))
5 |
6 | (s/def :opcode/fn #{:adc :and :asl :bcc :bcs :beq :bit :bmi :bne :bpl :brk
7 | :bvc :bvs :clc :cld :cli :clv :cmp :cpx :cpy :dec :dex
8 | :dey :eor :inc :inx :iny :jmp :jsr :lda :ldx :ldy :lsr
9 | :nop :ora :pha :php :pla :plp :rol :ror :rti :rts :sbc
10 | :sec :sed :sei :sta :stx :sty :tax :tay :tsx :txa :txs
11 | :tya})
12 |
13 | (s/def :opcode/address-mode #{:immediate :zero :zero-x :zero-y
14 | :absolute :absolute-x :absolute-y
15 | :indirect :indirect-x :indirect-y
16 | :relative :implied :accumulator})
17 |
18 | (s/def :opcode/bytes-read (s/int-in 1 4))
19 |
20 | (s/def :opcode/cycles (s/int-in 1 8))
21 |
22 | (s/def :opcode/op (s/keys :req-un [:opcode/fn
23 | :opcode/address-mode :opcode/bytes-read
24 | :opcode/cycles]))
25 |
26 | (s/def :opcode/ops (s/map-of :opcode/code :opcode/op))
27 |
28 | (defn make-op
29 | [m code fn address-mode bytes-read cycles]
30 | (assert (nil? (get m code)))
31 | (assoc m code {:fn fn
32 | :address-mode address-mode
33 | :bytes-read bytes-read
34 | :cycles cycles}))
35 |
36 | (def ops (-> {}
37 | ;; ADC official
38 | (make-op 0x69 :adc :immediate 2 2)
39 | (make-op 0x65 :adc :zero 2 3)
40 | (make-op 0x75 :adc :zero-x 2 4)
41 | (make-op 0x6D :adc :absolute 3 4)
42 | (make-op 0x7D :adc :absolute-x 3 4)
43 | (make-op 0x79 :adc :absolute-y 3 4)
44 | (make-op 0x61 :adc :indirect-x 2 6)
45 | (make-op 0x71 :adc :indirect-y 2 5)
46 | ;; AND official
47 | (make-op 0x29 :and :immediate 2 2)
48 | (make-op 0x25 :and :zero 2 3)
49 | (make-op 0x35 :and :zero-x 2 4)
50 | (make-op 0x2D :and :absolute 3 4)
51 | (make-op 0x3D :and :absolute-x 3 4)
52 | (make-op 0x39 :and :absolute-y 3 4)
53 | (make-op 0x21 :and :indirect-x 2 6)
54 | (make-op 0x31 :and :indirect-y 2 5)
55 | ;; ASL official
56 | (make-op 0x0A :asl :accumulator 1 2)
57 | (make-op 0x06 :asl :zero 2 5)
58 | (make-op 0x16 :asl :zero-x 2 6)
59 | (make-op 0x0E :asl :absolute 3 6)
60 | (make-op 0x1E :asl :absolute-x 3 7)
61 | ;; BCC official
62 | (make-op 0x90 :bcc :relative 2 2)
63 | ;; BCS official
64 | (make-op 0xB0 :bcs :relative 2 2)
65 | ;; BEQ official
66 | (make-op 0xF0 :beq :relative 2 2)
67 | ;; BIT official
68 | (make-op 0x24 :bit :zero 2 3)
69 | (make-op 0x2C :bit :absolute 3 4)
70 | ;; BMI official
71 | (make-op 0x30 :bmi :relative 2 2)
72 | ;;BNE official
73 | (make-op 0xD0 :bne :relative 2 2)
74 | ;; BPL official
75 | (make-op 0x10 :bpl :relative 2 2)
76 | ;; BRK official
77 | (make-op 0x00 :brk :implied 1 7)
78 | ;; BVC official
79 | (make-op 0x50 :bvc :relative 2 2)
80 | ;; BVS official
81 | (make-op 0x70 :bvs :relative 2 2)
82 | ;; CLC official
83 | (make-op 0x18 :clc :implied 1 2)
84 | ;; CLD official
85 | (make-op 0xD8 :cld :implied 1 2)
86 | ;; CLI official
87 | (make-op 0x58 :cli :implied 1 2)
88 | ;; CLV official
89 | (make-op 0xB8 :clv :implied 1 2)
90 | ;; CMP official
91 | (make-op 0xC9 :cmp :immediate 2 2)
92 | (make-op 0xC5 :cmp :zero 2 3)
93 | (make-op 0xD5 :cmp :zero-x 2 4)
94 | (make-op 0xCD :cmp :absolute 3 4)
95 | (make-op 0xDD :cmp :absolute-x 3 4)
96 | (make-op 0xD9 :cmp :absolute-y 3 4)
97 | (make-op 0xC1 :cmp :indirect-x 2 6)
98 | (make-op 0xD1 :cmp :indirect-y 2 5)
99 | ;; CPX official
100 | (make-op 0xE0 :cpx :immediate 2 2)
101 | (make-op 0xE4 :cpx :zero 2 3)
102 | (make-op 0xEC :cpx :absolute 3 4)
103 | ;; CPY official
104 | (make-op 0xC0 :cpy :immediate 2 2)
105 | (make-op 0xC4 :cpy :zero 2 3)
106 | (make-op 0xCC :cpy :absolute 3 4)
107 | ;; DEC official
108 | (make-op 0xC6 :dec :zero 2 5)
109 | (make-op 0xD6 :dec :zero-x 2 6)
110 | (make-op 0xCE :dec :absolute 3 6)
111 | (make-op 0xDE :dec :absolute-x 3 7)
112 | ;; DEX official
113 | (make-op 0xCA :dex :implied 1 2)
114 | ;; DEY official
115 | (make-op 0x88 :dey :implied 1 2)
116 | ;; EOR official
117 | (make-op 0x49 :eor :immediate 2 2)
118 | (make-op 0x45 :eor :zero 2 3)
119 | (make-op 0x55 :eor :zero-x 2 4)
120 | (make-op 0x4D :eor :absolute 3 4)
121 | (make-op 0x5D :eor :absolute-x 3 4)
122 | (make-op 0x59 :eor :absolute-y 3 4)
123 | (make-op 0x41 :eor :indirect-x 2 6)
124 | (make-op 0x51 :eor :indirect-y 2 5)
125 | ;; INC official
126 | (make-op 0xE6 :inc :zero 2 5)
127 | (make-op 0xF6 :inc :zero-x 2 6)
128 | (make-op 0xEE :inc :absolute 3 6)
129 | (make-op 0xFE :inc :absolute-x 3 7)
130 | ;; INX official
131 | (make-op 0xE8 :inx :implied 1 2)
132 | ;; INY official
133 | (make-op 0xC8 :iny :implied 1 2)
134 | ;; JMP official
135 | (make-op 0x4C :jmp :absolute 3 3)
136 | (make-op 0x6C :jmp :indirect 3 5)
137 | ;; JSR official
138 | (make-op 0x20 :jsr :absolute 3 6)
139 | ;; LDA official
140 | (make-op 0xA9 :lda :immediate 2 2)
141 | (make-op 0xA5 :lda :zero 2 3)
142 | (make-op 0xB5 :lda :zero-x 2 4)
143 | (make-op 0xAD :lda :absolute 3 4)
144 | (make-op 0xBD :lda :absolute-x 3 4)
145 | (make-op 0xB9 :lda :absolute-y 3 4)
146 | (make-op 0xA1 :lda :indirect-x 2 6)
147 | (make-op 0xB1 :lda :indirect-y 2 5)
148 | ;; LDX official
149 | (make-op 0xA2 :ldx :immediate 2 2)
150 | (make-op 0xA6 :ldx :zero 2 3)
151 | (make-op 0xB6 :ldx :zero-y 2 4)
152 | (make-op 0xAE :ldx :absolute 3 4)
153 | (make-op 0xBE :ldx :absolute-y 3 4)
154 | ;; LDY official
155 | (make-op 0xA0 :ldy :immediate 2 2)
156 | (make-op 0xA4 :ldy :zero 2 3)
157 | (make-op 0xB4 :ldy :zero-x 2 4)
158 | (make-op 0xAC :ldy :absolute 3 4)
159 | (make-op 0xBC :ldy :absolute-x 3 4)
160 | ;; LSR official
161 | (make-op 0x4A :lsr :accumulator 1 2)
162 | (make-op 0x46 :lsr :zero 2 5)
163 | (make-op 0x56 :lsr :zero-x 3 6)
164 | (make-op 0x4E :lsr :absolute 3 6)
165 | (make-op 0x5E :lsr :absolute-x 3 7)
166 | ;; NOP official
167 | (make-op 0xEA :nop :implied 1 2)
168 | ;; ORA official
169 | (make-op 0x09 :ora :immediate 2 2)
170 | (make-op 0x05 :ora :zero 2 3)
171 | (make-op 0x15 :ora :zero-x 2 4)
172 | (make-op 0x0D :ora :absolute 3 4)
173 | (make-op 0x1D :ora :absolute-x 3 4)
174 | (make-op 0x19 :ora :absolute-y 3 4)
175 | (make-op 0x01 :ora :indirect-x 2 6)
176 | (make-op 0x11 :ora :indirect-y 2 5)
177 | ;; PHA official
178 | (make-op 0x48 :pha :implied 1 3)
179 | ;; PHP official
180 | (make-op 0x08 :php :implied 1 3)
181 | ;; PLA official
182 | (make-op 0x68 :pla :implied 1 4)
183 | ;; PLP official
184 | (make-op 0x28 :plp :implied 1 4)
185 | ;; ROL official
186 | (make-op 0x2A :rol :accumulator 1 2)
187 | (make-op 0x26 :rol :zero 2 5)
188 | (make-op 0x36 :rol :zero-x 2 6)
189 | (make-op 0x2E :rol :absolute 3 6)
190 | (make-op 0x3E :rol :absolute-x 3 7)
191 | ;; ROR official
192 | (make-op 0x6A :ror :accumulator 1 2)
193 | (make-op 0x66 :ror :zero 2 5)
194 | (make-op 0x76 :ror :zero-x 2 6)
195 | (make-op 0x6E :ror :absolute 3 6)
196 | (make-op 0x7E :ror :absolute-x 3 7)
197 | ;; RTI official
198 | (make-op 0x40 :rti :implied 1 6)
199 | ;; RTS official
200 | (make-op 0x60 :rts :implied 1 6)
201 | ;; SBC official
202 | (make-op 0xE9 :sbc :immediate 2 2)
203 | (make-op 0xE5 :sbc :zero 2 3)
204 | (make-op 0xF5 :sbc :zero-x 2 4)
205 | (make-op 0xED :sbc :absolute 3 4)
206 | (make-op 0xFD :sbc :absolute-x 3 4)
207 | (make-op 0xF9 :sbc :absolute-y 3 4)
208 | (make-op 0xE1 :sbc :indirect-x 2 6)
209 | (make-op 0xF1 :sbc :indirect-y 2 5)
210 | ;; SEC official
211 | (make-op 0x38 :sec :implied 1 2)
212 | ;; SED official
213 | (make-op 0xF8 :sed :implied 1 2)
214 | ;; SEI official
215 | (make-op 0x78 :sei :implied 1 2)
216 | ;; STA official
217 | (make-op 0x85 :sta :zero 2 3)
218 | (make-op 0x95 :sta :zero-x 2 4)
219 | (make-op 0x8D :sta :absolute 3 4)
220 | (make-op 0x9D :sta :absolute-x 3 5)
221 | (make-op 0x99 :sta :absolute-y 3 5)
222 | (make-op 0x81 :sta :indirect-x 2 6)
223 | (make-op 0x91 :sta :indirect-y 2 6)
224 | ;; STX official
225 | (make-op 0x86 :stx :zero 2 3)
226 | (make-op 0x96 :stx :zero-y 2 4)
227 | (make-op 0x8E :stx :absolute 3 4)
228 | ;; STY official
229 | (make-op 0x84 :sty :zero 2 3)
230 | (make-op 0x94 :sty :zero-x 2 4)
231 | (make-op 0x8C :sty :absolute 3 4)
232 | ;; TAX official
233 | (make-op 0xAA :tax :implied 1 2)
234 | ;; TAY official
235 | (make-op 0xA8 :tay :implied 1 2)
236 | ;; TSX official
237 | (make-op 0xBA :tsx :implied 1 2)
238 | ;; TXA official
239 | (make-op 0x8A :txa :implied 1 2)
240 | ;; TXS official
241 | (make-op 0x9A :txs :implied 1 2)
242 | ;; TYA official
243 | (make-op 0x98 :tya :implied 1 2)))
244 |
--------------------------------------------------------------------------------
/src/cljsnes/ppu.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.ppu
2 | (:require [clojure.spec :as s]
3 | [cljsnes.arith :as arith]
4 | [cljsnes.memory :as memory]))
5 |
6 | ;;$0000-1FFF is normally mapped by the cartridge to a CHR-ROM or CHR-RAM, often with a bank switching mechanism.
7 | ;; $2000-2FFF is normally mapped to the 2kB NES internal VRAM, providing 2 nametables with a mirroring configuration controlled by the cartridge, but it can be partly or fully remapped to RAM on the cartridge, allowing up to 4 simultaneous nametables.
8 | ; $3000-3EFF is usually a mirror of the 2kB region from $2000-2EFF. The PPU does not render from this address range, so this space has negligible utility.
9 | ;; $3F00-3FFF is not configurable, always mapped to the internal palette control.
10 |
11 | ;; We need a couple of internal registers for everything to work.
12 |
13 | ;; v is a register containing current vram address info.
14 | ;; t is a register containing temporary vram address info.
15 | ;; x is the fine x scroll, which allows for partial scrolling
16 | ;; w is the write toggle, which we use since some of our registers require two writes.
17 | ;; we'll write values to register flags too. To start let's phrase them as getters.
18 | ;; cycle is the horizontal render cycle
19 | ;; line is the scan line we're rendering
20 | ;; frame is a frame counter I'll use
21 | ;; I don't think I need to remove anything, I'll just make things consistent
22 | ;; with scroll
23 |
24 | (enable-console-print!)
25 |
26 | ;; Getters for internal state
27 |
28 | (defn get-v [state]
29 | (get-in state [:ppu :v]))
30 |
31 | (defn get-t [state]
32 | (get-in state [:ppu :t]))
33 |
34 | (defn get-x [state]
35 | (get-in state [:ppu :x]))
36 |
37 | (defn get-w [state]
38 | (get-in state [:ppu :w]))
39 |
40 | (defn get-memory [state]
41 | (let [memory (get state :memory)]
42 | (get state :memory)))
43 |
44 | (defn get-line [state]
45 | (get-in state [:ppu :line]))
46 |
47 | (defn get-nametable-byte [state]
48 | (get-in state [:ppu :nametable-byte]))
49 |
50 | (defn get-low-tile-byte [state]
51 | (get-in state [:ppu :low-tile-byte]))
52 |
53 | (defn get-high-tile-byte [state]
54 | (get-in state [:ppu :high-tile-byte]))
55 |
56 | (defn get-attribute-table-byte [state]
57 | (get-in state [:ppu :attribute-table-byte]))
58 |
59 | (defn get-tile-data [state]
60 | (get-in state [:ppu :tile-data]))
61 |
62 | (defn copy-y! [state]
63 | (let [v (get-v state)]
64 | (assoc-in state [:ppu :v] (bit-or (bit-and 0x841F v)
65 | (bit-and 0x7BE0 v)))))
66 |
67 | (defn copy-x! [state]
68 | (let [v (get-v state)]
69 | (assoc-in state [:ppu :v] (bit-or (bit-and v 0xFBE0)
70 | (bit-and v 0x041F)))))
71 |
72 | (defn inc-line [state]
73 | (update-in state [:ppu :line] inc))
74 |
75 | (defn zero-line [state]
76 | (assoc-in state [:ppu :line] 0))
77 |
78 | (defn get-cycle [state]
79 | (get-in state [:ppu :cycle]))
80 |
81 | (defn inc-cycle [state]
82 | (update-in state [:ppu :cycle] inc))
83 |
84 | (defn zero-cycle [state]
85 | (-> state
86 | (assoc-in [:ppu :cycle] 0)
87 | (update-in [:ppu :line] inc)))
88 |
89 | (defn get-flag-background-table [state]
90 | (let [memory (get-memory state)
91 | register (memory/ppu-read memory 0x2000)]
92 | (if (bit-test register 4) 1 0)))
93 |
94 | (defn get-flag-background-enabled [state]
95 | (let [memory (get-memory state)
96 | register (memory/ppu-read memory 0x2001)]
97 | (bit-test register 3)))
98 |
99 | (defn get-write-started [state]
100 | (get-in state [:ppu :write-started]))
101 |
102 | (defn set-vblank! [state]
103 | (let [memory (get-memory state)
104 | byte (memory/ppu-read memory 0x2002)
105 | back (get-in state [:display :back])
106 | front (get-in state [:display :front])]
107 | (-> state
108 | (assoc :memory (memory/ppu-write memory 0x2002 (bit-or 0x70 byte)))
109 | (assoc-in [:ppu :vblank] true)
110 | (assoc-in [:display :back] front)
111 | (assoc-in [:display :front] back))))
112 |
113 | (defn clear-vblank! [state]
114 | (let [memory (get-memory state)
115 | byte (memory/ppu-read memory 0x2002)]
116 | (-> state
117 | (assoc :memory (memory/ppu-write memory
118 | 0x2002
119 | (bit-or 0xE0 byte)))
120 | (assoc-in [:ppu :vblank] false))))
121 |
122 | (defn get-frame [state]
123 | (get-in state [:ppu :frame]))
124 |
125 | (defn nmi-enabled? [state]
126 | (get-in state [:ppu :nmi-enable]))
127 |
128 | ;; Register $2000, write only
129 | ;; PPU Control Register
130 | ;; VPHB SINN
131 | ;; V = VBLANK NMI, generate an interrupt if this is set.
132 | ;; P = PPU M/S, read backdrop from ext or write to ext
133 | ;; H = Sprite size, 0 = 8x8, 1 = 8x16
134 | ;; B = Background pattern table, 0 = $0000 1 = $1000
135 | ;; S = Sprite pattern table, 0 = $0000 1 = $1000, 8x16 ignores
136 | ;; I = VRAM increment, 0 adds 1, 1 adds 32 on vram increment
137 | ;; NN = base nametable address, 0, 1, 2, 3
138 |
139 |
140 | ;; t: ...BA.. ........ = d: ......BA
141 | (defn write-control [state byte]
142 | (let [nametable-bits (bit-shift-left (bit-and 0x03 byte) 10)
143 | t (get-t state)]
144 | (update-in state [:ppu :t] bit-or nametable-bits)))
145 |
146 | ;; Register $2002, read only
147 | ;; PPU status register
148 | ;; VSO. ....
149 |
150 | ;; V = Vblank has started
151 | ;; S = Sprite zero hit. Set when a nonzero pixel of sprite 0 overlaps with nonzero background
152 | ;; O = Sprite overflow, Set during sprite evaluation
153 |
154 | (defn read-status [state]
155 | (let [memory (get-memory state)
156 | status (memory/ppu-read memory 0x2000)
157 | v-cleared (bit-and 0x7F status)
158 | updated-memory (memory/ppu-write memory 0x2000 v-cleared)]
159 | (-> state
160 | (assoc-in [:ppu :w] false)
161 | (assoc :memory updated-memory))))
162 |
163 | ;; Register $2003, OAM address. TODO add this
164 |
165 | ;; Register $2004, OAM data. TODO add this.
166 |
167 | ;; Register $2005, PPUSCROLL
168 | ;; Write twice to set scroll x and then scroll y
169 |
170 | ;; If w is false
171 |
172 | ;;t: ....... ...HGFED = d: HGFED...
173 | ;;x: CBA = d: .....CBA
174 | ;;w: = 1
175 |
176 | ;; If w is true
177 | ;; t: CBA..HG FED..... = d: HGFEDCBA
178 | ;; w: = 0
179 |
180 | (defn- scroll-second-write [t byte]
181 | (let [t (bit-and 2r000110000011111 t)
182 | cba (bit-shift-left (bit-and 0x07 byte) 12)
183 | defgh (bit-shift-left (bit-and 0xF8 byte) 2)]
184 | (bit-or t cba defgh)))
185 |
186 | (defn write-register-scroll [state byte]
187 | (let [w (get-w state)
188 | for-t (bit-shift-right byte 3)
189 | for-x (bit-and byte 0x07)]
190 | (cond-> state
191 | (not w) (update-in [:ppu :t] #(-> % (bit-shift-right 5)
192 | (bit-shift-left 5)
193 | (bit-or for-t)))
194 | (not w) (assoc-in [:ppu :x] for-x)
195 | (not w) (assoc-in [:ppu :w] true)
196 | w (update-in [:ppu :t] scroll-second-write byte)
197 | w (assoc-in [:ppu :w] false))))
198 |
199 | ;; $2005 Address register first write
200 | ;;t: .FEDCBA ........ = d: ..FEDCBA
201 | ;;t: X...... ........ = 0
202 | ;;w: = 1
203 |
204 | ;;second write
205 | ;;t: ....... HGFEDCBA = d: HGFEDCBA
206 | ;;v = t
207 | ;;w: = 0
208 |
209 | (defn- first-address-write [t byte]
210 | (let [abcdef (-> byte (bit-and 0x3F) (bit-shift-left 8))]
211 | (bit-or t abcdef (bit-and 0x80FF t))))
212 |
213 | (defn- second-address-write [t byte]
214 | (bit-or t byte))
215 |
216 | (defn- copy-t-to-v [state]
217 | (assoc-in state [:ppu :v] (get-in state [:ppu :t])))
218 |
219 | (defn write-register-address [state byte]
220 | (let [w (get-w state)]
221 | (cond-> state
222 | (not w) (update-in [:ppu :t] first-address-write byte)
223 | (not w) (assoc-in [:ppu :w] true)
224 | w (update-in [:ppu :t] second-address-write byte)
225 | w copy-t-to-v
226 | w (assoc-in [:ppu :w] false))))
227 |
228 | (defn write-register-data [state byte]
229 | (let [memory (get-memory state)
230 | address (get-v state)
231 | increment-y (-> memory (memory/ppu-read 0x2000) (bit-test 3))]
232 | (cond-> state
233 | true (assoc :memory (memory/ppu-write memory address byte))
234 | increment-y (update-in [:ppu :v] + 32)
235 | (not increment-y) (update-in [:ppu :v] inc))))
236 |
237 | (defn get-ppu-mask [state]
238 | (memory/ppu-read (get-memory state) 0x2001))
239 |
240 | (defn- switch-vertical-nametable [v]
241 | (bit-and 0xFFFF (bit-xor v 0x0800)))
242 |
243 | (defn- apply-coarse-y [v y]
244 | (bit-or
245 | (bit-and v (bit-and 0xFFFF (bit-not 0x03E0)))
246 | (bit-shift-left y 5)))
247 |
248 | (defn increment-y-component [v]
249 | ;; If fine Y < 7, we'll increment it
250 | (let [coarse-y (bit-shift-right (bit-and v 0x03E0) 5)]
251 | (cond (not= (bit-and v 0x7000) 0x7000) (+ v 0x1000)
252 | (= 29 coarse-y) (-> v
253 | switch-vertical-nametable
254 | (apply-coarse-y 0))
255 | (= 31 coarse-y) (apply-coarse-y v 0)
256 | :else (apply-coarse-y v (inc coarse-y)))))
257 |
258 | (defn- zero-coarse-x [v]
259 | (bit-and 0xFFFF (bit-and v (bit-not 0x001F))))
260 |
261 | (defn- switch-horizontal-nametable [v]
262 | (bit-and 0xFFFF (bit-xor 0x0400 v)))
263 |
264 | (defn coarse-x-increment [v]
265 | (let [coarse-x (bit-and 0x001F v)]
266 | (if (= 31 coarse-x) (-> v zero-coarse-x switch-horizontal-nametable)
267 | (inc v))))
268 |
269 | (defn fetch-nametable-byte [state]
270 | (let [v (get-v state)
271 | memory (get-memory state)
272 | address (bit-or 0x2000 (bit-and v 0x0FFF))]
273 | (assoc-in state [:memory :nametable-byte] (memory/ppu-read memory address))))
274 |
275 | (defn fetch-attribute-table-byte [state]
276 | (let [v (get-v state)
277 | memory (get-memory state)
278 | address (bit-or 0x23C0
279 | (bit-and 0x0C00 v)
280 | (bit-and (bit-shift-right v 4) 0x38)
281 | (bit-and (bit-shift-right v 2) 0x07))
282 | shift (bit-or (bit-and 0x04 (bit-shift-right v 4))
283 | (bit-and 0x02 v))
284 | table-byte (bit-shift-left (bit-and
285 | (bit-shift-right (memory/ppu-read memory address) shift)
286 | 3)
287 | 2)]
288 | (assoc-in state [:ppu :attribute-table-byte] table-byte)))
289 |
290 | (defn fetch-low-tile-byte [state]
291 | (let [v (get-v state)
292 | memory (get-memory state)
293 | fine-y (bit-and (bit-shift-right v 12) 7)
294 | tile (get-nametable-byte state)
295 | background-table (get-flag-background-table state)
296 | address (+ (* 0x1000 background-table)
297 | (* tile 16)
298 | fine-y)]
299 | (assoc-in state [:ppu :low-tile-byte] (memory/ppu-read memory address))))
300 |
301 | (defn fetch-high-tile-byte [state]
302 | (let [v (get-v state)
303 | memory (get-memory state)
304 | fine-y (bit-and (bit-shift-right v 12) 7)
305 | tile (get-nametable-byte state)
306 | background-table (get-flag-background-table state)
307 | address (+ (* 0x1000 background-table)
308 | (* tile 16)
309 | fine-y)]
310 | (assoc-in state [:ppu :low-tile-byte] (memory/ppu-read memory (+ 8 address)))))
311 |
312 | (defn store-tile-data [state]
313 | (let [a (get-attribute-table-byte state)
314 | [data low high] (loop [i 0
315 | tile-data 0x0
316 | low-tile-byte (get-low-tile-byte state)
317 | high-tile-byte (get-high-tile-byte state)]
318 | (if (= 7 i) [tile-data low-tile-byte high-tile-byte]
319 | (let [p1 (bit-shift-right (bit-and low-tile-byte 0x80) 7)
320 | p2 (bit-shift-right (bit-and high-tile-byte 0x80) 6)]
321 | (recur (inc i)
322 | (-> tile-data
323 | (bit-shift-left 4)
324 | (bit-or a p1 p2))
325 | (bit-shift-left low-tile-byte 1)
326 | (bit-shift-left high-tile-byte 1)))))]
327 | (-> state
328 | (assoc-in [:ppu :tile-data] data)
329 | (assoc-in [:ppu :low-tile-byte] low)
330 | (assoc-in [:ppu :high-tile-byte] high))))
331 |
332 | (defn background-enabled? [state]
333 | (let [ppu-mask (get-ppu-mask state)]
334 | (bit-test ppu-mask 3)))
335 |
336 | (defn sprite-enabled? [state]
337 | (let [ppu-mask (get-ppu-mask state)]
338 | (bit-test ppu-mask 4)))
339 |
340 | (defn rendering-enabled? [state]
341 | (or (background-enabled? state)
342 | (sprite-enabled? state)))
343 |
344 | (defn nmi-interrupt? [state]
345 | (get-in state [:ppu :nmi-enable]))
346 |
347 | (defn even-frame? [state]
348 | (get-in state [:ppu :f]))
349 |
350 |
351 | (defn cycle-wrap? [state]
352 | (= 340 (get-cycle state)))
353 |
354 | (defn line-wrap? [state]
355 | (and (cycle-wrap? state)
356 | (= 261 (get-line state))))
357 |
358 | (defn v-blank? [state]
359 | (let [line (get-line state)
360 | cycle (get-cycle state)]
361 | (and (= line 241)
362 | (= cycle 1)
363 | (nmi-interrupt? state))))
364 |
365 | (defn clear-vblank? [state]
366 | (let [line (get-line state)
367 | cycle (get-cycle state)]
368 | (and (= 261 line)
369 | (= 1 cycle))))
370 |
371 | (defn visible-cycle [cycle]
372 | (<= 1 cycle 256))
373 |
374 | (defn pre-fetch-cycle [cycle]
375 | (<= 321 cycle 336))
376 |
377 | (defn fetch-cycle [cycle]
378 | (or (visible-cycle cycle)
379 | (pre-fetch-cycle cycle)))
380 |
381 | (defn visible-line [line]
382 | (< line 240))
383 |
384 | (defn render-line [line]
385 | (or (visible-line line)
386 | (= 261 line)))
387 |
388 | (def render-and-fetch? (comp fetch-cycle render-line))
389 |
390 | (defn do-fetch [state cycle])
391 |
392 | (defn tick! [state]
393 | (cond-> state
394 | (not (cycle-wrap? state)) inc-cycle
395 | (cycle-wrap? state) zero-cycle
396 | (line-wrap? state) zero-line))
397 |
398 | (defn trigger-vblank! [state]
399 | (println "Triggering Vblank")
400 | (-> state
401 | set-vblank!
402 | (assoc :interrupt :nmi)))
403 |
404 | (defn apply-fetch [state cycle]
405 | (case (mod cycle 8)
406 | ;; gross
407 | 0 (-> state
408 | store-tile-data
409 | (update-in [:ppu :v] coarse-x-increment))
410 | 1 (fetch-nametable-byte state)
411 | 3 (fetch-attribute-table-byte state)
412 | 5 (fetch-low-tile-byte state)
413 | 7 (fetch-high-tile-byte state)
414 | state))
415 |
416 | (defn background-pixel [state]
417 | (if (get-flag-background-enabled state)
418 | (let [tile-data (get-tile-data state)
419 | x (get-x state)
420 | shift-val (* 4 (- 7 x))]
421 | (-> tile-data
422 | (bit-shift-right shift-val)
423 | (bit-and 0x0F)))
424 | 0))
425 |
426 | (defn render-pixel [state]
427 | (let [x (dec (get-cycle state))
428 | y (get-line state)
429 | memory (get-memory state)
430 | ;; Omit left background later
431 | bg (background-pixel state)
432 | colour (memory/ppu-read memory
433 | (mod (+ 0x3F00 bg) 64))]
434 | (assoc-in state [:display :back y x] colour)))
435 |
436 | (defn render-background [state]
437 | ;; currently just for bg
438 | (let [line (get-line state)
439 | cycle (get-cycle state)]
440 | (if (get-flag-background-enabled state)
441 | (cond-> state
442 | ;; TODO
443 | (and (visible-line line)
444 | (visible-cycle cycle)) render-pixel
445 | (and (render-line line)
446 | (fetch-cycle cycle)) (apply-fetch cycle)
447 | (and (= line 261)
448 | (<= 280 cycle 304)) copy-y!
449 | (= 256 cycle) (update-in [:ppu :v] increment-y-component)
450 | (= 257 cycle) copy-x!)
451 | state)))
452 |
453 | (defn step [state]
454 | (let [cycle (get-cycle state)]
455 | (cond-> state
456 | true tick!
457 | true render-background
458 | (v-blank? state) trigger-vblank!
459 | (clear-vblank? state) clear-vblank!)))
460 |
--------------------------------------------------------------------------------
/src/cljsnes/cpu.cljs:
--------------------------------------------------------------------------------
1 | (ns cljsnes.cpu
2 | (:require [cljsnes.arith :as arith]
3 | [cljsnes.memory :as memory]
4 | [cljsnes.opcodes :as opcodes]
5 | [cljsnes.interrupts :as interrupts]
6 | [cljsnes.spec :as spec]
7 | [cljsnes.ppu :as ppu]
8 | [cljs.spec :as s]
9 | [cljs.core :as c]
10 | [cljs.pprint :as pprint]
11 | [cljs.reader :as reader])
12 | (:refer-clojure :exclude [and]))
13 |
14 | (enable-console-print!)
15 |
16 | ;; Memory map for state
17 | ;; $0000-$07FF $0800 2KB internal RAM
18 | ;; $0800-$0FFF $0800 Mirrors of $0000-$07FF
19 | ;; $1000-$17FF $0800
20 | ;; $1800-$1FFF $0800
21 | ;; $2000-$2007 $0008 NES PPU registers
22 | ;; $2008-$3FFF $1FF8 Mirrors of $2000-2007 (repeats every 8 bytes)
23 | ;; $4000-$4017 $0018 NES APU and I/O registers
24 | ;; $4018-$401F $0008 APU and I/O functionality that is normally disabled. See CPU Test Mode.
25 | ;; $4020-$FFFF $BFE0 Cartridge space: PRG ROM, PRG RAM, and mapper registers (See Note)
26 |
27 | ;; Registers
28 | ;; A is one byte
29 | ;; X and Y are one byte, not accumulators, mostly used for addressing
30 | ;; PC, program counter, references 65536 memory locations
31 | ;; S is stack pointer, one byte
32 | ;; P is the status register, each bit being a status flag.
33 | ;; we can represent each of the status flags if we like
34 | ;; C is carry, if last or addition or shift resulted in a carry
35 | ;; Z is zero, if last operation resulted in a zero.
36 | ;; Interrupt, can inhibit interrupts (0 IRQ and NMI, 1 just NMI)
37 | ;; D decimal, ignored
38 | ;; s used by stack copy
39 | ;; O is overflow, if ADC or SBC resulted in overflow
40 | ;; N is negative, set to bit 7 of last operation (sign bit)
41 |
42 | ;; So our state is: memory, registers, and status flags, everything
43 | ;; can operate on them.
44 |
45 |
46 | ;; Manipulating state
47 |
48 | (defn get-address [memory address]
49 | (let [lower (memory/cpu-read memory address)
50 | upper (memory/cpu-read memory (inc address))]
51 | (arith/make-address lower upper)))
52 |
53 | (defn get-address-wrap-upper [memory address]
54 | (let [lower-byte address
55 | upper-byte (mod (inc address) 0x100)
56 | lower (memory/cpu-read memory lower-byte)
57 | upper (memory/cpu-read memory upper-byte)]
58 | (arith/make-address lower upper)))
59 |
60 | ;; Special Reads
61 |
62 | (defn ppu-control-write? [{:keys [resolved-address] :as op}]
63 | (= resolved-address 0x2000))
64 |
65 | (defn ppu-status-read? [{:keys [resolved-address] :as op}]
66 | (= resolved-address 0x2002))
67 |
68 | (defn ppu-address-write? [{:keys [resolved-address] :as op}]
69 | (= resolved-address 0x2006))
70 |
71 | (defn ppu-data-write? [{:keys [resolved-address] :as op}]
72 | (= resolved-address 0x2007))
73 |
74 | (defn ppu-scroll-write? [{:keys [resolved-address] :as op}]
75 | (= resolved-address 0x2005))
76 |
77 | ;; Stack Manipulation
78 |
79 |
80 |
81 | (s/fdef push-8 :args (s/cat :state ::spec/state :byte ::spec/byte)
82 | :ret ::spec/state)
83 |
84 | (defn push-8 [{:keys [cpu memory] :as state} byte]
85 | (let [{:keys [s]} cpu]
86 | (-> state
87 | (assoc :memory (memory/cpu-write memory (+ 0x100 s) byte))
88 | (update-in [:cpu :s] dec))))
89 |
90 | (s/fdef pop-8 :args (s/cat :state ::spec/state)
91 | :ret ::spec/state)
92 |
93 | (defn pop-8 [{:keys [cpu memory] :as state}]
94 | (let [{:keys [s]} cpu
95 | to-pop (memory/cpu-read memory (+ 0x100 (inc s)))]
96 | [to-pop (update-in state [:cpu :s] inc)]))
97 |
98 | (s/fdef push-16 :args (s/cat :state ::spec/state :address ::spec/address)
99 | :ret ::spec/state)
100 |
101 | (defn push-16 [state address]
102 | (let [[low high] (arith/address->bytes address)]
103 | (-> state
104 | (push-8 high)
105 | (push-8 low))))
106 |
107 | (s/fdef pop-16 :args (s/cat :state ::spec/state)
108 | :ret ::spec/state)
109 |
110 | (defn pop-16 [state]
111 | (let [[low low-state] (pop-8 state)
112 | [high high-state] (pop-8 low-state)]
113 | [(arith/make-address low high) high-state]))
114 |
115 | (s/fdef status->byte :args (s/cat :state ::spec/state)
116 | :ret ::spec/byte)
117 |
118 | (defn status->byte [{:keys [cpu] :as state}]
119 | (let [{:keys [n v b d i z c]} cpu]
120 | (reader/read-string (str "2r" n v "1" b d i z c))))
121 |
122 | (s/fdef byte->status :args (s/cat :byte ::spec/byte)
123 | :ret :cpu/status)
124 |
125 | (defn bool->bit [bool]
126 | (if bool 1 0))
127 |
128 | (defn byte->status [status]
129 | {:n (bool->bit (bit-test status 7))
130 | :v (bool->bit (bit-test status 6))
131 | :b (bool->bit (bit-test status 4))
132 | :d (bool->bit (bit-test status 3))
133 | :i (bool->bit (bit-test status 2))
134 | :z (bool->bit (bit-test status 1))
135 | :c (bool->bit (bit-test status 0))})
136 |
137 | (defn get-memory [state]
138 | (get state :memory))
139 |
140 | (defn get-cycles [state]
141 | (get-in state [:cpu :cycles]))
142 |
143 | (defn set-ticks! [state ticks]
144 | (assoc-in state [:cpu :ticks] ticks))
145 |
146 | (defn inc-ticks! [state]
147 | (update-in state [:cpu :ticks] inc))
148 |
149 | (defn dec-ticks! [state]
150 | (update-in state [:cpu :ticks] dec))
151 |
152 | (defn inc-cycles! [state]
153 | (update-in state [:cpu :cycles] inc))
154 |
155 | ;; Addressing
156 |
157 | (defn page-crossed? [address offset]
158 | (let [mask 0x0F00]
159 | (not= (bit-and address mask)
160 | (bit-and (+ address offset) mask))))
161 |
162 | (defn read-next [memory pc]
163 | (memory/cpu-read memory (inc pc)))
164 |
165 | (defn get-sp [state]
166 | (get-in state [:cpu :s]))
167 |
168 | (defn set-sp-to [state v]
169 | (assoc-in state [:cpu :s] v))
170 |
171 | (defn get-pc [state]
172 | (get-in state [:cpu :pc]))
173 |
174 | (defn set-pc-to [state v]
175 | (assoc-in state [:cpu :pc] v))
176 |
177 | (defn get-a [state]
178 | (get-in state [:cpu :a]))
179 |
180 | (defn set-a-to [state v]
181 | (assoc-in state [:cpu :a] v))
182 |
183 | (defn dec-x [state]
184 | (update-in state [:cpu :x] dec))
185 |
186 | (defn inc-x [state]
187 | (update-in state [:cpu :x] dec))
188 |
189 | (defn get-x [state]
190 | (get-in state [:cpu :x]))
191 |
192 | (defn set-x-to [state v]
193 | (assoc-in state [:cpu :x] v))
194 |
195 | (defn get-y [state]
196 | (get-in state [:cpu :y]))
197 |
198 | (defn dec-y [state]
199 | (update-in state [:cpu :y] dec))
200 |
201 | (defn set-y-to [state v]
202 | (assoc-in state [:cpu :y] v))
203 |
204 | (defn inc-y [state]
205 | (update-in state [:cpu :y] inc))
206 |
207 | (defn get-interrupt [state]
208 | (get-in state [:cpu :i]))
209 |
210 | (defn set-interrupt-to [state v]
211 | (assoc-in state [:cpu :i] v))
212 |
213 | (defn set-interrupt [state]
214 | (set-interrupt-to state 1))
215 |
216 | (defn clear-interrupt [state]
217 | (set-interrupt-to state 0))
218 |
219 | (defn get-overflow [state]
220 | (get-in state [:cpu :v]))
221 |
222 | (defn set-overflow-to [state v]
223 | (assoc-in state [:cpu :v] v))
224 |
225 | (defn set-overflow [state]
226 | (set-overflow-to state 1))
227 |
228 | (defn clear-overflow [state]
229 | (set-overflow-to state 0))
230 |
231 | (defn get-carry [state]
232 | (get-in state [:cpu :c]))
233 |
234 | (defn set-carry [state]
235 | (assoc-in state [:cpu :c] 1))
236 |
237 | (defn set-carry-to [state v]
238 | (assoc-in state [:cpu :c] v))
239 |
240 | (defn clear-carry [state]
241 | (assoc-in state [:cpu :c] 0))
242 |
243 | (defn get-negative [state]
244 | (get-in state [:cpu :n]))
245 |
246 | (defn set-negative [state value]
247 | (let [to-set (if (arith/neg-byte? value) 1 0)]
248 | (assoc-in state [:cpu :n] to-set)))
249 |
250 | (defn set-negative-to [state v]
251 | (assoc-in state [:cpu :n] v))
252 |
253 | (defn get-zero [state]
254 | (get-in state [:cpu :z]))
255 |
256 | (defn set-zero [state value]
257 | (let [to-set (if (zero? value) 1 0)]
258 | (assoc-in state [:cpu :z] to-set)))
259 |
260 | (defn advance-pc [state offset]
261 | (update-in state [:cpu :pc] + offset))
262 |
263 | (defn set-decimal [state]
264 | (assoc-in state [:cpu :d] 1))
265 |
266 | (defn clear-decimal [state]
267 | (assoc-in state [:cpu :d] 0))
268 |
269 | (defn write-memory [state address byte]
270 | (update state :memory memory/cpu-write address byte))
271 |
272 | ;; Address modes
273 |
274 |
275 | (defmulti address (fn [state op]
276 | (:address-mode op)))
277 |
278 | (defmethod address :immediate [state op]
279 | (let [pc (get-pc state)
280 | memory (get-memory state)]
281 | (assoc op :resolved-arg (memory/cpu-read memory (inc pc)))))
282 |
283 | (defmethod address :zero [state op]
284 | (let [pc (get-pc state)
285 | memory (get-memory state)
286 | reference (memory/cpu-read memory (inc pc))
287 | value (memory/cpu-read memory reference)]
288 | (assoc op
289 | :resolved-arg value
290 | :resolved-address reference)))
291 |
292 | (defmethod address :zero-x [state op]
293 | (let [pc (get-pc state)
294 | x (get-x state)
295 | memory (get-memory state)
296 | address (memory/cpu-read memory (inc pc))
297 | [sum _] (arith/add address x)]
298 | (assoc op :resolved-arg (memory/cpu-read memory sum)
299 | :resolved-address sum)))
300 |
301 | (defmethod address :zero-y [state op]
302 | (let [pc (get-pc state)
303 | y (get-y state)
304 | memory (get-memory state)
305 | address (memory/cpu-read memory (inc pc))
306 | [sum _] (arith/add address y)]
307 | (assoc op :resolved-arg (memory/cpu-read memory sum)
308 | :resolved-address sum)))
309 |
310 | (defmethod address :absolute [state op]
311 | (let [pc (get-pc state)
312 | memory (get-memory state)
313 | address (get-address memory (inc pc))]
314 | (assoc op :resolved-arg (memory/cpu-read memory address)
315 | :resolved-address address)))
316 |
317 | (defmethod address :absolute-x [state op]
318 | (let [memory (get-memory state)
319 | pc (get-pc state)
320 | x (get-x state)
321 | address (get-address memory (inc pc))]
322 | (cond-> op
323 | (page-crossed? address x) (update :cycles inc)
324 | true (assoc :resolved-arg (memory/cpu-read memory (+ address x))
325 | :resolved-address (+ address x)))))
326 |
327 | (defmethod address :absolute-y [state op]
328 | (let [memory (get-memory state)
329 | pc (get-pc state)
330 | y (get-y state)
331 | address (get-address memory (inc pc))]
332 | (cond-> op
333 | (page-crossed? address y) (update :cycles inc)
334 | true (assoc :resolved-arg (memory/cpu-read memory (+ address y))
335 | :resolved-address (+ address y)))))
336 |
337 | (defmethod address :indirect [state op]
338 | (let [memory (get-memory state)
339 | pc (get-pc state)
340 | address (get-address memory (inc pc))
341 | indirect-address (get-address memory address)]
342 | (assoc op :resolved-arg (memory/cpu-read memory indirect-address)
343 | :resolved-address indirect-address)))
344 |
345 | (defmethod address :indirect-x [state op]
346 | (let [memory (get-memory state)
347 | pc (get-pc state)
348 | x (get-x state)
349 | address (memory/cpu-read memory (inc pc))
350 | offset-address (mod (+ x address) 0x100)
351 | indirect-address (get-address-wrap-upper memory offset-address)]
352 | (assoc op :resolved-arg (memory/cpu-read memory indirect-address)
353 | :resolved-address indirect-address)))
354 |
355 | (defmethod address :indirect-y [state op]
356 | (let [memory (get-memory state)
357 | pc (get-pc state)
358 | y (get-y state)
359 | address (get-address memory (inc pc))
360 | offset-address (+ y address)
361 | indirect-address (memory/cpu-read memory offset-address)]
362 | (cond-> op
363 | (page-crossed? pc offset-address) (update :cycles inc)
364 | true (assoc :resolved-arg (memory/cpu-read memory indirect-address)
365 | :resolved-address indirect-address))))
366 |
367 | (defmethod address :relative [state op]
368 | (let [memory (get-memory state)
369 | pc (get-pc state)
370 | offset (memory/cpu-read memory (inc pc))]
371 | (assoc op :resolved-arg offset
372 | :resolved-address (+ pc 2 offset))))
373 |
374 | (defmethod address :implied [state op]
375 | (assoc op :resolved-arg nil))
376 |
377 | (defmethod address :accumulator [state op]
378 | (let [a (get-a state)]
379 | (assoc op :resolved-arg a)))
380 |
381 | ;; Opcode implementations
382 |
383 | (defmulti exec-op (fn [state op] (:fn op)))
384 |
385 | (defmethod exec-op :adc [state {:keys [cycles bytes-read resolved-arg] :as op}]
386 | (let [a (get-a state)
387 | c (get-carry state)
388 | [sum carry] (arith/add a resolved-arg c)
389 | overflow (bool->bit (not= 0 (bit-and (bit-xor a sum)
390 | (bit-xor resolved-arg sum)
391 | 0x80)))]
392 | (cond-> state
393 | (ppu-status-read? op) ppu/read-status
394 | true (set-a-to sum)
395 | true (set-overflow-to overflow)
396 | true (set-carry-to carry)
397 | true (set-zero sum)
398 | true (set-negative sum)
399 | true (set-ticks! cycles)
400 | true (advance-pc bytes-read))))
401 |
402 | (defmethod exec-op :and [state {:keys [cycles bytes-read resolved-arg] :as op}]
403 | (let [a (get-a state)
404 | and-a (bit-and a resolved-arg)]
405 | (cond-> state
406 | (ppu-status-read? op) ppu/read-status
407 | true (set-zero and-a)
408 | true (set-negative and-a)
409 | true (set-ticks! cycles)
410 | true (set-a-to and-a)
411 | true (advance-pc bytes-read))))
412 |
413 | (defmethod exec-op :asl [state {:keys [cycles bytes-read
414 | resolved-arg resolved-address address-mode :as op]}]
415 | (let [memory (get-memory state)
416 | to-shift (if (= :accumulator address-mode) (get-a state)
417 | resolved-arg)
418 | [shifted carry] (arith/asl to-shift)]
419 | (cond-> state
420 | (ppu-status-read? op) ppu/read-status
421 | true (set-carry-to carry)
422 | true (set-zero shifted)
423 | true (set-negative shifted)
424 | true (set-ticks! cycles)
425 | (= :accumulator address-mode) (set-a-to shifted)
426 | (not= :accumulator address-mode) (update :memory memory/cpu-write resolved-address shifted)
427 | true (advance-pc bytes-read))))
428 |
429 | (defmethod exec-op :bcc [state
430 | {:keys [cycles bytes-read resolved-arg] :as op}]
431 | (let [c (get-carry state)
432 | pc (get-pc state)
433 | signed-arg (arith/unsigned->signed resolved-arg)]
434 | (cond-> state
435 | true (set-ticks! cycles)
436 | (zero? c) inc-ticks!
437 | (zero? c) (advance-pc signed-arg)
438 | true (advance-pc bytes-read)
439 | (page-crossed? pc signed-arg) inc-ticks!)))
440 |
441 | (defmethod exec-op :bcs [state
442 | {:keys [cycles bytes-read resolved-arg] :as op}]
443 | (let [c (get-carry state)
444 | pc (get-pc state)
445 | signed-arg (arith/unsigned->signed resolved-arg)]
446 | (cond-> state
447 | true (set-ticks! cycles)
448 | (pos? c) inc-ticks!
449 | (pos? c) (advance-pc signed-arg)
450 | true (advance-pc bytes-read)
451 | (page-crossed? pc signed-arg) inc-ticks!)))
452 |
453 | (defmethod exec-op :beq [state
454 | {:keys [cycles bytes-read resolved-arg] :as op}]
455 | (let [z (get-zero state)
456 | pc (get-pc state)
457 | signed-arg (arith/unsigned->signed resolved-arg)]
458 | (cond-> state
459 | true (set-ticks! cycles)
460 | (pos? z) inc-ticks!
461 | (pos? z) (advance-pc signed-arg)
462 | true (advance-pc bytes-read)
463 | (page-crossed? pc signed-arg) inc-ticks!)))
464 |
465 | (defmethod exec-op :bit [state
466 | {:keys [cycles bytes-read resolved-arg] :as op}]
467 | (let [a (get-a state)]
468 | (cond-> state
469 | true (set-ticks! cycles)
470 | true (set-zero (bit-and a resolved-arg))
471 | true (set-overflow-to (bool->bit (bit-test resolved-arg 6)))
472 | true (set-negative-to (bool->bit (bit-test resolved-arg 7)))
473 | true (advance-pc bytes-read))))
474 |
475 | (defmethod exec-op :bmi [state
476 | {:keys [cycles bytes-read resolved-arg] :as op}]
477 | (let [pc (get-pc state)
478 | n (get-negative state)
479 | signed-arg (arith/unsigned->signed resolved-arg)]
480 | (cond-> state
481 | true (set-ticks! cycles)
482 | (pos? n) inc-ticks!
483 | (pos? n) (advance-pc signed-arg)
484 | (c/and (pos? n) (page-crossed? pc signed-arg)) inc-ticks!
485 | true (advance-pc bytes-read))))
486 |
487 | (defmethod exec-op :bne [state
488 | {:keys [cycles bytes-read resolved-arg] :as op}]
489 | (let [z (get-zero state)
490 | pc (get-pc state)
491 | signed-arg (arith/unsigned->signed resolved-arg)]
492 | (cond-> state
493 | true (set-ticks! cycles)
494 | (zero? z) inc-ticks!
495 | (zero? z) (advance-pc signed-arg)
496 | true (advance-pc bytes-read)
497 | (c/and (zero? z) (page-crossed? pc signed-arg)) inc-ticks!)))
498 |
499 | (defmethod exec-op :bpl [state
500 | {:keys [cycles bytes-read resolved-arg] :as op}]
501 | (let [pc (get-pc state)
502 | n (get-negative state)
503 | signed-arg (arith/unsigned->signed resolved-arg)]
504 | (cond-> state
505 | true (set-ticks! cycles)
506 | (zero? n) inc-ticks!
507 | (zero? n) (advance-pc signed-arg)
508 | (c/and (zero? n) (page-crossed? pc signed-arg)) inc-ticks!
509 | true (advance-pc bytes-read))))
510 |
511 | (defmethod exec-op :brk [state
512 | {:keys [cycles bytes-read] :as op}]
513 | (let [pc (get-pc state)]
514 | (-> state
515 | (push-16 pc)
516 | (push-8 (status->byte state))
517 | (set-pc-to (get-in state [:cpu :irq]))
518 | (set-ticks! 7))))
519 |
520 | (defmethod exec-op :bvc [state
521 | {:keys [cycles bytes-read resolved-arg] :as op}]
522 | (let [pc (get-pc state)
523 | v (get-overflow state)
524 | signed-arg (arith/unsigned->signed resolved-arg)]
525 | (cond-> state
526 | true (set-ticks! cycles)
527 | (zero? v) (advance-pc (+ bytes-read signed-arg))
528 | (zero? v) inc-ticks!
529 | (c/and (zero? v) (page-crossed? pc signed-arg)) inc-ticks!
530 | (pos? v) (advance-pc bytes-read))))
531 |
532 | (defmethod exec-op :bvs [state
533 | {:keys [cycles bytes-read resolved-arg] :as op}]
534 | (let [pc (get-pc state)
535 | v (get-overflow state)
536 | signed-arg (arith/unsigned->signed resolved-arg)]
537 | (cond-> state
538 | true (set-ticks! cycles)
539 | (pos? v) (advance-pc (+ bytes-read signed-arg))
540 | (pos? v) inc-ticks!
541 | (c/and (pos? v) (page-crossed? pc signed-arg)) inc-ticks!
542 | (zero? v) (advance-pc bytes-read))))
543 |
544 | (defmethod exec-op :clc [state {:keys [cycles bytes-read] :as op}]
545 | (-> state
546 | clear-carry
547 | (advance-pc bytes-read)
548 | (set-ticks! cycles)))
549 |
550 | (defmethod exec-op :cld [state {:keys [cycles bytes-read] :as op}]
551 | (-> state
552 | clear-decimal
553 | (advance-pc bytes-read)
554 | (set-ticks! cycles)))
555 |
556 | (defmethod exec-op :cli [state {:keys [cycles bytes-read] :as op}]
557 | (-> state
558 | clear-interrupt
559 | (advance-pc bytes-read)
560 | (set-ticks! cycles)))
561 |
562 | (defmethod exec-op :clv [state {:keys [cycles bytes-read] :as op}]
563 | (-> state
564 | clear-overflow
565 | (advance-pc bytes-read)
566 | (set-ticks! cycles)))
567 |
568 | (defmethod exec-op :cmp [state {:keys [cycles bytes-read resolved-arg] :as op}]
569 | (let [a (get-a state)
570 | [diff carry] (arith/sub a resolved-arg)]
571 | (cond-> state
572 | true (set-negative diff)
573 | true (set-zero diff)
574 | (<= resolved-arg a) set-carry
575 | (< a resolved-arg) clear-carry
576 | true (set-ticks! cycles)
577 | true (advance-pc bytes-read))))
578 |
579 | (defmethod exec-op :cpx [state {:keys [cycles bytes-read resolved-arg] :as op}]
580 | (let [x (get-x state)
581 | [diff carry] (arith/sub x resolved-arg)]
582 | (cond-> state
583 | true (set-negative diff)
584 | true (set-zero diff)
585 | (<= resolved-arg x) set-carry
586 | (< x resolved-arg) clear-carry
587 | true (set-ticks! cycles)
588 | true (advance-pc bytes-read))))
589 |
590 | (defmethod exec-op :cpy [state {:keys [cycles bytes-read resolved-arg] :as op}]
591 | (let [y (get-y state)
592 | [diff carry] (arith/sub y resolved-arg)]
593 | (cond-> state
594 | true (set-negative diff)
595 | true (set-zero diff)
596 | (<= resolved-arg y) set-carry
597 | (< y resolved-arg) clear-carry
598 | true (set-ticks! cycles)
599 | true (advance-pc bytes-read))))
600 |
601 | (defmethod exec-op :dec [state
602 | {:keys [cycles bytes-read
603 | resolved-arg resolved-address] :as op}]
604 | (let [memory (get-memory state)
605 | [decced _] (arith/dec resolved-arg)]
606 | (cond-> state
607 | true (write-memory resolved-address decced)
608 | true (set-zero decced)
609 | true (set-negative decced)
610 | true (set-ticks! cycles)
611 | true (advance-pc bytes-read))))
612 |
613 | (defmethod exec-op :dex [state
614 | {:keys [cycles bytes-read] :as op}]
615 | (let [x (get-x state)
616 | [decced _] (arith/dec x)]
617 | (cond-> state
618 | true (set-x-to decced)
619 | true (set-zero decced)
620 | true (set-negative decced)
621 | true (set-ticks! cycles)
622 | true (advance-pc bytes-read))))
623 |
624 | (defmethod exec-op :dey [state
625 | {:keys [cycles bytes-read] :as op}]
626 | (let [y (get-y state)
627 | [decced _] (arith/dec y)]
628 | (cond-> state
629 | true (set-y-to decced)
630 | true (set-zero decced)
631 | true (set-negative decced)
632 | true (set-ticks! cycles)
633 | true (advance-pc bytes-read))))
634 |
635 | (defmethod exec-op :eor [state
636 | {:keys [cycles bytes-read resolved-arg] :as op}]
637 | (let [a (get-a state)
638 | x-or (bit-xor a resolved-arg)]
639 | (cond-> state
640 | true (set-a-to x-or)
641 | true (set-zero x-or)
642 | true (set-negative x-or)
643 | true (set-ticks! cycles)
644 | true (advance-pc bytes-read))))
645 |
646 | (defmethod exec-op :inc [state
647 | {:keys [cycles bytes-read
648 | resolved-arg resolved-address] :as op}]
649 | (let [memory (get-memory state)
650 | [inced _] (arith/inc resolved-arg)]
651 | (cond-> state
652 | true (write-memory resolved-address inced)
653 | true (set-zero inced)
654 | true (set-negative inced)
655 | true (set-ticks! cycles)
656 | true (advance-pc bytes-read))))
657 |
658 | (defmethod exec-op :inx [state
659 | {:keys [cycles bytes-read] :as op}]
660 | (let [x (get-x state)
661 | [inced _] (arith/inc x)]
662 | (cond-> state
663 | true (set-x-to inced)
664 | true (set-zero inced)
665 | true (set-negative inced)
666 | true (set-ticks! cycles)
667 | true (advance-pc bytes-read))))
668 |
669 | (defmethod exec-op :iny [state
670 | {:keys [cycles bytes-read] :as op}]
671 | (let [y (get-y state)
672 | [inced _] (arith/inc y)]
673 | (cond-> state
674 | true (set-y-to inced)
675 | true (set-zero inced)
676 | true (set-negative inced)
677 | true (set-ticks! cycles)
678 | true (advance-pc bytes-read))))
679 |
680 | (defmethod exec-op :jmp [state
681 | {:keys [cycles bytes-read resolved-address] :as op}]
682 | ;; fix weird paging error
683 | (-> state
684 | (set-ticks! cycles)
685 | (assoc-in [:cpu :pc] resolved-address)))
686 |
687 | (defmethod exec-op :jsr [state
688 | {:keys [cycles bytes-read resolved-arg
689 | resolved-address] :as op}]
690 | (let [pc (get-pc state)
691 | return (+ pc bytes-read)
692 | memory (get-memory state)]
693 | (-> state
694 | (push-16 (dec return))
695 | (set-pc-to resolved-address)
696 | (set-ticks! cycles))))
697 |
698 | (defmethod exec-op :lda [state
699 | {:keys [cycles bytes-read resolved-arg] :as op}]
700 | (cond-> state
701 | (ppu-status-read? op) ppu/read-status
702 | true (set-a-to resolved-arg)
703 | true (set-zero resolved-arg)
704 | true (set-negative resolved-arg)
705 | true (set-ticks! cycles)
706 | true (advance-pc bytes-read)))
707 |
708 | (defmethod exec-op :ldx [state
709 | {:keys [cycles bytes-read resolved-arg] :as op}]
710 | (cond-> state
711 | (ppu-status-read? op) ppu/read-status
712 | true (set-x-to resolved-arg)
713 | true (set-zero resolved-arg)
714 | true (set-negative resolved-arg)
715 | true (set-ticks! cycles)
716 | true (advance-pc bytes-read)))
717 |
718 | (defmethod exec-op :ldy [state
719 | {:keys [cycles bytes-read resolved-arg] :as op}]
720 | (cond-> state
721 | (ppu-status-read? op) ppu/read-status
722 | true (set-y-to resolved-arg)
723 | true (set-zero resolved-arg)
724 | true (set-negative resolved-arg)
725 | true (set-ticks! cycles)
726 | true (advance-pc bytes-read)))
727 |
728 | (defmethod exec-op :lsr [state
729 | {:keys [cycles bytes-read resolved-arg
730 | resolved-address address-mode] :as op}]
731 | (let [memory (get-memory state)
732 | to-shift (if (= :accumulator address-mode) (get-a state) resolved-arg)
733 | [shifted carry] (arith/lsr to-shift)]
734 | (cond-> state
735 | true (set-carry-to carry)
736 | true (set-zero shifted)
737 | true (set-negative shifted)
738 | true (set-ticks! cycles)
739 | true (advance-pc bytes-read)
740 | (= :accumulator address-mode) (set-a-to shifted)
741 | (not= :accumulator address-mode) (write-memory resolved-address shifted))))
742 |
743 | (defmethod exec-op :nop [state {:keys [cycles bytes-read]}]
744 | (-> state
745 | (set-ticks! cycles)
746 | (advance-pc bytes-read)))
747 |
748 | (defmethod exec-op :ora [state {:keys [cycles bytes-read resolved-arg]}]
749 | (let [a (get-a state)
750 | ored-a (bit-or a resolved-arg)]
751 | (cond-> state
752 | true (set-zero ored-a)
753 | true (set-negative ored-a)
754 | true (set-a-to ored-a)
755 | true (set-ticks! cycles)
756 | true (advance-pc bytes-read))))
757 |
758 | (defmethod exec-op :pha [state {:keys [cycles bytes-read]}]
759 | (let [a (get-a state)]
760 | (-> state
761 | (push-8 a)
762 | (set-ticks! cycles)
763 | (advance-pc bytes-read))))
764 |
765 | (defmethod exec-op :php [state {:keys [cycles bytes-read]}]
766 | (-> state
767 | (push-8 (bit-or 0x10 (status->byte state)))
768 | (set-ticks! cycles)
769 | (advance-pc bytes-read)))
770 |
771 | (defmethod exec-op :pla [state {:keys [cycles bytes-read]}]
772 | (let [[pop popped-state] (pop-8 state)]
773 | (-> popped-state
774 | (set-a-to pop)
775 | (set-zero pop)
776 | (set-negative pop)
777 | (set-ticks! cycles)
778 | (advance-pc bytes-read))))
779 |
780 | (defmethod exec-op :plp [state {:keys [cycles bytes-read]}]
781 | (let [[pop popped-state] (pop-8 state)
782 | adjusted-pop (bit-or 0x20 (bit-and pop 0xEF)) ;; nestest needs this
783 | flags (byte->status adjusted-pop)]
784 | (-> popped-state
785 | (update :cpu merge flags)
786 | (set-ticks! cycles)
787 | (advance-pc bytes-read))))
788 |
789 | (defmethod exec-op :rol [state
790 | {:keys [cycles bytes-read address-mode
791 | resolved-arg resolved-address] :as op}]
792 | (let [[shifted carry] (arith/asl resolved-arg)
793 | c (get-carry state)
794 | rotated (+ shifted c)
795 | memory (get-memory state)]
796 | (cond-> state
797 | (= :accumulator address-mode) (set-a-to rotated)
798 | (not= :accumulator address-mode) (write-memory resolved-address rotated)
799 | true (set-carry-to carry)
800 | true (set-zero rotated)
801 | true (set-negative rotated)
802 | true (set-ticks! cycles)
803 | true (advance-pc bytes-read))))
804 |
805 | (defmethod exec-op :ror [state
806 | {:keys [cycles bytes-read address-mode
807 | resolved-arg resolved-address] :as op}]
808 | (let [[shifted carry] (arith/lsr resolved-arg)
809 | c (get-carry state)
810 | rotated (+ shifted (* 128 c))
811 | memory (get-memory state)]
812 | (cond-> state
813 | (= :accumulator address-mode) (set-a-to rotated)
814 | (not= :accumulator address-mode) (write-memory resolved-address rotated)
815 | true (set-carry-to carry)
816 | true (set-zero rotated)
817 | true (set-negative-to c)
818 | true (set-ticks! cycles)
819 | true (advance-pc bytes-read))))
820 |
821 | (defmethod exec-op :rti [state {:keys [cycles bytes-read]}]
822 | (let [[status state] (pop-8 state)
823 | status-map (byte->status status)
824 | [pc state] (pop-16 state)]
825 | (-> state
826 | (update :cpu merge status-map (merge status-map))
827 | (set-pc-to pc)
828 | (set-ticks! cycles))))
829 |
830 | (defmethod exec-op :rts [state {:keys [cycles]}]
831 | (let [[pc state] (pop-16 state)]
832 | (-> state
833 | (set-pc-to (inc pc))
834 | (set-ticks! cycles))))
835 |
836 | (defmethod exec-op :sbc [state {:keys [cycles resolved-arg bytes-read]}]
837 | (let [a (get-a state)
838 | c (get-carry state)
839 | [diff _] (arith/add a (bit-and 0xFF (bit-not resolved-arg)) c)
840 | carry (bool->bit (<= 0 (- a resolved-arg (- 1 c))))
841 | overflow (bool->bit (not= 0 (bit-and (bit-xor a diff)
842 | (bit-xor (bit-and 0xFF (bit-not resolved-arg)) diff)
843 | 0x80)))]
844 | (cond-> state
845 | true (set-a-to diff)
846 | true (set-zero diff)
847 | true (set-negative diff)
848 | true (set-overflow-to overflow)
849 | true (set-carry-to carry)
850 | true (set-ticks! cycles)
851 | true (advance-pc bytes-read))))
852 |
853 | (defmethod exec-op :sec [state {:keys [cycles bytes-read]}]
854 | (-> state
855 | (advance-pc bytes-read)
856 | (set-ticks! cycles)
857 | set-carry))
858 |
859 | (defmethod exec-op :sed [state {:keys [cycles bytes-read]}]
860 | (-> state
861 | (advance-pc bytes-read)
862 | (set-ticks! cycles)
863 | set-decimal))
864 |
865 | (defmethod exec-op :sei [state {:keys [cycles bytes-read] :as op}]
866 | (-> state
867 | set-interrupt
868 | (set-ticks! cycles)
869 | (advance-pc bytes-read)))
870 |
871 | (defmethod exec-op :sta [state
872 | {:keys [cycles resolved-address bytes-read] :as op}]
873 | (let [memory (get-memory state)
874 | a (get-a state)]
875 | (cond-> state
876 | (ppu-control-write? op) (ppu/write-control a)
877 | (ppu-scroll-write? op) (ppu/write-register-scroll a)
878 | (ppu-data-write? op) (ppu/write-register-data a)
879 | (ppu-address-write? op) (ppu/write-register-address a)
880 | true (set-ticks! cycles)
881 | true (advance-pc bytes-read)
882 | true (write-memory resolved-address a))))
883 |
884 | (defmethod exec-op :stx [state
885 | {:keys [cycles resolved-address bytes-read] :as op}]
886 | (let [memory (get-memory state)
887 | x (get-x state)]
888 | (cond-> state
889 | (ppu-control-write? op) (ppu/write-control x)
890 | (ppu-scroll-write? op) (ppu/write-register-scroll x)
891 | (ppu-data-write? op) (ppu/write-register-data x)
892 | (ppu-address-write? op) (ppu/write-register-address x)
893 | true (set-ticks! cycles)
894 | true (advance-pc bytes-read)
895 | true (write-memory resolved-address x))))
896 |
897 | (defmethod exec-op :sty [state
898 | {:keys [cycles resolved-address bytes-read] :as op}]
899 | (let [memory (get-memory state)
900 | y (get-y state)]
901 | (cond-> state
902 | (ppu-control-write? op) (ppu/write-control y)
903 | (ppu-scroll-write? op) (ppu/write-register-scroll y)
904 | (ppu-data-write? op) (ppu/write-register-data y)
905 | (ppu-address-write? op) (ppu/write-register-address y)
906 | true (set-ticks! cycles)
907 | true (advance-pc bytes-read)
908 | true (write-memory resolved-address y))))
909 |
910 | (defmethod exec-op :tax [state
911 | {:keys [cycles resolved-address bytes-read] :as op}]
912 | (let [a (get-a state)]
913 | (cond-> state
914 | true (set-x-to a)
915 | true (set-zero a)
916 | true (set-negative a)
917 | true (set-ticks! cycles)
918 | true (advance-pc bytes-read))))
919 |
920 | (defmethod exec-op :tay [state
921 | {:keys [cycles resolved-address bytes-read] :as op}]
922 | (let [a (get-a state)]
923 | (cond-> state
924 | true (set-y-to a)
925 | true (set-zero a)
926 | true (set-negative a)
927 | true (set-ticks! cycles)
928 | true (advance-pc bytes-read))))
929 |
930 | (defmethod exec-op :tsx [state
931 | {:keys [cycles resolved-address bytes-read] :as op}]
932 | (let [sp (get-sp state)]
933 | (cond-> state
934 | true (set-x-to sp)
935 | true (set-zero sp)
936 | true (set-negative sp)
937 | true (set-ticks! cycles)
938 | true (advance-pc bytes-read))))
939 |
940 | (defmethod exec-op :txa [state
941 | {:keys [cycles resolved-address bytes-read] :as op}]
942 | (let [x (get-x state)]
943 | (cond-> state
944 | true (set-a-to x)
945 | true (set-zero x)
946 | true (set-negative x)
947 | true (set-ticks! cycles)
948 | true (advance-pc bytes-read))))
949 |
950 | (defmethod exec-op :txs [state
951 | {:keys [cycles resolved-address bytes-read] :as op}]
952 | (let [x (get-x state)]
953 | (cond-> state
954 | true (assoc-in [:cpu :s] x)
955 | true (set-ticks! cycles)
956 | true (advance-pc bytes-read))))
957 |
958 | (defmethod exec-op :tya [state
959 | {:keys [cycles resolved-address bytes-read] :as op}]
960 | (let [y (get-y state)]
961 | (cond-> state
962 | true (set-a-to y)
963 | true (set-zero y)
964 | true (set-negative y)
965 | true (set-ticks! cycles)
966 | true (advance-pc bytes-read))))
967 |
968 | ;; Interrupt handling
969 |
970 | (defn handle-interrupt [state interrupt]
971 | (println "Handling interrupt: " interrupt)
972 | (let [vector (get-in state [:cpu interrupt])
973 | _ (println (pprint/cl-format nil "~x" vector))
974 | pc (get-pc state)
975 | status (status->byte state)
976 | interrupt-cycles 7]
977 | (-> state
978 | (push-16 pc)
979 | (push-8 status)
980 | (set-pc-to vector)
981 | (set-ticks! interrupt-cycles)
982 | (assoc :interrupt nil))))
983 |
984 | ;; Tick
985 |
986 | (defn tick! [{:keys [cpu] :as state} instruction]
987 | (let [{:keys [cycles ticks]} cpu
988 | interrupt (interrupts/check-interrupt state)]
989 | (cond (< 0 ticks) (-> state
990 | dec-ticks!
991 | inc-cycles!)
992 | interrupt (handle-interrupt state interrupt)
993 | :else (do
994 | (println instruction)
995 | (exec-op state instruction)))))
996 |
997 | (defn log-indirect-x [state {:keys [address-mode resolved-arg resolved-address] :as op}]
998 | (let [memory (get-memory state)
999 | pc (get-pc state)
1000 | x (get-x state)
1001 | initial-address (memory/cpu-read memory (inc pc))]
1002 | (pprint/cl-format nil "($~:@(~2,'0X~),X) @ ~:@(~2,'0X~) = ~:@(~4,'0X~) = ~:@(~2,'0X~)"
1003 | (memory/cpu-read memory (inc pc))
1004 | (mod (+ x initial-address) 0x100)
1005 | resolved-address
1006 | resolved-arg)))
1007 |
1008 | (defn log-address [state {:keys [address-mode resolved-arg resolved-address fn] :as op}]
1009 | (let [memory (get-memory state)
1010 | pc (get-pc state)]
1011 | (case address-mode
1012 | :immediate (pprint/cl-format nil "#$~:@(~2,'0X~)" resolved-arg)
1013 | :absolute (cond (get #{:jmp :jsr} fn) (pprint/cl-format nil "$~:@(~4,'0X~)" resolved-address)
1014 | :else (pprint/cl-format nil "$~:@(~4,'0X~) = ~:@(~2,'0X~)" resolved-address resolved-arg))
1015 | :implied ""
1016 | :indirect-x (log-indirect-x state op)
1017 | :accumulator "A"
1018 | :relative (pprint/cl-format nil "$~:@(~2,'0X~)" resolved-address)
1019 | :zero (pprint/cl-format nil "$~:@(~2,'0X~) = ~:@(~2,'0X~)" resolved-address resolved-arg))))
1020 |
1021 | (defn cpu-state [state]
1022 | (let [a (get-a state)
1023 | x (get-x state)
1024 | y (get-y state)
1025 | p (status->byte state)
1026 | sp (get-sp state)]
1027 | (pprint/cl-format nil "A:~:@(~2,'0X~) X:~:@(~2,'0X~) Y:~:@(~2,'0X~) P:~:@(~2,'0X~) SP:~:@(~2,'0X~)" a x y p sp)))
1028 |
1029 | (defn log-step [state]
1030 | (let [pc (get-pc state)
1031 | memory (get-memory state)
1032 | op (memory/cpu-read memory pc)
1033 | instruction (->> (get opcodes/ops op)
1034 | (address state))
1035 | {:keys [bytes-read]} instruction
1036 | byte-one (pprint/cl-format nil "~:@(~2,'0X~)" (memory/cpu-read memory pc))
1037 | byte-two (if (<= 2 bytes-read) (pprint/cl-format nil "~:@(~2,'0X~)" (memory/cpu-read memory (inc pc))) " ")
1038 | byte-three (if (<= 3 bytes-read) (pprint/cl-format nil "~:@(~2,'0X~)" (memory/cpu-read memory (+ 2 pc))) " ")
1039 | opcode (pprint/cl-format nil "~:@(~A~)" (name (:fn instruction)))
1040 | address (log-address state instruction)
1041 | cpu-state (cpu-state state)]
1042 | (pprint/cl-format nil "~:@(~4,'0X~) ~A ~A ~A ~A ~A ~A"
1043 | pc
1044 | byte-one
1045 | byte-two
1046 | byte-three
1047 | opcode
1048 | address
1049 | cpu-state)))
1050 |
1051 | (defn test-step [state]
1052 | (let [pc (get-pc state)
1053 | memory (get-memory state)
1054 | op (memory/cpu-read memory pc)
1055 | instruction (->> (get opcodes/ops op)
1056 | (address state))]
1057 | (-> state
1058 | (tick! instruction)
1059 | (set-ticks! 0))))
1060 |
1061 | (defn step [state]
1062 | (let [pc (get-pc state)
1063 | memory (get-memory state)
1064 | op (memory/cpu-read memory pc)
1065 | _ (println op)
1066 | instruction (->> (get opcodes/ops op)
1067 | (address state))]
1068 | (tick! state instruction)))
1069 |
--------------------------------------------------------------------------------