Amiga Machine Language (Chapter 4)
Innen: amigaspirit.hu - pegasos.hu Wiki
Ugrás a navigációhozUgrás a kereséshez
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.