├── .gitignore ├── README.md ├── _config.yml ├── _includes ├── end.html ├── snake.html ├── start.html └── widget.html ├── _layouts ├── basic.html └── default.html ├── index.markdown ├── javascripts └── scale.fix.js ├── params.json ├── simulator.markdown ├── simulator ├── assembler.js ├── es5-shim.js └── style.css ├── snake.markdown └── stylesheets ├── pygment_trac.css └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # easy6502 2 | [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) 3 | 4 | Easy6502 by Nick Morgan is one-stop accessible tutorial on 6502 assembly language programming, 5 | including a series of worked example programs which you can edit and run in the embedded emulator. 6 | 7 | See http://skilldrick.github.io/easy6502/ for the live site. 8 | 9 | This (original) fork is now in a strict maintenance-only mode. Pull requests are welcome for bug fixes. 10 | 11 | Please see other active forks for further refinements and developments of the tutorial and the emulator: 12 | https://github.com/skilldrick/easy6502/network 13 | 14 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | safe: true 2 | lsi: false 3 | markdown: kramdown 4 | highlighter: rouge 5 | -------------------------------------------------------------------------------- /_includes/end.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 | 29 | 54 | 55 | -------------------------------------------------------------------------------- /_includes/snake.html: -------------------------------------------------------------------------------- 1 | {% include start.html %} 2 | ; ___ _ __ ___ __ ___ 3 | ; / __|_ _ __ _| |_____ / /| __|/ \_ ) 4 | ; \__ \ ' \/ _` | / / -_) _ \__ \ () / / 5 | ; |___/_||_\__,_|_\_\___\___/___/\__/___| 6 | 7 | ; Change direction: W A S D 8 | 9 | define appleL $00 ; screen location of apple, low byte 10 | define appleH $01 ; screen location of apple, high byte 11 | define snakeHeadL $10 ; screen location of snake head, low byte 12 | define snakeHeadH $11 ; screen location of snake head, high byte 13 | define snakeBodyStart $12 ; start of snake body byte pairs 14 | define snakeDirection $02 ; direction (possible values are below) 15 | define snakeLength $03 ; snake length, in bytes 16 | 17 | ; Directions (each using a separate bit) 18 | define movingUp 1 19 | define movingRight 2 20 | define movingDown 4 21 | define movingLeft 8 22 | 23 | ; ASCII values of keys controlling the snake 24 | define ASCII_w $77 25 | define ASCII_a $61 26 | define ASCII_s $73 27 | define ASCII_d $64 28 | 29 | ; System variables 30 | define sysRandom $fe 31 | define sysLastKey $ff 32 | 33 | 34 | jsr init 35 | jsr loop 36 | 37 | init: 38 | jsr initSnake 39 | jsr generateApplePosition 40 | rts 41 | 42 | 43 | initSnake: 44 | lda #movingRight ;start direction 45 | sta snakeDirection 46 | 47 | lda #4 ;start length (2 segments) 48 | sta snakeLength 49 | 50 | lda #$11 51 | sta snakeHeadL 52 | 53 | lda #$10 54 | sta snakeBodyStart 55 | 56 | lda #$0f 57 | sta $14 ; body segment 1 58 | 59 | lda #$04 60 | sta snakeHeadH 61 | sta $13 ; body segment 1 62 | sta $15 ; body segment 2 63 | rts 64 | 65 | 66 | generateApplePosition: 67 | ;load a new random byte into $00 68 | lda sysRandom 69 | sta appleL 70 | 71 | ;load a new random number from 2 to 5 into $01 72 | lda sysRandom 73 | and #$03 ;mask out lowest 2 bits 74 | clc 75 | adc #2 76 | sta appleH 77 | 78 | rts 79 | 80 | 81 | loop: 82 | jsr readKeys 83 | jsr checkCollision 84 | jsr updateSnake 85 | jsr drawApple 86 | jsr drawSnake 87 | jsr spinWheels 88 | jmp loop 89 | 90 | 91 | readKeys: 92 | lda sysLastKey 93 | cmp #ASCII_w 94 | beq upKey 95 | cmp #ASCII_d 96 | beq rightKey 97 | cmp #ASCII_s 98 | beq downKey 99 | cmp #ASCII_a 100 | beq leftKey 101 | rts 102 | upKey: 103 | lda #movingDown 104 | bit snakeDirection 105 | bne illegalMove 106 | 107 | lda #movingUp 108 | sta snakeDirection 109 | rts 110 | rightKey: 111 | lda #movingLeft 112 | bit snakeDirection 113 | bne illegalMove 114 | 115 | lda #movingRight 116 | sta snakeDirection 117 | rts 118 | downKey: 119 | lda #movingUp 120 | bit snakeDirection 121 | bne illegalMove 122 | 123 | lda #movingDown 124 | sta snakeDirection 125 | rts 126 | leftKey: 127 | lda #movingRight 128 | bit snakeDirection 129 | bne illegalMove 130 | 131 | lda #movingLeft 132 | sta snakeDirection 133 | rts 134 | illegalMove: 135 | rts 136 | 137 | 138 | checkCollision: 139 | jsr checkAppleCollision 140 | jsr checkSnakeCollision 141 | rts 142 | 143 | 144 | checkAppleCollision: 145 | lda appleL 146 | cmp snakeHeadL 147 | bne doneCheckingAppleCollision 148 | lda appleH 149 | cmp snakeHeadH 150 | bne doneCheckingAppleCollision 151 | 152 | ;eat apple 153 | inc snakeLength 154 | inc snakeLength ;increase length 155 | jsr generateApplePosition 156 | doneCheckingAppleCollision: 157 | rts 158 | 159 | 160 | checkSnakeCollision: 161 | ldx #2 ;start with second segment 162 | snakeCollisionLoop: 163 | lda snakeHeadL,x 164 | cmp snakeHeadL 165 | bne continueCollisionLoop 166 | 167 | maybeCollided: 168 | lda snakeHeadH,x 169 | cmp snakeHeadH 170 | beq didCollide 171 | 172 | continueCollisionLoop: 173 | inx 174 | inx 175 | cpx snakeLength ;got to last section with no collision 176 | beq didntCollide 177 | jmp snakeCollisionLoop 178 | 179 | didCollide: 180 | jmp gameOver 181 | didntCollide: 182 | rts 183 | 184 | 185 | updateSnake: 186 | ldx snakeLength 187 | dex 188 | txa 189 | updateloop: 190 | lda snakeHeadL,x 191 | sta snakeBodyStart,x 192 | dex 193 | bpl updateloop 194 | 195 | lda snakeDirection 196 | lsr 197 | bcs up 198 | lsr 199 | bcs right 200 | lsr 201 | bcs down 202 | lsr 203 | bcs left 204 | up: 205 | lda snakeHeadL 206 | sec 207 | sbc #$20 208 | sta snakeHeadL 209 | bcc upup 210 | rts 211 | upup: 212 | dec snakeHeadH 213 | lda #$1 214 | cmp snakeHeadH 215 | beq collision 216 | rts 217 | right: 218 | inc snakeHeadL 219 | lda #$1f 220 | bit snakeHeadL 221 | beq collision 222 | rts 223 | down: 224 | lda snakeHeadL 225 | clc 226 | adc #$20 227 | sta snakeHeadL 228 | bcs downdown 229 | rts 230 | downdown: 231 | inc snakeHeadH 232 | lda #$6 233 | cmp snakeHeadH 234 | beq collision 235 | rts 236 | left: 237 | dec snakeHeadL 238 | lda snakeHeadL 239 | and #$1f 240 | cmp #$1f 241 | beq collision 242 | rts 243 | collision: 244 | jmp gameOver 245 | 246 | 247 | drawApple: 248 | ldy #0 249 | lda sysRandom 250 | sta (appleL),y 251 | rts 252 | 253 | 254 | drawSnake: 255 | ldx snakeLength 256 | lda #0 257 | sta (snakeHeadL,x) ; erase end of tail 258 | 259 | ldx #0 260 | lda #1 261 | sta (snakeHeadL,x) ; paint head 262 | rts 263 | 264 | 265 | spinWheels: 266 | ldx #0 267 | spinloop: 268 | nop 269 | nop 270 | dex 271 | bne spinloop 272 | rts 273 | 274 | 275 | gameOver: 276 | {% include end.html %} 277 | -------------------------------------------------------------------------------- /_includes/start.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 |
36 |
37 | 38 | 63 |
64 | -------------------------------------------------------------------------------- /_layouts/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | Easy 6502 by skilldrick 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 |
29 |
30 |

Easy 6502

31 |

by Nick Morgan, licensed under CC BY 4.0

32 |

Fork me on GitHub

33 |
34 |
35 | 36 | {{ content }} 37 | 38 |
39 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | Easy 6502 by skilldrick 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 |
29 |
30 |

Easy 6502

31 |

by Nick Morgan, licensed under CC BY 4.0

32 |

Fork me on GitHub

33 | 44 |
45 |
46 | 47 | {{ content }} 48 | 49 |
50 | 53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |

Introduction

