Lab 2 - 6502 Math Lab

Introduction
In this blog post, I will share my experience working on the second lab of the SPO (Systems Programming and Operating Systems) course. The goal of this lab was to create a subroutine in 6502 Assembly Language that draws an image on a bitmapped screen and then animate it by making it bounce around the screen. The lab involved writing code to move a graphic diagonally across the screen and then modifying it to make the graphic bounce off the edges of the screen.


The Code 
The initial code provided draws a 5x5 pixel image (either an "O" or an "X") on the screen and moves it diagonally from the top-left corner to the bottom-right corner. The image is cleared and redrawn in a new position to create the illusion of movement. The code uses a delay loop to slow down the animation so that it is visible to the human eye.


Here is the initial code:

;
; 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


Modifying the Code for Bouncing

To make the graphic bounce off the edges of the screen, I made the following modifications:

  1. Starting Position: I chose a starting position where X and Y have different values. For example, X = 10 and Y = 5.
  2. Increments: I introduced two new zero-page variables, XINC and YINC, to hold the increments for the X and Y positions. These can be either -1 or +1, depending on the direction of movement.
  3. Bouncing Logic: I added logic to check if the graphic has hit the edge of the screen. If it has, the corresponding increment is negated to reverse the direction.


;
; 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    $5      ; X increment (-1 or +1)
define YINC    $6      ; 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 #$08      ; 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 X position
    LDA XPOS
    CLC
    ADC XINC
    STA XPOS

    ; Check X boundary
    CMP #$1B  ; Check if XPOS >= 27
    BCC CHECK_X_MIN
    LDA #$FF  ; Reverse direction
    STA XINC
    JMP UPDATE_Y

CHECK_X_MIN:
    CMP #$00  ; Check if XPOS <= 0
    BNE UPDATE_Y
    LDA #$01  ; Reverse direction
    STA XINC

UPDATE_Y:
    ; Update Y position
    LDA YPOS
    CLC
    ADC YINC
    STA YPOS

    ; Check Y boundary
    CMP #$1B  ; Check if YPOS >= 27
    BCC CHECK_Y_MIN
    LDA #$FF  ; Reverse direction
    STA YINC
    JMP MAINLOOP

CHECK_Y_MIN:
    CMP #$00  ; Check if YPOS <= 0
    BNE MAINLOOP
    LDA #$01  ; Reverse direction
    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


How the Code Works
  1. Initialization:
    1. The graphic starts at (XPOS = 10, YPOS = 8).
    2. The increments XINC and YINC are set to +1, making the graphic move diagonally down and to the right.

  2. Main Loop:
    1. The graphic is drawn at the current (XPOS, YPOS) using the DRAW subroutine.
    2. A delay loop ensures the animation is visible.
    3. The graphic is cleared by drawing a blank image at the current position.
    4. The XPOS and YPOS are updated by adding XINC and YINC.

  3. Boundary Checking:
    1. If XPOS reaches 0 or 27, XINC is reversed.
    2. If YPOS reaches 0 or 27, YINC is reversed.

  4. Drawing the Graphic:
    1. The DRAW subroutine calculates the screen address for the graphic and copies the image data to the screen.

Results
The graphic moves diagonally across the screen, bouncing off the edges. The animation is smooth, and the graphic stays within the visible area of the screen. The delay loop ensures the movement is visible to the human eye.

Reflections
This lab was an excellent exercise in understanding 6502 Assembly Language and working with bitmapped displays. The bouncing logic added complexity but was rewarding to implement. I gained a deeper understanding of memory manipulation, boundary checking, and animation techniques in low-level programming.

Comments

Popular posts from this blog

Lab-1: Exploring 6502 Assembly

Project Stage III: Multi-Clone Analysis in GCC Pass