VPPL文法

VPPLはSMYLEvideoのハードウエアに即した並列化オブジェクトコードを得ることを最優先にした言語です。 その並列化はZOMPモデルに基づくものですが、今後の若干のコンパイラの拡張でアクターモデルに近いものも書くことは可能だと思います。 半面、普通のスレッドモデルのサポートは今のところありません。 VPPLは主としてSMYLEvideo用に作成したアセンブラプログラムをそのまま置き換えられることを目標としています。 ハンドコードのアセンブラよりコード効率は幾分か落ちますが、インライン関数展開で用いられるコードジェネレータの呼び出し に帰着できえば、その部分はハンドコードとそん色ない性能のコードが生成できます。 その上、アセンブラに比べたら非常にお手軽に並列処理コードを書くことが可能です。 また、出力するコードはSMYLEvideoのアセンブラコードのスタイルに合致しているので手作業でのチューニングも可能だと思います。 なお、バックエンドのアセンブラは標準のものとGNU gasを使用できますが、gasに関してはまだ各種の機能制限があります。 VPPLは静的でごく弱い型づけの言語です。現代的な動的なところはありません。 綿密な型チェックでプログラマを助けるよりは、自由な操作で素早い処理が書けることを狙っています。 またオブジェクト指向ではありません。 現状は関数型でもありませんが、その方向に発展させていきたい意図はあります。

使用文字セット

使用文字セットはUTF-8。行末はCR+LFです。

コメント

コメントは # に始まり行末までです。 コメント部にのみ日本語文字列の記入が可能です。

識別子に使用可能な文字

英数字と _ です。英大文字と英小文字は識別されます。 なお、先頭が _ で始まる文字列をコンパイラが内部使用しているので、 ユーザーは先頭文字として _ を使用しないことを推奨します。 (意図してコンパイラの内部変数を参照する場合を除きます。)

予約語等

予約語はすべて英小文字です。

行と区切り文字、文

一般的なフリーフォーマットの構文を採用しており、予約語や識別子、演算子等の中間でなければ任意の場所に任意の個数の スペース、タブを置いて記述することができます。行末文字もスペースと同様に扱われます。

VPPLコンパイラは、複合文中の文末の区切り文字として ; を使用しています。 これはC言語と共通していますが、その適用はPASCAL言語式のC言語よりやや厳格な適用です。 複合文の終端である end 文の直前に ; を入れてはいけません。 コンパイラは、; があるとまだ文が続くものと解釈し、そこに end を発見するとあるべき文が無いとエラーを報告します。

文の中に、同等の定義を列挙する場合、 , を区切りに使います。 例えば、

global
        a, b, c : integer;

上の例では、a b c という3つの大域変数が定義されています。 ここでグローバル変数定義である global の後に異なるタイプの変数を複数定義する場合、

global
        a, b, c : integer,
        ptr1 : pointer;

のように、integerの後は、 , となり、次行の pointer の後が ; となることに注意してください。 これは、global から ; までが「一つの文」であり、その中に複数の同等な定義が列挙されているためです。

数値の記述

10進数

通常の数字文字により記載します。負の数値は内部的に2の補数として扱われます。

16進数

C言語同様の以下の書式に従います。

0xABCD
        ABCDには10進数字とAからFまでの英文字(大小問わず)が入ります。

固定小数点数

現状、VPPLコンパイラは、いくつかのフォーマットの固定小数点数をサポートしています。 固定小数点数フォーマットについては以下のような記述フォーマットを利用します。 固定小数点数の即値データについては数値としてソース内に記述することはできず、コンパイラ変換関数を介して即値を記述する必要があります。

FxxQyy
        xxには数値が利用するビット幅を指定します。
        yyには小数点以下の数として使用されるビット幅を指定します。
VFxxQyy
        ベクトル型の固定小数点数については頭文字にVが付加されます。

例えば F32Q16形式は、32ビット幅のスカラー変数で32ビット幅、下の16ビットが小数点以下の数値を記述するのに使われます。

平方根、対数、三角関数等を近似するスカラー関数については、F16Q12型を引数とし、F32Q16型を返す関数が利用可能です。 詳しくはコンパイラ変換関数、ベクタドット演算子等についての各項目を参照してください。

文字列

現状、文字列リテラルを使用できる場所は限定されていますが、使用する場合は、” に始まり ” に終わります。

"ABC"

なお、マルチバイト文字列には未対応です。

プログラムの全体構造

プログラムは、

solution ソリューション名 is

から始まり、

end ソリューション名

で終わるテキストで、solutionと endとの間に以下の順番で6種のセクションを並べます。

  • パラメータセクション
  • プロセス定義セクション
  • FIFOセクション
  • 大域変数セクション
  • 共通プロシージャもしくはインラインプロシージャ定義セクション
  • プロセス本体セクション

パラメータセクション

パラメータセクションは、記述する項目が無ければ省略可能です。 パラメータセクションには、2つの機能があります。第1の機能は、ユーザー定義の定数を定義するという機能です。 例えば

ABC = 5

と定義することで、以後、ユーザープログラム中で数値5と書くべき部分に直接5と書く代わりに ABC というシンボルで代用して書くことができます。

第2の機能は、ターゲットとなるプラットフォームやコンパイラの動作を制御するためのパラメータを明示的に設定するというものです。 VPPLコンパイラは、内部に多数の既定のパラメータをもっており、動作を決定する場合にそれらを参照しています。 例えば

__ASM_MODE

