DEV Community

Cover image for 6502 Math - Lab 2
Juro Zaw
Juro Zaw

Posted on

6502 Math - Lab 2

For this lab, we are going to take an existing code and make changes to it using Math in 6502! After last week's lab, I've understood the fundamentals of this Assembly language, and now we are using the Math instructions available to use to implement a small program.

If you are following along, please use the 6502 Emulator to test these codes out!


Initial Code

The following code moves a 5×5 graphic diagonally across the screen:

;
; draw-image-subroutine.6502
;
; This is a routine that can place an arbitrary 
; rectangular image on to the screen at given
; coordinates.
;
; Chris Tyler 2024-09-17
; Licensed under GPLv2+
;

;
; The subroutine is below starting at the 
; label "DRAW:"
;

; Test code for our subroutine
; Moves an image diagonally across the screen

; Zero-page variables
define XPOS $20
define YPOS $21


START:

; Set up the width and height elements of the data structure
  LDA #$05
  STA $12       ; IMAGE WIDTH
  STA $13       ; IMAGE HEIGHT

; Set initial position X=Y=0
  LDA #$00
  STA XPOS
  STA YPOS

; Main loop for diagonal animation
MAINLOOP:

  ; Set pointer to the image
  ; Use G_O or G_X as desired
  ; The syntax #<LABEL returns the low byte of LABEL
  ; The syntax #>LABEL returns the high byte of LABEL

  LDA #<G_O
  STA $10
  LDA #>G_O
  STA $11

  ; Place the image on the screen
  LDA #$10  ; Address in zeropage of the data structure
  LDX XPOS  ; X position
  LDY YPOS  ; Y position
  JSR DRAW  ; Call the subroutine

  ; Delay to show the image
  LDY #$00
  LDX #$50
DELAY:
  DEY
  BNE DELAY
  DEX
  BNE DELAY

  ; Set pointer to the blank graphic
  LDA #<G_BLANK
  STA $10
  LDA #>G_BLANK
  STA $11

  ; Draw the blank graphic to clear the old image
  LDA #$10 ; LOCATION OF DATA STRUCTURE
  LDX XPOS
  LDY YPOS
  JSR DRAW

  ; Increment the position
  INC XPOS
  INC YPOS

  ; Continue for 29 frames of animation
  LDA #28
  CMP XPOS
  BNE MAINLOOP

  ; Repeat infinitely
  JMP START

; ==========================================
;
; DRAW :: Subroutine to draw an image on 
;         the bitmapped display
;
; Entry conditions:
;    A - location in zero page of: 
;        a pointer to the image (2 bytes)
;        followed by the image width (1 byte)
;        followed by the image height (1 byte)
;    X - horizontal location to put the image
;    Y - vertical location to put the image
;
; Exit conditions:
;    All registers are undefined
;
; Zero-page memory locations
define IMGPTR    $A0
define IMGPTRH   $A1
define IMGWIDTH  $A2
define IMGHEIGHT $A3
define SCRPTR    $A4
define SCRPTRH   $A5
define SCRX      $A6
define SCRY      $A7

DRAW:
  ; SAVE THE X AND Y REG VALUES
  STY SCRY
  STX SCRX

  ; GET THE DATA STRUCTURE
  TAY
  LDA $0000,Y
  STA IMGPTR
  LDA $0001,Y
  STA IMGPTRH
  LDA $0002,Y
  STA IMGWIDTH
  LDA $0003,Y
  STA IMGHEIGHT

  ; CALCULATE THE START OF THE IMAGE ON
  ; SCREEN AND PLACE IN SCRPTRH
  ;
  ; THIS IS $0200 (START OF SCREEN) +
  ; SCRX + SCRY * 32
  ; 
  ; WE'LL DO THE MULTIPLICATION FIRST
  ; START BY PLACING SCRY INTO SCRPTR
  LDA #$00
  STA SCRPTRH
  LDA SCRY
  STA SCRPTR
  ; NOW DO 5 LEFT SHIFTS TO MULTIPLY BY 32
  LDY #$05     ; NUMBER OF SHIFTS
MULT:
  ASL SCRPTR   ; PERFORM 16-BIT LEFT SHIFT
  ROL SCRPTRH
  DEY
  BNE MULT

  ; NOW ADD THE X VALUE
  LDA SCRX
  CLC
  ADC SCRPTR
  STA SCRPTR
  LDA #$00
  ADC SCRPTRH
  STA SCRPTRH

  ; NOW ADD THE SCREEN BASE ADDRESS OF $0200
  ; SINCE THE LOW BYTE IS $00 WE CAN IGNORE IT
  LDA #$02
  CLC
  ADC SCRPTRH
  STA SCRPTRH
  ; NOTE WE COULD HAVE DONE TWO: INC SCRPTRH

  ; NOW WE HAVE A POINTER TO THE IMAGE IN MEM
  ; COPY A ROW OF IMAGE DATA
