• Please review our updated Terms and Rules here

Why did the Intel 4004 implement subtraction this way?

alank2

Veteran Member
Joined
Aug 3, 2016
Messages
2,264
Location
USA
After some reading in the MCS programming manual I noticed that the SUB instruction is a bit odd in that it expects the carry to mean something on the way in:

carry=1 means borrow occurred
carry=0 means borrow did not occur

but on the way out, it means the opposite!

carry=1 means borrow did not occur
carry=0 means borrow occurred

The manual shows examples where you should use the CMC instruction to complement the carry before calling SUB again.

Here is my question: WHY????

Is it because of the way they implemented SUB in the silicon? Did they not want to spend any more gates on making it work differently? Some other reason?
 
If you look at the manual on bitsavers, page 30, you can see that someone made a note about the state of carry, going into the execution, that the carry was not complemented going in the operation, when showing what the operation actually did, in silicon.
You are right, the carry being set is to indicate that there was no barrow.
It is an error in the manual, not the silicon.
I suspect it was what the person making the manual expected the operation to be.
It is clear that if the carry is set there is no barrow is correct, both going into the SUB and leaving the SUB.
This looks to effect the SBM instruction as well.
I'd not thought about it as I think my users manual has the inked in CY in place of the CYbar, like the one on the web.
I suspect that Faggin didn't want to add two carry inversions, one before and one after, to the add operation, to make it work the other way, just for SUB.
He already needed to decoded a complement of a memory value, for another instruction, so adding that extra double inversion for subtract would have been a pain.
Like I said, I don't think he wrote the manual.
Dwight
 
I've seen it like /CY also in the programming manual too, and I wonder if that is correct and not CY. The reason I wonder is that to subtract they are doing the 2's complement which is normally complementing the number and THEN adding 1. So I figured also complementing the carry accomplished that so that when the carry was set it would become a borrow and when the carry was clear, it would be complemented and end up adding 1 to complete the 2's complement conversion of the number being added.

Look at the top of page 3-23 in the programming manual. It shows the same subtraction being done with the carry going into it both ways and the result of carry being 1 when going into it means that it is a "borrow" and the answer is then 1 less than the example where carry being 0 going into it (3 vs. 4).
 
I stand corrected.
I complement both the value and the CARRY before the ADD
I do not complement the CARRY when done.
Extracting from my code, it is ( in Forth)

Reg@ $0F XOR
Carry @ 1 XOR
+ ACC +
DUP $10 AND $10 / TO CARRY
$0F AND TO ACC

Dwight
 
Last edited:
No worries Dwight; thanks for checking your sim. That sounds correct.

Here is what I do for my add:
//add
APtr->acc+=(unsigned char)(APtr->carry+APtr->reg[c1&0xf]);
APtr->carry=(unsigned char)((APtr->acc>>4)&1);
APtr->acc&=0xf;
my sub:
//sub
APtr->acc-=(unsigned char)(APtr->carry+APtr->reg[c1&0xf]);
APtr->carry=(unsigned char)(((~APtr->acc)>>4)&1);
APtr->acc&=0xf;

So I'm using a subtract operation, but part of me wants to just re-code it the way the i4004 processor does it with an add operation of the complemented acc and complemented carry. It should produce the same output. I'll test that.
 
I replaced my //sub above with one that is technically more the way the i4004 does it, but first I created some test code that would run through all possible 16*16*2 combinations of the sub command (acc, reg, carry set or clear). It then outputs the carry output and the acc after the SUB instruction.

R0 = current acc to test
R1 = current value to sub to test
R2 = current value of carry to test