というパラメータは、オブジェクト生成を、FPGA用にするか、RTL用にするか、ISIM用にするかの選択をするためのパラメータです。 デフォルトでは、ISIM用に設定されていますが、コンパイラのコマンドライン引数で変更することができます。 しかし、特定の環境でのみ意味を持つソースコードのため、明示的に設定したい場合もあるでしょう。 そのような場合、このセクションで、

__ASM_MODE = 2

のように、パラメータへの直接代入を行うことができます。上記の例のように記述すると、コンパイラはFPGA用のオブジェクトを得るためのアセンブラソースを出力します。 (コマンドラインの設定よりもこのセクションでの設定が優先します。)

これらのコンパイラパラメータ群には、このセクションの中でのみ代入が可能です。プログラムボディの中での代入は禁止されます。 (実行文の途中でコンパイラの制御パラメータを書き換えられたとすると、無茶苦茶なオブジェクトコードになってしまう可能性があるからです。) ユーザ定義の定数についても同様な制限となります。

すべてのコンパイラパラメータは、アンダバー2文字から始まる名前を持っています。ユーザがアンダバー2文字から始まる識別子を定義することは 妨げられていませんが、内蔵のコンパイラパラメータとの衝突を避けるために使用しないことを推奨します。

なお、すべてのコンパイラパラメータはユーザプログラム中でユーザ定義の定数同様に定数として参照可能です。

パラメータセクションの構文)
parameter
        パラメータシンボル = 数値 [, パラメータシンボル2 = 数値2 ...] ;

パラメータシンボルと数値の組を = でつないで並べます。複数の定義をならべる場合には、 , で区切ります。 このセクションの終了は、 ; です。

内部動作

このセクションで定義されるパラメータは、コンパイラの動作時に適宜参照されるほか、出力するアセンブラソース中に EQU文として出力され、アセンブラプログラム中でも 参照可能です。

TODO

  • プラットフォームのデフォルト値は、現状、コンパイラ内部で初期化されていますが、後日、SHIM.XMLより読み取る機能を追加する予定です。
  • 現状は数値のみの取り扱いですが、文字列についてもハンドリングできるようにする予定です。

プロセス定義セクション

最低限1つのプロセスの定義が必要です。 プロセス定義セクションでは、プラットフォーム上の実ハードウエアコアと後のプロセス本体セクションで定義されるプロセス本体の対応関係を記述します。 プラットフォーム上の実ハードウエアコア名は内部で定義されており、その名前に任意のプロセス名を紐づけます。 そこで使用されたプロセス名は、後のプロセス本体セクションに必ず現れなければなりません。 なお、コンパイラ内部では、実ハードウエアコアの全コアに対して実行プログラムを常に生成しますが、このプロセス定義セクションで使用されなかったコアに 対しては、既定の「何もしない」プロセスを自動生成して割り当てます。

プロセス定義セクションの構文)
process
        コア名 = プロセス名 [, コア名2 = プロセス名2 ...] ;

コア名とプロセス名の組を = でつないで並べます。複数の定義をならべる場合には、 , で区切ります。 このセクションの終了は、 ; です。 なお、複数の物理コアに同一のプロセス名を紐づけた場合、そのコードはシェアされますが、コア毎に異なるプロセスとなり、 プロセス内で定義される変数などは実体は別なものとなります。

コア名

コア名として現在、8個が規定のコアとして内部定義されています。

  • __QVP0
  • __QVP1
  • __QVP2
  • __QVP3
  • __QVP4
  • __QVP5
  • __QVP6
  • __QVP7

なお、特殊なコア名 __this は、自身のコアを参照するコア名として定義されています。

このうち __QVP0 が ハードウエアのコールドスタートを受けて起動されるコアであり、他はホットスタートにより起動されるコアとなります。 また、HRU割り込み信号は__QVP0に接続されており、他コアでは受信できません。PCOM割り込みについては全コア同等の扱いとなります。 詳しくは、SMYLEvideoのハードウエア資料を参照してください。

内部動作

このセクションで定義された実コアとプロセスの関係は、割り込みテーブル、ブートコード他プログラムの外形を自動生成するために使用されます。

TODO

  • 実コア一つに対して、複数のプロセスを割り当てることについては後日の拡張とします。
  • また、静的にではなく、動的に実コアとコードの関係を割り当てる方法についても後日の拡張とします。

FIFOセクション

ZOMPを使用したコア間通信を使用しないのであれば省略可能です。 FIFOセクションでは、コア間に張られるデータ同期の制御機構FIFOを記述します。 プログラム内で with文を使って同期をとる場合には、必ずFIFOが定義されていないとなりません。

FIFOセクション構文)
fifo
        ファイフォ名 from コア名S to コア名D depth 深さ定数 [, ファイフォ名2 from コア名S2 to コア名D2 depth 深さ定数2 ... ] ;

ファイフォ名の後に キーワード from、 to、 depthを使って関係を記述します。 FIFOは1対1の関係であるコアから出力したデータを別なコアへ入力します。 出力側のコア名をfromの後に、入力側のコア名をtoの後に記述します。 depthはFIFOの深さです。数値もしくはパラメータセクションで定義されたシンボルで指定します。深さの上限はハードウエアによって決まっています。

TODO

FIFO定義語は、各コアのCCBエントリ0番に自動セットされていますが、

  • CCBエントリからの初期化については未実装

です。

大域変数セクション