COPYROW:
  LDY #$00
ROWLOOP:
  LDA (IMGPTR),Y
  STA (SCRPTR),Y
  INY
  CPY IMGWIDTH
  BNE ROWLOOP

  ; NOW WE NEED TO ADVANCE TO THE NEXT ROW
  ; ADD IMGWIDTH TO THE IMGPTR
  LDA IMGWIDTH
  CLC
  ADC IMGPTR
  STA IMGPTR
  LDA #$00
  ADC IMGPTRH
  STA IMGPTRH

  ; ADD 32 TO THE SCRPTR
  LDA #32
  CLC
  ADC SCRPTR
  STA SCRPTR
  LDA #$00
  ADC SCRPTRH
  STA SCRPTRH

  ; DECREMENT THE LINE COUNT AND SEE IF WE'RE
  ; DONE
  DEC IMGHEIGHT
  BNE COPYROW

  RTS

; ==========================================

; 5x5 pixel images

; Image of a blue "O" on black background
G_O:
DCB $00,$0e,$0e,$0e,$00
DCB $0e,$00,$00,$00,$0e
DCB $0e,$00,$00,$00,$0e
DCB $0e,$00,$00,$00,$0e
DCB $00,$0e,$0e,$0e,$00

; Image of a yellow "X" on a black background
G_X:
DCB $07,$00,$00,$00,$07
DCB $00,$07,$00,$07,$00
DCB $00,$00,$07,$00,$00
DCB $00,$07,$00,$07,$00
DCB $07,$00,$00,$00,$07

; Image of a black square
G_BLANK:
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
Enter fullscreen mode Exit fullscreen mode

When you assemble and run this code, it will generate either an 'X' or an 'O' (depending on the variable used) which will move across the screen diagonally.

One thing to note here is that when the image reaches the end of its path, it will regenerate at the starting position and loop again.

Now the change we want to do is to make sure it bounces on every tap of the corner. The changes are highlighted in the next section.


Bouncing Graphic

1. New Variables!

We will create two new variables; they will store the current direction of the graphic along the X and Y axes.

The directional values will be either +1 ($01) or -1 ($FF).

define XINC    $22      ; X increment (-1 or +1)
define YINC    $23      ; Y increment (-1 or +1)
Enter fullscreen mode Exit fullscreen mode

2. Change Initial Positions

The current starting position is in the top left corner. We will change it to ensure that the image starts from somewhere that is not touching the corner.

While we're at it, we will also initialize the XINC and YINC variables that we defined. Both of them are first initialized to +1 ($01)

; Set Initial Position
LDA #$0A      ; 10 in decimal
STA XPOS
LDA #$05      ; 5 in decimal
STA YPOS

; Initialize increments: XINC = +1, YINC = +1
LDA #$01      ; +1 represented as $01
STA XINC
STA YINC
Enter fullscreen mode Exit fullscreen mode

3. Update Positions Based on Increments

Let's start with just looking at the X axis. We will determine if the XPOS (the X position of the image) should increment or decrement (move right or left) based on the XINC being +1 or -1.

We do this by branching to INC_XPOS if equal to +1. If not, it will just decrement.

Logically, the next step after moving the image is to check whether it is hitting a boundary or not. We jump to CHECK_X_BOUNDARY at the end of this block.

; Update position based on increments
; Check X direction
LDA XINC
CMP #$01
BEQ INC_XPOS
; If XINC = $FF (-1), decrement XPOS
LDA XPOS
SEC
SBC #$01
STA XPOS
JMP CHECK_X_BOUNDARY

INC_XPOS:
; If XINC = +1, increment XPOS
INC XPOS
Enter fullscreen mode Exit fullscreen mode

4. Boundary Checking for X Axis

Both right edge and left edge will be checked. Basically if XPOS is equal or greater than 27 ($1B), move left. The value is 27 because the screen is 32 pixels wide and the image is 5 pixels. (32 - 5). If it's less, move right.

CHECK_X_BOUNDARY:
; Check if XPOS has hit screen boundaries
; Screen width is 32 pixels and image width is 5
; Max XPOS = 32 - 5 = 27 ($1B)
LDA XPOS
CMP #$1B
BCC NO_CHANGE_X
; If XPOS >= 27, reverse XINC to -1
LDA #$FF
STA XINC
JMP UPDATE_Y

