-
Notifications
You must be signed in to change notification settings - Fork 1
Undocumented 6809 Behaviours
- Introduction
- References
- Undocumented Base-page Opcodes
- Undocumented Page-2 Opcodes
- Undocumented Page-3 Opcodes
- Undefined values of the Half-Carry Flag
- Undefined values of the Overflow Flag
- Multiple Prefixes
This page attempts to describe the behaviour of all undefined 6809 opcodes.
In addition, it describes the behaviour of flags where the existing documentation is either unclear, or is inconsistent. For example, when the overflow (V) or half-carry flag (H) become undefined after an instruction, what actually happens?
There is a similar page covering the 6309 here.
These behaviours have been validated against original devices, by writing test cases that carefully (sometimes exhaustively) exercised the instruction, then exposed the resultant state by pushing it onto the stack. The data seen in each bus cycle is then verified against the emulation model in the 6809 Decoder.
The following 6809 references were used in the development of the 6809/6309 Bus Protocol decoder:
- Motorola 6809E datasheet
- Motorola 6809 and Hitachi 6309 Programmer's Reference, by Darren Atkinson
- MC6809 Cycle-By-Cycle Performance
- Undocumented 6809 Opcodes
- comp.sys.m6809 usenet archive
- The Comprehensive Document of 6809 Undocumented and Undefined Bahaviour by David Flamand, August 2022
More general references:
The best existing description of undocumented 6809 opcodes is Atkinson page 153, but even this was not completely accurate. Several of the findings disclosed on this page are new discoveries.
Halt-and-Catch-Fire!
Opcodes:
0x14 - XHCF (runs until RESET)
0x15 - XHCF (runs until RESET)
0xCD - XHCF (runs until RESET)
After executing this instruction, the processor enters a test mode where the address is incremented by one each cycle, starting at the current program counter value. The mode cannot be interrupted, except by a RESET.
All three varient of HCF appear identical, and any prefixes are ignored.
Opcodes:
0x18 - X18 (3 cycles)
This instruction updates the flags.
Flags:
- E' = (F & B6)
- F' = (H & B5)
- H' = (I & B4)
- I' = (N & B3)
- N' = (Z & B2)
- Z' = (V & B1)
- V' = (C & B0) | (Z & B2)
- C' = 0
where Bn is bit n of the byte at PC + 1 (the next opcode)
Note: the extact way in which this instruction depends on the following byte (Bn) was discovered by David Flamand, see his document in the References section.
Opcodes:
0x1B - NOP (2 cycles)
This instruction behaves the same as NOP (0x12)
Opcodes:
0x38 - XANDCC #immediate8 (4-cycles)
This instruction behaves like a 4-cycle version of ANDCC
Opcodes:
0x3E - XRES (19 cycles)
This instruction is similar to SWI (0x3F), except the RESET vector (0xFFFE/F) is used to determine the next PC value, and it does not correctly set the E flag in the saved machine state.
Flags:
- all flags are unchanged
Note: unlike a hardware RESET, the F and I flags are not set.
Bus cycles:
cycle 1 - read opcode (0x3E)
cycle 2 - read PC + 1
cycle 3 - dead cycle
cycle 4 - write PCL to system stack
cycle 5 - write PCH to system stack
cycle 6 - write UL to system stack
cycle 7 - write UH to system stack
cycle 8 - write YL to system stack
cycle 9 - write YH to system stack
cycle 10 - write XL to system stack
cycle 11 - write XH to system stack
cycle 12 - write DP to system stack
cycle 13 - write ACCA to system stack
cycle 14 - write ACCB to system stack
cycle 15 - write CC to system stack (note: E is not explicitely)
cycle 16 - dead cycle
cycle 17 - read 0xFFFE
cycle 18 - read 0xFFFF
cycle 19 - dead cycle
Opcodes:
0x01 - NEG direct
0x41 - NEGA
0x51 - NEGB
0x61 - NEG indexed
0x71 - NEG extended
These are exact aliases of the documented NEG instruction, i.e. bit 0 of the opcode is ignored when decoding NEG.
Opcodes:
0x02 - XNC direct
0x42 - XNCA
0x52 - XNCB
0x62 - XNC indexed
0x72 - XNC extended
This instruction behaves like NEG if C=0 or COM if C=1;
Opcodes:
0x05 - LSR direct
0x45 - LSRA
0x55 - LSRB
0x65 - LSR indexed
0x75 - LSR extended
These are exact aliases of the documented LSR instruction, i.e. bit 0 of the opcode is ignored when decoding LSR.
Opcodes:
0x0B - XDEC direct
0x4B - XDECA
0x5B - XDECB
0x6B - XDEC indexed
0x7B - XDEC extended
This instruction is similar to DEC instruction, except that the carry flag is modified.
Flags:
- Z - set if the result is zero, otherwise cleared (the same as DEC)
- N - set if the result is negative, otherwise cleared (the same as DEC)
- V - set if the operand is is 0x80, otherwise cleared (the same as DEC)
- C - cleared if the operand is zero, otherwise set (different to DEC)
- all other flags unchanged
Opcodes:
0x4E - XCLRA
0x5E - XCLRB
This instruction is similar to CLRA/B, except the C flag is unchanged.
Flags:
- Z = 1
- N = 0
- V = 0
- all other flags unchanged.
Opcodes:
0x87 - XSTA #immediate8
0x8F - XSTX #immediate16
0xC7 - XSTB #immediate8
0xCF - XSTU #immediate16
These instructions are only partly functional.
Bus cycles (XSTA):
cycle 1 = read opcode (0x87)
cycle 2 = read the 8-bit immediate operand (and discard the value)
Bus cycles (XSTB):
cycle 1 = read opcode (0xC7)
cycle 2 = read the 8-bit immediate operand (and discard the value)
Bus cycles (XSTX):
cycle 1 = read opcode (0x8F)
cycle 2 = read MSB of the 16-bit immediate operand (and discard the value)
cycle 3 = write LSB of the 16-bit immediate operand with the LSB of the X register
Bus cycles (XSTU):
cycle 1 = read opcode (0xCF)
cycle 2 = read MSB of the 16-bit immediate operand (and discard the value)
cycle 3 = write LSB of the 16-bit immediate operand with the LSB of the U register
For the forms of Store Immediate that are unprefixed, the N, Z, V flag behaviour is complex, and depends on the group of the previous instruction.
The previous instuction can be categorized into one of 8 groups:
-
GRP_N0_Z1 (BITA, CMPA)
- Z = 1
- N = 0
- V = 0
-
GRP_A_00 (LDA, LDD)
- Z set if A is zero, otherwise cleared
- N set if A is negative, otherwise cleared
- V = 0
-
GRP_A_FF (ADCA, ADDA, ANDA, EORA, ORA, SBCA, SUBA)
- Z set if A = 0xFF, otherwise cleared
- N set if A is positive, otherwise cleared
- V = 0
-
GRP_A_01 (ANDD, ASLA, ASRA, COMA, DAA, DECA, INCA, LSRA, NEGA, ROLA, RORA, SUBD, TSTA, XDECA, XNCA)
- Z set if A = 0x01, otherwise cleared
- N set if (A - 1) is negative, otherwise cleared
- V set if A = 0x80, otherwise cleared
-
GRP_B_01 (SEX)
- Z set if B = 0x01, otherwise cleared
- N set if (B - 1) is negative, otherwise cleared
- V set if B = 0x80, otherwise cleared
-
GRP_R16_01 (CMPD, CMPS, CMPU, CMPX, CMPY, XADDD, XADDU)
- let R = result[15:8]
- Z set if R = 0x01, otherwise cleared
- N set if (R - 1) is negative, otherwise cleared
- V set if R = 0x80, otherwise cleared
-
GRP_LEA (LEAS, LEAU, LEAX, LEAY)
- let R = (index[15:8] | index[7:0])
- Z set if R = 0x01, otherwise cleared
- N set if (R - 1) is negative, otherwise cleared
- V set if R = 0x80, otherwise cleared
-
Default (all other instructions)
- N = 1
- Z = 0
- V = 0
- N updated as above
- if (X[7:0] != 0) then Z is cleared, otherwise updated as above
- V = 0
- N updated as above
- if (U[7:0] != 0) then Z is cleared, otherwise updated as above
- V = 0
The undocumented Page-2 opcodes generally behave as the unprefixed opcode, with the additional cycle for processing and discarding the 0x10 prefix. There are a few exceptions where the prefix does trigger different behaviour:
Opcodes:
0x10 0x20 - XLBRA relative16 (5 cycles)
This instruction is the same as LBRA (0x16), except it take 5 cycles rather than 4 cycles.
Opcodes:
0x10 0x3E - XSWI2 (20 cycles)
This instruction is similar to SWI2 (0x10 0x3F), except it does not correctly set the E flag in the saved machine state.
Bus cycles:
cycle 1 - read prefix (0x10)
cycle 2 - read opcode (0x3E)
cycle 3 - read PC + 1
cycle 4 - dead cycle
cycle 5 - write PCL to system stack
cycle 6 - write PCH to system stack
cycle 7 - write UL to system stack
cycle 8 - write UH to system stack
cycle 9 - write YL to system stack
cycle 10 - write YH to system stack
cycle 11 - write XL to system stack
cycle 12 - write XH to system stack
cycle 13 - write DP to system stack
cycle 14 - write ACCA to system stack
cycle 15 - write ACCB to system stack
cycle 16 - write CC to system stack (note: E is not set explicitely)
cycle 17 - dead cycle
cycle 18 - read 0xFFF4
cycle 19 - read 0xFFF5
cycle 20 - dead cycle
Opcodes:
0x10 0xC3 - XADDD #immediate16.
0x10 0xD3 - XADDD direct.
0x10 0xE3 - XADDD indirect.
0x10 0xF3 - XADDD extended.
XADDD performs a 16-bit addition of the operand with D, and sets the Z,N,C,V flags in an identical manner to ADDD. The result is, however, not written back to D. So it's not idendical to ADDD. It could also be thought of as a CMPD where the ALU is set to ADD16 rather than SUB16.
Flags:
- Z - set if the result is zero, otherwise cleared
- N - set if the result is negative, otherwise cleared
- C - set if there is carry out of the 16-bit addition, otherwise cleared
- V - set if there is two-complement signed overflow in the 16-bit addition, otherwise cleared
Opcodes:
0x10 0x87 - XSTA #immediate8
0x10 0x8F - XSTY #immediate16
0x10 0xC7 - XSTB #immediate8
0x10 0xCF - XSTS #immediate16
These instructions are only partly functional; the lower 8 bits of the register is written to the lower 8 bits of the immediate operand.
Flags:
- N = 1
- Z = 0
- V = 0
- all other flags unchanged
Bus cycles (XSTY):
cycle 1 = read prefix (0x10)
cycle 2 = read opcode (0x8F)
cycle 3 = read MSB of the 16-bit immediate operand (and discard the value)
cycle 4 = write LSB of the 16-bit immediate operand with the LSB of the Y register
Bus cycles: (XSTS):
cycle 1 = read prefix (0x10)
cycle 2 = read opcode (0xCF)
cycle 3 = read MSB of the 16-bit immediate operand (and discard the value)
cycle 4 = write LSB of the 16-bit immediate operand with the LSB of the S register
These instructions are similar to the unprefixed versions, except the flag behaviour is simpler.
Flags:
- N = 1
- Z = 0
- V = 0
- all other flags unchanged
The undocumented Page-3 opcodes generally behave as the unprefixed opcode, with the additional cycle for processing and discarding the 0x11 prefix. There are a few exceptions where the prefix does trigger different behaviour:
Opcodes:
0x11 0x3E - XFIRQ (20 cycles)
This instruction is simular to the unprefixed XRES (0x3E), except it take an additional cycle and uses the FIRQ vector.
Flags:
- all flags are unchanged
Note: unlike a hardware FIRQ, the F and I flags are not set, and the full machine state is pushed.
Bus cycles:
cycle 1 - read prefix (0x11)
cycle 2 - read opcode (0x3E)
cycle 3 - read PC + 1
cycle 4 - dead cycle
cycle 5 - write PCL to system stack
cycle 6 - write PCH to system stack
cycle 7 - write UL to system stack
cycle 8 - write UH to system stack
cycle 9 - write YL to system stack
cycle 10 - write YH to system stack
cycle 11 - write XL to system stack
cycle 12 - write XH to system stack
cycle 13 - write DP to system stack
cycle 14 - write ACCA to system stack
cycle 15 - write ACCB to system stack
cycle 16 - write CC to system stack (note: E is not set explicitely)
cycle 17 - dead cycle
cycle 18 - read 0xFFF6
cycle 19 - read 0xFFF7
cycle 20 - dead cycle
Opcodes:
0x11 0xC3 - XADDU #immediate16
0x11 0xD3 - XADDU direct
0x11 0xE3 - XADDU indirect
0x11 0xF3 - XADDU extended
XADDU performs a 16-bit addition of the operand with (U | 0xFF00), and sets the Z,N,C,V flags in an identical manner to ADDD. The result is, however, not written back to U.
Flags:
- Z - set if the result is zero, otherwise cleared
- N - set if the result is negative, otherwise cleared
- C - set if there is carry out of the 16-bit addition, otherwise cleared
- V - set if there is two-complement signed overflow in the 16-bit addition, otherwise cleared
Opcodes:
0x11 0x87 - XSTA #immediate8
0x11 0x8F - XSTX #immediate16
0x11 0xC7 - XSTB #immediate8
0x11 0xCF - XSTU #immediate16
These instructions are similar to the unprefixed versions, except the flag behaviour is simpler.
Flags:
- N = 1
- Z = 0
- V = 0
- all other flags unchanged
The Motorola datasheet states the half-carry flag (H) becomes undefined after the following operations:
- ASLA/ASLB/ASL
- ASRA/ASRB/ASR
- CMPA/CMPB
- NEGA/NEGB/NEG
- SBCA/SBCB
- SUBA/SUBB
(Footnote 8 says: Value of half-carry flag is undefined)
In practice, in all these cases the half-carry flag is unchanged by the operation.
The Motorola datasheet states the SEX instruction clears the overflow flag. That is incorrect, the overflow flag is unchanged.
The DAA operation perform the decimal adjust by adding a correction factor:
ACCA = ACCA + correction
The correction factor is calcuated as expected in all cases, see Atkinson page 56 for details:
int correction = 0x00;
if (H == 1 || (ACCA & 0x0f) > 0x09) {
correction |= 0x06;
}
if (C == 1 || (ACCA & 0xf0) > 0x90 || ((ACCA & 0xf0) > 0x80 && (ACCA & 0x0f) > 0x09)) {
correction |= 0x60;
}
The Motorola datasheet states the overflow flag (V) is cleared after DAA.
In practice the overflow flag is updated as follows:
V = (the C new value) XOR (the new bit 7 of ACCA)
It's possible to chain multiple prefixes together. Only the first prefix is significant, all subsequent prefixes are discarded. Each discarded prefix adds one cycle to the instruction. This allows for arbitrarily long instructions that are not interruptable.