Arithmetics & boolean algebra

Arithmetics

Adding, larger adding, and more adding

Let's talk a bit more about add, shall we? It actually comes in two flavors: the add a, X we just saw, which does 8-bit addition, but rumors speak of a mythical 16-bit variant: add hl, X. This one has two major differences: one, only the Z flag is updated; two, unlike the 8-bit flavor, X cannot be an immediate value!

There exists an instruction that is basically add but with an added twist: adc. This instruction adds with carry. What does it mean? Well, where add a, X was doing a = a + X, adc a, X does a = a + X + carry (either 0 or 1). If you're wondering how that's useful, we're going to use it to emulate add hl, $XXXX!

    ; add hl, $1337
    ; Note: destroys A

    ld a, l
    add a, $37 ; Process the low 8 bits...
    ld l, a ; Store back
    ld a, h
    adc a, $13 ; Process the upper 8 bits, counting the carry that may have stemmed from the low 8 bits
    ld h, a

Let's run though this again, but supposing HL = $CAFE.

    ; hl = $CAFE

    ld a, l
    add a, $37
    ; A = $37 + $FE = $35, and Carry set
    ld l, a
    ; hl = $CA35

    ld a, h
    adc a, $13 ; A = $13 + $CA + 1 (carry set) = $DE
    ld h, a
    ; hl = $DE35

If you take your calculator out, you'll notice that this is the correct result! Using add instead of adc would have yielded $DD35 instead, which is wrong. Basically, the purpose of adc is to simplify carrying out multi-byte additions. You saw an application above, here is another.

    ; Guess what this does?

    ld a, c
    add a, [hl] ; Remember, [hl] is basically an 8-bit register, so you CAN do this!
    ld c, a
    inc hl
    ld a, b
    adc a, [hl]
    ld b, a
    dec hl

    ; That's right -- this is basically `add bc, [hl]`. Neat, huh?

If you remember what I said about endianness, this is basically the reason why little-endian is more practical: you have to process (and therefore read and/or write) the low byte(s) first to be able to ripple the carry to the upper byte(s). Thus, we store the lower byte first. It may be a little awkward at first, but I promise you get used to it.

Oh, and by the way, adc hl, X doesn't exist. Get used to it -- there's basically just add hl, X as far as 16-bit operations are concerned. I miss my Z80 extended opcodes... ;-;

The other way around

Okay, great, I can add, but what if I wanted to subtract? Well, as the bold characters may have made you guess, sub is right there for you! It has the same syntax as add a, X, and - what a surprise! - sub hl, X doesn't exist. It also affects the Z and C flags, although it's worth mentioning how C works there.

    ld a, 3
    ld b, 2

    sub b
    ; A = $01, Z reset, C reset
    sub b
    ; A = $FF, Z reset, C set
    sub $FF
    ; A = $00, Z set, C reset
    sub 0
    ; A = $00, Z set, C reset

As you can see, C is set when a borrow/underflow occurs. Note that other CPUs, such as the 6502 family, use an inverted meaning - C is reset when a borrow occurs. It's less intuitive (cue a swath of "oops"-level bugs), but it's more practical in some cases.

Oh, and by the way, there's also sbc a, X. It works the same way as adc a, X (a = a - X - carry), and there's no sbc hl, X, PLEASE STOP INSISTING

Comparing

When you want to compare two values, you use cp a, X. cp is extremely straightforward -- it's sub, but it doesn't write the result back to a. If you're wondering what it's useful for aside for wasting time, you didn't notice that it still affects flags! As such, it's the preferred operator for, well, comparisons.

The cool part about cp is that you can test both equality cases (==, !=) and two inequality cases (<, >=) using only one cp. Don't believe me?

    ld a, 42

    cp 10 ; 42 - 10 = 32,  C reset (42 >= 10), Z reset (42 != 10)
    ; Notice how you can chain comparisons? That comes in handy at times
    cp 57 ; 42 - 57 = 241, C set   (42 < 57) , Z reset (42 != 57)
    cp 42 ; 42 - 42 = 0,   C reset (42 >= 42), Z set   (42 == 42)

Where mah mul and div?