NO_CHANGE_X:
; Check if XPOS <= 0
LDA XPOS
CMP #$00
BNE UPDATE_Y
; If XPOS <= 0, reverse XINC to +1
LDA #$01
STA XINC

Enter fullscreen mode Exit fullscreen mode

5. Repeat 3 and 4 for Y axis

We will repeat the above two steps for the Y-axis as well.

⭐ Final Code

This brings us to the finalized code. If you run this code on the Emulator,

;
; draw-image-subroutine.bouncing.6502
;
; This routine places an arbitrary 
; rectangular image on the screen at given
; coordinates and makes it bounce within 
; the screen boundaries.
;
; Chris Tyler 2024-09-17
; Licensed under GPLv2+
;

;
; The subroutine is below starting at the 
; label "DRAW:"
;

; Test code for our subroutine
; Moves an image diagonally across the screen and bounces it off the edges

; Zero-page variables
define XPOS    $20      ; Current X position
define YPOS    $21      ; Current Y position
define XINC    $22      ; X increment (-1 or +1)
define YINC    $23      ; Y increment (-1 or +1)

START:

    ; Set up the width and height elements of the data structure
    LDA #$05
    STA $12       ; IMAGE WIDTH
    STA $13       ; IMAGE HEIGHT

    ; Set initial position
    LDA #$0A      ; 10 in decimal
    STA XPOS
    LDA #$05      ; 5 in decimal
    STA YPOS

    ; Initialize increments: XINC = +1, YINC = +1
    LDA #$01      ; +1 represented as $01
    STA XINC
    STA YINC

MAINLOOP:

    ; Set pointer to the image
    ; Use G_O or G_X as desired
    ; The syntax #<LABEL returns the low byte of LABEL
    ; The syntax #>LABEL returns the high byte of LABEL

    LDA #<G_O
    STA $10
    LDA #>G_O
    STA $11

    ; Place the image on the screen
    LDA #$10      ; Address in zeropage of the data structure
    LDX XPOS      ; X position
    LDY YPOS      ; Y position
    JSR DRAW      ; Call the subroutine

    ; Delay to show the image
    LDY #$00
    LDX #$50
DELAY:
    DEY
    BNE DELAY
    DEX
    BNE DELAY

    ; Set pointer to the blank graphic
    LDA #<G_BLANK
    STA $10
    LDA #>G_BLANK
    STA $11

    ; Draw the blank graphic to clear the old image
    LDA #$10      ; LOCATION OF DATA STRUCTURE
    LDX XPOS
    LDY YPOS
    JSR DRAW

    ; Update position based on increments
    ; Check X direction
    LDA XINC
    CMP #$01
    BEQ INC_XPOS
    ; If XINC = $FF (-1), decrement XPOS
    LDA XPOS
    SEC
    SBC #$01
    STA XPOS
    JMP CHECK_X_BOUNDARY

INC_XPOS:
    ; If XINC = +1, increment XPOS
    INC XPOS

CHECK_X_BOUNDARY:
    ; Check if XPOS has hit screen boundaries
    ; Screen width is 32 pixels and image width is 5
    ; Max XPOS = 32 - 5 = 27 ($1B)
    LDA XPOS
    CMP #$1B
    BCC NO_CHANGE_X
    ; If XPOS >= 27, reverse XINC to -1
    LDA #$FF
    STA XINC
    JMP UPDATE_Y

NO_CHANGE_X:
    ; Check if XPOS <= 0
    LDA XPOS
    CMP #$00
    BNE UPDATE_Y
    ; If XPOS <= 0, reverse XINC to +1
    LDA #$01
    STA XINC

UPDATE_Y:
    ; Check Y direction
    LDA YINC
    CMP #$01
    BEQ INC_YPOS
    ; If YINC = $FF (-1), decrement YPOS
    LDA YPOS
    SEC
    SBC #$01
    STA YPOS
    JMP CHECK_Y_BOUNDARY

INC_YPOS:
    ; If YINC = +1, increment YPOS
    INC YPOS

CHECK_Y_BOUNDARY:
    ; Check if YPOS has hit screen boundaries
    LDA YPOS
    CMP #$1B
    BCC NO_CHANGE_Y
    ; If YPOS >= 27, reverse YINC to -1
    LDA #$FF
    STA YINC
    JMP MAINLOOP

NO_CHANGE_Y:
    ; Check if YPOS <= 0
    LDA YPOS
    CMP #$00
    BNE MAINLOOP
    ; If YPOS <= 0, reverse YINC to +1
    LDA #$01
    STA YINC

    JMP MAINLOOP

