SP600 - Lab 2: 6502 Math Lab

In this post, I'll go into the world of 6502 assembly language, which is one of the most fundamental low-level languages in computing. This lab focusses on using assembly to control the movement of a simple graphic on a bitmapped display, simulating basic animation with a set of programmed instructions.


The goal of this lab is to create a graphic that moves across the screen and "bounces" off the edges, changing direction whenever it encounters a boundary. As part of the lab, I used the 6502 emulator to write and test the assembly code, as well as experiment with various movement increments and behaviours. This project provides an introduction to low-level programming concepts that are required for understanding more complex architectures such as x86_64 and AArch64.

Set Up
1. I haved open the 6502 Emulator.

Initial Code
2. Then copied and pasted for the 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

; Set up the data structure
; The syntax #<LABEL returns the low byte of LABEL
; The syntax #>LABEL returns the high byte of LABEL
LDA #<G_X     ; POINTER TO GRAPHIC
STA $10
LDA #>G_X
STA $11
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
  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 $0600

; ==========================================
;
; 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
3. I tested the code by pressed the Assemble button and the pressed the Run button

then I saw on the 6502 Emulator the image move diagonally .

Bouncing Graphic
4. Setting the Initial Position for the Graphic
To choose a sarting point where X and Y have different values, I change the initial setup of the program by assigning different values to XPOS and YPOS just before entering the main loop. I decided to start the graphic at X position of 5 and Y position of 3.

; Set initial position X=5, Y=3
LDA #$05   ; X position
STA XPOS
LDA #$03   ; Y position
STA YPOS
This ensures that the graphic begins moving across the screen at coordinates (5, 3), giving it a slight offset from the origin. By initialising X and Y with different values, the graphic will move in a more interesting manner than simply along a straight diagonal.

5. Chossing the X and Y Increments
To control the graphic's movement, I needed to specify how much the X and Y positions would change between frames of the animation. In this case, I chose an X increment and a Y increment that would increase or decrease the positions by one pixel in both directions. This enables the graphic to move smoothly across the screen.
I chose +1 for the X increment and -1 for the Y increment to move the graphic to the right and upward, respectively. These increments are saved in the X_INC and Y_INC zero-page variables.
define X_INC $22
define Y_INC $23

LDA #$01    ; X increment (+1)
STA X_INC

LDA #$FF    ; Y increment (-1)
STA Y_INC
The value #$01 for X_INC indicates that the X position will increase by one pixel per frame, causing the graphic to move to the right. The value #$FF (which is -1 in two's complement) for Y_INC indicates that the Y position will decrease by one pixel, moving the graphic upward.

This setup allows the graphic to move diagonally in a unique way, resulting in a more dynamic animation as it moves across the screen.

6. Moving the Graphic with X and Y Increments
To animate the graphic, I added X and Y increments to the current X and Y positions with each loop iteration. This moves the graphic smoothly across the screen.
; Update X position
LDA XPOS
CLC
ADC X_INC
STA XPOS

; Update Y position
LDA YPOS
CLC
ADC Y_INC
STA YPOS
Each loop increases the X position by +1 (moving right) and decreases the Y position by -1 (moving up), resulting in a diagonal motion.

7. Making the Graphic Bounce
To make the graphic bounce off the screen edges, I added a check for when it reached the boundaries and reversed the direction by flipping the X and Y increments. This allows it to bounce off the screen's left and right edges, as well as the top and bottom.
; Check for X bounce (left/right edges)
LDA XPOS
CMP #$00
BEQ REVERSE_X
CMP #$1F   ; Assuming screen width of 32
BEQ REVERSE_X
JMP NO_X_BOUNCE

REVERSE_X:
LDA X_INC
EOR #$FF   ; Flip increment direction
STA X_INC
NO_X_BOUNCE:

; Check for Y bounce (top/bottom edges)
LDA YPOS
CMP #$00
BEQ REVERSE_Y
CMP #$17   ; Assuming screen height of 24
BEQ REVERSE_Y
JMP NO_Y_BOUNCE

REVERSE_Y:
LDA Y_INC
EOR #$FF   ; Flip increment direction
STA Y_INC
NO_Y_BOUNCE:
This code determines whether the graphic has hit the screen's edges and flips the increments to reverse its movement, resulting in a bouncing effect.

Full code:
- Start the graphic at position (5,3)
- Move it diagonally using increments of +1 for X and -1 for Y.
- Bounce it off the screen edges when it reaaches the boundaries.
;
; 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+
;

; Zero-page variables
define XPOS $20
define YPOS $21
define X_INC $22
define Y_INC $23

; Set up the data structure
; The syntax #<LABEL returns the low byte of LABEL
; The syntax #>LABEL returns the high byte of LABEL
LDA #<G_X     ; POINTER TO GRAPHIC
STA $10
LDA #>G_X
STA $11
LDA #$05
STA $12       ; IMAGE WIDTH
STA $13       ; IMAGE HEIGHT

; Set initial position X=5, Y=3
LDA #$05
STA XPOS
LDA #$03
STA YPOS