大域変数を使用しない場合には省略可能です。 大域変数セクションには、すべてのコアがアクセス可能な共有メモリ上に静的に領域が確保される大域変数を記述します。 そのままの状態では、すべてのコアが大域変数を読み書きすることができます。 しかし、複数のコアが同じ大域変数に書き込む場合、他のプロセッサ同様の排他制御が必要となり処理効率が落ちます。 並列性を保つためは、ある大域変数には特定のコアのみが書き込むようにしてください。 大域変数スコープ演算子により、特定のコアのみが書き込めるように大域変数の「オーナー」を定めることができます。 物理的な読み出しはどのコアからも可能ですが、同期をとった読み出しにはFIFOを張るのが基本です。

大域変数を列挙する場合には、 , で区切り、このセクションの終わりは ; で区切ります。

大域変数セクション構文)
global
        [プロセス名::]
        大域変数名 : 変数型 [= 初期化子],
        ... 大域変数定義続き ...
        ;

大域変数スコープ演算子によりスコープを定義した場合、以後のすべての大域変数は同じスコープとなります。 スコープを変える毎にスコープ演算子を記述します。 なお、スコープ演算子を定義する以前の大域変数はどのプロセスからも同等にアクセス可能となります。

変数型には以下の8種があります。

variant
integer
pointer
i32ベクタ
i16ベクタ
i8ベクタ
u32ベクタ
u16ベクタ
u8ベクタ

variant型

variant型はバイト、ワード、ダブルワードのビット幅、符号なし、ありの種別、単純変数、固定小数点数、真偽値、アドレス、データの区別なく なんでも保存可能な汎用型です。 この性質は大域変数でもローカル変数でも同じです。基本は符号なしの32ビット整数型で、使用するコンテキストにより動作が変更されます。

例えば、

-1

という数値を変数に代入すると、2の補数形式で0xFFFFFFFFとして保存されます。(符号付きと符号なしで動作が異なる演算子の処理の切り替えについては TO DOです。現状は符号なしのみ実装)。ビット論理演算については符号なしとして取り扱い。真偽値は、0が偽でそれ以外が真。真偽値を計算する演算子については 偽0に対して真1を生成するのでビット演算との区別を意識する必要があります。

integer型

現状、integer型はvariant型の別名です。将来、integerとしての型チェックを行う可能性があります。

pointer型

pointer型は、ローカル変数のアドレス型と似ていますが、その役割は異なります。 pointer型は、ベクタ型の大きな領域をいくつかに分割して操作するため使われる型でベクタ型の一種のエイリアスです。 これに対して、ローカル変数のアドレス型はC言語のポインタ型に似ています。 pointer型は、特定のベクタ型変数とペアリングすることで初期化されます。 初期化は一度だけ許されます。 例えば、vecAをベクタ型変数、ptrAをpointer型変数とすると

ptrA = `vecA

と記すことで、vecAの諸元がptrAに記録されます。`記号は、ベクタプロパティ抽出子です。 pointer型は単にベクタ変数の位置を覚えているだけではありません。 2次元ベクタ型のXサイズ、Yサイズも記憶しており、X方向はSIMD演算によりループ無しで処理、Y方向はpointer型変数をnext文を用いて操作することで走査することができます。 また、ベクタ型の末尾に達すると自動的に先頭に戻るので、FIFOの実体としてベクタ型変数を使用したり、ビデオバッファなどの大きな構造を処理するためにも用いることができます。

ベクタ型

ベクタ型については、その要素数を定義する必要があり、以下の書式で型を指定します。

1次元ベクタの場合)
        ベクタ型名 [ x方向要素数 ]
2次元ベクタの場合)
        ベクタ型名 [ x方向要素数 , y方向要素数 ]

現状x方向の要素数は i8ベクタで最大512、i32ベクタで128です。また、y方向の要素数には制限がありません。

内部動作

ベクタ型のX要素数は最大値を超えなければ、半端な数でも構いません。内部的には256ビット幅のSIMDレジスタにフィットするように メモリをアロケートしているので、半端な部分にはパディングが入ります。最大16個までのSIMDレジスタについて「アンロール」したSIMD命令を 生成することで、X方向についてはループなく処理されます。また、ベクタ型の変数は、内部のSIMDレジスタバンクに「キャッシュ」されます。 つまり、繰り返し同じベクタ変数を参照する場合には、内部レジスタが参照され外部メモリにはアクセスされません。なお、「キャッシュ」のスコープは 構文ブロック内に限定されており、例えば条件分岐、サブルーチンコールなどが発生するとキャッシュはクリアされ、再びロードが起こります。

TODO

  • 大域変数に関するアクセス制御子。特定のコアのみに書き込みを許し、他コアは参照だけとするオプション。並列処理での間違えを無くすため。
  • 未初期化の大域変数宣言。変数初期化領域の大きさを小さくとどめたいので、未初期化で良い大域変数領域をサポートする。

大域変数初期化子

大域変数は、オプショナルな初期化子によって初期化をすることができます。初期化子は型名の後に = で続けて書きます。

初期化子)
= 数値 もしくは コンパイラ初期化子関数[初期値, 増分値]

integer型は単一の数値を一つとります。pointer型に初期化子を与えても無視されます。 ベクタ型に単一の数値を与えると、すべての要素が同じその数値に初期化されます。 ベクタ型に等差数列などを設定するためにコンパイラ初期化子関数を使用することができあます。 コンパイラ初期化子関数はコンパイル時に値を解決するための関数で、実行時には使用できません。

コンパイラ初期化子関数

コンパイラ初期化子関数は、実行時の関数ではなく、コンパイル時に有効な関数です。これにより、複雑な初期値データなどをベクタ変数に割り当てて使用できるようになります。

