Close

Adding without carry

A project log for Tetra - 4-bit CPU

Experiment to create a minimal 4-bit CPU that can do something fun

kaimackaimac 08/28/2022 at 16:433 Comments

It would be nice to be able to make use of the carry output from the 74LS283 adder, but it's going to require at least one extra chip to store the carry bit, and maybe more to decode the opcode into a "write carry" signal.

The alternative is to OR all the accumulator's bits together to test for zero. That doesn't need any chips, just four diodes connected like this:

"jnz" (jump if accumulator is not zero) will be our conditional jump.

The question now is, how do you synthesise a carry bit in software if you don't have one in hardware? I couldn't find much information about this - a common definition of the carry bit is "1 if the result of A+B is less than A (or B)", but that's not very helpful - it's not very easy to do an unsigned comparison without a carry flag! In the end I found the answer in the source code for the Gigatron, which I knew doesn't have a carry flag.

Q = A + B
if top (sign) bit of Q is set:
  carry bit = top bit of (A & B)
else:
  carry bit = top bit of (A | B)

This is where having a NAND operation becomes very useful. ANDing A and B is just a case of NANDing, then inverting:

lda $A
nan $B
nan f    ; nand with 0b1111 = invert

 OR is ~(~A . ~B), i.e. NAND with both inputs inverted. This requires a temporary location:

lda $A
nan f
sta $notA
lda $B
nan f
nan $notA     ; ~A nand ~B == A or B

Putting it all together, here's how to add two 8-bit numbers:

; input values

        lda f       ;a=0xff (big endian, stored at $0/$1)
        sta $0
        lda f
        sta $1
        
        lda 5       ;b=0x52 (stored at $2/$3)
        sta $2
        lda 2
        sta $3

; add two 8-bit numbers

        lda $1      ; add lo nibbles
        add $3
        sta $5      ; store result at $5
        nan %1000   ; check hi bit
        nan f
        jnz set
        lda $1      ; msb clr: a or b
        nan f
        sta $f          ; $f = not a
        lda $3
        nan f
        nan $f
        jmp next
set:    lda $1      ; msb set: a and b
        nan $3
        nan f
next:   nan %1000   ; hi bit is carry
        nan f
        jnz carry
        jmp addhi   ; acc already zero if no carry
carry:  lda 1
addhi:  add $0      ; add hi nibs + carry
        add $2
        sta $4      ; store result at $4

Discussions

zpekic wrote 08/28/2022 at 18:44 point

Did you think of combining conditional skips with add? After all, most of times carry and zero flags are used in branch/if instructions. You could have skips that actually are same as add, but don't store the value in accumulator, just execute the operation and at same time increment the PC by 2 if the carry/zero/combination of both is true. 

  Are you sure? yes | no

kaimac wrote 09/04/2022 at 20:50 point

Skips would be nice but I considered them too complicated to implement, although maybe there is a nice way to do it. Thanks for the idea!

  Are you sure? yes | no

zpekic wrote 09/04/2022 at 22:59 point

You are welcome! Add 1 to PC using a MUX constant input, and skip by generating the carry-in using the skip criteria. For example, a 4-to-1 mux driving carry etc. Or no carry-in, but bits 1 and 0 added to PC are driven by condition, so you either add 01 (no skip) or 10 (skip)

  Are you sure? yes | no