; ==========================================
;
; DRAW :: Subroutine to draw an image on 
;         the bitmapped display
;
; Entry conditions:
;    A - location in zero page of: 
;        a pointer to the image (2 bytes)
;        followed by the image width (1 byte)
;        followed by the image height (1 byte)
;    X - horizontal location to put the image
;    Y - vertical location to put the image
;
; Exit conditions:
;    All registers are undefined
;
; Zero-page memory locations
define IMGPTR    $A0
define IMGPTRH   $A1
define IMGWIDTH  $A2
define IMGHEIGHT $A3
define SCRPTR    $A4
define SCRPTRH   $A5
define SCRX      $A6
define SCRY      $A7

DRAW:
    ; SAVE THE X AND Y REG VALUES
    STY SCRY
    STX SCRX

    ; GET THE DATA STRUCTURE
    TAY
    LDA $0000,Y
    STA IMGPTR
    LDA $0001,Y
    STA IMGPTRH
    LDA $0002,Y
    STA IMGWIDTH
    LDA $0003,Y
    STA IMGHEIGHT

    ; CALCULATE THE START OF THE IMAGE ON
    ; SCREEN AND PLACE IN SCRPTRH
    ;
    ; THIS IS $0200 (START OF SCREEN) +
    ; SCRX + SCRY * 32
    ; 
    ; WE'LL DO THE MULTIPLICATION FIRST
    ; START BY PLACING SCRY INTO SCRPTR
    LDA #$00
    STA SCRPTRH
    LDA SCRY
    STA SCRPTR
    ; NOW DO 5 LEFT SHIFTS TO MULTIPLY BY 32
    LDY #$05     ; NUMBER OF SHIFTS
MULT:
    ASL SCRPTR   ; PERFORM 16-BIT LEFT SHIFT
    ROL SCRPTRH
    DEY
    BNE MULT

    ; NOW ADD THE X VALUE
    LDA SCRX
    CLC
    ADC SCRPTR
    STA SCRPTR
    LDA #$00
    ADC SCRPTRH
    STA SCRPTRH

    ; NOW ADD THE SCREEN BASE ADDRESS OF $0200
    ; SINCE THE LOW BYTE IS $00 WE CAN IGNORE IT
    LDA #$02
    CLC
    ADC SCRPTRH
    STA SCRPTRH
    ; NOTE WE COULD HAVE DONE TWO: INC SCRPTRH

    ; NOW WE HAVE A POINTER TO THE IMAGE IN MEM
    ; COPY A ROW OF IMAGE DATA
COPYROW:
    LDY #$00
ROWLOOP:
    LDA (IMGPTR),Y
    STA (SCRPTR),Y
    INY
    CPY IMGWIDTH
    BNE ROWLOOP

    ; NOW WE NEED TO ADVANCE TO THE NEXT ROW
    ; ADD IMGWIDTH TO THE IMGPTR
    LDA IMGWIDTH
    CLC
    ADC IMGPTR
    STA IMGPTR
    LDA #$00
    ADC IMGPTRH
    STA IMGPTRH

    ; ADD 32 TO THE SCRPTR
    LDA #32
    CLC
    ADC SCRPTR
    STA SCRPTR
    LDA #$00
    ADC SCRPTRH
    STA SCRPTRH

    ; DECREMENT THE LINE COUNT AND SEE IF WE'RE
    ; DONE
    DEC IMGHEIGHT
    BNE COPYROW

    RTS

; ==========================================

; 5x5 pixel images

; Image of a blue "O" on black background
G_O:
DCB $00,$0e,$0e,$0e,$00
DCB $0e,$00,$00,$00,$0e
DCB $0e,$00,$00,$00,$0e
DCB $0e,$00,$00,$00,$0e
DCB $00,$0e,$0e,$0e,$00

; Image of a yellow "X" on a black background
G_X:
DCB $07,$00,$00,$00,$07
DCB $00,$07,$00,$07,$00
DCB $00,$00,$07,$00,$00
DCB $00,$07,$00,$07,$00
DCB $07,$00,$00,$00,$07

; Image of a black square
G_BLANK:
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00

Enter fullscreen mode Exit fullscreen mode

Conclusion

I learned on more advanced instructions such as subroutines, loops and jumps working on this lab. I did have trouble wrapping my mind around decimal values which are represented as hex in the 6502 system. Adding and subtracting values are clear in decimals but I had to take some time to actually implement them using memory addresses and hex notation.

All in all, 6502 Math Lab has taught me more to think in the 6502 language. Logics such as if/else, loops and functions are very easy to implement in languages I know such as JavaScript, but it is interesting to see how a low level language can implement these logics!

Thank you so much for reading! 😄

Top comments (0)