2009/07/18 からのアクセス回数 15633
GMC-4は、学研の「大人の科学」Vol.24の付録に付いている4ビットマイコンボードです。
http://otonanokagaku.net/magazine/vol24/index.html
GMC-4には、
が載っており、
を持っています。
として使用され、その他は、補助用のレジスタとなっています。
GMC-4の命令セットは、
| 命令コード | 命令記号 | 実行フラグ | 機能 |
| 0 | KA | 0,1 | 押している数字キーをArに入れる。押されていない場合は実行フラグが1。 |
| 1 | AO | 1 | Arの内容を数字LEDに点灯する。 |
| 2 | CH | 1 | ArとBr、YrとZrの内容をそれぞれ入れ替える。 |
| 3 | CY | 1 | ArとYrの内容を入れ替える。 |
| 4 | AM | 1 | Arの内容をデータメモリに入れる。 |
| 5 | MA | 1 | データメモリの内容をArに入れる。 |
| 6 | M+ | 0,1 | データメモリの内容にArの内容を加え、Arに入れる。桁上げがあると実行フラグが1。 |
| 7 | M- | 0,1 | データメモリの内容からArの内容を引き、Arに入れる。引けないとき実行フラグが1。 |
| 8 | TIA | 1 | 命令の次のデータをArに入れる。 |
| 9 | AIA | 0,1 | Arの内容に命令の次のデータを加え、Arに入れる。桁上げがあると実行フラグが1。 |
| A | TIY | 1 | 命令の次のデータをYrに入れる。 |
| B | AIY | 0,1 | Yrの内容に命令の次のデータを加え、Yrに入れる。桁上げがあると実行フラグが1。 |
| C | CIA | 0,1 | Arと命令の次のデータを比べる。異なる場合は実行フラグが1。 |
| D | CIY | 0,1 | Yrと命令の次のデータを比べる。異なる場合は実行フラグが1。 |
| F | JUMP | 1 | 実行フラグが1のとき、続けて指定したアドレスにジャンプする。 |
この他にサービスコールとして、
| 命令コード | 命令記号 | 実行フラグ | 機能 |
| E0 | CAL RSTO | 1 | 数字LEDを消灯する。 |
| E1 | CAL SETR | 1 | 2進LEDを1個点灯する。点灯するLEDはYrで指定する。 |
| E2 | CAL RSTR | 1 | 2進LEDを1個消灯する。消灯するLEDはYrで指定する。 |
| E4 | CAL CMPL | 1 | Arの内容をbit反転する。 |
| E5 | CAL CHNG | 1 | Ar、Br、Yr、Zrと補助レジスタA'r、B'r、Y'r、Z'rの内容を入れ替える。 |
| E6 | CAL SIFT | 0,1 | Arの内容を1bit右へ移動する。結果が偶数のとき実行フラグが1。 |
| E7 | CAL ENDS | 1 | エンド音を鳴らす。 |
| E8 | CAL ERRS | 1 | エラー音を鳴らす。 |
| E9 | CAL SHTS | 1 | "ピッ"という音を鳴らす。 |
| EA | CAL LONS | 1 | "ピー"という音を鳴らす。 |
| EB | CAL SUND | 1 | Arの内容に対応する音階を鳴らす。 |
| EC | CAL TIMR | 1 | (Arの内容+1)×0.1秒間プログラムの実行を停止する。 |
| ED | CAL DSPR | 1 | データメモリの内容を2進LEDに点灯する。5F番地の内容を上位、5E番地の内容を下位に表示する。 |
| EE | CAL DEM- | 1 | データメモリからArの内容を引き、10進数に直してデータメモリに入れる。 |
| EF | CAL DEM+ | 1 | データメモリにArの内容を加え、10進数に直してデータメモリに入れる。桁上げがある場合は一つ前の番地の値に1を加える。 |
があります。*1
GMC-4では、
することで、プログラミングします。しかし、これではミスも多く、小さなプログラムを作るのでも大変です。
そこで、以下のような簡易言語から命令セットに変換するコンパイラーを作成しました。
例)15秒カウンタ
int a;
a = 15;
while (a > 0) {
out(a);
shts();
a = a - 1;
timer(10);
}
生成された命令セット(アセンブラ)は、
TIA f TIY 0 AM L1: TIY 0 TIA 0 AIA 1 M- JUMP L2 TIY 0 MA AO CAL SHTS TIY 0 TIA 1 M- TIY 0 AM TIA a CAL TIMR JUMP L1 L2: CAL ENDS L3: JUMP L3
命令セットからコードに翻訳するアセンブラが、以下のサイトにあります。
http://www.musashinodenpa.com/misc/GMC4/
ここで変換すると
アドレス 命令 命令コード 00 TIA 8 01 <F> F 02 TIY A 03 <0> 0 04 AM 4 05 TIY A 06 <0> 0 07 TIA 8 08 <0> 0 09 AIA 9 0A <1> 1 0B M- 7 0C JUMP F 0D <0> 0 0E <2> 2 0F TIY A 10 <0> 0 11 MA 5 12 AO 1 13 CAL E 14 _SHTS 9 15 TIY A 16 <0> 0 17 TIA 8 18 <1> 1 19 M- 7 1A TIY A 1B <0> 0 1C AM 4 1D TIA 8 1E <A> A 1F CAL E 20 _TIMR C 21 JUMP F 22 <0> 0 23 <5> 5 24 CAL E 25 _ENDS 7 26 JUMP F 27 <2> 2 28 <6> 6
ただし、最初のJUMP 02はバグのようで必ず最初のJUMPの値が正しくセットされません。 正しくは、JUMP 24です。
GMC-4の元になった「FX-マイコン」のシミュレータが以下のURLに公開されています。
http://homepage2.nifty.com/kocha_web/fxms/fxms.html
これを使って生成されたコードが正しく動作するか試してみます。*2
言語は、命令セットの影響を強く受け、
となりました。
コンパイラーの処理する言語仕様を以下に示します。
prog ::
declaration* statement+
declaration ::
int ID ( ',' ID )*
compoundStatement ::
'{' statement+ '}'
statement ::
expr ';'
| ID '=' expr;
| ';'
| compoundStatement
| ifStm
| whileStm
| 'led' '(' expr ')' ';'
| 'out' '(' expr ')' ';'
| 'cmpl' '(' expr ')' ';'
| 'shift' '(' expr ')' ';'
| 'shts' '(' expr ')' ';'
| 'sound' '(' expr ')' ';'
| 'timer' '(' expr ')' ';'
expr ::
'$A'
| 'key' '(' ')'
| term ( '+' CONST | '+' ID )*
| ID '-' CONST
| ID '-' ID
term ::
CONST | ID
ifStm ::
'if' '(' comparisonExpr ')' statement
whileStm ::
'while' '(' comparisonExpr ')' statement
comparisonExpr ::
'$Z'
| 'key' '(' ')'
| ID '>' CONST
| term '==' ( ID | CONST )
ID :: ('a'..'z'|'A'..'Z')('a'..'z'|'A'..'Z'|'0'..'9')*
CONST :: '0'..'9'+
コンパイラーは、ANTLRを使って作成しました。 構文のチェックやデバッグは、ANTLRWorksを使いました。
ANTLRでは、字句解析と構文解析が1つにまとまっているので、字句解析は以下の部分だけです。
ID : ('a'..'z'|'A'..'Z')('a'..'z'|'A'..'Z'|'0'..'9')* ;
CONST : '0'..'9'+ ;
WS : (' '|'\t'|'\r'|'\n')+ {skip();} ;
以下に構文解析の各処理について説明します。
最初に、変数宣言での変数名の登録について説明します。
Main.javaに変数用のメソッドをいくつか定義しました。
static int symIndex = 0;
static Map symtable = new HashMap();
public static void addVar(String name) {
symtable.put(name, new Integer(symIndex++));
}
public static String varHexIndex(String name) {
Integer index = (Integer)symtable.get(name);
if (index == null) {
System.err.println("Invalid variable name");
}
return (String.format("%x", index.intValue()));
}
これを使って変数宣言は、次のようになります。
declaration
: 'int' (i=ID)
{
Main.addVar($i.text);
}
( ',' (i=ID)
{
Main.addVar($i.text);
}
)* ';'
;
宣言された変数をsymtableに登録するだけです。
比較の結果はZレジスタに入り、Zレジスタが0の時に条件に一致するようにしました。 これは、JUMP命令がZレジスタが0の時だけ通過し、通過後Zレジスタを1に戻す仕様になっているからです。
comparisonExpr
: '$Z'
| 'key' '(' ')'
{ System.out.println("KA"); }
|
(i=ID) '>' (c=CONST)
{
System.out.println("TIY\t" + Main.varHexIndex($i.text));
System.out.println("TIA\t" + Main.toHexString($c.text));
System.out.println("AIA\t1");
System.out.println("M-");
}
| term (
'==' (c=CONST)
{ System.out.println("CIA\t" + Main.toHexString($c.text)); }
| '==' (i=ID)
{ System.out.println("CIY\t" + Main.varHexIndex($i.text)); }
)
;
ここでは、プログラムが組める最低限の演算にとどめました。
演算はAレジスタに結果が入るようにしました。
項には、定数と変数が指定されますので、その値をAレジスタにセットする命令を出力します。
term
: (c=CONST)
{ System.out.println("TIA\t" + Main.toHexString($c.text)); }
| (i=ID)
{
System.out.println("TIY\t" + Main.varHexIndex($i.text));
System.out.println("MA");
}
;
演算処理には、足し算, 引き算, key(), $Aを使うことができます。 命令セットの制限が強いので、今回は引き算は、変数に対する処理のみとしました。 $Aは、key()関数でセットされたAレジスタの値を使用するために導入しました。
演算の定義は、
expr
: '$A'
| term (
'+' (c=CONST)
{
System.out.println("AIA\t" + Main.toHexString($c.text));
}
| '+' (i=ID)
{
System.out.println("TIY\t" + Main.varHexIndex($i.text));
System.out.println("M+");
}
)*
| 'key' '(' ')'
{ System.out.println("KA"); }
| (i=ID) '-' (c=CONST)
{
System.out.println("TIY\t" + Main.varHexIndex($i.text));
System.out.println("TIA\t" + Main.toHexString($c.text));
System.out.println("M-");
}
| (a=ID) '-' (b=ID)
{
System.out.println("TIY\t" + Main.varHexIndex($b.text));
System.out.println("MA");
System.out.println("TIY\t" + Main.varHexIndex($a.text));
System.out.println("M-");
}
;
文では、代入文、複合文、if文、while文、関数呼び出しを定義します。
statement
: expr ';'
| (i=ID) '=' expr ';'
{
System.out.println("TIY\t" + Main.varHexIndex($i.text));
System.out.println("AM");
}
| ';'
| compoundStatement
| ifStm
| whileStm
| 'led' '(' expr ')' ';'
{
System.out.println("TIY E");
System.out.println("AM");
System.out.println("CAL DSPR");
}
| 'out' '(' expr ')' ';'
{ System.out.println("AO"); }
| 'cmpl' '(' expr ')' ';'
{ System.out.println("CAL CMPL"); }
| 'shift' '(' expr ')' ';'
{ System.out.println("CAL SIFT"); }
| 'shts' '(' ')' ';'
{ System.out.println("CAL SHTS"); }
| 'sound' '(' expr ')' ';'
{ System.out.println("CAL SOUND"); }
| 'timer' '(' expr ')' ';'
{ System.out.println("CAL TIMR"); }
;
複合文は、{と}で文を複数宣言することを定義するだけです。
compoundStatement
: '{' statement+ '}'
;
if文は、elseなしの簡単なものとしました。(今後の課題)
ifStm
@init {
String label = Main.genLabel();
}
@after {
System.out.print(label + ": ");
}
: 'if' '(' comparisonExpr ')'
{
System.out.println("JUMP\t" + label);
}
statement
;
@initと@afterで囲まれた部分は、if文の最初と終わりに呼び出されるものです。 ここでは、JUMP先のラベルの生成とレベル付けを行っています。 if文の比較の後で、JUMP命令を入れるだけの簡単な構造にしています。
while文もbreak, continueなしの簡単なものにしています。 if文と同様に@init, @afterでラベルの生成とJUMP, 終了ラベル付けを行っていました。
whileStm
@init {
String label1 = Main.genLabel();
String label2 = Main.genLabel();
}
@after {
System.out.println("JUMP\t" + label1);
System.out.print(label2 + ": ");
}
: 'while'
{ System.out.print(label1 + ": "); }
'(' comparisonExpr ')'
{ System.out.println("JUMP\t" + label2); }
statement
;
プログラム全体は、変数宣言と文のならびと定義し、終了したら、終了サウンドをならし、無限ループに入ります。
prog
: declaration*
(
statement
)+
{
String label = Main.genLabel();
System.out.println("CAL ENDS");
System.out.println(label + ": JUMP\t" + label);
}
;
これだけの処理で、コンパイラーができるのです。ANTLRを使うと簡単でしょう!
GMC-4コンパイラーのEclipseプロジェクトを以下のファイルをダウンロードしてください。
この記事は、
皆様のご意見、ご希望をお待ちしております。