MSXPENでアセンブラを試してみる。

コンピュータ

幼少のころMSXというパソコンで雑誌のゲームプログラムを打ち込んで遊んだ記憶があります。
MSX-BASICは何となく学習した記憶がありますが、マシン語はとても理解できるような代物ではないと感じた記憶があります。
最近WebMSXというWebで実行できるMSXのエミュレーターがあることを知りました。さらにWebMSXにBASICとアセンブラの開発機能を付加したMSXPENというサイトがあることを知りました。
昔全く手も足も出なかったMSXのマシン語を開発する環境がWEBで誰でも利用できるようになっていることを知り少し試してみたい衝動に駆られました。とりあえずアセンブラで文字を表示するプログラムとそのプログラムを呼び出すBASICのプログラムを抑えておきたいと思います。

1文字を表示するサンプルでMSXPENの操作確認

ソースコード

Basic

10 CLEAR 100,&HD000
20 DEFUSR=&HD000
30 BLOAD"program.bin"
40 A=USR(0)
50 END

Asm

           ORG     0D000H
CHPUT       EQU     00A2H
            LD      A,41H
            CALL    CHPUT
            RET
            END

実行

RunをクリックするかCtrl + Alt +R

アルファベットの”A”が表示されました。

BASICのプログラムの流れを確認します。

10 CLEAR 100,&HD000
アドレスD000から100バイトの領域をクリア
20 DEFUSR=&HD000
USR()で呼び出すマシン語のアドレスを指定
30 BLOAD"program.bin"
マシン語のバイナリが入ったファイル”program.bin”をディスクから読み込み
40 A=USR(0)
マシン語のプログラムを呼び出す。
END
終了

アセンブラのプログラムの流れを確認します。
ORG 0D000H
プログラムの開始アドレスを0D000Hに設定
CHPUT EQU 00A2H
シンボルCHPUTに00A2Hを設定。
00A2HはMSXのBIOSで文字を表示するサブルーチンのアドレスになります。
後でCALL命令で呼び出しますが、その際呼び出すアドレスを00A2Hの代わりにCHPUTというシンボルで指定します。
LD A,41H
Aレジスタに41Hを代入
16進数の41HはASCII文字コードの”A”のコードになります。
CALL CHPUT
CALL文字を表示するBIOSを呼び出しています。
RET
プログラムの呼び出し元にプログラムの制御を返します。
このプログラムの場合BASICのUSR()で呼び出されていますので、BASIC側にプログラムの制御が戻ります。
END
アセンブラの終了。

BASICからマシン語(アセンブラ)を呼び出す流れになっています。MSXPENではアセンブラを記述することが出来ますが、記述したアセンブラは実行時マシン語に変換され”program.bin”ファイルとして保存されます。
BASICのBLOAD”program.bin”はAドライブの仮想フロッピーに保存されるファイルを読み込みメモリ上にセットします。歯車アイコンでファイル名を変更することが出来ます。またBASICのコードは同じくフロッピーディスクにAUTOEXEC.BASというファイル名で保存されます。AUTOEXEC.BASというファイル名でBASICのプログラムをフロッピーに保存すると、フロッピーディスクを挿入した状態でMSX(エミュレーター)を起動した際自動的にAUTOEXEC.BASをロードし実行するようになります。

大まかなながれとして、
フロッピーディスクが挿入された状態でMSX起動→フロッピーディスクのAUTOEXEC.BASを読み込み実行→BASIC内でBLOAD”program.bin”でマシン語バイナリを読み込み→マシン語プログラムを実行→BASICへ戻り終了



今回学習したことは、こんなところです。

WebMSX側で文字をペーストする方法

Alt+V→Ctrl+V

ディスクイメージファイル(.DSK)からファイルを抽出する方法

フリーソフトのWinImageを使う。
Windows11でversion10.0の動作を確認しました。

雑誌掲載プログラム

MSXマガジンのバックナンバーがアカシックライブラリで無料で公開されています。
記事にBASICプログラムのソースコードが掲載されていて昔は慣れないキーボードに四苦八苦しながら入力した記憶あります。

