12

The 6502's instructions have the form aaa-bbb-cc, where cc picks an instruction group. Group 01 is the most regular and easy to understand. In this group, aaa picks an instruction from the list

  • 000 ORA
  • 001 AND
  • 010 EOR
  • 011 ADC
  • 100 STA
  • 101 LDA
  • 110 CMP
  • 111 SBC

and bbb picks an addressing mode, from the list:

  • 000 (zero page,X)
  • 001 zero page
  • 010 #immediate
  • 011 absolute
  • 100 (zero page),Y
  • 101 zero page,X
  • 110 absolute,Y
  • 111 absolute,X

so this question is about the opcode 10001001, which is $89 in hexadecimal. This looks as though it could've encoded STA #imm, but it doesn't.

Now, the 6502 famously has many undefined opcodes which have been used by actual software, especially in the Commodore 64 demoscene and other places. These arise from the partial decoding of the opcode. It is known that the engineers who made the 6502 did not waste any logic trying to do some kind of illegal instruction trap or anything like that. So in my eyes, I think this instruction should just have the immediate addressing mode, just like any xxx-010-01 instruction, and also store the accumulator, like any 100-xxx-01 instruction. Obviously, it would not have been useful. But would have arisen from a straight-forward encoding of the 01 group.

So in a reference of illegal opcodes $89 appears to be NOP zp, which I imagine reads a byte from the zero page and ignores it.

I wonder why this instruction doesn't actually store the accumulator in the byte immediately after the fetched opcode. As I recall, the related 6800 and 6801 have an illegal store immediate instruction. But not the 6502. Was there a deliberate effort to avoid this instruction?

8
  • 3
    The concept of "store immediate" is pretty strange to modern-day sensibilities... but you're right, it would make perfect sense that it would store the accumulator into the immediate value slot after the instruction. Commented Sep 28, 2022 at 18:57
  • 1
    @Raffzahn I mean the byte(s) after the instruction, where the immediate value is. No, x86 does not have "store immediate" Commented Sep 28, 2022 at 20:06
  • 5
    @Raffzahn After the opcode, to be pedantic. Storing an immediate value to an address specified by an immediate value is not in any way the same as storing a register to the immediate value itself which is what this hypothetical STA # instruction would do. Commented Sep 28, 2022 at 21:00
  • 4
    @Raffzahn well that's what this question is asking about, isn't it!! Commented Sep 28, 2022 at 21:28
  • 7
    @Raffzahn and I did not question how it should perform, merely commented it would be a very strange instruction if it did perform the obvious way. Why are you arguing? Commented Sep 28, 2022 at 21:36

3 Answers 3

21

Applying the same decoding logic to $89 as is applied to $85, $A9, and $A5 would result in an instruction whose external behavior would match LDA #imm, but which doesn't update any registers or flags. Not coincidentally, an NMOS 6502 behaves in exactly that fashion.

The 6502 generally decides what operation is going to be doing on each memory cycle before the end of the read of the previous cycle; the primary exception to this is that "perform an operation with a specified low address, along with a high address fetched from the data bus" and "perform an operation with a specified high address, along with a low address fetched from the data bus" are available as operation choices(*)

The distinctions among LDA #imm, LDA zp, and STA zp are all made while the second byte of the instruction is being fetched. This is fine for any instruction which would read an immediate operand, since the CPU will know what to do with the data by the time it arrives on the bus at the end of the second cycle. In order for STA #imm to work, however, the CPU would have to know before the beginning of the second cycle that it would need to perform a write. There's no way the CPU can go back in time after it has fetched the operand byte and retroactively change that read to a write. While it might be theoretically possible to have the instruction insert a write cycle, doing so would eliminate any benefit that STA #imm could have offered over STA zp. If STA #imm, STX #imm, and STY #imm could execute in two cycles each, that could reduce the cost of saving registers on interrupt entry by three cycles compared with using zero-page forms. If the instructions existed but took three cycles each, however, using zero-page forms would be just as fast but more convenient.

Another way of looking at things might be to say that immediate mode doesn't instruct the processor to fetch the byte after the opcode and do something with it, but instead instructs the processor that logic which would normally fetch a memory operand into a temporary register should instead do nothing, leaving that register holding a byte that was blindly fetched without knowing what if anything it would be used for. Store instruction piggy-back on the memory-access logic used for other instructions, except that they assert R/W during what would otherwise be the final memory fetch. Since the memory-access logic does nothing during LDA #imm, it likewise does nothing during STA #imm.

(*) These are, of course, fundamental to the efficient operation of the machine, since in most programs, the majority of cycles that aren't code fetches will fit one of those patterns. When processing LDA (zp),y, for example, the third cycle combines a zero high byte with a newly-fetched low byte, and the fifth combines a newly-computed low byte with a newly-fetched high byte). Of the cycles that aren't code fetches, only the fourth cycle and (if present) sixth cycle won't load half of the address bus with newly-fetched data.