Tough luck, the GBz80 does not have any multiplication or division capabilities. You can be clever about multiplication, though, using one simple trick: add a, a basically multiplies a by two! (This makes multiplying by powers of two much more efficient, btw). You could also multiply by 6 using this:

    ld b, a
    add a, a ; *2
    add a, b ; *3
    add a, a ; *6

We will program, in a section further up, a function that performs multiplication. Don't worry.

Boolean algebra

Now, we can add and subtract. But we're dealing with bits, too, and maybe I'll want to manipulate them as well? Well, boolean algebra has got you covered. There are three operations that you need to know about: and, or and xor.

Let's begin by what they have in common. They all operate on the a register, basically like add a, X. Each of these operations work bit by bit -- they're binary operations, not logical operations. All three of them reset the C flag, always, and modify the Z flag as you'd expect.

AND

This operation takes each bit of both operands, and sets the corresponding bit of the result if and only if both bits are set.

    ld a, %01010011
    ld b, %00110101
    and a, b
    ;       A  %0101 0011
    ;       B  %0011 0101
    ;          ----------
    ; A and B  %0001 0001

    ; Now, a = $11

This is pretty useful for "masking out" (resetting) some flags/bits, and to test if some bits are zero (since the Z flag will be set accordingly).

OR

This operation takes each bit of both operands, and sets the corresponding bit of the result if and only if any of both bits is set.

    ld a, %01010011
    ld b, %00110101
    or a, b
    ;      A  %0101 0011
    ;      B  %0011 0101
    ;         ----------
    ; A or B  %0111 0111

    ; Now, a = $77

This is pretty useful for setting some flags/bits. This is used less frequently than and, but still.

XOR

This operation takes each bit of both operands, and sets the corresponding bit of the result if and only if both bits differ.

    ld a, %01010011
    ld b, %00110101
    xor a, b
    ;       A  %0101 0011
    ;       B  %0011 0101
    ;          ----------
    ; A xor B  %0110 0110

    ; Now, a = $66

This is pretty useful to invert some flags/bits. This is heavily used instead of counters for "flip-flop" states (now you know why a lot of games have two-frame animations!).

BCD

Remember how I quickly skimmed over the H and N flags? Let's talk about them. But first, we need to talk about parallel universes what BCD is. When you store a number, say 42, you'd usually store it raw -- 42, 0x2A, %00101010. BCD is storing each digit in each nibble -- $42, %01000010. It is less efficient, but much more practical for decimal displaying (instead of dividing by 10, you and a, $0F). Therefore, BCD is better for displaying, but not for calculating: $59 + $01 = $60 and not $5A.

Luckily, there's an instruction that does just that: daa performs decimal adjustment on the accumulator (the a) register. It acts depending on the H, N and C flags, and the accumulator's contents. On a surface level, it basically adjusts the contents of the a register to match the result of the BCD operation.

    ld a, $39
    add a, $01
    ; A = $3A
    daa ; Perform M.A.G.I.C
    ; A = $40

Let it be known that daa may set the C flag (for example if A was $99 in the example above instead of $39), and alters Z and H. If you don't care about how it works, you can safely skip the rest of this section. But if you want the nitty-gritty, then here we go.

First, let's describe how the H and N flags work. H is the half-carry, and it works exactly the same way as the carry (even depending on the instruction!), but taking only the lower 4 bits into account. So, for example, when adding $08 to $39, H is set, because there's is an overflow in the low 4 bits (they go from $9 to $1). N tells whether the previous operation was "positive" (flag reset) or "negative" (flag set). For example, add a, X resets that flag, and sub a, X sets it. This is because the adjusting depends on the last operation that occurred.

If H is set or the lower 4 bits of A contain a non-BCD digit ($A-$F), then an implicit add a, $06 is performed (this includes modifying the C flag). Then if C is set or the upper 4 bits of A contain a non-BCD digit, an implicit add a, $60 is performed, including modifying the C flag. (Note: I believe the H flag isn't modified by this second add, but I'm not sure.) But, if the N flag was set, instead of add, the operations performed are instead subs. (Note: I think that's how it's done, but I'm unsure.)