Skip to main content

MC 68000 ASSEMBLY LANGUAGE COURSE PART X by Mark van den Boer

Now  all instructions of the 68000 have been explained it's  time 
to  bring  this knowledge into practice.  This last part  of  the 
course  will  deal  with the subject  of  translating  high-level 
constructs to the equivalent assembler constructs. The C program-
ming  language will be used as the high-level language which  has 
to  be translated to assembler.  A note to those of you  who  are 
more familiar with Pascal or BASIC:  litte imagination is needed 
to deduct the similar constructs in Pascal and BASIC.
What now follows is a C-program which containing several commonly 
used data- and control structures.  The examples show you how  to 
translate these structures into assembler.
There  should  also be a file called M68000.DOC on your  ST  NEWS
disk.  This  file contains a quick-reference card containing  all 
instructions  and allowed addressing modes.  This reference  card 
has been made by Trustware,  Inc.  (an unregistered trademark  of 
Victor Langeveld).  I am very grateful to Victor for allowing  me 
to  include  this card,  since it's a rather tedious job  to  put 
together such a card.  One thing's for sure:  this card is one of 
the  better of its kind and it's the most compact reference  card 
for the 68000 I've ever seen.
/*
        A function. (Called a procedure or function in Pascal
        and a subroutine in BASIC)
        Note how parameters are passed in the assembly language
        translation.
        Als pay attention to how local variables are stored.
*/
int function (number, pointer)
int     number;
char    *pointer;
{
        register int    i, j;
        char    *c;

        i = number - 1;
        c = pointer;
        c = "new Queensryche album: Operation Mindcrime";
        /* Note how a string is stored */
        return i;          /* Note how a value is returned */
}

.text
function:
* offset of number = 8
* offset of pointer = 10
        LINK    A6,#-4
* save registers
        MOVEM.L D6-D7,-(sp)     * sp = A7
* i in D7
* j in D6
* offset of c = -4
* i = number - 1
        MOVE.W  8(A6),D7
        SUB.W   #1,D7
* c = pointer
        MOVE.L  10(A6),-4(A6)
* c = "new Queensryche album: Operation Mindcrime"
.data

L2:
        .dc.b 'new Queensryche album: Operation Mindcrime',0
        MOVE.L  #L2,-4(A6)
* D0 is used for resulting values from functions
* return i; D0 is always used for the result of a function
        MOVE.W  D7,D0
* restore registers
        MOVEM.L (sp)+,D6-D7
        UNLK    A6
* }
        RTS

* global variables
.bss
* int     i;
i:      .ds.w   1
* char    character
character:      .ds.b   1
* int     *i_pointer
i_pointer:      .ds.l   1

* int     i_array[10][5]
i_array:        .ds.w   10*5
* one struct is in fact 3 bytes long, but for every structure
* 4 bytes are reserved. This is because the 68000 can only
* address words at even addresses.
* struct { /* this is the equivalent of a record in PASCAL */
*         int     i;
*         char    c;
* } structure[5];
structure:      .ds.b   4*5

main()
{
        /* assignment of a constant to a variable */
        i = 9;

        /* assignment of a constant to a variable */
        character = 'c';

        /* assignment of a constant to a variable */
        i_pointer = &i;
        /* watch how indexing of array is done */
        /* integer is 2 bytes, so the address of
           array-element [3][4] is:
           (3 * 5 (the length of i_array[3]) + 4)
           * 2 (size of an integer) = 38.
           So the integer should be stored at i_array + 38.
        */
        i_array[3][4] = *i_pointer;

        /* Now the distance in bytes from the beginning of the
           array must be computed during program execution,
           in contrary to the previous example.
        */
        i_array[i][i - 1] = 2;

        /* Assignments to arrays of structures */
        structure[1].i = 3;
        structure[i].c = character;

        /* expression evaluation and assignment */
        i = i_array[0][0] * i_array[0][1] +
            i_array[0][2] / i_array[0][3];
        /* conditional statement */
        if (i < i_array[i][i]) i = 1;
        else i = 2;

        /* while loop */
        while (i <= 10) i++;
        /* continue and break statements */
        while (i++ <= 10) {
                if (i != 4) continue;
                else break;
        }

        /* for loop */
        for (i = 4; i >= 0; i--) i_array[i][i] = i;

        /* do loop */
        do i++; while (i < 10 && i != 5);

        /* switch statement; watch the application of a
           jump-table. Pay special attention to how 'case 4'
           which must 'default' is solved.

        */
        switch (i) {
        case 0:
                i = 0;
                break;
        case 1:
                i = 5;
                break;
        case 2:
        case 3:
                i = 7;
                break;
        case 5:
                i = 1;
                break;
        default:
                i = 2;
                break;
        }

        /* switch statement;
           watch how 'case 999' has destroyed the
           jumptable-optimization.
        */
        switch (i) {
        case 0:
                i = 0;
                break;
        case 1:
                i = 5;
                break;
        case 2:
        case 3:
                i = 7;
                break;
        case 5:
                i = 1;
                break;
        case 999:
                /* This case should be tested seperately so
                   the assembler code can be more efficient.

                */
                i = 100;
                break;
        default:
                i = 2;
                break;
        }

        /* manipulating bits */
        i = i & 0x2345;
        i = i | 0x2345;
        i = i ^ 0x2345;
        i = ~i;
        i <<= i;

        /* using the result of a function */
        i = function(5, &character);

}

