Инструкция Z80 DAA

#math #bcd #z80

#математика #bcd #z80

Вопрос:

Прошу прощения за этот, казалось бы, незначительный вопрос, но, похоже, я нигде не могу найти ответа — я как раз подхожу к реализации инструкции DAA в моем эмуляторе Z80, и я заметил в руководстве Zilog, что это предназначено для настройки накопителя для двоично-десятичной арифметики. В нем говорится, что инструкция предназначена для запуска сразу после команды сложения или вычитания.

Мои вопросы таковы:

  • что произойдет, если она будет запущена после другой инструкции?
  • как он узнает, какая инструкция предшествовала ему?
  • Я понимаю, что существует флаг N — но это, конечно, не будет окончательно указывать на то, что предыдущая инструкция была инструкцией сложения или вычитания?
  • Все равно ли это просто изменяет аккумулятор на основе условий, изложенных в таблице DAA, независимо от предыдущей инструкции?

Ответ №1:

Все равно ли это просто изменяет аккумулятор на основе условий, изложенных в таблице DAA, независимо от предыдущей инструкции?

ДА. Документация только сообщает вам, для чего предназначен DAA. Возможно, вы имеете в виду таблицу по этой ссылке:

 --------------------------------------------------------------------------------
|           | C Flag  | HEX value in | H Flag | HEX value in | Number  | C flag|
| Operation | Before  | upper digit  | Before | lower digit  | added   | After |
|           | DAA     | (bit 7-4)    | DAA    | (bit 3-0)    | to byte | DAA   |
|------------------------------------------------------------------------------|
|           |    0    |     0-9      |   0    |     0-9      |   00    |   0   |
|   ADD     |    0    |     0-8      |   0    |     A-F      |   06    |   0   |
|           |    0    |     0-9      |   1    |     0-3      |   06    |   0   |
|   ADC     |    0    |     A-F      |   0    |     0-9      |   60    |   1   |
|           |    0    |     9-F      |   0    |     A-F      |   66    |   1   |
|   INC     |    0    |     A-F      |   1    |     0-3      |   66    |   1   |
|           |    1    |     0-2      |   0    |     0-9      |   60    |   1   |
|           |    1    |     0-2      |   0    |     A-F      |   66    |   1   |
|           |    1    |     0-3      |   1    |     0-3      |   66    |   1   |
|------------------------------------------------------------------------------|
|   SUB     |    0    |     0-9      |   0    |     0-9      |   00    |   0   |
|   SBC     |    0    |     0-8      |   1    |     6-F      |   FA    |   0   |
|   DEC     |    1    |     7-F      |   0    |     0-9      |   A0    |   1   |
|   NEG     |    1    |     6-F      |   1    |     6-F      |   9A    |   1   |
|------------------------------------------------------------------------------|
  

Должен сказать, я никогда не видел спецификации инструкции daafter. Если вы внимательно изучите таблицу, то увидите, что действие инструкции зависит только от флагов C и H и значения в накопителе — оно вообще не зависит от предыдущей инструкции. Кроме того, в ней не раскрывается, что произойдет, если, например, C=0 , H=1 и младшая цифра в сумматоре равна 4 или 5. Поэтому в таких случаях вам придется выполнить NOP , или сгенерировать сообщение об ошибке, или что-то в этом роде.

Комментарии:

1. Большое спасибо — я надеюсь найти не слишком много более неоднозначных инструкций, подобных этой 🙂

2. DAA Z80 должен быть эквивалентен DAA и DAS для x86, поскольку они имеют одинаковое назначение. Ознакомьтесь с описаниями обоих для x86. Какой-то DAA доступен на многих процессорах.

3. @Alex: чипы x86 имеют две инструкции настройки десятичной дроби: DAA (настройка десятичной дроби после сложения) и DAS (настройка десятичной дроби после вычитания). Инструкция Z80 DAA объединяет их в одну, предполагая, что операнды самого последнего сложения / вычитания были действительными числами BCD.

