Polyphony で SPI
Vivado HLS でいろいろ試していたのですが、結局、この手のは Python で書いて、Polyphony で Verilog に落とすことにしました。
polyphony のバージョンは 0.3.0 を使用。
GitHub - ktok07b6/polyphony: Polyphony is Python based High-Level Synthesis compiler. から 0.3.0 の branch を使用してください。最新だとエラーになる模様。
import polyphony from polyphony.io import Bit, Uint from polyphony.timing import clksleep, clkfence, wait_rising, wait_falling import pdb CONVST_PULSE_CYCLE = 10 CONVERSION_CYCLE = 40 @polyphony.module class SPIController: def __init__(self): self.sclk = Bit() self.sdo = Bit() self.sdi = Bit() self.convst_n = Bit(init=1) self.cs_n = Bit(init=1) self.dout = Uint(width=12) self.chout = Uint(width=3) self.din = Uint(width=16) self.data_ready = Bit() self.append_worker(self.main) def main(self): while polyphony.is_worker_running(): self.convst_n.wr(1) self.cs_n.wr(1) self.data_ready.wr(0) clkfence() self.convst_n.wr(0) clksleep(CONVST_PULSE_CYCLE) self.convst_n.wr(1) clksleep(CONVERSION_CYCLE) # starting ADC I/O self.cs_n.wr(0) sdo_tmp = 0 clksleep(1) for i in range(16): self.sclk.wr(0) sdi_tmp = 1 if (self.din() & (1 << (15-i))) else 0 self.sdi.wr(sdi_tmp) clksleep(1) self.sclk.wr(1) sdo_tmp = sdo_tmp<<1 | self.sdo.rd() self.sclk.wr(0) self.dout.wr(sdo_tmp & 0x0fff) self.chout.wr((sdo_tmp & 0x7000)>>12) self.cs_n.wr(1) clkfence() self.data_ready.wr(1)
これを polyphony で光合成。じゃなくて高位合成。
> ../bin/polyphony spi.py > ls polyphony_out.v polyphony_out_SPIController_spic.v spi.py
Vivado に入れてみる。
なんとなくできた。
hls::stream + axiu + SDSoC の実験
SDSoC (2016.2) のサンプルで pacektize して minmax を使うものがある。
Xilinx/SDSoC/2016.2/samples/platforms/zc702_axis_io/samples/stream
これに自分の stream を差し込んでみた。xilinx のサンプルは ap_axiu<32,1,1,1> を直接つかっていて hls::stream を使っていない。これだと不便だ。
SDSoC の sample をみると
samples/hls_if/hls_stream/
とちゃんと hls_stream を使ったものがある。なので、これをうまく差し込めれば自由に stream を使えることになる。でうまく組み合わせたのが、冒頭の絵になる。
で、これを差し込んだだけ?でうまくいくかというとさにあらず。hls_stream のサンプルでは
void bytestrm_dwordproc( hls::stream<uint8_t> &strm_out, hls::stream<uint8_t> &strm_in, uint16_t strm_len ) { //HLS: #pragma HLS interface axis port=strm_in #pragma HLS interface ap_fifo port=strm_in //HLS: #pragma HLS interface axis port=strm_out #pragma HLS interface ap_fifo port=strm_out
となっている。これだとうまくいかない。ap_fifo は恐らく Vivado HLS で fifo のインタフェースをつくってしまう。そして、SDSoC では stub で
#ifndef __SDSVHLS__ void bytestrm_dwordproc( unsigned char *strm_out, unsigned char *strm_in, unsigned short strm_len ) { // Prototype used by SDSoC to generate stub for hardware function // HLS function does not cross-compile with arm-xilinx*-gnueabi-g++ } #else
とつくっている。完全にダミールーチン。C++ 側のスタブを作るためだけにある。Vivado HLS の機能をフルに使おうとすると、g++ の用意する(最終的にコンパイラは g++だから。フロントエンドは llvm + clang)言語体系とうまくあわない。なので、このように
#ifdef __SDSVHLS__ Vivado HLS の関数 #else g++ の関数 #endif
と両方書いて、うまくその整合性を合わせるのは使う側の責任だ。で、ここの情報が不足している。簡単に破たんする。破綻すると opt が死んだり、謎のエラーが出たりする。
でもサンプルをよく見ると
//HLS: #pragma HLS interface axis port=strm_in
とコメントアウトされている。ありがとう。優秀なプログラマ。ヒントを残してくれてたんだね。ということで、ap_fifo ではなく axis を指定することにする。これで、完全にハードの世界になる。が、これで終わらない。
void my_p2p(hls::stream <ap_axiu<32,1,1,1> > & strm_in, hls::stream <ap_axiu<32,1,1,1> > & strm_out) { //#pragma HLS pipeline enable_flush #pragma HLS interface axis port=strm_in #pragma HLS interface axis port=strm_out //#pragma HLS interface ap_ctrl_none port=return // これが重要だった。
ソース上にすでにコメントで答えが書いてあるが、 ap_ctrl_none が必要だった。my_p2p の引数がすべてハードになってしまったために、C++ とのインタフェースを失ってしまったらしい。通常あるはずの C++ のインターフェスに追加する形で戻り値のインタフェースを追加する(のでしょう)ので、そのインタフェースがないと return のインターフェースが追加できずに合成時にエラーになる。
ということで、上の ap_ctrl_none を追加することでひとまず解決。
で、次のステップとして引数を増やして、stream が流れて行った後の統計情報などをとりたい。(実際にやろうと思っているのは width と height の取得)そこで、
void my_p2p(uint32_t *temp, hls::stream <ap_axiu<32,1,1,1> > & strm_in, hls::stream <ap_axiu<32,1,1,1> > & strm_out)
に変更したら、こんどは temp のインタフェースの offset が特定できずに opt の起動でエラー。opt と偉そうに書いているが何をしているかは不明。どうやら sds++ でコンパイルすると、インタフェースの情報を *.o の中に埋め込む(objdump でみると通常は見られないヘッダーがついている)。opt はそれをみて、どうやら tcl やら xci をつくる。その tcl と xci をみて vivado が IP コアを生成しているようだ。
上記の記述だと hls::stream が邪魔して temp の offset がわからないようだ。ということで、明示的に追加。この辺は SDSoC のサンプルになかったので完全に試行錯誤。
っていうか、ちゃんとサンプル用意してよ。これ、言語仕様なんだから。試行錯誤する時間が馬鹿らしい。
FPGA の部屋ありがとう。ここの情報と twitter の情報で突破で来た。
void my_p2p(uint32_t *temp, hls::stream <ap_axiu<32,1,1,1> > & strm_in, hls::stream <ap_axiu<32,1,1,1> > & strm_out) { #pragma HLS pipeline enable_flush #pragma HLS interface axis port=strm_in #pragma HLS interface axis port=strm_out #pragma HLS interface ap_ctrl_none port=return #pragma HLS interface s_axilite port=temp offset=0x0
こんな感じ。あとは自分で offset を考えて書けばいいんだね。あーこれでたぶん SDSoC 2016.2 がもっている uint16_t の issue (バグじゃねぇか?)も回避できそうだ。まぁ 2016.3 に移行すれば問題ないんだけど。
こんなに苦労したけど、すべて 2016.2 です。2016.3 でどうなっているかは不明。全部、フィックスしてたりして。あと、最終動作確認もまだ。
sds_alloc と CMA
Linux で sds_alloc をすると、どうも CMA の領域を使うらしい。CMA は .config で 256 に設定してある(Mバイト)。つまり、私のシステムでは 0x30000000 からなのだが、VRAM の領域をとることができない。連続して取れる領域の上限があるみたい。(たぶん Linux 側の問題。.config で変えられるかも、、、、)しかも、仮にとれたとしても、物理アドレスがわからない。
vdma を uio 経由で使おうと思ったら物理アドレスが必要なんだよね。
sds_mmap を使う。これは”物理アドレス”を直接使っちゃう。0x30000000 からマップしたら見事にシステムがこけました。どうやら 0x30000000 の先頭はシステム(Linux のカーネル?) が使っているらしい。CMA としてつかっているのか XILINX の Linux ドライバが使っているのかは不明。使っている領域をマップしようとしたらエラーになってくれればいいのに、、、、
今は強引に 0x38000000 からマップしてます。いずれ sds_alloc とバッティングするでしょう。物理アドレスとの整合性を /dev/fb なみにしてほしい。おそらく ioctl で用意しているとは思うけど、現時点ではユーザに見せていない。
SDSoC のプラットフォームと Vivado の階層
どうも、SDSoC 2016.2 は Vivado の階層に対応してないみたい。階層の stream の口を指定したプラットフォームを用意して SDSoC でコンパイルすると、エラーになる。意味不明のエラーでなんだかわらない。しかたがないので、階層の横に stream の FIFO かなにか”よけいな"IPコアを一個用意する予定。合成時に時間がかかっちゃうな、、、、Vivado の最適化で取り除かれるようにしたい。あーでも OOC つかっていると IP コアごとにコンパイルするから取り除かれないかも。
いろいろ障害はあるけれど
いろいろ障害はあったけど、stream とりわけ hls::stream が使えるようになると便利になる(個人的にはね)。C++ のテンプレートがばしばしつかえるのもよい(個人的にはね)。
こういった情報交換が母国語(日本語)でできるしあわせをかみしめつつ。誰かのお役に立てれば。
Yocto に関するメモ
Linux Kernel において .config がどのようにつくられるか?Yocto は特殊なことをしているようだ。Yocto 的には普通みたいだけど。
Linux の Kernel を構築するときよく使われる手法は次みたいな感じ。
make ARCH=arm xilinx_zynq_defconfig
昔は「make の使い方これでいいのかね?」と気持ち悪さを感じたけど、今は普通に使ってます。なんか手法が隠ぺいされていて気持ち悪いんだよね。で、どうなるかというと、これは Linux の掟で、ARCH で指定したアーキテクチャのディレクトリの configs の下に指定されたコンフィグレーションのひな形があるので、これをベースに .config を自動的に作る。まぁこれはこれで便利だからいいか。
上記の例の場合は arch/arm/configs/xilinx_zynq_defconfig をつかう。
ところが、Yocto ではどうも、これをつかってないみたい。bitbake で .coonfig だけをつくる命令は kernel_configme 。最初、なんでわざわざこんな前にしたのか疑問だったけど、Yocto ではもともと configure というコマンドがありこれはこれで、"Yocto 的なコンフィグしてね" コマンドなので、それとは意味的に分離して、"Linux 的な kernel の config を作ってね" という命令を追加したみたい。よく考えられている。
さて、で実行してみる。
bitbake linux-xlnx -c kernel_configme -f
xilinx_zynq_defconfig とは違う .config ができる。そして、defconfig に相当するファイルや指定が recipe を見ても見当たらない。そこで、meta/classes/kernel-yocto.bbclass に do_kernel_configme という関数があるので見てみる。日本語の資料が少ないから、どうしてこ~~~なる?というのがわからないのが Yocto の悪いところだな。その上、適当にやっているとできたりするから始末に悪い。
do_kernel_configme() { bbnote "kernel configme" export KMETA=${KMETA} if [ -n "${KCONFIG_MODE}" ]; then configmeflags=${KCONFIG_MODE} else # If a defconfig was passed, use =n as the baseline, which is achieved # via --allnoconfig if [ -f ${WORKDIR}/defconfig ]; then configmeflags="--allnoconfig" fi fi cd ${S} PATH=${PATH}:${S}/scripts/util configme ${configmeflags} --reconfig --output ${B} ${LINUX_KERNEL_TYPE} ${KMACHINE} if [ $? -ne 0 ]; then bbfatal_log "Could not configure ${KMACHINE}-${LINUX_KERNEL_TYPE}" fi
これをみると最終的には configme という関数を呼んでいて、引数に ${LINUX_KERNEL_TYPE} ${KMACHINE} になっている。実行して bbwarn で表示させてみると、それぞれ standard、 zynq だった。どうやら zynq-standard.scc というファイルから .config を作っているようだ。モー資料がなさすぎ。検索するよりソース読んだ方がいいという(昔なら当然か)オールドスタイルで中身がわかっていく。もちろん、本家には scc の説明はある!!
さて、Yocto では BSP(Board Support Package) という概念があって(あるらしいぞ)、scc というファイルを用意することで、フレキシブルにできるらしい。もう、らしい、らしい、ばっかりでどう作ったらいいかが判明するには結構時間がかかりそうだ。
これつくるのは結構大変そうなので(あと他の人の scc に自分の bsp あわせるのは大変かもしれないぞ)、別の方法で対処する。defconfig を bbappend の recipe に追加すればよい。上記の python のコードにもなにやら、${WORKDIR}/defconfig があれば、別の処理をするようになっている。ということで、ここに自分の defconfig を置いておくことにしよう。Yocto は適当に書いたらサーチしてくれるぞ。あと、最初はファイルをおかずにエラーにさせておくと、サーチパスを表示して(エラーにして)くれるので、適当に書いて後から追加することが出来る。
Yocto 万歳。スゲー複雑なシステムだけど。
あと、特定の git の blob(?) を指定する方法。SRCREV を使う。
SRCREV_linux-xlnx_4.4 = "89cc643affcce18122373fe7c78e780526243fdf"
local.conf にこのように書くか、recipe のほうだったら、単純に SRCREV を書けばよい。
Polyphony : Python によるコンパイラ
GitHub - ktok07b6/polyphony: Polyphony is Python based High-Level Synthesis compiler.
とりあえず本家から fork してみる。っていうか、本家って言っても隣で K 君が開発してるんだけどね。
なにやら SSA Book なるものを読んでいて、開発の時なんだかすごく怪しい(これかな?http://ssabook.gforge.inria.fr/latest/book.pdf)。妙なオーラが立っている。
Polyphony は Python の文法を解析する機構を使って verilog を吐き出すコンパイラだ。折角なのでロゴも作った。
Python のコードを解析するたびに visitor がよばれ、そこでアクションを書いていくという方式。XML でもそういうのあったなぁ。まぁ、その辺の仕組みは Sythesijer と似ている。いろいろ対応して(たとえばクラスに対応している!!)いて、だいぶ充実してきている。そろそろ製品化だね。
ということで、わたしも Lisp なんてやってないで(うぐ)、Python をやることにした。OCaml やりたかったけど、それは後回しになった(会社の方針として、、、、)。とはいえ、Python は初心者だぜ。
ざっくりソースも読んだ。あくまでざっくり。Polyphony はつぎのことをやっている
- Python のコードを IR の tree に変換(IR は Tiger book を参考にしているそうだ)
- IR の tree をいじって最適化
- IR を AHDL に変換。AHDL は独自中間言語。
- AHDL を verilog に変換
普通のコンパイラと違うところはレジスタのスピルとかがない。スケジュールして同時に実行することを考慮している。というところか。
そして、この間、Python の勉強をしていて Decorator というのを覚えた。ん?これでマクロ作れるんじゃないか?
関係ないけど pico-8 も気になる。
Min Caml コンパイラ その4
一向に Min Caml にたどり着かない。
今日は予備知識
The Essence of Compiling with Continuations
https://slang.soe.ucsc.edu/cormac/papers/pldi93.pdf
何やら有益なことが書いてあるらしい。どういうわけか、誰かが(大和谷さんという人)が和訳していたりする。
http://www.jaist.ac.jp/~kiyoshiy/writing/flanagan_EssenceOfCompilingWithContinuations.tex
図などは原文を見ないといけないけど、こういう資料はありがたい。
pdf 化しておこうhttps://drive.google.com/file/d/0B03DLc17PR0DOTF4a0JIelBsV1k/view?usp=sharing
まぁつまり CPS は無駄なので K正規化しましょうということらしい。その真意はまだわかっていない(わたしが)。