COURS 6.TXT

From Atari Wiki
Jump to navigation Jump to search
   

   ******************************************************************
   *                                                                *
   *             68000 ASSEMBLER COURSE ON ATARI ST                 *
   *                                                                *
   *                 by The Ferocious Rabbit (from 44E)             *
   *                                                                *
   *                         Lesson number 6                        *
   *                                                                *
   ******************************************************************

   THE STACK    We have already used the concept of a 'tube' when it
   comes to memory. We can store different things in it, and
   if we remember the address, we can come back later to
   that place to retrieve what we left there.

   Try with this small program:

          MOVE.L     #$12345678,D0
          MOVE.L     D0,$493E0
          MOVE.L     #0,D0
          MOVE.L     $493E0,D0

   Assemble and then use MONST to step through. D0 is
   initially filled with $12345678, then the contents of D0 are transferred
   to address $493E0. Note that there is no # in front of $493E0,
   to indicate that it is indeed an address. Once this line is
   executed, activate window 3 ([Alternate+3]) and set the start
   of it to address $493E0 ([Alternate+A] then type 493E0)
   You can clearly see 12345678 at this location in the 'tube'. If I have
   chosen this address it's because it's located 300 Kilobytes from the
   beginning of memory. It is therefore accessible even on a 520, and
   it is far enough away not to be in GENST
   or MONST. Indeed there is only one memory 'tube'! So we are
   writing into memory while some of it
   is occupied by GENST and MONST! Writing within the areas oc-
   cupied by these programs is possible, which will very likely cause
   some crashes!

   Let’s continue to step through; we set D0 to 0 then the contents of
   address $493E0 (without #) are moved back into D0.

   The stack is a part of this tube, but that we will manage
   in a slightly different way. Indeed, instead of placing the
   data in the tube and noting their addresses, this time we will stack them and to retrieve them, unstack them. The advantage
   is the time saved (no need to wonder at which
   address we stored the data) and a space saving (if it's
   for temporarily storing data, no need to keep a
   portion of 'tube' just for that).

   On the other hand, the disadvantage is that management must be ri-
   gorous. Let's imagine that I stack one first number then 10 others
   on top of it. Next, I unstack, but, mistake on my part, I only
   unstack 9 numbers! When I unstack once more,
   thinking I will find the first stacked number, I will actually re-
   cover the first of the series of 10.

   We conclude 2 things from this: first, the stack is a sim-
   ple way to save data, but then it is a
   potential source of annoyance, such that some programmers he-
   sitate to use it. It's usually due to a lack of ri-
   gor, which I hope will not happen to you.

   Another note: the last element placed on the stack will al-
   ways be the first to come out. This is the same principle as that
   of a stack of plates: Look at your home, there's certainly
   a huge stack of plates, but simply because storing
   after washing is done by stacking and setting the table
   is done by unstacking, you are actually always eating in the same
   plates... (hence the interest of doing the dishes well!)

   This stack structure is called LIFO structure, that is
   Last In First Out. This structure is different from another structure frequently
   encountered in computing, that of the queue, also called struc-
   ture FIFO (First In First Out), the queue being similar to a
   waiting line in front of a counter: the first in line will be the
   first to leave.

   But concretely, what is the stack for? We will see with
   an example. Type the following program:



          MOVE.L     #$12345678,D0
          MOVE.L     #$BD88,D1
          MOVE.L     #$BD88,A0
          BSR        ADD
          MOVE.L     #0,D0
          MOVE.L     D2,D0


ADD       MOVE.L     #$11112222,D2
          ADD.L      D1,D2
          RTS

   First note: this program differs from the previous ones in that
   we are using a label, a label called ADD.

   This word, 'ADD', must be found all the way to the left, against the edge of
   the editor window. It is not something to place IN
   the tube but rather a mark BESIDE the tube.

   Another note, assembler listings, unlike lis-
   tings in other languages, are quite free in terms of presen-
   tation. It is quite possible to skip lines, which
   is done here to separate the 2 parts. Assembler sources
   are often very long, and even if it wastes some
   lines, spacing the modules allows to find one's way easier.

   Assemble and then debug. Step through with Control+Z. The
   first 3 lines are familiar to us but not the fourth.
   This line reads BRANCH SUB ROUTINE ADD, meaning bran-
   ching to a subroutine called ADD. To specify which su-
   broutine one wants to go to, its label is specified. Here in
   this case it's ADD but the name doesn't matter much. It is all
   quite possible to put fairly long names and I can only
   advise you to avoid names like X Y,
   Z or AX1 etc... which are still less explicit than
   IMAGE_START, NEW_PALETTE or END_GAME.

   Now be very attentive: upon reading this instruction
   many things will happen. The command therefore asks the 68000
   to continue reading its instructions in a sub-
   program whose start is located in the tube, opposite the la-
   bel ADD. However, this is indeed a subroutine.
   This means that once completed, the 68000 will go back up to execute
   the following line of BSR ADD, namely MOVE.L #0,D0. Ques-
   tion: how will the 68000 know where to go back to? Indeed the nature
   of a subroutine is to be able to be called multiple times and from
   several different places and to be able to return
   each time to the very place that called it.

   Well the 68000 will use the stack to note this place
   of return. This stack certainly has an address, where is it
   noted? In A7. Yes, this somewhat special register corresponds to the
   stack.

   But A7' then? Well, it's also a stack, but reserved for the
   Supervisor mode. So if we were running 2
   programs at the same time, one in user mode and the other in supervisor,
   each would have their own stack.

   Before executing the line BSR ADD, observe carefully the
   address registers and the data registers.

   We have seen that the registers, whether they are for data or
   address, can contain numbers coded over 32 bits. We
   have also seen that there are 2 types of numbers for the machine:
   those inside the 'tube' and those outside, AGAINST
   this tube, and indicating a sort of distance
   from the start of that tube.

   This second type of number is called an address. However, it is quite
   possible to store a number representing an address in a
   data register (D0-D7). Let's imagine now that we needed
   to store a player's score in the game we are programming. This
   score will for example be placed in memory (in the 'tube') at
   address $80792.

   But what will happen if we transfer this address to use with A1 for example? well A1 will take the value
   $80792. That's nice, but that's not what interests us.
   What we want to modify, check etc... it's what is
   IN the tube at this address.

   Well our debugger anticipates a bit this request. Indeed,
   considering that the numbers stored in D0-D7 or A0-A6 can
   represent address values, it indicates next to the regis-
   ters, what is in the tube at the address indicated in the
   register.

   For the data registers, MONST displays to their
   right the value of 8 bytes found in the tube at the address
   indicated in the register. For address registers, it is
   10 bytes which are indicated. You have certainly noticed that in
   front of D0 register (which should contain $12345678 if you have done
   the program advance correctly), MONST has only displayed
   stars. This is normal because the number $12345678 corresponds to an em-
   placement that would only be accessible with 305 megabytes of
   memory!!! MONST therefore indicates that it cannot reach this
   memory area by displaying stars.

   Now look at D1 and A0. The numbers on their right
   show the same thing, which is normal since both registers
   D1 and A0 are filled with the same number. We say they point to
   address $BD88. Let's check the memory just to verify
   the display. Activate window 3 with Alternate+3. This one dis-
   plays the contents of memory, but we are far from $BD88!

   Let's ask that this address be the one at the top of window
   3, with Alternate+A. We type this address (BD88). Window 3 is
   re-displayed with $BD88 at the top. On the right column
   we see the contents of memory, which we had already a
   peak with the display to the right of D1 and A0. Is it clear?

   Reactivate window 1 (alternate+1). Normally the small ar-
   row should always be in front of BSR ADD. Note the
   number in register A7 (thus the address of the
   Stack) and watch carefully the numbers on the right of this register, while
   making Control+Z.

   The numbers have changed! First the A7 register no longer contains the
   same number. The one currently there is indeed
   smaller than the previous one. Note that this difference is 4.
   The stack address has therefore been decremented by 4. Plus nu-
   mbers have been placed on the stack (they are seen to the right of the
   A7 register). Now, look at the number that is to the left of
   the instruction MOVE.L #0,D0 in our program, that is
   the address where the 68000 should return once the subroutine
   is complete: it is indeed this number that has been placed on the stack. So
   there is stacking of the return address, which also explains
   the change in the stack address by 4. Indeed an address is
   coded on 4 bytes!

   Note: since we are talking about a stack, it is more common
   to say that the data are placed on the stack and less often in the
   stack.

   Continue our program with Control+Z. We are now in
   the subroutine. Stop just before RTS. This ins-
   truction will make us "go back up". It reads RETURN FROM SUB
   ROUTINE.

   Observe A7 (its value but also the contents of the 'tube' at this
   address) and take a step (Control+Z). The return address has been
   unstacked, A7 has resumed its old address and we now point to MOVE.L #0,D0.

   Quit this program with Control+C, erase it and type this one.

          MOVE.L     #$12345678,D0
          MOVE.L     #$AAAAAAAA,D1
          BSR        ADD
          MOVE.W     D2,D3

ADD       MOVE.W     #$EEEE,D1
          MOVE.W     #$1111,D2
          ADD.W      D1,D2
          RTS

   Assemble and then debug. Step through: D0 takes the value
   $12345678 D1 the value AAAAAAAA, then we depart towards the subrou-
   tine ADD.

   Unfortunately this one uses D1 and on return we note
   that this one no longer contains AAAAAAAA. Indeed jumping to
   a subroutine only saves the return address, and in assembly local variables and other languages evol-
   ved features do not exist! It is therefore up to us to save the regis-
   ters, and this is what we will do now.

   Note: the A7 register containing the address of the stack
   pointer (this address varying of course with the stacking and unstacking),
   we can consider this address as a finger permanently pointing
   towards the top of the stack. For this reason the A7 register
   is also called the stack pointer. As always we will use
   the English vocabulary, and we will say Stack Pointer, abbreviated as
   SP. For this reason and because it is customary,
   we will henceforth replace A7 with SP (which is not read "ess-pee" but
   STACK POINTER!!!).

   Suppose we wanted to save D0 at the entry to the su-
   broutine:

                      We must not forget to retrieve it at the
   end! Let’s move the contents of D0 to the stack. Let's try
   MOVE.L D0,SP and think about it: This will put the contents of D0
   into A7, unfortunately that's not what we want to do.
   Indeed we want to put the contents of D0 INSIDE the tube, at
   the point indicated by A7 (so SP).

   This will be done with MOVE.L D0,(SP), the parentheses indicating
   that the source of the operation is the inside of the tube.

   Erase the current program and type the following one.

          MOVE.L     #$12345678,D0
          MOVE.L     D0,(A0)
          MOVE.W     D0,(A1)

   Assemble and then debug as usual. D0 takes the value
   $12345678, then D0 is transferred in its entirety (due to the .L
   indicating that the operation is on a long word) to the address
   noted in A0, then the lower half of D0 is transferred
   into the tube at the address noted in A1. To verify this, you
   can activate window 3 and ask to place the address noted
   in A0 at the top of this window, and you will see that indeed
   the value of D0 is in the 'tube'.

   We are therefore going to use this type of transfer to save D0

   But let's think a little. MOVE.L D0,(SP) will indeed place
   the contents of the long word D0 in the tube, but if we want to pla-
   ce another value on the stack, it is going to overwrite our first
   value because with MOVE.L D0,(SP) the address indicated by SP
   (thus A7) will not be modified, which should be the case.

   We are therefore going to perform the transfer differently (in fact we
   are going to further improve our vocabulary, since we are going
   to talk about addressing type or mode now).

   We are going to do
          MOVE.L     D0,-(SP)

   This is the addressing mode with pre-decrement. Behind this vo-
   cabulary hides a whole series of events. In a
   single instruction, we decrease the address of the stack pointer by
   4 (since in our example we wanted to transfer a long word
   therefore 4 bytes), and we place the long word D0 in memory at this address.

   To recover D0, that is to say unstack, we will have to:

          MOVE.L     (SP)+,D0

   As we decremented the stack pointer to then de-
   posit D0 at this address, we then recover D0 without for-
   getting to modify the stack pointer in the other direction, to
   make it find its old position again. Note that in this case, and if we
   limit ourselves to think very summarily, it would have been possible to save D0 with MOVE.L D0,(SP) and to reco-
   ver it with MOVE.L (SP),D0. It's without counting that the stack is a common reservoir for many things. It is therefore bet-
   ter to play each time the game of a correct stacking and a re-
   flection but also an unstacking 'sticking' perfectly with the pre-
   vious stacking.

   Let's verify all this with the following example:
          MOVE.L     #$12345678,D0 value in D0
          MOVE.W     #$AAAA,D1     value in D1
          MOVE.L     D0,-(SP)      saves D0.L on the stack
          MOVE.W     D1,-(SP)      same with D1 but in word
          MOVE.L     #0,D0         sets D0 to 0
          MOVE.W     #0,D1         and D1 too
          MOVE.W     (SP)+,D1      retrieves D1 (word)
          MOVE.L     (SP)+,D0      then D0

   Assemble and go through this program step by step under MONST.
   Note several things: first of all comments have been
   added to the source. It is enough that they are separated from the ope-
   rands for the assembler to know that it's about comments.
   If you want to type a line of comments (that is to say that
   on it there will be nothing else than this comment), you
   must precede it with an asterisk or a semicolon.

   Second thing, we had stacked D0 then D1, then we un-
   stacked D1 then D0. Indeed, we must be very careful with the order
   and the sizes of what we stack, in order to unstack
   the same sizes, in the reverse order of stacking.

   Here is one last example.
          MOVE.L     #$12345678,D0
          BSR        ADD       jump to subroutine
          MOVE.L     D0,D1     transfer

ADD       MOVE.L     D0,-(SP)  saves d0.l on the stack
          MOVE.W     #8,D0
          MOVE.W     #4,D1
          ADD.W      D0,D1
          MOVE.L     (SP)+,D0
          RTS

Assemble and then follow the execution under MONST, paying close attention
to the process. You will see that the BSR saves the return address
on the stack, then D0 is placed on top of it to be later
retrieved. Next, the return address is retrieved and the
program returns.

Now, let's provoke a small but
fatal error for our program. Instead of retrieving D0 with a
MOVE.L (SP)+,D0, let's make a typo and type
MOVE.W (SP)+,D0 instead.

Assemble and follow step by step. At the moment of saving D0,
4 bytes are indeed placed on the stack, altering
it accordingly. Unfortunately, the retrieval will only re-modify
the stack by 2 bytes. When the RTS instruction
tries to retrieve the return address, the stack pointer
will be off by 2 bytes from where the return address actually
is, and the return will occur to a wrong address. In conclusion: caution and precision are needed!

We have just seen that the stack is used by the 68000
for certain instructions and is very convenient for
saving.

It is also possible to use it for transmitting data,
which we will see to conclude this chapter.

Problem: Our main program uses registers A0 to A6
and D0 to D6. It will call a subroutine designed to add 2
numbers and return the result in D7. Therefore, we will need to use
2 registers, for example D0 and D1, to work in our
routine, and therefore save them at the entry of it.

Here is the beginning of the program.

      MOVE.L #$11111111,D0
      MOVE.L #$22222222,D1
      MOVE.L #$33333333,D2
      MOVE.L #$44444444,D3
      MOVE.L #$55555555,D4
      MOVE.L #$66666666,D5
      MOVE.L #$77777777,D6

The first 7 registers are filled with dummy values,
just to allow us to check for any changes.

Now we need to place the 2 numbers we want to add
somewhere they can be retrieved by the addition routine. Let's put these 2 numbers on the stack.

      MOVE.L #$12345678,-(SP)
      MOVE.L #$00023456,-(SP)
      BSR AJOUTE            and off we go!

Let's now write our subroutine, following the order of
work of the 68000.

What will we need in this routine?

We'll need D0 and D1, which will receive the stacked numbers and will
be used for calculation. We will also need an address
register. Indeed, when we unstack, we will
modify the stack pointer, but we just did a BSR and the
68000 has thus stacked the return address on the stack, and modifying
it will compromise the return! We will therefore copy
the stack address into A0, and use this copy.

Note: I've decided to use D0, D1, and A0, but any other
register would have been just as suitable.

Let's start by saving our 3 registers.

This could be done by:

      MOVE.L D0,-(SP)
      MOVE.L D1,-(SP)
      MOVE.L A0,-(SP)

Note: remember that this reads move long!

But the 68000 has a very useful instruction in such a
case, which allows transferring several registers at once.

We will therefore do:

      MOVEM.L D0-D1/A0,-(SP)

Which reads: move multiple registers.

If we needed to transfer from D0 to D5 we would have done:

      MOVEM.L D0-D5,-(SP)

and, to transfer all the registers at once:

      MOVEM.L D0-D7/a0-A6,-(SP)        Got it?

Now let's save the stack address in A0. As it's
the address we need to save and not the content, it's done
by:
      MOVE.L A7,A0      transfer from register A7 to A0

Now we will retrieve the 2 numbers we had stacked
before the BSR instruction.

Imagine what happened. (By the way, I HIGHLY
recommend you to use paper and pencil for this. Don't hesitate
to write on these courses. They are yours and I won't ask for them back!

Making a small drawing or placing objects on your desk
to help you understand is an excellent idea. Often, memory manipulations tend to become abstract, and a little drawing clears things up!)

We moved the STACK POINTER by 4 bytes, then we deposited $12345678 in it. But in which direction did we move this SP?

Towards the start of memory, towards address 0 of our tube since
we did -(SP). The stack pointer therefore moves up along
the tube. We then did the same thing again to deposit $23456. Then BSR, so the same thing but automatically performed by the 68000 to deposit the return address (4 bytes).

Is that all? No, because once in the subroutine, we
deposited the registers D0, D1, and A0 on the stack. The transfer being
performed in the long word format (MOVEM.L), we transferred
3 times 4 bytes, which makes 12 bytes.
 
Our copy of A7, which is in A0, therefore does not point to our 2 numbers but much further away. The number we placed second on the stack is therefore 16 bytes towards the start of the tube (do the calculation: 1BSR, + 12 bytes of backup makes 16 bytes) and the number placed first on the stack follows its buddy and is therefore 20 bytes from here, always by virtue of the stack principle: the last entered, is the first out.

We can therefore say that $23456 is at A0 offset by 16 and that $12345678 is at A0 offset by 20.

To retrieve these 2 numbers several actions are possible:

1) add 16 to the address of A0 then retrieve.