vseq[初期値, 増分値]
        初期値に始まり、交差が増分値となる等差数列をベクタに割り当てます。
        指定の型が許す範囲を値が超えてしまう場合、値はラップアラウンドします。
vRandom[下限値, 上限値]
        下限値と上限値の間の乱数を生成し、ベクタに割り当てます。
        指定の型が許す範囲を超える値を指定すると、指定型の上限、下限に丸められます。
        なお、現状、u32型の上限は0x7FFFFFFFです。
vList[要素0, ...]
        ベクトルの設定値を要素0から順に直接指定します。ベクトルの長さより指定リストが短い場合、残りの要素は0詰めされます。長い場合は切り捨てられます。
external[バイト長]
        ソース内部ではなく、外部のメモリ領域を参照したい場合に宣言します。pointer型変数宣言に対してこの初期化子を記述します。
        先頭要素は単純な整数変数としてアクセス可能ですが、アドレス型変数を介することで領域すべてへのアクセスが可能となります。
        ISIMモードでは、未初期化のメモリが割り当てられます。
        INCモード指定では、外部シンボルを必要とするコードが生成されます。
vFile["外部ファイル名文字列", "配列データ名文字列", "配列データ型"]
        外部ファイルから浮動小数点型のデータ配列を読み取って固定小数点型データとして参照したい場合に宣言します。
        pointer型変数宣言に対してこの初期化子を記述します。
        第1引数には外部ファイル名をそのまま指定します。第2引数には配列データ要素名を指定します。第3引数には固定小数点型を示す文字列を指定します。
        使用できる型は、F16Q8, F16Q10, F16Q12, F32Q16です。
        なお外部ファイルは、dataDumperライブラリが出力する形式のファイルとなります。

共通プロシージャもしくはインラインプロシージャ定義セクション

同じユーザ定義サブルーチン・コードの実体を複数のコアでシェアして使用できるのが、共通プロシージャです。 これに対して定義はシェアされますが、実際には各コア用のコードにインライン展開されるのがインラインプロシージャです。 インライン・プロシージャについては、現時点ではまだ実装されていません。 このセクションは定義するものがなければ、省略可能です。

共通プロシージャセクション構文)
shared procedure プロシージャ名 ; [仮引数リスト] [ローカル変数リスト] begin 実行文 end プロシージャ名
        必要な数だけこの定義を繰り返す。

内部動作

各プロシージャはサブルーチンコールされるサブルーチンとして実装されます。他のコンパイラ同様、呼び出し時にはスタックフレームが 生成され、スタックフレーム上に実引数やローカル変数が配置されます。また、呼び出し元のローカルなレジスタ変数や一時変数を破壊しないために Rレジスタ上に確保された変数は退避されます。これに対してDレジスタ上に「キャッシュ」されたSIMDレジスタ変数はキャッシュを無効とし、 復帰後に混同が起こらないように制御されます。キャッシュの無効かはコンパイル時の問題なので、実質的なオーバヘッドはありませんが、 Rレジスタの退避は実行時に必要なので、それなりのオーバヘッドを伴います。

ベクター変数の使用

ベクター変数もベクター変数を指すことのできるポインタ変数も大域です。 大域変数をそのまま使用したのでは、複数のプロセスから異なるベクターに対して共通処理を行うような共通プロシージャにはなりません。 共通プロシージャで複数のプロセスから依頼をうけて異なるベクター変数を処理する場合には以下のようにします。

  • 呼び出すプロセス側からは byValue 修飾でポインタ変数を渡す。byValue修飾によりポインタ変数の保持するアドレスがプロシージャに渡されます。
  • プロシージャ側では byName 修飾でアドレスを受け取る。これによりプロシージャ側ではローカルなアドレス変数がターゲットのアドレスを保持します。
  • このローカルなアドレス変数をポインタ変数を受け取るドット演算子に渡してベクトル処理を行う。

上記のようにすれば、複数の呼び出し側プロセスで異なるアドレスを指す異なるポインタ変数を一つの共通プロシージャに渡しても 共通プロシージャはそれぞれ異なるベクタに対する同様な処理が行えるようになります。

なお、作業領域としてalloc_vectorディレクティブで領域を確保する場合、 共通プロシージャとそれを呼び出すすべてのプロセスで同様なalloc_vectorの宣言が必要です。 プロセス側ではinitizlizeブロックの先頭で、共通プロシージャ側ではbeginの直後にまったく同じ順番で宣言します。 なお、alloc_vectorでの確保は大域の名前を作りだすので、すべての名前は異なる必要があります。 共通プロシージャ側での宣言は仮のもので、実体を確保するわけではありません。 呼び出しに応じ、実際には呼び出すプロセス側で確保された実体が使用されます。 2つのプロセスから呼び出される共通プロシージャ内でalloc_vectorされた仮変数は、あるプロセスから呼ばれた場合は そちらのプロセスで定義されているもの、他のプロセスから呼ばれた場合は、他のプロセスで定義されたものとなります。

TODO

  • Rレジスタの退避シーケンスは最適化可能ですが、今のところオプティマイザは未実装です。
  • インラインプロシージャ定義のサポート
  • 末尾再帰最適化

仮引数リスト

仮引数は、以下のような構文で共通プロシージャ内で使用する変数名を列挙することで指定できます。すべての仮引数の型は データでもアドレスでも渡せる汎用整数型であり、最大15個までに制限されます。

byValue 変数名1, byName 変数名2, ... ;