今の技術を使うと手軽に入力できるのでは無いかと考えました。
・記事からプログラム部分をスキャナなどで画像ファイルを作成する。
・紙の凹凸がスキャンされているので、GIMPで加工。「色」→「レベル」白色が画像の空白部分、黒色が文字になるように画像を修正。
・GIMPでプログラム以外不要部分も消去。プログラム以外が含まれないように画像サイズを変更など。
・画像をGoogleドライブにアップロードし、「アプリで開く」→「Googleドキュメント」で開く
・OCRされてテキスト化されますのでこれをMSXPENに張り付けて「Run」で実行
・エラーが出る場合は修正。

多分手で入力するより速いと思われます。ただ、OCRがうまくいかない部分のエラー修正は大変そうですが…

Z80命令の動作確認

書籍などを読んでもCPU(Z80)の動作がいまいちピンとこないので、命令を試す短いプログラムを組んで動作確認をしたいと思います。知りたい点としては命令を実行することでレジスタの状態のどのように変化するかです。レジスタの状態をリアルタイムに出来るデバッカーがあると良いのですが、とりあえずMSXPENで確認する方法を模索したいと思います。

LD命令とAFレジスタの中身

Basic

10 CLEAR 100,&HD000
20 DEFUSR=&HD00F
30 BLOAD"program.bin"
40 A=USR(0)
50 PRINT "A &H"+RIGHT$("00"+HEX$(PEEK(&HD000)),2)
60 PRINT "F "+RIGHT$("00000000"+BIN$(PEEK(&HD001)),8)
200 END

Asm

            ORG  0D00FH
            LD   A,1H
            PUSH AF
            POP  BC
            LD   A,B
            LD   (0D000H),A
            LD   A,C
            LD   (0D001H),A
            RET
            END

レジスタの中身を文字列で画面に表示するプログラムを組むことにします。ただ画面に文字列を表示するだけでもアセンブラで書くにはスキルが全く足りていません。表示部分はBASICで作成することにします。

BASICの命令でPEEK命令で指定アドレスのメモリの中身を数値として変数に取り出すことが出来ます。アセンブラでメモリに指定番地にレジスタの中身をセットし、BASIC側でPEEKを使ってメモリの指定番地の値を取り出せばレジスタの内容を取得できるはずです。(BASICへ戻ることでメモリの中身が破壊されていないことを希望)HEX$は数値を16進数文字列にBIN$は数値を2進数へ変換します。また、RIGHT$は右から指定文字を切り出す命令で文字列の先頭に0を埋めることで表示桁数を調整しています。文字列の加工が済んだらPRINT命令で画面へ表示しています。

アセンブラでLD A,1Hで数値の1をAレジスタに代入しています。(Hは16進数を表します。)
Aレジスタに関してはこのまま指定のアドレスに内容をセットすれば良いのですが、問題はFレジスタ(フラグレジスタ)です。
Fレジスタの内容をメモリにセットする命令が見つかりませんでした。存在するのかもしれませんが自分には見つけることが出来ませんでした。(理解が不足しているだけかも)

代替策として、PUSH命令でレジスタをスタックへ退避する命令を使いAレジスタとFレジスタを一旦退避し、POP命令でスタックからリストアします。その際AFではなくBCレジスタへリストアします。結果としてAFレジスタの中身をBCレジスタへ代入した形になります。

次にBレジスタCレジスタの内容をメモリにセットすることでAレジスタとFレジスタの内容をセットしたことに成ります。

実行したところ以下の様になりました。

Aレジスタはセットした1が表示されており、セットする値を変えると連動して表示される値も変わることから、想定通り動作していると思われます。
Fレジスタは確認する術がないのですが、とりあえず何らかのデータが表示されています。

Fレジスタは以下のビットに各フラグが割り当てられています。
7. S:サインフラグ
6. Z:ゼロフラグ
5. 未使用
4. H:ハーフキャリーフラグ
3. 未使用
2. P/V:パリティ・オーバーフローフラグ
1. N:加算減算フラグ
0. C:キャリーフラグ
フラグの意味はオイオイ確認するとしてBASICのプログラムにに以下の行を追加します。

55 PRINT "  SZ-H-PNC"


こうしてみると、いくつかフラグが立っていることが確認できます。ゼロフラグ(Z)が立っていますが、Aレジスタの状態だとすると0では無いので矛盾します。フラグレジスタの取得方法が間違っているのか、自分の理解が誤っているのかなんとも言えませんが、そのうち調べたいと思い思います。

