Some Assembly Required: Incremental Progress
By: Eric Shepherd
In this installment, we’re going to continue to learn more about the
three primary registers in the 6502 processor (accumulator, X, and Y).
We’ll also create our first simple subroutine.
You can download the source code
for this
program.
*Comments*
Before we do anything else, let’s take a moment to talk about comments.
Comments are text in your source code that are human-readable, intended
to help you remember what your code is doing (and so others can read
your code more easily).
There are two formats for comments. Comments starting with a semicolon
(”;”) can be placed stand-alone on a line of code, or can be at the end
of any line of code. Comments starting with an asterisk (”*”) can only
be placed on a line by themselves. In both cases, the comment extends
from the character that starts the comment to the end of the line.
We’ve been using these already, but it just occurred to me that I
haven’t actually talked about them yet, so there you go.
*Our First Subroutine*
Let’s start by creating our first subroutine. This simple routine just
takes the value in the X register and prints it to the screen, followed
by a carriage return. We’ve used the |PRBYTE| and |CROUT| firmware
routines in the past, so we won’t dwell on them.
Let’s look at the code:
*
* xcr
*
* Prints the byte in X then a carriage return.
*
xcr start
txa ;Copy X to A
jsr PRBYTE ;Print the byte in A
jsr CROUT ;Print CR
rts ;Return to caller
end
The first thing this code does is use the implied mode instruction TXA
(Transfer X register to Accumulator) to copy the value in the X register
into (believe it or not) the accumulator. We do this because the PRBYTE
firmware routine prints the value in the accumulator, but we want to use
the X register in this case.
Then we call CROUT to output a carriage return, and use RTS to return to
the caller.
*Incrementing and Decrementing*
A very common operation is the need to add or subtract one from a
number. For this reason, the 6502 has special instructions just for that
purpose. This time, we’re going to look specifically at incrementing and
decrementing the index registers.
The INX (INcrement X register) and INY (INcrement Y register)
instructions add one to the value of the respective registers.
Similarly, DEX (DEcrement X register) and DEY (DEcrement Y register)
subtract one from these registers.
Let’s take a look at a code sample:
* Demo increment and decrement
ldx #$00 ;Start with zero
jsr xcr ;Print it
inx ;Increment X
jsr xcr ;Print it
dex ;Decrement X
jsr xcr ;Print it
dex ;Oooh, now what?
jsr xcr ;Print that
rts ;Return to caller
We start by setting the X register to zero and calling our |xcr| routine
to print that value to the screen.
Then we increment X and print the resulting value (in this case, $01).
Then we decrement X and print the result again (this time, $00).
Now, let’s see what happens when you decrement the X register again. The
result is $FF (255). That’s right, they wrap around. if you then
incremented X again, the result would wrap back to $00.
This is called *carry*, and it’s similar to what you do when you’re
doing addition and subtraction by hand. You have to carry the result
from one column to the next when adding or subtracting by hand;
similarly, when doing math byte by byte, you have to carry the result
from one byte to the next.
We’ll be looking in more detail as to how the carry bit works and how to
perform multi-byte mathematics later.
*Next Time*
Next time, we’re going to apply this knowledge about incrementing and
decrementing values to build our first loop to repeat an operation
several times until the desired result is achieved.
Some Assembly Required: Introducing the Registers
By: Eric Shepherd
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
end
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.
Some Assembly Required: Hello World
By: Eric Shepherd
Among programmers, there’s a long-standing tradition whereby one’s first
program in any given language is one that simply displays the message
“Hello world.”
In this installment of Some Assembly Required, we’re going to create
“Hello world.” I’m not going to really explain how it works this time
around. We’ll get into understanding the programming next time. The
goal, for now, is to simply learn the commands needed to build and run
your programs, so we won’t have to deal with that later.
ORCA/M 4.1 (8-bit)
Let’s start by building our program for ORCA/M for 8-bit Apple IIs.
Start by launching ORCA.SYSTEM, which will drop you into the ORCA
command shell. Type “NEW” to start editing a new file, and press the
Return key.
Once you’re in the editor, type in the following listing. Note that the
editor is slightly unusual if you’re used to other editors; you’ll want
to read through Chapter 3 of the ORCA/M manual to learn its commands.
If your computer is an older Apple II without support for typing
lower-case letters, that’s fine, just use all upper-case instead.
COUT gequ $FDED ;The Apple II character output func.
keep HelloWorld
main start
ldx #0 ;Offset to the first character
loop lda msg,x ;Get the next character
cmp #0 ;End of the string?
beq done ;->Yes!
jsr COUT ;Print it out
inx ;Move on to the next character
jmp loop ;And continue printing
done rts ;All finished!
msg dc c'Hello world.'
dc h'0D'
dc h'00'
end
Once you’ve finished entering the code, press control-Q to bring up the
quit and save menu, then press “N” to save the file to a new name. Type
“HelloWorld.asm” and press return, then press “E” to exit the editor.
Running the program is as simple as typing “RUN HelloWorld.asm”. This
will compile, link, and run the program. If any errors occur, type “EDIT
HelloWorld.asm” to open the file back up in the editor and find and fix
any discrepancies between what you typed and the listing above, then try
again.
ORCA/M (16-bit)
To build the program using the 16-bit version of ORCA/M (but still run
it as an 8-bit program), start up the ORCA shell by launching ORCA.SYS16
from the Finder. At the command line, first type “ASM65816″ to select
the assembly code edit mode, then type “EDIT HelloWorld.asm” to start up
the editor.
Enter the following code:
65816 off
65C02 off ;Use 6502 opcodes only
COUT gequ $FDED ;The Apple II character output func.
keep HelloWorld
main start
ldx #0 ;Offset to the first character
loop lda msg,x ;Get the next character
cmp #0 ;End of the string?
beq done ;->Yes!
jsr COUT ;Print it out
inx ;Move on to the next character
jmp loop ;And continue printing
done rts ;All finished!
msb on
msg dc c'Hello world.'
dc h'8D'
dc h'00'
end
Once you’ve entered the code, press Command-S to save your work, then
Command-Q to quit to the command line prompt. Type “CMPL HelloWorld.asm”
to compile and link the program. If any errors occur, type “EDIT
HelloWorld.asm” to edit the program and fix the problems, then try again.
Since this program is designed as an 8-bit program, to be run under
ProDOS 8, but we’ve built it using the 16-bit version or ORCA, we need
to do an extra step. We need to convert it from a GS/OS load file into a
ProDOS 8 compatible binary file. To do this, type “MAKEBIN HelloWorld
Hello”. This will take the compiled “HelloWorld” program and convert it
into an 8-bit binary file named “Hello”.
Then you can quit the ORCA shell, start up Applesoft BASIC, use the
PREFIX command to make your way into the directory containing the
program, and type “BRUN HELLO” to run it.
Merlin
These instructions are for Merlin 8/16+. Your mileage may vary somewhat
on other versions of Merlin.
Once you’ve launched Merlin, press “F” to open the full-screen editor,
then enter the following code.
org $2000
xc
xc
COUT equ $FDED ;Apple II character out func.
ldx #0 ;Offset to first character
:loop lda msg,x ;Get the next character
cmp #0 ;End of the string?
beq :done ;->Yes!
jsr COUT ;Print it out
inx ;Move on to the next character
jmp :loop ;And continue printing
:done rts ;All finished!
msg asc "Hello world."
dfb $8D
dfb $00
sav Hello
Once you’ve entered the code, press Command-6 to save (Type “Hello” for
the filename when prompted) and assemble and link your code. You should
wind up with a binary file named “Hello” in the same directory as the
source code file you just saved (which will be named “Hello.S”).
You can then switch to Applesoft BASIC, use the PREFIX command to make
your way into the directory containing the program, and type “BRUN
HELLO” to run it, and type “BRUN HELLO” to run the program.
That’s the gist of how to build a simple program in these three
environments. Don’t worry about how this stuff works for now. We’ll
start learning actual assembly language next time.
Some Assembly Required: Choosing an Assembler
By: Eric Shepherd
Over the coming weeks (or maybe months or even years), I’ll be posting a
series of articles introducing you to the glorious, glamorous world of
assembly language programming for the 6502 series of microprocessors.
While, sure, there are plenty of other languages out there, and in this
day and age, assembly is something of a line of last resort among
“modern” computer programmers, on the Apple II, assembly remains the
optimal way to build software for the best possible performance.
However, assembly language can be a little scary to newcomers, with a
lot of seemingly cryptic stuff to look at and somewhat daunting code
listings.
My intention with this series of articles is to introduce assembly
language in a gentle, easy to manage way, taking very small steps. This
will have two advantages:
1. It should make it easier for you to follow and learn.
2. It will reduce the size and complexity of articles, thereby
hopefully letting me post more often. This will make Sean very happy.
The first thing you need to do is to select an assembler. There are a
great many of them out there. The two best-known, and most
highly-regarded, are arguably Merlin (which comes in both 8 and 16 bit
versions) and ORCA/M (which also comes in both 8 and 16 bit versions).
Both of these options are good ones, and I can easily recommend them
both. I do have some comments on them that you might want to consider
while trying to decide between the two.
Merlin is easily the faster of the two assemblers. It also has a less
steep learning curve, as its user interface is quite a lot simpler. That
said, I find its editor to be rather quirky, with non-standard key
equivalents for many things. In addition, source code files saved by
Merlin have the high bits set on all the text, which means it’s not
trivial to load the files in other editors, or to open them on other
computers.
ORCA/M uses a Unix-like command line interface for everything. It has a
significantly more powerful macro system, but that does have the
drawback that the macros are rather harder to figure out how to create
and use. Once you master it, however, ORCA/M’s power is formidable.
The Apple IIgs version of ORCA/M works within the ORCA shell
environment, which lets you use multiple languages in one project. In
ORCA, you can build your main code in, for example, C, then write key
tidbits of your code in assembly to speed things up. This isn’t
something you can do in Merlin.
All that said, choosing an assembler is a surprisingly personal
decision. I like a great many things about both Merlin and ORCA, and use
them both.
The primary drawback to Merlin is that its legal status is somewhat
uncertain. The wife of its author, Glen Bredon, released it as freeware
upon his death a few years back. However, it’s unclear whether or not
she actually had the legal right to do so, since Roger Wagner Publishing
owned the copyright to the software, and RWP has since been absorbed
through multiple companies until ownership of the copyright is somewhat
hard to determine.
If you can find a used copy of Merlin, you might find that you like it.
Both the 16-bit
and 8-bit
versions of ORCA/M, on the other hand, are still available for purchase
(please note that I own the company that sells it; it’s only fair for me
to mention that).
Because ORCA is still available in a clearly legal fashion, the code
examples I’ll be providing will be using its syntax most of the time;
however, I will strive to point out the differences as we go.
Next time, we’ll take a look at how to go about actually building code
in these assemblers, so you’ll be ready to go when we start learning how
to write assembly code.