What's this?
This is an online 6502 CPU emulator with assembler language compiler, disassembler, debugger and memory monitor, written entirely in JavaScript and based on source code by Stian Søreng. 6502 CPU core code was tested by Klaus Dormann's Functional Test for the NMOS 6502 and all found bugs were fixed.
The 6502 CPU was fitted into several 8-bit
computers and gaming consoles in the 1980's, such as the 'Apple ][', 'Commodore 64', 'Nintendo NES' and many more. If you have ever coded assembly on 6502, Z80 or i8080 powered computers, then this should be like a simple game for you.
It seems that this can be an excellent platform for easy learning assembly code.
About 6502 CPU in brief
The 6502 has the following registers:
PC |
|
S |
|
Y |
|
X |
|
P |
A |
PC — Program Counter is a 16 bit register which points to the next instruction to be executed. The value of program counter is modified automatically as instructions are executed.
The value of the program counter can be modified by executing a jump, a relative branch or a subroutine call to another memory address or by returning from a subroutine or interrupt.
S — Stack Pointer is an 8 bit register which holds the low 8 bits of the next free location on the stack. The 6502 microprocessor supports a 256 byte stack located between $0100 and $01FF. The location of the stack is fixed and cannot be moved. Pushing bytes to the stack causes the stack pointer to be decremented. Conversely pulling bytes causes it to be incremented.
Y — Index Register Y is a 8 bit register which is most commonly used to hold counters or offsets for accessing memory. The value of the Y register can be loaded and saved in memory, compared with values held in memory or incremented and decremented. It has no special functions.
X — Index Register X is a 8 bit register which is similar to the Y register and it is used to hold counters or offsets for accessing memory. The value of the X register can be loaded and saved in memory, compared with values held in memory or incremented and decremented.
The X register has one special function. It can be used to get a copy of the stack pointer or change its value.
A — Accumulator is a 8 bit register which is used all arithmetic and logical operations (with the exception of increments and decrements). The contents of the accumulator can be stored and retrieved either from memory or the stack.
P — Processor Status Register is a 8 bit special status register which holds the 6502 microprocessor "flags" which are the signs of the results of the operation. Each flag has a single bit within the register. As instructions are executed a set of processor flags are set or clear to record the results of the operation. Instructions exist to test the values of the various bits, to set or clear some of them and to push or pull the entire set to or from the stack.
Processor Status Register format |
||
BIT |
Det. |
Description |
0 |
С |
Carry Flag is set if the last operation caused an overflow from bit 7 of the result or an underflow from bit 0. This condition is set during arithmetic, comparison and during logical shifts. It can be explicitly set using the 'Set Carry Flag' (SEC) instruction and cleared with 'Clear Carry Flag' (CLC) instruction. |
1 |
Z |
Zero Flag is set if the result of the last operation as was zero. |
2 |
I |
Interrupt Disable Flag is set on Power On or if the program has executed a 'Set Interrupt Disable' (SEI) instruction. While this flag is set the processor will not respond to interrupts from devices until it is cleared by a 'Clear Interrupt Disable' (CLI) instruction. |
3 |
D |
Decimal Mode Flag is set the processor will obey the rules of Binary Coded Decimal (BCD) arithmetic during addition and subtraction. The flag can be explicitly set using 'Set Decimal Flag' (SED) instruction and cleared with 'Clear Decimal Flag' (CLD) instruction. |
4 |
B |
Break Command Flag — this bit is set in Status Register copy saved on stack when a BRK instruction has been executed and an interrupt has been generated to process it. |
5 |
1 |
Unused — always = 1 |
6 |
V |
Overflow Flag is set during arithmetic operations if the result has yielded an invalid 2's complement result (e.g. adding to positive numbers and ending up with a negative result: 64 + 64 => -128). It is determined by looking at the carry between bits 6 and 7 and between bit 7 and the carry flag. |
7 |
N |
Negative Flag is set if the result of the last operation had bit 7 set to a one. |
6502 Instruction Set
Instructions by Name:
ADC | .... | add with carry |
AND | .... | and (with accumulator) |
ASL | .... | arithmetic shift left |
BCC | .... | branch on carry clear |
BCS | .... | branch on carry set |
BEQ | .... | branch on equal (zero set) |
BIT | .... | bit test |
BMI | .... | branch on minus (negative set) |
BNE | .... | branch on not equal (zero clear) |
BPL | .... | branch on plus (negative clear) |
BRK | .... | interrupt |
BVC | .... | branch on overflow clear |
BVS | .... | branch on overflow set |
CLC | .... | clear carry |
CLD | .... | clear decimal |
CLI | .... | clear interrupt disable |
CLV | .... | clear overflow |
CMP | .... | compare (with accumulator) |
CPX | .... | compare with X |
CPY | .... | compare with Y |
DEC | .... | decrement |
DEX | .... | decrement X |
DEY | .... | decrement Y |
EOR | .... | exclusive or (with accumulator) |
INC | .... | increment |
INX | .... | increment X |
INY | .... | increment Y |
JMP | .... | jump |
JSR | .... | jump subroutine |
LDA | .... | load accumulator |
LDY | .... | load X |
LDY | .... | load Y |
LSR | .... | logical shift right |
NOP | .... | no operation |
ORA | .... | or with accumulator |
PHA | .... | push accumulator |
PHP | .... | push processor status (SR) |
PLA | .... | pull accumulator |
PLP | .... | pull processor status (SR) |
ROL | .... | rotate left |
ROR | .... | rotate right |
RTI | .... | return from interrupt |
RTS | .... | return from subroutine |
SBC | .... | subtract with carry |
SEC | .... | set carry |
SED | .... | set decimal |
SEI | .... | set interrupt disable |
STA | .... | store accumulator |
STX | .... | store X |
STY | .... | store Y |
TAX | .... | transfer accumulator to X |
TAY | .... | transfer accumulator to Y |
TSX | .... | transfer stack pointer to X |
TXA | .... | transfer X to accumulator |
TXS | .... | transfer X to stack pointer |
TYA | .... | transfer Y to accumulator |
Memory Addressing Modes
Addressing Mode |
Instruction |
Comments |
||
1. Implied: i |
TXA |
The operands being implied here are X, the source of the transfer, and A, the destination of the transfer. |
||
2. Immediate: # |
LDA #$22 |
The value $22 is loaded into the Accumulator. |
||
3. Zero Page: zp |
LDY $02 |
The value at address $0002 is loaded into the Y register. |
||
4. Absolute: a |
LDX $D010 |
The value at address $D010 is loaded into the X register. |
||
5. Zero Page Indexed with X(Y): zp,x(y) |
LDA $14,X |
The value in X is added to the specified zero page address for a sum address. The value at the sum address is used to perform the computation. |
||
6. Absolute Indexed with X(Y): a,x(y) |
ADC$1111,X |
The value $02 in X is added to $1111 for a sum of $1113. The value $5A at address $1113 is used to perform the add with carry (ADC) operation. |
||
7. Indirect: (a) |
JMP ($2E55) |
The little-endian address stored at the two-byte pair 2E55h, 2E56h is used to perform the computation. |
||
8. Zero Page Indexed Indirect: (zp,x) |
LDA ($13,X) |
The value in X is added to the specified zero page address for a sum address. The little-endian address stored at the two-byte pair of sum address (LSB) and sum address plus one (MSB) is loaded and the value at that address is used to perform the computation. |
||
9. Zero Page Indirect Indexed with Y: (zp),y |
LDA ($25),Y |
The value in Y is added to the address at the little-endian address stored at the two-byte pair of the specified address (LSB) and the specified address plus one (MSB). The value at the sum address is used to perform the computation. Indeed addressing mode actually repeats exactly the accumulator register's digits. |
||
10. Relative: r |
BPL $2D |
The offset $2D is added to the address in the Program Counter (say $C100). The destination of the branch (if taken) will be $C12D. |
All legal documented instructions from the 6502 are implemented. Those which are undocumented opcodes are executed as NOP's. They can be compiled using 'dcb'-s and executed, but have no effect. For a complete list of all available instructions, see:
6502 Instruction Set
or
http://www.6502.org/tutorials/6502opcodes.html
Unused $FF code is interpreted as an instruction HLT (HALT - Stop).
HLT instruction as a $FF byte is appended at the end of compiled program automatically, and it can also be added into the program as a HLT mnemonic, or as a single byte: DCB $FF.
Instruction timing and cycles
The CPU emulator part of this project does not take timing or cycles in consideration - it simply tries to execute the code as fast as possible.
The 6502 CPU emulator system memory map
$FFF0 |
••• |
$FFFA |
••• vectors ••• |
$FFFF |
|
$FFE0 |
••• |
$FFEF |
|||
••• ••• User Area 64000 bytes ••• ••• |
|||||
$0610 |
••• |
$061F |
|||
$0600 |
<< Start addr |
$060F |
|||
$05F0 |
••• |
$05FF |
|||
Dotted Graphics Screen Area 1024 bytes |
|||||
$0200 |
••• |
$010F |
|||
$01F0 |
••• |
$01FF |
|||
Stack 256 bytes |
|||||
$0100 |
••• |
$010F |
|||
$00F0 |
••• |
$00FE |
$00FF |
||
Zero Page 256 bytes |
|||||
$0000 |
••• |
$000F |
• $0000-$00FF – “Zero Page” is the series of memory addresses at the absolute beginning of a computer's address space; that is the page whose starting address is zero. The MOS Technology 6502 processor treats the first 256 bytes of memory specially, it's index registers' size is 8 bits and the page size is 256 bytes. Therefore, its zero page extends from address 0 to address 255. The zero page had a special fast addressing mode, which facilitated its use for temporary storage of data and compensated for the relative shortage of CPU registers.
• $0100-$01FF – hardware stack, 256 RAM bytes in page 1 is that the 6502 uses this page for its hardware stack. The low 8 bits of the stack pointer are held in register S, and the high 8 bits are always implied to be $01. When the 6502 stores a byte at the address pointed to by the stack pointer, it immediately decrements the stack pointer. In other words, the stack grows down, not up. The S register is not directly accessible; but the stack itself is. You can push values on the stack with PHA, PHX, and PHY, and pull values off the stack with PLA, PLX, and PLY. JSR, the 6502's subroutine-call instruction, pushes two bytes on the stack for a return address. An interrupt pushes one additional byte, the processor status - register P. The return-from-subroutine instruction is RTS and the return-from-interrupt instruction is RTI, including the restoration of the processor status register P.
• $0200-$05FF – Dotted Graphics Screen Area, a usual memory displayed on the dotted screen: at the ratio one bytes - one pixel.
• $0600-$FFF9 – User routines area, where $0600 is used as the beginning address of the user routine.
• $FFFA – NMI – Non-maskable interrupts: When the 6502's NMI input experiences and high-to-low transition, the processor finishes the currently-executing instruction and then gets the beginning address of the NMI interrupt service routine (ISR) from $FFFA-FFFB. The 6502 reads this pair of bytes to get the beginning address of the NMI ISR where it should start executing.
• $FFFC – Reset (RST): – When the 6502's RST input gets pulled low and then brought back high, the 6502 starts its reset process, and gets the address to start executing program instructions from $FFFC-FFFD. Notice it does not start executing at address $FFFC, but reads it to get the beginning address of the routine where it should start executing.
• $FFFE – IRQ/BRK – (Maskable) Interrupt request (IRQ): When the 6502's IRQ input is low and the I (interrupt-disable) bit in the status register is clear, the processor finishes the currently-executing instruction and then gets the beginning address of the IRQ ISR from $FFFE-FFFF. Again, it does not start executing at address $FFFE, but reads this pair of bytes to find the beginning address of the IRQ ISR. BRK instruction (BReaK) also gets the beginning address of the "softwared IRQ" ISR from $FFFE-FFFF.
The 6502 interrupt priority (from high to low): RESET, NMI, IRQ/BRK. The simultaneous assertion of the NMI and IRQ (maskable) hardware interrupt lines causes IRQ to be ignored. However, if the IRQ line remains asserted after the servicing of the NMI, the processor will immediately respond to IRQ, as IRQ is level sensitive. Thus a sort of built-in interrupt priority was established in the 6502 design.
I/O in Zero Page
The following I/O devices are implemented in the zero page at $00FE and $00FF locations:
$00FF – on read contains the ASCII code of the key pressed (or $00if none).
$00FE – on read contains a random generator value 0 – 255.
On write these cells perform some functions similar to a teletype:
$00FF – on write outputs a string of bytes in the system messages window. Bytes are accumulated in a buffer and a string is displayed in the service messages window if the $0D service code is send. Service code $00 – clears the buffer; $1F – clears the system messages window; $1B – switches the output to the hexadecimal format without prefixes, but with a space after each code. Service code $1B – switches the output in trigger mode: the next $1B code disables the hexadecimal output.
$00FE – on write outputs a string of bytes in the tille of the browser. All service codes are the same as described above.
The screen and palette
The address space for the screen is from $200 (top left corner) to $5FF (lower right corner). For your convenience the screen is 32 by 32 pixels - 1024 in total.
$200 |
$201 |
••• |
$21E |
$21F |
$220 |
••• |
$23F |
||
••• |
••• |
••• |
||
$5C0 |
••• |
$5DF |
||
$5E0 |
$5E1 |
••• |
$5FE |
$5FF |
Moving one line down is simply done by adding $20 to the current address what is equivalent for shifting
five times to the left (2^5=$20).
The palette is a direct the same from the 'Commodore 64',
and the colors are as follows:
Color |
Code |
Code |
Color |
Black |
$00 |
$08 |
Orange |
White |
$01 |
$09 |
Brown |
Red |
$02 |
$0A |
Light red |
Cyan |
$03 |
$0B |
Dark gray |
Purple |
$04 |
$0C |
Gray |
Green |
$05 |
$0D |
Light green |
Blue |
$06 |
$0E |
Light blue |
Yellow |
$07 |
$0F |
Light gray |
The color codes on the screen are always AND-ed by $0F, so no matter what value you poke into the memory, you will get one of these colors.
Assembler Directives
The assembler uses a minimal set of directives, which is typical for the 8-bit «Commodore 64» computer compiler. Assembler directives tell the assembler additional information how to create the machine code.
# — prefix of Immediate addressing.
Example: LDA #55
— the value 55 is loaded into the Accumulator.
$ — prefix of Hexadecimal argument format.
Example: LDA $55
— the value from zero page address $0055 is loaded
into the Accumulator. Remember: LDA #$55
— the value $55 is loaded.
: — suffix of label.
Example: LOOP: JMP LOOP
— jump to label LOOP.
; — before a comment.
Example: LOOP: JMP LOOP; jump to label LOOP
*= — set origin (ORG directive equivalent).
Example: *= $1200
START:
— START label equates to 1200 Hex.
Attached label can also be defined:
*= $00FF
KEYBD:
— the equivalent for KEYBD: EQU 00FFH
#< — prefix of the word Least Significant Byte.
Example: LDA #<START
— the value $00 is loaded into the Accumulator.
#> — prefix of the word Most Significant Byte.
Example: LDA #>START
— the value $12 is loaded into the Accumulator.
dcb — declear bytes.
Example: dcb $41,$42,$43
— load into memory symbols 'ABC' from current address.
6502 emulator program options
First one must click the [Compile] button and assembler program from the text window is compiled into executable code.
If this process is successful, the next options becomes available: [ Run ] program, [HexDump] hexadecimal dump of codes, [Clear] assembler text window and the service messages window, call interrupts [_NMI_], [_IRQ_].
Assembler language program can be entered in the text window manually from the keyboard, moved by copying and pasting from an external source or selected for download from the [Examples] menu.
[HexDump] option allows you to send the compiled code in hexadecimal format to the external programs, since the binary content exchange in the browser environment is restricted for security reasons.
During the program execution the simulation of masked and non-masked interrupts is available through a mouse click on [_IRQ_] or [_NMI_] buttons respectively.
This calls these interrupt subroutines, which handler addresses are seen under the appropriate buttons. If the program itself change the interrupt vectors, then their values are immediately displayed on the emulator panel. These vectors can always be quickly changed manually by entering the appropriate addresses.
Memory monitoring
The emulated system memory can be viewed by activating the following option: Memory monitor ON: [ • ]. Then opened an additional window with a hex dump of the memory content, starting at address Start: $[XXXX], with Length: $[YYYY] bytes.
Hexadecimal dump window content does not updates automatically, while not in debug mode, because the renewal process would greatly slow down the execution of the program. The [Refresh] button allows to update the contents of this window.
In debug mode hexadecimal dump window content updates automatically after each step.
You can edit memory content in debug mode if you click the left mouse button in the window with a hex dump.
Memory bytes are displayed in an additional window starting with the address Start: $[XXXX], 8 bytes per editing session to avoid confusion with their addresses.
Memory bytes are displayed in a following format:
|Addr: 00=01=02=03=04=05=06=07|
[0200: 00=00=00=00=00=00=00=00]
Disassembler
The emulator program has an option disassembly of both the program code or any block RAM. Disassembly process is activated by pressing the button [Disassm]. This option is available as soon as the assembler program compiled code is loaded into the memory, after pressing the [Compile] button. In this case all the compiled code is displayed in a separate window as disassembled text. When Memory: [ • ] option is activated code from memory starting at address Start: $[XXXX], Length: $[YYYY] bytes is disassembled.
Debugger
Step by step debugging and execution becomes available when option [ • ] Debugger is activated by clicking the left mouse button. In this case [Step] and [Jump to:] buttons becomes active.
If you press the [Step] button in debug mode a single line of assembler program code is executed, the content of all 6502 Microprocessor registers is displayed together with the content of RAM if memory monitoring window is activated.
[Jump to:] button allows you to change the address of program execution at the emulator request in a special window.
In a debug mode you can edit the content of all 6502 Microprocessor registers if you click the left mouse button in the Debugger frame. The 6502 Microprocessor registers are displayed in a following format:
[A=$00 X=$00 Y=$00 S=$FF P=$30 PC=$0600]
The debugger also has the option of slow continuous execution of the program to the breakpoint.
This function is activated by checking the [ • ] AutoDB option. It is proposed to enter the address of the breakpoint. If you only need to slow execution of the program in debugged mode, you should enter the address of the breakpoint equal to $0000.
In that case if the execution of the code is too fast, you must activate the memory monitoring window which will significantly slow down the process of debugging the program in a continuous mode.
On the other hand in some browsers this mode is not recommended at all due to too slow execution of JavaScript scripts. Such browsers can freeze and stops executing the script.
Compability
This project was tested mainly under Windows 98 ... Windows-7 with Opera 9.64 and K-Meleon Pro 1.5.3. It should also work in Internet Explorer version 10+, Google Chrome, and Firefox 1.0+.
Disclaimer:
This program is provided for free and AS IS, therefore without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.
It doesn't work or You noticed a bug
Send an email to: Lavr2707@gmail.com.