An address addition is done by ADDA (add address).

So we do
         ADDA.L     #16,A0

A0 now points to $23456, so let's retrieve this number and take advantage of the addressing mode to advance the address indicated in A0 and thus be immediately ready to retrieve the other number.
         MOVE.L     (A0)+,D0
The address having been increased we can therefore retrieve the continuation:
         MOVE.L     (A0)+,D1

2) Another method, using a different addressing mode:

The previous method has a disadvantage: after the ADDA, A0 is modified and if we wanted to keep this address, we would have had to save it.

Or we could have added the offset to A0, retrieve the data and then remove the offset from A0 so that it regains its initial state.

Another method, therefore, indicate in the addressing the offset to apply. This is done by:

         MOVE.L     16(A0),D0
         MOVE.L     20(A0),D1

This allows pointing to the 16th byte from the address given by A0 and then pointing to the 20th relative to A0.
In both cases, A0 is not modified.

Here is the complete listing of this example.

         MOVE.L     #$11111111,D0         initialization of D0
         MOVE.L     #$22222222,D1         same
         MOVE.L     #$33333333,D2         same
         MOVE.L     #$44444444,D3         same
         MOVE.L     #$55555555,D4         same
         MOVE.L     #$66666666,D5         same
         MOVE.L     #$77777777,D6         same
         MOVE.L     #$12345678,-(SP)      passing number 1 into the stack
         MOVE.L     #$00023456,-(SP)      passing number 2 into the stack
         BSR        ADD                   and off we go!
         MOVE.L     D7,D0                 transferring the result to see..


