More exercises with the 6502 ASM emulator

jerryhue

Gerardo Enrique Arriaga Rendon

Posted on October 5, 2021

More exercises with the 6502 ASM emulator

Last week, on my Software Portability and Optimization, we had our lesson about branching and control flow on the 6502. While I understood the basics, it is still difficult to think of a more structured way to write code when you have several limitations and you are used to high level constructs in modern programming languages. Even C, what many people consider a low level programming language, still provides so many abstractions over the actual architecture of the processor.

Either way, I managed to somewhat finish one exercise that I got assigned with a team last week. There were 4 tasks we could choose from, and we chose the kaleidoscope effect.

The Kaleidoscope Effect

Essentially, we need to let a pen cursor draw on one of the quarters of the screen, and that drawing should be mirrored on the other quadrants of the screen.

A nice example is the following image:

Kaleidoscope effect on the 6502 emulator screen

We can see that the upper left quadrant is actually being mirrored on the X and Y axis to produce the effect. The top right quadrant is derived by mirroring on the Y axis, while the bottom left is derived by mirroring on the X axis, and the bottom right is derived by mirroring on both axes.

Mirroring

The code that is in charge of such mirroring is the following:

LDA POINTER ;; save the pointer to the
PHA         ;; original location in top_left_quad
LDA POINTER_H
PHA

LDA #$10        ;; mirror on the y axis (drawing top right quad)
CLC
SBC COL
CLC
ADC #$10
TAY
LDA DOT
STA (POINTER),y

TYA
PHA ; save the y offset

                ;; start mirroring on x axis (bottom left quad)
lda #$10    ; load POINTER with start-of-row
CLC
SBC ROW
CLC
ADC #$10
TAY
lda table_low,y
sta POINTER
lda table_high,y
sta POINTER_H
ldy COL     ; store CURSOR at POINTER plus COL
lda DOT
sta (POINTER),y

PLA             ;; reusing the prev calculations to mirror on
TAY             ;; both axes now
lda DOT
sta (POINTER),y

PLA
STA POINTER_H
PLA
STA POINTER
Enter fullscreen mode Exit fullscreen mode

It seems long, mainly because of the heavy math we have to do, but overall, it is very straight to the point.

Another feature of this code is that we can draw with several pen colours. This is what the line lda DOT. We have a cell that is labelled with DOT, which stores the current pen colour. This acts very similarly to how a variable works on a high level programming language, but it is very actually very primitive. In reality, this is a macro directive that associates the string DOT with the string $01, which is the address of the cell. Variables on programming languages behave a little bit more differently, since they have scope associated with them, among other features that we are used to.

There is also the instructions PLA and PHA, which seem to be new instructions we haven't seen before.

PLA and PHA pop and push out of the stack, respectively. If you are familiar with concepts related to computer architecture, you may have heard the concept of a 'stack' and a 'heap'. Essentially, the stack is a region of memory managed in a really special way. We use something called a 'stack pointer', which points at the top of the stack, and this pointer keeps track of how much memory the stack is currently taking. This is how high level code keeps track of variables. If a function calls another function, we need a way to save the state of the caller so that it does not get lost after the callee finishes its execution.

I used the stack to save the pointer that I was using to draw on the top left quadrant, since it also gets used on another part of the program.

Pen Colour

Returning back to the colour of the pen, the way we change the colour is by reading a specific place in memory that stores the key pressed. If you press a number from 0 to 9, it changes the colour of the pen depending on the number.

The code that deals with that specific part is this:

getkey: lda $ff     ; get a keystroke

    ldx #$00    ; clear out the key buffer
    stx $ff

    cmp #$30
    bmi getkey
    cmp #$40
    bpl continue

    sec
    sbc #$30
    tay
    lda color_pallete, y
    sta DOT
Enter fullscreen mode Exit fullscreen mode

This specific implementation of the emulator encodes keys with the ASCII encoding. Thus, we make sure that our key is within the ranges 0x30 to 0x39, which are the values for the characters 0 to 9. If it is, we want to get our values from a table, that being the following:

color_pallete:
dcb $01,$02,$03,$04,$05,$06,$07,$08,$09,$0a
Enter fullscreen mode Exit fullscreen mode

I declare an array of constant bytes, where each value corresponds to a specific color value. While I could simply subtract a specific offset from the key code value to calculate the color value, this would restrict us to only getting color values that have to be next to each other. If we wanted to specify another order, we wouldn't be able to, unless we use the table approach. In another hand, if we don't use this table, we would save 10 bytes of memory, which could be very valuable.

The Whole Program

