Intel 8085の未定義命令DSUBの動作

Intel 8085のエミュレータを作っていて、未定義命令DSUBの動作詳細が分からなかったので調査しました。分からなかったのは、ハーフキャリーフラグ(ACフラグ)と、未定義フラグX、Vの処理です。

DSUB命令とは

DSUBは、HL = HL - BC を実施する16ビット減算命令です。まぁまぁ使えそうな命令なのですが、公式ドキュメントには記載がなく、未定義命令扱いになっています。

Intel 8085には、DSUBを含めて10種類の未定義命令があります。また、未定義のフラグが2つ(XフラグとVフラグ)追加されています。詳しくは調べていないのですが、これらの未定義命令や未定義フラグは、どのメーカーの8085でも共通に利用できるようです。

ただし、実際にはほとんど使われることがなかったようで、インターネットで公開されている各種のエミュレータでは、これらの未定義命令や未定義フラグは、ほぼ完全に無視されています。

未定義なものなので正しいといえば正しいのですが、実機と動作が異なるのは少し気持ちが悪いので、SBC8085というワンボードマイコンを組み立てて動作を調査してみました。

調査用のアセンブリコード

SBC8085のBASICでは、MONコマンドを実行すると機械語モードになり、そこでアセンブリコードを入力して機械語コードをメモリー上に配置できます。配置した機械語コードは、EXECコマンドで呼び出せます。また、BASICの変数AはF002Hから、変数BはF004Hから、変数CはF006Hから、変数DはF008Hからの16ビット値として配置されるので、それを使ってBASICコードと機械語コードの間で値を受け渡せます。

この仕組みを使って、テストコードを作ります。まずは、機械語モードで次のアセンブリコードを入力します。

LHLD F002
MOV B, H
MOV C, L
LHLD F004
DEFINE 08
SHLD F006
PUSH PSW
POP H
MVI H, 0
SHLD F008
RET

「DEFINE 08」がDSUB命令を示します。見ての通り、変数Aの値をBCレジスタ、変数Bの値をHLレジスタにセットしてから、DSUB命令を実行するコードになっています。演算結果は変数Cに、フラグレジスタの値は変数DにセットしてBASICに戻ります。

調査用のBASICコード

BASICコードは次の4種類を用意しました。全入力値に対するDSUB命令の処理結果を調べられればよかったのですが、実機の処理が遅く、それは断念しました。

  1. HLは-32768固定、BCを-32768〜32767まで変化させるコード
  2. BCは-32768固定、HLを-32768〜32767まで変化させるコード
  3. 境界値になりそうな30個の値を用意し、それらの組み合わせをBC、HLにセットするコード
  4. ランダムな値をBC、HLにセットするコード

1番めのコードは次の通りです。

10 B=-32767
20 B=B-1
30 FOR A=B TO 32766
40 EXEC 0
50 PRINT #1,A,",",
51 PRINT #1,B,",",
52 PRINT #1,C,",",
53 PRINT #1,D
60 NEXT A
70 A=32767
80 EXEC 0
90 PRINT #1,A,",",
91 PRINT #1,B,",",
92 PRINT #1,C,",",
93 PRINT #1,D

2番めのコードは次の通りです。

10 A=-32767
20 A=A-1
30 FOR B=A TO 32766
40 EXEC 0
50 PRINT #1,A,",",
51 PRINT #1,B,",",
52 PRINT #1,C,",",
53 PRINT #1,D
60 NEXT B
70 B=32767
80 EXEC 0
90 PRINT #1,A,",",
91 PRINT #1,B,",",
92 PRINT #1,C,",",
93 PRINT #1,D

3番めのコードは次の通りです。

