Реализация Z80 DAA и проблемы с тестовым ПЗУ Blargg

Я хочу сначала предварить свой вопрос, сказав, что я довольно опытный программист, особенно с Java, который используется в течение 8 лет.

Чтобы улучшить свое понимание работы оборудования и операционной системы, я решил запрограммировать простой эмулятор Gameboy. Запрограммировав основные функции всего за несколько дней, я протестировал эмулятор и обнаружил, что на экране ничего не рисуется. Пройдя несколько сотен кодов операций в моем эмуляторе по одному и сравнив его со значениями, найденными в эмуляторе BGB, я понял, что рассматриваемые тайлы и спрайты загружались в память, а не рисовались. Из этого я понял, что проблема должна быть в одной или нескольких реализациях моего кода операции, из-за чего программа в какой-то момент демонстрирует неправильное поведение. Из-за этого я решил использовать тестовые ромы процессора Blargg (http://gbdev.gg8.se/files/roms/blargg-gb-tests/), чтобы помочь мне выявить проблему. Однако при запуске первого тестового диска выдается следующее сообщение об ошибке:

01-special

36E1FE30 
DAA

Failed #6

Я несколько раз проверял работу DAA, и мне кажется, что она правильно реализована. Приведенный код ошибки («36E1FE30») совершенно бесполезен, поскольку я не могу понять, что это значит. Для меня это означает, что либо DAA реализован неправильно, и я просто не вижу своей ошибки, либо одна из операций, используемых для проверки правильности DAA, неверна. Если я запускаю какие-либо другие тесты, они зацикливаются на неопределенный срок.

03-op sp,hl

03-op sp,hl

03-op sp,hl

03-op sp,hl

Для справки, моя реализация DAA находится на github (https://github.com/qkmaxware/GBemu/blob/master/src/gameboy/cpu/Opcodes.java) или можно увидеть ниже следующим образом:

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);
});

Там, где такие вызовы, как reg.a(), означают чтение из регистра a, reg.a(value) означает запись в регистр a (маскируется до 8 или 16 бит в зависимости от регистра). Точно так же флаги Z,N,H,C можно получить или установить/сбросить с помощью функций нуля, вычитания, половинного переноса и переноса объекта 'reg'.

Итак, мой вопрос состоит из трех частей: неправильно ли я реализовал операцию DAA, чтобы она не прошла тесты Бларгга, знает ли кто-нибудь, что означает код ошибки, который у меня есть, или у кого-нибудь есть идеи, как я могу сосредоточить свой поиск на неправильной операции.


person Hals    schedule 21.07.2017    source источник


Ответы (1)


Похоже, что тесты Blargg заимствованы из старой тестовой программы Z-80 под названием zexlax, которая использует прагматичный подход, рассматривая тестирование инструкций как простое сравнение данных. Для DAA он запускает все возможные комбинации входных данных и эффективно проверяет их на соответствие ожидаемому ответу. Но сохранение всех ответов сделало бы тестовый код непрактично большим. Вместо этого он сравнивает CRC данных. Как вы уже убедились, это довольно эффективно для проверки правильности работы эмулятора, но совершенно бесполезно для указания, как это исправить.

Хотя было бы идеально, если бы кто-то сохранил правильный вывод, чтобы вы могли сравнить его с вашей реализацией, вы все равно можете сделать это, запустив тест на заведомо хорошем эмуляторе. Или просто сравните свою реализацию с известным эмулятором.

Вот как это делает MAME:

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;

Для большего контекста, вот ссылка на весь источник:

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

Похоже, в вашем коде могут быть некоторые различия, но я не очень внимательно смотрел.

Я отмечаю, что тесты Blargg включают недокументированные флаговые биты 3 и 5. Если бы это был процессор Z-80, он не смог бы эмулятор, который не устанавливает эти биты, как это делает Z-80, что на самом деле предсказуемо, просто не задокументировано как что-либо, что вы может зависеть. Я не знаю, есть ли у Sharp LR35902 аналогичная проблема, но если да, то вполне возможно, что MAME этого не реализует. Эти биты вряд ли будут иметь значение для «настоящей» программы.

person George Phillips    schedule 21.07.2017
comment
Вау, спасибо за прекрасную информацию о тестовых ромах и ссылку на реализацию кода операции MAME. Реализации кода операции гораздо приятнее читать, чем некоторые другие проекты с открытым исходным кодом. - person Hals; 24.07.2017
comment
@Hals: вы когда-нибудь выясняли, в чем проблема в вашем собственном коде, или вы просто выбросили его и вместо этого использовали MAME? - person Jongware; 12.01.2018
comment
@usr256430:1 usr256430:1 В итоге я использовал комбинацию версии MAME и других версий других эмуляторов на GitHub вместо своей собственной. - person Hals; 13.01.2018