FrontPage

2009/07/18 からのアクセス回数 6716

GMC-4について

GMC-4は、学研の「大人の科学」Vol.24の付録に付いている4ビットマイコンボードです。

http://otonanokagaku.net/magazine/vol24/index.html

GMC-4.jpg

ハードウェア構成

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レジスタは、条件レジスタ

として使用され、その他は、補助用のレジスタとなっています。

命令セット

GMC-4の命令セットは、

命令コード命令記号実行フラグ機能
0KA0,1押している数字キーをArに入れる。押されていない場合は実行フラグが1。
1AO1Arの内容を数字LEDに点灯する。
2CH1ArとBr、YrとZrの内容をそれぞれ入れ替える。
3CY1ArとYrの内容を入れ替える。
4AM1Arの内容をデータメモリに入れる。
5MA1データメモリの内容をArに入れる。
6M+0,1データメモリの内容にArの内容を加え、Arに入れる。桁上げがあると実行フラグが1。
7M-0,1データメモリの内容からArの内容を引き、Arに入れる。引けないとき実行フラグが1。
8TIA1命令の次のデータをArに入れる。
9AIA0,1Arの内容に命令の次のデータを加え、Arに入れる。桁上げがあると実行フラグが1。
ATIY1命令の次のデータをYrに入れる。
BAIY0,1Yrの内容に命令の次のデータを加え、Yrに入れる。桁上げがあると実行フラグが1。
CCIA0,1Arと命令の次のデータを比べる。異なる場合は実行フラグが1。
DCIY0,1Yrと命令の次のデータを比べる。異なる場合は実行フラグが1。
FJUMP1実行フラグが1のとき、続けて指定したアドレスにジャンプする。

この他にサービスコールとして、

命令コード命令記号実行フラグ機能
E0CAL RSTO1数字LEDを消灯する。
E1CAL SETR12進LEDを1個点灯する。点灯するLEDはYrで指定する。
E2CAL RSTR12進LEDを1個消灯する。消灯するLEDはYrで指定する。
E4CAL CMPL1Arの内容をbit反転する。
E5CAL CHNG1Ar、Br、Yr、Zrと補助レジスタA'r、B'r、Y'r、Z'rの内容を入れ替える。
E6CAL SIFT0,1Arの内容を1bit右へ移動する。結果が偶数のとき実行フラグが1。
E7CAL ENDS1エンド音を鳴らす。
E8CAL ERRS1エラー音を鳴らす。
E9CAL SHTS1"ピッ"という音を鳴らす。
EACAL LONS1"ピー"という音を鳴らす。
EBCAL SUND1Arの内容に対応する音階を鳴らす。
ECCAL TIMR1(Arの内容+1)×0.1秒間プログラムの実行を停止する。
EDCAL DSPR1データメモリの内容を2進LEDに点灯する。5F番地の内容を上位、5E番地の内容を下位に表示する。
EECAL DEM-1データメモリからArの内容を引き、10進数に直してデータメモリに入れる。
EFCAL 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

FX-Simulator.jpg

  • 例題をFX-マイコン・シミュレータで実行するためのファイルfilesample.fxp

言語仕様

言語は、命令セットの影響を強く受け、

  • $A, $ZでAレジスターの値、Zレジスターの値をそのまま使うことを指定できるようにしました。
  • 条件レジスタの制約がきつく、比較が>と==のみとなりました。
  • 引き算は、変数に対してのみ可能です。

となりました。

コンパイラーの処理する言語仕様を以下に示します。

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));		}
	  )
	;
  • kye()関数の場合、KA命令を実行し、その結果のZレジスタの値をそのまま使います。
  • >演算は、Aレジスタの値から(定数+1)を引いて判断します。
  • ==演算は、CIA, CIYの比較命令を使います。

ここでは、プログラムが組める最低限の演算にとどめました。

演算は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文

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文

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プロジェクトを以下のファイルをダウンロードしてください。

コメント

この記事は、

選択肢 投票
おもしろかった 27  
そうでもない 1  
わかりずらい 0  

皆様のご意見、ご希望をお待ちしております。

  • コンパイラーの作成を追加しました。 -- 竹本 浩? 2009-07-26 (日) 19:14:35
  • 始めまして。「学研 大人の科学マガジン Vol.24 4ビットマイコン GMC-4 @ wiki」に登録させていただきました。http://www15.atwiki.jp/gmc4/pages/12.html -- ソフト屋 巣? 2009-08-05 (水) 16:54:52
  • ソフト屋 巣さま、ありがとうございます。 -- 竹本 浩? 2009-08-05 (水) 17:14:16
  • ArduinoでGMC-4を作ってみました。Arduino勉強会/0E-GMC4をArduinoで作ってみる -- 竹本 浩? 2015-01-03 (土) 15:09:59

(Input image string)


*1 E3のCAL INPTは、ありません
*2 コンパイラーのデバッグに重宝しました

添付ファイル: fileGem4c.zip 673件 [詳細] filesample.fxp 790件 [詳細] fileFX-Simulator.jpg 801件 [詳細] fileGMC-4.jpg 778件 [詳細]

トップ   編集 凍結解除 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-08-11 (金) 19:00:35 (9d)
SmartDoc