* our subroutine
ADD        MOVEM.L    D0-D1/A0,-(SP)        saving
           MOVE.L     A7,A0                 copy of SP in A0
           MOVE.L     16(A0),D0             retrieves 23456 and puts it in D0
           MOVE.L     20(A0),D1             retrieves 12345678 in D1
           ADD.L      D0,D1                 addition
           MOVE.L     D1,D7                 transferring the result
           MOVEM.L    (SP)+,D0-D1/A0        recovery
           RTS                              and return

         * Note: this program not having a 'normal' end,
         when you* get to the return of the subroutine
         that is after the line" MOVE.L D7,D0 ", exit
         it with Control+C, Assemble and follow ALL the
         proceedings well.

Of course, it would have been possible to do this all differently.
For example, we could have avoided working with A0. Indeed
16(A0) and 20(A0) not modifying A0, it would have been simpler to
do 16(A7) and 20(A7) instead of first copying A7 into A0. Similarly, it would have been possible to transfer $23456 directly to D7
and $12345678 to D1 then do ADD.L D1,D7 to avoid saving
D0 (which would have been unused), and the transfer D1
to D7 which would then not have had to happen. Likewise, we could have returned the result through the stack instead of doing it through
D7.

Many possible variants, isn't it?

To finish, a little exercise. Relaunch this little program and
analyze PERFECTLY EVERYTHING that happens there. Something is not
right! I help you by saying that it is of course about the stack.
Search and try to find out how to fix it.

The answer will be at the beginning of the next course but try to imagine
that it's your program and it's not working and search!!!

Well, the course on the stack ends here. It was a bit long but
I think, not too complicated. Reread it, because the stack is a tricky thing that we are going to use VERY abundantly in the next
course. If you have understood almost everything so far there is
still time to catch up and start over from the beginning, because
it is necessary to have PERFECTLY understood everything and not almost!

To cheer you up, I'll tell you that you are almost
halfway through the course...

Back to ASM_Tutorial