DEC社 TOPS-20のSNOBOL言語で8080アセンブラを作成(設計編)

SNOBOL

SNOBOL

SNOBOL言語

2018年4月、歴史的に貴重なDEC社 TOPS-20のSNOBOL言語で8080アセンブラを作成しました。私は大学生のとき(1977年)に、DEC社 SYSTEM-20 (TOPS-20)でSNOBOL言語と出会ってその素晴らしさに感動しました。

KLH10エミュレーター(Panda TOPS-20 distribution)をインストールして、SNOBOL言語が使える環境が整いました。

DEC社 TOPS-20を使う方法 SNOBOL言語使用編

DEC社 TOPS-20を使う方法 SNOBOL言語の組み込み関数 編

SNOBOL言語で8080アセンブラ

大学4回生のときに、BASIC風のインタプリターをSNOBOL言語で作成しました。残念ながらそのSNOBOLソースコードをいつの間にか捨ててしまったようで、その資料は手元にありません。ただSNOBOL言語の素晴らしさだけが記憶に残っています。

そこで、SNOBOL言語の醍醐味(だいごみ)を再び体験しようと考えて、今回はインテル8080のアセンブラ(ASM80.SNO)を作成しました。

8080プロセッサーも懐かしくわが青春のひとコマです。1978年ごろ、TK-80マイコン・ボードで趣味のプログラミングをしていました。

ベンチャー企業V社でインテル8080を使う

TK-80トレーニングキットで趣味のプログラミング

このブログでは、ASM80.SNOの設計編を書きます。

ASM80 By SNOBOL

ASM80 By SNOBOL

SNOBOL言語による8080アセンブラをTダイアグラムで記述すると下記のようになります。

ASM80 Tダイアグラム

ASM80 Tダイアグラム

8080ソースコードは、SNOBOLで記述したASM80によって8080機械語にアセンブルします。SNOBOLは、PDP-10 KL10機械語で記述しています。KL10機械語は、x86プラットフォームでKL10 Emulatorでエミュレートします。

ASM80.SNOの仕様

TK-80マイコン・ボードはもう手元になく、8080が動作するボードの手持ちもありません。そこで、ASM80.SNOは実用レベルというよりはトイプログラム(Toy)レベルとします。

コメントは、セミコロン(;)から始まり行末までです。英大文字と英子文字の区別はなく、内部的にすべて英大文字で扱います。

ラベルは、英数字で構成してコロン(:)で区切ります。

疑似命令は、以下をサポートします。

機能 疑似命令 動作
定数定義 ラベル名 EQU 数値 ラベル名を数値で定義
アドレス設定 ORG アドレス アドレスを設定
1バイトの定数定義 DB バイト定数 数値 or ラベル名でバイトを定義
2バイトの定数定義 DW ワード定数 数値 or ラベル名でワードを定義
領域定義 DS 領域数 数値 or ラベル名で領域を定義

ASM80.SNOの設計

8080命令コードの生成

8080機械語を扱うときは16進数を使うことが多く、よく使う命令の機械語は覚えていたものです。
(JMPは’C3’、CALLは’CD’、RETは’C9’など)

8080機械語を説明したドキュメントの中には8進数で表記したものもあります。その理由は、8080機械語のビットフィールドは8進数で表記した方がわかりやすいからです。

 


8080命令コード (例1)

8080命令コード (例1)

  • 命令「MOV Rd,Rs」は、レジスタRd←レジスタRsレジスタ間の転送命令です。
  • 機械語は2進数表記で「01 ddd sss」(8進数表記 1DS)となります。dddとsssの各3ビットはレジスタ(B,C,D,E,H,L,M,A)を0~7で示します。
  • 例えば「MOV A,C」は ddd=A=7、sss=C=1 ですので、「01 111 001(171)」となります。

8080命令コード (例2)

