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.