7

Parts of the 6502 opcode map are fairly regular, and parts of it aren't. One bit that strikes me as really weird is the decoding of LD_ instructions, in relation to the decoding of other ALU instructions.

In general, opcodes of the form xxxMMM01 or 10xMMMxx use addressing mode MMM, where mode 010 is immediate. While processing 100010xx in that fashion would not be terribly useful [the pattern would suggest they should be ST_ #imm], processing 101010xx in that fashion would seem like the most straightforward way of handling LDY #imm and LDX #imm. Curiously, though, those opcodes get mapped to TAY and TAX instructions, requiring LDY #imm and LDX #imm to be moved to opcodes 10100000 and 10100010.

Did anything in the 6502's hardware make it easier to disable the "load-immediate" sequencing for "101010x0" but enable it for "101000x0", while making the former instruction output the accumulator on the internal bus from which X and Y are latched, than to simply put "LDX #imm" and "LDY #imm" next to "LDA #imm" in a fashion similar to all the other supported operand forms?

9

All of the instructions that use the A register and an immediate operand use an encoding matching the pattern of xxx01001. The rest of the immediate instructions use either X or Y as the register and are in the pattern 1xx000x0. All opcodes that fit either of these patterns use immediates or are undefined.

On the other hand, TAY and TAX are grouped together with other instructions that have an implied operand, including the shift instructions that use the A register, all matching the pattern xxxx10x0. This pattern doesn't include all implied instructions, but all opcodes that match this pattern either have implied operands, shift the A register, or are undefined.

So the position of LDY immediate and LDX immediate opcodes doesn't seem weird to me, they're off in their own section of the opcode table separate from the immediate instructions that use the A register. Similarly TAX and TAY are in the section of the opcode table with other similar instructions.

To put it another way, the pattern you've noticed only works for opcodes that match the pattern xxxMMM01. It doesn't work for 10xMMMxx where MMM is 010 because none of the xxx01000 and xxx01010 opcodes use immediates, only the xxx01001 opcodes do.

  • I guess that makes sense, though opcodes that match the "immediate XY" pattern end up being inconsistent with regard to how they select X or Y. I would think it would have been easier to say that everything of the form 00xxxxxx is a single-byte instruction, and everything else has its format determined by three of the remaining bits. Use the encodings for eight of what would otherwise be store-immediate or read-modify-write immediate instructions for branches (or use all nine and add unconditional branch), and three otherwise-unused opcodes that would include a two-byte address for... – supercat Apr 16 '18 at 21:50
  • ...jmp, indirect-jmp, and maybe jsr. Such a design approach may be easier when looking at a whole instruction set than it would be if the instruction set was being designed concurrently with the hardware. I wonder what the actual design approach was? – supercat Apr 16 '18 at 22:02
7

Parts of the 6502 opcode map are fairly regular, and parts of it aren't. One bit that strikes me as really weird is the decoding of LD_ instructions, in relation to the decoding of other ALU instructions.

Well, for one, LDX/LDY do not use the ALU, but more importantly, the way you decode it is a bit off. See below for a more detailed view on the 6500 encoding.

The LDX/LDY immediate encoding is fine. The quirk within the transfer instructions is at TYA which is 98 instead of 88. And there is no technical reson why it has been done that way. There are similar other encodings, especially around the increment/decrement for the index registers.


6500 Opcode encoding

The format isn't built the way you assume. While the general structure follows a 3-3-2 stucture like

aaa-bbb-tt

reading it requires to start at the end with

tt defining an instruction group

This is even visible the way the decoder hardware is done, as tt is not taken as seperate bits like all others, but turned into 3 lines for the three groups used, thus being the primary differentiation for all decoding.

(To simplify this, lets push group 0 (tt=00) to the end.)

Group 01 is the most simple. Here aaa selects one of 8 instructions (ORA, AND, EOR, ADC, STA, LDA, CMP, SBC) and bbb defines an addressing mode ((ZP,X), ZP, IMM, ABS, (ZP),Y, ZP,X, ABS,Y, ABS,X). Drawing this up(*1) will give a filled table with one empty spot at 89, where the nonsensical STA IMM would end up (*2).

Group 2 (tt=10) uses a similar encoding with aaa as instruction and bbb as addressing, just with different opcodes (ASL, ROL, LSR, ROR, STX, LDX, DEC, INC) and some addressing rearranged (IMM replacing (ZP,X) and ACC IMM) or not used (IMM, ZP, ACC, ABS, na, ZP,X/ZP,Y, na, ABS,X/ABS,Y). Here the STX A and LDX A are occupied exactly by TXA and TAX. Similarly, the 'unused' addressing 110 is used with STX/LDX to form TXS and TSX.

Group 2 is also the point where they started to screw it up, as 9E, where STX ABS,Y should be, is left out. And while the logical position for DEC A is filled by DEX, and NOP resides where a INC A could be (*3).

Group 3 (tt=11) is the most simple of all, as it doesn't exist :))

Group 0 (tt=00) finally is where it gets crowded. At first it's much like group 2. The instructions are now (na, BIT, na, na, STY, LDY, CPY, CPX) while the general addressing modes are the same as with group 2 plus REL and FLAG(IMM, ZP, ACC, ABS, REL, ZP,X, FLAG, ABS,X).

Addressing REL now overwrites (in a table for group 0) the instruction with BRx and FLAG with flag manipulation - with the exception, that CLV ended up where SEV would be if it existed - and TYA taking up its space.

Similarly, the stack operations lie diagonally to the encoding - and again TAY is screwing up the somewhat regular placement of increment/decrement for the index registers.

JMP does follow the scheme in so far as the 01x.011.00 encoding used the ABS addressing.

The remaining instructions (BRK, JSR, RTI, RTS) fall completely out of context. But they show at least some inherent logic, especially when considering that BRK is internally fed into the decoder also whenever a hardware interrupt occurs.


*1 - Drawing tables on SE sucks - so please use your imagination.

*2 - A hole filled up by the 65C02 with BIT IMM, since the 'rightful' spot 20 was taken by the irregular JSR.

*3 - as a result the 65C02 had to put INC A/DEC A onto new places.

  • 1
    I think you mean 20 was taken up by JSR. – supercat Apr 17 '18 at 14:48
  • I guess what puzzles me is why instructions of the form 1iimmmxa where xa isn't 00 weren't interpreted as [st/ld/cp/??][axy] with "normal" addressing mode mmm, which would have eliminated the need to do anything special with them. There's plenty of room elsewhere in the opcode map (including the entire group 11) to accommodate anything else that would conflict with such decoding, and decoding instructions that are of the form 1iimmmxa but not 1XXXXX00 in such fashion would have eliminated the need for all other decoding logic associated with ldx/ldy/stx/sty/cpx/cpy. – supercat Apr 17 '18 at 14:53
  • 1
    @supercat, I think you may be making the same mistake as many others, that the bit positions somehow indicate the operation to be carried out. That would be so with a simple gate-based decoder but the 6502 used a lookup table on the T-state and a majority of the opcode, and the lookup table itself dictated which "micro-actions" took place. That allowed for arbitrarily complex mappings from bits to actions. – user6464 Nov 15 '18 at 6:06
6

If you really want to understand why things were the way they were on the 6502, I'd suggest looking into 27c3: Reverse Engineering the MOS 6502 CPU on YouTube (or search for that text if the video ever disappears).

Starting at about 25 minutes into the video (but I urge you to watch the whole thing), it details the impressive work of Greg James, Barry and Brian Silverman and Ed Spittles, in deconstructing the inner workings of the 6502. This involved the use of hot acid to remove the plastic, taking massively high resolution photos of the chip at multiple layers, splicing them together, and analysing and debugging all the tracks.

The end result of all that hard work can be found at visual6502.org. This isn't your garden-variety 6502 emulator that does what the opcode map says it should do. No, this emulator reaches into the guts of the 6502 internal hardware (the transistors and electrical signals) to accurately perform the same operations as the original chip.

But the important information is still in the video at about 34.5 minutes in, in part III.

In this section based on the Visual6502 work, the presenter details how the 6502 logic works in terms of the T-states (timing states) and opcodes to control the individual bits of hardware within the 6502. Basically, the CPU uses a decode ROM to translate these two inputs into a series of outputs which cause other bits of hardware (via a "random control logic" controller) to weave their magic.

And it's this driving of the other hardware bits which shows why certain operations exist at specific opcodes and what the outcome is of using the roughly 40% of unused opcodes.

Any further description I could give would be inadequate next to the presentation, it does a great job of ripping apart the mystique. Seriously, go and watch the video.

In terms of your specific question:

Did anything in the 6502's hardware make it easier to disable the "load-immediate" sequencing for "101010x0" but enable it for "101000x0"?

The answer is yes. The decode ROM (which you will hopefully understand once you've seen the video) looked at the top six bits of the opcode and used that to select which internal operation sets were carried out in each T-state.

So, in all honesty, they could have been two totally different operations (such as LDA and NOP, all it would have taken was a slightly modified decode ROM.

Your contention that "opcodes of the form xxxMMM01 or 10xMMMxx use addressing mode MMM, where mode 010 is immediate" is valid only if you assume very simple interpretation of the bit values in an opcode. The presence of the decode ROM makes the interpretation able to be arbitrarily complex.

  • I would have expected that it would have been easiest to say that if either of the top two bits of the opcode is set, the bottom three bits will identify one of eight addressing modes. The CPU would then spend the next 1-5 cycles computing an address and fetching a value from there except that some store instructions would skip the fetch. Then the CPU would either do a modify and/or write cycle if needed, and an instruction fetch. – supercat Oct 16 '18 at 22:00
  • By my count, there are six instructions that simply load a value and latch a result into A, six that simply load a value but don't write the result to A (one examines X, one examines Y, one latches X, and one latches Y), three that simply write, and six that do read-modify-write. Supporting all eight addressing modes for the first 12, and seven for the remaining nine would seem simpler than having different instructions support different combinations of addressing modes. – supercat Oct 16 '18 at 22:03

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.