Math Z80 DAA指令

Math Z80 DAA指令,math,bcd,z80,Math,Bcd,Z80,对于这个看似微不足道的问题,我深表歉意,但我似乎在任何地方都找不到答案——我只是想在我的Z80模拟器中实现DAA指令,我在Zilog手册中注意到,它是为了调整二进制编码十进制算术的累加器。它说,该指令打算在加法或减法指令之后立即运行 我的问题是: 如果在另一条指令后运行,会发生什么情况 它如何知道它之前的指令 我意识到有N标志-但这肯定不会明确表示之前的指令是加法或减法指令 它是否只是根据DAA表中列出的条件修改累加器,而不考虑前面的指令 它是否只是根据DAA表中列出的条件修改累加器,而不考

对于这个看似微不足道的问题,我深表歉意,但我似乎在任何地方都找不到答案——我只是想在我的Z80模拟器中实现DAA指令,我在Zilog手册中注意到,它是为了调整二进制编码十进制算术的累加器。它说,该指令打算在加法或减法指令之后立即运行

我的问题是:

  • 如果在另一条指令后运行,会发生什么情况
  • 它如何知道它之前的指令
  • 我意识到有N标志-但这肯定不会明确表示之前的指令是加法或减法指令
  • 它是否只是根据DAA表中列出的条件修改累加器,而不考虑前面的指令
它是否只是根据DAA表中列出的条件修改累加器,而不考虑前面的指令

对。文档只是告诉您DAA的用途。也许您指的是以下表格:


我必须说,我从未见过dafter指令规范。如果仔细检查该表,您将看到指令的效果只取决于
C
H
标志以及累加器中的值——它根本不取决于之前的指令。此外,它也不会泄露发生什么情况,例如,
C=0
H=1
,累加器中的低位是4或5。因此,在这种情况下,您必须执行一个
NOP
,或者生成一条错误消息,或者其他什么。

只是想补充一点,当他们谈到上一个操作时,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’。换句话说,一个二进制到十六进制的转换器。

我发现这条指令也相当混乱,但我发现对它的行为的描述非常有用

执行此指令时,使用标志的内容对A寄存器进行BCD校正。具体过程如下:如果A的最低有效位包含非BCD数字(即大于9)或设置了H标志,则$06被添加到寄存器中。然后检查四个最高有效位。如果这个更高的有效数字碰巧也大于9或设置了C标志,则添加$60

这为指令提供了一个简单的模式:

  • 如果低4位构成大于9的数字或设置了H,则向累加器中添加$06
  • 如果高4位构成大于9的数字或设置了C,则向累加器中添加$60

此外,虽然DAA计划在加法或减法后运行,但它可以随时运行。

这是生产代码,正确实现了DAA,并通过了zexall/zexdoc/z80test Z80操作码测试

根据第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;
}
void daa()
{
int t;
t=0;
//4T状态
T(4);
if(flags.H | |((A&0xF)>9))
t++;
if(flags.C | |(A>0x99))
{
t+=2;
C=1;
}
//建立最终的H标志
if(flags.N&&!flags.H)
H=0;
其他的
{
if(flags.N&&flags.H)
flags.H=((A&0x0F))<6);
其他的
flags.H=((A&0x0F)>=0x0A);
}
开关(t)
{
案例1:
A+=(flags.N)?0xFA:0x06;//-6:6
打破
案例2:
A+=(flags.N)?0xA0:0x60;//-0x60:0x60
打破
案例3:
A+=(flags.N)?0x9A:0x66;/-0x66:0x66
打破
}
flags.S=(A&BIT_7);
flags.Z=!A;
P=奇偶校验(A);
flags.X=A&BIT_5;
flags.Y=A&BIT_3;
}
为了可视化DAA交互,为了调试,我编写了一个小型Z80汇编程序,可以在实际的ZX频谱中运行,也可以在精确模拟DAA的仿真中运行:


就其行为而言,在使用上述汇编程序生成DAA之前和之后,得到了一个包含标志N、C、H和寄存器a的表格:

非常感谢-我希望不会有太多像这样模棱两可的指令:-)Z80的DAA应该等同于x86的DAA和DAS,因为它们具有相同的用途。查看两者的x86描述。许多CPU上都有某种DAA。@Alex:x86芯片有两条十进制调整指令:DAA(加法后的十进制调整)和DAS(减法后的十进制调整)。Z80 DAA指令通过假设最近的加法/减法运算的操作数是有效的BCD数,将它们合并为一个。请注意,8080 DAA与Z80 DAA在细微但重要的方面(我对此知之甚少)有所不同。如果将add、ADC、INC替换为N=0,将SUB、SBC、DEC、NEG替换为N=1,则该表可以大大改进。DAA不知道最后执行的是哪条指令。它似乎缺少最后四个操作(SUB、SBC、NEG和DEC)。@Salgat当
N=1
时,同样的规则适用。唯一的问题是,当
N=1
时,必须减去校正量。
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;
}