WIZnetのTCPプロセッサW5300を載せたボードWIZ830MJがくさるほど届いたので、もったいないからなんとか動かそうとしている。
いろいろ試行錯誤の結果、Terasic DE0上にQsysでNios IIプロセッサを作り、W5300とつないでTCPリスナーを作るところまで動いたので、その手順をざっとまとめておく(disclaimer: ハード開発については素人なので、間違ってる部分も大いにあるかもしれない)。
W5300について
W5300は、WIZnetがけっこう昔に開発したハードウェアベースのTCP/IPプロセッサ。CPUやOSが不要なので、Linuxの載らない小さなマイコンにTCP通信機能を持たせるためによく用いられている。ストロベリー・リナックスで評価ボードを買うと1個2000円くらい。主要スペックは以下のとおり:
- TCP, UDP, ICMP, ARP, IGMP, PPPoEをハードウェア実装
- 8または16ビットのバスインタフェースを備える
- DMAに対応し、最大80Mbpsで動作
- 最大8本のソケット(リスナまたはクライアント)に対応
- 128KBバッファ搭載
- 10BaseT/100BaseTXコネクタ装備
ハード実装だから仕方がないけど、ソケットは8本しか使えない。また下位製品のW5200/W5100はSPIでお手軽にマイコンとつなげられる一方で、W5300は速度重視のため16ビットのバスインタフェースでつなぐ必要がある。
DE0とWIZ830MJの接続
まずは、WIZ830MJに付いている2つの28ピンコネクタをどうやってDE0に物理的に結線するかを考えた。
これを、aitendoで売ってるピンヘッダ接続ケーブルを使ってひたすら一本ずつDE0のGPIOにつなげてく。こんな感じ:
こんなにたくさんケーブル挿して、一本でも断線しててデバッグではまったりするのは嫌だったので、念のためWIZに繋いだところでテスターで一本ずつ導通確認しておいた。
DE0とWIZ間のマッピングはこんなふうにメモっておいて、あとのピンアサイン時に使った。これも1つでも間違えるとハマりそう。
とりあえずLチカ
このバスをどんなふうに使うのかはさておき、とりあえずは電源を入れてLEDがチカチカするところまで確認する。WIZのピンから出てるLED用の出力(Ethernetのリンク状態やTx/Rx動作など)をそのままDE0のLEDにつなげるVerilog HDLを書く。
assign WIZ_BIT16EN = 1'b1; // 16 bit mode
assign WIZ_RESET_N = RST_N; // reset
assign LED9 = !WIZ_LINKLED_N;
assign LED8 = !WIZ_TXLED_N;
assign LED7 = !WIZ_RXLED_N;
assign LED6 = !WIZ_COLLED_N;
assign LED5 = !WIZ_FDXLED_N;
assign LED4 = !WIZ_SPDLED_N;
assign LED3 = !WIZ_ACTLED_N;
assign LED2 = 1'b0;
assign LED1 = 1'b0;
assign LED0 = 1'b0;
さらにQuartusのPin Plannerでは、さっきメモったマッピング通りにLocationを設定、またすべてのピンのI/O StandardをW5300の仕様に合わせて3.3-V LVCMOSにセットする。
(Pin Plannerの画面ってなんかカッコいい...)
ここまでできたらDE0の電源を入れて、Quartusでコードをビルド、Programmerで流してみる。イーサケーブルをWIZのコネクタに挿したらDE0のLEDがチカチカして、どうやら壊れずに動いてるらしいことを確認。リセットボタンも動いている。
W5300のバスの使い方
さて、最大の難関はW5300のバスをうまく使いこなせるかである。生まれてこのかたバスなんて使ったことない。My First Bus! ほんまにうごくんかいな。。
W5300のドキュメントを見る限りは、とてもシンプルそうなバスではある。
まあつまり、
- 10ビットの
ADDR
に読み書きしたいW5300のレジスタのアドレスを渡して、 -
CS
(chip select) で「これから君と通信するよー」と伝えて、 -
RD
(read) で「データ読んでー」と伝えると、アドレスで指定したレジスタの値が16ビットのDATA
に載ってくる
という仕組みだ。図に書くとこんな流れ:
ここに書かれたtRD
やらtDATAs
やらで決められたタイミングを外れないようにする必要がある。今回、DE0のクロックは50MHz=20nsなので、例えばtRD
の最小値65nsを下回らないようにRD
は4クロック以上維持する、などなど。
W5300のレジスタ
W5300を使うには、とりあえずこのバスを経由したレジスタの読み書きさえできればよい。割り込み信号INT
やDMAを使うといろいろ高度なこともできるけど、hello worldしたい段階ではこれらは考えなくてもよい。TCP接続に必要なコマンド送信や状態管理はすべてレジスタの読み書きのみで行える。
例えば、8本のソケットそれぞれのTCPステータスを見るには、対象のソケットのSocket Status Register (SSR)レジスタをバスから読めばよい。仕様書にはその値が以下のいずれかのTCPステータスに対応するかが記載されている。
ホスト側では、この状態に応じて次に出すべきコマンドや、ソース/宛先IPアドレス等のデータを別のレジスタに書き込みして、W5300に仕事を依頼する。なるほど、原理的にはそんな難しくはなさそうだ。
QsysでNios IIを作る
その仕事の依頼方法には、以下の2つが考えられる。
- W5300の制御をすべてハードウェアで実装する
- W5300をバス経由でCPUにつなぎ、制御をソフトウェアで実装する
我が師匠のみよしさんは、仕事の合間にあっという間にハードウェア実装でW5300を動かしてしまっていた。プロこわい。しかもSynthesijerという、みよしさんお手製の高位合成ツール(高級言語からハードウェア記述言語を自動生成するやつ)を使って、あっさりJavaで書いてある。これからのハードウェア開発はこんなふうにソフトウェアプログラミングにどんどん近くなっていくのだろうなー。。
しかし師匠のコレをそのまんま使ったのでは修行にならないので、今回はオーソドックスに後者の方法で行くことにした。Quartusに付属のSoCツールQsysを使ってMIPSベースCPUであるNios IIをFPGA上に作成し、その上でCで書いたコードを動かして上述のようなW5300のレジスタの読み書きを行う。
これまでQsysもNios IIも使ったことがなかったのだけど、神本「FPGAスタータ・キットで初体験!オリジナル・マイコン作り」のおかげで、あまり引っかかることなくQsys上のNios II作成とLチカまでを進めることができた。This is the god's bookである。
この本を読みながら、QsysでNios IIやSDRAMコントローラを作り、クロック信号やAvalonバスをぽちぽちUI上でつないでいく。さらにQuartus付属のEclipseでCのコードを書き、Qsys上で適当に指定したメモリマップI/Oのアドレスにデータを読み書きすると、そのI/Oに繋いだLEDがチカチカしたりする。これは楽しいぞ。。Alteraにお金を払わずにここまでできるなんて。
まぁ、クロックは50MHzだし、無償版のNios IIにはキャッシュもパイプラインもないので激遅ではあるのだけど。SDRAMとの転送帯域を実測してみたら280KB/sだった。RAMなのに、GCEのLocal SSDより2500倍くらい遅いw
Avalon MM Slave TranslatorでW5300のバス制御
QsysではNios IIにAvalonというバスがつながっているのだけど、AvalonとW5300のバスはどうやってつなげよう? と、Qsysのライブラリをいろいろ探したら、どうやらAvalon MM Slave Translatorというやつを使えばいい感じに行けそうだ。詳細ドキュメントが見つからなかったので、ウィザード画面で指定するパラメータの最適構成を見つけるのに苦労したけど、以下のような設定で今のところいい感じの動きになっている:
タイミングまわりのパラメータは、とりあえずすべて5クロックにしといてあとで追い込むつもり。このTranslatorにwiz0
という名前を付けて、HDLの方では以下のようなコードでW5300と繋いだ。
nios2e u0 (
.clk_clk (CLK),
.reset_reset_n (RST_N),
.wiz0_address (WIZ_A),
.wiz0_write (wiz_wr),
.wiz0_read (wiz_rd),
.wiz0_readdata (wiz_readdata),
.wiz0_writedata (wiz_writedata),
.wiz0_chipselect (wiz_cs),
.wiz0_byteenable (wiz_byteenable)
);
wire [15:0] wiz_readdata;
wire [15:0] wiz_writedata;
wire [1:0] wiz_byteenable;
wire wiz_cs, wiz_wr, wiz_rd;
assign WIZ_CS_N = !wiz_cs;
assign WIZ_WR_N = !(wiz_wr && wiz_byteenable[0] && wiz_byteenable[1]);
assign WIZ_RD_N = !(wiz_rd && wiz_byteenable[0] && wiz_byteenable[1]);
assign WIZ_D = (!WIZ_CS_N && !WIZ_WR_N) ? wiz_writedata : 15'bz;
assign wiz_readdata = (!WIZ_CS_N && !WIZ_RD_N) ? WIZ_D : 15'b0;
こんな書き方でよいのか不明だが、、W5300のバスはトライステートなのでCS
とWR
もしくはRD
がイネーブルの時にだけリードやライトのデータを流す。それと、byteenable
がワード書き込みの時だけ対応するようにした。
W5300のバスにはbyteenableがないので、こういうときどうすればよいのだろう? と悩んだ。Nios II上のコードから(char *)
のポインタを使い、バイト単位で書き込みすると、byteenable
にはバイト単位書き込みのフラグが立つ。つまり、1バイトだけ更新したい。しかしW5300は1ワード(2バイト)単位でしか書き込みできない。なので、いちど対象ワードを読んで、そのうち1バイトのみ書き換え、ふたたびワード単位で書き戻すというread-modify-writeの動作が必要になる。これってI/O側でがんばってハード実装すべきなのかな? と識者の皆さんに聞いてみたら、たけむら師匠ととよしま師匠から「そういうのはI/O側で勝手にやらずにホスト側でやるのが常道」とのアドバイスをいただいた。というわけで、I/O側ではバイト単位書き込みには対応しないことにした。Cコードからは(unsigned short *)
のみで読み書きする。
このあたりのデバッグはQuartus付属のSignalTapなしでは不可能。もっとちゃんとしたデバッグ環境を整えるために、実機でなくシミュレーターを使おうかなとも考えたけけど、ひと通りのモデルを用意するのが面倒なのでとりあえず実機+SignalTapで。
Nios IIのEclipse上でNios II Application and BSP from Template
プロジェクトを作成、hello worldテンプレートを使って以下のようなコードを書いて、W5300をソフトリセットしてみた。
volatile unsigned short *p = WIZ0_BASE + 0x000; // W5300 base address + 0x000 register
*p = *p | 0b10000000; // setting the reset flag
うまくリセットされ、レジスタから読み込んだ値の内容もOK。SignalTapでも、想定どおりに動いてるっぽいことを確認した。
WIZnetのドライバコードを動かす
これでCコードからW5300のレジスタを自由に読み書きできるようになったけど、レジスタの仕様を読むと結構細々とした状態遷移管理やビット操作が必要そうで、すべてを自分で書くのは面倒そう。しかしありがたいことに、WIZnetのサイトからCで書かれたドライバコードのサンプルをダウンロードすることができる。
このドライバはどうやらXscaleマイコン用に書かれたもののようで、そのままではログ出力のprintfまわりが引っかかって動かない。Altera提供のalt_printfと差し替えたりちょこちょこ直したりして動いた。
これを使うと、TCPソケットの作成をとても簡単に書くことができた。
setSIPR({ 192, 168, 1, 250 }); // set source IP address
setGAR({ 192, 168, 1, 1 }); // set gateway IP address
setSUBR({ 255, 255, 255, 0 }); // set subnet mask address
setSHAR({ 0x00, 0x08, 0xDC, 0x00, 111, 200 }); // set source hardware address
という感じにソースIPアドレスやゲートウェイ、サブネットマスク、MACアドレス等をレジスタにセットして、main.c
にあるループバックテスト用の関数loopback_tcps()
を呼び出せば、指定したポート番号でTCPリスナーを作成できる。
loopback_tcps(0, 5000, data_buf, 0);
接続テストのため、PCからtelnetでつないでみる。
$ telnet 192.168.1.250 5000
Trying 192.168.1.250...
Connected to 192.168.1.250.
Escape character is '^]'.
hello
hello
world
world
適当な文字列を叩くとそのままエコーバックされてきた。いえーい、動いたぜ!!!
これからの野望
とりあえずCPU上のソフトウェアからW5300を制御してhello worldするという第一目標は達した。これからはTCPより上のプロトコルをソフトで実装し、さらにその一部をハード実装に移行する、、ってことをやってみるつもり。
ここまでに書いたドライバコード(WIZnetのドライバの修正版)やHDLコードはGitHubリポジトリに置いておいた。