Demystifying the GameBoy/SM83’s DAA Instruction

Posted on Nov 19, 2023

As an off-and-on side project, I’m working on a GameBoy emulator. I definitely still have a long way to go (still working on the CPU!) but I wanted to share a detail that I felt deserved a more in-depth explanation: the DAA (“Decimal Adjust Accumulator”) instruction. This instruction is notorious for being a bit difficult to wrap your head around, but there is seemingly very little written about it.

In principle, this instruction is quite simple; if we perform an arithmetic instruction with two binary coded decimal (BCD) numbers, we should be able to get an output that is also in BCD. Of course, this raises a natural question…

What the Heck is Binary Coded Decimal?

Binary Coded Decimal is quite literal once you learn what it is; it’s just a decimal number whose digits are encoded in binary. While that’s a bit of an obtuse explanation, it really is that literal. This is likely best illustrated through an example.

Let’s encode the number 42 in BCD. The GameBoy uses four bits to represent each digit, so we will use that here. In a four-bit representation, the digit 2 can be written as 0010, and the digit 4 can be represented as 0100. Put together, this means 42 in BCD is simply 0100 0010, or 0x42 in hexadecimal. Of course, this can be extended to as many digits as you’d like, but we’re going to stick to two-digit numbers here, as this is all we can fit in an eight bit register on the GameBoy.

While this representation is certainly simple on paper, it is a bit limiting. Though a single byte can be used to represent any number from 0 through 255, a single byte of BCD can only represent 0 through 99, since we only have two four-bit “slots” to store our decimal digits (which can only be 0-9)1. Given this loss, one might wonder why anyone would use this. While inefficient, BCD is actually quite useful when displaying numbers, as you can display the digits themselves with no additional logic. You can even see it used for this if you go digging through a disassembly of Tetris.

Adding Binary Coded Decimal Numbers

We’re on our way to understanding DAA, but before we get there, we need to learn to do some arithmetic with BCD. Going forward, I’m going to use hexadecimal for BCD, but the math will work the same in binary, as well.

Let’s add the BCD numbers 0x42 and 0x35. This is quite simple, we just need to add the digits and don’t carry anything.

  0x42
+ 0x35
------
  0x77

Our result, in BCD, is 77, and this matches what we might expect. Things get a little more complicated if we add BCD numbers that force us stretch beyond our decimal limits, though.

  0x42
+ 0x29
------
  0x6B

Eek! Our output is not in BCD, one of the digits is more than 9! In general, the output of these operations is relatively meaningless; adding the decimal numbers 42 and 29 nets 71, and adding the hex numbers 0x42 + 0x29 is decimal 107 (0x6B), which is certainly not the same number. Instead, we must perform an adjustment to correct it. It turns out, when the unit digit is greater than nine, we can correct it by simply adding six to our result. Indeed, 0x6B + 0x06 equals 0x71, which is the BCD result we want! This conversion from these “hex-ish” numbers to BCD is actually precisely what the DAA instruction does to our A register (the “accumulator”).

This offset of six might seem a bit random, but it’s actually derived from the difference between the highest numbers representable in bases 10 and 16 (9 and 15/F). A hand-wavy way of looking at it is that we must “move” the excess over nine into the next digit by adding this difference.

Let’s try another example to demonstrate the point.

  0x54
+ 0x28
------
  0x7C

We have a digit greater than nine, so we add six…

  0x7C
+ 0x06
------
  0x82

and there you have it. 54 + 28 is indeed, 82, and we have a BCD representation.

Correcting an Error in the Tens Place

While this strategy works great in the ones place, it breaks down a bit if we attempt to apply it to the tens place. Let’s add 0x54 and 0x60.

  0x54
+ 0x70
------
  0xC4

If we add six to the tens place (i.e. add 0x60), we will end up with 0x24 (plus a carry). This certainly seems right; decimal 54 plus 70 equals 124, and since we’re limited to two digits, 24 is a correct result. However, the rule is slightly more complicated than just “if the tens place is greater than nine, add 0x60”. Consider the following example:

  0x98
+ 0x04
------
  0x9C

If we follow our two rules above, we would simply add 0x06, since the ones place is greater than nine.

  0x98
+ 0x06
------
  0xA2

That’s certainly not BCD! However, if we apply our second rule to this result, we will get a correct result!

  0xA2
+ 0x60
------
  0x02 (plus a carry)

