Machine Language Made Easy ! (Part 1)
by Lyle Giese
DELPHI Mail: LYLEG
This is the first in a series of columns to help ease the newcomer into the world of Machine Language. I am still not sure how I got into this job, but I will give it my best!
It is quite hard to get started in Machine Language programming, but just like the BASIC in your first computer, once you know a little about it is not that difficult. This column will be based on the 6502 & 6510 microprocessors that are used in the Commodore 64 & Vic-20, Atari 400-600-800 series machines, and in some Apples. The 8500 processor in the C-128 is a direct cousin of the 6502. All instructions for the 6502 will do the same things on the 8500. The 8500 can do more, but that will not be covered here.
In starting out with anything that is foreign, it takes time to cover all the terminology needed to understand the simplest instruction. So, this month's column will be covering just that, terminology.
I got my 'training' in Machine Language by first reading Richard Mansfield's book "Machine Language for Beginners". It is, in my opinion, an excellent book for the novice. I cannot, however, comment on his second book as I have not read it.
A book that is on the MUST have list for the C-64 owner is the 'Programmers Reference Guide' (PRG). It has valuable information on how to use the Kernal routines inside the C-64. Later, I hope to cover how to read the Kernal routine charts in the PRG.
Memory maps of the computer you are using are invaluable! Two good ones for the C-64 are "The Anatomy of the Commodore 64", from Abacus Software, and "Mapping the C-64" by Sheldon Leeman, from Compute! Publications. "The Anatomy of the Commodore 64" has very good disassembles of all the ROM routines in the 64. Sheldon's book is a good reference for the low memory usage in the 64 and for some comments on BASIC and the KERNAL.
You will also need a simple assembler and a simple disassembler to use. There are good Public Domain ones here in the database of the Flagship. Later, if you want to get more involved in Machine Language, you will want a more complex assembler. But again, that is a topic for future column.
MEMORY. That is the stuff we want more and more of! But what is memory? Memory, in its simplest form, is called a "bit." Common belief has it that the term bit is short for binary digit, and got its name from that. A binary digit has only two states, a zero or a one. Not much power or information can be stored in one of two digits! By chaining the bits in a logical manner, we can use them to store more information.
The next building block we use is a byte. A byte is 8 bits of memory used together. With 8 bits, we have 256 different combinations of one's and zero's to work with.
Richard Mansfield used an excellent analogy to explain how memory is configured. Imagine the 8 bits as 8 lights in a house. The lights will be on or off as the bits are turned on or off. Now, line up number of houses in a row down a long street. The first house will have an address of zero. The second one an address of 1 and so on, until we get to the end of our street (memory). On the 64 the end will be at 64K. That is not 64,000. A K of memory is 1024. That number is used partly because it is a factor of 2 (2 to the power of 10). This means the last house in memory will have an address of 65,535 (remember that we started with an address of zero). Remember, I said that a byte can hold 256 different bit combinations? Since that is what a byte can handle, memory is again divided into 256 byte chunks, or blocks on our street, called pages.
Zero page, besides being the first page, is special. Because the page is zero, the microprocessor is set up to be able to access zero page faster. The reason is that in order to access memory we have to use the address of the byte of memory. The address is divided into two bytes called the "least significant byte" (LSB) and the "most significant byte" (MSB). When talking about the bytes in zero page, the MSB will be zero. So a special set of instructions were set up inside the 6502 to work with zero page. These instructions work faster because the processor fetches one less byte in order to find the address. That fact was not lost on Commodore when they designed their computers. They made heavy use of zero page.
Another thing that you will find is the heavy use of Hexadecimal notation. This is a numbering system based on 16 digits instead of 10 used in decimal that we are used to. Hexadecimal, or hex for short, uses 0-9, and A-F for its digits, with A=10, B=11, etc. Hex is normally prefixed by a "$" as $10, which is 16 in decimal. It is sometimes written as 10h, but that is seldom seen anymore.
Hex is convenient for use with byte sized memory as 2 digits are all that are needed to show the maximum value that a byte can hold (ranging from $00 to $FF(255)). Four digits are all that are needed to designate memory locations in a 64k machine. That is two bytes can range from $0000 to $FFFF ( or 65,535). I strongly suggest that you become familiar with hex as it is used often in articles or books written about ML.
The hex designation for memory locations can be split very easily. In the example above, $FFFF, the LSB will hold $FF and the MSB will hold $FF. Note: the MSB also refers to the page number when talking about memory locations.
At this point, you have to know something about what is inside your microprocessor. There is a little Random Access Memory (RAM) and the instructions, contained in Read Only Memory (ROM)-like memory.
The RAM memory is divided into 'registers': the A register, commonly called the Accumulator; X register; Y register] Stack Pointer (SP); Program counter (PC); and the Status Register (SR), also called the P register. Each register has its own special properties and/or functions that enable it to do certain tasks that other registers cannot do.
I will be using the term 6502 even though everything applies equally to the 6510 and 8500 unless otherwise noted.
The A reg is where all the math functions of the 6502 are done. In order to add, subtract, or do logical functions, one number has to be in the accumulator.
The X and Y registers are use for indexing, but they do have some different properties. The differences re subtle and I will cover them in a later column.
The PC is used to keep track of where we are in the program so that the 6502 will know where to get its next instruction.
The SP is a pointer to the Stack in memory. This is used to store the return address when you JSR (Jump with Return - similar to GOSUB in BASIC). The stack may also be used for temporary storage, but this application should be left to advanced programmers.
The P reg, or Status Reg, is used to keep track of certain conditions that happened in the last operation. Did the last operation result in a zero result? Did the last addition cause a carry? Did the last subtraction use a borrow? In the P reg, 7 flags that are used for this purpose. I will cover them in a later column.
With an article on anything new, it is very helpful to show a working example. ML is no exception to that rule. In this vain I thought we would do something simple, change the color memory on the C-64. In case some of you have forgotten in the Version2 Kernal C-64 a clear screen caused the 64 to set the background color memory to the current foreground color. So if then you wanted to poke to the screen, you couldn't see it as the character was the same color as the foreground.
We would need to fill color memory with something other than the foreground color. On the 64 the default foreground color is blue, which has a color number of 6. We need to change it to some other number. We could do that in BASIC with a for-next loop but that takes much too long. In ML it is a short and simple operation.
With this example, I will use the convections used with Supermon. That program is in the PD and was included on the disk bonus pack inside the 1541. So we will start this project by loading and running Supermon (most other simple monitors use very simular syntax).
The first thing you will see is a status line. That shows you the contents of all the 6502 registers, starting with the Program counter(PC), IRQ vector(this are the routines that service the keyboard and TOD clocks and other things and occurs approx every 60th of a second), the Status Reg(SR also called the P reg), the A reg(or Accumulator, the X reg, the Y reg and the Stack pointer. Then a period on the next line. The period replaces the cursor.
Here's a printout of our program and I will explain.
.A C000 LDY #$D8
.A C002 STY $FF
.A C004 LDY #$00
.A C004 STY $FE
.A C006 STY $FE
.A C008 LDA #$00
.A C00A STA ($FE),Y
.A C00C INY
.A C00D BNE $C00A
.A C00F INC $FF
.A C011 LDA $FF
.A C013 CMP #$DC
.A C015 BNE $C008
.A C017 RTS
.A C018
After the period, we type an A. This tells the program to start assembling want we type in. Next we type in the starting address in HEX. In this example we use $C000, which is 49152 decimal. This a free zone that BASIC does use and is quite frequently used for ML subroutines and programs, like CBM's Wedge program.
Next (we are still on the first line) we type in the mnemonic for the instruction we want executed. In this case the Op code is LDY which means LoaD the Y reg. The #$D8 is called the argument. The '#' use the number that follows(as opposed to treating it as an address). So it is telling us to LoaD the Y reg with the number $D8. This method of addressing is called immediate. Now we press the <CR> and you have put your first instruction into memory!
After the <CR> the computer will respond with '.A C002'. After you give the assembler the start address it will figure out where the next instruction goes for you. If this was the end of the program we would just answer by typing a <CR>.
The second op code is Store The Y reg. Now all the store and load instructions actually copy the information from one place to another without destroying the original. So in this case (without the '#' means that we treat the argument as an address) we will store (or copy) the contents of the Y reg into memory location $FE. By the way this addressing mode is called zero page absolute, absolute because we are specifying the exact address to use and zero page because the address is in zero page.
The next two instructions are the same as above except we will put $00 into memory location $FE. In case you have not noticed we have put address $D800 into $FE & $FF. The address $D800 is the start of color memory at 55296 decimal.
But HEY Wait A Minute!!! Didn't you just put the address in backwards?? Yes we did but that is the way the 6502 expects to find all of its address. LSB first then the MSB. I don't know why but that is the way it is in the 6502, addresses are stored in memory backwards.
The next instruction tells us to LoaD the A reg with something. The argument tells what. In this case the '#' again tells us to use this number and not treat it as an address. By the way this is called the immediate addressing mode. So we put the number $01 into the A reg, which is the color number for white. By changing this number we could fill color memory with any of the 16(0-15) colors.
Now we want to put the color number into the color memory. This next instruction does that for us. We are going to STore the A reg, but where? Well, the parenthesis around the $FE tells us that $FE is not the target address but that we will find the target contained in memory locations $FE & $FF(which we set to $D800 earlier).
Now what do we do with the ',Y"? We take the address found at $FE & $FF($D800) and add the Y reg to it. The first time through this routine the Y reg contains $00. So we will STore the A reg at $D800+0 or at $D800. This is a very useful addressing mode called indirect Y, you will use it often!
In the next instruction, we INcrement the Y reg or add 1 to it. This addressing mode is called implied. I guess it got its name because the argument does not need to be specified or is implied.
The next instruction is an important one. It stands for Branch if Not Equal. Not equal to what? Well the instruction checks the Zero flag in the Status reg. When the previous instruction (in this case INY) results in a zero result or equals the zero flag is set in the Status reg. If we just keep increment the Y reg, how will that ever be equal to zero?
Good question! Earlier in this column I stated that a byte could only hold a number from $00-$FF(255). The registers in the 6502 are only 1 byte long, except the PC which is 2 bytes long. So what happens when the Y reg holds $FF(255) and we increment or add one to it? It can't hold a bigger number, so it rolls over to $00, but when that happens it sets the zero flag in the Status reg.
Back to the instruction Branch if Not Equal to zero. If the Y reg is not zero then we go back to $C00A. Now the Y reg holds a $01 and we will add the address found at $FE-$FF to the contents of the Y reg and get $D800+1 or $D801. So the contents of the A reg is now copied into $D801 or the second byte in color memory.
This of course continues until the Y reg rolls over to zero. Then you fall through to $C00F. The instruction there is INCrement (no '#') the number in memory location $FF. Then we will LoaD the A reg with that value. The first time we hit this we will be going from $D8 to $D9.
Now that we have that value in the A reg we can CoMPare it to something, (Note the '#' which means that we compare to the number $DC not memory location $DC). Why $DC? That's above our color memory. When we are at $DB, we still have some of our color memory there and need to fill it up with our color number.
The next instruction is the Branch if Not Equal, which again checks the zero flag. This time the zero flag was set or not set according to the outcome of the CoMPare instruction at $C013. During that compare if the A reg contained the number $DC then the zero flag was set, telling that they were equal.
And the last instruction is ReTurn for Subroutine. This is just like the return found in BASIC. What do we do know? The computer asked me to assemble an instruction at $C018. We tell the assembler in Supermon that we are done by a <CR> alone.
Now how do we run the program? It is in memory that is not directly used by BASIC and besides it is not a BASIC program! Well, there are two ways to do that at this point. The first one is after the period to type 'G'(for Go) and C000<CR>. That tells Supermon to go to $C000(which is where we put our program) and try to execute our instructions there (I emphasize try because sometimes typing errors do happen!).
The other way is to exit to BASIC by type 'X' at the period, which eXits the monitor and returns us to BASIC. Then 'SYS49152'. This tells BASIC to try to execute the ML program we put at $C000.
And if all went well, all of our letters on the screen will turn white and the prompt 'READY' will appear (or a period if we were still in Supermon).
Well, that's alot to digest and I hope I didn't lose anyone, but if you did get lost along send me a note and I will try to sort it out for you!