6 ms·
6502 “Illegal” Opcodes Demystified
- pandemic_region 5y agoWhat baffles me a bit is that, even in this day and digital age, there is apparently no employee left from MOS Technology that still has the old dusty manual - for internal use only of course - with all the opcodes documented and explained. Digital archeology is really a thing.
- flohofwoe 5y agoI bet there wasn't one. The undocumented instructions are just unintended side effects of how the hardware works, not a ploy to give some programming teams an advantage via secret knowledge ;)
- dkersten 5y agoYeah, it was my understanding that the opcodes directly encoded what we would nowadays call microcode, that is, bits which selected which CPU component acted on which data[1], so these illegal instructions are just unused bit patterns that select various operations just by having those bits set, not something that was ever intended to be actually used. So it would make sense that they're not documented, since they're entirely incidental. [1] https://www.youtube.com/watch?v=dXdoim96v5A https://www.youtube.com/watch?v=dXdoim96v5A
- nicetryguy 5y agoThere is a story about how BIT sets bit 6 into the overflow flag, and this was documented on a piece of parchment paper in pencil! http://www.6502.org/tutorials/vflag.html http://www.6502.org/tutorials/vflag.html Either way, every transistor in the processor has been broken down completely, and even recreated in a visual simulation: http://visual6502.org/ http://visual6502.org/
- masswerk 5y agoThe overflow flag and its intrinsics was quite a mystery for a while. (Related fun fact: There is no such thing as a break flag, it just gets inserted, whenever the the status register is pushed to the stack by an instruction, i.e. by software, as opposed to a hardware interrupt. This is also, why it is commonly shown as 1, because this is the value it would show up with, if there was a PHP or BRK instruction at the given time, which is also: always. But this is really just a virtual state and there is no such flag in the CPU.)
- DonHopkins 5y ago>(Here, for the “illegal” opcodes, we use the mnemonics used by the DASM and ACME assemblers, with the exception of “USBC” for instruction code $EB, where these use plain “SBC”.) 6502 assembly code is more sleek and beautiful and perfectly justified than most other assembly code, because all the opcode mnemonics are exactly three letters. In comparison, assembly code in other CPU instruction sets looks so rough and messy, like the code was torn off along the right edge. Kudos to the authors of that guide for respecting the lovely three letter convention for the illegal opcode mnemonics.
- masswerk 5y agoI actually considered inventing a new three-character one for "USBC" (maybe "SNC" for "SBC+NOP"?), but thought it would be better to not to add to the existing confusion regarding the names of these undocumented instructions. (Some of them have a long history, not all of them are perfectly chosen, but they are what they are.) The same applies to "ANC" and "ANC2": is the distinction of these externally similar instructions really worth ruining the simplicity and beauty of the three-letter convention?
- stevesimmons 5y agoI started as a teenager coding 6502 assembly on a Vic20. The three letter opcodes definitely made my code look beautiful. Z80 assembly a few years later was a real disappointment. No doubt the instruction set was more powerful. But the code always looked ugly to me. I've never forgotten my love for 6502 assembly. My mobile number ends with the 4 digits '6502'. <3
- kbmunchkin 5y agoThat's a cool mobile number! Was it intentional?
- DonHopkins 5y agoAlmost as cool is the P.O. Box that Mitch Bradley coincidentally obtained for his Forth company: Mitch Bradley Bradley Forthware P.O. Box 4444 Mountain View, CA 94040 https://groups.google.com/g/comp.lang.forth/c/cAFn_jDDu50/m/qeWZhkmnfpsJ https://groups.google.com/g/comp.lang.forth/c/cAFn_jDDu50/m/... >Mitch swears the PO box number was totally coincidental!
- flohofwoe 5y agoIf anybody is wondering why those undocumented instructions exist: https://www.pagetable.com/?p=39 https://www.pagetable.com/?p=39 ...it's basically a tolerated side effect of the very simple table-based instruction decoding on the 6502. The main design goal of the 6502 was to create a very cheap yet practical CPU (compared to more complex and more expensive CPUs like the Z80), and one way to achieve this was to simplify the instruction decoding with a simple "switch board" even if this means that there's nonsensical "cross-talk" for some opcode bit patterns. Here's also a very nice blog post by Ken Shirriff about the instruction decoding differences on ARM1, 6502 and Z80: https://www.righto.com/2016/02/reverse-engineering-arm1-instruction.html https://www.righto.com/2016/02/reverse-engineering-arm1-inst...
- wruza 5y agoThat was my first thought when seeing what these opcodes do. Just a result of a random mix of various parts of cpu in action. Thanks for confirming it!
- masswerk 5y agoThis is also, what this article is actually about. It's just from a more "hermeneutic" perspective, rather than a bottom-up approach based on an analysis of the hardware.
- nexuist 5y agoIt was my understanding that this sort of combinatorial logic is used by every CPU's control unit - is this outdated? What do modern chips use now?
- flohofwoe 5y agoI don't know :) But for instance on the Z80, even though there's a clear opcode bit pattern (see http://www.z80.info/decoding.htm http://www.z80.info/decoding.htm), any holes in the instruction set that make no sense have been "plugged" with official special-case instructions. For instance the opcode 0x76 should be "LD (HL),(HL)", but instead this opcode has been reused for the HALT instruction. The Z80 has undocumented instructions too (for instance in the prefix-ranges for the IX and IY indexed instructions), but those behave the same as documented instructions, except that they cost more cycles. This seems to indicate that the instruction decoding on the Z80 works quite a bit different than on the 6502.
- _Microft 5y agoSo you found similarities between the instructions, arranged them in a pattern that preserved it and made predictions from there. A bit like what Mendeleev did with natural elements to create the periodic table. Cool work, thanks for sharing!
- masswerk 5y agoIt's just another (hopefully somewhat systematic) perspective on a known phenomenon, though. (OP here.) P.S.: What might be of minor interest is that this may help decide, whether it's "ROL A" (as in older assemblers) or just "ROL", etc. (as in some of the more modern ones). We may see that there is a slot for "ROL A" and another one for "ROL implied", which is a NOP. As is the case with all the other accumulator instructions. – However, brevity is still a thing. :-)
- h2odragon 5y agoI like it as an example of inferential process; here's what we can see the chip do in response to inputs, here's what we think that must mean for its design. With the ability to go look at the hardware and many explanations of it to go look at later and see how and why the observed effects happen.
- andi999 5y agoDoes anybody know if the mnemonics can be derived from the opcode? (like lda). I remember I was looking for a table of a dissambler, I couldn't find one, and was always wondering how this is implemented.
- dirkt 5y agoNot sure what you mean, but all 6502 disassemblers I've seen use a table to get the mnemonic (and the addressing mode) from the opcode. Mnemonics are arbitrary, there is no other way.
- ectopod 5y agoYou can convert from an opcode to an index into a lookup table of mnemonics quite efficiently, and vice versa. You can also pack a single three character mnemonic into two bytes, so the lookup table may be hard to spot. Assemblers and disassemblers running on 6502 used to do this because space was at a premium.
- vidarh 5y agoNow I want to golf a 6502 opcode -> mnemonic decoder... Not that I have any use for one.
- satoshinm 5y agoThese undocumented 6502 opcodes were very useful during the 90s, when developing Game Genie codes for the Nintendo Entertainment System. GG codes were essentially[1] single-byte ROM patches. For example, the code AAAAA would patch address 0x8000 to 0x00 (I have a web-based decoder (and Node.js module) for those interested [2]) The Galoob Game Genie device only let you enter 3 codes at a time (unless you chained together multiple Game Genies), so minimizing the number of bytes you have to change to make the code work was a worthwhile goal. If your effect requires 3 codes, a typical player will not be able to use any other code. Modifying only one byte is ideal. How do unofficial opcodes help here? Well, the official opcode list is quite limited. Undocumented/illegal opcodes help in at least two ways: Multi-byte no-operations: want to remove an instruction completely? You can NOP the first byte, the opcode, but this leaves the operands (if there are any), desynchronizing the instruction stream (sometimes you can get away with this and it produces interesting effects, however). Fine, so you can add one or two more codes NOP'ing out the operands, but then your effect is 2 or 3 codes long! Undocumented codes to the rescue. DOP and TOP, double-nop and triple-nop, turn this 2 or 3 code effect into only 1 code. Although to be fair, there is another workaround: patch to a legitimate opcode, which has an operand of the same size (same or similar "addressing mode"), but an effect which does not meaningfully impact the program operation. Frequently this was possible, with some effort, but DOP and TOP is nonetheless a cleaner solution. Another benefit is combining multiple operations into one, also for a more compact code. Many of the undefined opcodes exist, as I understand it, due to "don't cares" in minimizing the instruction decoding logic, hence they combine multiple operations, sometimes usefully so (sometimes not): ASR = AND + LSR ANC = AND + ASL XXA = AND + AND ARR = AND + ROR DCP = DEC + CMP ISC = ISB + INS LAS = LDA + TSX LAX = LDA + LDX LXA = STX + AND RLA = ROL + AND RRA = ROR + ADC SAX = AND + AND SBX = CMP + DEX SHA = AND + AND SHX = AND + AND SHY = AND + AND SLO = ASL + ORA SRE = LSR + EOR TAS = AND + AND etc The multiple-store (Sxx) and load (Lxx) operations most useful for this purpose, from my recollection. I think the most I used was LAX, which would load the operand from memory into both registers A and X, in one opcode. The combined store and arithmetic opcodes in theory could be useful though in practice many were unstable. [1] Technically, GG codes patch the lower 15 bits of _CPU_ addresses, which can map to multiple ROM addresses depending on the mapper hardware in the cartridge. But this effect is difficult to usefully harness (though it is possible) to patch multiple ROM addresses you want because they have to line up exactly, in practice on these games the coder will use an 8-letter code to restrict the patching to one ROM bank (example: the famous SLXPLOVS = 1123?de:bd, meaning "if CPU address 0x9123 sends data 0xde, replace with 0xbd - replacing the DEC opcode with LDA, disabling decrementing the player life count variable when they die, resulting in the "Infinite Lives" effect on Super Mario Bros. 3. Galoob codemasters didn't use undocumented opcodes in their official GG codes as far as I know, but they were useful in homemade amateur codes.) [2] https://satoshinm.github.io/nescode/ https://satoshinm.github.io/nescode/
- cellularmitosis 5y agoI just recently started learning 6502 asm and I've started building a small collection of katas: https://gist.github.com/cellularmitosis/6f8e835efd856710f3019aec22ce3247 https://gist.github.com/cellularmitosis/6f8e835efd856710f301...
- diydsp 5y agoNice! I might be willong to collab. What i would like is a set of block copy routines, since there are so many variations... e.g. src/dest in zero page, same page, distant page, etc. I'm tired of re-writing them each time :)