Ah yes, the good old days of DevPac 2
on the Amiga
. In my spare time I would write little programs or even rewrite DOS
commands using assembly language
. I loved the idea that I knew exactly what the processor
was doing and as such not only could I do pretty much as I pleased with the system, but the program would be incredibly compact too. We acquired our first Amiga over ten years ago, the humble 500 which was powered by Motorola
's 68000 CPU
, but a few years later we got our hands on the 1200, which sported a whopping 2 meg of RAM
and a 32-bit 68020 processor. When my dad bought the first PC
into the house five years ago, I set about learning assembler, and frankly I was amazed, and not in a good way. Those of you who aren't into assembler programming look away now; this is going to get very boring.
In x86 assembler, many of the instructions are very limited - for example, to multiply or divide a number, you are restricted to the AX register. Also, speaking of registers, there are a mere four general purpose registers, only one of which, BX, can be used for effective addressing. On top of this there is the whole 16-bit problem where the registers were too small to fit addresses into, thus giving birth to the confusing segment-offset system. The later extended 32-bit registers went some way to solving this, but their legacy still remains.
However the 680x0 processors had it sorted. There were eight data registers and eight address registers, all 32-bit from the beginning (even in the 68000, which was a 16-bit processor). These were referenced as d0 to d7 and a0 to a7. Address registers were basically the same as the data registers, except that effective addressing was possible with them and certain instructions would assume their contents to be an address. Theoretically, data could be stored in them but this was considered bad form, especially as you already had eight dedicated data registers to play with.
Instructions were also much more flexible, with most operations giving the user the choice of where they were to be performed. For example, you could perform an unsigned multiplication of the amount held in d1 by the amount held in d0 with mulu d1,d0 with the result being stored in d0. Along with the ability to easily store any address in a register with just one instruction, this made assembler programming on the Amiga a doddle.
Another major difference with the Amiga was the way the operating system was integrated into the ROM. This meant that you could make library function calls (the equivelant to x86's interrupts - more on that in a sec) to open windows, create pull-down menus and so on fairly easily with the built in API taking care of everything, which basically meant that your program would work on any Amiga.
Perhaps slightly more complex than the x86 interrupt method was the Amiga's library system. With the PC, you just load the registers with the desired parameters and call an interrupt with the int instruction. The Amiga on the other hand had libraries of routines, similar to the PC's interrupts, built into the ROM. Before accessing any of the routines, you had to open the library which contained it, and you were also obligated to close it at the end of the program. The opening and closing of libraries, along with a few other basic system operations, was done via function calls to the exec.library which was permanently open. To access any library's routines you needed its base address. Once obtained it was usually stored in a6, and any routines in that library were called with the jsr instruction (a general jump to subroutine command) supplied with the appropriate offset to that address; for example jsr -48(a6) would jump to the library function which started at -48 bytes from the base address (the library offsets tended to go backward).
For example, if you wanted to perform disk or I/O operations, you would need the dos.library. To open this you would need to make a call to the exec.library's OpenLib routine. Of course, to call an exec.library routine you needed its base address, but as this library was always open, the address remained a constant 4 (sapphirecat informs me this was actually a register). Therefore by moving the address 4 into a6 (move.l 4,a6 - the .l meant long word, the equivalent of DWORD), you called the OpenLib routine by loading the address of a string containing the name of the desired library (in our case "dos.library") into a1 with lea _dosname,a1 (_dosname is the name I gave it, it is only a user defined variable, defined in a similar way to PC assemblers: _dosname dc.b "dos.library",0) and then calling the offset of the OpenLib routine, which if memory serves me correctly was -408, like this: jsr -408(a6). However, instead of having to remember all of these numbers, there were usually include files provided with most assembler packages which assigned them all to names, eg OpenLib = -408, so that all you had to do was jsr OpenLib(a6). (Actually my version of DevPac didn't, and I wrote all the includes for the major librabries myself - I was so 1337!). The OpenLib routine then opened your library and stored its base address in d0, after which it was always a good idea to save it in a variable (maybe something like _dosbase dc.l 0) as you would need it later to call CloseLib. For short programmes however, I just stored it in registers instead, as this was obviously quicker. Taking all of this into account, a typical Hello World program would look something like this:
include "DosLib.h" ;the include files assign addresses and offsets to
include "ExecLib.h" ;names so you don't have to remember tons of numbers
move.l ExecBase,a6 ;put exec.library base address (always 4) into a6
lea _dosname,a1 ;load a1 with dos.library name
jsr OpenLib(a6) ;and open it
move.l d0,a6 ;base address returned in d0, so put it in a6
jsr Output(a6) ;and call dos.library function to get handle to stdout
move.l a0,d1 ;handle returned in a0, so put in d1 for Write function
move.l #_message,d2 ;put address of output string in d2
moveq #12,d3 ;use quick move to fill d3 with length of string
jsr Write(a6) ;call Write function
move.l a6,a1 ;put dos.library base address into a1
move.l ExecBase,a6 ;put exec.library base address back into a6
jsr CloseLib(a6) ;and call CloseLib which takes a1 as parameter
rts ;all programs must end with rts (return from subroutine)
_dosname dc.b "dos.library",0
_message dc.b "Hello, world!"
Phew! Those are the major principles involved in 680x0 assembler, and you can see the differences between that and x86. Admittedly the library system is a little more involved than the PC's int system, but in my opinion the whole experience of programming the Amiga with assembly language was far easier and more enjoyable than it is for the PC. Bring back the 68020 I say! Actually that would be bad as it ran at 14Mhz.