変数名リストの末尾は ; で終了させます。すべての変数の前に byValue もしくは、byName 修飾語 を置かねばなりません。 byValueあるいはbyNameとことさらに指定している場所は、その引数がスタックフレームを介してサブルーチンに渡されていることを示しています。 これに対して同じような関数形式にみえるものでもbyValue, byNameという指定が不要な場合があります。 それらの引数はスタック渡しではなく、コンパイル時に解釈されている引数です。 当然ですが、スタック渡しよりコンパイル時解釈の方がオーバヘッドがありません。 しかし、コンパイル時には静的な解釈しかできないので制限があります。 共通プロシージャの引数は、すべてスタックフレーム経由なのでオーバヘッドがありますが、処理の自由度も大きくなります。 また、他の組み込み関数の一部は呼び出し毎にインライン展開され、サブルーチン形式になりません。 しかし、共通プロシージャはかならずサブルーチン形式で生成されます。 これもオーバヘッドを大きくしますが、同じコードが複数のコアでシェアできるのでコードサイズの縮小には貢献します。 byValue は C言語やPASCAL言語同様の 値呼び(call by value) であることを示します。 byValueの後には、変数、即値だけでなく式や値を返す関数を書くことも可能です。 byName は、Fortran言語などで一般的な 名前呼び(call by name)、もしくはC言語で実引数に & を付けた場合にほぼ相当します。 なお、レジスタ変数については byName 呼び出しはできません。また、式をbyNameで呼び出すこともできません。

  • byValue で渡されたデータを共通プロシージャ側で操作しても、呼び出し元の変数は変化しない
  • byName で渡されたデータを共通プロシージャ側で操作すると、呼び出し元の変数が変化する

VPPLコンパイラは、関数呼び出しにおいて実引数と仮引数の数の合同はチェックしますが、byValueとbyNameの合同はチェックしません。 この「手抜き」により以下のような操作が可能となります。

  • 実引数側で byName で渡されたアドレスを byValue の仮引数で受けることで、共通プロシージャ側でアドレス演算が可能となる
  • 実引数側で byValue で渡したデータを byName の仮引数で受けることで、共通プロシージャ側で渡されたデータをアドレスと解釈してメモリアクセスが可能となる

この操作の正当性については、プログラマが管理しなければなりません。

内部動作

byValue変数は内部的にはローカルな integer として扱われ、byName変数は内部的にはアドレス型変数として扱われます。 アドレス型変数を読むと、該当のアドレスの内容が返り、アドレス型変数に書くと、該当のアドレスに書き込まれます。

ローカル変数リスト

ローカル変数は動的なスコープ(共通プロシージャの実行時にのみ存在し共通プロシージャから戻れば消える)です。 変数名が現れた場合、まず引数を含むローカルスコープの中で検索され、発見できればローカル変数として取り扱われます。 ローカルスコープに無い場合は、グローバルスコープで検索され、発見できればグローバル変数として取り扱われます。 ローカルスコープは各関数、各プロセス独立なので、ローカル変数名が重複しても実体は別なものとなります。 ただし、ローカルスコープとグローバルスコープで同じ変数名を使用した場合、グローバルスコープ解決のための演算子のようなものは 今のところ用意されていないので、グローバルスコープの変数はローカルスコープの変数に隠されてしまいます。

以下の型が使用できます。

  • integer
  • register

integer型のローカル変数は仮引数と合わせて15個までに制限されます。 また、register型も汎用の整数型であることは変わりありませんが、必ず Rレジスタに置かれることが保証される変数です。 最大8個までの register型変数を確保できますが、スカラー演算の途中の情報もレジスタに置かれるので、 あまり多くの register型変数を確保すると複雑なスカラー演算式の処理が不可能となりコンパイラがエラーを報告することとなります。 多くとも5個程度に抑えることを推奨します。 ローカル変数は以下のような構文をとります。

var
        a, b : integer;

TODO

  • val宣言の追加。一度だけしか書き込めない変数のサポート。並列処理のため。
  • 拡張形式のサポートによる、ローカル変数の数の拡張。最大4096個

共通プロシージャの処理本体構文

共通プロシージャの処理本体構文規則は、プロセス本体セクションとほぼ共通です。 唯一の違いは return 文によって明示的に値を呼び出し元に返すことができる点です。

return 変数;

明示的なreturn文が無い場合、どのような結果が返るかは保証されません。 現状は、return文の無い共通プロシージャであっても共通プロシージャの「結果を用いる」ようなコードはエラーとなりません。

プロセス本体セクション

最低限ひとつのプロセス本体の記述が必要です。一つのプロセス本体セクションを複数のコアでシェアすること(一種のマルチスレッドモデル)も可能ですが、 その場合には、ZOMPを使ったプロセス間通信が記述できません。 複数コア間でZOMPを使ったプロセス間通信を使用し、データフロー型のプロセスネットワークモデルで動作させる場合には、 同時に実行させるコアの数だけのプロセス本体記述が必要です。 プロセスは、その内部で純粋ソフトウエア的なループを記述することも可能ですが、外部のトリガ(フレーム割り込みなど)や他コアからのトリガをきっかけとして 繰り返し起動させる場合もあります。そのため、実行文は2つの部分に分かれています。

proc プロセス名 ; プロセス変数宣言 initialize 初期化複合文 iteration 繰り返し複合文 end プロセス名