The code of the program was derived from a sample program the professor gave us as an aid. The whole program is the following:

 ; zero-page variable locations
 define ROW     $20 ; current row
 define COL     $21 ; current column
 define POINTER     $10 ; ptr: start of row
 define POINTER_H   $11

 ; constants
 define DOT     $01 ; dot colour location
 define CURSOR      $04 ; purple colour


 setup: lda #$0f    ; set initial ROW,COL
    sta ROW
    sta COL
    LDA #$01
    STA DOT

 draw:  jsr draw_cursor

 getkey:    lda $ff     ; get a keystroke

    ldx #$00    ; clear out the key buffer
    stx $ff

    cmp #$30
    bmi getkey
    cmp #$40
    bpl continue

    sec
    sbc #$30
    tay
    lda color_pallete, y
    sta DOT
    jmp done

continue:   cmp #$43    ; handle C or c
    beq clear
    cmp #$63
    beq clear

    cmp #$80    ; if not a cursor key, ignore
    bmi getkey
    cmp #$84
    bpl getkey

    pha     ; save A

    lda DOT ; set current position to DOT
    sta (POINTER),y
    jsr draw_on_quads

    pla     ; restore A

    cmp #$80    ; check key == up
    bne check1

    dec ROW     ; ... if yes, decrement ROW
    jmp done

 check1:    cmp #$81    ; check key == right
    bne check2

    inc COL     ; ... if yes, increment COL
    jmp done

 check2:    cmp #$82    ; check if key == down
    bne check3

    inc ROW     ; ... if yes, increment ROW
    jmp done

 check3:    cmp #$83    ; check if key == left
    bne done

    dec COL     ; ... if yes, decrement COL
    clc
    bcc done

 clear: lda table_low   ; clear the screen
    sta POINTER
    lda table_high
    sta POINTER_H

    ldy #$00
    tya

 c_loop:    sta (POINTER),y
    iny
    bne c_loop

    inc POINTER_H
    ldx POINTER_H
    cpx #$06
    bne c_loop

 done:  clc     ; repeat
    bcc draw


 draw_cursor:
    lda ROW     ; ensure ROW is in range 0:31
    and #$0f
    sta ROW

    lda COL     ; ensure COL is in range 0:31
    and #$0f
    sta COL

    ldy ROW     ; load POINTER with start-of-row
    lda table_low,y
    sta POINTER
    lda table_high,y
    sta POINTER_H

    ldy COL     ; store CURSOR at POINTER plus COL
    lda #CURSOR
    sta (POINTER),y

    rts

 draw_on_quads:     
    LDA POINTER ;; save the pointer to the
    PHA         ;; original location in top_left_quad
    LDA POINTER_H
    PHA

    LDA #$10
    CLC
    SBC COL
    CLC
    ADC #$10
    TAY
    LDA DOT
    STA (POINTER),y

    TYA
    PHA ; save the y offset

    lda #$10    ; load POINTER with start-of-row
    CLC
    SBC ROW
    CLC
    ADC #$10
    TAY
    lda table_low,y
    sta POINTER
    lda table_high,y
    sta POINTER_H

    ldy COL     ; store CURSOR at POINTER plus COL
    lda DOT
    sta (POINTER),y

    PLA
    TAY
    lda DOT
    sta (POINTER),y

    PLA
    STA POINTER_H
    PLA
    STA POINTER

    RTS

 ; these two tables contain the high and low bytes
 ; of the addresses of the start of each row

 table_high:
 dcb $02,$02,$02,$02,$02,$02,$02,$02
 dcb $03,$03,$03,$03,$03,$03,$03,$03
 dcb $04,$04,$04,$04,$04,$04,$04,$04
 dcb $05,$05,$05,$05,$05,$05,$05,$05,

 table_low:
 dcb $00,$20,$40,$60,$80,$a0,$c0,$e0
 dcb $00,$20,$40,$60,$80,$a0,$c0,$e0
 dcb $00,$20,$40,$60,$80,$a0,$c0,$e0
 dcb $00,$20,$40,$60,$80,$a0,$c0,$e0

color_pallete:
dcb $01,$02,$03,$04,$05,$06,$07,$08,$09,$0a
Enter fullscreen mode Exit fullscreen mode

I would have to say that there are a lot of room for improvements, such as improving the whole code structure. Right now, the only way to understand the program is by reading the program from start to finish, which is fine for the size of the program, but it is still somewhat difficult to do.

Another improvement is the responsiveness of the keyboard. Right now, the program seems to take a little bit of time to move the cursor or to change the colour of the pen, which can be rather frustrating.

In any case, this is the best I can do with the lab without spending too much time on it. Thank you if you read all the way and I hope you read other of my posts!

πŸ’– πŸ’ͺ πŸ™… 🚩
jerryhue
Gerardo Enrique Arriaga Rendon

Posted on October 5, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

64-bit Assembly Programming: AArch64
assembly 64-bit Assembly Programming: AArch64

October 26, 2024

Modifying the 6502 Assembly Program
assembly Modifying the 6502 Assembly Program

October 6, 2024

6502 Assembly - Intro
assembly 6502 Assembly - Intro

October 1, 2024