.text

main:
* Reserve 4 bytes. This way, when the first parameter for a
* function is pushed onto the stack, no pre-decrementing of sp
* has to be done. This 'trick' is used by the DRI-C-compiler.
        LINK    A6,#-4
* i = 9
        MOVE.W  #9,i
* character = 'c'
        MOVE.B  #'c,character
* i_pointer = &i
        MOVE.L  #i,i_pointer
* i_array[3][4] = *i_pointer
        MOVE.L  i_pointer,A0
        MOVE.W  (A0),38+i_array
* i_array[i][i - 1] = 2
        MOVE.W  i,D0     * compute byte offset from first element
        MULS    #10,D0
        MOVE.W  i,D1
        SUB.W   #1,D1
        ASL.W   #1,D1   * multiply by 2 because an int is 2 bytes
        EXT.L   D1
        ADD.L   D1,D0
        ADD.L   #i_array,D0
        MOVE.L  D0,A0   * move computed address to address-reg
        MOVE.W  #2,(A0)
* structure[1].i = 3
        MOVE.W  #3,4+structure
* structure[i].c = character
        MOVE.W  i,A0
        ADD.L   A0,A0
        ADD.L   A0,A0
* Two ADD operations are faster than a MUL and a MOVE
        ADD.L   #structure,A0
        MOVE.B  character,2(A0)
* i = i_array[0][0] * i_array[0][1] +
*     i_array[0][2] / i_array[0][3];
        MOVE.W  i_array,D0
        MULS    2+i_array,D0
        MOVE.W  4+i_array,D1
        EXT.L   D1
        DIVS    6+i_array,D1
        ADD.W   D1,D0
        MOVE.W  D0,i
* if (i < i_array[i][i]) i = 1;
* else i = 2;
        MOVE.W  i,D0
        MULS    #10,D0
        MOVE.W  i,D1
        ASL.W   #1,D1
        EXT.L   D1
        ADD.L   D1,D0
        MOVE.L  D0,A0
        MOVE.L  #i_array,A1
        MOVE.W  0(A0,A1.L),D0
        CMP     i,D0
        BLE     L4
* i = 1
        MOVE.W  #1,i
        BRA     L5
L4:
* i = 2
        MOVE.W  #2,i
L5:
        BRA     L8

* while (i <= 10) i++;
* This loop has been optimized:
* one BRA instruction was saved by putting the test after the
* do-part. The label L5:    BRA L8    takes care that the while
* condition is executed first at the beginning of the loop
L7:
        ADD.W   #1,i
L8:
        CMP.W   #10,i
        BLE     L7
* while (i++ <= 10) {
*   if (i != 4) continue;
*   else break;
* }
L6:
        BRA     L11
L10:
        CMP.W   #4,i
        BNE     L11
        BRA     L9

L11:
        CMP     #10,i
        MOVE.W  SR,D0   * save condition codes
        ADD.W   #1,i
        MOVE    D0,CCR  * and restore
        BLE     L10
* for (i = 4; i >= 0; i--) i_array[i][i] = i
L9:
        MOVE.W  #4,i
        BRA     L14