プロセス名には、プロセス定義セクションで宣言済のプロセス名を与えます。なお、末尾のendの後にもプロセス名が必要です。 initializeキーワードに続けて、初期化時に実行される複合文を書きます。 初期化時とは、プロセスの起動に際して1度だけ実行されるコードです。 iterationキーワードに続けて、プロセスが呼ばれる度に繰り返し実行される複合文を書きます。 一端 end まで実行したとすると、プロセスは割り込み待ちのアイドル状態となります。 外部あるいは、他コアからトリガを受けると、アイドル状態を脱し、iterationキーワードに続く繰り返し複合文の先頭から再度動作を開始します。 その際、プロセス変数などは前の状態が保存されています。

initializeとiterationに関する補足

initialize部は通常、コールドスタート後1度だけ実行されます。ここには各繰り返し毎に初期化されたくない変数の初期化などを置きます。 また、実機(FPGA)用にコンパイルされた場合、この部分の末尾に割り込みの初期化が含まれています。逆に言えば、initialize部におかれた ステートメントは割り込まれることはありません。ISIMモードでは、initialize部の実行後、即座にiteration部が1度だけ実行され 実行完了すれば、各コアは終了の無限ループに入り脱出することはありません。それに対して実機(FPGA)用にコンパイルされた場合は、割り込み要因 (動画処理の場合はフレーム割り込み)をトリガとしてiteration部が繰り返し実行されます。FIFOの初期設定など各実行回の先頭で 初期化した方がよいものは、iteration部の先頭で初期化した方が良いでしょう。そうしておくことで、 万が一そのフレームの処理がオーバランしたとしても次の回からリカバリさせることが可能となります。 (初期設定ではオーバランが検出された場合、システムは停止するようになっています。)

プロセス変数宣言

共有プロシージャと同様に、var宣言をおいてプロセスにローカルな変数を宣言することができます。 構文的には共有プロシージャと同様です。共有プロシージャと異なるのは、ここで宣言されたプロセスローカルな変数は スタティックでソリューションが存在する限り有効だ、という点です。

TODO

  • ネストした共通プロシージャ内から呼び出したプロセス固有のスタティック・ローカル変数にアクセスするための仕組み
  • CCB領域へのアクセスの仕組み

複合文

複合文は、C言語などと同様に文を ; で区切って並べることで作ることができます。ただし、前述のように PASCAL言語風に末尾の end の直前に ; を入れてはいけません。

注意

このセミコロンとカンマの使い方に注意してください。C言語風にすべての文末にセミコロンを入れていくと end の前でコンパイルエラーが発生します。 また、なんらかの列挙を行う場合は、並列な列挙はカンマで区切ります。セミコロンは列挙の終了を意味します。

代入文

代入は、代入先変数名を左辺(LHS)とし、右辺(RHS)の式を = 記号でつないで記述します。ここは、C言語と同様で、PASCAL言語の := ではないので注意してください。

代入先変数名 = 式

なお、式は変数名や定数を演算子とカッコ ( ) で結合することで記述することができますが、 演算子の優先順位などはC言語などと比べて簡略化されているので以下の記述を参照してください。 また、演算の途中でペンディングにしておかないとならない中間結果などはレジスタにテンポラリに置かれます。 非常に複雑な式ではテンポラリが足りなくなってエラーとなる場合があります。 そのような場合、テンポラリは一端代入すると解消されるので、複数の文に分けてください。

特殊な代入

ベクタ変数のエイリアスであるポインタ変数への代入は1回のみ可能です。また、その代入は必ず以下の構文によらねばなりません。

ポインタ変数名 = `ベクタ変数名

ベクタ変数名に先立つ ` 記号は、ベクタ変数の保持するデータではなく、ベクタ変数のプロパティをポインタ変数に抽出することを 示す修飾子です。

ポインタ変数の指すベクタの参照

ポインタ変数の指すベクタを参照する場合、

$ポインタ変数名

$ 修飾子を変数名の先につける形式で、参照することが可能です。右辺に現れれば読み出し、左辺に現れれば書き込みとなります。 ポインタ変数による参照は、常に2次元ベクタの第1の次元1列です。第2の次元を進めるためには以下の構文により、 次元を送らねばなりません。

next ポインタ変数名

スカラー演算子

演算子の優先順位は3段階です。それ以外は左から順に評価されるので、必要に応じて ( ) でくくって優先順位を明示的に示してください。

スカラー単項演算子として、現在のところ以下の4つをサポートしています。単項演算子の優先度は2項演算子に優越します。

  • + 正を示す。特に効果はありません
  • - 負を示す。後に続く値の2の補数をとります
  • ! 論理否定。値0であれば1、値1であれば0をとります
  • ~ ビット反転。ビットを反転します。値0であれば0xFFFFFFFF, 値1であれば 0xFFFFFFFEとなります。

低い優先順位の2項演算子として、現在のところ以下のものをサポートしています。

  • + 加法演算子
  • - 減法演算子
  • == 一致演算子、2項が一致していたら1、そうでなければ0を与えます
  • != 不一致演算子、2項が不一致なら1、そうでなければ0を与えます
  • < 小なり演算子、a < b が成り立つなら1、そうでなければ0を与えます
  • <= 小なりイコール演算子、a <= b が成り立つなら1、そうでなければ0を与えます
  • > 大なり演算子、a > b が成り立つなら1、そうでなければ0を与えます
  • >= 大なりイコール演算子、a >= b が成り立つなら1、そうでなければ0を与えます
  • | ビットOR演算子
  • ^ ビットXOR演算子

