Search code examples
javadebuggingemulationz80

Z80 DAA implementation and Blargg's test rom issues


I want to first preface my question by first saying that I am quite an experienced programmer, particularly with Java having used it for 8 years.

I decided, in an effort to improve my understanding of hardware operation and operating system topics, to program a simple Gameboy emulator. Having programmed the core functionality in just a few days I tested the emulator only to discover that nothing was drawn on the screen. After stepping through several hundred opcodes in my emulator one at a time and comparing it to the values found in the BGB emulator, I realized that the tiles and sprites in question were being loaded into memory, just not drawn. From this I figured that the issue must be in one or more of my opcode implementations which causes the program to exhibit wrong behavior at some point. Because of this, I decided to use Blargg's cpu test roms (http://gbdev.gg8.se/files/roms/blargg-gb-tests/) to help me pin down the issue. However, running the first test rom, gives the following error message:

01-special

36E1FE30 
DAA

Failed #6

I have checked the DAA operation several times and it appears to be correctly implemented to me. The error code given ("36E1FE30") is totally unhelpful as I cannot seem to find what that means. To me this implies that either DAA is incorrectly implemented and I just can't see my mistake or one of the operations used to validate the correctness of DAA is incorrect. If I run any of the other tests they appear to loop indefinitely

03-op sp,hl

03-op sp,hl

03-op sp,hl

03-op sp,hl

For reference my DAA implementation is on github (https://github.com/qkmaxware/GBemu/blob/master/src/gameboy/cpu/Opcodes.java) or can be seen below as follows:

Op DAA = new Op(0x27, "DAA", map, () -> {
    int a = reg.a();

    if(!reg.subtract()){
        if(reg.halfcarry() || (a & 0xF) > 9)
            a += 0x06;

        if(reg.carry() || a > 0x9F)
            a += 0x60;
    }else{
        if(reg.halfcarry())
            a = (a - 0x6) & 0xFF;

        if(reg.carry())
            a = (a - 0x60) & 0xFF;
    }

    reg.a(a);

    reg.zero(isZero(a));
    reg.carry((a & 0x100) == 0x100);
    reg.halfcarry(false);

    clock.m(1);
    clock.t(4);
});

Where calls such as reg.a() means read from register a, reg.a(value) means write to register a (mask to 8 or 16 bits depending on the register). Similarly, the flags Z,N,H,C can be obtained or set/reset with the zero, subtract, halfcarry, an carry functions of the 'reg' object.

So my question is threefold, have I implemented the DAA operation incorrectly so that it fails Blargg's tests, does anyone know what the error code I have means, or does anyone have any ideas how I can focus my search for the incorrect operation.


Solution

  • Looks like Blargg's tests borrow from an old Z-80 test program called zexlax which takes the pragmatic approach of treating instruction testing as simple data comparison. For DAA it runs all possible combinations of inputs and effectively checks that against the expected answer. But keeping around all the answers would make the test code impractically large. Instead it compares the CRCs of the data. As you have experienced, this is quite effective in verifying an emulator's correct operation but quite useless in pointing out how to fix it.

    While it'd be ideal if someone saved the correct output so you could check it against your implementation you can still do so by running the test on a known good emulator. Or just comparing your implementation against a know good emulator.

    Here's how MAME does it:

    case 0x27: /*      DAA */
        {
            int tmp = m_A;
    
            if ( ! ( m_F & FLAG_N ) ) {
                if ( ( m_F & FLAG_H ) || ( tmp & 0x0F ) > 9 )
                    tmp += 6;
                if ( ( m_F & FLAG_C ) || tmp > 0x9F )
                    tmp += 0x60;
            } else {
                if ( m_F & FLAG_H ) {
                    tmp -= 6;
                    if ( ! ( m_F & FLAG_C ) )
                        tmp &= 0xFF;
                }
                if ( m_F & FLAG_C )
                        tmp -= 0x60;
            }
            m_F &= ~ ( FLAG_H | FLAG_Z );
            if ( tmp & 0x100 )
                m_F |= FLAG_C;
            m_A = tmp & 0xFF;
            if ( ! m_A )
                m_F |= FLAG_Z;
        }
        break;
    

    For more context, here's a link to the whole source:

    https://github.com/mamedev/mame/blob/master/src/devices/cpu/lr35902/opc_main.hxx#L354

    Looks like there might be some differences with your code but I haven't looked really closely.

    I note the Blargg's tests include the undocumented flag bits 3 and 5. If this were a Z-80 processor it would fail an emulator that doesn't set those bits as a Z-80 does which is actually predictable just not documented as anything you can depend upon. I don't know if the Sharp LR35902 has a similar issue but if so it is entirely possible that MAME doesn't implement that. Those bits are never likely to make a difference for a "real" program.