7
  • 1
    @Raffzahn: What do you mean "not including the reason"? In order for the 6502 to process STA #imm as a store, it would have to be able to select whether the second cycle is a read or write based upon the value that was read in the first cycle, but the 6502's circuitry will already be committed to performing a read on the second cycle before data from the first cycle is available.
    – supercat
    Commented Sep 28, 2022 at 16:13
  • Shouldn't it be "change that read to a write" in the second paragraph? Commented Sep 28, 2022 at 16:55
  • 2
    I upvoted, but I found the answer pretty confusing. I didn't understand where the explanation was going until the first sentence of paragraph 3. I wonder if you could summarize at the top, perhaps along the lines of "there is separate instruction-fetching and data-access logic, and immediate operands are handled by the instruction-fetching logic, which only does reads"—assuming that is accurate.
    – benrg
    Commented Sep 28, 2022 at 17:16
  • 4
    @benrg: The issue isn't that there's separate logic, but rather that the CPU's decision of what to do on the cycle following an opcode fetch must be made before the CPU can know what the opcode will be. The designers of the 6502 decided to use that cycle to perform a fetch of the byte following the opcode, on the basis that doing so would be useful most of the time, and would almost always be harmless even in cases where it isn't useful (it could cause problems if e.g. address $BFFF held an RTS and address $C000 was mapped to read-triggered I/O, but such issues would be rare).
    – supercat
    Commented Sep 28, 2022 at 18:50
  • 3
    @Raffzahn: If the designers of the 6502 had thought store immediate would be useful, there are two approaches they could have taken to supporting it. Supporting two-cycle store-immediate instructions while satisfying timing constraints would likely have been possible but expensive. Supporting three-cycle store-immediate instructions would have been cheaper, but the time penalty would undermine the usefulness of those instructions. In neither approach would the benefit offered by the approach exceed the cost thereof.
    – supercat
    Commented Sep 28, 2022 at 18:58
8

TL;DR:

Why doesn't the NMOS 6502 have the illegal instruction, STA immediate?

It doesn't, because $89 is none of the undecoded ('illegal') operations, but decoded as NOP by design.

So in a reference of illegal opcodes $89 appears to be NOP zp, which I imagine reads a byte from the zero page and ignores it.

That reference seems off. $89 is a NOP #imm, much like already implied by your question. It's executed in two cycles, there is no ZP access of any kind.

As I recall, the related 6800 and 6801 have an illegal store immediate instruction

That would be the infamous STA* instructions (*1), except that these do not store the value into the immediate field of the instruction, but after the instruction, at the location where the next instruction would be - except execution continues after that (*2). The immediate value gets ignored.

Obviously, it would not have been useful.

Not really, so why should they have bothered to implement it, as doing so would have needed to add at least one (more like two or three) additional ROM entries (*3).


So Why Is It That Way

When it comes to the 'illegal' opcodes (*4) it helps to differentiate between the 'tamed' ones and the 'wild' beasts. The differentiation becomes quite obvious when looking at the opcode chart:

Opcode Chart with 'illegal' opcodes grouped