高い優先順位の2項演算子として、現在のところ以下のものをサポートしています。

  • * 乗法演算子
  • / 除法演算子
  • % 剰余演算子
  • >> 右論理シフト演算子
  • << 左論理シフト演算子
  • & ビットAND演算子

注釈

現在の スカラー除法演算子、剰余演算子は符号なしのみのサポートです。 符号付きの演算を行う場合には、divQ関数を利用してください。

ベクタ2項演算子

ベクタ型変数に対する2項演算子として、現在のところ以下のものをサポートしています。

低い優先順位

  • + 加法演算子
  • - 減法演算子
  • | ビットOR演算子
  • ^ ビットXOR演算子
  • == 一致演算子
  • != 不一致演算子

高い優先順位

  • * 乗法演算子(I16型のサチュレート演算限定)
  • & ビットAND演算子

ベクタ型の2項演算子については2項の両辺は同じ型でなければなりません。

TODO

  • ベクタ論理演算子 NOT
  • ベクタ比較演算子 > >= < <=

ベクタ・ドット演算子

通常の2項演算子でカバーできないベクタ型の演算をサポートするためドット演算子が用意されています。

ベクタ型変数名.ドット演算子名1(引数リスト).ドット演算子名2(引数リスト)~

ドット演算子は操作対象のベクタ型変数に対して、左から順に適用されるので通常の式よりも効率のよい演算が可能です。 また、複数の引数をとることができるので2項演算に限定されず、また、2項の両辺が同一の型でなくとも演算可能です。

なお、ドット演算子は、通常の関数と異なり、引数に式はとれません。単純変数もしくは定数のみを引数にとることができます。

ドット演算子の一覧は ベクタ・ドット演算子の一覧 に書かれています。

制御構文

if

条件分岐のため if 文が用意されています。

if 条件式 then 複合文 [else 複合文] end

条件式としてはスカラー値をとる式を与えます。0以外なら then以下を実行し、0のときに else部があればそれを実行します。

while

条件が満たされるまでのループとして while 文が用意されています。

while 条件式 do 複合文 end

条件式としてはスカラー値をとる式を与えます。0以外ならループ本体の複合文を実行します。

for

決まった回数のループ制御用の for 文が用意されています。

for ループ変数名 = 初期値式 to 上限式 do 複合文 end

ループ変数として宣言済のローカル、グローバル変数をとります。通常はローカルなレジスタ変数などを使用します。 ループ変数は、初期値式の値で初期化され、繰り返しの末尾で+1されます。上限式の値を超えない間、複合文を実行します。

TODO

+1するTOのみ準備してあるが、DOWNTO、STEPについても追加を検討したい。

with

ZOMPを使って同期をとるための構文です。

FIFOに書き込む場合)
with syncFifoWrite ファイフォ名 do 複合文 end
FIFOを読み出す場合)
with syncFifoRead ファイフォ名 do 複合文 end

FIFOに書き込む場合、対象のFIFOに空きがあれば、即座に複合文が実行されます。 FIFOがフルの場合、FIFOに空きができるまで複合文の実行はブロックされます。

FIFOから読み出す場合、対象のFIFOにデータがあれば、即座に複合文が実行されます。 FIFOがエンプティの場合、FIFOにデータが到来するまで複合文の実行はブロックされます。

複合文の実行期間中は、対象のFIFO要素は確保されていますが、endに至るとその要素は開放されます。

TODO

ノンブロッキングのFIFO判定構文。コントロールレジスタを読み出してソフト処理する予定。

next

ポインタ型変数を2次元ベクタの次のベクタX列(行)へとむけます。ベクタ末尾に至るとベクタの先頭のX列(行)に移動します。

next ポインタ型変数

return

return 戻り値

continue

continue

continue文を含む最内ループの次のループへ飛びます。

break

break

break文を含む最内ループから脱出します。

pass

pass

文法的に文を置かねばならない位置に何も置かないとコンパイルエラーとなってしまします。 とりあえず空のままにしておきたい場合に、明示的な空文である pass を置きます。

組み込みプロシージャ

組み込みプロシージャは、コンパイラが内蔵している関数のうち、単独の文としての実行のみ許されるものです。 式の右辺やユーザ定義関数の引数の中に現れてはいけません。 組み込みプロシージャは、必要に応じインライン展開されたり、サブルーチンコールとしてコードジェネレーションされます。 組み込みプロシージャの中には変数を引数として値を返せるものや、コンパイラ既定変数 _emb_result_ を介して値を返せるものもあります。 なお、組み込みプロシージャは、通常のユーザ定義関数と異なり、引数に式はとれません。 単純変数もしくは定数のみを引数にとることができます。

組み込みプロシージャの一覧は 組み込みプロシージャの一覧 に書かれています。

組み込み関数

組み込み関数は、コンパイラが内蔵している関数のうち、 式の右辺や関数の引数の中で呼び出して値を返せる数値計算のための関数です。

式の左辺においたり、単独の文としては実行することはできません。 組み込み関数は、ユーザ関数同様にサブルーチンコールとしてコードジェネレーションされます。 なお、組み込み関数は、通常のユーザ定義関数同様に引数に式を取ることが可能ですが、 byValue 渡しの引数でなければなりません。

組み込み関数の一覧は 組み込み関数の一覧 に書かれています。

コンパイラ既定変数

コンパイラ既定変数は、コンパイラにより定義される変数です。組み込みプロシージャなどの実行にともない値が変化します。 代入も可能ですが、内部的に書き換えられてしまうので値の保持には使えません。

_emb_result_

