COURS 6.TXT
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