6 | 7 | In this tiny ebook I'm going to show you how to get started writing 6502 8 | assembly language. The 6502 processor was massive in the seventies and 9 | eighties, powering famous computers like the 10 | [BBC Micro](http://en.wikipedia.org/wiki/BBC_Micro), 11 | [Atari 2600](http://en.wikipedia.org/wiki/Atari_2600), 12 | [Commodore 64](http://en.wikipedia.org/wiki/Commodore_64), 13 | [Apple II](http://en.wikipedia.org/wiki/Apple_II), and the [Nintendo Entertainment 14 | System](http://en.wikipedia.org/wiki/Nintendo_Entertainment_System). Bender in 15 | Futurama [has a 6502 processor for a 16 | brain](http://www.transbyte.org/SID/SID-files/Bender_6502.jpg). [Even the 17 | Terminator was programmed in 18 | 6502](http://www.pagetable.com/docs/terminator/00-37-23.jpg). 19 | 20 | So, why would you want to learn 6502? It's a dead language isn't it? Well, 21 | so's Latin. And they still teach that. 22 | [Q.E.D.](http://en.wikipedia.org/wiki/Q.E.D.) 23 | 24 | (Actually, I've been reliably informed that 6502 processors are still being 25 | produced by [Western Design Center](http://www.westerndesigncenter.com/wdc/w65c02s-chip.cfm) 26 | and [sold to hobbyists](http://www.mouser.co.uk/Search/Refine.aspx?Keyword=65C02), so clearly 6502 27 | *isn't* a dead language! Who knew?) 28 | 29 | Seriously though, I think it's valuable to have an understanding of assembly 30 | language. Assembly language is the lowest level of abstraction in computers - 31 | the point at which the code is still readable. Assembly language translates 32 | directly to the bytes that are executed by your computer's processor. 33 | If you understand how it works, you've basically become a computer 34 | [magician](http://skilldrick.co.uk/2011/04/magic-in-software-development/). 35 | 36 | Then why 6502? Why not a *useful* assembly language, like 37 | [x86](http://en.wikipedia.org/wiki/X86)? Well, I don't think learning x86 is 38 | useful. I don't think you'll ever have to *write* assembly language in your day 39 | job - this is purely an academic exercise, something to expand your mind and 40 | your thinking. 6502 was originally written in a different age, a time when the majority of 41 | developers were writing assembly directly, rather than in these new-fangled 42 | high-level programming languages. So, it was designed to be written by humans. 43 | More modern assembly languages are meant to written by compilers, so let's 44 | leave it to them. Plus, 6502 is *fun*. Nobody ever called x86 *fun*. 45 | 46 | 47 |

Our first program

48 | 49 | So, let's dive in! That thing below is a little [JavaScript 6502 assembler and 50 | simulator](https://github.com/skilldrick/6502js) that I adapted for this book. 51 | Click **Assemble** then **Run** to assemble and run the snippet of assembly language. 52 | 53 | {% include start.html %} 54 | LDA #$01 55 | STA $0200 56 | LDA #$05 57 | STA $0201 58 | LDA #$08 59 | STA $0202 60 | {% include end.html %} 61 | 62 | Hopefully the black area on the right now has three coloured "pixels" at the 63 | top left. (If this doesn't work, you'll probably need to upgrade your browser to 64 | something more modern, like Chrome or Firefox.) 65 | 66 | So, what's this program actually doing? Let's step through it with the 67 | debugger. Hit **Reset**, then check the **Debugger** checkbox to start the 68 | debugger. Click **Step** once. If you were watching carefully, you'll have 69 | noticed that `A=` changed from `$00` to `$01`, and `PC=` changed from `$0600` to 70 | `$0602`. 71 | 72 | Any numbers prefixed with `$` in 6502 assembly language (and by extension, in 73 | this book) are in hexadecimal (hex) format. If you're not familiar with hex 74 | numbers, I recommend you read [the Wikipedia 75 | article](http://en.wikipedia.org/wiki/Hexadecimal). Anything prefixed with `#` 76 | is a literal number value. Any other number refers to a memory location. 77 | 78 | Equipped with that knowledge, you should be able to see that the instruction 79 | `LDA #$01` loads the hex value `$01` into register `A`. I'll go into more 80 | detail on registers in the next section. 81 | 82 | Press **Step** again to execute the second instruction. The top-left pixel of 83 | the simulator display should now be white. This simulator uses the memory 84 | locations `$0200` to `$05ff` to draw pixels on its display. The values `$00` to 85 | `$0f` represent 16 different colours (`$00` is black and `$01` is white), so 86 | storing the value `$01` at memory location `$0200` draws a white pixel at the 87 | top left corner. This is simpler than how an actual computer would output 88 | video, but it'll do for now. 89 | 90 | So, the instruction `STA $0200` stores the value of the `A` register to memory 91 | location `$0200`. Click **Step** four more times to execute the rest of the 92 | instructions, keeping an eye on the `A` register as it changes. 93 | 94 | ### Exercises ### 95 | 96 | 1. Try changing the colour of the three pixels. 97 | 2. Change one of the pixels to draw at the bottom-right corner (memory location `$05ff`). 98 | 3. Add more instructions to draw extra pixels. 99 | 100 | 101 |

Registers and flags

102 | 103 | We've already had a little look at the processor status section (the bit with 104 | `A`, `PC` etc.), but what does it all mean? 105 | 106 | The first line shows the `A`, `X` and `Y` registers (`A` is often called the 107 | "accumulator"). Each register holds a single byte. Most operations work on the 108 | contents of these registers. 109 | 110 | `SP` is the stack pointer. I won't get into the stack yet, but basically this 111 | register is decremented every time a byte is pushed onto the stack, and 112 | incremented when a byte is popped off the stack. 113 | 114 | `PC` is the program counter - it's how the processor knows at what point in the 115 | program it currently is. It's like the current line number of an executing 116 | script. In the JavaScript simulator the code is assembled starting at memory 117 | location `$0600`, so `PC` always starts there. 118 | 119 | The last section shows the processor flags. Each flag is one bit, so all seven 120 | flags live in a single byte. The flags are set by the processor to give 121 | information about the previous instruction. More on that later. [Read more 122 | about the registers and flags here](https://web.archive.org/web/20210626024532/http://www.obelisk.me.uk/6502/registers.html). 123 | 124 | 125 |

Instructions

126 | 127 | Instructions in assembly language are like a small set of predefined functions. 128 | All instructions take zero or one arguments. Here's some annotated 129 | source code to introduce a few different instructions: 130 | 131 | {% include start.html %} 132 | LDA #$c0 ;Load the hex value $c0 into the A register 133 | TAX ;Transfer the value in the A register to X 134 | INX ;Increment the value in the X register 135 | ADC #$c4 ;Add the hex value $c4 to the A register 136 | BRK ;Break - we're done 137 | {% include end.html %} 138 | 139 | Assemble the code, then turn on the debugger and step through the code, watching 140 | the `A` and `X` registers. Something slightly odd happens on the line `ADC #$c4`. 141 | You might expect that adding `$c4` to `$c0` would give `$184`, but this 142 | processor gives the result as `$84`. What's up with that? 143 | 144 | The problem is, `$184` is too big to fit in a single byte (the max is `$FF`), 145 | and the registers can only hold a single byte. It's OK though; the processor 146 | isn't actually dumb. If you were looking carefully enough, you'll have noticed 147 | that the carry flag was set to `1` after this operation. So that's how you 148 | know. 149 | 150 | In the simulator below **type** (don't paste) the following code: 151 | 152 | LDA #$80 153 | STA $01 154 | ADC $01 155 | 156 | {% include widget.html %} 157 | 158 | An important thing to notice here is the distinction between `ADC #$01` and 159 | `ADC $01`. The first one adds the value `$01` to the `A` register, but the 160 | second adds the value stored at memory location `$01` to the `A` register. 161 | 162 | Assemble, check the **Monitor** checkbox, then step through these three 163 | instructions. The monitor shows a section of memory, and can be helpful to 164 | visualise the execution of programs. `STA $01` stores the value of the `A` 165 | register at memory location `$01`, and `ADC $01` adds the value stored at the 166 | memory location `$01` to the `A` register. `$80 + $80` should equal `$100`, but 167 | because this is bigger than a byte, the `A` register is set to `$00` and the 168 | carry flag is set. As well as this though, the zero flag is set. The zero flag 169 | is set by all instructions where the result is zero. 170 | 171 | A full list of the 6502 instruction set is [available 172 | here](http://www.6502.org/tutorials/6502opcodes.html) and 173 | [here](http://www.obelisk.me.uk/6502/reference.html) (I usually refer to 174 | both pages as they have their strengths and weaknesses). These pages detail the 175 | arguments to each instruction, which registers they use, and which flags they 176 | set. They are your bible. 177 | 178 | ### Exercises ### 179 | 180 | 1. You've seen `TAX`. You can probably guess what `TAY`, `TXA` and `TYA` do, 181 | but write some code to test your assumptions. 182 | 2. Rewrite the first example in this section to use the `Y` register instead of 183 | the `X` register. 184 | 3. The opposite of `ADC` is `SBC` (subtract with carry). Write a program that 185 | uses this instruction. 186 | 187 | 188 |

Branching

189 | 190 | So far we're only able to write basic programs without any branching logic. 191 | Let's change that. 192 | 193 | 6502 assembly language has a bunch of branching instructions, all of which 194 | branch based on whether certain flags are set or not. In this example we'll be 195 | looking at `BNE`: "Branch on not equal". 196 | 197 | {% include start.html %} 198 | LDX #$08 199 | decrement: 200 | DEX 201 | STX $0200 202 | CPX #$03 203 | BNE decrement 204 | STX $0201 205 | BRK 206 | {% include end.html %} 207 | 208 | First we load the value `$08` into the `X` register. The next line is a label. 209 | Labels just mark certain points in a program so we can return to them later. 210 | After the label we decrement `X`, store it to `$0200` (the top-left pixel), and 211 | then compare it to the value `$03`. 212 | [`CPX`](http://www.obelisk.me.uk/6502/reference.html#CPX) compares the 213 | value in the `X` register with another value. If the two values are equal, the 214 | `Z` flag is set to `1`, otherwise it is set to `0`. 215 | 216 | The next line, `BNE decrement`, will shift execution to the decrement label if 217 | the `Z` flag is set to `0` (meaning that the two values in the `CPX` comparison 218 | were not equal), otherwise it does nothing and we store `X` to `$0201`, then 219 | finish the program. 220 | 221 | In assembly language, you'll usually use labels with branch instructions. When 222 | assembled though, this label is converted to a single-byte relative offset (a 223 | number of bytes to go backwards or forwards from the next instruction) so 224 | branch instructions can only go forward and back around 256 bytes. This means 225 | they can only be used to move around local code. For moving further you'll need 226 | to use the jumping instructions. 227 | 228 | ### Exercises ### 229 | 230 | 1. The opposite of `BNE` is `BEQ`. Try writing a program that uses `BEQ`. 231 | 2. `BCC` and `BCS` ("branch on carry clear" and "branch on carry set") are used 232 | to branch on the carry flag. Write a program that uses one of these two. 233 | 234 | 235 |

Addressing modes

236 | 237 | The 6502 uses a 16-bit address bus, meaning that there are 65536 bytes of 238 | memory available to the processor. Remember that a byte is represented by two 239 | hex characters, so the memory locations are generally represented as `$0000 - 240 | $ffff`. There are various ways to refer to these memory locations, as detailed below. 241 | 242 | With all these examples you might find it helpful to use the memory monitor to 243 | watch the memory change. The monitor takes a starting memory location and a 244 | number of bytes to display from that location. Both of these are hex values. 245 | For example, to display 16 bytes of memory from `$c000`, enter `c000` and `10` 246 | into **Start** and **Length**, respectively. 247 | 248 | ### Absolute: `$c000` ### 249 | 250 | With absolute addressing, the full memory location is used as the argument to the instruction. For example: 251 | 252 | STA $c000 ;Store the value in the accumulator at memory location $c000 253 | 254 | ### Zero page: `$c0` ### 255 | 256 | All instructions that support absolute addressing (with the exception of the jump 257 | instructions) also have the option to take a single-byte address. This type of 258 | addressing is called "zero page" - only the first page (the first 256 bytes) of 259 | memory is accessible. This is faster, as only one byte needs to be looked up, 260 | and takes up less space in the assembled code as well. 261 | 262 | ### Zero page,X: `$c0,X` ### 263 | 264 | This is where addressing gets interesting. In this mode, a zero page address is given, and then the value of the `X` register is added. Here is an example: 265 | 266 | LDX #$01 ;X is $01 267 | LDA #$aa ;A is $aa 268 | STA $a0,X ;Store the value of A at memory location $a1 269 | INX ;Increment X 270 | STA $a0,X ;Store the value of A at memory location $a2 271 | 272 | If the result of the addition is larger than a single byte, the address wraps around. For example: 273 | 274 | LDX #$05 275 | STA $ff,X ;Store the value of A at memory location $04 276 | 277 | ### Zero page,Y: `$c0,Y` ### 278 | 279 | This is the equivalent of zero page,X, but can only be used with `LDX` and `STX`. 280 | 281 | ### Absolute,X and absolute,Y: `$c000,X` and `$c000,Y` ### 282 | 283 | These are the absolute addressing versions of zero page,X and zero page,Y. For example: 284 | 285 | LDX #$01 286 | STA $0200,X ;Store the value of A at memory location $0201 287 | 288 | Unlike zero page,Y, absolute,Y can't be used with `STX` but can be used with `LDA` and `STA`. 289 | 290 | ### Immediate: `#$c0` ### 291 | 292 | Immediate addressing doesn't strictly deal with memory addresses - this is the 293 | mode where actual values are used. For example, `LDX #$01` loads the value 294 | `$01` into the `X` register. This is very different to the zero page 295 | instruction `LDX $01` which loads the value at memory location `$01` into the 296 | `X` register. 297 | 298 | ### Relative: `$c0` (or label) ### 299 | 300 | Relative addressing is used for branching instructions. These instructions take 301 | a single byte, which is used as an offset from the following instruction. 302 | 303 | Assemble the following code, then click the **Hexdump** button to see the assembled code. 304 | 305 | {% include start.html %} 306 | LDA #$01 307 | CMP #$02 308 | BNE notequal 309 | STA $22 310 | notequal: 311 | BRK 312 | {% include end.html %} 313 | 314 | The hex should look something like this: 315 | 316 | a9 01 c9 02 d0 02 85 22 00 317 | 318 | `a9` and `c9` are the processor opcodes for immediate-addressed `LDA` and `CMP` 319 | respectively. `01` and `02` are the arguments to these instructions. `d0` is 320 | the opcode for `BNE`, and its argument is `02`. This means "skip over the next 321 | two bytes" (`85 22`, the assembled version of `STA $22`). Try editing the code 322 | so `STA` takes a two-byte absolute address rather than a single-byte zero page 323 | address (e.g. change `STA $22` to `STA $2222`). Reassemble the code and look at 324 | the hexdump again - the argument to `BNE` should now be `03`, because the 325 | instruction the processor is skipping past is now three bytes long. 326 | 327 | ### Implicit ### 328 | 329 | Some instructions don't deal with memory locations (e.g. `INX` - increment the 330 | `X` register). These are said to have implicit addressing - the argument is 331 | implied by the instruction. 332 | 333 | ### Indirect: `($c000)` ### 334 | 335 | Indirect addressing uses an absolute address to look up another address. The 336 | first address gives the least significant byte of the address, and the 337 | following byte gives the most significant byte. That can be hard to wrap your 338 | head around, so here's an example: 339 | 340 | {% include start.html %} 341 | LDA #$01 342 | STA $f0 343 | LDA #$cc 344 | STA $f1 345 | JMP ($00f0) ;dereferences to $cc01 346 | {% include end.html %} 347 | 348 | In this example, `$f0` contains the value `$01` and `$f1` contains the value 349 | `$cc`. The instruction `JMP ($f0)` causes the processor to look up the two 350 | bytes at `$f0` and `$f1` (`$01` and `$cc`) and put them together to form the 351 | address `$cc01`, which becomes the new program counter. Assemble and step 352 | through the program above to see what happens. I'll talk more about `JMP` in 353 | the section on [Jumping](#jumping). 354 | 355 | ### Indexed indirect: `($c0,X)` ### 356 | 357 | This one's kinda weird. It's like a cross between zero page,X and indirect. 358 | Basically, you take the zero page address, add the value of the `X` register to 359 | it, then use that to look up a two-byte address. For example: 360 | 361 | {% include start.html %} 362 | LDX #$01 363 | LDA #$05 364 | STA $01 365 | LDA #$07 366 | STA $02 367 | LDY #$0a 368 | STY $0705 369 | LDA ($00,X) 370 | {% include end.html %} 371 | 372 | Memory locations `$01` and `$02` contain the values `$05` and `$07` 373 | respectively. Think of `($00,X)` as `($00 + X)`. In this case `X` is `$01`, so 374 | this simplifies to `($01)`. From here things proceed like standard indirect 375 | addressing - the two bytes at `$01` and `$02` (`$05` and `$07`) are looked up 376 | to form the address `$0705`. This is the address that the `Y` register was 377 | stored into in the previous instruction, so the `A` register gets the same 378 | value as `Y`, albeit through a much more circuitous route. You won't see this 379 | much. 380 | 381 | 382 | ### Indirect indexed: `($c0),Y` ### 383 | 384 | Indirect indexed is like indexed indirect but less insane. Instead of adding 385 | the `X` register to the address *before* dereferencing, the zero page address 386 | is dereferenced, and the `Y` register is added to the resulting address. 387 | 388 | {% include start.html %} 389 | LDY #$01 390 | LDA #$03 391 | STA $01 392 | LDA #$07 393 | STA $02 394 | LDX #$0a 395 | STX $0704 396 | LDA ($01),Y 397 | {% include end.html %} 398 | 399 | In this case, `($01)` looks up the two bytes at `$01` and `$02`: `$03` and 400 | `$07`. These form the address `$0703`. The value of the `Y` register is added 401 | to this address to give the final address `$0704`. 402 | 403 | ### Exercise ### 404 | 405 | 1. Try to write code snippets that use each of the 6502 addressing modes. 406 | Remember, you can use the monitor to watch a section of memory. 407 | 408 | 409 |

The stack

410 | 411 | The stack in a 6502 processor is just like any other stack - values are pushed 412 | onto it and popped ("pulled" in 6502 parlance) off it. The current depth of the 413 | stack is measured by the stack pointer, a special register. The stack lives in 414 | memory between `$0100` and `$01ff`. The stack pointer is initially `$ff`, which 415 | points to memory location `$01ff`. When a byte is pushed onto the stack, the 416 | stack pointer becomes `$fe`, or memory location `$01fe`, and so on. 417 | 418 | Two of the stack instructions are `PHA` and `PLA`, "push accumulator" and "pull 419 | accumulator". Below is an example of these two in action. 420 | 421 | {% include start.html %} 422 | LDX #$00 423 | LDY #$00 424 | firstloop: 425 | TXA 426 | STA $0200,Y 427 | PHA 428 | INX 429 | INY 430 | CPY #$10 431 | BNE firstloop ;loop until Y is $10 432 | secondloop: 433 | PLA 434 | STA $0200,Y 435 | INY 436 | CPY #$20 ;loop until Y is $20 437 | BNE secondloop 438 | {% include end.html %} 439 | 440 | `X` holds the pixel colour, and `Y` holds the position of the current pixel. 441 | The first loop draws the current colour as a pixel (via the `A` register), 442 | pushes the colour to the stack, then increments the colour and position. The 443 | second loop pops the stack, draws the popped colour as a pixel, then increments 444 | the position. As should be expected, this creates a mirrored pattern. 445 | 446 | 447 |

Jumping

448 | 449 | Jumping is like branching with two main differences. First, jumps are not 450 | conditionally executed, and second, they take a two-byte absolute address. For 451 | small programs, this second detail isn't very important, as you'll mostly be 452 | using labels, and the assembler works out the correct memory location from the 453 | label. For larger programs though, jumping is the only way to move from one 454 | section of the code to another. 455 | 456 | ### JMP ### 457 | 458 | `JMP` is an unconditional jump. Here's a really simple example to show it in action: 459 | 460 | {% include start.html %} 461 | LDA #$03 462 | JMP there 463 | BRK 464 | BRK 465 | BRK 466 | there: 467 | STA $0200 468 | {% include end.html %} 469 | 470 | 471 | ### JSR/RTS ### 472 | 473 | `JSR` and `RTS` ("jump to subroutine" and "return from subroutine") are a 474 | dynamic duo that you'll usually see used together. `JSR` is used to jump from 475 | the current location to another part of the code. `RTS` returns to the previous 476 | position. This is basically like calling a function and returning. 477 | 478 | The processor knows where to return to because `JSR` pushes the address minus 479 | one of the next instruction onto the stack before jumping to the given 480 | location. `RTS` pops this location, adds one to it, and jumps to that location. 481 | An example: 482 | 483 | {% include start.html %} 484 | JSR init 485 | JSR loop 486 | JSR end 487 | 488 | init: 489 | LDX #$00 490 | RTS 491 | 492 | loop: 493 | INX 494 | CPX #$05 495 | BNE loop 496 | RTS 497 | 498 | end: 499 | BRK 500 | {% include end.html %} 501 | 502 | The first instruction causes execution to jump to the `init` label. This sets 503 | `X`, then returns to the next instruction, `JSR loop`. This jumps to the `loop` 504 | label, which increments `X` until it is equal to `$05`. After that we return to 505 | the next instruction, `JSR end`, which jumps to the end of the file. This 506 | illustrates how `JSR` and `RTS` can be used together to create modular code. 507 | 508 | 509 |

Creating a game

510 | 511 | Now, let's put all this knowledge to good use, and make a game! We're going to 512 | be making a really simple version of the classic game 'Snake'. 513 | 514 | Even though this will be a simple version, the code will be substantially larger 515 | than all the previous examples. We will need to keep track of several memory 516 | locations together for the various aspects of the game. We can still do 517 | the necessary bookkeeping throughout the program ourselves, as before, but 518 | on a larger scale that quickly becomes tedious and can also lead to bugs that 519 | are difficult to spot. Instead we'll now let the assembler do some of the 520 | mundane work for us. 521 | 522 | In this assembler, we can define descriptive constants (or symbols) that represent 523 | numbers. The rest of the code can then simply use the constants instead of the 524 | literal number, which immediately makes it obvious what we're dealing with. 525 | You can use letters, digits and underscores in a name. 526 | 527 | Here's an example. Note that immediate operands are still prefixed with a `#`. 528 | {% include start.html %} 529 | define sysRandom $fe ; an address 530 | define a_dozen $0c ; a constant 531 | 532 | LDA sysRandom ; equivalent to "LDA $fe" 533 | 534 | LDX #a_dozen ; equivalent to "LDX #$0c" 535 | {% include end.html %} 536 | 537 | The simulator widget below contains the entire source code of the game. I'll 538 | explain how it works in the following sections. 539 | 540 | [Willem van der Jagt](https://twitter.com/wkjagt) made a [fully annotated gist 541 | of this source code](https://gist.github.com/wkjagt/9043907), so follow along 542 | with that for more details. 543 | 544 | {% include snake.html %} 545 | 546 | 547 | ### Overall structure ### 548 | 549 | After the initial block of comments (lines starting with semicolons), the first 550 | two lines are: 551 | 552 | jsr init 553 | jsr loop 554 | 555 | `init` and `loop` are both subroutines. `init` initializes the game state, and 556 | `loop` is the main game loop. 557 | 558 | The `loop` subroutine itself just calls a number of subroutines sequentially, 559 | before looping back on itself: 560 | 561 | loop: 562 | jsr readkeys 563 | jsr checkCollision 564 | jsr updateSnake 565 | jsr drawApple 566 | jsr drawSnake 567 | jsr spinwheels 568 | jmp loop 569 | 570 | First, `readkeys` checks to see if one of the direction keys (W, A, S, D) was 571 | pressed, and if so, sets the direction of the snake accordingly. Then, 572 | `checkCollision` checks to see if the snake collided with itself or the apple. 573 | `updateSnake` updates the internal representation of the snake, based on its 574 | direction. Next, the apple and snake are drawn. Finally, `spinWheels` makes the 575 | processor do some busy work, to stop the game from running too quickly. Think 576 | of it like a sleep command. The game keeps running until the snake collides 577 | with the wall or itself. 578 | 579 | 580 | ### Zero page usage ### 581 | 582 | The zero page of memory is used to store a number of game state variables, as 583 | noted in the comment block at the top of the game. Everything in `$00`, `$01` 584 | and `$10` upwards is a pair of bytes representing a two-byte memory location 585 | that will be looked up using indirect addressing. These memory locations will 586 | all be between `$0200` and `$05ff` - the section of memory corresponding to the 587 | simulator display. For example, if `$00` and `$01` contained the values `$01` 588 | and `$02`, they would be referring to the second pixel of the display ( 589 | `$0201` - remember, the least significant byte comes first in indirect addressing). 590 | 591 | The first two bytes hold the location of the apple. This is updated every time 592 | the snake eats the apple. Byte `$02` contains the current direction. `1` means 593 | up, `2` right, `4` down, and `8` left. The reasoning behind these numbers will 594 | become clear later. 595 | 596 | Finally, byte `$03` contains the current length of the snake, in terms of bytes 597 | in memory (so a length of 4 means 2 pixels). 598 | 599 | 600 | ### Initialization ### 601 | 602 | The `init` subroutine defers to two subroutines, `initSnake` and 603 | `generateApplePosition`. `initSnake` sets the snake direction, length, and then 604 | loads the initial memory locations of the snake head and body. The byte pair at 605 | `$10` contains the screen location of the head, the pair at `$12` contains the 606 | location of the single body segment, and `$14` contains the location of the 607 | tail (the tail is the last segment of the body and is drawn in black to keep 608 | the snake moving). This happens in the following code: 609 | 610 | lda #$11 611 | sta $10 612 | lda #$10 613 | sta $12 614 | lda #$0f 615 | sta $14 616 | lda #$04 617 | sta $11 618 | sta $13 619 | sta $15 620 | 621 | This loads the value `$11` into the memory location `$10`, the value `$10` into 622 | `$12`, and `$0f` into `$14`. It then loads the value `$04` into `$11`, `$13` 623 | and `$15`. This leads to memory like this: 624 | 625 | 0010: 11 04 10 04 0f 04 626 | 627 | which represents the indirectly-addressed memory locations `$0411`, `$0410` and 628 | `$040f` (three pixels in the middle of the display). I'm labouring this point, 629 | but it's important to fully grok how indirect addressing works. 630 | 631 | The next subroutine, `generateApplePosition`, sets the apple location to a 632 | random position on the display. First, it loads a random byte into the 633 | accumulator (`$fe` is a random number generator in this simulator). This is 634 | stored into `$00`. Next, a different random byte is loaded into the 635 | accumulator, which is then `AND`-ed with the value `$03`. This part requires a 636 | bit of a detour. 637 | 638 | The hex value `$03` is represented in binary as `00000011`. The `AND` opcode 639 | performs a bitwise AND of the argument with the accumulator. For example, if 640 | the accumulator contains the binary value `10101010`, then the result of `AND` 641 | with `00000011` will be `00000010`. 642 | 643 | The effect of this is to mask out the least significant two bits of the 644 | accumulator, setting the others to zero. This converts a number in the range of 645 | 0–255 to a number in the range of 0–3. 646 | 647 | After this, the value `2` is added to the accumulator, to create a final random 648 | number in the range 2–5. 649 | 650 | The result of this subroutine is to load a random byte into `$00`, and a random 651 | number between 2 and 5 into `$01`. Because the least significant byte comes 652 | first with indirect addressing, this translates into a memory address between 653 | `$0200` and `$05ff`: the exact range used to draw the display. 654 | 655 | 656 | ### The game loop ### 657 | 658 | Nearly all games have at their heart a game loop. All game loops have the same 659 | basic form: accept user input, update the game state, and render the game 660 | state. This loop is no different. 661 | 662 | 663 | #### Reading the input #### 664 | 665 | The first subroutine, `readKeys`, takes the job of accepting user input. The 666 | memory location `$ff` holds the ascii code of the most recent key press in this 667 | simulator. The value is loaded into the accumulator, then compared to `$77` 668 | (the hex code for W), `$64` (D), `$73` (S) and `$61` (A). If any of these 669 | comparisons are successful, the program branches to the appropriate section. 670 | Each section (`upKey`, `rightKey`, etc.) first checks to see if the current 671 | direction is the opposite of the new direction. This requires another little detour. 672 | 673 | As stated before, the four directions are represented internally by the numbers 674 | 1, 2, 4 and 8. Each of these numbers is a power of 2, thus they are represented 675 | by a binary number with a single `1`: 676 | 677 | 1 => 0001 (up) 678 | 2 => 0010 (right) 679 | 4 => 0100 (down) 680 | 8 => 1000 (left) 681 | 682 | The `BIT` opcode is similar to `AND`, but the calculation is only used to set 683 | the zero flag - the actual result is discarded. The zero flag is set only if the 684 | result of AND-ing the accumulator with argument is zero. When we're looking at 685 | powers of two, the zero flag will only be set if the two numbers are not the 686 | same. For example, `0001 AND 0001` is not zero, but `0001 AND 0010` is zero. 687 | 688 | So, looking at `upKey`, if the current direction is down (4), the bit test will 689 | be zero. `BNE` means "branch if the zero flag is clear", so in this case we'll 690 | branch to `illegalMove`, which just returns from the subroutine. Otherwise, the 691 | new direction (1 in this case) is stored in the appropriate memory location. 692 | 693 | 694 | #### Updating the game state #### 695 | 696 | The next subroutine, `checkCollision`, defers to `checkAppleCollision` and 697 | `checkSnakeCollision`. `checkAppleCollision` just checks to see if the two 698 | bytes holding the location of the apple match the two bytes holding the 699 | location of the head. If they do, the length is increased and a new apple 700 | position is generated. 701 | 702 | `checkSnakeCollision` loops through the snake's body segments, checking each 703 | byte pair against the head pair. If there is a match, then game over. 704 | 705 | After collision detection, we update the snake's location. This is done at a 706 | high level like so: First, move each byte pair of the body up one position in 707 | memory. Second, update the head according to the current direction. Finally, if 708 | the head is out of bounds, handle it as a collision. I'll illustrate this with 709 | some ascii art. Each pair of brackets contains an x,y coordinate rather than a 710 | pair of bytes for simplicity. 711 | 712 | 0 1 2 3 4 713 | Head Tail 714 | 715 | [1,5][1,4][1,3][1,2][2,2] Starting position 716 | 717 | [1,5][1,4][1,3][1,2][1,2] Value of (3) is copied into (4) 718 | 719 | [1,5][1,4][1,3][1,3][1,2] Value of (2) is copied into (3) 720 | 721 | [1,5][1,4][1,4][1,3][1,2] Value of (1) is copied into (2) 722 | 723 | [1,5][1,5][1,4][1,3][1,2] Value of (0) is copied into (1) 724 | 725 | [0,5][1,5][1,4][1,3][1,2] Value of (0) is updated based on direction 726 | 727 | At a low level, this subroutine is slightly more complex. First, the length is 728 | loaded into the `X` register, which is then decremented. The snippet below 729 | shows the starting memory for the snake. 730 | 731 | Memory location: $10 $11 $12 $13 $14 $15 732 | 733 | Value: $11 $04 $10 $04 $0f $04 734 | 735 | The length is initialized to `4`, so `X` starts off as `3`. `LDA $10,x` loads the 736 | value of `$13` into `A`, then `STA $12,x` stores this value into `$15`. `X` is 737 | decremented, and we loop. Now `X` is `2`, so we load `$12` and store it into 738 | `$14`. This loops while `X` is positive (`BPL` means "branch if positive"). 739 | 740 | Once the values have been shifted down the snake, we have to work out what to 741 | do with the head. The direction is first loaded into `A`. `LSR` means "logical 742 | shift right", or "shift all the bits one position to the right". The least 743 | significant bit is shifted into the carry flag, so if the accumulator is `1`, 744 | after `LSR` it is `0`, with the carry flag set. 745 | 746 | To test whether the direction is `1`, `2`, `4` or `8`, the code continually 747 | shifts right until the carry is set. One `LSR` means "up", two means "right", 748 | and so on. 749 | 750 | The next bit updates the head of the snake depending on the direction. This is 751 | probably the most complicated part of the code, and it's all reliant on how 752 | memory locations map to the screen, so let's look at that in more detail. 753 | 754 | You can think of the screen as four horizontal strips of 32 × 8 pixels. 755 | These strips map to `$0200-$02ff`, `$0300-$03ff`, `$0400-$04ff` and `$0500-$05ff`. 756 | The first rows of pixels are `$0200-$021f`, `$0220-$023f`, `$0240-$025f`, etc. 757 | 758 | As long as you're moving within one of these horizontal strips, things are 759 | simple. For example, to move right, just increment the least significant byte 760 | (e.g. `$0200` becomes `$0201`). To go down, add `$20` (e.g. `$0200` becomes 761 | `$0220`). Left and up are the reverse. 762 | 763 | Going between sections is more complicated, as we have to take into account the 764 | most significant byte as well. For example, going down from `$02e1` should lead 765 | to `$0301`. Luckily, this is fairly easy to accomplish. Adding `$20` to `$e1` 766 | results in `$01` and sets the carry bit. If the carry bit was set, we know we 767 | also need to increment the most significant byte. 768 | 769 | After a move in each direction, we also need to check to see if the head 770 | would become out of bounds. This is handled differently for each direction. For 771 | left and right, we can check to see if the head has effectively "wrapped 772 | around". Going right from `$021f` by incrementing the least significant byte 773 | would lead to `$0220`, but this is actually jumping from the last pixel of the 774 | first row to the first pixel of the second row. So, every time we move right, 775 | we need to check if the new least significant byte is a multiple of `$20`. This 776 | is done using a bit check against the mask `$1f`. Hopefully the illustration 777 | below will show you how masking out the lowest 5 bits reveals whether a number 778 | is a multiple of `$20` or not. 779 | 780 | $20: 0010 0000 781 | $40: 0100 0000 782 | $60: 0110 0000 783 | 784 | $1f: 0001 1111 785 | 786 | I won't explain in depth how each of the directions work, but the above 787 | explanation should give you enough to work it out with a bit of study. 788 | 789 | 790 | #### Rendering the game #### 791 | 792 | Because the game state is stored in terms of pixel locations, rendering the 793 | game is very straightforward. The first subroutine, `drawApple`, is extremely 794 | simple. It sets `Y` to zero, loads a random colour into the accumulator, then 795 | stores this value into `($00),y`. `$00` is where the location of the apple is 796 | stored, so `($00),y` dereferences to this memory location. Read the "Indirect 797 | indexed" section in [Addressing modes](#addressing) for more details. 798 | 799 | Next comes `drawSnake`. This is pretty simple too - we first undraw the tail 800 | and then draw the head. `X` is set to the length of the snake, so we can index 801 | to the right pixel, and we set `A` to zero then perform the write using the 802 | indexed indirect addressing mode. Then we reload `X` to index to the head, set 803 | `A` to one and store it at `($10,x)`. `$10` stores the two-byte location of 804 | the head, so this draws a white pixel at the current head position. As only 805 | the head and the tail of the snake move, this is enough to keep the snake 806 | moving. 807 | 808 | The last subroutine, `spinWheels`, is just there because the game would run too 809 | fast otherwise. All `spinWheels` does is count `X` down from zero until it hits 810 | zero again. The first `dex` wraps, making `X` `#$ff`. 811 | -------------------------------------------------------------------------------- /javascripts/scale.fix.js: -------------------------------------------------------------------------------- 1 | var metas = document.getElementsByTagName('meta'); 2 | var i; 3 | if (navigator.userAgent.match(/iPhone/i)) { 4 | for (i=0; i` element will link to the contributor's GitHub Profile. For example: In 2007, Chris Wanstrath (@defunkt), PJ Hyett (@pjhyett), and Tom Preston-Werner (@mojombo) founded GitHub.\r\n\r\n### Support or Contact\r\nHaving trouble with Pages? Check out the documentation at http://help.github.com/pages or contact support@github.com and we’ll help you sort it out.","tagline":"","google":"UA-3174668-13","note":"Don't delete this file! It's used internally to help with page regeneration."} -------------------------------------------------------------------------------- /simulator.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: basic 3 | --- 4 | 5 |

Simulator

6 | 7 | To use the disassembler, click **Assemble**, then **Disassemble**. [Back to Easy 6502](index.html). 8 | 9 | {% include start.html %} 10 | start: 11 | jsr init 12 | 13 | loop: 14 | jsr drawMap 15 | jsr genMap 16 | jmp loop 17 | 18 | testMemory: 19 | rts 20 | pha 21 | txa 22 | pha 23 | 24 | lda #0 25 | ldx $10 26 | sta $500,x 27 | ldx $78 28 | lda #1 29 | sta $500,x 30 | stx $10 31 | 32 | lda #0 33 | ldx $11 34 | sta $500,x 35 | ldx $79 36 | lda #3 37 | sta $500,x 38 | stx $11 39 | 40 | lda #0 41 | ldx $12 42 | sta $500,x 43 | ldx $7a 44 | lda #4 45 | sta $500,x 46 | stx $12 47 | 48 | lda #0 49 | ldx $13 50 | sta $500,x 51 | ldx $7b 52 | lda #4 53 | sta $500,x 54 | stx $13 55 | 56 | pla 57 | tax 58 | pla 59 | 60 | rts 61 | 62 | init: 63 | ldx #0 64 | lda walls 65 | 66 | ;draw exactly 256 pixels of wall at top and bottom 67 | drawinitialwalls: 68 | sta $200,x ;draw the top bit of wall 69 | sta $400,x ;draw the bottom bit of wall 70 | dex ;count down from 0 71 | cpx #0 ;until we hit 0 72 | bne drawinitialwalls 73 | 74 | lda #$10 75 | sta $80 76 | ldx #$0f 77 | 78 | ;fill $81-$90 with $10 (initial wall offset) 79 | setinitialwalloffsets: 80 | sta $81,x ; target 81 | dex 82 | bpl setinitialwalloffsets 83 | rts 84 | 85 | ;-- 86 | 87 | drawMap: 88 | lda #$00 89 | sta $78 90 | lda #$20 91 | sta $79 92 | lda #$c0 93 | sta $7a 94 | lda #$e0 95 | sta $7b 96 | 97 | ldx #$0f 98 | drawLoop: 99 | lda $81,x 100 | sta $82,x ;shift wall offsets along 101 | 102 | tay 103 | sty $02 ;store current wall offset in $02 104 | lda pixels,y ;lookup current wall offset in pixels 105 | sta $00 ;and store it in $00 106 | iny 107 | lda pixels,y ;lookup current wall offset + 1 in pixels 108 | sta $01 ;and store it in $01 109 | ;$00 now points to a two-byte pixel memory location 110 | 111 | lda walls 112 | ldy $78 ;top edge of wall 113 | sta ($00),y 114 | iny 115 | sta ($00),y 116 | 117 | ldy $7b 118 | sta ($00),y ;bottom edge of wall 119 | iny 120 | sta ($00),y 121 | 122 | ldy $79 ;top edge of tunnel 123 | lda #0 ;black for tunnel 124 | sta ($00),y 125 | iny 126 | sta ($00),y 127 | 128 | ldy $7a 129 | sta ($00),y ;bottom edge of tunnel 130 | iny 131 | sta ($00),y 132 | 133 | ; move offsets right two pixels 134 | inc $78 135 | inc $79 136 | inc $7a 137 | inc $7b 138 | inc $78 139 | inc $79 140 | inc $7a 141 | inc $7b 142 | dex 143 | bpl drawLoop 144 | rts 145 | 146 | ;--- 147 | 148 | genMap: 149 | lda $80 ;$80 is next wall inflection point 150 | cmp $81 ;$81 is next wall offset 151 | beq newinflectionpoint 152 | lda $80 153 | clc 154 | sbc $81 ;is next wall offset above or below inflection point? 155 | bpl raisewalls 156 | bmi lowerwalls 157 | newinflectionpoint: 158 | lda $fe 159 | and #$f ;make 4-bit 160 | asl ;double (make even number) 161 | sta $80 ;set $80 to random value 162 | rts 163 | lowerwalls: 164 | dec $81 165 | dec $81 166 | rts 167 | raisewalls: 168 | inc $81 169 | inc $81 170 | rts 171 | 172 | pixels: 173 | dcb $00,$02,$20,$02,$40,$02,$60,$02 174 | dcb $80,$02,$a0,$02,$c0,$02,$e0,$02 175 | dcb $00,$03,$20,$03,$40,$03,$60,$03 176 | dcb $80,$03,$a0,$03,$c0,$03,$e0,$03 177 | 178 | walls: 179 | dcb $d 180 | {% include end.html %} 181 | -------------------------------------------------------------------------------- /simulator/assembler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 6502 assembler and simulator in Javascript 3 | * (C)2006-2010 Stian Soreng - www.6502asm.com 4 | * 5 | * Adapted by Nick Morgan 6 | * https://github.com/skilldrick/6502js 7 | * 8 | * Released under the GNU General Public License 9 | * see http://gnu.org/licenses/gpl.html 10 | */ 11 | 12 | 'use strict'; 13 | 14 | function SimulatorWidget(node) { 15 | var $node = $(node); 16 | var ui = UI(); 17 | var display = Display(); 18 | var memory = Memory(); 19 | var labels = Labels(); 20 | var simulator = Simulator(); 21 | var assembler = Assembler(); 22 | 23 | function initialize() { 24 | stripText(); 25 | ui.initialize(); 26 | display.initialize(); 27 | simulator.reset(); 28 | 29 | $node.find('.assembleButton').click(function () { 30 | assembler.assembleCode(); 31 | }); 32 | $node.find('.runButton').click(simulator.runBinary); 33 | $node.find('.runButton').click(simulator.stopDebugger); 34 | $node.find('.resetButton').click(simulator.reset); 35 | $node.find('.hexdumpButton').click(assembler.hexdump); 36 | $node.find('.disassembleButton').click(assembler.disassemble); 37 | $node.find('.debug').change(function () { 38 | var debug = $(this).is(':checked'); 39 | if (debug) { 40 | ui.debugOn(); 41 | simulator.enableDebugger(); 42 | } else { 43 | ui.debugOff(); 44 | simulator.stopDebugger(); 45 | } 46 | }); 47 | $node.find('.monitoring').change(function () { 48 | var state = this.checked; 49 | ui.toggleMonitor(state); 50 | simulator.toggleMonitor(state); 51 | }); 52 | $node.find('.start, .length').blur(simulator.handleMonitorRangeChange); 53 | $node.find('.stepButton').click(simulator.debugExec); 54 | $node.find('.gotoButton').click(simulator.gotoAddr); 55 | $node.find('.notesButton').click(ui.showNotes); 56 | 57 | var editor = $node.find('.code'); 58 | 59 | editor.on('keypress input', simulator.stop); 60 | editor.on('keypress input', ui.initialize); 61 | editor.keydown(ui.captureTabInEditor); 62 | 63 | $(document).keypress(memory.storeKeypress); 64 | 65 | simulator.handleMonitorRangeChange(); 66 | } 67 | 68 | function stripText() { 69 | //Remove leading and trailing space in textarea 70 | var text = $node.find('.code').val(); 71 | text = text.replace(/^\n+/, '').replace(/\s+$/, ''); 72 | $node.find('.code').val(text); 73 | } 74 | 75 | function UI() { 76 | var currentState; 77 | 78 | var start = { 79 | assemble: true, 80 | run: [false, 'Run'], 81 | reset: false, 82 | hexdump: false, 83 | disassemble: false, 84 | debug: [false, false] 85 | }; 86 | var assembled = { 87 | assemble: false, 88 | run: [true, 'Run'], 89 | reset: true, 90 | hexdump: true, 91 | disassemble: true, 92 | debug: [true, false] 93 | }; 94 | var running = { 95 | assemble: false, 96 | run: [true, 'Stop'], 97 | reset: true, 98 | hexdump: false, 99 | disassemble: false, 100 | debug: [true, false] 101 | }; 102 | var debugging = { 103 | assemble: false, 104 | reset: true, 105 | hexdump: true, 106 | disassemble: true, 107 | debug: [true, true] 108 | }; 109 | var postDebugging = { 110 | assemble: false, 111 | reset: true, 112 | hexdump: true, 113 | disassemble: true, 114 | debug: [true, false] 115 | }; 116 | 117 | 118 | function setState(state) { 119 | $node.find('.assembleButton').attr('disabled', !state.assemble); 120 | if (state.run) { 121 | $node.find('.runButton').attr('disabled', !state.run[0]); 122 | $node.find('.runButton').val(state.run[1]); 123 | } 124 | $node.find('.resetButton').attr('disabled', !state.reset); 125 | $node.find('.hexdumpButton').attr('disabled', !state.hexdump); 126 | $node.find('.disassembleButton').attr('disabled', !state.disassemble); 127 | $node.find('.debug').attr('disabled', !state.debug[0]); 128 | $node.find('.debug').attr('checked', state.debug[1]); 129 | $node.find('.stepButton').attr('disabled', !state.debug[1]); 130 | $node.find('.gotoButton').attr('disabled', !state.debug[1]); 131 | currentState = state; 132 | } 133 | 134 | function initialize() { 135 | setState(start); 136 | } 137 | 138 | function play() { 139 | setState(running); 140 | } 141 | 142 | function stop() { 143 | setState(assembled); 144 | } 145 | 146 | function debugOn() { 147 | setState(debugging); 148 | } 149 | 150 | function debugOff() { 151 | setState(postDebugging); 152 | } 153 | 154 | function assembleSuccess() { 155 | setState(assembled); 156 | } 157 | 158 | function toggleMonitor (state) { 159 | $node.find('.monitor').toggle(state); 160 | } 161 | 162 | function showNotes() { 163 | $node.find('.messages code').html($node.find('.notes').html()); 164 | } 165 | 166 | function captureTabInEditor(e) { 167 | // Tab Key 168 | if(e.keyCode === 9) { 169 | 170 | // Prevent focus loss 171 | e.preventDefault(); 172 | 173 | // Insert tab at caret position (instead of losing focus) 174 | var caretStart = this.selectionStart, 175 | caretEnd = this.selectionEnd, 176 | currentValue = this.value; 177 | 178 | this.value = currentValue.substring(0, caretStart) + "\t" + currentValue.substring(caretEnd); 179 | 180 | // Move cursor forwards one (after tab) 181 | this.selectionStart = this.selectionEnd = caretStart + 1; 182 | } 183 | } 184 | 185 | return { 186 | initialize: initialize, 187 | play: play, 188 | stop: stop, 189 | assembleSuccess: assembleSuccess, 190 | debugOn: debugOn, 191 | debugOff: debugOff, 192 | toggleMonitor: toggleMonitor, 193 | showNotes: showNotes, 194 | captureTabInEditor: captureTabInEditor 195 | }; 196 | } 197 | 198 | 199 | function Display() { 200 | var displayArray = []; 201 | var palette = [ 202 | "#000000", "#ffffff", "#880000", "#aaffee", 203 | "#cc44cc", "#00cc55", "#0000aa", "#eeee77", 204 | "#dd8855", "#664400", "#ff7777", "#333333", 205 | "#777777", "#aaff66", "#0088ff", "#bbbbbb" 206 | ]; 207 | var ctx; 208 | var width; 209 | var height; 210 | var pixelSize; 211 | var numX = 32; 212 | var numY = 32; 213 | 214 | function initialize() { 215 | var canvas = $node.find('.screen')[0]; 216 | width = canvas.width; 217 | height = canvas.height; 218 | pixelSize = width / numX; 219 | ctx = canvas.getContext('2d'); 220 | reset(); 221 | } 222 | 223 | function reset() { 224 | ctx.fillStyle = "black"; 225 | ctx.fillRect(0, 0, width, height); 226 | } 227 | 228 | function updatePixel(addr) { 229 | ctx.fillStyle = palette[memory.get(addr) & 0x0f]; 230 | var y = Math.floor((addr - 0x200) / 32); 231 | var x = (addr - 0x200) % 32; 232 | ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); 233 | } 234 | 235 | return { 236 | initialize: initialize, 237 | reset: reset, 238 | updatePixel: updatePixel 239 | }; 240 | } 241 | 242 | function Memory() { 243 | var memArray = new Array(0x600); 244 | 245 | function set(addr, val) { 246 | return memArray[addr] = val; 247 | } 248 | 249 | function get(addr) { 250 | return memArray[addr]; 251 | } 252 | 253 | function getWord(addr) { 254 | return get(addr) + (get(addr + 1) << 8); 255 | } 256 | 257 | // Poke a byte, don't touch any registers 258 | function storeByte(addr, value) { 259 | set(addr, value & 0xff); 260 | if ((addr >= 0x200) && (addr <= 0x5ff)) { 261 | display.updatePixel(addr); 262 | } 263 | } 264 | 265 | // Store keycode in ZP $ff 266 | function storeKeypress(e) { 267 | var value = e.which; 268 | memory.storeByte(0xff, value); 269 | } 270 | 271 | function format(start, length) { 272 | var html = ''; 273 | var n; 274 | 275 | for (var x = 0; x < length; x++) { 276 | if ((x & 15) === 0) { 277 | if (x > 0) { html += "\n"; } 278 | n = (start + x); 279 | html += num2hex(((n >> 8) & 0xff)); 280 | html += num2hex((n & 0xff)); 281 | html += ": "; 282 | } 283 | html += num2hex(memory.get(start + x)); 284 | html += " "; 285 | } 286 | return html; 287 | } 288 | 289 | return { 290 | set: set, 291 | get: get, 292 | getWord: getWord, 293 | storeByte: storeByte, 294 | storeKeypress: storeKeypress, 295 | format: format 296 | }; 297 | } 298 | 299 | function Simulator() { 300 | var regA = 0; 301 | var regX = 0; 302 | var regY = 0; 303 | var regP = 0; 304 | var regPC = 0x600; 305 | var regSP = 0xff; 306 | var codeRunning = false; 307 | var debug = false; 308 | var monitoring = false; 309 | var executeId; 310 | 311 | // Set zero and negative processor flags based on result 312 | function setNVflags(value) { 313 | if (value) { 314 | regP &= 0xfd; 315 | } else { 316 | regP |= 0x02; 317 | } 318 | if (value & 0x80) { 319 | regP |= 0x80; 320 | } else { 321 | regP &= 0x7f; 322 | } 323 | } 324 | 325 | function setCarryFlagFromBit0(value) { 326 | regP = (regP & 0xfe) | (value & 1); 327 | } 328 | 329 | function setCarryFlagFromBit7(value) { 330 | regP = (regP & 0xfe) | ((value >> 7) & 1); 331 | } 332 | 333 | function setNVflagsForRegA() { 334 | setNVflags(regA); 335 | } 336 | 337 | function setNVflagsForRegX() { 338 | setNVflags(regX); 339 | } 340 | 341 | function setNVflagsForRegY() { 342 | setNVflags(regY); 343 | } 344 | 345 | var ORA = setNVflagsForRegA; 346 | var AND = setNVflagsForRegA; 347 | var EOR = setNVflagsForRegA; 348 | var ASL = setNVflags; 349 | var LSR = setNVflags; 350 | var ROL = setNVflags; 351 | var ROR = setNVflags; 352 | var LDA = setNVflagsForRegA; 353 | var LDX = setNVflagsForRegX; 354 | var LDY = setNVflagsForRegY; 355 | 356 | function BIT(value) { 357 | if (value & 0x80) { 358 | regP |= 0x80; 359 | } else { 360 | regP &= 0x7f; 361 | } 362 | if (value & 0x40) { 363 | regP |= 0x40; 364 | } else { 365 | regP &= ~0x40; 366 | } 367 | if (regA & value) { 368 | regP &= 0xfd; 369 | } else { 370 | regP |= 0x02; 371 | } 372 | } 373 | 374 | function CLC() { 375 | regP &= 0xfe; 376 | } 377 | 378 | function SEC() { 379 | regP |= 1; 380 | } 381 | 382 | 383 | function CLV() { 384 | regP &= 0xbf; 385 | } 386 | 387 | function setOverflow() { 388 | regP |= 0x40; 389 | } 390 | 391 | function DEC(addr) { 392 | var value = memory.get(addr); 393 | value--; 394 | value &= 0xff; 395 | memory.storeByte(addr, value); 396 | setNVflags(value); 397 | } 398 | 399 | function INC(addr) { 400 | var value = memory.get(addr); 401 | value++; 402 | value &= 0xff; 403 | memory.storeByte(addr, value); 404 | setNVflags(value); 405 | } 406 | 407 | function jumpBranch(offset) { 408 | if (offset > 0x7f) { 409 | regPC = (regPC - (0x100 - offset)); 410 | } else { 411 | regPC = (regPC + offset); 412 | } 413 | } 414 | 415 | function overflowSet() { 416 | return regP & 0x40; 417 | } 418 | 419 | function decimalMode() { 420 | return regP & 8; 421 | } 422 | 423 | function carrySet() { 424 | return regP & 1; 425 | } 426 | 427 | function negativeSet() { 428 | return regP & 0x80; 429 | } 430 | 431 | function zeroSet() { 432 | return regP & 0x02; 433 | } 434 | 435 | function doCompare(reg, val) { 436 | if (reg >= val) { 437 | SEC(); 438 | } else { 439 | CLC(); 440 | } 441 | val = (reg - val); 442 | setNVflags(val); 443 | } 444 | 445 | function testSBC(value) { 446 | var tmp, w; 447 | if ((regA ^ value) & 0x80) { 448 | setOverflow(); 449 | } else { 450 | CLV(); 451 | } 452 | 453 | if (decimalMode()) { 454 | tmp = 0xf + (regA & 0xf) - (value & 0xf) + carrySet(); 455 | if (tmp < 0x10) { 456 | w = 0; 457 | tmp -= 6; 458 | } else { 459 | w = 0x10; 460 | tmp -= 0x10; 461 | } 462 | w += 0xf0 + (regA & 0xf0) - (value & 0xf0); 463 | if (w < 0x100) { 464 | CLC(); 465 | if (overflowSet() && w < 0x80) { CLV(); } 466 | w -= 0x60; 467 | } else { 468 | SEC(); 469 | if (overflowSet() && w >= 0x180) { CLV(); } 470 | } 471 | w += tmp; 472 | } else { 473 | w = 0xff + regA - value + carrySet(); 474 | if (w < 0x100) { 475 | CLC(); 476 | if (overflowSet() && w < 0x80) { CLV(); } 477 | } else { 478 | SEC(); 479 | if (overflowSet() && w >= 0x180) { CLV(); } 480 | } 481 | } 482 | regA = w & 0xff; 483 | setNVflagsForRegA(); 484 | } 485 | 486 | function testADC(value) { 487 | var tmp; 488 | if ((regA ^ value) & 0x80) { 489 | CLV(); 490 | } else { 491 | setOverflow(); 492 | } 493 | 494 | if (decimalMode()) { 495 | tmp = (regA & 0xf) + (value & 0xf) + carrySet(); 496 | if (tmp >= 10) { 497 | tmp = 0x10 | ((tmp + 6) & 0xf); 498 | } 499 | tmp += (regA & 0xf0) + (value & 0xf0); 500 | if (tmp >= 160) { 501 | SEC(); 502 | if (overflowSet() && tmp >= 0x180) { CLV(); } 503 | tmp += 0x60; 504 | } else { 505 | CLC(); 506 | if (overflowSet() && tmp < 0x80) { CLV(); } 507 | } 508 | } else { 509 | tmp = regA + value + carrySet(); 510 | if (tmp >= 0x100) { 511 | SEC(); 512 | if (overflowSet() && tmp >= 0x180) { CLV(); } 513 | } else { 514 | CLC(); 515 | if (overflowSet() && tmp < 0x80) { CLV(); } 516 | } 517 | } 518 | regA = tmp & 0xff; 519 | setNVflagsForRegA(); 520 | } 521 | 522 | var instructions = { 523 | i00: function () { 524 | codeRunning = false; 525 | //BRK 526 | }, 527 | 528 | i01: function () { 529 | var zp = (popByte() + regX) & 0xff; 530 | var addr = memory.getWord(zp); 531 | var value = memory.get(addr); 532 | regA |= value; 533 | ORA(); 534 | }, 535 | 536 | i05: function () { 537 | var zp = popByte(); 538 | regA |= memory.get(zp); 539 | ORA(); 540 | }, 541 | 542 | i06: function () { 543 | var zp = popByte(); 544 | var value = memory.get(zp); 545 | setCarryFlagFromBit7(value); 546 | value = (value << 1) & 0xff; 547 | memory.storeByte(zp, value); 548 | ASL(value); 549 | }, 550 | 551 | i08: function () { 552 | stackPush(regP | 0x30); 553 | //PHP 554 | }, 555 | 556 | i09: function () { 557 | regA |= popByte(); 558 | ORA(); 559 | }, 560 | 561 | i0a: function () { 562 | setCarryFlagFromBit7(regA); 563 | regA = (regA << 1) & 0xff; 564 | ASL(regA); 565 | }, 566 | 567 | i0d: function () { 568 | regA |= memory.get(popWord()); 569 | ORA(); 570 | }, 571 | 572 | i0e: function () { 573 | var addr = popWord(); 574 | var value = memory.get(addr); 575 | setCarryFlagFromBit7(value); 576 | value = (value << 1) & 0xff; 577 | memory.storeByte(addr, value); 578 | ASL(value); 579 | }, 580 | 581 | i10: function () { 582 | var offset = popByte(); 583 | if (!negativeSet()) { jumpBranch(offset); } 584 | //BPL 585 | }, 586 | 587 | i11: function () { 588 | var zp = popByte(); 589 | var value = memory.getWord(zp) + regY; 590 | regA |= memory.get(value); 591 | ORA(); 592 | }, 593 | 594 | i15: function () { 595 | var addr = (popByte() + regX) & 0xff; 596 | regA |= memory.get(addr); 597 | ORA(); 598 | }, 599 | 600 | i16: function () { 601 | var addr = (popByte() + regX) & 0xff; 602 | var value = memory.get(addr); 603 | setCarryFlagFromBit7(value); 604 | value = (value << 1) & 0xff; 605 | memory.storeByte(addr, value); 606 | ASL(value); 607 | }, 608 | 609 | i18: function () { 610 | CLC(); 611 | }, 612 | 613 | i19: function () { 614 | var addr = popWord() + regY; 615 | regA |= memory.get(addr); 616 | ORA(); 617 | }, 618 | 619 | i1d: function () { 620 | var addr = popWord() + regX; 621 | regA |= memory.get(addr); 622 | ORA(); 623 | }, 624 | 625 | i1e: function () { 626 | var addr = popWord() + regX; 627 | var value = memory.get(addr); 628 | setCarryFlagFromBit7(value); 629 | value = (value << 1) & 0xff; 630 | memory.storeByte(addr, value); 631 | ASL(value); 632 | }, 633 | 634 | i20: function () { 635 | var addr = popWord(); 636 | var currAddr = regPC - 1; 637 | stackPush(((currAddr >> 8) & 0xff)); 638 | stackPush((currAddr & 0xff)); 639 | regPC = addr; 640 | //JSR 641 | }, 642 | 643 | i21: function () { 644 | var zp = (popByte() + regX) & 0xff; 645 | var addr = memory.getWord(zp); 646 | var value = memory.get(addr); 647 | regA &= value; 648 | AND(); 649 | }, 650 | 651 | i24: function () { 652 | var zp = popByte(); 653 | var value = memory.get(zp); 654 | BIT(value); 655 | }, 656 | 657 | i25: function () { 658 | var zp = popByte(); 659 | regA &= memory.get(zp); 660 | AND(); 661 | }, 662 | 663 | i26: function () { 664 | var sf = carrySet(); 665 | var addr = popByte(); 666 | var value = memory.get(addr); 667 | setCarryFlagFromBit7(value); 668 | value = (value << 1) & 0xff; 669 | value |= sf; 670 | memory.storeByte(addr, value); 671 | ROL(value); 672 | }, 673 | 674 | i28: function () { 675 | regP = stackPop() | 0x30; // There is no B bit! 676 | //PLP 677 | }, 678 | 679 | i29: function () { 680 | regA &= popByte(); 681 | AND(); 682 | }, 683 | 684 | i2a: function () { 685 | var sf = carrySet(); 686 | setCarryFlagFromBit7(regA); 687 | regA = (regA << 1) & 0xff; 688 | regA |= sf; 689 | ROL(regA); 690 | }, 691 | 692 | i2c: function () { 693 | var value = memory.get(popWord()); 694 | BIT(value); 695 | }, 696 | 697 | i2d: function () { 698 | var value = memory.get(popWord()); 699 | regA &= value; 700 | AND(); 701 | }, 702 | 703 | i2e: function () { 704 | var sf = carrySet(); 705 | var addr = popWord(); 706 | var value = memory.get(addr); 707 | setCarryFlagFromBit7(value); 708 | value = (value << 1) & 0xff; 709 | value |= sf; 710 | memory.storeByte(addr, value); 711 | ROL(value); 712 | }, 713 | 714 | i30: function () { 715 | var offset = popByte(); 716 | if (negativeSet()) { jumpBranch(offset); } 717 | //BMI 718 | }, 719 | 720 | i31: function () { 721 | var zp = popByte(); 722 | var value = memory.getWord(zp) + regY; 723 | regA &= memory.get(value); 724 | AND(); 725 | }, 726 | 727 | i35: function () { 728 | var addr = (popByte() + regX) & 0xff; 729 | regA &= memory.get(addr); 730 | AND(); 731 | }, 732 | 733 | i36: function () { 734 | var sf = carrySet(); 735 | var addr = (popByte() + regX) & 0xff; 736 | var value = memory.get(addr); 737 | setCarryFlagFromBit7(value); 738 | value = (value << 1) & 0xff; 739 | value |= sf; 740 | memory.storeByte(addr, value); 741 | ROL(value); 742 | }, 743 | 744 | i38: function () { 745 | SEC(); 746 | }, 747 | 748 | i39: function () { 749 | var addr = popWord() + regY; 750 | var value = memory.get(addr); 751 | regA &= value; 752 | AND(); 753 | }, 754 | 755 | i3d: function () { 756 | var addr = popWord() + regX; 757 | var value = memory.get(addr); 758 | regA &= value; 759 | AND(); 760 | }, 761 | 762 | i3e: function () { 763 | var sf = carrySet(); 764 | var addr = popWord() + regX; 765 | var value = memory.get(addr); 766 | setCarryFlagFromBit7(value); 767 | value = (value << 1) & 0xff; 768 | value |= sf; 769 | memory.storeByte(addr, value); 770 | ROL(value); 771 | }, 772 | 773 | i40: function () { 774 | regP = stackPop() | 0x30; // There is no B bit! 775 | regPC = stackPop() | (stackPop() << 8); 776 | //RTI 777 | }, 778 | 779 | i41: function () { 780 | var zp = (popByte() + regX) & 0xff; 781 | var value = memory.getWord(zp); 782 | regA ^= memory.get(value); 783 | EOR(); 784 | }, 785 | 786 | i45: function () { 787 | var addr = popByte() & 0xff; 788 | var value = memory.get(addr); 789 | regA ^= value; 790 | EOR(); 791 | }, 792 | 793 | i46: function () { 794 | var addr = popByte() & 0xff; 795 | var value = memory.get(addr); 796 | setCarryFlagFromBit0(value); 797 | value = value >> 1; 798 | memory.storeByte(addr, value); 799 | LSR(value); 800 | }, 801 | 802 | i48: function () { 803 | stackPush(regA); 804 | //PHA 805 | }, 806 | 807 | i49: function () { 808 | regA ^= popByte(); 809 | EOR(); 810 | }, 811 | 812 | i4a: function () { 813 | setCarryFlagFromBit0(regA); 814 | regA = regA >> 1; 815 | LSR(regA); 816 | }, 817 | 818 | i4c: function () { 819 | regPC = popWord(); 820 | //JMP 821 | }, 822 | 823 | i4d: function () { 824 | var addr = popWord(); 825 | var value = memory.get(addr); 826 | regA ^= value; 827 | EOR(); 828 | }, 829 | 830 | i4e: function () { 831 | var addr = popWord(); 832 | var value = memory.get(addr); 833 | setCarryFlagFromBit0(value); 834 | value = value >> 1; 835 | memory.storeByte(addr, value); 836 | LSR(value); 837 | }, 838 | 839 | i50: function () { 840 | var offset = popByte(); 841 | if (!overflowSet()) { jumpBranch(offset); } 842 | //BVC 843 | }, 844 | 845 | i51: function () { 846 | var zp = popByte(); 847 | var value = memory.getWord(zp) + regY; 848 | regA ^= memory.get(value); 849 | EOR(); 850 | }, 851 | 852 | i55: function () { 853 | var addr = (popByte() + regX) & 0xff; 854 | regA ^= memory.get(addr); 855 | EOR(); 856 | }, 857 | 858 | i56: function () { 859 | var addr = (popByte() + regX) & 0xff; 860 | var value = memory.get(addr); 861 | setCarryFlagFromBit0(value); 862 | value = value >> 1; 863 | memory.storeByte(addr, value); 864 | LSR(value); 865 | }, 866 | 867 | i58: function () { 868 | regP &= ~0x04; 869 | throw new Error("Interrupts not implemented"); 870 | //CLI 871 | }, 872 | 873 | i59: function () { 874 | var addr = popWord() + regY; 875 | var value = memory.get(addr); 876 | regA ^= value; 877 | EOR(); 878 | }, 879 | 880 | i5d: function () { 881 | var addr = popWord() + regX; 882 | var value = memory.get(addr); 883 | regA ^= value; 884 | EOR(); 885 | }, 886 | 887 | i5e: function () { 888 | var addr = popWord() + regX; 889 | var value = memory.get(addr); 890 | setCarryFlagFromBit0(value); 891 | value = value >> 1; 892 | memory.storeByte(addr, value); 893 | LSR(value); 894 | }, 895 | 896 | i60: function () { 897 | regPC = (stackPop() | (stackPop() << 8)) + 1; 898 | //RTS 899 | }, 900 | 901 | i61: function () { 902 | var zp = (popByte() + regX) & 0xff; 903 | var addr = memory.getWord(zp); 904 | var value = memory.get(addr); 905 | testADC(value); 906 | //ADC 907 | }, 908 | 909 | i65: function () { 910 | var addr = popByte(); 911 | var value = memory.get(addr); 912 | testADC(value); 913 | //ADC 914 | }, 915 | 916 | i66: function () { 917 | var sf = carrySet(); 918 | var addr = popByte(); 919 | var value = memory.get(addr); 920 | setCarryFlagFromBit0(value); 921 | value = value >> 1; 922 | if (sf) { value |= 0x80; } 923 | memory.storeByte(addr, value); 924 | ROR(value); 925 | }, 926 | 927 | i68: function () { 928 | regA = stackPop(); 929 | setNVflagsForRegA(); 930 | //PLA 931 | }, 932 | 933 | i69: function () { 934 | var value = popByte(); 935 | testADC(value); 936 | //ADC 937 | }, 938 | 939 | i6a: function () { 940 | var sf = carrySet(); 941 | setCarryFlagFromBit0(regA); 942 | regA = regA >> 1; 943 | if (sf) { regA |= 0x80; } 944 | ROR(regA); 945 | }, 946 | 947 | i6c: function () { 948 | regPC = memory.getWord(popWord()); 949 | //JMP 950 | }, 951 | 952 | i6d: function () { 953 | var addr = popWord(); 954 | var value = memory.get(addr); 955 | testADC(value); 956 | //ADC 957 | }, 958 | 959 | i6e: function () { 960 | var sf = carrySet(); 961 | var addr = popWord(); 962 | var value = memory.get(addr); 963 | setCarryFlagFromBit0(value); 964 | value = value >> 1; 965 | if (sf) { value |= 0x80; } 966 | memory.storeByte(addr, value); 967 | ROR(value); 968 | }, 969 | 970 | i70: function () { 971 | var offset = popByte(); 972 | if (overflowSet()) { jumpBranch(offset); } 973 | //BVS 974 | }, 975 | 976 | i71: function () { 977 | var zp = popByte(); 978 | var addr = memory.getWord(zp); 979 | var value = memory.get(addr + regY); 980 | testADC(value); 981 | //ADC 982 | }, 983 | 984 | i75: function () { 985 | var addr = (popByte() + regX) & 0xff; 986 | var value = memory.get(addr); 987 | testADC(value); 988 | //ADC 989 | }, 990 | 991 | i76: function () { 992 | var sf = carrySet(); 993 | var addr = (popByte() + regX) & 0xff; 994 | var value = memory.get(addr); 995 | setCarryFlagFromBit0(value); 996 | value = value >> 1; 997 | if (sf) { value |= 0x80; } 998 | memory.storeByte(addr, value); 999 | ROR(value); 1000 | }, 1001 | 1002 | i78: function () { 1003 | regP |= 0x04; 1004 | throw new Error("Interrupts not implemented"); 1005 | //SEI 1006 | }, 1007 | 1008 | i79: function () { 1009 | var addr = popWord(); 1010 | var value = memory.get(addr + regY); 1011 | testADC(value); 1012 | //ADC 1013 | }, 1014 | 1015 | i7d: function () { 1016 | var addr = popWord(); 1017 | var value = memory.get(addr + regX); 1018 | testADC(value); 1019 | //ADC 1020 | }, 1021 | 1022 | i7e: function () { 1023 | var sf = carrySet(); 1024 | var addr = popWord() + regX; 1025 | var value = memory.get(addr); 1026 | setCarryFlagFromBit0(value); 1027 | value = value >> 1; 1028 | if (sf) { value |= 0x80; } 1029 | memory.storeByte(addr, value); 1030 | ROR(value); 1031 | }, 1032 | 1033 | i81: function () { 1034 | var zp = (popByte() + regX) & 0xff; 1035 | var addr = memory.getWord(zp); 1036 | memory.storeByte(addr, regA); 1037 | //STA 1038 | }, 1039 | 1040 | i84: function () { 1041 | memory.storeByte(popByte(), regY); 1042 | //STY 1043 | }, 1044 | 1045 | i85: function () { 1046 | memory.storeByte(popByte(), regA); 1047 | //STA 1048 | }, 1049 | 1050 | i86: function () { 1051 | memory.storeByte(popByte(), regX); 1052 | //STX 1053 | }, 1054 | 1055 | i88: function () { 1056 | regY = (regY - 1) & 0xff; 1057 | setNVflagsForRegY(); 1058 | //DEY 1059 | }, 1060 | 1061 | i8a: function () { 1062 | regA = regX & 0xff; 1063 | setNVflagsForRegA(); 1064 | //TXA 1065 | }, 1066 | 1067 | i8c: function () { 1068 | memory.storeByte(popWord(), regY); 1069 | //STY 1070 | }, 1071 | 1072 | i8d: function () { 1073 | memory.storeByte(popWord(), regA); 1074 | //STA 1075 | }, 1076 | 1077 | i8e: function () { 1078 | memory.storeByte(popWord(), regX); 1079 | //STX 1080 | }, 1081 | 1082 | i90: function () { 1083 | var offset = popByte(); 1084 | if (!carrySet()) { jumpBranch(offset); } 1085 | //BCC 1086 | }, 1087 | 1088 | i91: function () { 1089 | var zp = popByte(); 1090 | var addr = memory.getWord(zp) + regY; 1091 | memory.storeByte(addr, regA); 1092 | //STA 1093 | }, 1094 | 1095 | i94: function () { 1096 | memory.storeByte((popByte() + regX) & 0xff, regY); 1097 | //STY 1098 | }, 1099 | 1100 | i95: function () { 1101 | memory.storeByte((popByte() + regX) & 0xff, regA); 1102 | //STA 1103 | }, 1104 | 1105 | i96: function () { 1106 | memory.storeByte((popByte() + regY) & 0xff, regX); 1107 | //STX 1108 | }, 1109 | 1110 | i98: function () { 1111 | regA = regY & 0xff; 1112 | setNVflagsForRegA(); 1113 | //TYA 1114 | }, 1115 | 1116 | i99: function () { 1117 | memory.storeByte(popWord() + regY, regA); 1118 | //STA 1119 | }, 1120 | 1121 | i9a: function () { 1122 | regSP = regX & 0xff; 1123 | //TXS 1124 | }, 1125 | 1126 | i9d: function () { 1127 | var addr = popWord(); 1128 | memory.storeByte(addr + regX, regA); 1129 | //STA 1130 | }, 1131 | 1132 | ia0: function () { 1133 | regY = popByte(); 1134 | LDY(); 1135 | }, 1136 | 1137 | ia1: function () { 1138 | var zp = (popByte() + regX) & 0xff; 1139 | var addr = memory.getWord(zp); 1140 | regA = memory.get(addr); 1141 | LDA(); 1142 | }, 1143 | 1144 | ia2: function () { 1145 | regX = popByte(); 1146 | LDX(); 1147 | }, 1148 | 1149 | ia4: function () { 1150 | regY = memory.get(popByte()); 1151 | LDY(); 1152 | }, 1153 | 1154 | ia5: function () { 1155 | regA = memory.get(popByte()); 1156 | LDA(); 1157 | }, 1158 | 1159 | ia6: function () { 1160 | regX = memory.get(popByte()); 1161 | LDX(); 1162 | }, 1163 | 1164 | ia8: function () { 1165 | regY = regA & 0xff; 1166 | setNVflagsForRegY(); 1167 | //TAY 1168 | }, 1169 | 1170 | ia9: function () { 1171 | regA = popByte(); 1172 | LDA(); 1173 | }, 1174 | 1175 | iaa: function () { 1176 | regX = regA & 0xff; 1177 | setNVflagsForRegX(); 1178 | //TAX 1179 | }, 1180 | 1181 | iac: function () { 1182 | regY = memory.get(popWord()); 1183 | LDY(); 1184 | }, 1185 | 1186 | iad: function () { 1187 | regA = memory.get(popWord()); 1188 | LDA(); 1189 | }, 1190 | 1191 | iae: function () { 1192 | regX = memory.get(popWord()); 1193 | LDX(); 1194 | }, 1195 | 1196 | ib0: function () { 1197 | var offset = popByte(); 1198 | if (carrySet()) { jumpBranch(offset); } 1199 | //BCS 1200 | }, 1201 | 1202 | ib1: function () { 1203 | var zp = popByte(); 1204 | var addr = memory.getWord(zp) + regY; 1205 | regA = memory.get(addr); 1206 | LDA(); 1207 | }, 1208 | 1209 | ib4: function () { 1210 | regY = memory.get((popByte() + regX) & 0xff); 1211 | LDY(); 1212 | }, 1213 | 1214 | ib5: function () { 1215 | regA = memory.get((popByte() + regX) & 0xff); 1216 | LDA(); 1217 | }, 1218 | 1219 | ib6: function () { 1220 | regX = memory.get((popByte() + regY) & 0xff); 1221 | LDX(); 1222 | }, 1223 | 1224 | ib8: function () { 1225 | CLV(); 1226 | }, 1227 | 1228 | ib9: function () { 1229 | var addr = popWord() + regY; 1230 | regA = memory.get(addr); 1231 | LDA(); 1232 | }, 1233 | 1234 | iba: function () { 1235 | regX = regSP & 0xff; 1236 | LDX(); 1237 | //TSX 1238 | }, 1239 | 1240 | ibc: function () { 1241 | var addr = popWord() + regX; 1242 | regY = memory.get(addr); 1243 | LDY(); 1244 | }, 1245 | 1246 | ibd: function () { 1247 | var addr = popWord() + regX; 1248 | regA = memory.get(addr); 1249 | LDA(); 1250 | }, 1251 | 1252 | ibe: function () { 1253 | var addr = popWord() + regY; 1254 | regX = memory.get(addr); 1255 | LDX(); 1256 | }, 1257 | 1258 | ic0: function () { 1259 | var value = popByte(); 1260 | doCompare(regY, value); 1261 | //CPY 1262 | }, 1263 | 1264 | ic1: function () { 1265 | var zp = (popByte() + regX) & 0xff; 1266 | var addr = memory.getWord(zp); 1267 | var value = memory.get(addr); 1268 | doCompare(regA, value); 1269 | //CPA 1270 | }, 1271 | 1272 | ic4: function () { 1273 | var value = memory.get(popByte()); 1274 | doCompare(regY, value); 1275 | //CPY 1276 | }, 1277 | 1278 | ic5: function () { 1279 | var value = memory.get(popByte()); 1280 | doCompare(regA, value); 1281 | //CPA 1282 | }, 1283 | 1284 | ic6: function () { 1285 | var zp = popByte(); 1286 | DEC(zp); 1287 | }, 1288 | 1289 | ic8: function () { 1290 | regY = (regY + 1) & 0xff; 1291 | setNVflagsForRegY(); 1292 | //INY 1293 | }, 1294 | 1295 | ic9: function () { 1296 | var value = popByte(); 1297 | doCompare(regA, value); 1298 | //CMP 1299 | }, 1300 | 1301 | ica: function () { 1302 | regX = (regX - 1) & 0xff; 1303 | setNVflagsForRegX(); 1304 | //DEX 1305 | }, 1306 | 1307 | icc: function () { 1308 | var value = memory.get(popWord()); 1309 | doCompare(regY, value); 1310 | //CPY 1311 | }, 1312 | 1313 | icd: function () { 1314 | var value = memory.get(popWord()); 1315 | doCompare(regA, value); 1316 | //CPA 1317 | }, 1318 | 1319 | ice: function () { 1320 | var addr = popWord(); 1321 | DEC(addr); 1322 | }, 1323 | 1324 | id0: function () { 1325 | var offset = popByte(); 1326 | if (!zeroSet()) { jumpBranch(offset); } 1327 | //BNE 1328 | }, 1329 | 1330 | id1: function () { 1331 | var zp = popByte(); 1332 | var addr = memory.getWord(zp) + regY; 1333 | var value = memory.get(addr); 1334 | doCompare(regA, value); 1335 | //CMP 1336 | }, 1337 | 1338 | id5: function () { 1339 | var value = memory.get((popByte() + regX) & 0xff); 1340 | doCompare(regA, value); 1341 | //CMP 1342 | }, 1343 | 1344 | id6: function () { 1345 | var addr = (popByte() + regX) & 0xff; 1346 | DEC(addr); 1347 | }, 1348 | 1349 | id8: function () { 1350 | regP &= 0xf7; 1351 | //CLD 1352 | }, 1353 | 1354 | id9: function () { 1355 | var addr = popWord() + regY; 1356 | var value = memory.get(addr); 1357 | doCompare(regA, value); 1358 | //CMP 1359 | }, 1360 | 1361 | idd: function () { 1362 | var addr = popWord() + regX; 1363 | var value = memory.get(addr); 1364 | doCompare(regA, value); 1365 | //CMP 1366 | }, 1367 | 1368 | ide: function () { 1369 | var addr = popWord() + regX; 1370 | DEC(addr); 1371 | }, 1372 | 1373 | ie0: function () { 1374 | var value = popByte(); 1375 | doCompare(regX, value); 1376 | //CPX 1377 | }, 1378 | 1379 | ie1: function () { 1380 | var zp = (popByte() + regX) & 0xff; 1381 | var addr = memory.getWord(zp); 1382 | var value = memory.get(addr); 1383 | testSBC(value); 1384 | //SBC 1385 | }, 1386 | 1387 | ie4: function () { 1388 | var value = memory.get(popByte()); 1389 | doCompare(regX, value); 1390 | //CPX 1391 | }, 1392 | 1393 | ie5: function () { 1394 | var addr = popByte(); 1395 | var value = memory.get(addr); 1396 | testSBC(value); 1397 | //SBC 1398 | }, 1399 | 1400 | ie6: function () { 1401 | var zp = popByte(); 1402 | INC(zp); 1403 | }, 1404 | 1405 | ie8: function () { 1406 | regX = (regX + 1) & 0xff; 1407 | setNVflagsForRegX(); 1408 | //INX 1409 | }, 1410 | 1411 | ie9: function () { 1412 | var value = popByte(); 1413 | testSBC(value); 1414 | //SBC 1415 | }, 1416 | 1417 | iea: function () { 1418 | //NOP 1419 | }, 1420 | 1421 | i42: function () { 1422 | //WDM -- pseudo op for emulator: arg 0 to output A to message box 1423 | var value = popByte(); 1424 | if (value == 0) 1425 | message(String.fromCharCode(regA)); 1426 | }, 1427 | 1428 | iec: function () { 1429 | var value = memory.get(popWord()); 1430 | doCompare(regX, value); 1431 | //CPX 1432 | }, 1433 | 1434 | ied: function () { 1435 | var addr = popWord(); 1436 | var value = memory.get(addr); 1437 | testSBC(value); 1438 | //SBC 1439 | }, 1440 | 1441 | iee: function () { 1442 | var addr = popWord(); 1443 | INC(addr); 1444 | }, 1445 | 1446 | if0: function () { 1447 | var offset = popByte(); 1448 | if (zeroSet()) { jumpBranch(offset); } 1449 | //BEQ 1450 | }, 1451 | 1452 | if1: function () { 1453 | var zp = popByte(); 1454 | var addr = memory.getWord(zp); 1455 | var value = memory.get(addr + regY); 1456 | testSBC(value); 1457 | //SBC 1458 | }, 1459 | 1460 | if5: function () { 1461 | var addr = (popByte() + regX) & 0xff; 1462 | var value = memory.get(addr); 1463 | testSBC(value); 1464 | //SBC 1465 | }, 1466 | 1467 | if6: function () { 1468 | var addr = (popByte() + regX) & 0xff; 1469 | INC(addr); 1470 | }, 1471 | 1472 | if8: function () { 1473 | regP |= 8; 1474 | //SED 1475 | }, 1476 | 1477 | if9: function () { 1478 | var addr = popWord(); 1479 | var value = memory.get(addr + regY); 1480 | testSBC(value); 1481 | //SBC 1482 | }, 1483 | 1484 | ifd: function () { 1485 | var addr = popWord(); 1486 | var value = memory.get(addr + regX); 1487 | testSBC(value); 1488 | //SBC 1489 | }, 1490 | 1491 | ife: function () { 1492 | var addr = popWord() + regX; 1493 | INC(addr); 1494 | }, 1495 | 1496 | ierr: function () { 1497 | message("Address $" + addr2hex(regPC) + " - unknown opcode"); 1498 | codeRunning = false; 1499 | } 1500 | }; 1501 | 1502 | function stackPush(value) { 1503 | memory.set((regSP & 0xff) + 0x100, value & 0xff); 1504 | regSP--; 1505 | if (regSP < 0) { 1506 | regSP &= 0xff; 1507 | message("6502 Stack filled! Wrapping..."); 1508 | } 1509 | } 1510 | 1511 | function stackPop() { 1512 | var value; 1513 | regSP++; 1514 | if (regSP >= 0x100) { 1515 | regSP &= 0xff; 1516 | message("6502 Stack emptied! Wrapping..."); 1517 | } 1518 | value = memory.get(regSP + 0x100); 1519 | return value; 1520 | } 1521 | 1522 | // Pops a byte 1523 | function popByte() { 1524 | return(memory.get(regPC++) & 0xff); 1525 | } 1526 | 1527 | // Pops a little-endian word 1528 | function popWord() { 1529 | return popByte() + (popByte() << 8); 1530 | } 1531 | 1532 | // Executes the assembled code 1533 | function runBinary() { 1534 | if (codeRunning) { 1535 | // Switch OFF everything 1536 | stop(); 1537 | ui.stop(); 1538 | } else { 1539 | ui.play(); 1540 | codeRunning = true; 1541 | executeId = setInterval(multiExecute, 15); 1542 | } 1543 | } 1544 | 1545 | function multiExecute() { 1546 | if (!debug) { 1547 | // use a prime number of iterations to avoid aliasing effects 1548 | 1549 | for (var w = 0; w < 97; w++) { 1550 | execute(); 1551 | } 1552 | } 1553 | updateDebugInfo(); 1554 | } 1555 | 1556 | 1557 | function executeNextInstruction() { 1558 | var instructionName = popByte().toString(16).toLowerCase(); 1559 | if (instructionName.length === 1) { 1560 | instructionName = '0' + instructionName; 1561 | } 1562 | var instruction = instructions['i' + instructionName]; 1563 | 1564 | if (instruction) { 1565 | instruction(); 1566 | } else { 1567 | instructions.ierr(); 1568 | } 1569 | } 1570 | 1571 | // Executes one instruction. This is the main part of the CPU simulator. 1572 | function execute(debugging) { 1573 | if (!codeRunning && !debugging) { return; } 1574 | 1575 | setRandomByte(); 1576 | executeNextInstruction(); 1577 | 1578 | if ((regPC === 0) || (!codeRunning && !debugging)) { 1579 | stop(); 1580 | message("Program end at PC=$" + addr2hex(regPC - 1)); 1581 | ui.stop(); 1582 | } 1583 | } 1584 | 1585 | function setRandomByte() { 1586 | memory.set(0xfe, Math.floor(Math.random() * 256)); 1587 | } 1588 | 1589 | function updateMonitor() { 1590 | if (monitoring) { 1591 | var start = parseInt($node.find('.start').val(), 16); 1592 | var length = parseInt($node.find('.length').val(), 16); 1593 | 1594 | var end = start + length - 1; 1595 | 1596 | var monitorNode = $node.find('.monitor code'); 1597 | 1598 | if (!isNaN(start) && !isNaN(length) && start >= 0 && length > 0 && end <= 0xffff) { 1599 | monitorNode.html(memory.format(start, length)); 1600 | } else { 1601 | monitorNode.html('Cannot monitor this range. Valid ranges are between $0000 and $ffff, inclusive.'); 1602 | } 1603 | } 1604 | } 1605 | 1606 | function handleMonitorRangeChange() { 1607 | 1608 | var $start = $node.find('.start'), 1609 | $length = $node.find('.length'), 1610 | start = parseInt($start.val(), 16), 1611 | length = parseInt($length.val(), 16), 1612 | end = start + length - 1; 1613 | 1614 | $start.removeClass('monitor-invalid'); 1615 | $length.removeClass('monitor-invalid'); 1616 | 1617 | if(isNaN(start) || start < 0 || start > 0xffff) { 1618 | 1619 | $start.addClass('monitor-invalid'); 1620 | 1621 | } else if(isNaN(length) || end > 0xffff) { 1622 | 1623 | $length.addClass('monitor-invalid'); 1624 | } 1625 | } 1626 | 1627 | // Execute one instruction and print values 1628 | function debugExec() { 1629 | //if (codeRunning) { 1630 | execute(true); 1631 | //} 1632 | updateDebugInfo(); 1633 | } 1634 | 1635 | function updateDebugInfo() { 1636 | var html = "A=$" + num2hex(regA) + " X=$" + num2hex(regX) + " Y=$" + num2hex(regY) + "
"; 1637 | html += "SP=$" + num2hex(regSP) + " PC=$" + addr2hex(regPC); 1638 | html += "
"; 1639 | html += "NV-BDIZC
"; 1640 | for (var i = 7; i >=0; i--) { 1641 | html += regP >> i & 1; 1642 | } 1643 | $node.find('.minidebugger').html(html); 1644 | updateMonitor(); 1645 | } 1646 | 1647 | // gotoAddr() - Set PC to address (or address of label) 1648 | function gotoAddr() { 1649 | var inp = prompt("Enter address or label", ""); 1650 | var addr = 0; 1651 | if (labels.find(inp)) { 1652 | addr = labels.getPC(inp); 1653 | } else { 1654 | if (inp.match(/^0x[0-9a-f]{1,4}$/i)) { 1655 | inp = inp.replace(/^0x/, ""); 1656 | addr = parseInt(inp, 16); 1657 | } else if (inp.match(/^\$[0-9a-f]{1,4}$/i)) { 1658 | inp = inp.replace(/^\$/, ""); 1659 | addr = parseInt(inp, 16); 1660 | } 1661 | } 1662 | if (addr === 0) { 1663 | message("Unable to find/parse given address/label"); 1664 | } else { 1665 | regPC = addr; 1666 | } 1667 | updateDebugInfo(); 1668 | } 1669 | 1670 | 1671 | function stopDebugger() { 1672 | debug = false; 1673 | } 1674 | 1675 | function enableDebugger() { 1676 | debug = true; 1677 | if (codeRunning) { 1678 | updateDebugInfo(); 1679 | } 1680 | } 1681 | 1682 | // reset() - Reset CPU and memory. 1683 | function reset() { 1684 | display.reset(); 1685 | for (var i = 0; i < 0x600; i++) { // clear ZP, stack and screen 1686 | memory.set(i, 0x00); 1687 | } 1688 | regA = regX = regY = 0; 1689 | regPC = 0x600; 1690 | regSP = 0xff; 1691 | regP = 0x30; 1692 | updateDebugInfo(); 1693 | } 1694 | 1695 | function stop() { 1696 | codeRunning = false; 1697 | clearInterval(executeId); 1698 | message("\nStopped\n"); 1699 | } 1700 | 1701 | function toggleMonitor (state) { 1702 | monitoring = state; 1703 | } 1704 | 1705 | return { 1706 | runBinary: runBinary, 1707 | enableDebugger: enableDebugger, 1708 | stopDebugger: stopDebugger, 1709 | debugExec: debugExec, 1710 | gotoAddr: gotoAddr, 1711 | reset: reset, 1712 | stop: stop, 1713 | toggleMonitor: toggleMonitor, 1714 | handleMonitorRangeChange: handleMonitorRangeChange 1715 | }; 1716 | } 1717 | 1718 | 1719 | function Labels() { 1720 | var labelIndex = []; 1721 | 1722 | function indexLines(lines, symbols) { 1723 | for (var i = 0; i < lines.length; i++) { 1724 | if (!indexLine(lines[i], symbols)) { 1725 | message("**Label already defined at line " + (i + 1) + ":** " + lines[i]); 1726 | return false; 1727 | } 1728 | } 1729 | return true; 1730 | } 1731 | 1732 | // Extract label if line contains one and calculate position in memory. 1733 | // Return false if label already exists. 1734 | function indexLine(input, symbols) { 1735 | 1736 | // Figure out how many bytes this instruction takes 1737 | var currentPC = assembler.getCurrentPC(); 1738 | assembler.assembleLine(input, 0, symbols); //TODO: find a better way for Labels to have access to assembler 1739 | 1740 | // Find command or label 1741 | if (input.match(/^\w+:/)) { 1742 | var label = input.replace(/(^\w+):.*$/, "$1"); 1743 | 1744 | if (symbols.lookup(label)) { 1745 | message("**Label " + label + "is already used as a symbol; please rename one of them**"); 1746 | return false; 1747 | } 1748 | 1749 | return push(label + "|" + currentPC); 1750 | } 1751 | return true; 1752 | } 1753 | 1754 | // Push label to array. Return false if label already exists. 1755 | function push(name) { 1756 | if (find(name)) { 1757 | return false; 1758 | } 1759 | labelIndex.push(name + "|"); 1760 | return true; 1761 | } 1762 | 1763 | // Returns true if label exists. 1764 | function find(name) { 1765 | var nameAndAddr; 1766 | for (var i = 0; i < labelIndex.length; i++) { 1767 | nameAndAddr = labelIndex[i].split("|"); 1768 | if (name === nameAndAddr[0]) { 1769 | return true; 1770 | } 1771 | } 1772 | return false; 1773 | } 1774 | 1775 | // Associates label with address 1776 | function setPC(name, addr) { 1777 | var nameAndAddr; 1778 | for (var i = 0; i < labelIndex.length; i++) { 1779 | nameAndAddr = labelIndex[i].split("|"); 1780 | if (name === nameAndAddr[0]) { 1781 | labelIndex[i] = name + "|" + addr; 1782 | return true; 1783 | } 1784 | } 1785 | return false; 1786 | } 1787 | 1788 | // Get address associated with label 1789 | function getPC(name) { 1790 | var nameAndAddr; 1791 | for (var i = 0; i < labelIndex.length; i++) { 1792 | nameAndAddr = labelIndex[i].split("|"); 1793 | if (name === nameAndAddr[0]) { 1794 | return (nameAndAddr[1]); 1795 | } 1796 | } 1797 | return -1; 1798 | } 1799 | 1800 | function displayMessage() { 1801 | var str = "Found " + labelIndex.length + " label"; 1802 | if (labelIndex.length !== 1) { 1803 | str += "s"; 1804 | } 1805 | message(str + "."); 1806 | } 1807 | 1808 | function reset() { 1809 | labelIndex = []; 1810 | } 1811 | 1812 | return { 1813 | indexLines: indexLines, 1814 | find: find, 1815 | getPC: getPC, 1816 | displayMessage: displayMessage, 1817 | reset: reset 1818 | }; 1819 | } 1820 | 1821 | 1822 | function Assembler() { 1823 | var defaultCodePC; 1824 | var codeLen; 1825 | var codeAssembledOK = false; 1826 | var wasOutOfRangeBranch = false; 1827 | 1828 | var Opcodes = [ 1829 | /* Name, Imm, ZP, ZPX, ZPY, ABS, ABSX, ABSY, IND, INDX, INDY, SNGL, BRA */ 1830 | ["ADC", 0x69, 0x65, 0x75, null, 0x6d, 0x7d, 0x79, null, 0x61, 0x71, null, null], 1831 | ["AND", 0x29, 0x25, 0x35, null, 0x2d, 0x3d, 0x39, null, 0x21, 0x31, null, null], 1832 | ["ASL", null, 0x06, 0x16, null, 0x0e, 0x1e, null, null, null, null, 0x0a, null], 1833 | ["BIT", null, 0x24, null, null, 0x2c, null, null, null, null, null, null, null], 1834 | ["BPL", null, null, null, null, null, null, null, null, null, null, null, 0x10], 1835 | ["BMI", null, null, null, null, null, null, null, null, null, null, null, 0x30], 1836 | ["BVC", null, null, null, null, null, null, null, null, null, null, null, 0x50], 1837 | ["BVS", null, null, null, null, null, null, null, null, null, null, null, 0x70], 1838 | ["BCC", null, null, null, null, null, null, null, null, null, null, null, 0x90], 1839 | ["BCS", null, null, null, null, null, null, null, null, null, null, null, 0xb0], 1840 | ["BNE", null, null, null, null, null, null, null, null, null, null, null, 0xd0], 1841 | ["BEQ", null, null, null, null, null, null, null, null, null, null, null, 0xf0], 1842 | ["BRK", null, null, null, null, null, null, null, null, null, null, 0x00, null], 1843 | ["CMP", 0xc9, 0xc5, 0xd5, null, 0xcd, 0xdd, 0xd9, null, 0xc1, 0xd1, null, null], 1844 | ["CPX", 0xe0, 0xe4, null, null, 0xec, null, null, null, null, null, null, null], 1845 | ["CPY", 0xc0, 0xc4, null, null, 0xcc, null, null, null, null, null, null, null], 1846 | ["DEC", null, 0xc6, 0xd6, null, 0xce, 0xde, null, null, null, null, null, null], 1847 | ["EOR", 0x49, 0x45, 0x55, null, 0x4d, 0x5d, 0x59, null, 0x41, 0x51, null, null], 1848 | ["CLC", null, null, null, null, null, null, null, null, null, null, 0x18, null], 1849 | ["SEC", null, null, null, null, null, null, null, null, null, null, 0x38, null], 1850 | ["CLI", null, null, null, null, null, null, null, null, null, null, 0x58, null], 1851 | ["SEI", null, null, null, null, null, null, null, null, null, null, 0x78, null], 1852 | ["CLV", null, null, null, null, null, null, null, null, null, null, 0xb8, null], 1853 | ["CLD", null, null, null, null, null, null, null, null, null, null, 0xd8, null], 1854 | ["SED", null, null, null, null, null, null, null, null, null, null, 0xf8, null], 1855 | ["INC", null, 0xe6, 0xf6, null, 0xee, 0xfe, null, null, null, null, null, null], 1856 | ["JMP", null, null, null, null, 0x4c, null, null, 0x6c, null, null, null, null], 1857 | ["JSR", null, null, null, null, 0x20, null, null, null, null, null, null, null], 1858 | ["LDA", 0xa9, 0xa5, 0xb5, null, 0xad, 0xbd, 0xb9, null, 0xa1, 0xb1, null, null], 1859 | ["LDX", 0xa2, 0xa6, null, 0xb6, 0xae, null, 0xbe, null, null, null, null, null], 1860 | ["LDY", 0xa0, 0xa4, 0xb4, null, 0xac, 0xbc, null, null, null, null, null, null], 1861 | ["LSR", null, 0x46, 0x56, null, 0x4e, 0x5e, null, null, null, null, 0x4a, null], 1862 | ["NOP", null, null, null, null, null, null, null, null, null, null, 0xea, null], 1863 | ["ORA", 0x09, 0x05, 0x15, null, 0x0d, 0x1d, 0x19, null, 0x01, 0x11, null, null], 1864 | ["TAX", null, null, null, null, null, null, null, null, null, null, 0xaa, null], 1865 | ["TXA", null, null, null, null, null, null, null, null, null, null, 0x8a, null], 1866 | ["DEX", null, null, null, null, null, null, null, null, null, null, 0xca, null], 1867 | ["INX", null, null, null, null, null, null, null, null, null, null, 0xe8, null], 1868 | ["TAY", null, null, null, null, null, null, null, null, null, null, 0xa8, null], 1869 | ["TYA", null, null, null, null, null, null, null, null, null, null, 0x98, null], 1870 | ["DEY", null, null, null, null, null, null, null, null, null, null, 0x88, null], 1871 | ["INY", null, null, null, null, null, null, null, null, null, null, 0xc8, null], 1872 | ["ROR", null, 0x66, 0x76, null, 0x6e, 0x7e, null, null, null, null, 0x6a, null], 1873 | ["ROL", null, 0x26, 0x36, null, 0x2e, 0x3e, null, null, null, null, 0x2a, null], 1874 | ["RTI", null, null, null, null, null, null, null, null, null, null, 0x40, null], 1875 | ["RTS", null, null, null, null, null, null, null, null, null, null, 0x60, null], 1876 | ["SBC", 0xe9, 0xe5, 0xf5, null, 0xed, 0xfd, 0xf9, null, 0xe1, 0xf1, null, null], 1877 | ["STA", null, 0x85, 0x95, null, 0x8d, 0x9d, 0x99, null, 0x81, 0x91, null, null], 1878 | ["TXS", null, null, null, null, null, null, null, null, null, null, 0x9a, null], 1879 | ["TSX", null, null, null, null, null, null, null, null, null, null, 0xba, null], 1880 | ["PHA", null, null, null, null, null, null, null, null, null, null, 0x48, null], 1881 | ["PLA", null, null, null, null, null, null, null, null, null, null, 0x68, null], 1882 | ["PHP", null, null, null, null, null, null, null, null, null, null, 0x08, null], 1883 | ["PLP", null, null, null, null, null, null, null, null, null, null, 0x28, null], 1884 | ["STX", null, 0x86, null, 0x96, 0x8e, null, null, null, null, null, null, null], 1885 | ["STY", null, 0x84, 0x94, null, 0x8c, null, null, null, null, null, null, null], 1886 | ["WDM", 0x42, 0x42, null, null, null, null, null, null, null, null, null, null], 1887 | ["---", null, null, null, null, null, null, null, null, null, null, null, null] 1888 | ]; 1889 | 1890 | // Assembles the code into memory 1891 | function assembleCode() { 1892 | var BOOTSTRAP_ADDRESS = 0x600; 1893 | 1894 | wasOutOfRangeBranch = false; 1895 | 1896 | simulator.reset(); 1897 | labels.reset(); 1898 | defaultCodePC = BOOTSTRAP_ADDRESS; 1899 | $node.find('.messages code').empty(); 1900 | 1901 | var code = $node.find('.code').val(); 1902 | code += "\n\n"; 1903 | var lines = code.split("\n"); 1904 | codeAssembledOK = true; 1905 | 1906 | message("Preprocessing ..."); 1907 | var symbols = preprocess(lines); 1908 | 1909 | message("Indexing labels ..."); 1910 | defaultCodePC = BOOTSTRAP_ADDRESS; 1911 | if (!labels.indexLines(lines, symbols)) { 1912 | return false; 1913 | } 1914 | labels.displayMessage(); 1915 | 1916 | defaultCodePC = BOOTSTRAP_ADDRESS; 1917 | message("Assembling code ..."); 1918 | 1919 | codeLen = 0; 1920 | for (var i = 0; i < lines.length; i++) { 1921 | if (!assembleLine(lines[i], i, symbols)) { 1922 | codeAssembledOK = false; 1923 | break; 1924 | } 1925 | } 1926 | 1927 | if (codeLen === 0) { 1928 | codeAssembledOK = false; 1929 | message("No code to run."); 1930 | } 1931 | 1932 | if (codeAssembledOK) { 1933 | ui.assembleSuccess(); 1934 | memory.set(defaultCodePC, 0x00); //set a null byte at the end of the code 1935 | } else { 1936 | 1937 | var str = lines[i].replace("<", "<").replace(">", ">"); 1938 | 1939 | if(!wasOutOfRangeBranch) { 1940 | message("**Syntax error line " + (i + 1) + ": " + str + "**"); 1941 | } else { 1942 | message('**Out of range branch on line ' + (i + 1) + ' (branches are limited to -128 to +127): ' + str + '**'); 1943 | } 1944 | 1945 | ui.initialize(); 1946 | return false; 1947 | } 1948 | 1949 | message("Code assembled successfully, " + codeLen + " bytes."); 1950 | return true; 1951 | } 1952 | 1953 | // Sanitize input: remove comments and trim leading/trailing whitespace 1954 | function sanitize(line) { 1955 | // remove comments 1956 | var no_comments = line.replace(/^(.*?);.*/, "$1"); 1957 | 1958 | // trim line 1959 | return no_comments.replace(/^\s+/, "").replace(/\s+$/, ""); 1960 | } 1961 | 1962 | function preprocess(lines) { 1963 | var table = []; 1964 | var PREFIX = "__"; // Using a prefix avoids clobbering any predefined properties 1965 | 1966 | function lookup(key) { 1967 | if (table.hasOwnProperty(PREFIX + key)) return table[PREFIX + key]; 1968 | else return undefined; 1969 | } 1970 | 1971 | function add(key, value) { 1972 | var valueAlreadyExists = table.hasOwnProperty(PREFIX + key) 1973 | if (!valueAlreadyExists) { 1974 | table[PREFIX + key] = value; 1975 | } 1976 | } 1977 | 1978 | // Build the substitution table 1979 | for (var i = 0; i < lines.length; i++) { 1980 | lines[i] = sanitize(lines[i]); 1981 | var match_data = lines[i].match(/^define\s+(\w+)\s+(\S+)/); 1982 | if (match_data) { 1983 | add(match_data[1], sanitize(match_data[2])); 1984 | lines[i] = ""; // We're done with this preprocessor directive, so delete it 1985 | } 1986 | } 1987 | 1988 | // Callers will only need the lookup function 1989 | return { 1990 | lookup: lookup 1991 | } 1992 | } 1993 | 1994 | // Assembles one line of code. 1995 | // Returns true if it assembled successfully, false otherwise. 1996 | function assembleLine(input, lineno, symbols) { 1997 | var label, command, param, addr; 1998 | 1999 | // Find command or label 2000 | if (input.match(/^\w+:/)) { 2001 | label = input.replace(/(^\w+):.*$/, "$1"); 2002 | if (input.match(/^\w+:[\s]*\w+.*$/)) { 2003 | input = input.replace(/^\w+:[\s]*(.*)$/, "$1"); 2004 | command = input.replace(/^(\w+).*$/, "$1"); 2005 | } else { 2006 | command = ""; 2007 | } 2008 | } else { 2009 | command = input.replace(/^(\w+).*$/, "$1"); 2010 | } 2011 | 2012 | // Nothing to do for blank lines 2013 | if (command === "") { 2014 | return true; 2015 | } 2016 | 2017 | command = command.toUpperCase(); 2018 | 2019 | if (input.match(/^\*\s*=\s*\$?[0-9a-f]*$/)) { 2020 | // equ spotted 2021 | param = input.replace(/^\s*\*\s*=\s*/, ""); 2022 | if (param[0] === "$") { 2023 | param = param.replace(/^\$/, ""); 2024 | addr = parseInt(param, 16); 2025 | } else { 2026 | addr = parseInt(param, 10); 2027 | } 2028 | if ((addr < 0) || (addr > 0xffff)) { 2029 | message("Unable to relocate code outside 64k memory"); 2030 | return false; 2031 | } 2032 | defaultCodePC = addr; 2033 | return true; 2034 | } 2035 | 2036 | if (input.match(/^\w+\s+.*?$/)) { 2037 | param = input.replace(/^\w+\s+(.*?)/, "$1"); 2038 | } else if (input.match(/^\w+$/)) { 2039 | param = ""; 2040 | } else { 2041 | return false; 2042 | } 2043 | 2044 | param = param.replace(/[ ]/g, ""); 2045 | 2046 | if (command === "DCB") { 2047 | return DCB(param); 2048 | } 2049 | for (var o = 0; o < Opcodes.length; o++) { 2050 | if (Opcodes[o][0] === command) { 2051 | if (checkSingle(param, Opcodes[o][11])) { return true; } 2052 | if (checkImmediate(param, Opcodes[o][1], symbols)) { return true; } 2053 | if (checkZeroPage(param, Opcodes[o][2], symbols)) { return true; } 2054 | if (checkZeroPageX(param, Opcodes[o][3], symbols)) { return true; } 2055 | if (checkZeroPageY(param, Opcodes[o][4], symbols)) { return true; } 2056 | if (checkAbsoluteX(param, Opcodes[o][6], symbols)) { return true; } 2057 | if (checkAbsoluteY(param, Opcodes[o][7], symbols)) { return true; } 2058 | if (checkIndirect(param, Opcodes[o][8], symbols)) { return true; } 2059 | if (checkIndirectX(param, Opcodes[o][9], symbols)) { return true; } 2060 | if (checkIndirectY(param, Opcodes[o][10], symbols)) { return true; } 2061 | if (checkAbsolute(param, Opcodes[o][5], symbols)) { return true; } 2062 | if (checkBranch(param, Opcodes[o][12])) { return true; } 2063 | } 2064 | } 2065 | 2066 | return false; // Unknown syntax 2067 | } 2068 | 2069 | function DCB(param) { 2070 | var values, number, str, ch; 2071 | values = param.split(","); 2072 | if (values.length === 0) { return false; } 2073 | for (var v = 0; v < values.length; v++) { 2074 | str = values[v]; 2075 | if (str) { 2076 | ch = str.substring(0, 1); 2077 | if (ch === "$") { 2078 | number = parseInt(str.replace(/^\$/, ""), 16); 2079 | pushByte(number); 2080 | } else if (ch ==="%") { 2081 | number = parseInt(str.replace(/^%/, ""), 2) 2082 | pushByte(number) 2083 | } else if (ch >= "0" && ch <= "9") { 2084 | number = parseInt(str, 10); 2085 | pushByte(number); 2086 | } else { 2087 | return false; 2088 | } 2089 | } 2090 | } 2091 | return true; 2092 | } 2093 | 2094 | // Try to parse the given parameter as a byte operand. 2095 | // Returns the (positive) value if successful, otherwise -1 2096 | function tryParseByteOperand(param, symbols) { 2097 | if (param.match(/^\w+$/)) { 2098 | var lookupVal = symbols.lookup(param); // Substitute symbol by actual value, then proceed 2099 | if (lookupVal) { 2100 | param = lookupVal; 2101 | } 2102 | } 2103 | 2104 | var value; 2105 | var match_data; 2106 | 2107 | // Is it a decimal operand? 2108 | match_data = param.match(/^([0-9]{1,3})$/); 2109 | if (match_data) { 2110 | value = parseInt(match_data[1], 10); 2111 | } 2112 | 2113 | // Is it a hexadecimal operand? 2114 | match_data = param.match(/^\$([0-9a-f]{1,2})$/i); 2115 | if (match_data) { 2116 | value = parseInt(match_data[1], 16); 2117 | } 2118 | 2119 | // Is it a binary operand? 2120 | match_data = param.match(/^%([0-1]{1,8})$/); 2121 | if (match_data) { 2122 | value = parseInt(match_data[1], 2); 2123 | } 2124 | 2125 | // Validate range 2126 | if (value >= 0 && value <= 0xff) { 2127 | return value; 2128 | } else { 2129 | return -1; 2130 | } 2131 | } 2132 | 2133 | // Try to parse the given parameter as a word operand. 2134 | // Returns the (positive) value if successful, otherwise -1 2135 | function tryParseWordOperand(param, symbols) { 2136 | if (param.match(/^\w+$/)) { 2137 | var lookupVal = symbols.lookup(param); // Substitute symbol by actual value, then proceed 2138 | if (lookupVal) { 2139 | param = lookupVal; 2140 | } 2141 | } 2142 | 2143 | var value; 2144 | 2145 | // Is it a hexadecimal operand? 2146 | var match_data = param.match(/^\$([0-9a-f]{3,4})$/i); 2147 | if (match_data) { 2148 | value = parseInt(match_data[1], 16); 2149 | } else { 2150 | // Is it a decimal operand? 2151 | match_data = param.match(/^([0-9]{1,5})$/i); 2152 | if (match_data) { 2153 | value = parseInt(match_data[1], 10); 2154 | } 2155 | } 2156 | 2157 | // Validate range 2158 | if (value >= 0 && value <= 0xffff) { 2159 | return value; 2160 | } else { 2161 | return -1; 2162 | } 2163 | } 2164 | 2165 | // Common branch function for all branches (BCC, BCS, BEQ, BNE..) 2166 | function checkBranch(param, opcode) { 2167 | var addr; 2168 | if (opcode === null) { return false; } 2169 | 2170 | addr = -1; 2171 | if (param.match(/\w+/)) { 2172 | addr = labels.getPC(param); 2173 | } 2174 | if (addr === -1) { pushWord(0x00); return false; } 2175 | pushByte(opcode); 2176 | 2177 | var distance = addr - defaultCodePC - 1; 2178 | 2179 | if(distance < -128 || distance > 127) { 2180 | wasOutOfRangeBranch = true; 2181 | return false; 2182 | } 2183 | 2184 | pushByte(distance); 2185 | return true; 2186 | } 2187 | 2188 | // Check if param is immediate and push value 2189 | function checkImmediate(param, opcode, symbols) { 2190 | var value, label, hilo, addr; 2191 | if (opcode === null) { return false; } 2192 | 2193 | var match_data = param.match(/^#([\w\$%]+)$/i); 2194 | if (match_data) { 2195 | var operand = tryParseByteOperand(match_data[1], symbols); 2196 | if (operand >= 0) { 2197 | pushByte(opcode); 2198 | pushByte(operand); 2199 | return true; 2200 | } 2201 | } 2202 | 2203 | // Label lo/hi 2204 | if (param.match(/^#[<>]\w+$/)) { 2205 | label = param.replace(/^#[<>](\w+)$/, "$1"); 2206 | hilo = param.replace(/^#([<>]).*$/, "$1"); 2207 | pushByte(opcode); 2208 | if (labels.find(label)) { 2209 | addr = labels.getPC(label); 2210 | switch(hilo) { 2211 | case ">": 2212 | pushByte((addr >> 8) & 0xff); 2213 | return true; 2214 | case "<": 2215 | pushByte(addr & 0xff); 2216 | return true; 2217 | default: 2218 | return false; 2219 | } 2220 | } else { 2221 | pushByte(0x00); 2222 | return true; 2223 | } 2224 | } 2225 | 2226 | return false; 2227 | } 2228 | 2229 | // Check if param is indirect and push value 2230 | function checkIndirect(param, opcode, symbols) { 2231 | var value; 2232 | if (opcode === null) { return false; } 2233 | 2234 | var match_data = param.match(/^\(([\w\$]+)\)$/i); 2235 | if (match_data) { 2236 | var operand = tryParseWordOperand(match_data[1], symbols); 2237 | if (operand >= 0) { 2238 | pushByte(opcode); 2239 | pushWord(operand); 2240 | return true; 2241 | } 2242 | } 2243 | return false; 2244 | } 2245 | 2246 | // Check if param is indirect X and push value 2247 | function checkIndirectX(param, opcode, symbols) { 2248 | var value; 2249 | if (opcode === null) { return false; } 2250 | 2251 | var match_data = param.match(/^\(([\w\$]+),X\)$/i); 2252 | if (match_data) { 2253 | var operand = tryParseByteOperand(match_data[1], symbols); 2254 | if (operand >= 0) { 2255 | pushByte(opcode); 2256 | pushByte(operand); 2257 | return true; 2258 | } 2259 | } 2260 | return false; 2261 | } 2262 | 2263 | // Check if param is indirect Y and push value 2264 | function checkIndirectY(param, opcode, symbols) { 2265 | var value; 2266 | if (opcode === null) { return false; } 2267 | 2268 | var match_data = param.match(/^\(([\w\$]+)\),Y$/i); 2269 | if (match_data) { 2270 | var operand = tryParseByteOperand(match_data[1], symbols); 2271 | if (operand >= 0) { 2272 | pushByte(opcode); 2273 | pushByte(operand); 2274 | return true; 2275 | } 2276 | } 2277 | return false; 2278 | } 2279 | 2280 | // Check single-byte opcodes 2281 | function checkSingle(param, opcode) { 2282 | if (opcode === null) { return false; } 2283 | // Accumulator instructions are counted as single-byte opcodes 2284 | if (param !== "" && param !== "A") { return false; } 2285 | pushByte(opcode); 2286 | return true; 2287 | } 2288 | 2289 | // Check if param is ZP and push value 2290 | function checkZeroPage(param, opcode, symbols) { 2291 | var value; 2292 | if (opcode === null) { return false; } 2293 | 2294 | var operand = tryParseByteOperand(param, symbols); 2295 | if (operand >= 0) { 2296 | pushByte(opcode); 2297 | pushByte(operand); 2298 | return true; 2299 | } 2300 | 2301 | return false; 2302 | } 2303 | 2304 | // Check if param is ABSX and push value 2305 | function checkAbsoluteX(param, opcode, symbols) { 2306 | var number, value, addr; 2307 | if (opcode === null) { return false; } 2308 | 2309 | var match_data = param.match(/^([\w\$]+),X$/i); 2310 | if (match_data) { 2311 | var operand = tryParseWordOperand(match_data[1], symbols); 2312 | if (operand >= 0) { 2313 | pushByte(opcode); 2314 | pushWord(operand); 2315 | return true; 2316 | } 2317 | } 2318 | 2319 | // it could be a label too.. 2320 | if (param.match(/^\w+,X$/i)) { 2321 | param = param.replace(/,X$/i, ""); 2322 | pushByte(opcode); 2323 | if (labels.find(param)) { 2324 | addr = labels.getPC(param); 2325 | if (addr < 0 || addr > 0xffff) { return false; } 2326 | pushWord(addr); 2327 | return true; 2328 | } else { 2329 | pushWord(0xffff); // filler, only used while indexing labels 2330 | return true; 2331 | } 2332 | } 2333 | 2334 | return false; 2335 | } 2336 | 2337 | // Check if param is ABSY and push value 2338 | function checkAbsoluteY(param, opcode, symbols) { 2339 | var number, value, addr; 2340 | if (opcode === null) { return false; } 2341 | 2342 | var match_data = param.match(/^([\w\$]+),Y$/i); 2343 | if (match_data) { 2344 | var operand = tryParseWordOperand(match_data[1], symbols); 2345 | if (operand >= 0) { 2346 | pushByte(opcode); 2347 | pushWord(operand); 2348 | return true; 2349 | } 2350 | } 2351 | 2352 | // it could be a label too.. 2353 | if (param.match(/^\w+,Y$/i)) { 2354 | param = param.replace(/,Y$/i, ""); 2355 | pushByte(opcode); 2356 | if (labels.find(param)) { 2357 | addr = labels.getPC(param); 2358 | if (addr < 0 || addr > 0xffff) { return false; } 2359 | pushWord(addr); 2360 | return true; 2361 | } else { 2362 | pushWord(0xffff); // filler, only used while indexing labels 2363 | return true; 2364 | } 2365 | } 2366 | return false; 2367 | } 2368 | 2369 | // Check if param is ZPX and push value 2370 | function checkZeroPageX(param, opcode, symbols) { 2371 | var number, value; 2372 | if (opcode === null) { return false; } 2373 | 2374 | var match_data = param.match(/^([\w\$]+),X$/i); 2375 | if (match_data) { 2376 | var operand = tryParseByteOperand(match_data[1], symbols); 2377 | if (operand >= 0) { 2378 | pushByte(opcode); 2379 | pushByte(operand); 2380 | return true; 2381 | } 2382 | } 2383 | 2384 | return false; 2385 | } 2386 | 2387 | // Check if param is ZPY and push value 2388 | function checkZeroPageY(param, opcode, symbols) { 2389 | var number, value; 2390 | if (opcode === null) { return false; } 2391 | 2392 | var match_data = param.match(/^([\w\$]+),Y$/i); 2393 | if (match_data) { 2394 | var operand = tryParseByteOperand(match_data[1], symbols); 2395 | if (operand >= 0) { 2396 | pushByte(opcode); 2397 | pushByte(operand); 2398 | return true; 2399 | } 2400 | } 2401 | 2402 | return false; 2403 | } 2404 | 2405 | // Check if param is ABS and push value 2406 | function checkAbsolute(param, opcode, symbols) { 2407 | var value, number, addr; 2408 | if (opcode === null) { return false; } 2409 | 2410 | var match_data = param.match(/^([\w\$]+)$/i); 2411 | if (match_data) { 2412 | var operand = tryParseWordOperand(match_data[1], symbols); 2413 | if (operand >= 0) { 2414 | pushByte(opcode); 2415 | pushWord(operand); 2416 | return true; 2417 | } 2418 | } 2419 | 2420 | // it could be a label too.. 2421 | if (param.match(/^\w+$/)) { 2422 | pushByte(opcode); 2423 | if (labels.find(param)) { 2424 | addr = (labels.getPC(param)); 2425 | if (addr < 0 || addr > 0xffff) { return false; } 2426 | pushWord(addr); 2427 | return true; 2428 | } else { 2429 | pushWord(0xffff); // filler, only used while indexing labels 2430 | return true; 2431 | } 2432 | } 2433 | return false; 2434 | } 2435 | 2436 | // Push a byte to memory 2437 | function pushByte(value) { 2438 | memory.set(defaultCodePC, value & 0xff); 2439 | defaultCodePC++; 2440 | codeLen++; 2441 | } 2442 | 2443 | // Push a word to memory in little-endian order 2444 | function pushWord(value) { 2445 | pushByte(value & 0xff); 2446 | pushByte((value >> 8) & 0xff); 2447 | } 2448 | 2449 | function openPopup(content, title) { 2450 | var w = window.open('', title, 'width=500,height=300,resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no'); 2451 | 2452 | var html = ""; 2453 | html += ""; 2454 | html += "" + title + ""; 2455 | html += "
";
2456 | 
2457 |       html += content;
2458 | 
2459 |       html += "
"; 2460 | w.document.write(html); 2461 | w.document.close(); 2462 | } 2463 | 2464 | // Dump binary as hex to new window 2465 | function hexdump() { 2466 | openPopup(memory.format(0x600, codeLen), 'Hexdump'); 2467 | } 2468 | 2469 | // TODO: Create separate disassembler object? 2470 | var addressingModes = [ 2471 | null, 2472 | 'Imm', 2473 | 'ZP', 2474 | 'ZPX', 2475 | 'ZPY', 2476 | 'ABS', 2477 | 'ABSX', 2478 | 'ABSY', 2479 | 'IND', 2480 | 'INDX', 2481 | 'INDY', 2482 | 'SNGL', 2483 | 'BRA' 2484 | ]; 2485 | 2486 | var instructionLength = { 2487 | Imm: 2, 2488 | ZP: 2, 2489 | ZPX: 2, 2490 | ZPY: 2, 2491 | ABS: 3, 2492 | ABSX: 3, 2493 | ABSY: 3, 2494 | IND: 3, 2495 | INDX: 2, 2496 | INDY: 2, 2497 | SNGL: 1, 2498 | BRA: 2 2499 | }; 2500 | 2501 | function getModeAndCode(byte) { 2502 | var index; 2503 | var line = Opcodes.filter(function (line) { 2504 | var possibleIndex = line.indexOf(byte); 2505 | if (possibleIndex > -1) { 2506 | index = possibleIndex; 2507 | return true; 2508 | } 2509 | })[0]; 2510 | 2511 | if (!line) { //instruction not found 2512 | return { 2513 | opCode: '???', 2514 | mode: 'SNGL' 2515 | }; 2516 | } else { 2517 | return { 2518 | opCode: line[0], 2519 | mode: addressingModes[index] 2520 | }; 2521 | } 2522 | } 2523 | 2524 | function createInstruction(address) { 2525 | var bytes = []; 2526 | var opCode; 2527 | var args = []; 2528 | var mode; 2529 | 2530 | function isAccumulatorInstruction() { 2531 | var accumulatorBytes = [0x0a, 0x4a, 0x2a, 0x6a]; 2532 | if (accumulatorBytes.indexOf(bytes[0]) > -1) { 2533 | return true; 2534 | } 2535 | } 2536 | 2537 | function isBranchInstruction() { 2538 | return opCode.match(/^B/) && !(opCode == 'BIT' || opCode == 'BRK'); 2539 | } 2540 | 2541 | //This is gnarly, but unavoidably so? 2542 | function formatArguments() { 2543 | var argsString = args.map(num2hex).reverse().join(''); 2544 | 2545 | if (isBranchInstruction()) { 2546 | var destination = address + 2; 2547 | if (args[0] > 0x7f) { 2548 | destination -= 0x100 - args[0]; 2549 | } else { 2550 | destination += args[0]; 2551 | } 2552 | argsString = addr2hex(destination); 2553 | } 2554 | 2555 | if (argsString) { 2556 | argsString = '$' + argsString; 2557 | } 2558 | if (mode == 'Imm') { 2559 | argsString = '#' + argsString; 2560 | } 2561 | if (mode.match(/X$/)) { 2562 | argsString += ',X'; 2563 | } 2564 | if (mode.match(/^IND/)) { 2565 | argsString = '(' + argsString + ')'; 2566 | } 2567 | if (mode.match(/Y$/)) { 2568 | argsString += ',Y'; 2569 | } 2570 | 2571 | if (isAccumulatorInstruction()) { 2572 | argsString = 'A'; 2573 | } 2574 | 2575 | return argsString; 2576 | } 2577 | 2578 | return { 2579 | addByte: function (byte) { 2580 | bytes.push(byte); 2581 | }, 2582 | setModeAndCode: function (modeAndCode) { 2583 | opCode = modeAndCode.opCode; 2584 | mode = modeAndCode.mode; 2585 | }, 2586 | addArg: function (arg) { 2587 | args.push(arg); 2588 | }, 2589 | toString: function () { 2590 | var bytesString = bytes.map(num2hex).join(' '); 2591 | var padding = Array(11 - bytesString.length).join(' '); 2592 | return '$' + addr2hex(address) + ' ' + bytesString + padding + opCode + 2593 | ' ' + formatArguments(args); 2594 | } 2595 | }; 2596 | } 2597 | 2598 | function disassemble() { 2599 | var startAddress = 0x600; 2600 | var currentAddress = startAddress; 2601 | var endAddress = startAddress + codeLen; 2602 | var instructions = []; 2603 | var length; 2604 | var inst; 2605 | var byte; 2606 | var modeAndCode; 2607 | 2608 | while (currentAddress < endAddress) { 2609 | inst = createInstruction(currentAddress); 2610 | byte = memory.get(currentAddress); 2611 | inst.addByte(byte); 2612 | 2613 | modeAndCode = getModeAndCode(byte); 2614 | length = instructionLength[modeAndCode.mode]; 2615 | inst.setModeAndCode(modeAndCode); 2616 | 2617 | for (var i = 1; i < length; i++) { 2618 | currentAddress++; 2619 | byte = memory.get(currentAddress); 2620 | inst.addByte(byte); 2621 | inst.addArg(byte); 2622 | } 2623 | instructions.push(inst); 2624 | currentAddress++; 2625 | } 2626 | 2627 | var html = 'Address Hexdump Dissassembly\n'; 2628 | html += '-------------------------------\n'; 2629 | html += instructions.join('\n'); 2630 | openPopup(html, 'Disassembly'); 2631 | } 2632 | 2633 | return { 2634 | assembleLine: assembleLine, 2635 | assembleCode: assembleCode, 2636 | getCurrentPC: function () { 2637 | return defaultCodePC; 2638 | }, 2639 | hexdump: hexdump, 2640 | disassemble: disassemble 2641 | }; 2642 | } 2643 | 2644 | 2645 | function addr2hex(addr) { 2646 | return num2hex((addr >> 8) & 0xff) + num2hex(addr & 0xff); 2647 | } 2648 | 2649 | function num2hex(nr) { 2650 | var str = "0123456789abcdef"; 2651 | var hi = ((nr & 0xf0) >> 4); 2652 | var lo = (nr & 15); 2653 | return str.substring(hi, hi + 1) + str.substring(lo, lo + 1); 2654 | } 2655 | 2656 | // Prints text in the message window 2657 | function message(text) { 2658 | if (text.length>1) 2659 | text += '\n'; // allow putc operations from the simulator (WDM opcode) 2660 | $node.find('.messages code').append(text).scrollTop(10000); 2661 | } 2662 | 2663 | initialize(); 2664 | } 2665 | 2666 | $(document).ready(function () { 2667 | $('.widget').each(function () { 2668 | SimulatorWidget(this); 2669 | }); 2670 | }); 2671 | -------------------------------------------------------------------------------- /simulator/es5-shim.js: -------------------------------------------------------------------------------- 1 | // vim: ts=4 sts=4 sw=4 expandtab 2 | // -- kriskowal Kris Kowal Copyright (C) 2009-2011 MIT License 3 | // -- tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal Project) 4 | // -- dantman Daniel Friesen Copyright (C) 2010 XXX TODO License or CLA 5 | // -- fschaefer Florian Schäfer Copyright (C) 2010 MIT License 6 | // -- Gozala Irakli Gozalishvili Copyright (C) 2010 MIT License 7 | // -- kitcambridge Kit Cambridge Copyright (C) 2011 MIT License 8 | // -- kossnocorp Sasha Koss XXX TODO License or CLA 9 | // -- bryanforbes Bryan Forbes XXX TODO License or CLA 10 | // -- killdream Quildreen Motta Copyright (C) 2011 MIT Licence 11 | // -- michaelficarra Michael Ficarra Copyright (C) 2011 3-clause BSD License 12 | // -- sharkbrainguy Gerard Paapu Copyright (C) 2011 MIT License 13 | // -- bbqsrc Brendan Molloy (C) 2011 Creative Commons Zero (public domain) 14 | // -- iwyg XXX TODO License or CLA 15 | // -- DomenicDenicola Domenic Denicola Copyright (C) 2011 MIT License 16 | // -- xavierm02 Montillet Xavier Copyright (C) 2011 MIT License 17 | // -- Raynos Jake Verbaten Copyright (C) 2011 MIT Licence 18 | // -- samsonjs Sami Samhuri Copyright (C) 2010 MIT License 19 | // -- rwldrn Rick Waldron Copyright (C) 2011 MIT License 20 | // -- lexer Alexey Zakharov XXX TODO License or CLA 21 | 22 | /*! 23 | Copyright (c) 2009, 280 North Inc. http://280north.com/ 24 | MIT License. http://github.com/280north/narwhal/blob/master/README.md 25 | */ 26 | 27 | // Module systems magic dance 28 | (function (definition) { 29 | // RequireJS 30 | if (typeof define == "function") { 31 | define(definition); 32 | // CommonJS and