L15:
        MOVE.W  i,D0
        MULS    #10,D0
        MOVE.W  i,D1
        ASL.W   #1,D1
        EXT.L   D1
        ADD.L   D1,D0
        ADD.L   #i_array,D0
        MOVE.L  D0,A0
        MOVE.W  i,(A0)
L13:
        SUB.W   #1,i
L14:
        TST     i
        BGE     L15
L12:
* do i++; while (i < 10 && i != 5);
L18:
        ADD.W   #1,i
L17:
        CMP.W   #10,i
        BGE     L10000
        CMP.W   #5,i
        BNE     L18
L10000:
* switch (i) {
* case 0:
*        i = 0;
*        break;
* case 1:
*        i = 5;
*        break;

* case 2:
* case 3:
*        i = 7;
*        break;
* case 5:
*        i = 1;
*        break;
* default:
*        i = 2;
*        break;
* }
L16:
        MOVE.W  i,D0
        BRA     L20
L21:
        CLR.W   i
        BRA     L19
L22:
        MOVE.W  #5,i
        BRA     L19

L23:
L24:
        MOVE.W  #7,i
        BRA     L19
L25:
        MOVE.W  #1,i
        BRA     L19
L26:
        MOVE.W  #2,i
        BRA     L19
L20:
* Test if i is in case-range
* if not goto default
* if in range compute address to jump to
        CMP.W   #5,D0
        BHI     L26
        ASL.W   #2,D0
        MOVE.W  D0,A0
        ADD.L   #L27,A0
        MOVE.L  (A0),A0
        JMP     (A0)
.data
L27:
* jumptable
.dc.l L21
.dc.l L22
.dc.l L23
.dc.l L24
.dc.l L26
.dc.l L25
.text
L19:
* switch (i) {
* case 0:
*         i = 0;
*         break;
* case 1:
*         i = 5;
*         break;
* case 2:
* case 3:
*         i = 7;
*         break;

* case 5:
*         i = 1;
*         break;
* case 999:
*         /* This case should be tested seperately so
*            the assembler code can be more efficient.
*         */
*         i = 100;
*         break;
* default:
*         i = 2;
*         break;
* }
        MOVE.W  i,D0
        BRA     L29
L30:
        CLR.W   i
        BRA     L28
L31:
        MOVE.W  #5,i
        BRA     L28

L32:
L33:
        MOVE.W  #7,i
        BRA     L28
L34:
        MOVE.W  #1,i
        BRA     L28
L35:
        MOVE.W  #100,i
        BRA     L28
L36:
        MOVE.W  #2,i
        BRA     L28
        BRA     L28
L29:
        EXT.L   D0
        MOVE.L  #L37,A0
        MOVE.W  #6,D1

L38:
        CMP.L   (A0)+,D0
        DBEQ    D1,L38
        MOVE.L  24(A0),A0
        JMP     (A0)
.data
L37:
* table of case values
.dc.l 0
.dc.l 1
.dc.l 2
.dc.l 3
.dc.l 5
.dc.l 999
.dc.l 0
* jump table
.dc.l L30
.dc.l L31
.dc.l L32
.dc.l L33
.dc.l L34
.dc.l L35
.dc.l L36
.text
L28:
* i = i & 0x2345
        ANDI.W  #$2345,i
* i = i | 0x2345
        ORI.W   #$2345,i
* i = i ^ 0x2345
        EORI.W  #$2345,i
* i = ~i
        NOT.W   i
* i <<= i
        MOVE.W  i,D1
        MOVE.W  D1,D0
        ASL.W   D1,D0
        MOVE.W  D0,i
* i = function (5, &character)
        MOVE.W  #51,(sp)
        MOVE.L  #character,-(sp)
        ADDQ.L  #4,sp
        MOVE.W  D0,i

L3:
        UNLK    A6
        RTS

I  would like to thank all people who have attended  this  course 
and who have reflected in some way.

Disclaimer
The text of the articles is identical to the originals like they appeared in old ST NEWS issues. Please take into consideration that the author(s) was (were) a lot younger and less responsible back then. So bad jokes, bad English, youthful arrogance, insults, bravura, over-crediting and tastelessness should be taken with at least a grain of salt. Any contact and/or payment information, as well as deadlines/release dates of any kind should be regarded as outdated. Due to the fact that these pages are not actually contained in an Atari executable here, references to scroll texts, featured demo screens and hidden articles may also be irrelevant.