Amiga Machine Code Letter III - Branching
In this post we are going to look at branching as it is explained in Letter III.
At it’s most basic level, branching is a conditional jump statement. The Amiga hardware offers opcodes for several conditional jumps. But first we will have to take a look at the 68K status register.
Letter III mentions that we are not going to read the status register directly. Rather, it’s used indirectly by the branching instructions.
I have superimposed the condition codes C, V, Z, N, and X on the image.
Letter III introduces a couple of the branching codes, and they can be found in the manual The 68000’s Instruction Set, as instruction Bcc, which is short for Branch on condition cc.
Here are some of the branching instructions. The third column show what status register flags are set if the cc condition is satisfied.
E.g. if we use the BNE instruction, then the jump is only performed if the zero flag is set to 1 in the status register.
I won’t go through the trouble of showing the opcodes, but just refer to the excellent one pager of the 68K opcodes.
We’ll return to the branching later. First we must look at the CMP instruction, which also can be found in the 68K manual. It subtracts the source operand from the destination operand and sets the condition codes N, Z, V, and Z accordingly.
Here’s a little program that investicates CMP.
; file mc0301cmp.S
first:
move.l #2,d0 ; put 2 into d0
cmp.l #0,d0 ; does 0 compare with the value in d0?
cmp.l #2,d0 ; does 2 compare with the value in d0?
cmp.l #4,d0 ; does 4 compare with the value in d0?
rts ; return from subroutine
Assemble the program, and write it to disk with the “wo” command in Seka. Then exit Seka with “!” and go into CLI. Press shift+F12 to enter the WinUAE debugger and write
fp "mc0301cmp"
This will trigger a breakpoint, when the process is run. Back in the CLI enter “mc0301cmp”. The debugger will trigger and you can step through the program using “t”. When done use “g” to continue execution.
Keep an eye on the condition codes in the green box.
Here’s the condition codes for the three calls to cmp, when d0 contains 2.
cmp.l #0,d0 ; X:0, N: 0, Z: 0, V: 0, C: 0
cmp.l #2,d0 ; X:0, N: 0, Z: 1, V: 0, C: 0
cmp.l #4,d0 ; X:0, N: 1, Z: 0, V: 0, C: 1
When we subtract the number 2 from the number in d0, which also happens to be 2, then we get zero, and that’s exactly what the condition flags says in the second line.
Adressing modes
Letter III introduces two new addressing modes: Address and Address with postincrement.
These addressing modes acts as pointers. It translates to “use the address stored in the address register”. I won’t go into details, because the letter explains this very well.
If postincrement is used, then one is added to the content of the address register, after the value is read. Postincrement also considers the width of data beeing moved. So if a byte is moved, the pointer value is incremented with 1, and if it’s a word or long, it’s incremented with 2 and 4.
Block and Declare
The next instructions are blk and dc. None of these have equivalent 68K opcodes, and are not machine code instructions. They are, however, instructions for the Seka assembler.
The dc can declare bytes, words, and longs that is hardcoding values into memory. We use this instruction when writting copper lists.
The blk instruction, allocates a block of memory and can also initialize it to a given value. Take a look in Letter III, where this is explained really well.
Binding it all together
Letter III uses the following program to demonstrate the new addressing modes and branching techniques. The first version of the program works, but does not utilize the new techniques.
first:
move.l #16,d0 ; use d0 as a counter
move.l #$00,a0 ; let a0 point to a source address
lea.l buffer,a1 ; allocate a destination buffer
loop:
move.b (a0),d1 ; copy the source into d1
add.l #1,a0 ; increment source address
move.b d1,(a1) ; move data from source into destination bufffer
add.l #1,a1 ; increment destination address
sub.l #1,d0 ; subtract one from the counter
cmp.l #0,d0 ; have the counter reached zero?
bne loop ; if not continue to loop.
rts
buffer:
blk.b 16,0 ; allocate 16 bytes and intialize them to zero
The program simply copies a 16 bytes of memory, starting at address $000000 into a buffer of the same length.
The letter mentions that the following line is redundant.
cmp.l #0,d0 ; have the counter reached zero?
The “trick” is that when we subtract one from the counter, then we will eventually reach zero. When that happens, the condition code Z is set to 1 in the status register. This zero flag is exactly what bne checks on, as we saw in the top of this post. Hence, we do not need to make the comparison.
Here’s the revised and more compact program.
first:
move.l #16,d0
move.l #$00,a0
lea.l buffer,a1
loop:
move.b (a0)+,(a1)+
sub.l #1,d0
bne loop
rts
buffer:
blk.b 16,0
I won’t go into how this transformation took place, since it’s all explained in the letter. One thing is certain though, those 68K opcodes are surely very well put together. I like it!
The DBRA instruction
Decrementing a loop counter is such an essential part of a loop, that it has it’s own set of machine code instructions, known as DBcc.
This family of instructions, combines the usual decrement counter, test, and branch instructions of a loop construct, into one machine code instruction. The DBcc opcode is also found in the 68K opcode sheet.
The instruction consists of one opcode and two operands; the loop counter to be decremented and the label to jump to, if the loop counter is not -1.
There are 14 branch conditions supported by DBcc. We are going to use a variant called DBRA (decrement and branch back), it’s similar to DBF.
Using the 68K opcode sheet, we can find DBF by first locating DBcc and then use the condition table to find F. This means that we should continue the loop while the condition is false. The condition is that the counter should be equal to -1.
Let’s use DBRA in the above code, and notice that the counter is reduced from 16 to 15, because we now count down to -1.
first:
move.l #15,d0
move.l #$00,a0 ; could also written as: clr.l a0
lea.l buffer,a1
loop:
move.b (a0)+,(a1)+
dbra d0,loop
rts
buffer:
blk.b 16,0
The code can be made a little more readable by using the instruction clr.l to clear a0.
At the end of Letter III, there is a small program that demonstrate bitplanes, and at the start of Letter IV, there is a program that demonstrates branching. I feel that these programs should have been given in the reverse order.
So, here’s the program from Letter IV.
start: ; mc0401
clr.l d0
move.l #$04,d1
lea.l table,a0
loop01:
add.w (a0)+,d0
dbra d1,loop01
lea.l result,a0
move.l d0,(a0)
rts
result:
blk.l 1,0
table:
dc.w 2,4,6,8,10
The program sums the numbers in the table and stores them in the rusult. It’s a nice use of labels, that works well with the Seka assembler.
Assemble the progam using “a” and in the option use “vh” to get the scrolling to pause in the output. Use space to continue. The program can be started by using “jstart” and the result can be viewed by using “qresult”.
We have used q before, to see the hex dump from a cetain address. It also works with labels, since they are just addresses.
The next letter will go into more details about bitplanes.
Previous post: Amiga Machine Code Letter III - Copper Revisited
Next post: Amiga Machine Code Letter IV.