ASM day 3: I figure out branching and play Sporcle
Learning assembly by heart
Without further ado, we’re diving into the Branching section of the 6502 tutorial I’m following. I’m hopeful to learn how to use the equivalent of if
statements in assembly.
The following program is given as an example of branching:
LDX #$08
decrement:
DEX
STX $0200
CPX #$03
BNE decrement
STX $0201
BRK
First off, I notice a label called decrement
. Instead of JMP
ing to an exact address figured out through disassembly, we can instead simply refer to a label. That makes things easier straight off the bat.
I interpreted this program as follows: We store value 8 in the X
register. After the decrement
label, we then store X
’s value at memory address $0200
, which I later realize is the start of screen memory ($0200
-$05ff
). With CPX
we compare 3 to X. If it’s not equal, we jump back to the address of the decrement
label. Else, we keep running and store X at memory address $0201
. The program exits.
Reading the explanation, it is the Compare instructions (CMP
, CPX
, CPY
) that set or clear the Zero flag in the Processor’s status register. This is why BEQ is documented as Branch on EQual zero set, and BNE as Branch Not Equal zero clear. Branch on Carry Clear and Branch on Carry Set incorporate how they react to this flag within their names. It checks for the Z
flag to determine whether to branch. In pseudocode, I see it as:
BEQ: if (Z) { branch(); }
BNE: if (!Z) { branch(); }
BCC: if (!C) { branch(); }
BCS: if (C) { branch(); }
The tutorial then offers you two challenges:
Exercise 1: Try writing a program that uses Branch on Equal
I got to work and started experimenting. The logic is clear enough to me, but drawing single pixels to the screen isn’t. I decided to draw a pixel heart with STA
commands so we have a nice visual to see what’s happening.
I add the following loop logic to redraw the heart in cycled colors.
INX
CPX #$ff
BEQ skip
BNE loop
skip:
BRK
We keep incrementing X
to change colors and redraw the heart to reflect the change. Once X
hits $ff
, we break out of the loop.
With the simulator running quickly, I want to count to $ff
before changing colors so that there is a bit of delay. I use X
as the timer multiplier that determines how many times Y
should wrap back to $00
before continuing:
LDX #$05 ; Set timer multiplier to 5 times
draw:
{Draw heart}
wait:
INY
CPY #$00
BNE wait ; Go back to wait: until Y has wrapped around
DEX
CPX #$00
BNE wait ; Go back to wait: until X has become zero.
{Change drawing color}
JMP draw ; Back to drawing.
This lets me have all the control I want: I can change the colors each loop, and by adding more logic I can even decide how many loops each color should last for. Adding everything I know so far together, I created a pulsating heart animation.
I included the entire code because it fit. ^-^ I was quite happy per usual. In this GIF, we can see how the Y
register keeps counting up, triggering the X
register to count down to zero where it is then reset to a multiplier value of my choice. One that’s shorter for the dark color, and another that’s longer for the light color, mimicking the rhythm of a heartbeat. As a fancy experiment, I store both the colors and their multipliers in memory at $0000
and $0001
. The code is available here.
This code includes the BEQ
instruction, so I consider the exercise done!
Exercise 2: Write a program that uses Branch on Carry Clear or Set
Ignoring the mathematical purpose the Carry flag may actually be for, I just focused on what I know it does in effect: set once the Accumulator overflows.
The Accumulator will be the timer counter this time, counting up to $ff
letting Y
increase every time Carry
is set. X
will be the drawing color. The looping logic is similar to before.
We define a palette of five fading colors in memory addresses $0001
to $0005
so that we only need to define their values once. Y increments, and we handpick each of the values where Y triggers a branch to a different part of the program. These parts each load different values from memory to set the drawing color X
. Once we have cycled through all colors we set Y
back to the starting value we stored at address $0010
. This is the result:
I think it achieved the effect I had in mind, and it’s thanks to branching that I can now create something like this at all. I look forward to learn much more effective ways to achieve this same logic.
I challenge you to beat my WRs on Sporcle!
Now something I have been distracting myself with that still relates to this journey is Sporcle. In short it’s a website where you can play memory quizzes and create ones yourself as well. I decided to make my own 6502 themed quizzes:
Can you name the 6502 Instruction Set? https://www.sporcle.com/games/Cyanne/6502-instructions-easy
My current best time: all 56 in 00:41.Can you name the 6502 Instruction Set (Blind)? https://www.sporcle.com/games/Cyanne/6502-instructions-blind
My current best time: all 56 in 01:35.
Can you name the 6502 Opcode Table? (tough!) https://www.sporcle.com/games/Cyanne/6502-opcode-table
My current best: 71 instructions or 47%.
I’m going to have much more fun with this in my downtime, so maybe you’ll find these quizzes or completely different ones fun as well.
See you next time!
One thing about those Sporcle quizzes: I tried the first one and got a particular time, I figured I could do better so I tried again and got a worse time even though I felt I'd gotten better, then I tried again and got an even *worse* time even though I'd tried even harder.
Turns out, the timer counts *down*, not *up*. You can switch the timer from "Default timer" mode to "Stopwatch" mode, but you have to do that every time before you start the game. I got it down to 1:01, still not as fast as you, but less embarrassing than getting "worse" every time. :P