1. Introduction.

In this chapter we'll take a look at the remaining instructions which we have yet to cover. There are not many left now - you'll be glad to hear.

1.1. A Few Quickies !

This section deals with a few instructions that the QL programmers rarely, if ever, use. These instructions are :

CHK
ILLEGAL
RESET
RTR
STOP
TRAPV

The CHK instruction has the format :

CHK <ea>,Dn

and causes an exception to be generated if the value in Dn.W is less than 0 or greater than the value in the effective address. On a normal QL this is totally ignored - the exception that is - however, with a bit of deft QDOS programming, this can be redirected to your own routine. I have never seen this done in any programs - yet ! By the way, the value in the effective address is a two's compliment signed number. The flags affected are :

  • N - set if Dn.W is less than zero, cleared is Dn.W is greater than the effective address value. Oterwise it is undefined.

  • Z - undefined.

  • V - undefined.

  • C - undefined.

  • X - unaffected.

The format of the ILLEGAL instruction is quite simply :

ILLEGAL

and all it does, by default, is to crash the QL ! It can however be redefined to do something useful as with the CHK instruction. (We may get around to covering QDOS stuff in a much later episode.) This instruction also causes an exception to be generated. No condition codes are affected.

The RESET instruction has the format :

RESET

and causes the 'reset' line to be 'asserted' causing all external equipment interfaced to the processor to be reset. On the QL, it actually causes a system reset - similar to you pressing the reset switch. This instruction will only be executed if the processor is running in supervisor mode, in user mode, all that happens is that the program counter is incremented by 2 to skip over this instruction. No flags are affected.

RTR has the format :

RTR

and is actually equivalent to the following two instructions :

MOVE (A7)+,SR
RTS

However, the MOVE (A7)+,SR instruction is privileged on the 68000 so can only be run in supervisor mode. Using RTR is not privileged so the two instructions can be combined as one. This is a useful instruction for subroutines where the status register is saved on the stack on top of the return address. The following code is an example.

start   BSR example
        ; more code here

example MOVE SR,-(A7)       ; Stack the status register etc
        ; do some code here
        RTR                 ; Unstack the status code

What happens when a subroutine is called is that the return address is placed on the stack and then the subroutine jumped to. In this example the status regisetr is placed on the stack as well. This is a word sized SR on top of a long sized Program Counter.

The subroutine carries out various bits of processing - probably trashing the status codes etc as it does so. At the end, the old SR is put back into the SR and the return address placed in the PC by the RTR instruction.

It is a quirk of the 68000 that the instructions to move data from the SR are not privileged while those that move data into the SR are privileged. This is a handy way around this restriction.

Obviously, the various flags in the SR are changed according to the word removed from the stack except for the supervisor bit whihc is unchanged.

The STOP instruction has the format :

STOP #data

and causes the processor to put the word of data into the SR, increment the PC to point at the instruction following this one, and then the processor just stops - until any trace, interrupt or reset exceptions are generated. The interrupt must be higher that the current processor interrupt level to have any effect. The flags are set according to the data word in the instruction. Theis is another privileged instruction and is the processor is in user mode, and a privilege violation exception will be generated.

The TRAPV instruction has the format :

TRAPV

and is used to cause an exception if the V flag is set. (Overflow flag). Normally this is ignored on a QL but can be redirected with the afore mentioned QDOS jiggery pokery to do something useful. No flags are affected.

1.2. A Few Little Bits

This section deals with instructions that check, change, set or otherwise fiddle about with the individual bits in a register or memory address. All of these instructions have a similar format, which is :

Bxxx Dn,<effective address>
Bxxx #data,<effective address>

They all TEST the bit about to be fiddled with BEFORE fiddling with it. The flags are set according to the state of the bit BEFORE the fiddling was done. Remember this important fact. The bit number is either supplied in a data register or as immediate data.

When the bit number is being processed the 68000 makes sure that it is in range for the actual data being operated on. If the effective address is a data register (you cannot use these instructions on address registers) then the actual bit number is bit number MOD 32.

If a memory address is being manipulated, the range is adjusted to be 0 to 8 using bit number MOD 8.

The flags are all unaffected except for the Z flag whch takes the state of the 'previous' value of the bit being manipulated.

The instructions are :