メモリをアドレス指定で参照したり書き換えたりできたりと、C言語のポインタ以上に今時のプログラミング言語では見られない操作だと思います。また、アセンブラの命令のLDを試しました。これは高級言語における変数への代入命令だと思うのですが、変数の変わりにレジスタやアドレスを指定しています。高級言語と比べてプログラマが意識的に管理する項目が多く、もっと若いころに習得していれば良かったと痛感しています。

OR A

Aレジスタ同士?の論理和をとる命令です。高級言語ぽく書くとA = A | Aだと思われます。

Aレジスタは8ビットですので0か1の値がもてるbitが8個で、それを論理和では各bitに対し以下のよう組み合わせになります。

0 OR 0 = 0
0 OR 1 = 1
1 OR 0 = 1
1 OR 1 = 1

8ビットの並びを2進数で表すと000000000~11111111で、2の8乗で256パターン、正の整数値であれば0~255の10進数の数値に割り当てることがでます。

Aレジスタ同士の各ビットは当然同じbitの並びになり、論理和をとっても0 OR 0 = 0または1 OR 1 = 1のいずれかとなり、元のAレジスタと同じbitの並びになります。

値に変化は無いはずなので一見無意味そうに見えます。この辺りも自分の理解が誤っているような気がしてきます。
では前回のサンプルプログラムのアセンブラ部分にOR A加えてみたいと思います。
Asm(BASICは変更なし)

            ORG  0D00FH
            LD   A,01H
            OR   A
            PUSH AF
            POP  BC
            LD   A,B
            LD   (0D000H),A
            LD   A,C
            LD   (0D001H),A
            RET
            END


予想通りAレジスタの値は変化していません。ただし、Fレジスタに変化が見受けられます。前回1になっていたゼロフラグが今回は0となっており矛盾が解消されています。

それではAレジスタに0をセットした場合ゼロフラグが1になるか試してみたいと思います。
Asm(BASICは変更なし)

            ORG  0D00FH
            LD   A,00H
            OR   A
            PUSH AF
            POP  BC
            LD   A,B
            LD   (0D000H),A
            LD   A,C
            LD   (0D001H),A
            RET
            END


予想通りゼロフラグが1になっていました。
OR AはAレジスタは変化しませんがFレジスタが更新される機能があるようです。
山本研究室様のZ80命令セットのページを見ながら勉強しているのですが、Z80の命令表を見るとフラグの項目があり、命令を実行すると変更される可能性があるフラグを表しているようです。
これも誤った理解かもしれませんが、コードを変更して予想通りの結果が得られると小さな喜びを感じます。

XOR A

こちらはAレジスタ同士の排他的論理和を取ります。

OR Aを見たときにはレジスタの値が変化しない処理に意味があるのかと思いましたが、XOR Aの場合Aレジスタに何がセットされているか不明の状態で処理が行われてる点が理解不能です。

XORの組み合わせは以下の通りなります。
0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0

Aレジスタ同士の取りうるパターンはORと同様に 0 XOR 0 = 0と 1 XOR 1 = 0の二通りで今度はいずれも結果が0になっています。
この特性を使ってAレジスタのすべてのbitを0にすることでAレジスタの値を0で初期化することになります。
さらにXORはFレジスタが更新します。

OR AのAsmのサンプルではLD A,00HとOR Aの2つの命令でAレジスタを0に初期化しFレジスタを更新を行っていましたが、XOR Aの一つの命令で済ませることができます。
Asm(BASICは変更なし)

            ORG  0D00FH
            XOR  A
            PUSH AF
            POP  BC
            LD   A,B
            LD   (0D000H),A
            LD   A,C
            LD   (0D001H),A
            RET
            END

結果

Aレジスタが0となっていてFレジスタが更新されていることが確認できます。

高級言語でも論理演算を使うことがありますが、アセンブラでは改めて学びなおしたほうが良いと感じました。

DECとINC

DECはレジスタの値を1つ減算し、INCはレジスタの値を1つ加算します。

Aレジスタが0の状態でDECを実行してみます。
Asm

            ORG  0D00FH
            XOR  A         ; 0クリア
            DEC  A         ; デクリメント
            PUSH AF
            POP  BC
            LD   A,B
            LD   (0D000H),A
            LD   A,C
            LD   (0D001H),A
            RET
            END


S(サイン)フラグとN(加減算)フラグが立っています。
S(サイン)フラグは演算結果が正の数値の場合0、負の数値の場合1。
N(加減算)フラグは加算命令が0、減算命令が1。