Code:
PGM:000   A2      LD 2                    ;is r2 clear or set carry
PGM:001   14 06   JCN 4(JZ) 006
PGM:003   FA      STC                     ;it is non-zero set carry
PGM:004   40 06   JUN 006
PGM:006   F1      CLC                    ;it is zery clear carry
PGM:007   A0      LD 0                 ;load acc from r0
PGM:008   91      SUB 1               ;sub r1 from it
PGM:009   B3      XCH 3              ;store acc result in r3 for later
PGM:00A   12 14   JCN 2(JC) 014      ;output carry bit
PGM:00C   D3      LDM 3                  ;tx char
PGM:00D   FE      EMU
PGM:00E   D3      LDM 3                  ;send 0x30 (0)
PGM:00F   FE      EMU
PGM:010   D0      LDM 0
PGM:011   FE      EMU
PGM:012   40 1A   JUN 01A
PGM:014   D3      LDM 3                  ;output carry bit
PGM:015   FE      EMU
PGM:016   D3      LDM 3                  ;tx char
PGM:017   FE      EMU
PGM:018   D1      LDM 1                  ;send 0x31 (1)
PGM:019   FE      EMU
PGM:01A   D3      LDM 3                 ;tx char
PGM:01B   FE      EMU
PGM:01C   D4      LDM 4                 ;0x4X (@ABCDEFGHIJKLMNO represented for 0123456789ABCDEF)
PGM:01D   FE      EMU
PGM:01E   A3      LD 3                   ;retrieve result from r3
PGM:01F   FE      EMU
PGM:020   60      INC 0     ;loop through all r0 values (acc)
PGM:021   A0      LD 0
PGM:022   1C 00   JCN C(JNZ) 000
PGM:024   61      INC 1     ;loop through all r1 values (sub)
PGM:025   A1      LD 1
PGM:026   1C 00   JCN C(JNZ) 000
PGM:028   62      INC 2     ;loop through non-carry and then carry
PGM:029   A1      LD 1
PGM:02A   F8      DAC
PGM:02B   14 00   JCN 4(JZ) 000
PGM:02D   D0      LDM 0                ;halt command
PGM:02E   FE      EMU

Output with my original SUB:
1@1A1B1C1D1E1F1G1H1I1J1K1L1M1N1O0O1@1A1B1C1D1E1F1G1H1I1J1K1L1M1N0N0O1@1A1B1C1D1E1F1G1H1I1J1K1L1M0M0N0O1@1A1B1C1D1E1F1G1H1I1J1K1L0L0M0N0O1@1A1B1C1D1E1F1G1H1I1J1K0K0L0M0N0O1@1A1B1C1D1E1F1G1H1I1J0J0K0L0M0N0O1@1A1B1C1D1E1F1G1H1I0I0J0K0L0M0N0O1@1A1B1C1D1E1F1G1H0H0I0J0K0L0M0N0O1@1A1B1C1D1E1F1G0G0H0I0J0K0L0M0N0O1@1A1B1C1D1E1F0F0G0H0I0J0K0L0M0N0O1@1A1B1C1D1E0E0F0G0H0I0J0K0L0M0N0O1@1A1B1C1D0D0E0F0G0H0I0J0K0L0M0N0O1@1A1B1C0C0D0E0F0G0H0I0J0K0L0M0N0O1@1A1B0B0C0D0E0F0G0H0I0J0K0L0M0N0O1@1A0A0B0C0D0E0F0G0H0I0J0K0L0M0N0O1@

Output with a SUB that doesn't use subtraction, but instead adds the complement of the carry and acc:

APtr->acc+=(unsigned char)(((~APtr->carry)&1)+((~APtr->reg[c1&0xf])&0xf));
APtr->carry=(unsigned char)((APtr->acc>>4)&1);

1@1A1B1C1D1E1F1G1H1I1J1K1L1M1N1O0O1@1A1B1C1D1E1F1G1H1I1J1K1L1M1N0N0O1@1A1B1C1D1E1F1G1H1I1J1K1L1M0M0N0O1@1A1B1C1D1E1F1G1H1I1J1K1L0L0M0N0O1@1A1B1C1D1E1F1G1H1I1J1K0K0L0M0N0O1@1A1B1C1D1E1F1G1H1I1J0J0K0L0M0N0O1@1A1B1C1D1E1F1G1H1I0I0J0K0L0M0N0O1@1A1B1C1D1E1F1G1H0H0I0J0K0L0M0N0O1@1A1B1C1D1E1F1G0G0H0I0J0K0L0M0N0O1@1A1B1C1D1E1F0F0G0H0I0J0K0L0M0N0O1@1A1B1C1D1E0E0F0G0H0I0J0K0L0M0N0O1@1A1B1C1D0D0E0F0G0H0I0J0K0L0M0N0O1@1A1B1C0C0D0E0F0G0H0I0J0K0L0M0N0O1@1A1B0B0C0D0E0F0G0H0I0J0K0L0M0N0O1@1A0A0B0C0D0E0F0G0H0I0J0K0L0M0N0O1@

I'm going to keep my original sub code however because it is less complicated for CPU's which have a subtract opcode (which is all of them).
 
Back
Top