8080命令コード (例2)

  • 命令「INX Rp」は、ペアレジスタRpのインクリメント命令です。
  • 機械語は2進数表記で「00 pp0 011」(8進数表記 0P3)となります。pp0の3ビットは0~6の偶数(最下位ビットは0)でレジスタ(B,D,H,SP)を示します。
  • 例えば「INX H」は pp0=H=4、「00 100 011(043)」となります。

  • 命令「LDAX Rp」は、ペアレジスタRpのレジスタ間接ロード命令です。
  • 機械語は2進数表記で「00 qq1 010」(8進数表記 0Q2)となります。qq1の3ビットは0~3の奇数(最下位ビットは1)でレジスタ(B,D)を示します。
  • 例えば「LDAX D」は qq1=D=3、「00 011 010(032)」となります。

  • 命令「STAX Rp」は、ペアレジスタRpのレジスタ間接セーブ命令です。
  • 機械語は2進数表記で「00 pp0 010」(8進数表記 0P2)となります。pp0の3ビットは0~2の偶数(最下位ビットは0)でレジスタ(B,D)を示します。
  • 例えば「STAX D」は pp0=D=2、「00 010 010(022)」となります。

SNOBOLでASM80を実装するために、命令コードテーブルに機械語の8進数表記を格納します。そして、レジスタ指定によりレジスタ部分(D,S)を置換して命令コードを生成します。
また、8080機械語は命令の種別によって1~3バイトの命令長となりますので、命令コードテーブルに命令長も格納します。

OPCODE<“MOV” > = “1DS+1”

OPCODE<“MVI” > = “0D6+2”

8080命令のオペランド部

8080の命令によりオペランド部を解析します。

形式 命令 オペランド部
RM_RM MOV Rd,Rs
RM_IMM MVI Rd,Imd8
RM INR/DCR
ADD/ADC/SUB/SBB
ANA/XRA/ORA/CMP
R
IMM ADI/ACI/SUI/SBI
ANI/XRI/ORI/CPI
Imd8
PORT IN /OUT Port
NON HLT/EI /DI /NOP
RLC/RRC/RAL/RAR
RET
RC /RNC/RZ /RNZ
RP /RM /RPE/RPO
XCHG/XTHL/SPHL/PCHL
CMA/STC/CMC/DAA
ADR JMP
JC /JNC/JZ /JNZ
JP /JM /JPE/JPO
CALL
CC /CNC/CZ /CNZ
CP /CM /CPE/CPO
LDA /STA/LHLD/SHLD
Adr
VEC RST Vect
BDHA PUSH/POP Rp
BD LDAX/STAX Rp
BDHS DAD/INX/DCX Rp
BDHS_I LXI Rp,Imd16

ASM80.SNOのプログラム構造図

SNOBOL言語による8080アセンブラ(ASM80.SNO)は、2パス方式で処理します。

  • PASS1 : ラベル登録、エラーチェック
  • PASS2 : ラベル参照、エラーチェック、オブジェクトコード生成

全体のプログラム構造

プログラムのロジックはフローチャートではなく、構造化フローチャートのPAD図で書いた方が理解しやすいです。

ASM80.SNOのプログラム構造をPAD図で示します。

ASM80 PAD図 メイン部

ASM80 PAD図 メイン部

2パス方式なので、PASSで2回ループします。

ひとつのパスでは、PC←0に設定しINPUT_DEVからソースコードのラインを入力してEOFになるまで繰り返します。

ひとつのラインでは、次の処理を行います。

  • エラーリセット
  • 命令長 OPLEN←0
  • INPUT_DEVから入力
  • 英大文字に変換
  • コメント除去
  • ラベルの形式なら、ラベル登録
  • 疑似命令形式なら、疑似命令処理へ分岐
  • 8080命令の処理なら、命令コードと命令長を取得し、オペランド処理へ分岐
  • PASS=2なら、リスト出力とエラー出力
  • PC←PC+命令長

ORG 疑似命令

ORG アドレス ; アドレス設定

ASM80 PAD図 EQU部