4. Имейте в виду, что 8080 DAA отличается от Z80 тонкими, но важными особенностями (о которых я мало знаю).

5. Таблицу можно значительно улучшить, если заменить ADD, ADC, INC на N = 0, а SUB, SBC, DEC, NEG на N = 1. DAA не имеет представления, какая инструкция была выполнена последней.

Ответ №2:

Просто хотел добавить, что флаг N — это то, что они имеют в виду, когда говорят о предыдущей операции. Набор сложений N = 0, набор вычитаний N = 1. Таким образом, содержимое регистра A и флагов C, H и N определяют результат.

Инструкция предназначена для поддержки BCD-арифметики, но имеет и другие применения. Рассмотрим этот код:

     and  15
    add  a,90h
    daa
    adc  a,40h
    daa
  

Завершается преобразование младших 4 бит регистра в значения ASCII ‘0’, ‘1’, … ‘9’, ‘ A’, ‘B’, …, ‘F’. Другими словами, конвертер двоичных файлов в шестнадцатеричные.

Ответ №3:

Я также нашел эту инструкцию довольно запутанной, но я нашел это описание ее поведения из z80-heaven наиболее полезным.

При выполнении этой инструкции регистр A исправляется в формате BCD с использованием содержимого флагов. Точный процесс заключается в следующем: если четыре младших значащих бита A содержат цифру, отличную от BCD (т. е. она больше 9), или установлен флаг H, то в регистр добавляется $ 06. Затем проверяются четыре наиболее значимых бита. Если эта более значащая цифра также оказывается больше 9 или установлен флаг C, то добавляется 60 долларов.

Это обеспечивает простой шаблон для инструкции:

  • если младшие 4 бита образуют число, большее 9, или установлено значение H, добавьте 06 долларов к накопителю
  • если установлены старшие 4 бита, образующие число, большее 9 или C, добавьте 60 долларов к накопителю

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

Комментарии:

1. Похоже, что здесь отсутствуют последние четыре операции (SUB, SBC, NEG и DEC).

2. @Salgat То же правило применяется, когда N=1 . Единственное, что вам нужно вычесть поправку, когда N=1 .

Ответ №4:

Это рабочий код, правильно реализующий DAA и проходящий тесты zexall / zexdoc / z80test с кодом операции Z80.

Основана на недокументированном Z80 Documented, pag 17-18.

 void daa()
{
   int t;
    
   t=0;
    
   // 4 T states
   T(4);
    
   if(flags.H || ((A amp; 0xF) > 9) )
         t  ;
    
   if(flags.C || (A > 0x99) )
   {
         t  = 2;
         flags.C = 1;
   }
    
   // builds final H flag
   if (flags.N amp;amp; !flags.H)
      flags.H=0;
   else
   {
       if (flags.N amp;amp; flags.H)
          flags.H = (((A amp; 0x0F)) < 6);
       else
          flags.H = ((A amp; 0x0F) >= 0x0A);
   }
    
   switch(t)
   {
        case 1:
            A  = (flags.N)?0xFA:0x06; // -6:6
            break;
        case 2:
            A  = (flags.N)?0xA0:0x60; // -0x60:0x60
            break;
        case 3:
            A  = (flags.N)?0x9A:0x66; // -0x66:0x66
            break;
   }
    
   flags.S = (A amp; BIT_7);
   flags.Z = !A;
   flags.P = parity(A);
   flags.X = A amp; BIT_5;
   flags.Y = A amp; BIT_3;
}
  

Для визуализации взаимодействий DAA, в целях отладки, я написал небольшую программу сборки Z80, которая может быть запущена в реальном ZX Spectrum или в эмуляции, которая точно эмулирует DAA: https://github.com/ruyrybeyro/daatable

Что касается ее поведения, то я получил таблицу флагов N, C, H и register A до и после DAA, созданную с помощью вышеупомянутой программы сборки: https://github.com/ruyrybeyro/daatable/blob/master/daaoutput.txt