_____ _________________________________ / /\ / \ / / / / ___________________________ \ / / / / / _________________________/\ \ / / / / /_/___________ _____ \ \ \ / / / / \ /\ \ \ \ \ / / / /_________________ \ \ \ \ \ \ \ / / / \________________/\ \ \ \ \ \ \ \ / /_/__________________________\_\ \ \ \ \___\/ \ / \ \ \ / /____________________________________________\ \ \____________/ \____________________________________________/ \/___________/FISH P R E S E N T AMIGA MACHINE LANGUAGE - FROM ABACUS BOOKS P A R T I Typed by DEE JAY of X-CELL for LSD Table of Contents. ------------------ 1. Introduction 1.1 Why machine code? 1.2 A look into the Amiga's memory 1.2.1 RAM,ROM,hardware register 1.2.2 Bits,bytes and words 1.2.3 Number systems 1.3 Inside the Amiga 1.3.1 Components and libraries 1.3.2 Memory 1.3.3 Multi-tasking 2 The MC68000 processor 2.1 Registers 2.2 Addressing memory 2.3 Operating modes 2.3.1 User and supervisor modes 2.3.2 Exceptions 2.3.3 Interrupts 2.3.4 Condition codes 2.4 The 68000 Instructions 3 Working with assemblers 3.1 The development assembler 3.2 AssemPro 3.3 The K-SEKA assembler 4 Our first programs 4.1 Adding tables 4.2 Sorting tables 4.3 Converting number systems 4.3.1 Converting hex to ASCII 4.3.2 Converting decimal to ASCII 4.3.3 Converting ASCII to hex 4.3.4 Converting ASCII to decimal 5 Hardware registers 5.1 Checking for special keys 5.2 Timing 5.3 Reading the mouse or joystick 5.4 Tone production 5.5 Hardware registers overview 6 The Amiga operating system 6.1 Load libraries 6.2 Calling functions 6.3 Program initialization 6.3.1 Reserve memory 6.3.2 Opening a simple window 6.4 Input/output 6.4.1 Screen output 6.4.2 Keyboard input 6.4.3 Printer control 6.4.4 Serial I/O 6.4.5 Speech output 6.5 Disk operations 6.5.1 Open files 6.5.2 Reading and writing data 6.5.3 Erase files 6.5.4 Rename files 6.5.5 CLI directory 6.5.6 Read directory 6.5.7 Direct access to disk 7 Working with Intuition 7.1 Open screen 7.2 Openwindow 7.3 Requesters 7.4 Event handling 7.5 Menu programming 7.6 Text output 7.7 Images 7.8 Borders 7.9 Gadgets 7.9.1 Boolean gadgets 7.9.2 String gadgets 7.9.3 Proportional gadgets 7.10 Example program 8 Advanced programming 8.1 Supervisor mode 8.2 Exception programming Appendix Overview of library functions Overview of the MC68000 Instruction CHAPTER 1 --------- 1.Introduction. -------------- Before you tackle machine language,you should take a closer look at several things that are vital to machine language programming. 1.1.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. 1.2.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. 1.2.1.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(Electically 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. 1.2.2.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. 1.2.3.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. Binary; 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: 1*2^3+0*2^2+1*2^1+0*2^0=2^3+2^1=8+2=10 (in decimal system) 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?.Thats 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 Octal; 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. Hex; 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. Its 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.Heres 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. 1.3.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. 1.3.1.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. Custom Chips; This task force is comprised of three chips,whose poetic names are Agnus,Denice,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 adresses 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 1.3.2.Memory. ------------ 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. 1.3.3.Multi-Tasking. ------------------- 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 has 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 aprogram without paying any attension 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. Chapter 2. --------- 2.The MC68000 Processor. ----------------------- The Amiga`s MC68000 processor is a 16/32 bit processor,which means that while it can process data of 32 bits,it"only"has a 16 bit data bus and a 24 bit address bus.Thus,it can access 2^24=16777216 bytes(or 16 Mbytes)of memory directly. 7.1 Megaherz; The Amiga 68000 processor,running at 7.1 megaherz,is quite fast, which is required for a computor with a workload as heavy as the Amiga`s.The Amiga also processes a number of custom chips that greatly ease the workload of the processor.These custom chips manage sound in/output,graphics and animation,thus freeing the processor for calculations. 2.1.Registers. ------------- In addition to the standard RAM,the processor contains internal memory called registers.There are eight data registers(D0-D7), eight address registers(A0-A7),a status register(SR),two stack pointers,a user stack pointer,a system stack pointer(USP and SSP) and the program counter(PC). Register Sizes; The data registers,the address registers,and the program counter are all 32 bits,while the status register is 16 bits.These registers are located dirctly in the processor so they are not accessed the same way memory would be accessed.There are special instructions for accessing these registers. Data Registers; The data registers are used for all kinds of data.They can handle operations with bytes(8 bits)words(16 bits)and longwords(32bits). Address Registers; The address registers are used for storing and processing addresses.This way they can be used as pointers to tables,in which case only words and longwords operations are possible. Stack Pointer; The address register A7 plays a special role:this register is utilized as the Stack Pointer(SR)by the processor,and thus is not recommended for normal use by the programmer.Which of the two possible stacks is being pointed to depends on the present mode of the processor,but more about that later. The stack,to whose actual position the stack pointer is pointing, is used to store temporary internal data.The stack works similar to a stack of notes on your desk:the note that was added to the stack last is the first one to come off the stack.This type of stack is known as LIFO(Last in,First out).There is another type of stack,the FIFO(First in,First out)which is not used by the processor itself. How these registers and the SP can be manipulated,or how to work with the stack,is presented in the next chapter.Lets continue with the registers for now. Status Register; The status register plays an important role in machine language programming.This 16-bit quality(word)contains important information about the processor status in 10 of its bits.The word is divided into two bytes,the lower byte(the user byte)and the upper byte(the system byte).The bits that signify that certain conditions are refered to as flags.This means that when a certain condition is present,a particular bit is set. The user byte contains five flags,which have the following meaning Bit Name Meaning ----------------------------------------------- 0 (C,Carry) Carry bit,modified by math calculation,and shift instructions. 1 (V,Overflow) Similar to carry,indicates a change of sign,in other words,a carry from bit six to bit seven. 2 (Z,Zero) Bit is set when the result of an operation is zero. 3 (N,Negative) Is set when the result of an operation is negative. 4 (X,Extended) Like carry,is set for arithmetic operations. 5-7 Not used. The system byte contains five significant bits: Bit Nane Meaning ----------------------------------------------- 8 I0 Interupt mask.Activates interupt 9 I1 levels 0 to 7,where 0 is the lowest 10 I2 and 7 is the highest priority. 11 not used. 12 not used. 13 (S,Supervisor) This bit indicates the actual pocessor mode(0=User,1=Supervisor mode). 14 not used. 15 (T,Trace) If this bit is set,the processor is in single step mode. Here's an overview of the status word; bit : 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 name : T - S - - I2 I1 I0 - - - X N Z V C Don't let the new terms,like mode and interupt confuse you.We'll talk about these in greater detail in the chapter dealing with the operating conditions of the processor. 2.2.Addressing Memory. --------------------- In the standard Amiga 500's and 1000's,the processor has over 512k of RAM available.The Amiga 2000 has one mega-byte of RAM that can be accessed by the processor.How does the processor access all this memory? If you're programming in BASIC you don't have to worry about memory management.You can simply enter MARKER%=1,and the value is stored in memory by the BASIC interpreter. In assembler,there are two ways of accomplishing this task: 1) Store the value in one of the data or address registers,or 2)Write it directly into a memory location. To demonstrate these two methods let's get a little ahead and introduce a machine language instruction,which is probably the most common:MOVE.As its name states,this instruction moves values. Two parameters are required with the instruction:source and destination. Lets see what the example from above would look like if you utilize the MOVE instruction... 1) MOVE #1,D0 This instruction moves the value 1 into data register D0.As you can see,the source value is always entered before the destination. Thus,the instruction MOVE D0,#1 is not possible. 2) MOVE #1,$1000 deposits the value 1 in the memory location at $1000.This address was arbitrarily chosen.Usually addresses of this form won't be used at all in assembler programs,since labels that point to a certain address are used instead.Thus,the more common way of writing this would be: ... MOVE #1,MARKER ... MARKER:DC.W 1 These are actually two pieces of program:the first part executes the normal MOVE instruction whose destination is `MARKER`.This label is usually defined at the end of a program and specifies the address at which the value is stored. The paraneter DC.W 1 is a pseudo op,apseudo operation.This means that this isn`t an instruction for the processor,but an instruction for the assembler.The letters DC stand for`DeClare`and the suffix .W indicates that the data is a Word.The other two suffix alternatives would be .B for a byte(8 bits)and .L for a long word(32 bits). This suffix(.B.W or.L)is used with most machine language instructions.If the suffix is omitted,the assembler uses .W(word) as the default parameter.If you wanted a long word,you`d use an instruction that looks something like this:MOVE .L #$1234678,D0 where as an instruction like MOVE.B #$12,D0 would be used for a byte of data.However,with this instruction there`s one thing you must be aware of... CAUTION: If the memory is accessed by words or long words,the address must be even(end digit must be 0,2,4,6,8,A,C,E)! Assemblers usually have a pseudo-op,`EVEN`or`ALIGN`,depending on the assembler,that aligns data to an even address.This becomes necessary in situations similar to this: ... VALUE1: DC.B 1 VALUE2: DC.W 1 If the VALUE1 is located at an even address,VALUE2 is automaticaly located at an odd one.If an ALIGN(EVEN)is inserted here,a fill byte(0)is inserted by the assembler,thus making the second address even. ... VALUE1: DC.B 1 ALIGN VALUE2: DC.W 1 Back to the different ways of addressing.The variations listed above are equivalent to the BASIC instruction MARKER%=1 where the % symbol indicates an integer value. Lets go a step further and translate the BASIC instruction MARKER %=VALUE% into assembler.You`ve probably already guessed the answer right? MOVE VALUE,MARKER ... ... MARKER: DC.W 1 VALUE : DC.W 1 In this case,the contents in the address at VALUE are moved into the address at MARKER. With the help of these simple examples,you`ve already become familiar with four different ways of addressing,in other words, ways that the processor can access memory.The first is characterized by the number sign(#)and represents a direct value. Thus,this method is also known as direct addressing,and is legal only for the source parameter! A further method in which a direct address(in our case,`MARKER`and `VALUE`)can be specified is known as absolute addressing.This method is legal for the source parameter as well as for the destination parameter. This method can be divided into two different types,between which the programmer usually does'nt notice a difference.Depending on whether the absolute address is smaller or larger than $FFFF,in other words if it requires a long word,it is called absolute addressing(for addresses above $FFFF)or otherwise absolute short addressing.The assembler generally makes the distinction between these two types,and thus,only general knowledge of absolute addressing is required. The fourth method of addressing that you've encountered so far is known as DATA REGISTER DIRECT.It was the first one introduced(MOVE #1,D0)in conjunction with direct addressing,the only difference being that this type accesses a data register(such as D0). These four methods aren't the only ones available to the MC68000 processor,in fact there are a total of 12.One other variation called ADDRESS REGISTER DIRECT,is almost identical to data register direct,except that it accesses the address register instead of the data register.Thus,you can use MOVE.L #MARKER,A0 to access the address register A0 directly. You now know five ways to address memory with which quite a bit can be accomplished.Now,lets tackle something more complicated and more interesting. Lets take another example from BASIC: 10 A=1000 20 POKE A,1 In this example the first line assigns the value 1000 to the variable A.This can be done in assembler as well:MOVE.L #1000,A0. In the assembler version the absolute value of 1000 ia stored in the address register A0. Line 20 doesn't assign a value to the variable A itself,but rather to the memory location at the address stored in A.This is an indirect access,which is quite easy to duplicate in assembler: MOVE.L #1000,A0 ;bring address in A0 MOVE #1,(A0) ;write 1 into this address The parentheses indicates an addressing known as ADDRESS REGISTER INDIRECT.This method only works with address registers,since a 'data register indirect' does not exist. There are several variations of this method.For instance,a distance value can be specified,which is added to the address presently located in the address register before the register is actually accessed.The instruction MOVE #1,4(A0),if applied to the example above,writes the value 1 into the memory cell at 1000+4= 1004.This distance value can be positive or negative.Thus,values from -32768 to +32768 are accepted.This specific variation of addressing is called ADDRESS REGISTER INDIRECT WITH A 16 BIT DISPLACEMENT value. There is another very similar variation:ADDRESS REGISTER INDIRECT WITH AN 8 BIT INDEX value.While this variation is limited to 8 bits,it also brings another register into play.This second register is also a distance value,except that it is a variable as well. We'll try to clarify this with an example.Lets assume that a program includes a data list that is structured like this: ... RECORD: DC.W 2 ;number of entries -1 DC.W 1,2,3 ;elements of list We'll use MOVE.L #RECORD,A0 to load the list into the address register A0.Then you can use MOVE (A0),D0 to pull the number of in the list into the data register.To access the last element of the listonly one instruction is needed.The whole thing looks something like this: CLR.L D0 ;erase D0 completely MOVE.L #RECORD,A0 ;address of list in A0 MOVE (A0),D0 ;number of elements -1 in D0 MOVE 1(A0,D0),D1 ;last element in D1 ... RECORD: DC.W 2 ;number of entries -1 DC.W 1,2,3 ;elements of list This last instruction accesses the byte that is located at 1+A0+D0 in other words,the record +1 where the data begins plus the contents of D0(in this case 2). This method of accessing is very useful.It works exquisitely for the processing of tables and lists,as the example demonstrates.If no distance value is needed,simply use a distance value of zero, which some assemblers automatically insert as the default value if for instance only MOVE (A0,D0)is entered. The latter two methods have a third variation,which as its own characteristic trait.It dosen't utilize an address register,but uses the Program Counter(PC)instead.The program counter with displacement method proves useful when a program must function without any changes in all address ranges.The following two statements(in the 15 bit limits)have the same effect: MOVE MARKER,D0 and MOVE MARKER(PC),D0 This method is actually rather imprecise,since the first instruction specifies the actual address of the marker with MARKER while the second line specifies the distance between the instruction and the marker.However since it would be quite cumbersome to constanly calculate the distance,the assembler takes this task off our hands and calculates the actual value automatic. Lets examine the difference between the two instructions.In a program they'll accomplish the same thing,although they are interpreted as two completely different things by the assembler. You'll assume a program being at the address $1000 and the marker is located at $1100.The generated program code looks something like this: $001000 30 39 00 00 11 00 MOVE MARKER,D1 or $001000 30 3A 00 FE MOVE MARKER(PC),D1 As you can see,the generated code of the second line is two bytes shorter than the first line.In addition,if you were to shift this code to the address $2000,the first version still accesses the memory at $1100,while the second line using the PC indirect addressing accesses memory at $2000 correctly.Thus,the program can be transferred to almost any location. This,then,is PROGRAM COUNTER WITH 16 BIT DISPACEMENT value.As we mentioned,there is also PROGRAM COUNTER WITH 8 BIT INDEX value, which permits a second register as a distance value,also known as offset. There are two addressing modes left.These two are based on indirect addressing.They offer the capability of automatically raising or lowering the address register by one when memory is accessed with address register indirect. To automatically increase the register,you'd use ADDRESS REGISTER DIRECT WITH POST-INCREMENT.The address register raises this by the number of bytes used AFTER accessing memory.Thus if you write: MOVE.L #1000,A0 MOVE.B #1,(A0)+ the 1 is written in the address $1000 and then A0 is raised by one Instructions like this are very helpful when a memory range is to be filled with a specific value(for instance when the screen is cleared).For such purposes the instruction can be placed in a loop ...which we'll get to later. The counter part to post increment is ADDRESS REGISTER WITH PRE- DECREMENT.In this case the specified address register is lowered by one BEFORE the access to memory.The instructions: MOVE.L #1000,A0 MOVE.B #1,-(A0) writes 1 in the address 999,since the contents of A0 is first decremented and the 1 is written afterwards. These two methods of addressing are used to manage the stack pointer(SP).Since the stack is filled from top to bottom,the following is written to place a word(s.aD0)on the stack: MOVE.B D0,-(SP) and to remove it from the stack,again in D0: MOVE.B (SP)+,D0 This way the stack pointer always points to the byte last deposited on the stack.Here,again,you'll have to be careful that an access to the stack with a word or a long word is always on an even address.Thus,if you're going to deposit a byte on the stack, either use a whole word or make sure the byte is not followed by a JSR or BSR.The JSR or BSR instructions deposit the addresses from which they stem on the stack in the form of a long word. In the code above,the SP is generally replaced by the address register A7 in the program code,since this register is always used as the SP.Thus,you can write A7 as well as S,the resulting program is the same.However,we recommend the use of SP,since this makes the code somewhat easier to read.After all,quite often you'll want to employ your own stacks,in which case the difference between the processor stack and your own stacks become very important. These are the twelve ways of addressing the MC68000 processor,Here is a summary: No Name Format -- ---- ------ 1 data register direct Dn 2 address register direct An 3 address register indirect (An) 4 address register indirect with post-increment (An)+ 5 address register indirect with pre-decrement -(An) 6 address register indirect with 16 bit displacement d16(An) 7 address register indirect with 8 bit index value 8(An,Rn) 8 absolute short xxxx.W 9 absolute long xxxxxxxx.L 10 direct #'data' 11 program counter indirect with 16 bit displacement d16(PC) 12 program counter indirect with 8 bit index value d8(PC,Rn) The abbreviations above have the following meanings: An address registers A0-A7 Dn data registers D0-D7 d16 16 bit value d8 8 bit value Rn register D0-D7,A0-A7 'data' up to 32 bit value,(either .B .W .L) These are the addressing modes used by the MC68000 processor.The bigger brother of this processor,the 32 bit MC68020,has six more methods which we won't discuss here. Next you're going to see under what conditions the processor can operate. 2.3.Operating Modes. ------------------- In the previous section about registers you encountered the Status Register(SR).The individual bits of this register reflect the present operating condition of the processor.You differentiated between the system byte(bits 8-15)and the user byte(bits 0-7).Now, lets take a closer look at the system byte and its effects upon the operation of the processor. 2.3.1.User and Supervisor Modes. ------------------------------- Isn't it rather strange that the processor classifies you either as a'user'or a 'supervisor'?Both of these operating modes are possible,the user mode being the more common mode.In this mode it is impossible to issue some instructions,and that in your own computor! Don't worry though,you can get around that,as well.The Amiga's operating system contains a function that allows us to switch the processor from one mode to the other. The mode is determined by bit 13 of the Status Register.Usually this bit is cleared(0),indicating that the processor is in user mode.It is possible to write directly into the status register, although this is a privileged instruction that can only be executed from the supervisor mode.Thus,this instructioncould only be used to switch from the supervisor mode into the user mode,by using AND #$DFFF,SR to clear the supervisor bit.However,it is quite preferable to let the operating system perform the switch between these two modes. Now what differentiates these two modes in terms of application? Well,we already mentioned the first difference:some instructions, such as MOVE xx,SR, are privileged and can only be executed from the supervisor mode.An attempt to do this in user mode would result in an exception and interruption of the program.Exceptions are the only way of switching to the supervisor mode,but more about later. A further difference is in the stack range used.Although A7 is still used as the stack pointer,another memory range is used for the stack itself.Thus,the SP is changed rach time you switch from one mode to the other.Because of this you differentiate between the User(USP)and the Supervisor SP(SSP). Accessing memory can also depend on these two modes.During such accessing,the processor sends signals to the peripheral components informing them of the current processor moce.This way a MC68000 computor can protect(privilege)certain memory ranges so they can't be accessed by the user. In the supervisor mode it is possible to execute all instructions and access all areas of memory.Because of this,operating systems usually run in the supervisor mode.This is accomplished through the use of Exceptions. 2.3.2.Exceptions. ---------------- Exceptions are similar to interupts on 6500 computors.This allows stopping a program,running a sub-program,and then restarting the stopped program.When an exception occurs the following seps are taken: 1) The status register is saved. 2) The S bit in SR is set(supervisor mode)and the T bit is cleared(no trace). 3) The program counter and the user SP are saved. 4) The exception vector,which points to the needed exception routine,is retrieved. 5) The routine is executed. The vectors mentioned,which contain the starting addresses for the various routines,are located at the very beginning of the memory. Here is an overview of the vectors and their respective addresses: Number Address Used for ----- ------ -------- 0 $000 RESET:starting SSP 1 $004 RESET:starting PC 2 $008 bus error 3 $00C address error 4 $010 illegal instruction 5 $014 division by zero 6 $018 CHK instruction 7 $01C TRAPV instruction 8 $020 privilege violation 9 $024 trace 10 $028 Axxx-instruction emulation 11 $02C Fxxx-instruction emulation $030-$038 reserved 15 $03C uninitialed interrupt $040-$05F reserved 24 $060 unjustified interrupt 25-31 $064-$083 level 1-7 interrupt 32-47 $080-$0BF TRAP instructions $0C0-$0FF reserved 64-255 $100-$3FF user interrupt vectors The individual entries in the table above need detailed explanation.So lets go through them one by one... RESET:staring SSP; At reset,the long word stored at this location is used as the stack pointer for the supervisor mode(SSP).This way you can specify the stack for the RESET routine. RESET:starting PC; Again at reset,the value at this location is used as the program counter.In other words,the RESET routine is started at the address stored here. Bus Error; This exception is activated by a co-processor when,for instance,a reserved or non-existant memory range is accessed. Address Error; This error occurs when a word or long word access is atempted at an odd address. Illegal Instruction; Since all MC68000 instructions consist of one word,a total 65536 different instructions are possible.However,since the processor doesn't that many instructions,there are a number of words that are invalid instructions.Should such a word occur,the exception is prompted. Division by Zero; Since the processor has a division function,and the division of anything by zero is mathematically undefined and thus illegal,this exception occurs when such an operation is attempted. CHK Instruction; This exception only occurs with the CHK instruction.This instruction tests that a data registers contents are within a certain limit.If this is not the case,the exception is activated. TRAPV Instruction; If the TRAPV instruction is executed and the V bit(bit 1)in the status word is set,this exception is prompted. Privilege Violation; If a privileged instruction is called from the user mode,this exception is activated. Trace; If the trace bit(bit 15)in the status word is set,this exception is activated after each instruction that is executed.This method allows you to employ a step by step execution of machine programs. Axxx-Instruction Emulation; Fxxx-Instruction Emulation; These two vectors can be used for a quite interesting trick.If an instruction beginning with $A or $F(such as $A010 or $F200)is called the,the routine to which the corresponding vector is pointing is accessed.In these routines you can create chains of other instructions,in effect expanding the processors instruction vocaulary! Reserved; These vectors are not used. Uninitialized Interrupt; This exception is activated when a peripheral component that was not initialized sends an interrupt. Unassigned Interrupt; Is activated when a BUS error occurs during the interrupt verification of the activating component.However,the interrupt is usually only by some type of disturbance. Level 1-7 Interrupt; These seven vectors point to the interrupt routines of the corresponding priority levels.If the level indicated in the status word is higher than the level of the occuring interrupt,the interrupt is simply ignored. TRAP Instructions; These 16 vectors are used when a corresponding TRAP instruction occurs.Thus,TRAP instructions from TRAP #0 to TRAP #15 are possible. User Interrupt Vectors; These vectors are used for interrupts which are activated by several peripheral components that generate their own vector number. At this point you don't want to delve any deeper into the secrets of exceptions,since we'd be expanding this book beyond its frame work.However,there's one last thing to say about exceptions:the exception routines are ended with the RTE(ReTurn from Exception) instruction,with which the original status is restored and the user program is continued. 2.3.3.Interrupts. ---------------- Interrupts are processed similarly to exceptions.They are breaks (or interruptions)in the program which are activated through hardware(such as a peripherical component or an external trigger). The interrupt level is stored in bits 8-10 of the status register. A value between 0 and 7 indicates the interrupt level.There are eight possible interrupts,each of which as a different priority.If the level of this interrupt happens to be higher than the value in the status register,the interrupt is executed,or else ignored. When a valid interrupt occurs,the computor branches to the corresponding routine whose address is indicated in the exception vector table above. The interrupts are very important if you're trying to synchronize a program with connected hardware.In this manner,a trigger(s.a the keyboard)which is to feed the computor data,can signal the request for a legal value using an interrupt.The interrupt routine then simply takes the value directly.This method is also employed for the operation of the serial interface(RS232). We'll talk about the use of interrupts at a later time.The last thing we want to mention about interrupts at this time is that, like exceptions,interrupt routines are terminated with the RTE instruction. 2.3.4.Condition Codes. --------------------- When you write a program in any language,the need for a conditional operation arises quite often.For instance,in a BASIC program IF D1=2 THEN D2=0 represents a conditional operation.To write the equivalent in machine language,you first need to make the comparison: CMP #2,D1 CMP stands for compare and compares two operands,in this case D1 and D2.How is this operation evaluated? For this purpose you have condition codes(CC's),which exist in the branch instructions of machine language.Because of this,you must specify when and where the program is to branch. The simplest variation of the branch instruction is an unconditional branch.The corresponding instruction is 'BRA address ',although this won't help you here.After all,you need a conditional branch. To retain the result of the operation,in this case a comparrison (CMP),several bits are reserved in the status word.Lets look at bit 2 first,which is the zero flag.This flag is set when the result of the previous operation was zero. To explain the relationship between CMP and the Z flag,you must first clarify the function of the CMP instruction.Actually this instruction performs the subtraction of the source operand from the destination operand.In the example above,the number 2 is subtracted from the content of the memory cell at D1.Before the result of this subtraction is discarded,the corresponding flags are set. If the contents of D1 in our example above happened to be 2,the result of the subtraction would be 0.Thus,the Z flag would be set, which can then be evaluated through a corresponding branch instruction.Our example would then look like this: ... CMP #2,D1 ;comparison,or subtraction BNE UNEQUAL ;branch,if not equal(Z flag not set) MOVE #0,D2 ;otherwise execute D2=0 UNEQUAL: ... ;program branches to here BNE stands for Branch if Not Equal.This means,that if the Z flag was cleared(=0)by the previous CMP,the program branches to the address specified by BNE(here represented by UNEQUAL).The counter part to the BNE instruction is the BEQ(Branch if EQual)instruction which is executed if Z=1. Here's a list of condition codes,which allow you to form conditional branches using the Bcc(cc=condition code)format: cc Condition Bits --------------------------------------------------- T true,corresponds to BRA F false,never branches HI higher than C'* Z' LS lower or same C + Z CC,HS carry clear,higher or same C' CS,LO carry set,lower C NE not equal Z' EQ equal Z VC overflow clear V' VS overflow set V PL plus,positive MI minus,negative GE greater or equal N*V+N'*V' LT less than N*V'+N'*V GT greater than N*V*Z'+N'*V'*Z' LE less or equal Z + N*V' + N'*V *=logic AND, +=logic OR, '=logic NOT Here are a few examples to demonstrate how these numerous conditions can be utilized: CMP #2,D1 BLS SMALLER_EQUAL This branches if the contents of D1<=2,whether D1 is 0,1 or 2.In this example,the BLE instruction would allow the program to branch even if D1 is negative.You can tell this by the fact that the V bit is used in the evaluation of this expression(see chart above). When the sign is changed during the operation,this V bit is compared with the N bit.Should both bits be cleared(N bit=0 and V bit=0)after the CMP subtraction(D1-2),the result has remained positive:the condition as not been met. The conditions EQ and NE are quite important for other uses,as well.For instance,they can be used to determine if particular bits in a data word are set,by writing the following sequence... ... AND #%00001111,D1 ;masks bits out BEQ SMALLER ;branches when none of the four ; ;lower bits is set CMP #%00001111,D1 BEQ ALL ;branches when all four bits set The AND instruction causes all bits of D1 to be compared with the bits of the parameter(in this case #%00001111).If the same bits are set in both bytes,the corresponding bits are also set in the result.If one bit of the pair is cleared,the resulting bit is zero as well.Thus,in the result,the only bits that are set are those bits of the lowest four that were set in D1. The technique is known as masking.In the example above,only the lowest four bits were masked out,which meansthat in the resulting byte,only the lowest four appear in their original condition.All other bits are cleared with the AND operand.Of course you can use any bit combination with this method. If no bit at all is set in the result,the zeroflag is set,thus fullfilling the BEQ condition and branching the program.Otherwise, the next instruction is processed,in which D1 is compared with %00001111.When both are equal,at leastall of the four lowest bits of the original byte have been set,in which case the following BEQ instruction branches. Aside from CMP,the CC and CS conditions can also be used to determine whether a HI bit was pushed out of the data word during data rotation with the ROL and ROR instructions. Before you move on the instruction vocabulary of the MC68000,we'd like to give you another tip: The AssemPro assembler makes it quite easy to try every command in posible situations.Take the CMP command which we've been talking about,for example.To test this command with various values and to receive the results of the comparisons directly via the flags,try the following. Type the following into the Editor. run: cmp $10,d1 bra run end Assemble it,save the resulting code and enter the debugger.After reloading the code you can then single step through the program observing the results the program has on the flags.Try changing the values in the register D1 and see how higher and lower values affect the flag. By the way,using the start command at this time causes it to run forever.Well,at least until reset is hit,which isn't exactly desirable,either... This procedure isn't limited to just the CMP instruction.You can use it to try any other instructions you're interested in. 2.4.The 68000 Instructions. -------------------------- Its about time to explain the MC68000 instructions.You don't have room for an in-depth discussion of each instruction in this book; for that purpose we recommend PROGRAMMING THE 68000 from Sybex by Steve Williams. The following tables show the required parameters and arguments for each instruction.AssemPro have access to built in help tables covering effective addressing modes and many of the Amiga Intuition calls.The following notation is used for arguments: Label a label or address Reg register An address register n Dn data register n Source source operand Dest destination operand address or register #n direct value Here is a short list of the instructions for the MC68000 processor AssemPro owners can simply place the cursor at the beginning of the instruction and press the help key to see the addressing modes allowed: Mnemonic Meaning --------------------------------------------------- Bcc Label conditional branch,depends on condition BRA Label unconditional branch(similar to JMP) BSR Label branch to subprogram.Return address is deposited on stack,RTS causes return to that address. CHK ,Dx check data register for limits,activate the CHK instruction exception. DBcc Reg,Label check condition,decrement and branch JMP Label jump to address(similar to BRA) JSR Label jump to subroutine.Return address is deposited on stack,RTS causes return to that address. NOP no operation RESET reset peripherals(caution!) RTE return from exception RTR return with loading of flags RTS return from subroutine(after BSR and JSR) Scc set a byte to -1 when condition is met STOP stop processing(caution!) TRAP #n jump to an exception TRAPV check overflow flag,the TRAPV exception Here are a few important notes... When a program jumps(JSR)or branches(BSR)to subroutine,the return address to which the program is to return is placed on the stack. At the RTS instruction,the address is pulled back off the stack, and the program jumps to that point. Lets experiment a little with this procedure.Please enter the following short program: run: pea subprogram ;address on the stack jsr subprogram ;subprogram called move.l (sp)+,d1 ;get a long word from stack ; illegal ;for assemblers without ;debuggers subprogram: move.l (sp),d0 ;return address in D0 rts ;and return end The first instruction,PEA,places the address of the subprogram on the stack.Next,the JSR instruction jumps to the subprogram.The return address,or the address at which the main program is to continue after the completion of the subprogram,is also deposited on the stack at this point. In the subprogram,the long word pointed to by the stack pointer is now loaded into the data register D0.After that,the RTS instruction pulls the return address from the stack,and the program jumps to that address. Back in the main program,the long word which is on the top of the stack,is pulled from the stack and written in D1.Assemblers that do not have the debugging features of AssemPro may need the ILLEGAL instruction so they can break the program and allow you to view the register contents. Assemble the program and load the resulting code into the debugger Single step thru the program and examine the register contents. Here you can see that D0 contains the address at which the program is to continue after the RTS command.Also,D1 contains the address of the subprogram which you can verify by comparing the debugger listing. The STOP and RESET instructions are so powerful that they can only be used in supervisor mode.Even if you do switch to the supervisor mode,you should NOT use these instructions if there is any data in memory that has not been saved and you wish to retain. The TRAP instruction receives a number between 0 and $F,which determines the particular TRAP vector(addresses $0080-$00BF)and thus the corresponding exception routine.Several operating systems for the 68000 utilize this instruction to call operating system functions.You'll deal more with this instruction later. In the short sample program that compared two numbers,the CMP instruction performed an arithmetic function,namely a subtraction. This subtraction could be performed with an actual result as well using the SUB instruction.The counterpart to this is in addition, for which the ADD instruction is used.In 8 bit processors,like the 6502,these arithmetic functions are the only mathematical operations.The MC68000 can also multiply,divide,and perform these operations with a variety of data sizes. Most of the functions require two parameters.For instance the ADD instruction... ADD source,destination where source and destination can be registers or memory addresses. Source can also be a direct value(#n).The result of the opoeration is placed in the destination register or the destination address. This is same for all operation of this type.These instructions can be tried out with the AssemPro assembler.In this case we recommend the use of a register as the destination. Heres an overview of the arithmetic operations with whole numbers: Mnemonic Meaning -------------------------------------------------------------- ADD source,dest binary addition ADDA source,An binary addition to a address register ADDI #n, addition with a constant ADDQ #n, fast addition of a constant which can be only from 1-8 ADDX source,dest addition with transfer in X flag CLR clear an operand CMP source,dest comparison of two operands CMPA ,An comparison with an address register CMPI #n, comparison with a constant CMPM source,dest comparison of two memory operands DIVS source,dest sign-true division of a 32 bit destination by a 16 bit source operand. The result of the division is stored in the LO word of the destination,the remainder in the HI word. DIVU source,dest division without regard to sign,similar to DIVS EXT Dn sign-true expansion to twice original size(width)data unit MULS source,dest sign-true multiplication of two words into one long word MULU source,dest multiplication without regard to sign, similar to MULS NEG negation of an operand(twos complement) NEGX negation of an operand with transfer SUB source,dest binary subtraction SUBA ,An binary subtraction from an address register SUBI #n, subtraction of a constant SUBQ #n, fast subtraction of a 3 bit constant SUBX source,dest subtraction with transfer in X flag TST test an operand and set N and Z flag For the processing of whole numbers,the processor can operate with BCD numbers.These are Binary Coded Decimal numbers,which means that the processor is working with decimals.In this method,each halfbyte contains only numbers from 0 to 9,so that these numbers can be easily processed.For this method,the following instructions are available: Mnemonic Meaning ----------------------------------------------------------------- ABCD source,dest addition of two BCD numbers NBCD source,dest negation of a BCD number(nine complement) SBCD source,dest subtraction of two BCD numbers Again,we recommend that you try this out yourself.Although handling the BCD numbers is relatively easy,it can be rather awkward at first.Be sure that you enter only BCD numbers for source and destination,since the results are not correct otherwise. Next are the logical operations,which you might know from BASIC. With these functions,you can operate on binary numbers bit for bit. Mnemonic Meaning ----------------------------------------------------------------- AND source,dest logic AND ANDI #n, logic AND with a constant EOR source,dest exclusive OR EORI #n, exclusive OR with a constant NOT inversion of an operand OR source,dest logic OR ORI #n, logic OR wuth a constant TAS check a byte and set bit 7 Single bits can also be manipulated by the following set of instructions: Mnemonic Meaning ---------------------------------------------------------------- BCHG #n, change bit n(0 is changed to 1 and vice versa) BCLR #n, clear bit n BSET #n, set bit n BTST #n, test bit n,result is displayed in Z flag These instructions are particularly important from the manipulation and evaluation of data from peripherals.After all,in this type of data,single bits are often very significant.You'll come across this more in later chapters. The processor can also shift and rotate an operand within itself ('n'indicates a register,'#'indicates a direct value which specifies the number of shiftings)... Mnemonic Meaning ---------------------------------------------------------------- AS n, arithmetic shift to the left(*2^n) ASR n, arithmetic shift to the right(/2^n) LSL n, logic shift to the left LSR n, logic shift to the right ROL n, rotation left ROR n, rotation right ROXL n, rotation left with transfer in X flag ROXR n, rotation right with transfer in X flag All these instructions allow you to shift a byte,a word or a long word to the left or right.Its not too surprising that this is the equivalentof multipling(dividing)the number by a power of 2.Here's a little example to demonstrate why Lets take a byte containing the value 16 as an example.In binary, it looks like this: %00010000 =16 Now,if you shift the byte to the left by inserting a 0 at the right,you'll get the following result... %00010000 shifted to the left equals %00100000 =32,in effect 16*2 Repeated shifting results in repeated doubling of the number.Thus if you shift the number n times,the number is multiplied by 2^n. The same goes for shifting to the right.However,this operation as a slight quirk:here's a sample byte with the value 5: %00000101 =5,shifted once to the right equals %00000010 =2 The answer in this case is not 2.5 as you might expect.The result such a division is always a whole number,since any decimal places are discarded.If you use the DIV instruction instead of shifting, you'll retain the digits to the right of the decimal point.However shifting is somewhat faster,and shifting can also receive long words as results. After explaining the principle of shifting,you still need to know why more than two instructions are required for the procedure.Well this is because there are several different types of shifting. First,you must differentiate between shifting and rotating.In shifting,the bit that is added to the left or the right side is always a zero.In rotating,it is always a specific value that is inserted.This means that with the ROR or the ROL instructions,the bit that is taken out on one side is the one that is inserted on the other.With the ROXR and the ROXL instructions this bit takes a slight detour to the X flag.Thus,the content of the flag is inserted into the new bit,while the old bit is loaded into the flag. Shifting as well,has two variations:arithmetic and logical shifting.You've already dealt with logical shifting.In this variation,the inserted bit is always a zero,and the extracted bit is deposited in the C flag and in the X flag. Although the highest bit,which always represents the sign,is shifted in arithmetic shifting,the sign is still retained by ASR. This has the advantage that when these instructions are used for division,the operation retains the correct sign(-10/2 equals-5). However,should an overflow or underflow cause the sign to change, this change is noted in the V flag,which always indicates a change in sign.With logical shifting this flag is always cleared. Now to the instructions that allow you to move data.These are actually the most important instructions for any processor,for how else could you process data? Mnemonic Meaning ------------------------------------------------------------------ EXG Rn,Rn exchange of two register contents(don't confuse with swap) LEA ,An load an effective address in address register An LINK An,#n build stack range MOVE source,dest carry value over from source to dest MOVE SR, transfer the status register contents MOVE ,SR transfer the status register contents MOVE ,CCR load flags MOVE USP, transfer the user stack point MOVE ,USP transfer the user stack point MOVEA ,An transfer a value to the address register An MOVEM Regs, transfer several registers at once MOVEM ,Regs transfer several registers at once MOVEP source,dest transfer data to peripherals MOVEQ #n,Dn quickly transfer a 8 bit constant to the data register Dn PEA deposit an address on the stack SWAP Dn swap the halves of the register(the upper 16 bits with the lower) UNLK An unlink the stack The LEA or PEA instructions are often used to deposit addresses in an address register or on the stack.The instruction LEA label,A0 loads the address of the label'label' into the address register A0.In practice,this corresponds to MOVE.L #label,A0 which is equivalent to PEA label All these instructions deposit the address of 'label' on the stack The following instruction also does this: MOVE.L #label,-(SP) The LEA instruction becomes much more interesting when the label is replaced by indirect addressing.Here's an example: LEA 1(A0,D0),A1 The address that's produced by the addition of 1(direct value offset)+A0+D0 is located in A1.To duplicate this instruction with MOVE would be quite cumbersome.Take a look: MOVE.L A0,A1 ADD.L D0,A1 ADDQ.L #1,A1 As you can see,the LEA instruction offers you quite some interesting possibilities. Those are all the instructions of the MC68000.Through their combination using the diverse methods of addressing,you can create a great number of different instructions,in order to make a program as efficent as possible. The following table is an overview of all MC68000 instructions along with their possible addressing types and the influence of flags.The following abbreviations are used: x=legal s=source only d=destination only -=not effected 0=cleared *=modified accordingly 1=set u=undermined P=privileged Mnemonic 1 2 3 4 5 6 7 8 9 10 11 12 X N Z V C P ----------------------------------------------------------------- ABCD x ADD s s x x x x x x x s s s * * * * * ADDA x x x x x x x x x x x x - - - - - ADDI x x x x x x x x * * * * * ADDQ x x x x x x x x x * * * * * ADDX x x * * * * * AND s x x x x x x x s s s - * * 0 0 ANDI x x x x x x x x - * * 0 0 ASL, ASR x x x x x x x x * * * * * Bcc - - - - - BCHG x x x x x x x x - - * - - BCLR x x x x x x x x - - * - - BRA - - - - - BSET x x x x x x x x - - * - - BSR - - - - - BTST x x x x x x x x z x x - - * - - CHK x x x x x x x x x x x - * u u u CLR x x x x x x x x - 0 1 0 0 CMP x x x x x x x x x x x x - * * * * CMPA x x x x x x x x x x x x - * * * * CMPI x x x x x x x x - * * * * CMPM x x x x x x x - * * * cpGEN - - - - - DBcc - - - - - DIVS x x x x x x x x x x x - * * * 0 DIVU x x x x x x x x x x x - * * * 0 EOR x x x x x x x x - * * 0 0 EORI x x x x x x x x - * * 0 0 EORI CCR * * * * * EORI SR * * * * * EXG - - - - - EXT - * * 0 0 EXTB - * * 0 0 ILLEGAL - - - - - JMP x x x x x x x - - - - - JSR x x x x x x x - - - - - LEA x x x x x x x - - - - - LINK x - - - - - LSL, LSR x x x x x x x * * * 0 * MOVE x s x x x x x x x s s s - * * 0 0 MOVEA x x x x x x x x x x x x - - - - - MOVE to CCR x x x x x x x x x x x * * * * * MOVE from SR x x x x x x x x - - - - - P MOVE to SR x x x x x x x x x x x * * * * * P MOVE USP x - - - - - P MOVEM x s d x x x x s s - - - - - MOVEP s d - - - - - MOVEQ d - * * 0 0 MULS x x x x x x x x x x x - * * 0 0 MULU x x x x x x x x x x x - * * 0 0 NBCD x x x x x x x x * u * u * NEG x x x x x x x x * * * * * NEGX x x x x x x x x * * * * * NOP - - - - - NOT x x x x x x x x - * * 0 0 OR s x x x x x x x s s s - * * 0 0 ORI x x x x x x x x - * * 0 0 PEA x x x x x x x - - - - - RESET - - - - - P ROL, ROR x x x x x x x - * * 0 * ROXL, ROXR x x x x x x x - * * 0 * RTE - - - - - P RTR * * * * * RTS - - - - - SBCD x x * u * u * Scc x x x x x x x x - - - - - STOP x - - - - - SUB s s x x x x x x x s s s * * * * * SUBA x x x x x x x x x x x x - - - - - SUBI x x x x x x x x * * * * * SUBQ x x x x x x x x x * * * * * SUBX x x * * * * * SWAP x - * * 0 0 TAS x x x x x x x x - * * 0 0 TRAP x - - - - - TRAPV - - - - - TST x x x x x x x x - * * 0 0 UNLK x - - - - - Chapter 3. --------- 3.Working With Assemblers. ------------------------- The instructions that you've learned so far are incomprehensible to the MC68000 processor.The letters MOVE mean absolutely nothing to the processor-it needs the instructions in binary form.Every instruction must be coded in a word-which normally takes a lot of work. An assembler does this work for you.An assembler is a program that translates the instructions from text into the coresponding binary instructions.The text that is translated is called Mnemonic or Memcode.Its a lot easier working with text instructions-or does $4280 mean more to you than CLR.L D0? This chapter is about working with assemblers.We'll describe the following three: ASSEM; This is the assembler from the Amiga's development package.This assembler is quite powerful,but it is clearly inferior to its two fellow compilers in some areas. AssemPro; This is the Abacus assembler.It has a debugger in addition to the assembler.This lets you test and correct programs.In our opinion, it is the best one to use for writing and testing practice programs.For this reason,we wrote the programs in this book with this assembler. KUMA-SEKA; This is a popular assembler which also has a debugger. All assemblers perform a similar task-they translate memcode,so that you can write a runable program to disk.To test the program directly,you need a debugger which is something Assem doesn't have. 3.1.The Development Assembler. ----------------------------- This assembler is a plain disk assembler.That means that it can only assemble text files that are on disk and write the result back to disk.You can't make direct input or test run the new program. You can call ASSEM from the CLI by typing ASSEM followed by parameters that specify what you wish the assembler to do. In the simplest case,you call it like this: ASSEM Source -O Destination Source is the filename of the file containing the program text. Destination is the name of the file that contains the results of assembling after the process is over.The "-O"means that the following name is used for the object file. There are several other parameters that can be passed.These are written with their option(ie-O),so that the assembler knows what to do with the file that you've told it to use.The following possible options must be followed by a filename. -O Object file -V Error messages that occur during assembling are written to a file.If this isn't given,the error messages appear in the CLI window. -L The output of the assembled program lines are sent to this file.You can also use"PRT:"to have it printed. -H This file is read in at the beginning of the assembled file and assembled along with it. -E A file is created that contains lines which have EQU instructions. -C This option isn't followed by a filename but by another option.You can also use OPT to do this.The following options are available: OPT S A symbol table is created which contains all the labels and their values. OPT X A cross reference list is created(where labels are used). OPT W A number must follow this option.It sets the ammount of workspace to be reserved. The assembler creates an object file.This is not runnable.To make it runnable,you need to call the linker,ALINK.This program can link several assembled or compiled object files together to make a runnable program.In the simplest case,you enter the following instruction in the CLI: ALINK Source TO Destination Source is the object file produced by the assembler.Destination is the name of the program.It can be started directly. 3.2.AssemPro. ------------ Abacus's AssemPro is a package which combines editor,assembler and debugger in an easy to use package. The AssemPro Program is divided into several windows-one for the assembler,the editor,the debugger and several help functions. Producing a program is very easy: 1) Write the program with the editor and then store it to disk. 2) Start the assembler,so that the program is translated. 3) If desired,start the debugger,load the program and test it. Within the debugger you can work through parts of the program or even through single commands.As after each one of these steps the debugger shows you the state of the registers and flags in the status register,you can easily try the programs presented in this book. You need to load AssemPro only once when working with machine language programs.Thus you don't need to save back and to between editor,assembler,linker and debugger. AssemPro has an especially interesting function:the reassembler. With this debugger function you are able to convert runnable programs into the source text of the program.Once you have made the source text,you can edit the program using the editor and assemble it again.AssemPro is equipped with functions other assemblers miss.There are however,some differences you should know about.As many programs you see were written for the K-SEKA,be aware of one difference:the EVEN command.AssemPro uses the ALIGN instruction. Note,that when entering and assembling one of the programs in AssemPro you must make sure that you place an END directive at the end of the source text. The following is an introduction into working with AssemPro and the programs in this book. Start AssemPro normally,next click on the editor window and start typing in your program.If the program is on disk already,load it by selecting the appropriate menu or by using the key combination right key and .To do this you only need to ckick on the filename in the displayed requester and click on the OK gadget. Once you have typed in or loaded the program into the editor,you can assemble it.It is best to save your source before assembling. You assemble your program by clicking on the assembler window displayed above the editor window and pressing and .You can then choose how to locate your program in the memory.Remember that data used by the co-processors must be located in CHIP RAM. By clicking OK you start the assembler process.If you additionally select "breakable",you can cancel the process by pressing both shift keys.If any error occurs during assembling,AssemPro uses a window to tell you this.Use this window to correct the error and continue with "Save and try again". Now the runnable program is located in the Amiga's memory.Use the menu item "Save as"to save it on disk.If you want to store it on RAM disk,click the given filename and enter RAM: in front of this name.In addition you can click on the menu item "ICON"and choose if you only want the program itself on disk but the icon too.Use this icon to start the program at a later time from Workbench. To test-run the program,you move the debugger window to the foreground of the screen(for instance by clicking on the back gadget).Use "Load"in the debugger menu or to call the select file window,where you select the saved program.The program is then loaded into the memory and its shown disassembled. The highlighted line(orange)represents the current state of the program counter.This is the line where the processor reads its next instruction,provided you tell the processor so.There are three ways to do so. The first one is to start the program with "Start".This alternative does not enable you to stop the program if anything goes wrong. The second possibility,"Start breakable"is better in this respect. After the program starts,it continuously displays the registers contents on the left side of the window.In addition to that you can cancel the process by pressing .Note that this only works if your program doesn't use the key itself. The third possibility enables you to only partly run your program. You can do this by stepping through the program or by placing breakpoints throughout the program.You place these by clicking on the desired address and then pressing ."BREAKPOINT"is displayed where the command was displayed before.If you start the program now,it stops whenever it comes across any breakpoints. You can start a small part of the program by moving the mouse pointer to the orange line,clicking the left button and holding it down while you drag the mouse pointer downward.If you release the button,the processor works through this part of the program, stopping at the line,where you positioned the mouse pointer.This is a very useful method to step by step test a program. AssemPro as another helpful window:the Table.This window lists the valid address methods for instructions and the parameters of Amiga functions.This is extremely helpful whenever you are not sure about one of the instructions. 3.3.The K-SEKA Assembler. ------------------------ The SEKA assembler,from KUMA,has a simple text editor and a debugger in addition to the assembler.This program is controlled by simple instructions and it is easy to use.It is also multi- functional and quick,so it is great for small test and example programs.You can use it to write bigger programs once you've got use to the editor.Now lets look at the editor. To load a program as source code(text)into the editor,enter"r" (read).The program asks you for the name of the file with the ""prompt.You then enter the name of the text file.If you created the file with SEKA,the file is stored on disk with".s" on the end of its name.You don't need to include the".s"when you load the file.Thats taken care of automatically.("s"stands for source.) You can store programs you've just written or modified by using the"w"instruction.The program asks you for the name.If you enter "Test",the file is written to the disk with"Test.s"as its name. This is a normal text file in ASCII format. There are two ways to enter or change a programs:using the line editor or the screen editor.You can enter the second by hitting the key.The upper screen section is then reserved for the editor.You can move with the cursor keys and change the text easily.The lines that you enter are inserted into the existing text and automatically numbered.By hitting the key again,you leave the screen editor. There's really not much to say about this editor.It's really just for simple insertions and changes.Other functions are called in normal instruction mode,the mode in which">"is the input prompt. The following instructions are available to you for text editing (stands for a number.The meaning of the instructions is in parenthesis.) Instruction Function ---------------------------------------------------------------- t(Target) Puts the cursor on the highest line in the text. t Puts the cursor on line n. b(Bottom) Puts the cursor on the last line in the text. u(Up) Go up one line. u Go up n lines. d(Down) Go down one line. d Go down n lines. z(Zap) Deletes the current line. z Deletes n lines starting at the cursor line. e(Edit) Lets you edit the current line(and only that line). e Edit from line n. ftext(Find) Searches for the text entered starting at the current line.The case of a letter makes a difference,so make sure to enter it correctly. Blanks that appear after the f are looked for as well! f Continues searching beyond the text that was previously given. i(Insert) Starts the line editor.Now you can enter a program line by line.However,you can't use the cursor to move into another line.Line numbers are generated automatically.The lines that follow are moved down,not erased. ks(Kill Source) The source text is deleted if you answer"y" when the program asks if you are sure.Otherwise nothing happens. o(Old) Cancels the "ks"function and saves the old text p(Print) Prints the current line. p Prints n lines starting at cursor line. Those are the K-SEKA's editor functions.In combination with the screen editor,they allow for simple text editing.You can,for example,delete the current line(and other lines)while working in the screen editor by hitting to get into instruction mode and then entering"z"(or "z"). If you'd like to edit all lines that contain "trap",for example, you can do the following: -Jump to the beginning of the text using "t" -Search for a "trap"instruction by entering "ftrap" in the first line. -Press and edit the line. -Press again to get into instruction mode. -Search using "f",,etc.until you get to the end of the text. This sounds very clumsy,but in practise it works quite well and goes quickly.Experiment with the editor bit,so you can get use to it. Now here are the instructions for working with disks: Instruction Function ----------------------------------------------------------------- v(View files) Look at the disk's directory.You can also include the disk drive or subdirectory that interests you.For example,"vc"causes the "c"subdirectory to be listed and makes it the current directory. kf(Kill file) The program asks for the name of the file. The file is deleted(and you aren't asked if your sure either-so be careful). r(Read) After inputting this instruction,you'll be asked which file to load(FILENAME>).The file that you specify is then loaded.If only "r"is entered,a text file is loaded in the editor. ri(Read Image) Loads a file into memory.After you've entered the filename,SEKA asks for the address the file should begin at in memory (BEGIN>)and the highest address that should be used for the file(END>). rx(Read from Auxillary) This works just like the "ri"function except that it reads from the serial port instead of from disk(You don't need a file name). rl(Read Link file) This instruction reads in a SEKA created link file.First you'll be asked if you are sure,because the text buffer is erased when the link file is loaded. w(Write) After entering this instruction,you'll be asked for the name of the file the text should be written to.A".s"is automatically appended to the name,so that it can be recognized as a SEKA file. wi(Write Image) Stores a block of memory to disk after the name,beginning and end are entered. wx(Write to Auxillary) This is similar to"wi";the only difference is that the output is to the serial inter- face. wl(Write Link file) Asks for the name and then stores a link file that was assembled with the"I"option to disk.If this isn't available,the message "* * Link option not specified" appears. Once you've typed in or loaded a program,you can call the assembler and have the program translated.Just enter"a"to do so. You'll then be asked which options you want to use.If you enter a ,the program is assembled normally-ie the results of translating a program in memory is stored in memory.Then the program can be executed straight away. You can enter one or more of the following options,however: v The output of the results goes to the screen. p or e goes to the printer with a title line. h The output stops after every page and waits for a key stroke.This is useful for controlling output to the screen or for putting new sheets of paper in the printer. o This option allows the assembler to optimize all possible branch instructions.This allows the program code to be shorter than it would otherwise be.Several messages appear but you can ignore them. l This option causes linkable code to be produced.You can save it with the"wl"instruction and read it with the "rl" instruction. A symbol table is included at the end of the listing if desired. The table contains all labels and their values.It also contains macro names.A macro allows several instructions to be combined in to a single instruction. For example,suppose you wrote a routinethat outputs the text that register A0 points to.Every time you need to use the routine,you must type: lea text,a0 ;pointer to text in A0 bsr pline ;output text You can simplify this by defining a macro for this function.To do this,put the following at the beginning of the program: print:macro ;Macro with the name "Print" lea ?1,a0 ;Parameter in A0 bsr pmsg ;Output text endm ;End of macro Now,you can simply write the following in your program: print text ;Output text This line is replaced using the macro during assembly.The parameter "text"is inserted where "?1"appears in the macro.You can have several parameters in a macro.You give them names like "?2", "?3",etc... You can also decide whether you'd like to see the macros in the output listing of the assembler.This is one of the pseudo-ops that are available in the assembler.The SEKA assembler has the following pseudo-ops: dc Defines one or more data items that should appear in this location in the program.The word length can be specified with .B,.W,or .L-and if this is left off, .B is used.Text can be entered in question marks or apostrophes.For example:dc.b "Hello",10,13,0 blk Reserves a number of bytes,words or long words,depending on whether .B,.W,or .L is chosen.The first parameter specifies the number of words to be reserved.The second (which is optional)is used to fill the memory area.For example:blk.w 10,0 org The parameter that follows the org instruction is the address from which the (absolute) program should be assembled.For example: org $40000 code Causes the program to be assembled in relative mode,the mode in which a program is assembled starting at address 0.The Amiga takes care of the new addressing after the program is loaded. data This means that from here on only data appear.This can be left out. even Makes the current address even by sometimes inserting a fill byte. odd The opposite of even-it makes the address odd. end Assembling ends here. equ or Used for establishing the value of a label = For example: Value=123 or Value:equ 123 list Turns the output on again(after nlist).You can use the following parameters to influence the output: c Macro calls d Macro definitions e Macro expansion of the program x Code expansions For example: list e nlist Turns off output.You can use the same parameters here as with "list". page Causes the printer to execute a page feed,so that you'll start a new page. if The following parameter decides whether you should continue assembling.If it is zero,you won't continue assembling. else If the "if"parameter is zero,you'll begin assembling here. endif End of conditional assembling. macro Start of a macro definition. endm End of macro definition. ?n The text in the macro that is replaced by the nth parameter in the calling line. ?0 Generates a new three digit number for each macro call- this is very useful for local labels. For example: x?0:bsr pmsg illegal Produces an illegal machine language instruction. globl Defines the following label as globel when the "I"option of the assembler is chosen. Once you've assembled your program,the program code is in memory. Using the "h"instruction,you can find out how large the program is and where it is located in memory.The beginning and end address is given in hex and the length in decimal(according to the last executed operations): Work The memory area defined in the beginning Src Text in memory RelC Relocation table of the program RelD Relocation table of the memory area Code Program code produced Data The program's memory area You'll find program in memory at the location given by Code.It's a pain to have to enter this address whenever you want to start the program.It make's good sense to mark the beginning of the program with a label(for example,"run:").You can use the "g"instruction to run the program as follows: g run The"g"(GO)instruction is one of SEKA's debugger instrucions.Heres an overview: x Output all registers. xr Output and change of registers(ie xd0) gn Jump to address n.You`ll be asked for break points,addresses at which the program should be terminate. jn This is similar to the one above-a JSR is used to jump into the program.The program must end with a RTS instruction. qn Output the memory starting at address n.You can also specify the word length.For example: q.w $10000 nn Disassembled output starting at address n. an Direct assembling starting at address n.Direct program instructions are entered. nn Modify the contents of memory starting at address n.Here too the word length can be given.You can terminate input with the key. sn Executes the program instruction that the PC points to.After you enter this instruction,n program steps are executed. f Fill a memory area.You can choose the word width.All the needed parameters are asked for individually. c Copies one memory area to another.All the needed parameters are asked for individually. ? Outputs the value of an expression or a label. For example: ?run+$1000-256 a Sets an instruction sequence that is passed to the program when it starts as if it were started from CLI with this sequence. ! Leaves the SEKA assembler after being asked if your sure. You saw some of the calculations like SEKA can make in the "?" example.You can also use them in programming.The folowing operations work in SEKA: + Addition - Subtraction * Multiplication / Division & Logic AND ! Logic OR ~ EXclusive OR (XOR) These operations can also be combined.You can choose the counting system.A "$"stands for hexadecimal,"@"for octal,and "%"for binary. If these symbols aren`t used,the number is interpreted as a decimal number. Lets go back to the debugger.As mentioned,after entering "g address",you`ll be asked for break points.You can enter up to 16 addresses at which the program halts.If you don`t enter break points,but instead hit ,the program must end with an ILLEGAL instruction.If it ends instead with a RTS,the next return address from the stack is retrieved and jumped to.This is usually address 4 which causes SEKA to come back with "**Illegal Instruction at $000004",but theres no guarantee that it will.Your computor can end up so confused that it can`t function. The SEKA program puts an ILLEGAL instruction in the place specified as break points after saving the contents of these locations.If the processor hits an illegal instruction,it jumps back to the debugger by using the illegal instruction vector that SEKA set up earlier.Then SEKA repairs the modified memory locations and then displays the status line.Here you can find out where the program terminated. Using break points is a good technique for finding errors in the program.You can,for example,put a break point in front of a routine that you`re not sure about and start the program.When the program aborts at this spot,you can go through the routine step by step using the "s"option.Then you can watch what happens to the status line after each instruction and find the mistake. Program errors are called bugs.That`s why the program that finds them is called a debugger. Chapter 4. --------- 4.Our First Programs. -------------------- You`re getting pretty far along in your knowledge of machine language programming.In fact,you`re to the point where you can write programs,and not just programs for demonstration purposes, but ones that serve a real function.We`re assuming that you have the AssemPro assembler and have loaded it. If you`re using a different assembler,a few things must be done differently.We covered those differences already in the chapter on the different assemblers. We`ve written the example programs as subroutines so they can be tried out directly and used later.After assembling the program,you can put the desired values in the register.Then you can either single-step thru the programs or run the larger programs and observe the results in the registers directly.(using the SEKA assembler you can type "j program_name"to start the program.Then you can read the results from the register directly,or use "q address"to read it from memory.) Lets start with an easy example,adding numbers in a table. 4.1.Adding Tables. ----------------- Imagine that you have numbers in memory that you'd like to add. Lets assume that you have five numbers whose length is one word each.You want their sum to be written in register D0.The easiest way to do this is: ;(4.1A) adding1: clr.l D0 ;Erase D0 (=0) move table,d0 ;First entry in D0 add table+2,d0 ;Add second entry add table+4,d0 ;Add third entry add table+6,d0 ;Add fourth entry add table+8,d0 ;add fifth entry rts ;Return to main program table: dc.w 2,4,6,8,10, end Try out the program using the debugger by single stepping thru the program until you get to the RTS instruction(left Amiga T).(SEKA owners use "j adding1").You see that data register D0 really contains the sum of the values. The method above only reads and adds numbers from a particular set of addresses.The Amigas processor has lots of different sorts of addressing modes that give us a shorter and more elegant solution. Lets add a variable to the address of the table,so that the program can add different tables. Lets put the addresses of the table in an address register(for example, A0)instead.This register can be used as a pointer to the table.You must use move.l since only long words are relocatable.By using a pointer to a table you can use indirect addressing.You can change the expression "table+x" to"x(A0)". ;(4.1B) adding1: clr.l D0 ;Erase D0 (=0) move.l #table,a0 ;Put table addresses in A0 move 0(a0),d0 ;Put first entry in d0 add 2(a0),d0 ;Add second entry add 4(a0),d0 ;Add third entry add 6(a0),d0 ;Add forth entry add 8(a0),d0 ;Add fifth entry rts ;Return to main program] table: dc.w 2,4,6,8,10 end Assemble this program,load it into the debugger.Then single step (left Amiga T)thru this program and you'll see that this program adds five numbers in order just like the last one.The reason you used a step size of two for the ofset is that words are two bytes long.AssemPro also defaults to relocate code so that you must move #table as a long word. Lets improve the program more by using "(a0)+"instead of "x(a)". This way,every time you access elements of the table,the address register A0 is automatically incremented by the number of bytes that are read(in this case two).The difference between this and the last example is that here the registers contents are modified. The pointer is to the next unused byte or word in memory. Lets make it even better.Lets make the number of words to be added to a variable.You'll pass the number in register D1.Now you need to do a different sort of programming,since you can't do it with the old methods. Lets use a loop.You need to add D1 words.You can use(a0)+ as the addressing method(address register indirect with post increment), since this automatically gets you to the next word. Now for the loop.You'll have D1 decremented by one every time the contents of the pointer are added.If D1 is zero,then you're done. Otherwise,you need another addition.The program looks like this: ;(4.1C) adding2: clr.l d0 ;Erase D0 move.l #table,a0 ;Put table addresses in A0 move #$5,d1 ;Put number of entries in D1 loop: ;Label for loop beginning add (a0)+,d0 ;Add a word subq #1,d1 ;Decrement counter bne loop ;Continue if non-zero rts ;Else done table: dc.w 2,4,6,8,10 end Lets take a close look at this program.Load the pointer A0 with the addresses of the data and the counter D1 with the number of elements.Then you can single step thru the program and watch the results.Make sure not to run the final command,the RTS command, because otherwise a return address is popped from the stack,and the results of this are unpredictable.(SEKA owners can use"x pc" to point the program counter to "adding2".You can then step thru the program by using the "s"command and watch the results). To finish up this example,your assigning a little homework.Write the program so that it adds single bytes or long words.Try to write a program that takes the first value in a table and subtracts the following values.For example,the table table: dc.w 50,8,4,6 should return the value 50-8-4-6, ie 32 ($20). 4.2.Sorting a Table. ------------------- Lets keep working with tables.You don't want to just read data from one this time.You want to change it.You'll assort the table in assending order. You need to decide how to do the sorting.The simplest method is to do the following. Compare the first and the second value.If the second value is larger than the first one,things are OK so far.Do the next step, compare the second and third values,and so on.If you come to the final pair and in each case the preceding value was smaller than the following value,then the sorting is done(it was unnescessary). If you find a pair where the second value is smaller than the first,the two values are exchanged.You then get a flag(here let's use a register)that is checked once you're done going thru the table.If it is set,the table probably isn't completely sorted.You then erase the flag and start again from the beginning.If the flag is still zero at the end,the sorting is complete. Now let's write a program to do this.First let's figure out the variables you need.You'll use registers for the variables.You need a pointer to the table you're sorting(A0),a counter(D0)and a flag (D1).While the program is running,change these values,so you'll need two more registers to store the starting values(address and the number of the table entries).You'll use A1 and D2. Let's start writing the program,each section will be written and then explained.Then the complete program will be given.You put the tables address in A1 and the number of entries in D2. ;(4.2A) part of sort routine sort: ;Start address of the program move.l #table,a1 ;Load pointer with address move.l a1,a0 ;Copy pointer to working register move.l #5,d2 ;Number in the counter move.l d2,d0 ;Copy number of elements subq #2,d0 ;Correct conter value clr d1 ;Erase flag table: dc.w 3,6,9,5 end Now the preparations are complete.The pointer and the counter are ready and the flag is cleared.The counter is decremented by two because you want to use the DBRA command(take one off)and only X-1 comparrisons are needed for X numbers(take off one more). Next let's write the loop that compares the values.You compare one word with another.It looks like this: loop: move 2(a0),d3 ;Next value in register D3 cmp (a0),d3 ;Compare values You need to use register D3 because CMP (A0),2(A0) isn't a legal choice.If the second value is greater than or equal to the first value,you can skip an exchange. bcc noswap ;Branch if greater than or equal ;to Now you need to do the exchanging(unfortunatly you can't use EXC 2(a0),(a0) since this form of addressing does not exist). doswap: move (a0),d1 ;Save first valus move 2(a0),(a0) ;Copy second into first word move d1,2(a0) ;Move first into second moveq #1,d1 ;Set flag noswap: Now increment the counter and continue with the next pair.You do this until the counter is negative. addq.l #2,a0 ;Pointer+2 dbra d0,loop ;Continuing looping until the end Now you'll see if the flag is set.You start again at the beginning if it is. tst d1 ;Test flag bne sort ;Not finished sorting yet! rts ;Otherwise done.Return If the flag is zero,you're done and the subroutine ends.You jump back to the main program using the RTS command. Now a quick overview of the complete program. ;(4.2B) sort: ;Start address of the program move.l #table,a1 ;Load pointer with address move.l a1,a0 ;Copy pointer to working register move.l #5,d2 ;Number in the counter move.l d2,d0 ;Copy number of elements subq #2,d0 ;Correct counter value clr d1 ;Erase flag loop: move 2(a0),d3 ;Next value in register D3 cmp (a0),d3 ;Compare values bcc noswap ;Branch if greater than or equal to doswap: move (a0),d1 ;Save first value move 2(a0),(a0) ;Copy second into first word move d1,2(a0) ;Move first into second moveq #1,d1 ;Set flag noswap: addq.l #2,a0 ;Pointer+2 dbra do,loop ;Continue looping until the end tst d1 ;Test flag bne sort ;Not finished sorting yet! rts ;Otherwise done.Return table: dc.w 10,8,6,4,2 ;When finished acceding end To test this subroutine,assemble the routine with AssemPro,save it and then load it into the debugger.The table is directly after the RTS,notice its order.Set a breakpoint at the RTS,select the address with the mouse and press left-Amiga-B sets a breakpoint in AssemPro.Start the program the redisplay the screen by selecting "Parameter-Display-Dissassem-bled"and examine the order of the numbers in the table,they should now be ascending order. You use several registers in this example for storing values. Usually in machine language programming your subroutines cannot change any register or can only change certain registers.For this reason,there is a machine language command to push several registers onto the stack at the same time.This is the MOVEM ("MOVE multiple")command.If you insert this command twice in your program then you can have the registers return to the main program with the values they had when the subroutine was called.To do this,you need one more label.Let's call it"start";the subroutine is started from here. start: movem.l d0-d7/a0-a6,-(sp) ;save registers sort: etc... ... ... bne sort ;not finished sorting yet! movem.l (sp)+,d0-d7/a0-a6 ;retrieve registers rts ;finished The powerful command moves several registers at the same time.You can specify which registers should be moved.If you want to move the D1,D2,D3,D7,A2 and A3 registers,just write movem.l d1-d3/d7/a2-a3,-(sp) Before you quit sorting,do one little homework assignment.Modify the program,so that it sorts the elements in descending order. 4.3.Converting Number Systems. ----------------------------- As we mentioned in the chapter on number systems,converting numbers from one base to another can be rather difficult.There is another form of numeric representation-as a string that can be entered via the keyboard or output on the screen. You want to look at some of the many conversions possible and write programs to handle the task.You'll start by converting a hex number into a string using binary numbers and then print the value in hex. 4.3.1.Converting Hex To ASCII. ----------------------------- First you need to set the start and finish conditions.In this example,let's assume that data register D1 contains a long word that should be converted into a 8-digit long string of ASCII characters.You'll write it to a particular memory location so that you can output it later. The advantage of using hex instead of decimal is pretty clear in this example.To find out the hexadecimal digit for a particular spot in the number,you just need to take the corresponting 4 bits (half byte)and do some work on it.A half byte(also called a nibble)contains one hex digit. You'll work on a half byte in D2.To convert this to a printable character,you need to use the correct ASCII code.The codes of the 16 characters that are used as hex digits are the following: 0 1 2 3 4 5 6 7 8 9 A B C D E F $30 $31 $32 $33 $34 $35 $36 $37 $38 $39 $41 $42 $43 $44 $45 $46 To convert the digits 0-9,you just need to add $30.For the letters A-F that correspond to the values 10-15,you need to add $37.The program to evaluate a half byte must make a destinction between values between 0 and 9 and those between A and F and add either $30 or $37. Now let's write a machine language subroutine that you'll call for each digit in the long words hex representation. nibble: and #$0f,d2 ;just keep low byte add #$30,d2 ;add $30 cmp #$3a,d2 ;was it a digit? bcs ok ;yes:done add #7,d2 ;else add 7 ok: rts ;done This routine converts the nibble in D2 to an ASCII character that corresponds to the hex value of the nibble.To convert an entire byte,you need to call the routine twice.Here is a program to do this.The program assumes that A0 contains the address of the buffer that the characters are to be put in and that D1 contains the byte that is converted. ;(4.3.1a) bin-hex ; ;your program lea buffer,a0 ;pointer to buffer move #$4a,d1 ;byte to be converted(example) bsr byte ;and convert rts ; ... ;more of your program byte: move d1,d2 ;move value into d2 lsr #4,d2 ;move upper nibble into lower nibble bsr nibble ;convert d2 move.b d2,(a0)+ ;put character into buffer move d1,d2 ;value in d2 bsr nibble ;convert lower nibble move.b d2,(a0)+ ;and put it in buffer rts ;done nibble: and #$0f,d2 ;just keep low byte add #$30,d2 ;add $30 cmp #$3a,d2 ;was it a digit? bcs ok ;yes:done add #7,d2 ;else add 7 ok: rts ;done buffer: blk.b 9,0 ;space for long word data end To test this subroutine,use AssemPro to assemble the routine,save the program and load it intoi the debugger.Next set a breakpoint at the first RTS,to set the breakpoint in AssemPro select the correct address with the mouse and press the right-Amiga-B keys. Start the program and watch the contents of D2,it is first $34 (ASCII 4)and finally $41(ASCII A).Select "Parameter-Display-HEX- Dump"and you'll see that 4A has been moved into the buffer. This is how the routine operates.First,you move the value that you wish to convert into D2.Then you shift the register four times to the right to move the upper nibble into the lower four bits.After the subroutine call,you use "move.b d2,(a0)+"to put the HI nibble in the buffer.Then the original byte is put in D2 again.It is converted.This gives us the LO nibble as an ASCII character in D2. You put this in the next byte of the buffer. The buffer is long enough to hold a long word in characters and closing the null byte.The null byte is usually required by screen output routines.Screen output will be discussed in a later chapter.Now let's worry about converting a long word. When converting a long word,you need to be sure to deal with the nibbles in the right order.Before calling the "nibble"routine for the first time,you need to move the upper nibble into the lower 4 bits of the long word.You need to do this without losing anything. The LSR command isn't very good for this application.If you use it you'll lose bits.Its better to use the rotation commands like ROR or ROL,since they move the bits that are shifted out,back in on the other side. If you shift the original long word in D1 four times to the left, the upper four bits are shifted into the lower four bits.Now you can use our "nibble"routine to evaluate it and then put the resulting ASCII character in the buffer.You repeat this eight times and the whole long word has been converted.You even have D1 looking exactly the way it did before the conversion process began ;(4.3.1B) bin-hex-2 hexlong: lea buffer,a0 ;pointer to the buffer move.l #$12345678,d1 ;data to convert move #7,d3 ;counter for the nibbles:8-1 loop: rol #4,d1 ;move upper nibble into lower move d1,d2 ;write in d2 bsr nibble ;and convert it move.b d2,(a0)+ ;character in buffer dbra d3,loop ;repeat 8 times rts ;finished! nibble: and #$0f,d2 ;just keep low byte add #$30,d2 ;add $30 cmp #$3a,d2 ;was it a digit? bcs ok ;yes:done add #7,d2 ;else add 7 ok: rts ;done buffer: blk.b 9,0 ;space for long word,null byte end To test this subroutine,use AssemPro to assemble the routine,save the program and load it into the debugger.Next set a breakpoint at the first RTS,to set the breakpoint in AssemPro select the correct address with the mouse and press the right-Amiga-B keys.Start the program and when it is finished redisplay the output by selecting "Parameter-Display-HEX-dump"so you can examine the new buffer contents. You'll find that there's an error in the program-the buffer contains the digits "56785678"instead of "12345678".Try to find the error. Have you found it?This is the sort of error that causes you to start pulling your hair out.This sort is hard to find.The assembler assumes that the rotation operation should be done on a word,because the ".l"was left off.As a result,only the lower word of D1 was rotated-so you get the same value twice.If you change the "rol.l",things work just right. The error shows how easy it is to convert the program above into one that converts four digit hex numbers into ASCII characters. Just leave off the ".l"on the "rol"command and change the counter from seven to three.The program is done. Now for a little homework:change the program so that it can handle six digit hex numbers(D1 doesn't nescessarily have to stay the same...)! Now lets look at a different conversion problem:converting a four digit decimal number. 4.3.2.Converting Decimal To ASCII. --------------------------------- It's not quite as easy to convert decimal as hex.You can't group the bits to form individual digits.You need to use another method. Lets look at how a decimal number is constructed.In a four digit number,the highest place is in the thousands place,the next is the hundreds place,etc... If you have the value in a register and divide by 1000,you'll get the value that goes in the highest place in the decimal number. Since the machine language command DIV not only gives us the result of the division but also gives us the remainder,you can work out the remainder quite easily.You divide the remainder by 100 to find the hundreds place,divide the remainder by 10 and get the tens place,and the final remainder is the ones place. This isn't so hard after all!Heres the program that follows the steps above to fill the buffer with D1's ASCII value. main: lea buffer,a0 ;pointer to the buffer move #1234,d1 ;number to convert jsr deci_4 ;test subroutine illegal ;room for breakpoint deci_4: ;subroutine-four digit numbers divu #1000,d1 ;divide by 1000 bsr digit ;evaluate result-move remainder divu #100,d1 ;divide by 100 bsr digit ;evaluate result and move divu #10,d1 ;divide by 10 bsr digit ;evaluate result-move remainder ;evaluate the remainder directly digit: add #$30,d1 ;convert result into ASCII move.b d1,(a0)+ ;move it into buffer clr d1 ;erase lower word swap d1 ;move the remainder down rts ;return buffer:blk.b 5,0 ;reserve bytes for result end To test this subroutine,use AssemPro to assemble the routine,save the program and load it into the debugger.Next set a breakpoint at the illegal instruction.To set the breakpoint in AssemPro select the correct address with the mouse and press the right-Amiga-B keys.This breakpoint stops the program.Start the program and when it is finished redisplay the output by selecting"Parameter-Display -HEX-dump"so you can examine the ASCII values now in the buffer. You use a little trick in this program that is typical for machine language programming.After calling"digit"three times from the sub- routine"deci_4",you go right into the"digit"subroutine.You don't use a BSR or JSR command.Once the processor hits the RTS command, it returns to the main program,not the "deci_4"subroutine.Doing this,you save a fourth "bsr digit"command and an "rts"command for the "deci_4"routine. Try the program out.Make sure that you use values that are smaller than 9999,because otherwise strange things can happen. Now let's reverse what you've been doing and convert strings into binary numbers. 4.3.3.Converting ASCII To Hex. ----------------------------- In a string,each hex digit represents a half byte.You just need to write a program that exactly reverses what the hex conversion program did. You have two choices 1. The number of hex digits is known in advance 2. The number is unknown The first is easier to program,but has the disadvantage that if, you assume the strings are four digits in length and want to enter the value 1,you must enter 0001.That is rather awkward,so you'll use the second method. Let's convert a single digit first.You'll pass a pointer to this digit in address register A0.You want the binary value to come back in data register D0. The program looks like this: move.l #string,a0 ;this example jsr nibblein ;test routine nop ;set breakpoint here nibblein: ;*convert the nibble from (A0) clr.l d0 ;erase D0 move.b (a0)+,d0 ;get digit,increment A0 sub #'A',d0 ;subtract $41 bcc ischar ;no problem:in the range A-F add #7,d0 ;else correct value ischar: add #10,d0 ;correct value rts string:dc.b 'B',0 ;character to convert end To test this subroutine,use AssemPro to assemble the routine,save the program and load it into the debugger.Next set a breakpoint at the first NOP,to set the breakpoint in AssemPro select the correct address with the mouse and press the right-Amiga-B keys.Start the program and watch the contents of D0. Let's see how the program works.A0 points to a memory location that contains the character "B"that is represented by the ASCII value $42.This number is loaded into D0 right after this register is erased. After subtracting $41,you end up with the value $1.Now you're almost done.Before returning to the main program,you add 10 to get the correct value 11,$B. If the buffer has a digit in it,the subtraction causes the register to become negative.The C flag is set.Let's take the digit 5 as an example. The ASCII value of 5 is $35.After subtracting $41,you end up with -12 and the C flag is set.In this case,you won't branch with the BCC command.Instead you'll add 7 to get -5.Then 10 is added,and you end up with 5.Done! The routine as a disadvantage.If an illegal character is given,one that doesn't represent a hex digit,you'll get some nonsense result Let's ignore error checking for the moment though. Let's go on to multi-digit hex numbers.The first digit that you convert has the highest value and thus represents the highest nibble.To allow for this and to allow for an arbitrarily long number(actually not arbitrarily long,the number should fit in a long word-so it can only be eight digits long),you'll use a trick. Take a look at the whole program.It handles the calculations and puts the result in D1.It assumes that A0 is a pointer to a string and that this string is ended by null byte. hexin: ;converting a hex number clr.l d1 ;first erase D1 move.l #string,a0 ;address of the string in A0 jsr hexinloop ;test subroutine nop ;set breakpoint here hexinloop: tst.b (a0) ;test digit beq hexinok ;zero,then done bsr nibblein ;convert digit lsl.l #4,d1 ;shift result or.b d0,d1 ;insert nibble bra hexinloop ;and continue hexinok: rts nibblein: clr.l d0 ;convert the nibble from (A0) move.b (a0)+,d0 ;get digit,increment A0 sub #'A',d0 ;subtract $41 bcc ischar ;no problem:in range A-F add #7,d0 ;else correct value ischar: add #10,d0 ;correct value rts string:DC.B "56789ABC',00 ;eight digit string,null byte ;to be converted end To test this subroutine,use AssemPro to assemble the routine,save the program and load it into the debugger.Next set a breakpoint at the NOP,to set the breakpoint in AssemPro select the correct address with the mouse and press the right-Amiga-B keys.Start the program and watch the contents of D1,the hex value is placed in this register. The trick is to shift left four times,to shift one nibble.In this way,the place of the last digit is incremented by one and there is room for the nibble that comes back from the "nibblein"routine.The program uses the TST.B instruction to check for the null byte at the end of the string,when it encounters the null byte the program ends.The result is in the D1 long word already! To do some error checking,you need to make some changes in the program.You'll do this right after you come back from the"nibblin" routine with the value of the current character. If the value in D0 is bigger than $F,there is an error.You can detect this in several ways.You chose the simplest one-you'll use CMP #$10,D0 to compare D0 with $10.If it smaller,then the C flag is set(since CMP uses subtraction)and everything is fine.If C is zero,there is an error. You can use this trick to skip the test for a null byte,since its an invalid character as well.The program looks like this: ;(4_3_3C) hex-conv2 optional disk name hexin: ;converting a hex number clr.l d1 ;first erase D1 move.l #string,a0 ;address of string in A0 jsr hexinloop ;test subroutine nop ;set breakpoint here hexinloop: bsr nibblein ;convert digit cmp $10,d0 ;test if good bcc hexinok ;no,then done lsl.l #4,d1 ;shift result or.b d0,d1 ;insert nibble bra hexinloop ;and continue hexinok: rts nibblein: ;convert the nibble from (A0) clr.l d0 ;erase D0 move.b (a0)+,d0 ;get digit,increment A0 sub #'A',d0 ;subtract $41 bcc ischar ;no problem:in the range A-F add #7,d0 ;else correct value ischar: add #10,d0 ;correct value rts string:DC.B "56789ABC',00 ;8 digit string ending with a ;null byte to be converted end To test this subroutine,use AssemPro to assemble the routine,save the program and load it into the debugger.Next set a breakpoint at the NOP,to set the breakpoint in AssemPro select the correct address with the mouse and press the right-Amiga-B keys.Start the program and watch the contents of D1,the hex value is placed in this register. This is the method for converting hex to binary.If you convert decimal to binary,the conversion is not much harder. 4.3.4.Converting ASCII To Decimal. --------------------------------- You can use a very similar method to the one used above.Since you are not sure how many digits there are,you'll use a similar method for putting digits of a number in the next place up.You can't do this with shifting,but you can multiply by 10 and add the value of the digit. Heres the program for converting decimal numbers. decin: ;converting a decimal number clr.l d1 ;first erase D1 move.l #string,a0 ;the string to convert jsr decinloop ;test subroutine nop ;breakpoint here decinloop: bsr digitin ;convert digit cmp #10,d0 ;test,if valid bcc decinok ;no,then done mulu #10,d1 ;shift result add d0,d1 ;insert nibble bra decinloop ;and continue decinok: rts ;end of conversion digitin: ;converting the nibble from (A0) clr.l d0 ;erase D0 move.b (a0)+,d0 ;get digit,increment A0 sub #'0',d0 ;subtract $30 rts string:dc.b '123456' ;ASCII decimal string to convert end To test this subroutine,use AssemPro to assemble the routine,save the program and load it into the debugger.Next set a breakpoint at the NOP,to set the breakpoint in AssemPro select the correct address with the mouse and press thr right-Amiga-B keys.Select "Parameter-Output-numbers-Decimal"so the registers are displayed as decimal numbers.Then start the program and watch the contents of D1,the decimal value is placed in this register. This program can ONLY convert numbers upto 655350,although the hex conversion routine can go higher.Thats because the MULU command can only multiply 16-bit words.The last multiplication that can be done correctly is $FFFF*10--65535*10,which gives us the value 655350.Normally this is a large enough range,so you won't complicate the program further. NOW LOAD PART 2 end.