Unlike most modern processors, the 6502 series of processors has only three primary registers that you can use for manipulating data. A register is, basically, a slot within the processor that can contain a numeric value. On the 6502 and 65C02, these are each able to hold an 8-bit value. On the 65816, they can hold either 8-bit or 16-bit values, depending on whether or not the processor is switched to use 16-bit values for the register in question.

The accumulator — also known as the “A” register — is used for performing computations. Addition, subtraction, binary operations, and so forth are all performed on the accumulator.

The other two registers, X and Y, are called index registers. These registers are primarily used to index into a block of data while performing operations.

Addressing Modes: An Introduction

When you think about it, there are several ways to reference information. You can load or store a value directly into a specific memory address. You can set a register’s value to a specific number. You can use the address stored in a memory location as the address from which to load a value. These, among others, are called addressing modes. We’ll be introducing these gradually as we proceed through this series of articles.

Immediate Addressing

The first, and simplest, addressing mode is immediate mode. Immediate addressing is when you load a specific numeric value into a register. For example, here’s how you can set the value of the accumulator to zero:

            lda      #0

This is pretty straightforward. The LDA instruction (“LoaD Accumulator”) is used to load a value into the accumulator. When you want to represent an immediate numeric constant value in assembly language, you put a pound sign (“#”) in front of it.

Similarly, you can load immediate values into the X and Y registers like this:

            ldx      #$55
            ldy      #32

The LDX (“LoaD X register”) and LDY (“LoaD Y register”) instructions work exactly like LDA. Note, however, the presence of the dollar sign (“$”) in the LDX instruction above. That indicates that the value is hexadecimal.

Absolute Addressing

Absolute addressing is the mode by which you load and store values to a specific address in memory. For example:

            lda      #$ED
            sta      $0300

This code snippet loads the hexadecimal number $ED into the accumulator, then stores it in memory at the address $0300 using the STA instruction (“STore Accumulator”). You can similarly use STX (“STore X register”) and STY (“STore Y register”) to save the values of the X and Y registers into specific memory locations.

You’ll note that these instruction names get recycled. This is the first key lesson of assembly language programming. The 6502 has only about 50 different instructions, but most of them are available in multiple addressing modes. This lets each instruction take on a variety of uses; we’ll learn more about these over time.

Calling Subroutines

Before we get to our sample program for the week, let’s quickly look at how you call subroutines in assembly language. A subroutine is a (usually short) subprogram that handles specific tasks.

To call a subroutine, you use the JSR (Jump to SubRoutine) instruction. For now, we’ll only look at how to use its absolute addressing mode version.

Subroutines return to the calling code using the RTS (ReTurn from Subroutine) instruction. This instruction accepts no arguments. This is, for future reference, the implied addressing mode.

Let’s put together a little sample that uses what we’ve learned this week (note that this is using ORCA/M 8-bit syntax):

         keep  Registers                ;Name to compile to

PRBYTE   gequ  $FDDA                    ;Prints a hex byte
CROUT    gequ  $FD8E                    ;Prints a CR

         org   $2000

main     start
         lda   #$AB                     ;Set A to the number $AB
         jsr   PRBYTE                   ;Print A
         jsr   CROUT                    ;And a carriage return
         rts                            ;Exit program

This is actually a simpler program than last week’s, but we’re going to look at it in more detail.

The KEEP directive on the first line is a bit of ORCA detail; it tells the assembler to save the assembled binary code in a file named “Registers”. This is not a 6502 operation, but a directive — or special instruction — for the assembler itself.

The next two lines use the GEQU (Global EQUate) directive define the constants PRBYTE and CROUT to point to two memory locations; these constants let us refer to these locations by name instead of number, which makes our code easier to read. These locations, in the Apple II ROM, represent a routine that prints the value of the accumulator as a hexadecimal number and a routine that simply prints out a carriage return character, respectively.

The ORG (ORiGin) directive tells the assembler the address in memory at which the program should load. In this case, we’re specifying $2000 (which happens to be the location in memory at which the high resolution graphics screen starts, but since our program doesn’t use that memory, it’s a nice place to run our code).

Next comes the body of our program. In ORCA, blocks of code are surrounded by the START and END directives. These allow us some capabilities we’ll explore later; for now, it suffices to know that you need to include them in ORCA but not in Merlin.

The main program uses the LDA instruction to set the accumulator’s value to the immediate value $AB, then calls the PRBYTE routine to print it to the screen. The CROUT routine is called next, to send a carriage return to the screen — this moves the printing cursor to the beginning of the next line.

Finally, our program ends with an RTS instruction. Simple 8-bit programs are called using a JSR instruction, so we return to the calling program (such as the ORCA shell or the BASIC interpreter) with an RTS.

Note for Merlin users: The GEQU directive in ORCA doesn’t work in Merlin; instead, you should use simply EQU. Likewise, you don’t need the KEEP, START, and END directives in Merlin. Instead, to specify the filename with which to save the assembled file, add “SAV REGISTERS” to the end of the source code file.

Save this code and assemble it, following the same instructions as last time. In ORCA, once you’ve saved the file, you can assemble and run it using the command “RUN REGISTERS.ASM” (assuming you saved the source code with the name “REGISTERS.ASM”).

Final Notes

You’ll notice we’ve skipped over any details on how the JSR and RTS instructions actually function together. This is a whole interesting set of information that we’re going to ignore for now, but we’ll get to it in a couple of installments.

Next time, we’ll expand on what we learned in this installment by doing some basic manipulation of the contents of registers.