今回はAレジスタが0の状態でDEC(減算)を行い結果がHFFとなりました。
Sフラグは1で負にNフラグは1で減算命令となっています。

Aレジスタの値は16進数のFFですので2進数の11111111とすべてのビットが立っている状態です。
Sフラグが1で負の数値ということですので、ビットを反転し1を加えると2進数で00000001となり、-1という意味になるようです。

Aレジスタが7F(10進数127)の状態でINCを実行してみます。

            ORG  0D00FH
            LD   A,7FH     ; A <= 7F
            OR   A         ; Fレジスタ更新
            INC  A         ; インクリメント
            PUSH AF
            POP  BC
            LD   A,B
            LD   (0D000H),A
            LD   A,C
            LD   (0D001H),A
            RET
            END


Sフラグは1で負にNフラグは0で加算命令となっています。

Aレジスタは16進数で80で2進数で10000000になります。
Sフラグが1で負の数値ですのでビット反転で0111111さらに1を加えて10000000ともとに戻りましたが、-128という意味になるようです。

LD (アドレス) HL


サンプルプログラムはAFレジスタの中身を画面に表示するために、レジスタの内容を8ビットで2回に分けてメモリに書き込みをしています。
これを一度に命令で処理するために16ビットのLD命令に置き換えてみます。
Basic

10 CLEAR 100,&HD000
20 DEFUSR=&HD00F
30 BLOAD"program.bin"
40 A=USR(0)
50 PRINT "A "+RIGHT$("00"+HEX$(PEEK(&HD001)),2)
55 PRINT "  SZ-H-PNC"
60 PRINT "F "+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)
200 END

Asm

            ORG  0D00FH
            LD   A,7FH     ; A <= 7F
            OR   A         ; Fレジスタ更新
            INC  A         ; インクリメント
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            RET
            END

AFレジスタの内容をPUSH,POPを使いHLレジスタに代入しアドレス0D000Hにセットします。この場合、0D000HにF(L)レジスタの内容が、0D001HにA(H)レジスタの内容がセットされます。
それに合わせてBasicのPEEKのアドレスを変更しました。

CALL


CALL命令を使うとサブルーチンを呼び出すことが出来ます。サブルーチンの実行開始行のアドレスを指定して呼び出す必要がありますが、アドレスを手動で計算するのは大変ですので、ラベルを使ってアセンブラで自動計算させることが出来ます。
サンプルプログラムではレジスタの内容をメモリにセットする部分をサブルーチン化してみました。

            ORG  0D00FH
            LD   A,7FH     ; A <= 7F
            OR   A         ; Fレジスタ更新
            INC  A         ; インクリメント
            CALL SETMEM
            RET
SETMEM      PUSH AF
            POP  HL
            LD   (0D000H),HL
            RET
            END

実行するとBASICに戻ってきたので多分正常動作していると思います。

ほかの汎用レジスタも表示


CALLでサブルーチン化しましたので、AF以外の汎用レジスタに対応してみたいと思います。
Asm

            ORG  0D00FH
            XOR  A        ; A <= 0
            LD   B, 01H
            LD   C, 02H
            LD   D, 03H
            LD   E, 04H
            LD   H, 05H
            LD   L, 06H
            CALL SETMEM
            RET
SETMEM      LD   (0D006H),HL
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            LD   (0D002H),BC
            LD   (0D004H),DE
            RET
            END

Basic

10 CLEAR 100,&HD000
20 DEFUSR=&HD00F
30 BLOAD"program.bin"
40 A=USR(0)
50 PRINT "A:"+RIGHT$("00"+HEX$(PEEK(&HD001)),2)+"H"
60 PRINT "  SZ-H-PNC"
70 PRINT "F:"+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)
80 PRINT "B:"+RIGHT$("00"+HEX$(PEEK(&HD003)),2)+"H C:"+RIGHT$("00"+HEX$(PEEK(&HD002)),2)+"H"
90 PRINT "D:"+RIGHT$("00"+HEX$(PEEK(&HD005)),2)+"H E:"+RIGHT$("00"+HEX$(PEEK(&HD004)),2)+"H"
100 PRINT "H:"+RIGHT$("00"+HEX$(PEEK(&HD007)),2)+"H L:"+RIGHT$("00"+HEX$(PEEK(&HD006)),2)+"H"
200 END

結果

ダンプ機能


メモリの内容も見れると便利かと思いダンプ機能を追加してみました。
Basic(Asmは前項と同じ)

