This page contains an in-depth look at flags, and how they are used in arithmetic. This is followed by a brief explanation of all other instructions, both those that affect the flags and those that do not.
Like most computers, the Psion has a flag register. If something unexpected happens during arithmetic, its bits are used to flag the situation so that appropriate measures can be taken.
Suppose the computer is adding two bytes together. The only thing that might go wrong is that the result no longer fits in a single byte. For example, 200+150=300 becomes 11001000+10010110=(1)01011110. The ninth bit of the result no longer fits in a byte, so the result the computer gets is 01011110=94, which is 256 too small. In this situation a bit in the flag register called the Carry flag is set. Thus in effect the carry flag holds the 'carry' from the eighth to the ninth bit of the result. Of course, if two words are added the carry will be set whenever the result no longer fits in a word, in other words it contains the 'carry' from the 16th to the 17th bit of the result.
If two even longer numbers need to be added we have to split the addition up into parts. First add the lower bytes using the ADD instruction. Then add all the next bytes using the ADC instruction. This way the carry that might result from the addition of two bytes is carried over to the next bit, i.e. it is added to the lowest bits in the addition of the next two bytes.
There is another flag called the Zero flag. If all 8 or 16 bits of the result of our addition is zero then this flag is set.
Again the carry flag is set when the unexpected occurs. In this case whenever there is a 'borrow' from the non-existent ninth bit to the eighth one. In other words, it is set when the result is supposed to be negative. For example, 25-197=-172 becomes 00011001-11000101=(1)01010100. The result it gets is 01010100=84 which is 256 too much. When two words are subtracted the carry flag is set if a borrow occurs from the non-existent 17th bit.
As before, we can subtract two longer bytes. By using the SBC instruction the carry flag is also subtracted, so that the borrow from one subtraction will be taken from the next one.
If two numbers are compared using a CMP instruction, they are actually subtracted. The result is completely ignored but the flags are set anyway. Therefore if the carry flag is set after a compare instruction, the second unsigned binary number is larger than the first. If it is not set, then the first is greater or equal to the second. If zero flag is set it means that the two numbers were equal, if it is not then they were different.
After a compare instruction, a branch instruction is nearly always used to test the flags. The following table shows which branch instructions are generally used with unsigned integers.
|BHI||C=0 and Z=0||>|
|BLS||C=1 or Z=1||<=|
C denotes the Carry flag, Z the Zero flag. The last column
shows how the two compared values are related. For example, CMPA %1A followed by a BHI
instruction will branch whenever register A contains a value strictly greater than 1A (or
26 in decimal). The branch instructions themselves do not influence the flags in any way.
Let me digress for a moment here to comment on the distance byte of a branch instruction. In many texts about machine code, including Bill Aitken's book, the explanation is a little unclear (because of all that business about adding two). The easiest way to think about the distance is in terms of the number of bytes skipped. For example, after BRA 0 the processor simply continues with the instruction immediately following the zero. A branch can skip over at most 127 (=$7F) bytes. For backwards jumps, again count how many bytes are skipped but don't forget to include the two bytes of the branch instruction itself. A backwards branch can skip over at most 128 (=$80) bytes, or 126 bytes excluding the branch instruction.
To calculate the number of bytes skipped for long distances, subtract the address of the instruction following the branch from the address of the destination of the branch. This also works for backwards jumps.
With signed numbers there is a different circumstance that has to be noted namely a carry to (or borrow from) the 8th bit, because this could change the sign of the byte and therefore give the wrong result. If the answer is actually wrong, the Overflow flag is set. It is set whenever there is a carry from bit 6 to 7 and no carry from bit 7, or vice versa.
There is another flag that is used with two's complement numbers, namely the Negative flag. This is set if the result is negative, so it simply contains the value of the eighth bit of the result.
Let's see what happens when we subtract two signed numbers. In the following table every combination is shown. The columns C, V, and N denote the Carry, Overflow and Negative flags.
|X||Y||X-Y||Binary subtraction||Binary result||C||V||N|
The following branch instructions are generally used with signed integers.
|BGT||N=V and Z=0||>|
|BLE||N<>V or Z=1||<=|
There is one more flag that has to do with arithmetic, namely H, the Half carry flag. This flag is set when a carry occurs between bits 3 and 4 during and addition involving the A register. The only time when this is important is when using binary coded decimal numbers. In ordinary binary arithmetic such a carry has weight sixteen, but in BCD such a carry should be worth only ten so the result will be out by a value of six. Let me illustrate this with an example.
Consider 18+9=27. In BCD we have 00011000+00001001 and using ordinary addition this gives 00100001, which is 21 if read as a BCD. The half carry flag was set however, because there was a carry from bit 3 to bit 4. There is only one instruction that uses this flag, and this is DAA which stands for Decimal Adjust A. It will adjust the register A to give the correct answer in BCD form. Thus if H was set after an addition, it will add 6. It will also check if one of the byte halves contains a value between ten and fifteen, and adjust it accordingly. So after 16+37=4D, it will adjust the hexadecimal digit D by adding a further six to give 53 which is the correct BCD answer.
The DAA instruction changes the N, V and Z flags in the way you would expect, even though the N and V flags have little relevance to BCD numbers. The carry flag is set whenever there is a carry from the leftmost digit, so for example 99+4 will after DAA result in 3 with the carry flag set.
Each flag is a bit in the so called Condition Code Register. This register is denoted either by CCR or by P, and is only used directly in the instructions TAP and TPA which transfer the value of register A to P or vice versa. The P register looks like this in binary: 11HINZVC. (Note that the numbering on page 168 of Aitken's book is incorrect, though page 40 gives the correct version.) The I denotes the Interrupt flag, which is set whenever interrupts are disabled. The highest two bits are not used and seem always to be set.
So far we have only really discussed additions and subtractions (including compares) and all the ADD, ADC, SUB, SBC and CMP instructions affect the flags exactly as described.
It is interesting that the type of object the instruction is applied to does not influence the way the instruction affects the flags. For example:
|ADDA %10||Adds %10 to A|
|ADDA 41||Adds contents of zero-page address 41 to A|
|ADDA 2188||Adds contents of address 2188 to A|
|ADDA 1,X||Adds contents of address X+1 to A|
These four instructions all set the flags as previously described.
There is one more add instruction available: The very useful ABX instruction, which adds the 8 bit register B to the 16 bit register X. Note that this instruction does NOT change any flags at all.
There are other instructions that influence the flags. These will now be discussed.
Nearly all of the load and store instructions behave in the same way. The overflow is cleared, and the N and Z flags are set according to the value that is loaded or stored. The carry flag remains unaffected. This applies also to TBA and TAB. The only exceptions to this rule are TPA, TSX and TXS which do not change any flags, and of course TAP which changes the flags according to the bits in A.
The INC and DEC instructions increment or decrement a register or byte. They never change the carry flag. There is a good reason for this. Suppose we wish to add two long strings of bytes together. We want the carry from the addition of one pair of bytes to be carried over to the next two bytes, so that we can simply use an ADC instruction to get the correct result. For a long string of bytes, we would also like to use a loop and since INC and DEC do not change the carry flag, we can use these to adjust a loop counter without losing the carry.
The INC and DEC instructions which act on a single byte affect only the N, Z and V flags. The INX and DEX instructions only affect the Z flag, and the DES and INS instructions do not affect any flags.
The CLR instructions clear an 8 bit register or a byte. The flags are affected exactly as you would expect. The N, V and C flags are cleared, and the Z flag is set.
The COM instructions toggle every bit in an 8 bit register or byte. The N and Z flags are affected in the normal way, the carry flag is set, and the overflow flag is cleared.
The NEG instructions negate an 8 bit register or byte. It affects the four flags N, Z, V and C. The N and Z flags need no explanation. The only way an overflow can occur is when -128 is negated, because +128 is out of range for a two's complement number. In this case the V flag is set, and the value returned is -128 of course. The carry flag is set whenever the value used is not zero.
The TST instructions test an 8 bit register or byte. In effect this means that its value is compared with zero and the flags set accordingly. Thus the V and C flags are cleared, the N and Z flags are set when the value is negative or zero respectively as usual.
The MUL instruction multiplies (unsigned) values in the 8 bit registers A and B together, and put the 16 bit result in D. Only the carry flag is changed, and it is set whenever bit 7 of D (i.e. bit 7 of B) is set.
The instructions AND (and AIM), BIT (and TIM), ORA (and OIM), EOR (and EIM) use two 8 bit values and combine their corresponding bits one by one. This is done using the following table:
|First bit||Second bit||AND||OR||EOR|
The BIT (and TIM) instructions perform an AND operation, but the result is not stored, only the flags are affected. This is very much like the way that CMP performs a SUB instruction but ignores the result.
All these instructions have the same effect on the flags: The carry flag is unaffected, the overflow flag is cleared, and the Z and N flags are affected normally.
The mnemonics BCLR, BSET, BTGL, and BTST are sometimes used to denote special cases of AIM, OIM, EIM and TIM where only one bit is affected.
The instructions ROL and ROR rotate an 8 bit register or byte one bit to the left or right through the carry flag. This is best illustrated with a diagram:
ROR: ROL: --> 76543210 -> <-- 76543210 <- | | | | <----- C <----- -----> C ----->
The register or byte has all its bits moved along, the carry flag is used to fill the vacated bit as it were, and the value of bit at the end that would be lost is put in the carry flag.
The three flags N, Z and V are also changed as you would expect them to. The overflow flag is set whenever bit 7 changes value, the N flag contains the new value of bit 7, and Z is set if the byte is zero.
The instructions LSR and ASL shift an 8 bit register or byte left or right, and the vacated bit is cleared. It is completely equivalent to the ROR and ROL instructions when the carry flag is zero. Diagrammatically these look like this:
LSR: ASL: 0 --> 76543210 -> C C <-- 76543210 <- 0
The LSR instruction in effect causes a division by 2, in the same way as in the decimal system we can divide by ten by removing the rightmost digit. The remainder of the division is put in C. Note that since bit 7 is cleared, the N flag will be zero. The ASL instruction multiplies by 2.
There is one further shift instruction, ASR, which is used for two's complement numbers. Whereas ASL doubles both signed and unsigned binary numbers, LSR does not divide a negative number by two correctly. Instead ASR is used. It is similar to LSR except that bit 7 is not cleared. In diagram form we have:
ASR: -> 76543210 -> C | | <--
Note that LSR and ASL have sixteen bit versions, LSRD and ASLD, whereas ASR does not.
The instructions CLC, CLI, CLV, and SEC, SEI, SEV clear or set the C, I and V flags respectively. No other flags are changed.
Now I'll quickly discuss the remaining instructions. None of these change the flags.
The PSH instructions put the value of a register on the stack. This means that it is stored in memory at the address pointed to by the SP register. The SP register is then decremented so as to point to the next available address. Thus PSHA (or PSHB) will store A (or B) at address SP, and decrement SP by one. The PSHX instruction stores the low byte of X at address SP, the high byte of X at address SP-1 and decrements SP by two.
The PUL instructions are the opposite of PSH instructions, so they take the value of a register from the stack. Thus PULA (or PULB) will first increment SP by one, and then take the byte that is stored at address SP, and put it in register A (or B). Note that the PULX instruction first increments SP by two, and then takes the bytes stored at SP-1 and SP and put them in the high and low bytes of X respectively.
Note that the combination PSHX, PULA, PULB will put the value of X into register D and similarly PSHB, PSHA, PULX puts D in X.
The JMP instructions jump to a specified address, and continue the program there. This can be compared to GOTO instruction in OPL. The address can be a two byte address, or given as a displacement from the address pointed to by the X register. There are no conditional jumps, so to make a jump depending on the state of the flags, branch instructions will have to be used to decide whether or not a jump instruction has to be carried out.
The JSR instruction puts the address of the instruction
following it on the stack and jumps to a given address. This instruction is used in
combination with RTS, which takes an address from the stack and jumps there. Thus a JSR
instruction calls a subroutine. The subroutine is terminated when a RTS instruction is
encountered, after which the program is continued from the next instruction after the JSR.
Note that since these instructions require the stack, is important that care is taken with
PSH and PUL instructions, or else the return address may be removed prematurely, or the
RTS is encountered while there are still extra items on the stack below the return
address. The BSR is similar to JSR, except that it is a branch instruction instead of a
jump. This allows for the following useful construction in a relocatable program:
5 bytes of data
The X register now points to the address of the data.
The XGDX instruction swaps the values of X and D, without affecting any flags at all.
So called software interrupts are used to access the system programs that are stored in the ROM. When the SWI instruction is encountered, the following happens: First the return address (i.e. the address of the instruction following the SWI) is stacked, and then the registers X, A, B and the CCR are stacked as well. The two byte address of the software interrupt routine is read from the ROM at address $FFFA, further interrupts are disabled and then this routine is called.
In the Psion, the software interrupt routine will then jump to the address contained in memory at $2052. This is where the heart of the interrupt routine is. It first examines the return address and reads one data byte which immediately follows the SWI instruction. Therefore we can think of SWI as a two byte instruction, of which the second byte denotes which system program to call. After this data byte is read, the address of the appropriate system routine is looked up in an address table and the system routine is called. This table also lies in the ROM, but its address is stored at $23E7. If you wanted to, you could copy this table into RAM, adjust $23E7 to point to the new table, and change some of the addresses in the table to point to your own routines. This way you could replace certain ROM system calls by your own, or add your own to the existing ones.
In the ROM, the main SWI routine is ended by a RETI instruction, which automatically pulls all the registers off the stack and then jumps back to the return address it finds on the stack.
There is also a related instruction not mentioned in Aitken's book. The TRAP instruction (which has code 00) is very similar to the SWI instruction except that it calls the ROM routine which has its address stored at $FFFC. This routine in turn takes the address stored at $2046 and jumps there. At that address is a program that displays TRAP, the return address and some other number and then goes into an endless loop. The only way to stop it is to pull the battery, and reinsert it to restart the Psion. If there is any small error in any machine code program, it is highly likely to accidentally come across the TRAP instruction because its code is 00. If you change the contents of $2046 to point to a RTS instruction, the trap routine is disabled, and the TRAP instruction will have little effect. The Psion is now less likely to completely crash if it encounters a small bug.
The Psion has many other types of interrupt, with exotic names like NMI, IRQ1, ICI, OCI, TOI, CMI, IRQ2, and SIO. These are triggered by the hardware of the Psion rather than from within a program. If these such an interrupts occurs, the same things are done as in a software interrupt, i.e. the return address and the registers are stacked, an address is taken from the ROM (the table of addresses lies between $FFEA and $FFFF) and the routine that lies there is called. These interrupt routines in turn find the relevant address in the table that lies between $2042 and $2055, and the routine at that address is called.
If you examine the addresses stored between $2042 and $2055, you will notice that many of them simply point directly to a RTS instruction in the ROM. The only ones that don't are the TRAP and SWI ones already discussed, and the NMI and OCI addresses.
The OCI interrupt usually occurs automatically twenty times a second, and amongst other things it checks whether keys are pressed.
The NMI (which stands for Non-Maskable Interrupt) occurs once every second, and it does things like adjust the clock, check whether it is time to switch off, whether an alarm has to be raised etc.
By setting the I flag (with SEI) all interrupts are ignored, except for the NMI's which are too important to be disabled. (NMI's can be disabled on the Psion, using SWI 07 or SWI 09.)
There are two instructions that deal with hardware interrupts, SLP and WAI. SLP waits until any interrupt occurs. If the interrupts are not disabled, the appropriate interrupt routine is called before the program continues. If they are disabled, the program continues immediately. WAI has a similar effect (except that it waits for interrupts from peripherals or an NMI??).
The instruction NOP does nothing. There are two ways in which this is useful. First, it does take some time to execute so it may be used as a tiny pause, for example in sound routines. Second, you can erase an unwanted instruction in a program by overwriting it by a NOP instruction.
The BRN instruction is the opposite of BRA, so it is a branch instruction that never branches. Therefore it simply skips the next byte (which would have held the distance branched).