Programming Basics

From NESdev Wiki
Revision as of 04:22, 13 June 2009 by Ndwiki (talk | contribs) (1 revision: Rest of pages not related to reference)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

Opcodes and their operands

To be written.

Registers

To be written.

The stack

Overview

The stack on the 6502 is specific to page $01 of memory space (e.g. $0100 to $01FF). The stack works "downwards", meaning as you push values on to the stack, the stack pointer (herein referred to as S) decreases. The stack pointer is an 8-bit value, and should be initialised to $FF during program initialisation; the CPU will do this for you, but it's good practise to do it anyways.

As you push values on to the stack (using PHA or PHP), S will decrement by 1. As you pull values off the stack (using PLA or PLP), S will increment by 1.

If you have trouble conceptualising how the 6502 stack works, imagine stacking a bunch of dinner plates on top of one another; you can't take a plate out from the middle of the stack, you have to take one off the very top each time.

Pushing data on to the stack

Let's look at some example code: _init:

 ldx #$ff	; Set the stack pointer to $FF
 txs		; (e.g. $01FF)

_pushstack:

 lda #$e0	; Push value $e0 on to the stack.
 pha		; $01FF now contains $e0, and S is now $FE.
 ldy #$bb	; Push value $bb on to the stack.
 tya
 pha		; $01FE now contains $bb, and S is now $FD.
 txa
 pha		; Push value $ff (from the _init routine) on to the stack.

; $01FD now contains $ff, and S is now $FC.

At this point in our program, we have pushed 3 values on to the stack: $e0, $bb, and $ff. Since $ff was the last thing we pushed onto the stack, it will be the first thing we pull off the stack. We can't pull the $bb value until $ff has been pulled off, and so on -- hence the term "stack".

Pulling data off the stack

Using the above section (Pushing data on to the stack) as a preface, let's continue:

_pullstack:
  pla		; Pull the value $ff off the stack, and put it into the accumulator.
  tax		; S now becomes $FD.

  pla		; Pull the next value ($bb) off the stack, and put it into the X register.
  tay		; S now becomes $FE.

  pla		; Pull $e0 off the stack, and put it into the Y register.
		; S now becomes $FF -- which is where we started!

Pulling may be called "popping" by people who come from an 8080, Z80, or x86 background, where the instruction is called pop.

Stack underflow and overflow

The terms "overflow" and "underflow" refer to situations where the program is either attempting to push more data on to the stack when S is already at $FF, or attempting to pull data off of the stack when S is already at $00. Usually this implies a PHA vs. PLA mismatch of some sort.

Occasionally these two terms are reversed, depending upon who you ask.

Math operations

Simple operations

Addition and subtraction

To be written.

Bitwise (factor of 2) multiplication and division

To multiply the value in A by two, use the instruction ASL A.

To divide the value in A by two, use the instruction LSR A.

To be written.

Complex operations

Multiplication of arbitrary numbers

The following routine multiplies two unsigned 16-bit numbers, and returns an unsigned 32-bit value.

mulplr	= $c0		; ZP location = $c0
partial	= mulplr+2	; ZP location = $c2
mulcnd	= partial+2	; ZP location = $c4

_usmul:
  pha
  tya
  pha

_usmul_1:
  ldy #$10	; Setup for 16-bit multiply
_usmul_2:
  lda mulplr	; Is low order bit set?
  lsr a
  bcc _usmul_4

  clc		; Low order bit set -- add mulcnd to partial product
  lda partial
  adc mulcnd
  sta partial
  lda partial+1
  adc mulcnd+1
  sta partial+1
;
; Shift result into mulplr and get the next bit of the multiplier into the low order bit of mulplr.
;
_usmul_4:
  ror partial+1
  ror partial
  ror mulplr+1
  ror mulplr
  dey
  bne _usmul_2
  pla
  tay
  pla
  rts

Here's an example of the above _usmul routine in action, which multiplies 340*268:

  lda #<340	; Low byte of 16-bit decimal value 340  (value: $54)
  sta mulplr
  lda #>340	; High byte of 16-bit decimal value 340 (value: $01) (makes $0154)
  sta mulplr+1
  lda #<268	; Low byte of 16-bit decimal value 268  (value: $0C)
  sta mulcnd
  lda #>268	; High byte of 16-bit decimal value 268 (value: $01) (makes $010C)
  sta mulcnd+1
  lda #0		; Must be set to zero (0)!
  sta partial
  sta partial+1
  jsr _usmul	; Perform multiplication
;
; RESULTS
;    mulplr    = Low byte of lower word  (bits 0 through 7)
;    mulplr+1  = High byte of lower word (bits 8 through 15)
;    partial   = Low byte of upper word  (bits 16 through 23)
;    partial+1 = High byte of upper word (bits 24 through 31)
;

Division of arbitrary numbers

To be written.

Floating-point numbers

To be written.

Gaming: keeping score

To be written.

If you keep score in a binary number, you must convert it to a sequence of digits before displaying it. The article 16-bit BCD lists a subroutine to do this.

Making simple sounds

To be written.

Controller input

To be written.

Graphics (should be covered elsewhere!)

"Hello, world!" program

Since the NES can't easily do something like printf() (or echo for those familiar with scripting), one of the easiest ways to test code is to output some audio. Something along the lines of...

reset:
  lda #$01	; square 1
  sta $4015
  lda #$08	; period low
  sta $4002
  lda #$02	; period high
  sta $4003
  lda #$bf	; volume
  sta $4000
forever:
  jmp forever