10 CLEAR 100,&HD000
20 DEFUSR=&HD00F
30 BLOAD"program.bin"
40 A=USR(0)
50 PRINT "A:"+RIGHT$("00"+HEX$(PEEK(&HD001)),2)+"H"
60 PRINT "  SZ-H-PNC"
70 PRINT "F:"+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)
80 PRINT "B:"+RIGHT$("00"+HEX$(PEEK(&HD003)),2)+"H C:"+RIGHT$("00"+HEX$(PEEK(&HD002)),2)+"H"
90 PRINT "D:"+RIGHT$("00"+HEX$(PEEK(&HD005)),2)+"H E:"+RIGHT$("00"+HEX$(PEEK(&HD004)),2)+"H"
100 PRINT "H:"+RIGHT$("00"+HEX$(PEEK(&HD007)),2)+"H L:"+RIGHT$("00"+HEX$(PEEK(&HD006)),2)+"H"
200 INPUT "HEX or END"; N$      ' アドレスを16進数文字列で入力
210 IF N$ = "END" THEN GOTO 500 ' ENDと入力すると終了
220 C = 0
230 FOR I = 1 TO LEN(N$) ' 入力した文字数分ループ
240 C = C * 16           ' 16進数の桁上がり
250 A$ = MID$(N$, I, 1)  ' 文字列を左側から1文字づつとりだし
260 A = ASC(A$)          ' 文字コードに変換
270 B = 0
280 IF (A >= 65 AND A <= 70) THEN B = A - 55 ' A-F
290 IF (A >= 48 AND A <= 57) THEN B = A - 48 ' 0-9
300 C = C + B            ' 1文字分を数値へ変換し加算
310 NEXT
320 D = C + 15           ' 終了アドレスを計算
330 IF (D > 65535) THEN D = 65535 ' 終了アドレスがFFFFを超えないように調整
340 B$ = ""
350 PRINT "0 1 2 3 4 5 6 7 8 9 A B C D E F"
360 FOR J = C TO D
370 B$ = B$ + RIGHT$( "00" + HEX$(PEEK(J)), 2) ' アドレスの取出し
380 NEXT J 
390 PRINT B$ ' ダンプ出力
400 GOTO 200 ' 入力待ちに戻る
500 END

実行するとレジスタの表示の後、入力待ちになりますのでアドレスを16進数で入力します。
メモリの内容が16バイト分表示されます。
終了する場合ENDと入力します。
アドレスの入力文字列を数値へ変換するプログラムをBASICで組みましたが、ソースコードが長くなってしまいました。

16進数文字列⇒数値は以下のコードで出来そう。
C = VAL("&H" + N$)

BIT

レジスタまたはHLレジスタなどが示すメモリの指定ビットが0の場合Zフラグが1、1の場合Zフラグが0になる。

Asm

            ORG  0D00FH
            XOR  A
            LD   A,0F7H
            BIT  3,A
            CALL SETMEM
            RET
SETMEM      LD   (0D006H),HL
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            RET
            END

BASIC

10 CLEAR 100,&HD000
20 DEFUSR=&HD00F
30 BLOAD"program.bin"
40 A=USR(0)
50 PRINT "A "+RIGHT$("00"+HEX$(PEEK(&HD001)),2)
55 PRINT "  SZ-H-PNC"
60 PRINT "F "+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)
200 END

サンプルプログラムではAレジスタに16進数でF7Hをセットしています。これは2進数で11110111になり右0ビットから3ビット目が0となっています。

BIT 3,AでAレジスタの3ビット目が0か1か判定しています。
結果は3ビット目が0ですのでZフラグが1になっています。

Asm

            ORG  0D00FH
            XOR  A
            LD   A,08H
            LD   HL,0D008H
            LD   (HL),A
            XOR  A
            BIT  3,(HL)
            CALL SETMEM
            RET
SETMEM      LD   (0D006H),HL
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            RET
            END

BASIC

