Loops and dbra
A while loop written out of a compare and two branches, and then dbra, the one instruction that counts and jumps at once. Its off by one, its word sized counter and the db<cc> forms that leave the loop early.
The overview of this topic is in Assembly basics. The same topic in MIPS, RISC-V, Z80.
A loop is a comparison, a branch out of it and a jump backwards, which we can now write. The M68K also has one instruction that does the counting and the jumping together, and it is the one you will actually use.
The loop written out
Adding up the numbers from 1 to 10, in C, then flattened, then assembled:
int sum = 0;
for (int i = 1; i <= 10; i++) sum += i;
d0 comes out at 00000037, which is 55, and d1 at 11, one past the last value it used. Four of
those seven instructions are the loop machinery and one is the work.
clr.l d0 is move.l #0, d0 written shorter, and addq.l #1, d1 is add.l #1, d1 in a shorter
encoding, which is what addq is for: adding a number from 1 to 8, which is what a loop counter
does.
dbra does the counting
dbra dn, label subtracts 1 from the low word of dn and branches to the label unless the result is
-1. One instruction replaces the subq and the bne, and the loop becomes:
d0 is 55 again. Two things about that counter:
It runs one more time than the number you put in it. dbra stops at -1, not at 0, so a counter
of 9 gives ten passes: 9, 8, 7, down to 0, and then the pass that takes it to -1 and falls through.
Write move.w #count-1, d1 when you know how many times you want, and let the assembler do the
subtraction.
It is a word. dbra reads and writes only the low 16 bits of the register, so the most a single
dbra loop can run is 65536 times, and whatever is in the high word is left there and ignored.
The loop runs four times, so d0 comes out at 4, and d1 ends at FFFFFFFF: the low word walked
down to FFFF, which is the word -1, and the FFFF above it was never touched. This is why the
counter of a dbra loop is set with move.w and read as a word.
After the loop, the counter is $FFFF and not zero, which catches people who then want to reuse the
register.
db<cc> leaves the loop early
dbra is the plain member of a family. db<cc> dn, label takes a condition, and it goes round again
only when the condition is false and the counter has not run out. So dbeq means "keep going
until something is equal or we run out of elements", which is a search.
The 30 is the third element, so the loop goes round three times and stops. d1 comes out at 3,
a0 at 0000200C, four bytes past the element that matched, and d3 at 000000FF.
d3 is $FF because db<cc> writes no flags at all: the Z that seq reads is still the one the
last cmp left. That is how you tell the two ways out of the loop apart, since dbeq falls through
both when it found something and when it ran out. The other way to tell is the counter, which is
$FFFF only when the loop ran out.
dbne is the same idea for "keep going while they are equal", and every condition of the branch
family has a db version.
Nested loops
Nothing new: an inner loop is a loop between two lines of the outer one, with its own counter in its own register, reset at the top of every outer pass.
d0 comes out at 12, which is 3 times 4. The move.w #3, d2 has to be inside the outer loop:
put it above outer: and the inner counter is $FFFF on the second pass, which makes the inner loop
run 65536 times. Try moving that line up one and pressing Run to watch it happen.
The Playground stops a program after two million instructions and says so, with "Execution limit of
2000000 instructions reached (maybe an infinite loop?)". That message is what a loop with a broken
counter looks like, and it is the reason dbra reads its counter as a word rather than a long: a
long counter that starts wrong runs for four billion passes.
Your turn
Add up the numbers from 1 to 10 with a dbra loop and leave 55 in d0. The counter belongs in a
data register of your choice, and d0 starts at 0 in the test.
Show solution
The second one starts d0 at 64 and wants to know how many times it can be halved before it reaches
- Leave that count in
d1, which for 64 is 6.