BCHG or Bit Change, which sets the Z flag to the bit affected and then changes the bit from a 1 to a zero or from a zero to a 1. 
BCLR or Bit Clear, which always puts a zero into the affected bit. 
BSET or Bit Set, which aslways puts a 1 into the affected bit.
BTST or Bit Test, which sets the Z flag to the state of a bit. If the affected bit is a zero, the Z flag is set to show this. If it was a 1 then the Z flag will be reset to show this.

This family of instructions are very useful when using a byte, word or long to hold 8, 16 or 32 different flags in a program as each one can be tested, set or reset individually and this takes place within QDOS in a number of places.

As a small example, imagine you were writing a program and you needed to check when the user typed an UPPERCASE character. Rather than checking every one for 'A' and 'Z' (which only apply to the English language remember, you could set up a bitmap table of 256 bytes and have a single bit represent uppercase, another could be for numeric, another for control/unprintable characters etc etc. As each character was read, index into the table on that character code and check the appropriate bit.

;
; Some code above to get a character from the user/file etc
; Assume D1.W holds the character code. Top 8 bits are zero.
; Assume that bit 0 is the uppercase/lowercase flag bit.
;

check   LEA bitmap,A1       ; A1 is address of the bitmap table
        BTST #0,(A1,D1.W)   ; Is it uppercase ?
        BEQ.S   upper       ; Yes, if bit zero is set

lower   ; process lowercase here

upper   ; process uppercase here

bitmap  ; 256 bytes go here, one for every character.

The bitmap table has a single byte for each available character 0 to 255 and sets the bits in each one according to the character type. In this example we use bit 0 for upper/lower case only so wastes 7 bits of each byte, but remember, these extra bits could be used to define control characters, digits, hex digits, alpabetic, alpha-numeric, punctuation etc.

The advantage to this method is that different tables can be loaded for different languages. The disadvantage is that the program will be slightly longer because of the need to store the table.

1.3. Testing, Testing

In QLTdis, I have used the TST instruction to compare a value against zero. This is a useful instruction and replaces CMPI.size #0,Dn. The format is :

TST.size <effective address>

The flags are set differently from CMPI as well as the V and C falags are always cleared to zero. CMPI doesn't do this. The flags are :

  • N is set if the operand is negative, reset if positive.

  • Z is set if the operand is zero, reset otherwise.

  • V is always cleared.

  • C is always cleared.

  • X is not affected.

Why use TST when CMPI will do as good a job ? Well it is all down to three things really :

  • Do you want to use TST or CMPI #0 ?

  • Do you need to preserve the V and C flags ?

TST is quicker. TST takes 8 clock cycles while CMPI takes 16, 24 or 26 depeding on the operation. Both take the same time to work out the effective address calculation, but TST also needs fewer read cycles - 2 - while CMPI needs 4 or 6.

TAS is another testing instruction, which actually does two separate operations in one single UNINTERRUPTABLE step. The format is :

TAS <effective address>

The size is always byte and need not be specified. The flags affected are :

  • N is set if bit 7 of the operand was set, otherwise cleared.

  • Z is set of the operand was zero, Reset otherwise.

  • V is always cleared.

  • C is always cleared.

  • X is not affected.

The instruction reads the byte at <effective address>, checks bit 7, sets the flags and then sets bit 7. The modified byte is written back to the effective address. It is similar to the folowing code :

BTST    #7,<effective address>
BSET    #7,<effective address>

Obviously there are two instructions here which alter the flags, however, TAS does it in one. The main point about TAS is that it is a single instruction which cannot be interrupted once it has started. This makes it useful for multi tasking or multi processor systems where any sequence of instructions can be interrupted.

In the above example, the system could be interrupted by a floppy disc I/O request between the end of the BTST and the start of the BSET. This could result in a new value being placed into <effective address> by the interrupting routine. The BSET would then possibly give the wrong results after it executed.

This will not ever happen with the TAS instruction. If the above code was being used in a multi processor system to synchronise access to some system resource, the two instructions could lead to mis-synchronisation. Using TAS would not allow this to happen.

Finally in this section, although not quite a testing instruction, is the 'set according to condition code' instructions. These have the format :

Scc <effective address>

The size is always byte and is not specified in the instruction. What happens is that the condition code is tested, and if found to be true, the byte in < effective address> is changed to be all ones otherwise it is changed to be all zeros. The condition codes are as for Bcc and DBcc.

This sets a memory address or a byte in a register to 255 or 0 for True or false. On QDOS systems we tend to use 1 for true and 0 for false. How can we quickly change from 255 and zero to 1 and zero ?

The answer is quite simple, 255 is an unsigned number but if it was signed, it would be -1. Simply follow the Scc instruction with NEG.B as follows :

                  ; Do some code here to set condition flags.
SMI     D1        ; Set D1.B to $FF if 'something' was minus
NEG.B   D1        ; D1.B now is $01 or $00 which is what we want !

1.4. And Finally ?

I think we are just about finished covering all those boring instructions, but we still have a couple to do yet. These don't really fall under any of the headings I have used up til now, so I simply add them on at the end !

On the QL, assembly language programs must be written so that they are 'relocatable'. All this means is that you must not assume that your code will always run from a specific address but that it could run from ANY address.

The LEA instruction which has been used quite a lot in QLTdis already allows just this to happen. This has the format :

LEA <effective address>,An.

None of the flags are affected. So, a quick bit of revision, what is the difference between the following two instructions ?

MOVE <effective address>,A1
LEA <effective address>,A1

MOVE calculates the effective address and reads its contents into A1 while LEA calculates the effective address and puts that into A1, not its contents.

This allows position independant code to be written and is a very much used instruction in QDOS programs. It also helps get around the fact that PC relative mode addressing is forbidden as the destination in a MOVE instruction. The following code will not assemble :

MOVE.L  D0,buffer(PC)

But this will, and does what is required :

LEA     buffer,A1
MOVE.L  D0,(A1)

There is a similar instruction called Push Effective Address and this has the format :

PEA <effective address>

and simply calculates the effective address and puts it onto the stack. The stack pointer is predecremented and none of the flags are affected. All this is very similar to the following :

LEA     some_code,A1    ; Get the address of label some_code into A1
MOVE.L  A1,-(A7)        ; Stack it

But why would you use PEA to do this rather that the above, and what use is it afterwards ? Apart from it being shorter to code - one instruction instead of two - it doesn't require a register to be used. The address is on the stack, so what next ?

Think about these instructions :

PEA     some_code,A1    ; Get the address of label some_code into A1
RTS

What has just happened ? The address of the routine at 'some_code' has been placed on the stack, then when RTS is executed, it returns control to the address WHICH IS ON THE STACK. So this is another way of doing this :

LEA     some_code,A1
JSR     (A1)

Why would you use this ? I have absolutely no idea ! But it is important to note that the first method will NEVER return to the address after the RTS because there is no return address on the stack. The second and 'normal' method will return to the address after the JSR (A1) as the JSR stacks its return address.

The next and final two instructions are seldom used in normal assembler programs on the QL - at least, I have never seen one in all my years of reading & writing code. They are probably used most by the code generated by various compilers that exists for the QL so that 'stack frames' can be built and parameters passed to sub-routines created by the compiler. The two instructions are LINK and UNLK and they do not affect any flags.

The LINK instruction has the format :

LINK An,#displacement

and carries out the following actions :

  • First the stack pointer is decremented by 4.

  • Then, the current contents of An are copied onto the stack.

  • Then the stack pointer is copied to An.

  • Finally, the stack pointer has the displacement ADDED to it.

UNLK has the format :

UNLK An

and carries out the reverse of the LINK instruction in that the stack pointer is reloaded from An, then An is reloaded from the value on the stack and the stack pointer is incremented by 4.

Assuming that A7 is currently holding value $20000 and A4 is holding $00123456 the the sequence of instructions :

LINK A4,-$10
                ; do something here ...
UNLK A4

will result in the following :

  • A7 will be decremented by 4 to $1fffc

  • A4 will be stored at this address ($1fffc)

  • A4 will then have $1fffc loaded into it

  • A7 will have $10 subtracted (because we supplied a negative displacement) to give $1ffec.

This means that the code between the LINK and UNLK instructions can use the free space between (a7) and -4(A4) for working space. There are 16 bytes available for use between these addresses and they can be accessed using A4 as a 'stack frame pointer' and using negative offsets.

Once the UNLK instruction is reached, we must not have changed the value in A4 or all hell will break loose !

  • A7 is set to the value in A4 which should be $1fffc.

  • A4 will be set to the long word at 0(a7) which is where its original value of $00123456 was stored by the LINK instruction.

  • A7 will have 4 added to it giving the original $20000 that we had when the LINK was executed.

1.5. So Here We Are !

Well, that is the end of the most boring part of this series. I apologise for the dreary nature of the previous few episodes but I can't think of any other way to make a micro-processor's instruction set interesting reading !

We have now covered all the 68008 instructions and the time has come to start putting the information into practice. However, when I was learning all about 68000 assembly language, there were a few concepts that gave me troubles - and I still have to look them up even today !

To make things a bit easier for you, here are my bug-bears and an explanation of how to get around them.

1.5.1. Comparing Things

Comparing registers or registers and values etc always gives me problems. I can never remember which flags are set or which ones to check when using signed or unsigned values. The following should hopefully make life easier.

Remember, when using the CMP instruction, you should read it as 'if destination condition source'.

1.5.1.1. Equality checks - signed and unsigned are the same.
CMP.L   D0,D1
BEQ.S   equal       ; if d1 = d0 goto equal.

or

CMPI.L  #10,D1
BEQ.S   equal       ; if d1 = 10 goto equal.
1.5.1.2. Non equality checks - signed and unsigned are the same.
CMP.L   D0,D1
BNE.S   not_equal   ; if d1 <> d0 goto not_equal.

or

CMPI.L  #10,D1
BNE.S   not_equal   ; if d1 <> 10 goto not_equal.
1.5.1.3. Greater than - unsigned only.
CMP.L   D0,D1
BHI.S   greater     ; if D1 > D0 goto greater.

or

CMPI.L  #10,D1
BHI.S   greater     ; if D1 > 10 goto greater.
1.5.1.4. Greater than - signed only.
CMP.L   D0,D1
BGT.S   greater     ; if D1 > D0 goto greater.

or

CMP.L   #-5,D1
BGT.S   greater     ; if D1 > -5 goto greater.
1.5.1.5. Greater Than or Equal - unsigned only.
CMP.L   D0,D1
BCC.S   greater_eq  ; if (D1 >= D0) goto greater_eq

or

CMPI.L  #5,D1
BCC.S   greater_eq  ; if (D1 >= 5) goto greater_eq
1.5.1.6. Greater Than or Equal - signed only.
CMP.L   D0,D1
BGE.S   greater_eq  ; if (D1 >= D0) goto greater_eq

or

CMPI.L  #-5,D1
BGE.S   greater_eq  ; if (D1 >= -5) goto greater_eq
1.5.1.7. Less than - unsigned only.
CMP.L   D0,D1
BCS.S   less        ; if D1 < D0 goto less

or

CMPI.L  #5,D1
BCS.S   less        ; if D1 < 5 goto less
1.5.1.8. Less than - signed only.
CMP.L   D0,D1
BLT.S   less        ; if D1 < D0 goto less

or

CMPI.L  #-5,D1
BLT.S   less        ; if D1 < -5 goto less
1.5.1.9. Less than or equal - unsigned only.
CMP.L   D0,D1
BLS.S   less_eq        ; if D1 <= D0 goto less_eq

or

CMPI.L  #10,D1
BLS.S   less_eq        ; if D1 <= 10 goto less_eq
1.5.1.10. Less than or equal - signed only.
CMP.L   D0,D1
BLE.S   less_eq        ; if D1 <= D0 goto less_eq

or

CMPI.L  #10,D1
BLE.S   less_eq        ; if D1 <= 10 goto less_eq

1.5.2. Signed Numbers being MOVEd

Rememeber also that flags and conditions are set when data is MOVEd into data registers, or after arithmetic etc, so the following are valid as well. Obviously, the following code will not work correctly if you find this in a real program - don't use it !

MOVE    D1,D0               ; Copy D1 to D0 & set flags accordingly
BEQ.S   D1_is_zero          ; D1 is now zero
BNE.S   D1_is_not_zero      ; D1 is not zero
BGE.S   D1_is_0_or_more     ; D1 is now zero or greater
BPL.S   D1_is_0_or_more     ; Ditto
BGT.S   D1_is_1_or_more     ; D1 is now greater than zero
BLE.S   D1_is_0_or_less     ; D1 is now less then zero
BLT.S   D1_is_negative      ; D1 is now less than zero
BMI.S   D1_is_negative      ; Ditto