More exercises with the 6502 ASM emulator
Tags: spo600
Published: 2021-10-05 15:29:04

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/16hjeqyjli6jg0dtipcv.png)

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:

```asm
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
```

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:

```asm
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
```

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:

```asm
color_pallete:
dcb $01,$02,$03,$04,$05,$06,$07,$08,$09,$0a
```

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:

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

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!