GBZ80 — инструкции АЦП не прошли тест

Я запускал тесты процессора Blarggs через эмулятор Gameboy, и тест op r,r показывает, что моя инструкция ADC работает неправильно, а ADD работает. Насколько я понимаю, единственная разница между ними заключается в добавлении существующего флага переноса ко второму операнду перед добавлением. Таким образом, мой код ADC выглядит следующим образом:

void Emu::add8To8Carry(BYTE &a, BYTE b) //4 cycles - 1 byte
{
    if((Flags >> FLAG_CARRY) & 1)
        b++;
    FLAGCLEAR_N;
    halfCarryAdd8_8(a, b); //generates H flag based on addition
    carryAdd8_8(a, b); //generates C flag appropriately
    a+=b;
    if(a == 0)
        FLAGSET_Z;
    else
        FLAGCLEAR_Z;
}

Я ввел следующее в тестовое ПЗУ:

06 FE 3E 01 88

Что оставляет A со значением 0 (Flags = B0), когда флаг переноса установлен, и FF (Flags = 00), когда он не установлен. Вот как это должно работать, насколько я понимаю. Тем не менее, он все еще не проходит тест.

Исходя из моего исследования, я считаю, что на флаги влияет то же самое, что и на ADD. Буквально единственным изменением в моем коде по сравнению с рабочей инструкцией ADD является добавление флага проверки/потенциального приращения в первых двух строках, что, похоже, подтверждает мой тестовый код.

Я что-то упускаю? Возможно, есть особенность с состояниями флагов между ADD/ADC? В качестве примечания, инструкции SUB также проходят, но SBC не выполняется точно так же.

Спасибо


person Triforcer    schedule 07.02.2017    source источник


Ответы (1)


Проблема в том, что b является 8-битным значением. Если b равен 0xff и установлен перенос, то добавление 1 к b установит его в 0 и не будет генерировать перенос, если добавить с a >= 1. Аналогичные проблемы возникают с флагом половинного переноса, если младший полубайт равен 0xf.

Это может быть исправлено, если вы вызовете halfCarryAdd8_8(a, b + 1); и carryAdd8_8(a, b + 1);, когда установлен перенос. Однако я подозреваю, что эти подпрограммы также принимают байтовые операнды, поэтому вам, возможно, придется внести в них внутренние изменения. Возможно, добавив перенос в качестве отдельного аргумента, чтобы вы могли выполнять tmp = a + b + carry; без переполнения b. Но я могу только строить догадки без источника этих функций.

На несколько связанной ноте есть довольно простой способ проверить перенос всех битов:

int sum = a + b;
int no_carry_sum = a ^ b;
int carry_into = sum ^ no_carry_sum;
int half_carry = carry_into & 0x10;
int carry = carry_info & 0x100;

Как это работает? Учтите, что побитовое «исключающее ИЛИ» дает ожидаемый результат для каждого бита, если в этот бит не входит перенос: 0 ^ 0 == 0, 1 ^ 0 == 0 ^ 1 == 1 и 1 ^ 1 == 0. Объединяя sum с no_carry_sum, мы получаем биты, в которых сумма отличается от побитового сложения. sum отличается только всякий раз, когда есть перенос в определенную битовую позицию. Таким образом, как половинные биты переноса, так и биты переноса могут быть получены практически без накладных расходов.

person George Phillips    schedule 07.02.2017
comment
Я тоже видел эту проблему. Для реализации вы можете сделать следующее: если входной перенос установлен, увеличьте a, если a==0, установите флаг c, затем выполните модифицированное добавление a,b, которое не может сбросить флаг c, но может его установить. Тогда обычное добавление a,b может сбросить флаг c и вызвать то же модифицированное добавление. - person Zeda; 07.02.2017
comment
Спасибо, оба. Поскольку я уже проверял наличие H/C, объединяя результат с 0x10/0x100 соответственно, мне просто нужно было добавить перенос после их суммирования, чтобы правильно сохранить флаги. Инструкции теперь проходят тесты! - person Triforcer; 07.02.2017