ASM80 PAD図 EQU部

GETIMM()で、”ORG”の右側のアドレスを取得しPCに格納してアドレスの設定を行います。

DB 疑似命令

DB バイト定数(数値 or ラベル名) ; 1バイトの定数定義

ASM80 PAD図 DB部

ASM80 PAD図 DB部

  1. GETIMM()で、”DB”の右側のバイト定数(IMM)を取得します。1バイトの定数定義なので、255を超える場合はIMMエラーとします。
  2. 命令長 OPLEN←1として、オブジェクトの1バイト目に定数IMMを設定します。

DW 疑似命令

DW ワード定数(数値 or ラベル名) ; 2バイトの定数定義

ASM80 PAD図 DW部

ASM80 PAD図 DW部

  1. GETIMM()で、”DW”の右側のワード定数(AD)を取得します。
  2. 命令長 OPLEN←2として、オブジェクトの1バイト目に定数ADの下位バイト、2バイト目にADの上位バイトを設定します。

DS 疑似命令

DS 領域数(数値 or ラベル名) ; 領域定義

ASM80 PAD図 DS部

ASM80 PAD図 DS部

  1. GETIMM()で、”DS”の右側の領域数(IMM)を取得します。
  2. 命令長 OPLEN←IMMとして領域を確保します。
  3. オブジェクトの1~3バイト目には、”??”を設定します。

EQU 疑似命令

ラベル名 EQU 数値 ; 定数定義

ASM80 PAD図 EQU部

ASM80 PAD図 EQU部

  1. ラベルの形式でなければ、LABELエラーとします。
  2. GETIMM()で、”EQU”の右側の数値(IMM)を取得します。
  3. ラベルをIMMでラベル登録します。

命令 オペランド BD形式

LDAX/STAX命令は、オペランドにレジスタ(B,D)を指定します。

ASM80 PAD図 オペランド BD部

ASM80 PAD図 オペランド BD部

  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,D)をコード(0,2)に変換します。
  3. 命令コード OPCのレジスタ部分(P Q)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド BDHS形式

DAD/INX/DCX命令は、オペランドにレジスタ(B,D,H,SP)を指定します。

ASM80 PAD図 オペランド BDHS部

ASM80 PAD図 オペランド BDHS部

  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,D,H,SP)をコード(0,2,4,6)に変換します。
  3. 命令コード OPCのレジスタ部分(P Q)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド BDHA形式

PUSH/POP命令は、オペランドにレジスタ(B,D,H,PSW)を指定します。

ASM80 PAD図 オペランド BDHA部

ASM80 PAD図 オペランド BDHA部

  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,D,H,PSW)をコード(0,2,4,6)に変換します。
  3. 命令コード OPCのレジスタ部分(R)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド BDHS_I形式

LXI命令は、オペランドにレジスタ(B,D,H,PSW)とワード定数 を指定します。

ASM80 PAD図 オペランド BDHS_I部

ASM80 PAD図 オペランド BDHS_I部

  1. GETIMM()で、カンマ”,”の右側のワード定数(ADR)を取得します。
  2. 該当レジスタ以外のときは、レジスタエラーとします。
  3. レジスタ名(B,D,H,PSW)をコード(0,2,4,6)に変換します。
  4. 命令コード OPCのレジスタ部分(P)をコードで置換します。
  5. オブジェクトの1バイト目に命令コード OPCを設定します。
  6. オブジェクトの2バイト目にADRの下位バイト、3バイト目にADRの上位バイトを設定します。

命令 オペランド RM_RM形式

MOV命令は、オペランドに転送先レジスタと転送元レジスタを指定します。

転送先 転送元
A,B,C,D,E,H,L A,B,C,D,E,H,L
A,B,C,D,E,H,L M
M A,B,C,D,E,H,L
ASM80 PAD図 オペランド RM_RM部