98 + 4 is 102, which wraps around past 99 to 2. It would be a bit inconvenient to have to check your result twice, though, so you’ll often see this written as “if DAA’s argument (the A register) is greater than 0x99, add 0x60”. This is equivalent to the above, though in perhaps a roundabout way. Really what we’re doing is ensuring that the two-digit number fits within our BCD representation, and adding 0x60 to if it doesn’t; anything greater than 0x99 can’t fit in BCD, but we can push the “extra data” to the next place by adding the difference between the value of place in our two bases, 0x60. If you think about it, this actually neatly fits in line with our first rule; in that rule, we’re checking if the single-digit result (i.e. ignoring the top nibble) fits in BCD by checking if the value is greater than 0x09. This is exactly the same principle, just applied to both numeric places2.

Carry Bits

I have neglected one piece so far: carries. Consider the following BCD addition:

    1
  0x_90
+ 0x_80
-------
  0x110

If we are simply performing eight-bit arithmetic (as we are on the GameBoy), this will wrap to 0x10, and we will have the carry flag set. We can use our same “greater than 0x99” rule as above, here. The presence of a carry bit indicates that the result must be greater than 0x99, as our third place would be the hundreds place. As such, we should also add 0x60 here.

   0x10 # ignoring the hundreds place, since our adding wrapped
+  0x60
-------
   0x70

This is correct! 90 + 80 is 170, and since we wrapped around, 0x70 is the BCD result we expect.

For the exact same reason, the same applies to our weird friend, the half-carry flag.

     1
   0x09
+  0x08
-------
   0x11

We performed a half-carry, so we must add 0x06

   0x11
+  0x06
-------
   0x17

This matches our expectation; 9 plus 8 is 17, which matches our BCD result.

Let’s codify what we have so far.

fn get_daa_value(registers: &Registers) -> u8 {
    let mut offset = 0_u8;

    let a_value = registers
		.get_single_8bit_register(register::SingleEightBit::A);
    let half_carry = registers.get_flag_bit(register::Flag::HalfCarry);
    let carry = registers.get_flag_bit(register::Flag::Carry);

    if a_value & 0xF > 0x09 || half_carry == 1 {
        offset |= 0x06;
    }

    if a_value > 0x99 || carry == 1 {
        offset |= 0x60;
    }

	a_value.wrapping_add(offset)
}

You’ll notice that nothing in the above function depends on the original operands; the results stored in our flags are enough to perform the conversion!

Subtracting Binary Coded Decimal Numbers

We’re well on our way to understanding the DAA instruction, but there is one more thing we must consider: subtraction. Our rules regarding carries work when subtracting as well, but two caveats apply. Firstly, we will subtract our offsets instead of adding them. This makes (hand-wavy) sense; just as we wanted to “move” our extra data up a numeric place with addition (by adding six), here we want to “move” the extra data down into the lower numeric place (by subtracting six). When running the DAA instruction, we can simply check the subtraction flag (N) to determine if a subtraction has just been performed, and negate our adjustment accordingly. Secondly, we apply these rules to borrows, rather than carries (which the GameBoy’s H and C registers already store for us). As an example, let’s subtract 0x13 from 0x20.

     1
  0x20
- 0x13
------
  0x0D

While performing this subtraction, we needed to borrow from the tens place, so we will apply our half-carry rule, yielding the expected result of 20 - 13, 7.

  0x0D
- 0x06
------
  0x07

As you might expect, we also apply this rule to our tens place if we borrow from the hundreds place.

    1
  0x05
- 0x21
------
  0xE4

And applying our carry rule…

   0xE4
-  0x60
-------
   0x84

This matches what one might expect, 5 minus 21 (wrapping from 0 to 99) is 84.

“Wait, That’s Illegal”

While our carry rules apply neatly to borrowing, the idea of a digit being greater than nine is a little messy. Simply, you cannot subtract two BCD numbers and end with a digit greater than nine unless you perform a borrow. You may have to prove this to yourself, but we can’t “grow” a digit in size unless we perform a borrow.

Unfortunately, there are no restrictions on when the DAA instruction is run; we may have not performed a BCD subtraction beforehand. Consider a state where register A is 0xF0, and only the subtraction flag is set, indicating a subtraction has just been performed. If we blindly apply what we’ve already learned, we’d subtract 0x60 from this value, and get 0x90. However, this is not what the GameBoy actually does. In these cases, it will actually leave the value in register A alone. We can fix this pretty easily, by only applying our “digit greater than nine” rule if the subtraction flag is zero.