; Set X and Y increments (+1 for X, -1 for Y)
LDA #$01    ; X increment
STA X_INC
LDA #$FF    ; Y increment (-1 in two's complement)
STA Y_INC

; Main loop for diagonal animation
MAINLOOP:

  ; Set pointer to the image (G_O or G_X as desired)
  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 X_INC
  STA XPOS

  ; Update Y position
  LDA YPOS
  CLC
  ADC Y_INC
  STA YPOS

  ; Check for X bounce (left/right edges)
  LDA XPOS
  CMP #$00
  BEQ REVERSE_X
  CMP #$1F   ; Assuming screen width of 32
  BEQ REVERSE_X
  JMP NO_X_BOUNCE

REVERSE_X:
  LDA X_INC
  EOR #$FF   ; Flip increment direction
  STA X_INC
NO_X_BOUNCE:

  ; Check for Y bounce (top/bottom edges)
  LDA YPOS
  CMP #$00
  BEQ REVERSE_Y
  CMP #$17   ; Assuming screen height of 24
  BEQ REVERSE_Y
  JMP NO_Y_BOUNCE

REVERSE_Y:
  LDA Y_INC
  EOR #$FF   ; Flip increment direction
  STA Y_INC
NO_Y_BOUNCE:

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

  ; Repeat infinitely
  JMP $0600

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

Optional Challenges
1. Allowing Values Other Than -1 and +1 for X and Y Increments
To make the movement more dynamic, I changed the code to allow for different X and Y increments. Instead of being limited to +1 or -1, the increments could take any positive or negative value.
LDA #$02    ; X increment (+2)
STA X_INC
LDA #$FE    ; Y increment (-2, two's complement)
STA Y_INC
This causes the graphic to move faster horizontally (2 pixels per frame) and slower vertically (2 pixels in the opposite direction).

2. Permitting Fractional Increments
I divided the movement into two parts: the whole number and the fractional part. For example, to simulate a +1.5 increment, I added a fractional part every other frame.
define X_FRAC $24  ; Variable to store fractional part
define Y_FRAC $25

; Initialize with whole and fractional parts
LDA #$01    ; Whole part of X increment
STA X_INC
LDA #$80    ; Fractional part (0.5 in fixed-point)
STA X_FRAC

LDA #$FF    ; Whole part of Y increment (-1)
STA Y_INC
LDA #$C0    ; Fractional part (-0.75 in fixed-point)
STA Y_FRAC

; Add fractional part to position
LDA XPOS
CLC
ADC X_FRAC
STA XPOS

LDA YPOS
CLC
ADC Y_FRAC
STA YPOS
In each iteration, fractional parts are added to whole number positions, resulting in smoother movements that simulate fractional increments.

3. Changing the Graphic Each Time It Bounces
For visual interest, I changed the graphic every time it bounced off a wall. I used two different graphics (G_O and G_X), and each time the graphic encountered an edge, it switched to the other.
define GRAPHIC_FLAG $26

LDA GRAPHIC_FLAG
EOR #$01        ; Flip the flag (0 to 1 or 1 to 0)
STA GRAPHIC_FLAG

LDA GRAPHIC_FLAG
BEQ USE_G_O

; Use G_X graphic
LDA #<G_X
STA $10
LDA #>G_X
STA $11
JMP CONTINUE

USE_G_O:
; Use G_O graphic
LDA #<G_O
STA $10
LDA #>G_O
STA $11

CONTINUE:
This causes the graphic to alternate between the blue "O" and yellow "X" each time it reaches the screen's edge, creating a fun visual effect as it bounces.


Reflection
Working on this lab gave me firsthand knowledge of how low-level programming works, particularly with the 6502 assembly language. Assembly programming differs from working with high-level languages in that it requires direct memory and register management, which is both challenging and rewarding.

One of the most intriguing aspects was learning how to manually control the movement of a graphic on the screen, pixel by pixel. Tasks abstracted away in higher-level languages, such as screen rendering and boundary detection, necessitated meticulous assembly. It made me appreciate how much modern languages simplify things for developers.

The most difficult part for me was adjusting to how precise and manual assembly programming is. Working with binary and hexadecimal numbers, as well as managing carry flags during addition and subtraction, required a high level of concentration. I also found it rewarding to simulate fractional increments with fixed-point arithmetic, which increased complexity and forced me to think creatively about how to represent non-integer values in a language that does not naturally support them.

By the end of the lab, I could see the results of my efforts as the graphic moved and bounced across the screen, changing direction as it approached the edges. The optional challenges, such as allowing different increments and switching graphics on bounces, enhanced the program's dynamic and visually appealing design.

Overall, this lab taught me the fundamentals of assembly language while also emphasising the complexities of low-level programming. It has prepared me to explore more advanced architectures such as x86_64 and AArch64, and I'm excited to tackle more complex assembly tasks in the future.

Nhận xét

Bài đăng phổ biến từ blog này

Project Stage 2 (Part 2): Set Up GCC for My Clone-Pruning Pass

Project Stage 2 (Part 3): Implementing the Clone-Pruning Logic

SPO600 - Lab 3: 6502 Program Lab: Inches to Feet Converter