10 CLEAR 100,&HD000
20 DEFUSR=&HD00F
30 BLOAD"program.bin"
40 A=USR(0)
50 PRINT "A "+RIGHT$("00"+HEX$(PEEK(&HD001)),2)
55 PRINT "  SZ-H-PNC"
60 PRINT "F "+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)
200 INPUT "HEX or END"; N$      ' アドレスを16進数文字列で入力
210 IF N$ = "END" THEN GOTO 500 ' ENDと入力すると終了
220 C = VAL("&H"+N$)
320 D = C + 8           ' 終了アドレスを計算
330 IF (D > 65535) THEN D = 65535 ' 終了アドレスがFFFFを超えないように調整
340 B$ = ""
350 PRINT "0 1 2 3 4 5 6 7 8"
360 FOR J = C TO D
370 B$ = B$ + RIGHT$( "00" + HEX$(PEEK(J)), 2) ' アドレスの取出し
380 NEXT J 
390 PRINT B$ ' ダンプ出力
400 GOTO 200 ' 入力待ちに戻る
500 END

今度はD008番地のメモリの内容でビットを判定しています。

PUSH、POPとSPレジスタ

Asm

            ORG  0D00FH
            LD   (0D000H),SP ;+0
            PUSH AF
            LD   (0D002H),SP ;+1
            POP  AF
            LD   (0D004H),SP ;+2
            RET
            END

BASIC

10 SCREEN0:WIDTH 80:CLEAR 100,&HD000:DEFUSR=&HD00F
20 BLOAD"program.bin"
30 A=USR(0)
50 PRINT "SP+0:"+RIGHT$("00"+HEX$(PEEK(&HD001)),2)+RIGHT$("00"+HEX$(PEEK(&HD000)),2)+"H"
60 PRINT "SP+1:"+RIGHT$("00"+HEX$(PEEK(&HD003)),2)+RIGHT$("00"+HEX$(PEEK(&HD002)),2)+"H"
70 PRINT "SP+2:"+RIGHT$("00"+HEX$(PEEK(&HD005)),2)+RIGHT$("00"+HEX$(PEEK(&HD004)),2)+"H"
80 END


CALLした先でレジスタの内容が破壊されると困る場合、CALL前にレジスタをPUSHすることで内容を退避し、CALL後にPOPでレジスタの内容を復元します。
PUSHするとSPレジスタが-2されました。
POPするとSPレジスタが+2されました。
SPレジスタの動きはPUSHするたびスタックが積まれていくイメージになります。

CP

Aレジスタと他のレジスタを比較しZフラグに結果が反映されます。
BASIC

10 SCREEN0:WIDTH40
20 CLEAR100,&HD000
30 DEFUSR=&HD00F
40 BLOAD"program.bin"
50 A=USR(0)
60 PRINT "A "+RIGHT$("00"+HEX$(PEEK(&HD001)),2)
70 PRINT "B "+RIGHT$("00"+HEX$(PEEK(&HD003)),2)
80 PRINT "  SZ-H-PNC"
90 PRINT "F "+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)
200 END

Asm

            ORG  0D00FH
            LD   A,7FH     ; A <= 7F
            LD   B,7FH     ; B <= 7F
            CP   B
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            LD   (0D002H),BC
            RET
            END

結果

Aレジスタ7F
Bレジスタ7F
Zフラグが1(A-B == 0)

Asm2

            ORG  0D00FH
            LD   A,7FH     ; A <= 7F
            LD   B,7EH     ; B <= 7E
            CP   B
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            LD   (0D002H),BC
            RET
            END


Aレジスタ7F
Bレジスタ7E
Zフラグが0(A-B != 0)

AND

Aレジスタと他のレジスタの論理積を取ります。

ANDの組み合わせは以下の通りなります。
0 AND 0 = 0
0 AND 1 = 0
1 AND 0 = 0
1 XOR 1 = 1

Basic

 SCREEN0:WIDTH40
20 CLEAR100,&HD000
30 DEFUSR=&HD00F
40 BLOAD"program.bin"
50 A=USR(0)
80 PRINT "  76543210"
70 PRINT "A "+RIGHT$("00000000"+BIN$(PEEK(&HD001)),8)
80 PRINT "  SZ-H-PNC"
90 PRINT "F "+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)
200 END

Asm

            ORG  0D00FH
            LD   A,00000011B
            LD   B,00000101B
            AND  B
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            LD   (0D002H),BC
            RET
            END

結果

AレジスタBレジスタとも0ビット目が1で、それ以外は0ですので結果Aレジスタが1になりました。

SLAとSRA

レジスタのビットの並びを右または左に1ビットずらす命令です。
BASIC