10 @(0)=-32767
11 @(1)=-32513
12 @(2)=-32512
13 @(3)=-16384
14 @(4)=-256
15 @(5)=-241
16 @(6)=-1
17 @(7)=0
18 @(8)=1
19 @(9)=2
20 @(10)=15
21 @(11)=16
22 @(12)=255
23 @(13)=256
24 @(14)=511
25 @(15)=512
26 @(16)=766
27 @(17)=767
28 @(18)=768
29 @(19)=770
30 @(20)=1024
31 @(21)=1025
32 @(22)=4369
33 @(23)=4660
34 @(24)=8192
35 @(25)=16384
36 @(26)=32258
37 @(27)=32511
38 @(28)=32512
39 @(29)=32767
40 I=0
50 A=@(I)
60 FOR J=0 TO 29
61 B=@(J)
62 EXEC 0
63 PRINT #1,A,",",
64 PRINT #1,B,",",
65 PRINT #1,C,",",
66 PRINT #1,D
67 NEXT J
70 I=I+1
80 IF I<30 GOTO 50

4番めのコードは次の通りです。

10 FOR J=1 TO 100
20 GOSUB 100
21 A=X
30 GOSUB 500
31 IF X=1 GOTO 40
32 A=-A
40 GOSUB 100
41 B=X
50 GOSUB 500
51 IF X=1 GOTO 60
52 B=-B
60 EXEC 0
61 PRINT #1,A,",",
62 PRINT #1,B,",",
63 PRINT #1,C,",",
64 PRINT #1,D
65 NEXT J
70 STOP
100 E=RND(17)
110 IF E>7 GOTO 100
120 F=RND(17)
130 IF F>15 GOTO 120
140 G=RND(17)
150 IF G>15 GOTO 140
160 H=RND(17)
170 IF H>15 GOTO 160
180 X=E
181 X=X+X
182 X=X+X
183 X=X+X
184 X=X+X
185 X=X+F
186 X=X+X
187 X=X+X
188 X=X+X
189 X=X+X
190 X=X+G
191 X=X+X
192 X=X+X
193 X=X+X
194 X=X+X
195 X=X+H
200 RETURN
500 X=RND(17)
510 IF X>2 GOTO 500
520 IF X<1 GOTO 500
530 RETURN

ごちゃごちゃやっていますが、これはできるだけバラツキのある値にしようとした結果です。FORループを100回しか回していませんが、乱数周期の問題で、これ以上増やしても無意味なのです。

テスト結果

テストは、NEC D8085AHCと、OKI M80C85Aを使って実施しました。結果は次のCSVファイルに格納しています。

DSUB命令の動作

調査結果に基づいて推測したDSUB命令の動作を、Pythonコードで示します。

SF = 0x80
ZF = 0x40
XF = 0x20
AF = 0x10
PF = 0x04
VF = 0x02
CF = 0x01

def parity8(v: int) -> bool:
    return bin(v & 0xFF).count("1") % 2 == 0

while True:
    f = 0
    print("HL = ", end='', flush=True)
    hl = int(input())
    print("BC = ", end='', flush=True)
    bc= int(input())

    # mask
    hl = hl & 0xFFFF
    bc = bc & 0xFFFF
    minus_bc = (~bc + 1) & 0xFFFF
    result = hl + minus_bc

    # Sign
    if result & 0x8000:
        f |= SF
    else:
        f &= ~SF

    # Zero
    if (result & 0xFFFF) == 0:
        f |= ZF
    else:
        f &= ~ZF

    # Parity
    regH = (result >> 8) & 0xFF
    if parity8(regH):
        f |= PF
    else:
        f &= ~PF

    # Carry
    if hl < bc:
        f |= CF
    else:
        f &= ~CF

    # X/V
    a = hl
    b = bc
    res = result & 0xFFFF
    overflow = ((a ^ b) & (a ^ res) & 0x8000) != 0
    if overflow:
        f |= VF
    else:
        f &= ~VF
    x5 = overflow ^ bool(res & 0x8000)
    if x5:
        f |= XF
    else:
        f &= ~XF

    # AC
    a = hl & 0xFFF
    b = bc & 0xFFF
    ac = a >= b
    if ac:
        f |= AF
    else:
        f &= ~AF

    print("SZXA0PVC")
    print(f"{f:08b}")

注意すべきは、パリティフラグは、結果の上位8ビットだけに影響されるということです。あとは概ね、8ビット演算でのフラグ処理を、そのまま16ビット演算に拡張したような処理になっています。

コメント

タイトルとURLをコピーしました