The 6502 uses the carry bit system (over a borrow bit). On such machines, the bitwise NOT is applied to the subtrahend before it is "subtracted" (really, added, taking advantage of two's complement) from the minuend. Since obviously when you're generating machine from assembly code, you have control over the bytes which are actually emitted, for 6502 variants like the NES's 2A03, was the presence of both ADC and SBC by immediate necessary? Couldn't the immediate just be NOTed at the assembly phase? Or are the flags reckoned differently for ADC and SBC?


No, ADC and SBC does exactly the same except SBC does a NOT operation on the argument before adding, and when it comes to immediate values, they are definitely redundant.

However, when it comes to other addressing modes, ADC and SBC instructions were both needed. And as the 6502 is pretty orthogonal when it comes to instructions and addressing modes, this is why both SBC #imm exists even though it could always be replaced with ADC #~imm.

EDIT: The overflow flag, which is "simply" the XOR of the 6th and 7th bit carry, is the same whenever ADC and SBC is used.

I'd also say that pseudo-instructions, while common in the ARM world for example, were unknown/uncommon during the 6502 days. Back then people expected instructions to be in memory exactly what they typed in the assembly code.

Conclusion: The decimal mode is the only way and ADC #~imm and SBC #imm will make a difference. However, while the decimal mode rarely used and marginal today in hindsight, especially considering it was removed for the NES/Famicom, you should not forget that it was a major marketing tool back in the late '70s, which was a selling point for this CPU.

  • 1
    Is this true even of the overflow flag? – junius May 25 '20 at 16:17
  • 1
    @KevinKeith Yes. (righto.com/2012/12/the-6502-overflow-flag-explained.html) – Bregalad May 26 '20 at 7:06
  • 1
    "pseudo-instructions [..] were unknown/uncommon during the 6502 days" is not completely true. A lot of assemblers had BLE and BGT synonyms for BCS and BCC like the Lisa assembler on Apple II. But granted, it's really not very significant. – Patrick Schlüter May 26 '20 at 10:45
  • @PatrickSchlüter also BLE/BGT are 'only' aliases, not encodings o their own like an ADC #imm used to subtract. – Raffzahn May 26 '20 at 12:13
  • @PatrickSchlüter: I think the term "pseudo-instruction" refers to opcodes like $A7, $87 and $C7, sometimes called "LAX", "SAX", and "DCP" [the opcodes given are for zero-page direct mode, but other addressing modes are available]. LAX will read a byte from memory and copy it to A and X simultaneously (same timing as LDA) SAX will store the value (A and X) into a specified address without affecting flags (same timing as STA). DCP will decrement the value at the specified address and then compare the result to the accumulator, setting flags like CMP (but same timing as DEC). – supercat May 26 '20 at 15:15

was the presence of both ADC and SBC by immediate necessary?

Necessary is for instruction sets always a misleading question. After all, next to all instructions could be omitted from the hardware and put into code, essentially ending with a one instruction machine. The question is not about a minimizing the instruction set (including addressing variants) to an absolute minimum but rather to find a (*1) the sweet spot between bloated and unusable.

Couldn't the immediate just be NOTed at the assembly phase?

Sure, it could. But were to put it? But that would require either a(nother) temporary register to store the value after the 'NOTing' instruction to hold it for the 'ADDing' as the accumulator already holds the first operand. In addition the 'NOTing' instruction would have to have all addressing modes of the SBC instruction it replaces to be as useful. This means as many opcodes and decode-ROM entries with SBC - plus an additional one for 'Add Temp to A' (*2).

Now if this question is strictly only about dropping the SBC with an immediate value, then yes, it would have saved a single opcode, but no hardware at all, as the implied negation would still need to be there for all SBC with a memory operand. Maybe one line in the decode-ROM could be saved - but at horrible cost with potential buyers ... 'Wut? A CPU that can't subtract an immediate value? No way I'd buy that'

Bottom line: While removing SBC is a nice Gedankenexperiment, is will increase hardware requirement and bloat the instruction set when done general, or have no meaningful savings at all.

Next, the notion of 'at assembly phase' carries a lot of hindsight. Back when the 6500 was done, it's designated assembler was barely than a tool to replace a combination of mnemonic and operand markers into a single opcode. That beast was really dumb. Not to mention, that assembling from hand was considered a normal way to do it. Woz did write not only his monitor, but as well the first Integer BASIC exactly that way - no assembler involved.

P.S.: Instead the 6500 design saved on a not instruction, as it can simply be synthesized by an EOR #$FF instruction.

*1 - There isn't a single sweet spot, but many, depending on targets and resources.

*2 - Turning this into a Load-Temp instruction and a Neg-And-Add-Temp will lead to the same result.

  • 2
    Just to clarify, I did indeed mean just removing the SBC with immediate addressing mode, not SBC entirely. – junius May 25 '20 at 16:13
  • 1
    @KevinKeith Ok, but then,what it should be good for as all circuitry had to be there anyway? – Raffzahn May 25 '20 at 19:55
  • 1
    Yeah that's a fair point. – junius May 25 '20 at 20:29
  • 1
    Wut? A CPU that can't subtract an immediate value? No way I'd buy that Reminds me the oft-repeated fallacy that the 6502 can't multiply and divide - just because there's no such instructions. – Bregalad May 26 '20 at 6:12

Many 6502 programs exploit the ability to patch program instructions at runtime. If a program will have to perform a subtraction within a loop, being able to say, e.g.

    ; Compute value in X somehow, then...
    stx subPatch+1
    ; Now compute a different value in A and set carry
    sbc #0
    bcs subPatch

is more convenient than it would be if code had to XOR the value with $FF before storing it.

Note also that the processing "SBC #imm" involves two sets of logic: the immediate-operand-fetch logic which is shared with the six other valid opcodes sharing bit pattern (xxx0 1001) and the "subtract with carry instruction" logic which is shared with the seven other opcodes having the bit pattern (111x xx01). There are some instructions in the memory map which have their own "oddball" logic (for example, seven immediate-mode instructions have the bit pattern xxx01001 which is shared by no other instructions, three likewise have 1xx0 0000, two have 1x10 0100, and has xxxx 0010), making opcode E9 behave as "SBC #imm" requires less circuitry than would having it behave in any other consistent fashion.

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.