(Taken from Norbert Landsteiner's 6502 "Illegal" Opcodes Demystified page - with some colouring added (*5))

By marking the various types of undefined opcodes it becomes clear that there are three different groups:

  • The Good (Green) are in tamed as NOPs by design
  • The Bad (Red) are the wild ones everbody talks about
  • The Ugly (Pink), sending the CPU into an infinite T1 loop

The questioned $89 falls into the green group. They are well decoded withing their group (CC=00/01/10) and executed as NOP by design, because they do not make any sense - $80, STY #imm, is a similar case.

There are two exceptions to this, $8C SHY abs,X and $8E SHX abs,y (*6)

The read ones, mix up functionality from other groups as their group (CC=11) is simply not decoded at all. They are the real orphans of the 6500 design - and like orphans they look for surrogate parents in group CC=01 and 10 (*7).

The pink group in turn are somewhat similar. While their group line (CC=10) is decoded, there are no entries in the decode ROM except for AAA=1xx BBB=x00 (light green), which gives that $A2, LDX #imm its 3 siblings work as intended. Having no entries makes them stuck in a never ending T1 cycle, reading the second byte over and over until freed by RESET.

Bottom line: $89 has been made a NOP on purpose by design. It might be undefined, but is not really a member of the 'illegal' bunch.


1 - STAA ($87), STAB ($C7), STX ($8F) and STS ($CF), each being two byte opcodes with the second byte being ignored, store their register after the instruction and continue with execution thereafter. This means the STA variant would be a 3 byte structure, where the value of A or B would be stored in the third, while ST* would be 4 bytes storing IX or SP into bytes 3 (low) and 4 (high).

*2 - Now, that would have been a quite interesting use case for self modifying code, kind of an EX-instruction where the to be executed instruction had to be prepared in an accumulator, for single byte, or IX, for two byte instructions. Not that it would have saved code or make it more elegant, just interesting.

*3 - It would at least need to suppress the increment of the PC, like done with a NOP, during the second cycle, followed by the store and an increment after the store, so maybe 3 additional lines or ~2.5% additional ROM for an instruction with rather dubious usability?

*4 - Naming is a real crux here, as the term 'illegal' implies something that not really exists in an instruction set. Opcodes not defined to carry out some operation (aka 'unused') are simply that: Undefined/Unused Opcodes.

The story could end here, except that the 6500 design added an additional pitfall by not straight making all unused do nothing (like the 65C02 later corrected), but some of them were left undecoded, resulting in additional functionality. Not intended but deterministic - as long as thedcoder was not changed.

*5 - Norbert Landsteiner's page goes way beyond listing the 'illegal' opcodes, but dives deep into the structure, following Neil Parkers well known The 6502/65C02/65C816 Instruction Set Decoded

*6 - Personally I would see them as leftover/unfinished implementation of STY abs,X and STX abs,Y, but that might be a different story.

*7 - I know that this is due the way that the groups are decoded into three select lines within the decoder and 11 simply activating the group 1 (CC=01) line as well as the group 2 (CC=10) line. adding one gate here could have prevented all of that and saved us all the hype about 'secret' opcodes and so on.

5
  • 3
    How do we know the ‘green’ NOPs are by design? Is there a design document confirming them as such? Commented Sep 28, 2022 at 19:17
  • @user3840170 Well, is there one that tells otherwise? Beside, looking at the decode ROM does show that there are default entries that let them terminate that way - as usual, code is documentation :))=
    – Raffzahn
    Commented Sep 28, 2022 at 19:23
  • 1
    Opcodes of the form xxx0 mm01, including 1000 1001, all behave identically prior to the cleanup cycle, save for the fact that 1000 mm01 turns the last post-decode read, if any are done into a write with the accumulator on the bus. Instructions of the form xxx0 1001, including 1000 1001, all advance the program counter during the operand fetch, and then proceed to the cleanup/start next operation state without doing any post-decode reads, leaving the value that was fetched during the cycle after the opcode fetch in the temporary register.
    – supercat
    Commented Sep 28, 2022 at 20:41
  • 1
    @Raffzahn, it might improve your answer to include a relevant snippet from the microcode, showing STA immediate being "dummied out".
    – wizzwizz4
    Commented Sep 29, 2022 at 11:40
  • I suspect the change in how the 65C02 handles undefined opcodes was motivated by the fact that it's CMOS. In NMOS, if something will pull down a signal in all cases where it needs to be low, and nothing will pull it down in cases where it needs to be high, one can ignore cases where the value doesn't matter. In CMOS, one must identify all cases where nothing will pull down a signal and ensure that it gets pulled up in those cases, even it would have been acceptable for the signal to be pulled down instead.
    – supercat
    Commented Nov 5, 2023 at 18:04
3

For me, the instruction:

STA #$00

as an example, makes no sense. Where does $00 get stored? There is no address. Compare that with:

LDA #$00

Which loads the immediate value of $00 into the A register.

All the STA instructions use an addressing mode other than immediate which is not applicable for any register.

Your proposal for storing the immediate value in the address after the opcode would result in either "self-modifying code" if running out of RAM or basically a NOP, as was almost always the case, if the code was in ROM.

Because of this the designers of the 6502 simply did not decode that opcode even though they could have and, as you noted, trapped it or even done something else with it. But the design goals of the team were make make a part that would be an order of magnitude less expensive than what was on the market at that time. In order to be successful, and they were, the hardware design had to be as minimal as possible.

15
  • 4
    Of course the instruction makes no sense, but the question was why did they seem to go through the effort to remove it. The 6800 designers just left it there, and left it undocumented. I'm asking why the 6502 designers didn't do the same Commented Sep 28, 2022 at 13:37
  • 4
    There have been places I would have used the instruction had it existed. Interrupt entry time could have been reduced by three cycles versus using zero-page mode if the interrupt vector jumped to RAM containing STA #x / STX #x / STY #x. The reason the instructions don't seem to be decoded is, I think, that any such decoding would occur after the CPU has already committed itself to the second cycle being a read.
    – supercat
    Commented Sep 28, 2022 at 14:45
  • 1
    RE: "Where does $00 get stored?", STA doesn't store its operand, it stores the accumulator. You'd expect a STA immediate to store the accumulator in the byte following the opcode, orthogonally to all other stores and to all other immediates. Commented Sep 28, 2022 at 16:29
  • 3
    @OmarL: All instructions on the 6502 have the same behavior during the first two cycles, save only for the fact that some do not modify PC during the second. The logic for processing an immediate mode operand isn't "fetch a byte at the PC address and increment PC", but rather "don't bother doing anything to fetch an operand, since it will already have been fetched by the time the instruction was decoded".
    – supercat
    Commented Sep 28, 2022 at 16:50
  • 2
    @wizzwizz4 Not sure. IMHO it does not really tackle the question. The question is why a certain non existing Operation doesn't perform what one might assume from knowing that undecoded 6502 instructions often perform part of what would happen at that code position if decoding is assumed regular. But the answer muses if that instruction would be useful or not - something already denied by the Question asked. So it's not wrong (can't be wrong when talking about unknown), but simply missing the topic set by the question. Thus some might see it as not useful in this context.
    – Raffzahn
    Commented Sep 28, 2022 at 19:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .