Amiga Machine Language (Chapter 1)
The following text is part of the Amiga Machine Language tutorial.
Chapter 1, Introduction
Before you tackle machine language, you should take a closer look at several things that are vital to machine language programming.
Why Machine Language
Machine language is actually the only language the MC68000 processor understands. All other languages, such as Basic, Pascal or C, must first be translated (interpreted or compiled) into machine code. This process can take place either when the program is executed (the BASIC interpreter), or before program execution (the Pascal and C compilers).
Advantages: The great advantage of machine language over an interpreted and compiled program is machine language programs are faster. With an interpreter like BASIC, each line must first be interpreted before it is executed, which requires a great deal of time. A Pascal or C compiler translates the source into machine language.This translation procedure does not produce programs that are as fast as pure machine language programs.
Another advantage machine language has over BASIC is that an interpreter is not needed for the execution of a machine language program. Machine language can access all the capabilities of the computor since it is the language native to the computor. It is possible that machine subroutines are required by a higher level language to access functions that aren't directly accessible by that language.
A Look Into The Amiga's Memory
Before a machine language program can be written,you must know exactly what the program is required to do.You must also be aware of what resources are needed and available to achieve those goals. The most important of these resources is the memory in the Amiga.
RAM, ROM, Hardware Register
Random Access Memory, referred to as RAM, allows information to be placed in it and then withdrawn at a later time. This memory consists of electronic componants that retain data only while the computor is turned on (or until power failure). So that the computor is able to do something when it is first turned on,such as promting the Workbench or Kickstart disk, a program has to remain in memory when the power is off. A memory type which can retain data in memory without any power being needed. This second memory type is known as ROM.
- ROM: ROM stands for Read Only Memory, indicating that data can only be read from this memory,not written to it. The Amiga contains a ROM,that loads the Workbench or Kickstart disk into RAM. The first version of the Amiga did not contain the Kickstart in ROM.
- PROM: One variation of ROM is the PROM, or Programmable Read Only Memory. This special type of ROM can actually be programmed once. Since it cannot be erased once programmed, it isn't encountered very often. More often you will see EPROM's. Or Erasable Programmable ROM's. These special chips, which can be erased with ultraviolet light, have a little window on the surface of the chip usually covered with tape.
- EEROM: Although not available on the consumer market and much more expensive than RAM, the EEROM (Electrically Erasable ROM) offers another alternative to programmable ROM. These chips function like RAM, except that information is not lost when the power is turned off.
- WOM: With the birth of the Amiga, another type of memory, WOM, was created. This particular type of memory is Write Once Memory. The Kickstart disk is read into this memory when the computor is first booted. After this, no more data can be read into that memory. Actually this isn't a completely new component, but simply RAM that is locked once data has been read into it, after which the data can only be read from that memory.
- Registers: In addition to RAM and these variations of ROM there is another type of memory situated between those two groups. This memory is connected to the processor through a group of peripheral controllers. Thus it is commonly refered to as the Hardware Register, since the computor's hardware is managed by this system. We'll go into greater detail on how to use these hardware registers later in this book.
Lets take a closer look at the structure and use of the memory most familiar to us, RAM.
Bits, Bytes, and Words.
- Kilobyte: The standard size in which memory is measured is a Kilobyte (Kbyte). One kilobyte consists of 1024 bytes, not 1000 as you might expect. This unusual system stems from the computor's binary mode of operation, where numbers are given as powers of 2, including kilobytes. To access a memory block of one kilobyte,the processor requires 10 connections which carry either one volt or zero volts. Thus 2^10=1024 combinations or 1024 bytes of memory, are possible.
- Byte: A byte,in turn,consists of yes/no,on/off information as well. A byte can be one of 2^8 different values, and thus it can represent any one of 256 numbers. The individual numerical values that make up a byte,which also are the smallest and most basic unit encountered in any computor, are called bits (short for binary coded digit). A 512 kbyte memory,such as the Amiga's, contains 2^19=524288 bytes and 4194304 bits. It may seem unimaginable, but a memory of that size has 2^4194300 different combinations.
- Word: Back to the basics... bits and bytes are sufficent to program an eight bit processor like the 6500, since it can only work with bytes. To program a 16/32 bit processor like the Amiga's MC68000, you'll need to know two new data forms: words, consisting of 16 bits (the equivalent of two bytes), and long words, which are 32 bits(the equivalent of four bytes, or 2 words). A word can be any number between 0 and 65536, a long word can 0 to 4294967295. The MC68000 processor can process these gigantic numbers with a single operation. Once in a while you need to use negative numbers as well as positive ones. Since a bit can only be 1 or 0 and not -1, an alternative system has been adopted. If a word is to have a specific sign,the highest value digit or 15th bit in the word(positions are always counted from zero) determines the sign of the word. With this method words can carry values from -32768 to +32768. One byte can range from -127 to +127. In a byte, the value -1 is given by $FF; in a word it's $FFFF,-2 is $FE(FFFE), etc.
Lets stick with positive values for the time being, to aid in the visualization of a bit in relation to its bit pattern. Machine language does not use the familiar decimal system. Instead, it commonly employs the binary as well as the octal and hexadecimal number systems.
Lets take a look at the decimal system: its base number is 10. This means that every digit represents a power of 10. This means that the 246 represents 2*10^2+4*10^1+6*10^0. The decimal system offers a selection of 10 characters, namely 0-9.
This procedure is different for the binary system. The binary system offers only two different characters:1 and 0. Thus the systems base number is two. The decimal value of 1010 would be (in decimal system):
1*2^3 + 0*2^2 + 1*2^1 + 0*2^0 = 2^3 + 2^1 = 8+2 = 10
Generally binary numbers are identified by having a percentage symbol as a prefix. See if you can determine the decimal value of this number: 110010...
Well did you get 50?. That's the right answer. The most simple method to arrive at this result is to simply add up the values of the digits contained at 1. The values of the first eight digits are as follows:
digit 8 7 6 5 4 3 2 1 value 128 64 32 16 8 4 2 1
The octal system whose base is eight,is similar. The character set consists of numbers 0 to 7. The decimal equivalent of the octal number 31 is: 3*8^1 + 1*8^0 = 25. However the octal system isn't nearly as important as the next one... The base number of the hexadecimal system is 16, and its character set ranges from 0 to F. Thus, A would be equivalent of a decimal 10 and F would be 15. The dollar sign($) indicates a hexadecimal number. The binary and hexadecimal systems are the most important numerical systems for assembly language programming.
The hexadecimal representation of a byte ranging from 0 to 256 always has two digits: $00 to $FF. A word ranges from $0000 to $FFFF and a longword from $00000000 to $FFFFFFFF. It's quite easy to convert binary numbers into hexadecimal: simply split up the binary numbers into groups of four digits. Each of these groups of four digits then corresponds to one hexadecimal digit. Here's an example:
binary number %110011101111 split up %1100 %1110 %1111 result $C $E $F thus: %110011101111=$CEF
The opposite operation is just as easy...
hexadecimal $E30D split up $E $3 $0 $D result %1110 %0011 %0000 %1101 thus: $E30D = %1110001100001101
This method can also be used to convert binary into octal and vice versa, except that groups of three digits are used in that case:
octal number 7531 split up 7 5 3 1 result %111 %100 %011 %001 thus: octal 7531=%111101011001
This binary number can the be converted into hexadecimal, as well:
binary number %111101011001 split up %1111 %0101 %1001 result $F $5 $9 thus: octal 7531=$F59
The following calculation can then be used to convert the number into the familiar decimal system:
hexadecimal $F59 split up $F $5 $9 result 15*16^2+5*16+9 thus: $F59=3929 decimal
Although this conversions are quite simple, they can get to be rather annoying. Many assemblers can ease this task somewhat: they allow you to enter a value with '?' upon which it returns the value in decimal and hexadecimal forms. There are even calculators that perform number base conversions.
Often this conversion as to be performed in a program, for instance when a number is entered by the user and then processed by the computor. In this case the number entered, being simply a combination of graphic symbols, is evaluated and the usually converted into a binary number, in effect, a word or a longword. This process is often required in reverse order, as well. If the computor is to display a calculated value in a specific number system, it must first convert that number into a series of characters. In a later chapter you will develop machine language routines to solve these problems. You can then use these routines in your own programs. First you still have to cover some things that are fundamental to machine language programming on the Amiga.
Inside the Amiga
In order to program machine language, it is not sufficent to know only the commands of the particular processor, one must also have extensive knowledge of the Amiga being programmed. Lets take a look inside the Amiga.
Components and Libraries
The Amiga is a very capable machine, due to the fact that there are components that do a large part of the workload, freeing up the 68000 processor. These are refered to as the "custom" chips, which perform various tasks independantly of the 68000 processor.
This task force is comprised of three chips, whose poetic names are Agnus, Denise, and Paula. The main task of Agnus, alias blitter, is the shifting of memory blocks, which is helpful for operations such as quick screen changes. Denise is responsible for transfering the computors thoughts on to the screen. Paula's tasks consist of input/output jobs, such as disk operation or sound.
These chips are accessed by the processor through several addresses starting at $DFF000, which are also known as the hardware registers (you'll find more detailed information about the registers in the corresponding chapter). To simplify the otherwise complicated procedure of utilizing these chips, several programs have been included in the Kickstart and Workbench libraries. These programs can be called by simple routines and then take over the respective chips.
If only these library functions are used to program the Amiga, the parameters are the same, regardless of the language used. Only the parameter notations differs from language to language. BASIC is an exception in this respect, since its interpreter translates the program calls, which is why you don't need to know how the Amiga executes these functions in order to use them. The library functions are written in machine language and are thus closely related with your own machine language programs. Actually you could do without the library programs and write all of the functions yourself. However the incredible workload of this task is so discouraging, that you'd rather stick with the library functions.
First lets look at the RAM of the Amiga 1000. The standard version of this computor has over 512 kbytes of RAM, ranging from the address $00000 to $7FFFF, or 0 to 524287. If the memory is expanded to one megabyte, the first address still starts at $00000, however the start of anything greater than 512k can go anywhere in the address space between $200000 to $9FFFFF. With the release of AmigaDOS 1.2, the Amiga figures out where to put the memory expansion by using a special`Autoconfig`scheme. This allows you to add memory and I/O without worrying about addresses and dip switches.
- Chip RAM: The chips that support the Amiga`s processor access RAM almost totally independantly and thus ease the workload of the processor. However there is a draw back:these chips can only access the first 512k bytes of RAM. Thus graphics and sound data handled by these chips MUST be stored in this memory range. Because of this, that memory range is referred to as `Chip RAM`.
- Fast RAM: The counterpart to chip RAM is the remaining RAM which, if the computor is equipped with it, begins at $200000. Since only the processor itself as access to this part of memory it is known has `Fast RAM`.
Here`s an overview of the Amiga`s memory:
$000000-$07FFFF chip RAM $080000-$1FFFFF reserved $200000-$9FFFFF potential fast RAM $A00000-$BEFFFF reserved $BFD000-$BFDF00 PIA B (even addresses) $BFE001-$BFEF00 PIA C (odd addresses) $C00000-$DFEFFF reserved for expansion $DFF000-$DFFFFF custom chip registers $E00000-$E7FFFF reserved $E80000-$EFFFFF expansion ports $F00000-$F7FFFF reserved $F80000-$FFFFFF system ROM
Since the Amiga is multi-tasking, when a program is loaded into memory, it is simply loaded into another memory location. The memory range thus occupied is added to a list of occupied memory and the memory range is then considered barred from other uses. If another program is loaded, which is quite possible with the Amiga, it is read into another memory location which is then marked on the occupied list. If the first program should require additional memory, to use a text buffer for example, that memory first has to be reserved. Otherwise another program could accidently be loaded into the memory needed for this task. What`s interesting about this procedure is that when the first loaded has ended, the memory occupied by it is freed for further use. As a result, RAM is then chopped up into occupied and free parts, which are no longer related to each other. The Amiga can still utilize these chunks of memory as if they were one continuous chunk. After all, parts is parts. An example of this is the dynamic RAM disk which is always available under the name RAM: This RAM disk is actually quite a phenomenon, since it is always completely filled. If a program is erased from RAM disk, the memory allocated to that program, regardless of its location or structure, is given back to the system. Thus,if you reserved and filled 100 kbytes of memory, it would be quite posible that the 100kbytes actually consists of various pieces of memory independant of one another. You never notice this since the Amiga automatically corrects the difference between apparent and actual memory.
The Amiga is truly an amazing machine,being capable of doing several things at one time. A red and white ball might be bouncing around in one window while you`re working on text in another window and watching a clock tick away in a third. At least that`s the impression most people get when they recieve their first Amiga demonstration. However,there is a catch to this: even the Amiga as only one processor,which can really only do one thing at a time.
The tricky part is when more than one program is running, each program is executed part by part, and the Amiga is constantly switching from one program back to the other program. In the example above, the ball would first be moved by one pixel, then the processor would check for a text entry and if necessary display it, after which it would move the clock`s second hand. This procedure would be repeated over and over, as the three programs are executed together. The problem is,that the greater the work load on the processor, the slower the things happen. Thus, programs run slower during heavy multi-tasking.
- Tasks: Each of these jobs that the Amiga has to execute are commonly referred to as tasks... thus, multi-tasking. During multi-tasking, each task is assigned a special time segment during which that particular task is executed. These time segments can be controlled, so that more time consumming programs can be allotted somewhat more processing time.
The programmer actually doesn`t need to know how this time slicing works. You can write a program without paying any attention to multi-tasking and then run it simultaneously with another program running in the background. The only restriction is that you`ll have to start the program from the CLI with `run`,or from the Workbench. If you execute the program from the CLI by simply typing its name, the processor allots all the time it can get from the CLI to that program, until the execution is complete. Starting the program with run free`s the CLI for other uses while the program is being executed.
There is another restriction regarding multi-tasking that applies to assembler programmers. Aside from the use of extra memory, which must first be reserved, the hardware registers should not be directly accessed. Instead the library functions should be used. The reason for this is quite simple: Should you,for instance, specify the printer port as the input line and are reading data in, another task might suddenly think its supposed to be printing. The line would thus be switched to output and data would be written out. After this,your program would try to read more data in, which would not be possible.
This is an oversimplified example, but it points out the problem nevertheless. In real programming situations the effects of multiple direct programming of the hardware registers can be much more catastrophic. If your program still needs to access the hardware registers directly (which can have some advantages), then make sure that the program always runs by itself.