Fluentd対応MIDIキーボードを作ってみた

Fluentd Advent Calendar 24日目の記事です。

家にあるMIDIキーボードからMIDI信号をひろってFluentdにとばすという、誰得な工作をした。CPUやOSを使わず、MIDI信号のデコードからTCP接続、FluentdのMessagePackエンコードまで、すべてハードウェア実装なのだ。

まずは[デモ動画](http://youtu.be/SEr9_NqvpBM)をどうぞ：

![Screen Shot 2014-12-24 at 14.51.35.png](https://qiita-image-store.s3.amazonaws.com/0/38290/36dcfea9-6b1f-fa2f-6075-5b17d2557551.png)
[MIDI keyboard + DE0 + Fluentd demo](https://www.youtube.com/watch?v=SEr9_NqvpBM)

MIDIキーボードを叩くと、Mac上のFluentdにMIDIメッセージが送られ、Fluentdのログとして表示されてるのがわかる。以下、このデモの中身を解説したい。

## MIDI→DE0→WIZ→Fluentd

このデモの構成はこんな感じ：

![Screen Shot 2014-12-24 at 15.12.06.png](https://qiita-image-store.s3.amazonaws.com/0/38290/01dc91ea-0c2f-a171-32dc-095260adfff2.png)

以下、それぞれのコンポーネントの役割を見ていこう。

### MIDI信号のデコード

MIDIキーボードから送られてくるMIDI信号のデコードは[2年前に作ったMIDIデコーダー](http://d.hatena.ne.jp/kazunori_279/20121215/1355580932)をそのまま使った。MIDIインタフェースを介してFPGAボードの[Terasic DE0](https://www.terasic.com.tw/cgi-bin/page/archive.pl?No=364) のGPIOポートで信号を受信、[Verilog HDLで書いたデコーダー](https://github.com/kazunori279/hdllesson/blob/master/SynthTest/src/MIDI.v)でMIDIメッセージに変換する。鍵盤を押したり離したりするだびに、3バイトのMIDIメッセージがDE0上のバッファに入る動きだ。このあたり詳しくは上述のページを参照のこと。

![IMG_20141224_142035.jpg](https://qiita-image-store.s3.amazonaws.com/0/38290/72d7bff9-b76a-9d1f-2d2b-58ffef1b875f.jpeg)

この写真で手前にある赤い色のボードがMIDIインタフェースで、奥にあるDE0のGPIOにつながっている。

### WIZnet W5300でTCP接続

問題は、このMIDIメッセージをどうやってTCP/IPでFluentdに送るかだ。RasPiを使えば簡単だろう...（というか、RasPiにFluentdを載せればよい）。しかし！ ここはハードウェア実装でないと面白くない。

TCPスタックを実装した[商用のIPコア](http://www.slideshare.net/funadasatoshi/20140310-fpgax)を購入したり、[OpenCoresにあるそれっぽいHDL](http://opencores.org/project,tcp_socket)をがんばっていじれば、TCPのハード実装も可能だが、どちらの方法もハードル高い。そこで手っ取り早く、[WIZnet W5300](https://strawberry-linux.com/catalog/items?code=36002)を使うことにした。上の写真の右側に写ってるボードだ。

WIZについて詳しくは前に書いた[こちらのエントリ](http://qiita.com/kazunori279/items/287f956009fe022f1e33)を見てほしい。これはハードウェアベースのTCP/IPプロセッサで、

- CPUもOSも使わずお手軽TCP/IP通信できる
- 1個2000円くらい。安い
- TCP、UDP、その他もろもろサポート。リスナもクライアントもOK
- 16/8bitバスでつないで使う
- 最大80Mbpsで動くけど、最大8本のソケットしか使えない

という製品。実用性はともかく、とりあえずTCPより上のレイヤをハードウェアで直接いじりたい！ という人におすすめのボードと言えよう。

デコードしたMIDIメッセージをMessage Packにエンコードして、8bitバスを通じてWIZに渡すと、いい感じにMac上のFluentdに送ってくれる。このあたりの詳細は後述。

### MacのFluentdで受ける

Mac上のFluentdは、ほとんどデフォルト設定のままだ。`td-agent.conf`に

```
<source>
  type forward
</source>
```

と書いておいて、ポート24224でmsgpack形式でイベントログを受信できるようにしておく。

## SynthesijerでJavaからHDL生成

さて、キモとなるのは「MIDIメッセージをmsgpackでエンコードしてWIZ経由でTCP送信する」ところ。このロジックの記述には、みよし師匠作の高位合成ツール[Synthesijer](http://qiita.com/kazunori279/items/4951ca5f6164040878ce)を使った。というか、師匠がすでに[SynthesijerでWIZを動かすサンプル](http://wasa-labo.com/wp/?p=112)を作っていたので、それを俺がごにょごにょ改変しただけである。

[高位合成](http://en.wikipedia.org/wiki/High-level_synthesis)とは、ハードウェア回路の実装に用いられるハードウェア記述言語(HDL)を、高級言語から生成する技術のこと。NECの[CyberWorkBench](http://jpn.nec.com/cyberworkbench/)などは高位合成ツールの代表例で、C言語のようなソフトウェア言語を用いてハードウェアのふるまいを記述するとHDLを生成してくれる。HDLをすべて手書きするハードウェア開発は、あまりに低レベル過ぎて実装も検証も大きな労力を必要とするが、高位合成を使えばコーディングもデバッグもかなりラクできる...らしい。

CyberWorkBenchのような商用の高位合成ツールは恐ろしく高く、個人にまったく手が届かない。一方で、Synthesijerはなんとオープンソース！ AWESOME！しかもCじゃなくてJava（のサブセット）で書ける！ というわけで、FPGAで遊びたい人は皆いますぐSynthesijerをダウンロードしよう。

### Sjrの動き

Synthesijer（長いので以下sjr）を使うと、例えばWIZにデータを書き込むのも[こんな感じ](https://github.com/kazunori279/hdllesson/blob/master/SynthTest/src/WIZ830MJ_Test.java#L108)で記述できる。

```
	private void write_data(int addr, byte data) {
		wiz830mj.address = addr;
		wiz830mj.wdata = data;
		wiz830mj.cs = true;
		wiz830mj.we = true;
		wait_cycles(3);
		wiz830mj.we = false;
		wiz830mj.cs = false;
	}
```

これはつまり、

- WIZのバスに書き込み先アドレスとデータをセット
- WIZのcsとwe（書き込み信号）をtrueに
- 3クロックじっと待つ
- csとweと戻す

という動き。わかりやすい！ HDLはすべてがパラレルな並行世界でソフトウェアエンジニアにはかなりとっつきにくいけど、sjrを使えば手続き脳な俺達でもJavaの文法でやりたいことを書き下せる。

まるでCPUがあるみたいだけど、CPUはない。Javaで書いた手続きを順次実行してくれるシーケンサーのデジタル回路をsjrが自動生成してくれるのである。ちなみに、今回のデモではDE0を50MHzクロックで動かしてるので、上記の各行は数10nsきざみで動く。OSが勝手に割り込みしたりしない。自分が書いたJavaコードがナノセカンド単位できっちり動いてくれるの快感だ。

sjrのJavaコードを書いたら、sjrのコンパイラを実行してHDL（Verilog HDLかVHDL）を生成する。例えば今回の例では[こんなふうなやたら長いVerilog HDLコード](https://github.com/kazunori279/hdllesson/blob/master/SynthTest/src/WIZ830MJ_Test.v)が生成される。あとは、このHDLを元にAltera Quartus等のFPGA開発ツール上で回路を合成、DE0に読み込ませて動かす。

![Screen Shot 2014-12-24 at 16.40.31.png](https://qiita-image-store.s3.amazonaws.com/0/38290/1ca89172-57c1-db8d-0c28-4e8337a639c9.png)
Altera Quartusで回路合成

### SjrでWIZのコントロール

このsjrを使ってTCPクライアントを書くと、以下のような感じになる（コード全体は[ここ](https://github.com/kazunori279/hdllesson/blob/master/SynthTest/src/WIZ830MJ_Test.java#L264)）。

```
	// added by kaz
	private void tcp_client(int wiz_port) {

		// open TCP socket
		int wiz_port_offset = wiz_port << 6;
		write_data(Sn_MR0 + wiz_port_offset, (byte) 0x01); // use alignment
		write_data(Sn_MR1 + wiz_port_offset, Sn_MR_TCP); // TCP mode
		write_data(Sn_PORTR0 + wiz_port_offset, (byte) 0x13); // src port 5000
		write_data(Sn_PORTR1 + wiz_port_offset, (byte) 0x88);
		write_data(Sn_CR1 + wiz_port_offset, Sn_CR_OPEN); // open
		while (read_data(Sn_SSR1 + wiz_port_offset) != Sn_SOCK_INIT)
			;

		// set dest IP and port
		write_data(Sn_DIPR0 + wiz_port_offset, (byte) 192);
		write_data(Sn_DIPR1 + wiz_port_offset, (byte) 168);
		write_data(Sn_DIPR2 + wiz_port_offset, (byte) 1);
		write_data(Sn_DIPR3 + wiz_port_offset, (byte) 110);
		write_data(Sn_DPORTR0 + wiz_port_offset, (byte) 0x5e); // dest port
		write_data(Sn_DPORTR1 + wiz_port_offset, (byte) 0xa0);

		// connect
		write_data(Sn_CR1 + wiz_port_offset, (byte) 0x04);
		while (read_data(Sn_SSR1 + wiz_port_offset) != Sn_SOCK_ESTABLISHED)
			;
```

なんとなく動きをイメージできるだろう。要するにWIZのレジスタを通じていろいろコマンドを送りながら状態遷移を管理するのだけど、まずはTCPソケットをオープン、ステータスがSOCK_INITになったらFluentdが動くMacのIPアドレスとポートを指定してCONNECTコマンドを送信、SOCK_ESTABLISHEDになるのを待つ、という流れ。ソフト開発のようなノリだ。

### MIDIメッセージをmsgpackにエンコード

MacとTCP接続が確立したら、MIDIメッセージをmsgpack形式でエンコードしてWIZ経由で送る。送る内容は、バッファ上で以下のように書いて組み立てている。

```
	private void buildMidiMsg() {

		// send migi msg with msgpack ["midi", 0, {"msg": <<midi msg>>}]
		buffer[0] = (byte) 0b10010011; // array with 3 elements
		buffer[1] = (byte) 0b10100100; // str with 4 bytes
		buffer[2] = 0x6d; // "midi"
		buffer[3] = 0x69;
		buffer[4] = 0x64;
		buffer[5] = 0x69;
		buffer[6] = (byte) 0; // int 0
		buffer[7] = (byte) 0b10000001; // map with 1 pair
		buffer[8] = (byte) 0b10100011; // str with 3 bytes
		buffer[9] = 0x6d; // "msg"
		buffer[10] = 0x73;
		buffer[11] = 0x67;
		buffer[12] = (byte) 0b10010011; // array with 3 elements
		buffer[13] = (byte) 0xcc; // midi status
		buffer[14] = (byte) (midi_msg >> 16);
		buffer[15] = (byte) 0xcc; // midi data 1
		buffer[16] = (byte) (midi_msg >> 8);
		buffer[17] = (byte) 0xcc; // midi data 2
		buffer[18] = (byte) midi_msg;
		length = 19;
	}
```

MIDIメッセージは1バイトの「MIDIステータス」と2バイトの「データ」の合計3バイトで構成されていて、上記コードではその3バイトをarrayに入れて、`msg`というプロパティでカプセル化している。タイムスタンプはとりあえず0にしてある（システムクロックを実装する時間がなかった...）。

上記のメッセージがMacのポート24224に届くと、以下のようなイベントログが記録される。

```
2014-12-24T14:21:32+09:00       midi    {"msg":[140,59,64]} 
```

動いた動いた。yaay!! FluentdクライアントのFPGA実装という夢がかなって感無量である。

これは誰得か？ このデモはクロック50MHzだし速さを追求してないので実用性は全然ない。でも、CPUもOSも使わずにそこそこ複雑なアプリロジックをハードウェア実装できる、しかも1万円くらいのFPGAボードとオープンソースの高位合成ツールを使ってお手軽に、って状況になりつつあるのは、じつはdisruptive innovationなんじゃないかと思う。

## さらなる野望

しかし本当は、WIZのTCPリスナ機能を使ってKVSとかストリーム処理とかやってみたかった...のだが、なぜだか現状ではうまく動いてなくてあきらめた。年末休みを使ってもうちょっとがんばってみたい。あとMQTTもやってみたい！

今回書いたコードは[GitHub](https://github.com/kazunori279/hdllesson/tree/master/SynthTest)に置いておいた。