会社案内 (近況 2008/04)
先月 翌月CRC 計算の高速化は実は非常に困難だということが判明した。開発当初は「タ イミングが間に合わなかったら pipeline 化すればいいじゃんアハハン」と安 易に考えていたが、良く考えるとそれでは実用にならない。pipeline 化して も最終段の結果がわかるまで次の計算を開始できないので、n 段にわけたら最 終結果を求めるのに n clk かかってしまう。つまり n 個の TLP をバッファ しておき、n clk かけてゆっくり処理する必要がある。原理的には可能だがメ モリ資源が大量に必要だし回路が複雑化する。
Web を漁ったところ、fast-crc という pipeline 化された CRC 回路を opencores.com でみつけた。が、よく調べたところ、この回路は複数の TLP を並列処理する場合にしか throughput が出ないことが分かった。要するに上 記の方法で作った pipeline が複数同時に動くだけの回路だった。
さらに Web を漁り続けたところ、今度は本当に使えそうなアルゴリズムをみ つけた ("Pipelined Cyclic Redundancy Check (CRC) Calculation", M. Walma, IEEE Computer Communications and Networks, 2007)。論文が有料 だったのでよほどのことがない限りここで読むのを諦めるわけだが、よほどの ことなので 35 ドル払って買ってみた。
アルゴリズムの原理をちゃんと理解するにはガロア拡大体とかの算数の知識が 必要だが、原理はわからなくともアルゴリズムが正しいことを信じて実装して みることは可能そうだった。実際エミュレータを書いてみると正しく計算でき ているように見える。ガロア恐るべし。
アルゴリズムの概要:
実装したらほんとに動いた。これで pipeline 本数と latency (pipeline delay) さえ調節すれば、入力のビット幅がどんなに大きくなっても 125MHz で動かせるようになった。ただしロジックはかなり多量に消費する (pipeline 1 本に LE を 3k 個くらい)。
Altera は Arria GX で x8 をサポートしていない。その理由がもし「CRC 計 算のタイミングが間に合わないから」というだけなら、このアルゴリズムを使 って x8 PCI Express コアを Arria GX 上で動作させられるかも知れない。
x4 向けの拡張は全部実装したつもりなのに全然動かない (ホストから認識さ れない)。コアから送った TLP に対して Nak が返ってきてしまう。コアが送 出している TLP の内容をよくみると、LCRC が間違っていた。ロジックの間違 いではなく、64-bit 入力では CRC 生成回路のタイミングが厳しいために間違 うようだ。ついに開発当初からの懸念が現実 のものにっ! どうする? 待て次号!
レーン幅が増えると x1 では必要なかった機構があちこちに必要になる。 予想以上に面倒だ。例えば:
時間軸 ------------------------> lane3H b3 lane2H packetA b2 packetB lane1H の末尾 b1 の先頭 lane0H b0 lane3L aN b7 lane2L aN-1 b6 lane1L aN-2 b5 lane0L aN-3 b4
これを 64bit@125MHz PIPE interface (8byte/clk) で受けるので、packet A と B が一部分重なって届く clock cycle の後ろに 1clk 挿入して、A と B を分離する必要がある。
時間軸 ------------------------> lane3H 0 b7 lane2H packetA 0 b6 packetB lane1H の末尾 0 b5 の先頭 lane0H 0 b4 lane3L aN b3 lane2L aN-1 b2 lane1L aN-2 b1 lane0L aN-3 b0
Rx に届いたデータをいったん無条件にバッファして、そこから TLP の切れ目 までを読み出す、という方法で TLP の分離を行うことにした。バッファが溢 れたら正しく動かないわけだが、まぁバッファの大きさに余裕をみておけば大 丈夫だろう。いま使っているホスト (chipset) では最大で 50 word くらいし か溜らないようなので、バッファは深さ 8-bit (256 word) あれば充分。
x1 の target 機能がだいたい動くようになったので、本日より x4 の開発に 突入。master 機能 (DMA) は x4 の target が出来てから作ろう。
幕張メッセで開かれているボードコンピュータ展に行ってきた。コア開発を始 めるにあたってアドバイスを頂いた内藤さんに挨拶した。あっちも小さな会社 のようだが、製品が豊富で広報活動もしっかりしていてすげぇ。
以下のようにいろんなところでハマって結局 1 週間かかったが、Memory Read/Write が動くようになった。
Flow Control Credit の大小比較演算を間違えていた。片方が roll over し た場合でも正しい結果が得られるためには、2 の補数の減算で判定しては駄目 だった。unsigned(CL) から unsigned(CR) を減算して、結果の MSB が 0 だっ たら OK、というのが正しい判定方法。
仮に signed の大小比較 (つまり if signed(CL) > signed(CR) then) で 判定した場合には CL ≥ 128 かつ CR < 128 のときおかしくなる。 unsigned の大小比較 (つまり if unsigned(CL) > unsigned(CR) then) では CL < 128 かつ CR ≥ 128 のときおかしくなる。
さらに今まで PLDA のコアで使っていたソフトウェア (デバイスドライバとユー ザライブラリ) を自作コア向けに変更して、テストコードが一部動くようになった。 テストコードを使って PIO write の速度を測ってみた:
without write combining, 2B burst. kawai@gb2[24]>./hibtest 13 1 # hib[0] PIO write (host -> HIB) size: 256 DMA read: 1.161963 sec 68.849009 MB/s size: 512 DMA read: 1.146817 sec 69.758298 MB/s size: 1024 DMA read: 1.139276 sec 70.220033 MB/s size: 2048 DMA read: 1.135452 sec 70.456536 MB/s size: 4096 DMA read: 1.136034 sec 70.420427 MB/s with write combining, 64B burst. # hib[0] PIO write (host -> HIB) size: 256 DMA read: 0.453611 sec 176.362513 MB/s size: 512 DMA read: 0.438551 sec 182.418842 MB/s size: 1024 DMA read: 0.431044 sec 185.595858 MB/s size: 2048 DMA read: 0.427309 sec 187.218133 MB/s size: 4096 DMA read: 0.438372 sec 182.493450 MB/s PLDA core, write combining, 64B burst. kawai@gb2[6]>./hibtest 13 1 # hib[0] PIO write (host -> HIB) size: 256 DMA read: 0.453607 sec 176.364089 MB/s size: 512 DMA read: 0.438513 sec 182.434811 MB/s size: 1024 DMA read: 0.431037 sec 185.598938 MB/s size: 2048 DMA read: 0.427364 sec 187.194006 MB/s size: 4096 DMA read: 0.438373 sec 182.492954 MB/s
PLDA のコアとだいたい同じ速度が出ている。 転送内容が正しいかどうかについてはまだちゃんと確認できていないが w
ソースコードの場合分けだとか文字列マクロ機能が VHDL の仕様にもいちおう あるにはある (if generate とか constant とか) が、きわめて貧弱なので嫌 気がさして、代わりに cpp を使うことにした。例えばこんな記述:
entity ifpga is port ( #if (SIMTYPE > 0) // simulation only i_rx : in std_logic_vector(NLANE*18-1 downto 0); o_tx : out std_logic_vector(NLANE*18-1 downto 0); #endif
をしておいて、合成前に cpp -P を通して普通の VHDL に変換する。変換は make でやれば大した手間ではない。
SIMTYPE = 1 # 0:hardware 1:phy 2:dll 3:tl 4:app CPP = cpp -P -DSIMTYPE=$(SIMTYPE) VHDL = hoehoe.vhd auau.vhd VHDL0 = $(patsubst %.vhd,%.vhd0,$(VHDL)) all: $(VHDL) %.vhd: ./templates/%.vhd0 $(CPP) $< -o $@
Configuration Read/Write を扱えるようになったので、次は Memory Read/Write。特に Memory Read の Completion には今までサボっていた面倒な規則の実装が必須。さらに payload 先頭と末尾の 4-byte word に関しては byte enable も考慮が必要だった。めんどくせー
細かいとこがいろいろおかしかったが、地道に直して、ついにホストから PCI device として認識されるようになった! /sbin/lspci でちゃんとリストされ ている。カンドーした。
上流からの request に対する completion を返すには、completion TLP の生 成の他にもいろいろやるべきことがあった:
など。結局ひととおり作るのに 4 日かかった。Completion と Configuration Register を実装したので、Configuration cycle が正しく動けばホストの boot 時に認識されるはずだが、実際は boot しない。なんでー?
上流からの TLP を受け取れるようになったので、次はそれに返事を返す機能、 つまり Completion パケットの送出機能を実装する。仕様を読んだところ、 Completion TLP はデータの切れ目 (boundary) やサイズにいろいろ決まりご とがあって、作るのがひじょーに面倒だということがわかった。
Completion を生成するために必要な情報:
fmt & type : TPTYPE_Cpl/TPTYPE_CplD tc : 受け取った TLP の tc。 td : 0 (当面 ECRC はつけない)。 ep : 0 (当面 data poisoning は行わない)。 attr : 受け取った TLP の attr。 completer id : 初めて CfgWr が来たときに自分の BDF を記録しておいて、以降はそれを返す。 cpl status : CPLSTAT_SC (常に成功)。 bcm : 0 (only for PCI-X. always 0 for PCIe). requester id : 受け取った TLP の requester id。 tag : 受け取った TLP の tag。 length : 受け取った TLP の length。 byte count : 残りの byte 数。1 回の completion で全部返すので常に 0。 lower address : 受け取った TLP の address + 0 if 1st DW be="1111" + 1 if 1st DW be="1110" + 2 if 1st DW be="1100" + 3 if 1st DW be="1000"
Completion の満たすべき規則:
Configuration Read/Write の completion は payload 長さがそれぞれ 4byte, 0byte で固定なので、比較的簡単。まずはこれらを実装する。