コンパイラ組み込みプロシージャの結果を返します。有効なのは関数の実行直後に値の比較等をする場合に限られます。

コンパイラ・ディレクティブ

コンパイラ・ディレクティブも関数同様の見かけをもっていますが、実体のあるコードは生成されず、コンパイラにコンパイル時の指示をするためのものです。 コンパイル時に決定されるため、動的な値は引数にとることはできません。

alloc_vector(アロケーションするベクタ変数名, ベクタ要素型, ベクタサイズ)

alloc_vectorディレクティブは、一種の変数宣言です。主記憶でなく、SIMDレジスタ上に固定的な変数領域を確保します。 この変数領域は、プロセスが存在する間、固定されるので、かならずレジスタ上にあることが保証されます。 また、常にアキュムレータを含むようにアロケートされるので、アキュミュレータ演算のターゲットとしても使用可能です。 変数値は初期化されません。場所を確保するだけです。最大8個までのベクタ変数をアロケートしておくことができます。

アロケーションするベクタ変数名は、大域で名前が他と衝突しない新たな名前です。 ベクタ要素型には、以下のパラメータを与えます。

  • __I8V
  • __I16V
  • __I32V
  • __U8V
  • __U16V
  • __U32V

ベクタサイズには、アロケートする連続レジスタ数を示す1から16の数値を与えます。これにより最大512要素までのベクタがアロケートできます。

通常、procで定義されるひとつのプロセス定義の中で用いられます。 ベクタ変数なのでベクタ変数名自体は大域です。 しかし、レジスタ上にアロケーションするためのディレクティブなので、物理的にレジスタ確保が有効なスコープは該当のプロセスおよびレジスタ共有が可能な隣接プロセスからに限定されます。 shared procedure内で確保することも可能ですが、shared procedure内での定義は仮の確保です。 shared procedureはどのプロセスから呼び出されるか不明なので、それを呼び出すすべてのプロセス側の定義と整合性がとれている必要があります。 shared procedure内での利用はプログラマが意図して行うケースを除き不用意に行わないでください。 コンフリクトが発生してもコンパイラは関知しません。

なお、アロケーションしたベクタを作業領域に使用する場合、以下のような代入文となる場合があります。 代入を行っていますが、vTEMPにはメモリ上の実体がないので、実際にメモリへの転送は発生せず、無駄なコードも生成されません。 メモリ上の実体がないので、以下のvTEMPの保持する値は意図して実体のあるベクタにコピーしない限り、isim_outなどで表示することもできません。

alloc_vector( vTEMP, __I16V, 16)
~
vTEMP = vTEMP.r_min$IW(vTest0, scalar_result);

reserve_bank(リザーブ領域を代表する仮のベクタ変数名、物理バンク番号)

reserve_bankディレクティブは、物理的なレジスタバンクを予約します。 外部プログラムとVPPLでコンパイルされたプログラムを協調動作させるような場合に、外部プログラムが確保している領域をVPPLが勝手し使わないようにコンパイラに指示するためのディレクティブです。 また、alloc_vectorディレクティブで確保した領域が異なるスコープのshared procedureなどで使用されないように保護するためにも使用できます。

コンパイラ変換関数

コンパイラ変換関数は、コンパイル時に評価され、実際にプログラムで使われる値を生成する関数です。 式の右辺に使うことができます。 コンパイル時に評価されるため、動的な値は引数にとることはできません。

コンパイラ変換関数プロシージャの一覧は コンパイラ変換関数の一覧 に書かれています。

コンパイラパラメータ

コンパイラパラメータはデフォルト値を利用する限り設定の必要はありませんが、所望の制御を行いたい場合には、 ソースコード内のパラメータセクションで宣言する必要があります。また、一部のパラメータについてはコンパイラの コマンドライン引数でも設定が可能です。

  • __ASM_MODE
  • __CODE_SIZE
  • __PLATFORM_NUMBER_OF_CORE__
  • __MAX_FIFO
  • __MAX_FIFO_DEPTH
  • __ADDR_HRU_TOP
  • __ADDR_HRU_REG
  • __ADDR_HTU_TOP
  • __ADDR_HTU_REG
  • __IMAGE_BUFFER_A
  • __IMAGE_BUFFER_B
  • __IMAGE_BUFFER_END
  • __I32V
  • __I16V
  • __I8V
  • __U32V
  • __U16V
  • __U8V
  • __NOT_INIT
  • __START
  • __WAIT_HOT
  • __WAIT_HRU
  • __WAIT_INTR
  • __EXEC_START
  • __RUNNING
  • __TERMINATE
  • __BYPASS_WAITING
  • __DEBUG_CORE デバッグ対象にする物理コア番号を指定します。デフォルトは0となっています。
  • __OVERRUN_RECOVERY

__DEBUG_CORE

実機(FPGA)デバッグ対象にする物理コア番号を指定します。デフォルトは0となっています。 指定のコアのみが実機デバッグ用のリソース(LEDポートなど)にアクセス可能となります。 他コアで使用しようとしてもコンパイル時にWARNING扱いとなり、アクセスのためのコードは生成されません。

__OVERRUN_RECOVERY

実機上でハードウエアの繰り返しインターバル(フレームレートなど)のオーバランを検出した場合に デフォルトでは無限ループに入って停止するようになっています。(物理0番コアをデバッグコアとして 指定しておくとオーバラン検出ステータス ”.F” をLEDに出力します。) 停止させずに、初期化からリカバリさせたい場合には、本パラメータに0以外の数字を立てることで リカバリ動作を行うようになります。