#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

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
SmartDoc