10 SCREEN0:WIDTH40
20 CLEAR100,&HD000
30 DEFUSR=&HD00F
40 BLOAD"program.bin"
50 A=USR(0)
60 PRINT "  76543210"
70 PRINT "A "+RIGHT$("00000000"+BIN$(PEEK(&HD001)),8)+"B"
80 PRINT "  SZ-H-PNC"
90 PRINT "F "+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)+"B"
200 END

Asm

            ORG  0D00FH
            LD   A,00000001B
            SLA  A
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            RET
            END

結果

SLA命令でAレジスタのビットの並びが左方向に1ビットずれたことを確認
左にずれたことで結果として数値が1⇒2と2倍となっています。

Asm

            ORG  0D00FH
            LD   A,00000010B
            SRA  A
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            RET
            END

結果

SRA命令でAレジスタのビットの並びが右方向に1ビットずれたことを確認
右にずれたことで結果として数値が2⇒1と二分の一となっています。

ちなみに左にずらすSLAの場合一番左側7ビット目が失われますが、Cフラグに反映され桁あふれが起きたことが確認できます。
同じく右にずらすSRAの場合一番右側0ビット目が失われますが、こちらも同様にCフラグに反映され桁あふれが起きたことが確認できます。

ADDとSUB

BASIC

10 SCREEN0:WIDTH40
20 CLEAR100,&HD000
30 DEFUSR=&HD00F
40 BLOAD"program.bin"
50 A=USR(0)
70 PRINT "A "+RIGHT$("00"+HEX$(PEEK(&HD001)),2)+"H"
80 PRINT "  SZ-H-PNC"
90 PRINT "F "+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)+"B"
200 END

Asm

10 SCREEN0:WIDTH40
20 CLEAR100,&HD000
30 DEFUSR=&HD00F
40 BLOAD"program.bin"
50 A=USR(0)
70 PRINT "A "+RIGHT$("00"+HEX$(PEEK(&HD001)),2)+"H"
80 PRINT "  SZ-H-PNC"
90 PRINT "F "+RIGHT$("00000000"+BIN$(PEEK(&HD000)),8)+"B"
200 END


ADDで3+2=5になりました。

            ORG  0D00FH
            LD   A,03H
            SUB  03H
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            RET
            END


SUBで3-3=0になりました。

JP

BASIC

10 SCREEN0:WIDTH40
20 CLEAR100,&HD000
30 DEFUSR=&HD00F
40 BLOAD"program.bin"
50 A=USR(0)
200 END

Asm

            ORG  0D00FH
CHPUT       EQU  00A2H
            LD   A,41H
START       CALL CHPUT
            INC  A
            CP   44H
            JP   NZ,START
            PUSH AF
            POP  HL
            LD   (0D000H),HL
            RET
            END

結果

Aレジスタに41H(アルファベットのA)をセットしCHPUTをで文字を出力
INCでAレジストリに1加算
CP 44H(アルファベットのD)でAと44Hを比較(減算しZフラグに反映させる)
JP NZ,STARTでZフラグが0でない場合(NZ)STARTラベルへジャンプ
結果ABCと文字列が出力された。

BASICの命令をアセンブラで書いてみる

BASICの命令もうろ覚えですが、同じような結果になるようにアセンブラで書いてみたいと思います。

PRINT “HELLO WORLD”

複数の文字(文字列)を画面に出力してみます。
BASIC

10 CLEAR 100,&HD000
20 DEFUSR=&HD000
30 BLOAD"program.bin"
40 A=USR(0)
50 END

Asm

            ORG     0D000H
CHPUT       EQU     00A2H
STR_A
            DEFM    "HELLO WORLD"
            DEFB    0
INIT        LD      HL,STR_A
LOOP
            LD      A,(HL)
            CP      00H
            JP      Z,FIN
            CALL    CHPUT
            INC     HL
            JP      LOOP
FIN         RET
            END

結果

出力する文字列を定数としてメモリにセットする為、疑似命令DEFMを使っていて、そちらにラベルSTR_Aと付けました。
文字列の後に疑似命令のDEFBで0をセットしています。C言語の文字列をまねて0を文字列の終端記号としています。
文字列の先頭から文字を一文字ずつ出力し、取得した文字が0になるまで繰り返すプログラムになります。
探せば文字列を出力するBIOS命令がありそうな気がしますが、覚えたZ80命令を組み合わせて文字列を出力してみました。

CLS

画面をクリアします。
Asm

            ORG     0D000H
CLS         EQU     00C3H
            CALL    CLS
            RET
            END

コメント