18

I figure this is fitting for Retrocomputing.SE because CPUs like the 6502 and Z80 and the PDP-8, and probably others, have undocumented instructions and will happily execute them, in contrast with serious and modern CPUs which will trigger some sort of "this can't continue" state.

On the nesdev wiki it talks about some games which use alternatives to the usual NOP. It seems a though those games could have used the actual NOP on the 6502 ($EA) or left that instruction entirely out of course. And some programs mentioned use the LAX instruction, but those could have used LDA and TAX together for to get the same effect (even though it's a bit slower). Similarly, SAX can probably be replaced by a (longer and slower) sequence involving TAX, AND and STA.

My question is if there is a known use of an undocumented instruction, where a documented/official instruction would not have done?

  • 3
    Don't underestimate the utility of saving single bytes or single cycles. For instance, on the Apple II, cycle-accurate code is used in disk routines and to split the screen between different graphics modes. – zellyn Oct 21 '16 at 3:18
  • 3
    for the purposes of anti-disassembly, yes - that's a known use of an undocumented instruction, where a documented instruction would not have done. – peter ferrie Feb 12 '17 at 4:10
22

In some cases undocumented instructions may be useful as shortcuts to accomplish tasks that are "difficult" to achieve with the official instructions.

Going with the Z80, it is common practice amongst developers to make use of the undocumented instructions that allow to access index registers IX and IY as pairs of 8-bit registers (so IXh, IXl, IYh, IYl). For example, how do you load A with the upper half of IX? With the official instructions you are forced to do something like this:

push ix
pop af

;But hey, I don't want to lose my flags!
push ix
pop hl
ld a,h

However there's an undocumented instruction that does exactly that, so why not using it?

ld a,ixh

By the way, a good resource for undocumented Z80 behavior information is the The Undocumented Z80 Documented document by Sean Young.

  • 2
    You should always ask 'why was the opcode undocumented?' It may not work under certain odd conditions, or it may have been reserved for replacement by another function in later versions. – amI Oct 20 '16 at 20:41
  • 2
    @aml In the case of Z80 all the undocumented instructions have proven to be reliable across all the manufactured processors (the mirrored instructions are a different story), but that's indeed a very good point. – Konamiman Oct 21 '16 at 7:50
  • 1
    @Konamiman what are mirrored instructions? – Wilson Dec 20 '18 at 9:00
16

More “serious and modern” CPUs than the 6502, Z80 and PDP-8 have undocumented instructions too, and they don’t necessarily cause the CPU to stop.

The Intel x86 line has quite a few instructions which were initially undocumented; one very famous instruction, which can not be replaced by documented instructions, is LOADALL, which has been discussed here already. Others include:

  • AAD, “ASCII Adjust before Division”, which takes an undocumented 8-bit immediate operand n and calculates AH × n + AL, placing the result in AX (the documented form only mentions multiplication by 10, to convert a BCD value in AX to its non-BCD form);
  • AAM, “ASCII Adjust after Multiply”, which takes an undocumented 8-bit immediate operand n and calculates AL ÷ n, placing the quotient in AH and the remainder in AL (the documented form only mentions division by 10, to convert a non-BCD value in AX to its BCD form);
  • SALC, “Set AL on Carry”, which sets AL to FF if Carry is set, 0 otherwise.

The first two were well-known early on (for example, Robert L. Hummel’s The Processor and Coprocessor documents them, although it gets AAM slightly wrong), SALC not so much. They stayed officially undocumented for a very long time, well into the mid-nineties. All three can be replicated using other instructions, but they are useful in their own right because they don't affect the flags in the same way as their official counterparts. The NEC V20 and V30 clones followed the documentation and ignore the operand to AAD and AAM, so code using the undocumented forms wouldn’t give the expected result on these CPUs.

The Intel 8086 and 8088 also had some unintentional, undocumented instructions, probably corresponding to bits ignored by the instruction decoder. For example, the 0x60–0x6F opcodes map to 0x70–0x7F. These alias opcodes have since been re-purposed, so 8086 code relying on them doesn’t work on newer CPUs!

Another 8086/8088-specific, undocumented instruction is MOV CS, op which updates CS from op and causes an unconditional long jump (without changing the instruction pointer). This didn’t survive for long, even C-MOS 8086 and 8088 CPUs no longer support it. Unfortunately some software relies on this...

You’ll find lots more information on these and other instructions on Robert Collins’ old Undocumented OpCodes site, in 86bugs.lst, and in the more recent investigation conducted by Michal Necasek and Raúl Gutiérrez Sanz.

  • These instructions very much look like they were not unintended side effects of the instruction decoder (as in the 6502 or Z80). – dirkt Oct 21 '16 at 5:57
  • @dirkt agreed, but I didn't get the impression the question was referring specifically to opcodes that were side effects of the instruction decoder. – Stephen Kitt Oct 21 '16 at 7:56
  • @dirkt: For AAD and AAM, I would guess that the Intel designers early on didn't know whether it would be more cheaper to have microcode for multiply/divide by ten, multiply/divide by the supplied operand but only iterate four times, or multiply/divide by the supplied operand, iterating the same number of times as a byte multiply. If the former would have been cheaper, the 0A byte in the opcode would have been superfluous, and if they'd known they were going to do the latter there would have been no reason not to document the opcode. – supercat Oct 21 '16 at 16:13
12

In many cases, undocumented opcodes were not deliberately created, but are merely the result of designers including the minimal circuitry necessary to create a specified set of opcodes. On the 6502, for example, consider how one might go about creating the instructions LDA, LDY, and LDX. Rather than handling the three instructions separately, it would make sense to have six bits of the opcode mean "load something with address mode __", and then use the remaining bits to control the enables of the registers.

While it would be possible to have each register-enable respond to one particular pattern of those last two bits, one can save a few transistors by saying that one register responds when bit 0 is high, one responds when bit 1 is high, and one responds when neither is high. As it happens, that's exactly what the designers of the 6502 did. The bit pattern 101mmmrr is the bit pattern for "load something to the register specified by using memory mode m" opcode. If rr is 00, it loads the Y register; if the lower bit of rr is 1 it loads A, and if higher bit of rr is 1 it loads X. Now for the undocumented part: If both bits of rr are 1, it loads both A and X.

Unfortunately, because the designers of the chip never realized that loading two registers with one instruction might be useful, the decoding circuitry for some other instructions will also respond to bit pattern 10101011 which would be "load immediate, enabling both A and X". As a consequence, while the most useful memory-addressing mode for loading both A and X together would be immediate, there is no usable "load A and X immediate" instruction.

I would expect that the biggest reason many such opcodes were never documented is that the chip designers probably never gave any particular thought to the possibility that bit patterns to which they had never attached any particular meaning might actually serve some useful purpose. What makes this particularly bizarre in the case of the 6502 is that there are a number of cases where instructions don't support an addressing mode, but an undocumented variation does. Two of the most notable examples:

  • LDX does not support the indirect, Y addressing mode, but LAX does. As a consequence, it's sometimes useful to use LAX even in cases where the value in the accumulator afterward is irrelevant, since it's two cycles faster than doing an LDA followed by a TAX.

  • DEC only supports absolute, abs+x, zero page, and zp+x addressing modes, but the bitwise OR of the DEC and CMP opcodes will act as a DEC that supports any addressing mode that CMP supports other than immediate, and which will set flags on the result in the same way that CMP would set flags based upon its operand. Thus, code which needs to perform a DEC using some other addressing mode can use "DCP", whether or not it needs the flags (though in many cases setting Z based upon whether the value was decremented to match the accumulator--rather than zero--can be a nice bonus).

DCP brings up an interesting point: the 6502 documentation specifies that the maximum time required to execute any opcode is seven cycles, and some system designs might rely upon the worst-case interrupt response time that would imply. The indirect-X and indirect-Y variations of DCP take eight cycles. The 6502 executes them without difficulty, but some systems might have trouble if the interrupt line is sampled just before the interrupt line becomes active, and the next instruction is an eight-cycle DCP. While it might be considered bizarre to design a system that has zero margin on interrupt response time, there were many cases where an interrupt source would be synchronous with the CPU clock, and where hardware to allow precisely as much time as the 6502 required for an interrupt response would be cheaper than hardware which could allow one more cycle.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.