FPGAでコンピュータ将棋を作る(3)

なんか寝て起きたら頭のなかにコンパイラのコードが詰まっていたので数時間かけて書き出した。一応は、Cライク言語から VHDL/C#コードが生成できるようにはなった。


早速サンプルとしてCRCエンコーダを書いたのでこのエントリの下のほうにつけておく。(実はまだ一部未実装なのでこのコードはまだコンパイルできない。)


私の好みとして、caseのbreakを不要にしたり、case 8..15 : のように定数範囲を指定できるようにしたり、a = if (x==3) 0 else 1; のように式ifを書けるようにしたり細かな拡張をしているのだが、こういう拡張をするとVisualC#でコードを書いたときにことごとくエラー表示されて気分が悪い。


せめて見かけだけでもC#風にして、VisualC#のソースエディタ上でエラー表示が出ないようにしたい。しかし、そうするとどんどん言語が冗長になってくる。これでは、VHDLと比べてそれほどすっきり、というわけでもない。


パソコンの上で高速にシミュレートできるというのが唯一の利点だが、それならVHDL→Cのトランスレータを書いたほうがよっぽど役に立つのではないだろうか。そもそも、世間のVHDL用のシミュレータが何故そんなに遅いのかと言うとよほど手抜き余計な機能をごてごてつけているからではなかろうか。単にCのコードに変換して実行するだけならかなり高速だと思う。


しかし、いま作っているコンピュータ将棋で、そんな高速なVHDLのシミュレーションが必要かと言うと、VHDLのソースを書くのは最終段階で、直前までは自作の「コンピュータ将棋開発用統合環境」で開発するのでVHDLのシミュレーションが遅かろうがそんなことは知ったこっちゃないのである。


VHDL→Cのトランスレータを書く手間 >>>> (越えられない壁) >>>>> VHDLのソースを直接デバッグする手間


なので何をやっているかよくわからないことになってくるので、コンパイラ作成遊びはこれくらいにしといて、「コンピュータ将棋開発用統合環境」作りに戻るのである。



以下、ここで作成した言語で書いたCRCエンコーダのソース*1とこの言語の仕様。


#IF VHDL
use ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
#ENDIF
#IF CCSharp
#include "std_logic.h"

public class CRCEncoder
{
public static void Main(string [] args)
{
Curcuit cir = new Curcuit();

std_logic CLK = new std_logic();
... // 他のパラメータもここでインスタンス生成すべし
std_logic DATA_OUT;

// クロック信号をCurcuit.DoEventが呼び出されるごとに変化させる。
cir.DoEvent = delegate { CLK != CLK; };

CRCENC enc = new CRCENC(cir,CLK,...,out DATA_OUT,..);
// パラメータをすべて書くのが面倒なのでここでは省略

while (true)
{
cir.DoEvent();
Console.WriteLine( DATA_OUT.ToString());
// クロックごとにDATA_OUTの出力値をモニタする。
}
}
}
#ENDIF

class CRCENC
{
CRCENC(
in logic data_in ,
in logic start_in ,
in logic valid_in ,
in logic reset_n ,
in logic clk,
out logic data_out,
out logic start_out,
out logic valid_out
)
{
// processのようにセンシビティがあるのではないassginmentはここに書く。
start_out = start_in & valid_in;
data_out = if (state_reg != OUTPARITY) data_in
else parity_reg(15);
// プライオリティ付き選択には 式if が使える。
// このifは値を返す。
valid_out =
if ( state_reg == WAITDATA && start_in == 1 && valid_in == 1)
|| (state_reg = CALCPARITY && valid_in == 1)
|| (state_reg == OUTPARITY)
1 else 0;

/*
プライオリティなし選択には 式switchが使える。
このswitchは値を返す。

sig = switch(cond)
{
case 4..7 : 1;
case 2,3 : 2;
case 1: 3;
default: 4;
}
*/
}

logic_vector{15..0} parity_reg; // 16bitレジスタ
int{0..15} datacnt_reg; // 0から15までカウントできるレジスタ

enum STATE { WAITDATA , CALCPARITY , OUTPARITY };
STATE state_reg;

process (clk)
{
if (rising clk)
{
if (reset_n == 0)

state_reg = WAITDATA;

else
siwtch (state_reg)
{
case WAITDATA:
if (start_in == 1 && valid_in == 1)
{
parity_reg[15] = 0;
parity_reg[14] = 0;
parity_reg[13] = 0;
parity_reg[12] = data_in;
parity_reg[11] = 0;
parity_reg[10] = 0;
parity_reg[ 9] = 0;
parity_reg[ 8] = 0;
parity_reg[ 7] = 0;
parity_reg[ 6] = 0;
parity_reg[ 5] = data_in;
parity_reg[ 4] = 0;
parity_reg[ 3] = 0;
parity_reg[ 2] = 0;
parity_reg[ 1] = 0;
parity_reg[ 0] = data_in;
datacnt_reg = 1;
state_reg = CALCPARITY;
}
case CALCPARITY:
if (valid_in == 1)
{
parity_reg[15] = parity_reg[14];
parity_reg[14] = parity_reg[13];
parity_reg[13] = parity_reg[12];
parity_reg[12] = parity_reg[11] ^ parity_reg[15] ^ data_in;
parity_reg[11] = parity_reg[10];
parity_reg[10] = parity_reg[ 9];
parity_reg[ 9] = parity_reg[ 8];
parity_reg[ 8] = parity_reg[ 7];
parity_reg[ 7] = parity_reg[ 6];
parity_reg[ 6] = parity_reg[ 5];
parity_reg[ 5] = parity_reg[ 4] ^ parity_reg[15] ^ data_in;
parity_reg[ 4] = parity_reg[ 3];
parity_reg[ 3] = parity_reg[ 2];
parity_reg[ 2] = parity_reg[ 1];
parity_reg[ 1] = parity_reg[ 0];
parity_reg[ 0] = parity_reg[15] ^ data_in;
}

if (datacnt_reg == 15)
{
datacnt_reg = 0;
state_erg = OUTPARITY;
} else {
datacnt_reg = datacnt_reg + 1;
}
case OUTPARITY:
if (datacnt_reg == 15)
state_reg = WAITDATA;
else
{
parity_reg[15..1] = parity_reg[14..0]; // シフト代入
datacnt_reg = datacnt_reg + 1;
}
default:
state_reg = WAITDATA;
} // end switch
} // end if
}

}


