このマイコンには GPIF という、ステートマシンをソフトウェアの介在無しで動作させることが出来るペリフェラルが存在します。
普通にこれ便利ですよね。ちょっとやってみたいと思います。Cypress といえば PSoC ですが、このような機能が搭載されているのはやはりハードウェアが得意な企業だからでしょうか?ちょっと考えが雑すぎますがね。
本記事は、このペリフェラルを使うために取り敢えず手書きでステートマシン作ってみたよ!という内容になります。
本記事では EZ-USB FX2LP のユーザーマニュアル、AN66806 - Getting Started with EZ-USB® FX2LP™ GPIF を参考にしています。日本語の GPIF 用マニュアルあったのね。
ステートマシン
ステートマシンは状態機械とも訳され、λ や Σ 等の記号を用いて表される情報数学の分野ですが、要はいくつかの動作ステージのある動作機構です。今回に合わせて言えば、それぞれのステージごとに入力と出力を処理できる回路だと思ってください。論理回路で言う順序回路そのものです。
GPIFの特徴
GPIF (General Programmable InterFace) はこのステートマシンを取り扱う事のできるペリフェラルです。GPIF は基本的に時分割でステートを管理し、条件分岐によって自在に状態遷移をすることが可能です。
条件分岐に使える論理関数が簡単な2入力関数しか無いため、複雑なロジックを組むことは本質的に向いていませんが、変態的にやれば多分出来るはずです。若干動作は遅くなりそうですが、それでもまあ CPU を介在しないのでかなり速く動作できると思います。希望する動作は Waveform Descriptor と呼ばれる領域に書き込むことでセッティングが完了します。最大で S0 〜 S6 の7つのステートに対応しています。
あ、そうそう。いちばん大事なことを言うと、GPIF 基本的に外部回路(おそらく 外部メモリやその他通信する IC)との接続を意図した構成であり、入出力ピンの名前も CTL, RDY 等々となっています。
それで、この GPIF はやや分かりづらい構成をしているので本記事でまとめておきたいと思います。また、まだまだ理解不足なところもあって、今回では FIFO 関連以外の説明にとどまることになります。ご了承ください。
GPIFで使うピン名
ステートマシンを構成する上で使うことの出来るピンです。入出力レベルやオープンドレインなどの設定もできるのでカスタマイズが多様に出来ますね。
ピン名とその役割について列挙します。また、今回使っている 56pin のチップについても一部記述しますね。
IFCLK
- IFCLK というクロック信号に関するピン。これが GPIF を動作させる。
- 入力の時は最低でも 5MHz。出力として自律動作する際は 30MHz or 48MHz。
FD[15:0]
- データバス。入出力兼用。
- GPIF をオンにするとデフォルトで PortB が全て
FD[7:0]
に設定される。 - FIFO 関連のレジスタをいじると PortD も使う 16bit データパスモードになるが、今回は省略。
CTL[5:0]
- 出力ピンのこと。Port の周辺機能ではなく、そういう名前の専用ピンがある。
- 56pin デバイスでは
CTL0
〜CTL2
の3ビット分のみが使用可能。
RDY[5:0]
- 専用の入力ピンのこと。
- 56pin デバイスでは
RDY0
,RDY1
のみが使用可能。ひもじいね。
GPIFADR[8:0]
- 9bit 幅のアドレスを表す Port ピン。
GPIFADRL
,GPIFADRH
にデータを書き込むとすぐに反映される。LCD や メモリに使ってくれと言わんばかりのピンだね。 - 56pin デバイスではこのピンは存在しない。残念。
- 9bit 幅のアドレスを表す Port ピン。
GSTATE[2:0]
- デバッグ用のピン。ステート何番に居るかを表すらしい。
- こちらも 56pin デバイスでは使えない。
ステートマシンの設計 - AND を取る回路
とっっってもつまらないですが、基本ということで 2 つのピンの入力を取って、その AND をそのまま出力する回路を作っていきたいと思います。まずはステートを意識します。前述の通り、この GPIF はクロックによる時間経過がある場合のための機構なのでちょっと本旨からズレている感じがしますが、まあいいでしょう(適当)。
回路の入力及び出力の条件を見て、動作を分割していけば基本的にステートは出来上がります。そして、GPIF でコツになるのが、条件分岐をする専用のステートを設定する必要があるということです。例えば S0 → S1 と時間の経過で自動的に状態遷移する場合は条件分岐用のステートは要りませんが、S0 → (条件分岐) → S1 or S2 とかの場合は条件分岐用ステート S3 を一瞬だけ経過するように用意して、設定をします。
ではまず、今回のこの回路の動作を挙げてみましょう。
- 入力はすべてのステートで一緒であり、2入力(IN0, IN1 とおく)
- IN0 & IN1 == 1 のときは出力が 1
- IN0 & IN1 == 0 のときは出力が 0
これだけですね。ただ、「〜〜のとき」という文言を使っているので、出力が1のとき、0のときそれぞれで状態遷移用のステートが必要です。このようなステートを以後 DP と呼びます。設計してみると次のようでしょうか。
ステート名 | 出力 | 次のステート |
---|---|---|
S0 | 0 | S1 |
S1(DP) | 0 | If AND(IN0, IN1) == 1 Then S2 Else S0 |
S2 | 1 | S3 |
S3(DP) | 1 | If AND(IN0, IN1) == 1 Then S2 Else S0 |
これ以上回路動作は分割できないので、このような動作条件とします。一応時系列でこのステートを並べておけば AND は実現できます。起動直後は入力ピンの値に関わらず 0 を出力してしまいますが、それは仕方ないとしましょうか。
ステートマシンの記述
GPIF を使うためのお膳立ては置いておいて、上のステートマシンを実現させるための記述を見ていきましょう。ここからはユーザーマニュアル 10.3.4 を同時に見てください。
ステートマシンを記述するためのレジスタとして、以下の4つがあります。
- LENGTH / BRANCH
- ステートの長さ(持続するクロック数)や条件分岐先を指定。
- OPCODE
- こまごまとした設定。例えば割り込みとか。
- OUTPUT
- 各ステートでの出力を記述
- LOGIC FUNCTION
- 条件分岐の際に使う論理関数とオペランドを記述
LENGTH / BRANCH レジスタ
このレジスタは DP においては条件分岐先を記述し、それ以外ではステートを何クロック持続させるかを記述します。今回は S0, S2 ともに 1 としましょう。よってこの値は 0x01 となります。
また、 S1, S3 では論理関数が 1 の時に S2 へ、0 のときは S0 へ遷移するため、BRANCHON0 は 0 を、 BRANCHON1 は 2 を指定すればいいですね。さらに今回はループによって何度も遷移するため、7bit 目を 1 にしておきましょう。よって 0x90 となります。
OPCODE レジスタ
色々ありますが、今回は DP では 1 に、それ以外は 0 にしておけばいいです。
OUTPUT レジスタ
出力値をそのまま書きます。今回は CTL0 を出力ポートとして使用しましょうか。
LOGIC FUNCTION レジスタ
AND 関数を使い、またオペランドに IN0 と IN1 を取りますね。それぞれ RDY0 と RD1 を使うことにします。なので LFUNC ビットは 00、TERMA と TERMB はそれぞれ 0 と 1 を指定します。
レジスタ全体
以上を表にまとめます。
S0 | S1 | S2 | S3 | |
---|---|---|---|---|
L/B | 01 | 90 | 01 | 90 |
OP | 00 | 01 | 00 | 01 |
OUT | 00 | 00 | 01 | 01 |
LF | 00 | 01 | 00 | 01 |
これをどうやら指定のメモリ番地に代入すればいいですね。ではひとまずまとめた配列を作っておきましょう。構造はユーザーマニュアルの Table 10-7 を参考にしています。このような配列を Waveform Descriptor と言います。 Waveform Descriptor は 0 から 3 の4つ作ることが出来、それぞれ Read や Write で動作を変えることが出来ます。今回は Write をトリガーとした動作にする予定です。動作は1種類しか必要ないので、 Descriptor 0 に記述します。のこりはゼロで埋めておきましょう。
const char WaveData[4 /* num of Descs */][4 /* num of Regs */][8 /* max. num of States */] = {
{ // Descriptor 0
{0x01, 0x90, 0x01, 0x90}, // Length/Branch
{0x00, 0x01, 0x00, 0x01}, // Opcode
{0x00, 0x00, 0x01, 0x01}, // Output
{0x00, 0x01, 0x00, 0x01}, // LogicFunc
},
};
コピー先のアドレスは 0xE400
ですが、前回使ったヘッダ内で GPIF_WAVE_DATA
という名前がついているので、こちらを使ってマイコン起動時にコピーするようにします。
volatile char *dest = &GPIF_WAVE_DATA;
const char *src = (const void *)WaveData;
for(unsigned int i = 0; i < 4 * 4 * 8; i++) {
dest[i] = src[i];
}
...とまあ、こんな感じで初期化を行っていきます。記事が長くてゴチャゴチャしてきたので、一旦ここで切ります。実際の初期化記述は後半へ続く。