Let’s codify everything we’ve learned so far. We will only follow our “greater than 9” rules if the subtraction flag is not set, and will perform a wrapping_sub if it is.

fn get_daa_value(registers: &Registers) -> u8 {
    let mut offset = 0_u8;

    let a_value = registers
		.get_single_8bit_register(register::SingleEightBit::A);
    let half_carry = registers.get_flag_bit(register::Flag::HalfCarry);
    let carry = registers.get_flag_bit(register::Flag::Carry);
    let subtract = registers.get_flag_bit(register::Flag::Subtract);

    if (subtract == 0 && a_value & 0xF > 0x09) || half_carry == 1 {
        offset |= 0x06;
    }

    if (subtract == 0 && a_value > 0x99) || carry == 1 {
        offset |= 0x60;
    }


    if subtract == 0 {
        a_value.wrapping_add(offset)
    } else {
        a_value.wrapping_sub(offset)
    }
}

DAA’s Flags

We’re so close to finishing our DAA implementation, all we need to do now is update our flags. Thankfully, most of this is pretty straight forward.

Flag Value
Zero 1 if final value is zero, 0 otherwise
Subtract Untouched
Half-Carry 0
Carry Set if final BCD value is > 0x99

The only slightly confusing one here is the carry flag. This will be set under two conditions

  1. If a subtraction has not been performed, and the unconverted value is greater than 0x99. This indicates that our BCD value is larger than our maximum of 99, and we must indicate that.
  2. If our carry bit was already set. This indicates that our input result was already greater than our maximum of 99, and thus our final BCD value will be as well.

With these in place, we can finish our implementation!

fn run_instruction(processor: &mut Processor, opcode: u8) {
	match opcode {
		// ...
		0x27 => {
			use register::SingleEightBit;
			use register::Flag;

			let registers = &mut processor.registers;
			let (result, carry) = get_daa_value(registers);
			registers
				.set_single_8bit_register(SingleEightBit::A, result);

			registers.set_flag_bit(Flag::HalfCarry, 0);
			registers.set_flag_bit(Flag::Carry, carry.into());
			registers.set_flag_bit(Flag::Zero, (result == 0).into());
		}
	}
}

fn get_daa_value(registers: &Registers) -> (u8, bool) {
    let mut offset = 0_u8;
	let mut should_carry = false;

    let a_value = registers
		.get_single_8bit_register(register::SingleEightBit::A);
    let half_carry = registers.get_flag_bit(register::Flag::HalfCarry);
    let carry = registers.get_flag_bit(register::Flag::Carry);
    let subtract = registers.get_flag_bit(register::Flag::Subtract);

    if (subtract == 0 && a_value & 0xF > 0x09) || half_carry == 1 {
        offset |= 0x06;
    }

    if (subtract == 0 && a_value > 0x99) || carry == 1 {
        offset |= 0x60;
		should_carry = true;
    }


    let output = if subtract == 0 {
        a_value.wrapping_add(offset)
    } else {
        a_value.wrapping_sub(offset)
    };

	(output, should_carry)
}

Wrapping it up

Once you can wrap your head around it, the DAA instruction is fairly straight forward. The only tricky bit is that unlike the examples I’ve given above, DAA has no knowledge of the operands given in the original operation. However, by applying a few simple rules, we can convert the result to BCD. To summarize the rules we’ve established above:

  1. If we’re not subtracting, and the unit digit is greater than nine, we will add 0x06 to register A.
  2. If we’re not subtracting, and the value is greater than 0x99, we will add 0x60 to register A.
  3. If there was a half carry, we will add 0x06 to register A.
  4. If there was a full carry, we will add 0x60 to register A.

Applying the these rules, and then setting the appropriate flags, you will have a working DAA implementation! This implementation passes the tests provided by the JSMoo project. I want to extend a huge thanks to the maintainers of that project for sharing these tests. Without them, I’d definitely still be scratching my head.


  1. I hear the pigeons fighting over their cubbies already… ↩︎

  2. If you’re still not convinced, you may be falling into a similar trap as I did. My original implementation checked if value & 0xF0 > 0x90. The example I put in this post was carefully chosen. If we had 0x9C in our A register, we would perform 0x9C & 0xF0 > 0x90, which is false, and we would fail to perform a necessary correction! ↩︎