6502 “Illegal” Opcodes Demystified
masswerk.at
masswerk.at
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-inst...
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.
Mask ROM usually is just used as a big lookup table which replaces the combinatorial logic by just explicitly matching each input bitstring to the correct bitstring to control the ALU, etc. (along with some sort of state-machine for VLIW ISAs).
Moving to a ROM-style architecture makes development a little more straightforward because modifying instruction decoder only requires burning a different bitstring into a PROM rather than redesigning a combinatorial logic circuit and then manufacturing an entirely new chip.
A ROM-based architecture probably makes microcode updates easier to design into a CPU as well, because you can basically just overlay the microcode over the existing ROM and patch, at runtime, essentially any part of the instruction decode (or whatever else you've designed in the ROM) on an unmodified production IC.
I imagine that the combinatorial logic approach isn't really used anywhere today (for new designs) because of how "cheap" gates are considered to be in a digital IC design, but I could be entirely wrong about this (I'm not in the industry).
What's being discussed is at the same level as microcode, whereas most of the distinctions you're making apply to the normal code storage ROMs. For instance modern microcode is nearly never a PROM.
Additionally combinatorial logic is generally made out of cheap gates, the two concepts aren't in conflict, but instead orthogonal.
My understanding (as explained to me by someone who did work on some CPU-like ASICs) was that they were able to produce the early versions of ICs with some form of "PROM" (possibly not the correct term) which could then be one-time programmed so that they didn't have to pay for a new set of masks if they wanted to change any of their instruction decode (or whatever they had wired into the "PROM") logic.
All they had to do was burn a new version into a blank chip and test/use it from there.
The company in question was producing low-volume, high cost products for a narrow niche and so I don't know if they had to throw away some die space to have this "PROM-like" feature and if that is still how things are done today.
I my comment earlier about EDA tools being able to "simplify" the ROM into the die was probably a paraphrasing of what I was told about a tool. The tool (or workflow) allowed a designer to go from a working prototype microcode ROM to a hardcoded gate circuit (presumably by the same sort of process that one can go through to simplify a logic statement that is in disjunctive-normal form).
This was information passed on to me second hand from quite a few years back now so I probably should've tried to look up some primary sources before writing my comment! Oh well.
The 6502 approach of just executing some random (but deterministic) logic leads to a situation where the "illegal" opcodes cannot be expected to be unused (programs actually use them), and where assigning them new semantics effectively breaks compatibility with existing programs. That’s why CPUs usually invest in the extra logic necessary to give those opcodes undesirable behavior (i.e. traps).
It preceded the Z80 by a year or two.
Something I found especially interesting is the decision to not implement "STX abs,Y" and "STY abs,X", while the decoding matrix would have easily provided for this. Was this to keep the instruction set arbitrarily simple (as in explicitly not orthogonal)? Or was there a more serious architectural conflict? Or was this just a legacy from the 6800, which had just a single index register, thus lacking a similar option?
(I added a note on this to the article.)
This [1] summarizes it. Here [2] is an interview by Computer History Museum of Chuck Peddle. It matches up with the first chapters in Bagnalls book very closely. From about 50 minutes in he talks about his meetings w/Motorola customers that told him the 6800 was too expensive. From about 58m in they talk about basically setting a size metric for the chip to meet a low enough price point. So the starting point was what they could fit in small enough space.
You might also like the Computer History Museum interview w/Bill Mensch [3]
[1] http://www.cpushack.com/2013/08/03/mos-technology-mcs6501-pr...
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.
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
Mitch Bradley
Bradley Forthware
P.O. Box 4444
Mountain View, CA 94040
https://groups.google.com/g/comp.lang.forth/c/cAFn_jDDu50/m/...>Mitch swears the PO box number was totally coincidental!
I don't have Obsessive Compulsive Disorder. I just think of it as Obsessive Compulsive Order.
Even though all its opcodes weren't 3 characters, the PowerPC really appeals to me as a clean, powerful, orthogonal, crystalline clear instruction set. I always visualized the PowerPC as Superman's Fortress of Solitude, the ice crystal palace with all the crisp 90 degree angles.
Despite being incredibly dissimilar, the relative regularity of the 6502 and 68k felt familiar - a small set of rules and patterns flowed naturally into implementing quite complex code in a way it didn't on the x86. It's remarkable how much those things matters.
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:
(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.)
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. :-)
Mnemonics are arbitrary, there is no other way.
Not that I have any use for one.
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.)