ASM80 PAD図 オペランド RM_RM部

  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,C,D,E,H,L,M,A)をコード(0,1,2,3,4,5,6,7)に変換します。
  3. 命令コード OPCのレジスタ部分(D S)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド RM_IMM形式

MVI命令は、オペランドにレジスタ(B,C,D,E,H,L,M,A)とバイト定数を指定します。

ASM80 PAD図 オペランド RM_IMM部

ASM80 PAD図 オペランド RM_IMM部

  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. GETIMM()で、カンマ”,”の右側のバイト定数(IMM)を取得します。1バイトの定数定義なので、255を超える場合はIMMエラーとします。
  3. レジスタ名(B,C,D,E,H,L,M,A)をコード(0,1,2,3,4,5,6,7)に変換します。
  4. 命令コード OPCのレジスタ部分(D)をコードで置換します。
  5. オブジェクトの1バイト目に命令コード OPCを設定します。
  6. オブジェクトの2バイト目にIMMを設定します。

命令 オペランド RM形式

下記命令は、オペランドにレジスタ(B,C,D,E,H,L,M,A)とバイト定数を指定します。

INR/DCR
ADD/ADC/SUB/SBB/ANA/XRA/ORA/CMP

ASM80 PAD図 オペランド RM部

ASM80 PAD図 オペランド RM部

  1. 該当レジスタ以外のときは、レジスタエラーとします。
  2. レジスタ名(B,C,D,E,H,L,M,A)をコード(0,1,2,3,4,5,6,7)に変換します。
  3. 命令コード OPCのレジスタ部分(D)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド IMM形式

下記命令は、オペランドにバイト定数を指定します。

ADI/ACI/SUI/SBI/ANI/XRI/ORI/CPI

ASM80 PAD図 オペランド IMM部

ASM80 PAD図 オペランド IMM部

  1. GETIMM()で、バイト定数(IMM)を取得します。1バイトの定数定義なので、255を超える場合はIMMエラーとします。
  2. オブジェクトの1バイト目に命令コード OPCを設定します。
  3. オブジェクトの2バイト目にバイト定数 IMMを設定します。

命令 オペランド PORT形式

下記命令は、オペランドにバイト定数を指定します。

IN/OUT

ASM80 PAD図 オペランド PORT部

ASM80 PAD図 オペランド PORT部

  1. GETIMM()で、バイト定数(PORT)を取得します。1バイトの定数定義なので、255を超える場合はIMMエラーとします。
  2. オブジェクトの1バイト目に命令コード OPCを設定します。
  3. オブジェクトの2バイト目にバイト定数 PORTを設定します。

命令 オペランド VEC形式

RST命令は、オペランドにベクトルを指定します。

ASM80 PAD図 オペランド VEC部

ASM80 PAD図 オペランド VEC部

  1. GETIMM()で、定数(VEC)を取得します。0~7の定数定義なので、7を超える場合はVECエラーとします。
  2. ベクトル(0,1,2,3,4,5,6,7)をコード(0,1,2,3,4,5,6,7)に変換します。
  3. 命令コード OPCのベクトル部分(V)をコードで置換します。
  4. オブジェクトの1バイト目に命令コード OPCを設定します。

命令 オペランド ADR形式

下記命令は、オペランドにアドレスを指定します。

JMP /JC /JNC/JZ /JNZ/JP /JM /JPE/JPO
CALL/CC /CNC/CZ /CNZ/CP /CM /CPE/CPO
LDA /STA/LHLD/SHLD

ASM80 PAD図 オペランド ADR部

ASM80 PAD図 オペランド ADR部

  1. GETIMM()で、ワード定数(ADR)を取得します。
  2. オブジェクトの1バイト目に命令コード OPCを設定します。
  3. オブジェクトの2バイト目にADRの下位バイト、3バイト目にADRの上位バイトを設定します。

命令 オペランド NON形式

下記命令は、オペランドがありません。

