#freeze [[FrontPage]] 2009/07/18 からのアクセス回数 &counter; #contents ** GMC-4について [#ve7ca79f] GMC-4は、学研の「大人の科学」Vol.24の付録に付いている4ビットマイコンボードです。 http://otonanokagaku.net/magazine/vol24/index.html &ref(GMC-4.jpg); *** ハードウェア構成 [#qe5942f6] GMC-4には、 - 1個の7セグメントLED - 1個のスピーカ - 7個のLED - 数字キーとASET, INCR, RUN, RESETのキー が載っており、 - プログラム領域は、00-4F - メモリ領域は、50-5F - A, B, Y, Z, A', B', Y', Z'の8個のレジスタ を持っています。 - Aレジスタは、演算用レジスタ - Yレジスタは、アドレスレジスタ - Zレジスタは、条件レジスタ として使用され、その他は、補助用のレジスタとなっています。 *** 命令セット [#tf2638b8] 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を加える。| があります。((E3のCAL INPTは、ありません)) ** コンパイラー [#h5730b34] GMC-4では、 - プログラムを命令セットで表現し - 手でコードに翻訳 することで、プログラミングします。しかし、これではミスも多く、小さなプログラムを作るのでも大変です。 そこで、以下のような簡易言語から命令セットに変換するコンパイラーを作成しました。 例)15秒カウンタ #pre{{ int a; a = 15; while (a > 0) { out(a); shts(); a = a - 1; timer(10); } }} 生成された命令セット(アセンブラ)は、 #pre{{ 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 }} *** アセンブラ [#fbb36069] 命令セットからコードに翻訳するアセンブラが、以下のサイトにあります。 http://www.musashinodenpa.com/misc/GMC4/ ここで変換すると #pre{{ アドレス 命令 命令コード 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です。 ** シミュレータ [#i4366508] GMC-4の元になった「FX-マイコン」のシミュレータが以下のURLに公開されています。 http://homepage2.nifty.com/kocha_web/fxms/fxms.html これを使って生成されたコードが正しく動作するか試してみます。((コンパイラーのデバッグに重宝しました)) &ref(FX-Simulator.jpg); - 例題をFX-マイコン・シミュレータで実行するためのファイル&ref(sample.fxp); ** 言語仕様 [#f7bba4b6] 言語は、命令セットの影響を強く受け、 - $A, $ZでAレジスターの値、Zレジスターの値をそのまま使うことを指定できるようにしました。 - 条件レジスタの制約がきつく、比較が>と==のみとなりました。 - 引き算は、変数に対してのみ可能です。 となりました。 コンパイラーの処理する言語仕様を以下に示します。 #pre{{ 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'+ }} ** コンパイラーの作成 [#mc05e0de] コンパイラーは、ANTLRを使って作成しました。 構文のチェックやデバッグは、ANTLRWorksを使いました。 *** 字句解析 [#wc3b4229] ANTLRでは、字句解析と構文解析が1つにまとまっているので、字句解析は以下の部分だけです。 #pre{{ ID : ('a'..'z'|'A'..'Z')('a'..'z'|'A'..'Z'|'0'..'9')* ; CONST : '0'..'9'+ ; WS : (' '|'\t'|'\r'|'\n')+ {skip();} ; }} 以下に構文解析の各処理について説明します。 *** 変数宣言 [#lf961fc9] 最初に、変数宣言での変数名の登録について説明します。 Main.javaに変数用のメソッドをいくつか定義しました。 #pre{{ 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())); } }} これを使って変数宣言は、次のようになります。 #pre{{ declaration : 'int' (i=ID) { Main.addVar($i.text); } ( ',' (i=ID) { Main.addVar($i.text); } )* ';' ; }} 宣言された変数をsymtableに登録するだけです。 *** 比較演算 [#m156c68c] 比較の結果はZレジスタに入り、Zレジスタが0の時に条件に一致するようにしました。 これは、JUMP命令がZレジスタが0の時だけ通過し、通過後Zレジスタを1に戻す仕様になっているからです。 #pre{{ 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)); } ) ; }} - kye()関数の場合、KA命令を実行し、その結果のZレジスタの値をそのまま使います。 - >演算は、Aレジスタの値から(定数+1)を引いて判断します。 - ==演算は、CIA, CIYの比較命令を使います。 ここでは、プログラムが組める最低限の演算にとどめました。 *** 項 [#ad0ccb45] 演算はAレジスタに結果が入るようにしました。 項には、定数と変数が指定されますので、その値をAレジスタにセットする命令を出力します。 #pre{{ 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"); } ; }} *** 演算 [#e8361cf2] 演算処理には、足し算, 引き算, key(), $Aを使うことができます。 命令セットの制限が強いので、今回は引き算は、変数に対する処理のみとしました。 $Aは、key()関数でセットされたAレジスタの値を使用するために導入しました。 演算の定義は、 #pre{{ 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-"); } ; }} *** 文 [#fd0a6864] 文では、代入文、複合文、if文、while文、関数呼び出しを定義します。 #pre{{ 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"); } ; }} *** 複合文 [#wec616ed] 複合文は、{と}で文を複数宣言することを定義するだけです。 #pre{{ compoundStatement : '{' statement+ '}' ; }} *** if文 [#ye5a1710] if文は、elseなしの簡単なものとしました。(今後の課題) #pre{{ 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文 [#u9c134f6] while文もbreak, continueなしの簡単なものにしています。 if文と同様に@init, @afterでラベルの生成とJUMP, 終了ラベル付けを行っていました。 #pre{{ 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 ; }} *** プログラム全体 [#ob1df828] プログラム全体は、変数宣言と文のならびと定義し、終了したら、終了サウンドをならし、無限ループに入ります。 #pre{{ prog : declaration* ( statement )+ { String label = Main.genLabel(); System.out.println("CAL ENDS"); System.out.println(label + ": JUMP\t" + label); } ; }} これだけの処理で、コンパイラーができるのです。ANTLRを使うと簡単でしょう! ** ソース [#u66b7263] GMC-4コンパイラーのEclipseプロジェクトを以下のファイルをダウンロードしてください。 - &ref(Gem4c.zip); ** コメント [#ia6995e1] この記事は、 #vote(おもしろかった[47],そうでもない[1],わかりずらい[0]) #vote(おもしろかった[48],そうでもない[1],わかりずらい[0]) 皆様のご意見、ご希望をお待ちしております。 - コンパイラーの作成を追加しました。 -- [[竹本 浩]] &new{2009-07-26 (日) 19:14:35}; - 始めまして。「学研 大人の科学マガジン Vol.24 4ビットマイコン GMC-4 @ wiki」に登録させていただきました。http://www15.atwiki.jp/gmc4/pages/12.html -- [[ソフト屋 巣]] &new{2009-08-05 (水) 16:54:52}; - ソフト屋 巣さま、ありがとうございます。 -- [[竹本 浩]] &new{2009-08-05 (水) 17:14:16}; - ArduinoでGMC-4を作ってみました。[[Arduino勉強会/0E-GMC4をArduinoで作ってみる]] -- [[竹本 浩]] &new{2015-01-03 (土) 15:09:59}; #comment_kcaptcha