Last time, we looked at how to increment and decrement the values in registers. The code sample wasn’t very interesting, but learning how to use INC
and DEC
is a key step in learning how to build code that repeats tasks.
A loop is a code sequence that executes over and over again until some condition is met. A very common use case is to repeat some specific number of times. If you’re familiar with BASIC, this is equivalent to the notion of the FOR...NEXT
loop.
Our Code This Time
In this installment, we’re going to create our first subroutine, which will use a loop to output a string of characters to the screen. This is actually very similar code to the string printing code in the example in the first Some Assembly Required article a few weeks ago. We’ll likely be using this exact routine a lot in the future — a simple “print a string” routine. Let’s have a look at the code.
[download id=”6″ format=”2″]
* * print * * Prints the null-terminated string * pointed to by the X and Y registers. * print start string equ $00 ;String pointer address sty string ;Save the string stx string+1 ldy #$00 ;Start at the beginning loop lda (string),y ;Get a character beq done ;->If it's zero, done jsr COUT ;Print it iny ;Next character jmp loop ;And keep going done rts ;Return to caller end
Our print routine starts by using the EQU (EQUate) directive to give a name to the memory location at the address $00 in memory. This location is in the zero page, the very first page of memory. This page has special properties that make it particularly useful, since locations in the zero page can be referenced using a single byte (since the first 256 addresses in memory range from $00 to $FF). There are even special addressing modes just for working with the zero page, and instructions using these addressing modes are faster than when addressing other parts of memory. We’ll talk about that in more detail next time, though.
The first thing this routine does is take the value in the Y register and store it in the memory location pointed to by string, and then the X register into the next location in memory (string+1 equals $00+1 equals $01).
What we’ve done, then, is take the two-byte memory address pointed to by the bytes in the X and Y registers, and store them as a two-byte value in the memory locations at $00 and $01.
Numbers on the 6502
Let’s step back for a moment and look at how numbers are represented on the Apple II. A single byte can have the value $00 to $FF (that is, 0 to 255). To represent higher numbers, you need to use more bytes. By adding a second byte, you can represent values from $00 to $FFFF (0 to 65,535). For the foreseeable future, that will suffice for our needs.
It’s extremely important to keep in mind that the 6502 (like Intel’s processors, by the way) is a little-endian architecture. This means it uses low-byte first byte ordering. That means that if you need to represent a 16-bit (two byte) value, you put the lowest-order byte first, then the highest-order byte.
So, for example, the number $1234 would be represented in memory by $34 then $12.
This takes a little getting used to, but has key advantages on an 8-bit platform, since typically we need to manipulate the bytes in order from lowest to highest anyway when performing calculations. We’ll be looking at how to do math in the near(ish) future.
Back to Our Code
That said, now we can see that we’ve taken the address of the string we want to print and stored its low byte at the address $00 and its high byte at byte $01 in memory. This is the correct order for storing a 16-bit value (such as an address) on the 6502.
The next thing we do is initialize the Y register to have the numeric value 0, using the LDY
(LoaD Y register) instruction. We’ll be using the Y register as a counter to keep track of how far into the string we’ve progressed while printing it out.
The next line is the first line of our printing loop. As I mentioned earlier, a loop is a set of instructions that runs over and over again until a condition is met. Let’s see how it works.
The first line of the loop starts with the text “loop”. Text that starts at the left-hand edge of the source code is called a label. Labels represent a location in memory. The actual value of the label is computed by the assembler when you assemble your program. This lets you refer to memory locations without having to know where they are.
The first thing the loop does is use a new addressing mode we haven’t looked at before. This is the indirect indexed addressing mode. When you see an address in parentheses in 6502 assembly language, we’re performing an indirect access to memory. That is, we take the address specified by the operand (in this case, the address referenced by string
, which is $00), and then look at the address that’s stored in that memory. In this case, that’s the address that was specified by the X and Y registers when the print routine was run — which we previously stored in the two bytes at $00 and $01.
The indexed part of this addressing mode means we then take the retrieved address and add the value of the Y register to it to get the actual address to use for our operation.
For Example
Let’s say that when this routine was called, the value in string
was $1234, meaning that the string we’re going to print is in memory starting at the address $1234.
That means we would store the number $34 in address $00 and $12 at $01.
Then, Y is set to zero.
The next thing we do is get the first character of the string to print. We look at the two bytes located at string
and string+1
to determine the address of the string, so LDA
knows to start looking at $1234. Then it adds the value of the Y register to that, getting $1234 for the address of the first character. It then loads the byte from that location into A.
Finishing the Loop
Once the character is in the accumulator, we need to see if we need to actually print it, or if the string is done being printed. We’re using null terminated strings; these are strings that end with a null character (that is, a character with the ASCII value 0). So we need to look to see if the character we just loaded is zero.
This is easy to do using the BEQ
(Branch if EQual) instruction.
Whenever the value of a register changes, special status flags in the processor are changed. We’ll be looking at each of them over time as we proceed through this series of articles, but for now, let’s consider the zero flag. This flag’s value is 1 if the result of the last operation was zero, or if the result of a comparison operation indicated that two values were equal.
So BEQ
branches (that is, starts executing code at a different location in memory) if the zero flag is 1. Which it is, if the LDA
instruction loaded a zero into the accumulator.
So simply using BEQ
to exit our loop if the end of the string was reached works nicely for us here; we simply branch if equal to the line of code with the label done
. If the accumulator isn’t zero, the code continues to run on the next line.
The next line calls the ROM routine COUT
, located at $FDED
, which prints the character whose ASCII value is in the accumulator.
Now comes the fun part. We increment the value in the Y register so that the next time we run through our loop, we get the next character. This is done using the INY
instruction; this implied mode instruction takes no operands at all. It simply adds one to the value of the Y register. We looked at this last time.
Finally, we use the JMP
instruction to jump back to the start of the loop to continue printing the string.
Next Time
Next time, we’re going to learn more about the magic of the zero page, and the layout of the Apple II’s memory in general, which we only briefly touched on this time.