HLT/RLC/RRC/RAL/RAR
RET/RC /RNC/RZ /RNZ/RP /RM /RPE/RPO
XCHG/XTHL/SPHL/PCHL
CMA/STC/CMC/DAA/EI /DI /NOP

ASM80 PAD図 オペランド NON部

ASM80 PAD図 オペランド NON部

  1. オブジェクトの1バイト目に命令コード OPCを設定します。

サブルーチン ラベル登録 ADDLTBL()

ラベルテーブルのLABELにVALを登録します。

ADDLTBL(LABEL,VAL)

ASM80 PAD図 ラベル登録サブルーチン部

ASM80 PAD図 ラベル登録サブルーチン部

  1. LABELが登録済みの場合は、二重登録エラーなので FRETURNします。
  2. パス別にふたつのラベルテーブルを使い分けます。
  • PASS1 : TBL_LAB1<LABEL>にVALを登録
  • PASS2 : TBL_LAB2<LABEL>にVALを登録

サブルーチン ラベル参照 REFLTBL()

ラベルテーブルのLABELを参照します。

REFLTBL(LABEL)

ASM80 PAD図 ラベル参照サブルーチン部

ASM80 PAD図 ラベル参照サブルーチン部

 

  1. PASS=1のとき、LABELが未登録なら “0”を返します。
  2. TBL_LAB1<LABEL>を参照します。パス1用のラベルテーブルを参照します。

サブルーチン 直値参照 GETIMM()

文字列 STR から直値を参照します。

GETIMM(STR)

ASM80 PAD図 直値参照サブルーチン部

ASM80 PAD図 直値参照サブルーチン部

直値は、以下の形式とします。

  • 16進数 : 0~Fの組み合わせ に続く “H”
  •  2進数 : 0~1の組み合わせ に続く “B”
  • 10進数 : 0~9の組み合わせ
  • ラベル : 英字、数字、アンダーバー(“_”)の組み合わせ

ラベルの場合は、ラベルテーブルを参照します。

  1. 直値のパターンに一致しない場合は、パターン不一致エラーで FRETURNします。
  2. 直値のパターンに一致した場合は、一致部分のIMMとHB識別を設定します。HB識別(”H” or “B” or “”)によりBASE(16 or 2 or 10)に変換します。
  3. 一致部分 IMMをBASE進数の数値に変換します。
  4. 数値変換が正常なら、数値を戻り値にします。
  5. 数値変換が異常なら、ラベルとみなしてIMMでラベル参照してSTRに設定して直値のパターンチェックを繰り返します。

SNOBOLによる8080アセンブラ設計完了

SNOBOL言語による8080アセンブラの設計が完了しました。細かいエラーチェックなど省略していますが、使用する分には問題ないと考えます。SNOBOLで書いたソースコードは、次回に書きます。

TOPS-20に関するブログ

歴史的に貴重なDEC社 TOPS-20を使うためにKLH10エミュレーターをLinuxにインストールして、TOPS-20を実行することができました。

TOPS-20に関するブログを以下に示します。

DEC社 TOPS-20を使う方法 KLH10エミュレーター導入編

DEC社 TOPS-20を使う方法 KLH10エミュレーター実行編

DEC社 TOPS-20を使う方法 TOPS-20コマンド使用編

DEC社 TOPS-20を使う方法 エミュレーター簡単起動/停止編

DEC社 TOPS-20を使う方法 TELNETでログイン編

DEC社 TOPS-20を使う方法 タイムゾーンの設定編

DEC社 TOPS-20を使う方法 ディレクトリのツリーウォーク編

DEC社 TOPS-20を使う方法 UNIX Toolsとbash使用編

DEC社 TOPS-20を使う方法 ファイルタイプ編

DEC社 TOPS-20を使う方法 FORTHシステム使用編

DEC社 TOPS-20を使う方法 KermitTeraTerm でファイル転送

DEC社 TOPS-20を使う方法 SNOBOL言語使用編

DEC社 TOPS-20を使う方法 SNOBOL言語の組み込み関数 編