■■ 言語のBNF風の表記による定義

備考。
A :- B C. は、「Aとは BがきてそのあとにCが来る」と読む。
{XXX} は XXXが0回以上の繰り返し。(クリーネ閉包)
{XXX}+ は XXXが1回以上の繰り返し。
[XXX] は XXXは省略可能。(0回か1回の繰り返しとみなすことも出来る)
XXX | YYY は XXXかYYY。
例)
[XXX | YYY] {ZZZ} は、XXXかYYYのどちらかが1回くるか、どちらもこないかして、
  そのあとにZZZが0回以上繰り返されるの意味。
(XXX | YYY) ZZZ は、XXXかYYYのどちらかが来て、そのあとにZZZが続くの意味。
XXX YYY | ZZZ WWW は、「XXX YYY」か「ZZZ WWW」のいずれかと読む。

・字句解析部


digit = "0"|"1"|...|"9".
letter = "A"|"B"|...|"Z"|"a"|"b"|..."z"|"_".

binaryDigit = { 0" | "1" }+ "b".
octDigit = {"0"|"1"|..|"7"}+ "o".
hexDigit = {digit | "A" | "B" |...|"F" | "a" | "b" | ...| "f"}+ "h".

ident = letter { letter | digit }.
integer = binaryDigit | octDigit | hexDigit .

character = digit | letter.

char = "'" character "'" | '"' character '"' .
string = '"' { character } '"' | "'" { character } "'".


構文解析

// プログラム本体
program :- { "class" ident "{" { process_block | member_declarations } "}" }.

// processブロック
process_block :- "(" ident ")" "{" process_body "}".
process_body :- Statement.

// 文
Statement :- statement | "{" {statement} "}".
statement :-
assignment | FunctionCall | IfStatement |
SwitchStatement | ForStatement | "break" ; |
"return" [expression] "" .

// この3つはサポートしないかも。
// WhileStatement | RepeatStatement | LoopStatement

// 普通のswitchcase
SwitchStatement :- "switch" "(" expression ")" "{" {CaseStatement} "}" .
CaseStatement :- CaseLabel Statemenet.
CaseLabel :- "case" (Integer | IntegerRange)
{"," (Integer | IntegerRange) } | "default".

// 定数選択用のswitchcase
// プライオリティなし選択には 式switchが使える。
// このswitchは値を返す。

// sig = switch(cond)
// {
// case 4..7 : 1;
// case 2,3 : 2;
// case 1: 3;
// default: 4;
// }

// VHDL化するときprocess文のなかなら case〜whenで展開され、
// process文の外ならwith select〜 whenで展開される。

ExpressionSwitchStatement :- "switch" "(" expression ")" "{" {ExpressionCaseStatement} "}" .
ExpressionCaseStatement :- CaseLabel expression "".

// 普通のif
IfStatement :- "if" ifBlock.
ifBlock :- "(" expression ")" Statement
[ "else" statement | "ef" ifBlock ].

// 定数選択用のif
// a = if (x==3) 1; else 0; のように書ける。
// VHDL化するときprocess文のなかなら if then elsif に展開され、
// process文の外なら、when elseに展開される。

ExpressionIf :- "if" ExpressionIfBlock
ExpressionIfBlock :- "(" expression ")" expression
[ "else" expression | "ef" ExpressionIf ].

// 代入
assignment :- ident "=" (expression "" | ExpressionIf | ExpressionSwitch ).

// 式
expression :- SimpleExpression
[ ("=" | "<" | "<=" | ">" | ">=") SimpleExpression ].

factor :- ident | integer | "(" expression ")" | "~" factor | "rising" ident.
term :- factor { ("*" | "/" | "%" | "&") factor }.
SimpleExpression :- ["+" | "-"] term { ("+" | "-" | "|") term }.

// 定数範囲
IntergerRange :- digit ".." digit.

// ループ関係は、VHDL化したときはfor文によって並列的に展開される。
ForStatement :- "for" ident "=" IntegerRange Statement.