Z80的DAA指令

22

非常抱歉这个问题似乎很小,但我似乎找不到答案 - 我正要在我的Z80模拟器中实现DAA指令,并且我注意到在Zilog手册中,它旨在调整累加器进行二进制编码的十进制算术运算。它说该指令旨在在加法或减法指令之后立即运行。

我的问题是:

  • 如果在其他指令之后运行该指令会发生什么?
  • 它如何知道先前运行的是哪个指令?
  • 我知道有N标志 - 但这肯定不会明确表示之前的指令是加法还是减法指令吧?
  • 它是否基于DAA表中列出的条件,无论先前的指令如何,都会修改累加器?
Translated:

很抱歉,这个看起来似乎很小的问题,但是我却找不到答案 - 我将要在我的Z80模拟器中实现DAA指令,在Zilog手册中我注意到其目的是为了进行二进制编码的十进制算术运算而调整寄存器的值,该指令旨在在加法或减法指令之后立即运行。

我的问题如下:

  • 如果在其他指令之后运行该指令会发生什么?
  • 它如何知道先前运行的是哪个指令?
  • 我明白有N标志,但它是否能够明确表示之前的指令是加法还是减法指令?
  • 无论先前的指令是什么,它是否仍然会根据DAA表中的条件修改寄存器的值?
4个回答

18

无论先前的指令是什么,它是否只是根据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   |
|------------------------------------------------------------------------------|

我必须说,我从未见过如此愚蠢的指令规范。如果您仔细检查表格,您会发现该指令的效果仅取决于CH标志以及累加器中的值--它根本不依赖于先前的指令。此外,它也没有透露如果例如C=0H=1,并且累加器中的低位数字为4或5时会发生什么。因此,在这种情况下,您将不得不执行一个NOP,或生成一个错误消息,或其他操作。


非常感谢 - 我希望不会再遇到太多这样的模棱两可的指令 :-) - PhilPotter1987
2
Z80的DAA应该与x86的DAA和DAS等效,因为它们具有相同的目的。请查看x86对两者的描述。许多CPU上都提供某种形式的DAA。 - Alexey Frunze
2
@Alex:x86芯片有两个十进制调整指令:DAA(加法后十进制调整)和DAS(减法后十进制调整)。Z80的DAA指令将它们合并为一个,假设最近的加法/减法操作数是有效的BCD数字。 - TonyK
1
请注意,8080 DAA 与 Z80 的微妙但重要的差异(我知之甚少)不同。 - Dan Sheppard
1
如果用N=0替换ADD、ADC、INC,用N=1替换SUB、SBC、DEC、NEG,则可以大大改善表格。DAA不知道上一条指令是什么。 - Rui F Ribeiro

12

只是想补充一下,当他们谈论上一个操作时,N标志是他们的意思。加法设置N = 0,减法设置N = 1。因此,A寄存器和C、H和N标志的内容决定了结果。

该指令旨在支持BCD算术,但也具有其他用途。考虑以下代码:

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

它将A寄存器的低4位转换为ASCII值'0','1',...,'9','A','B',...,'F'。 换句话说,它是一个二进制到十六进制的转换器。


8
我也觉得这个指令有些困惑,但我在 z80-heaven 上找到了一个对其行为的描述,我认为这个描述是最有帮助的。
执行此指令时,使用标志的内容对A寄存器进行BCD校正。确切的过程如下:如果A的最低四位包含非BCD数字(即大于9),或设置了H标志,则添加$ 06到寄存器中。然后检查四个最高有效位。如果这个更高位数也恰好大于9或设置了C标志,则添加$ 60。
这提供了该指令的简单模式:
- 如果较低的4位形成的数字大于9或设置了H,则向累加器添加$ 06 - 如果较高的4位形成的数字大于9或设置了C,则向累加器添加$ 60
同时,虽然DAA旨在在加法或减法之后运行,但可以随时运行。

看起来缺少了最后四个操作(SUB,SBC,NEG和DEC)。 - Austin Salgat
3
N=1 时,同样适用这个规则。唯一需要注意的是,在 N=1 时需要减去校正值。 - GabrielOshiro

7

这是生产环境中的代码,正确实现了DAA,并通过了zexall / zexdoc / z80test Z80指令测试套件。

基于The Undocumented Z80 Documented,第17-18页。

void daa()
{
   int t;
    
   t=0;
    
   // 4 T states
   T(4);
    
   if(flags.H || ((A & 0xF) > 9) )
         t++;
    
   if(flags.C || (A > 0x99) )
   {
         t += 2;
         flags.C = 1;
   }
    
   // builds final H flag
   if (flags.N && !flags.H)
      flags.H=0;
   else
   {
       if (flags.N && flags.H)
          flags.H = (((A & 0x0F)) < 6);
       else
          flags.H = ((A & 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 & BIT_7);
   flags.Z = !A;
   flags.P = parity(A);
   flags.X = A & BIT_5;
   flags.Y = A & BIT_3;
}

为了可视化DAA交互,并用于调试目的,我编写了一个小的Z80汇编程序,可以在实际的ZX Spectrum或模拟器中运行,该模拟器可以精确模拟DAA: https://github.com/ruyrybeyro/daatable 使用上述汇编程序产生了一个标志位N,C,H和寄存器A之前和之后的DAA表格:https://github.com/ruyrybeyro/daatable/blob/master/daaoutput.txt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接