lp6m’s blog

いろいろかきます

Ubuntu on ZYBO Z7-20からFPGAで画像処理したPCam 5Cの映像をV4L2デバイスの映像として取得したい(失敗)

タイトルが随分ながくなってしまった。
前回Ubuntu on ZYBO Z7-20からPCam 5Cの映像を取得したい(成功) - lp6m’s blogでは、PCam 5CカメラをV4L2デバイスとして認識させ、画像を取得することができた。
せっかくMIPI経由でFPGA側に画像の信号があるので、HLSコアを用いて画像処理することはできないかと思った。
FPGAで画像処理しているカメラの画像がV4L2デバイスとしてOSに認識されて、簡単に画像取得できるとたぶんかなり嬉しい。
途中まで成功して、途中からは失敗したのでとりあえずログをまとめる。

やりたいこと

やりたいことの概略図は以下のようになる。
f:id:lp6m:20181022155350j:plain
元々のプロジェクトのFrame Buffer Write IPをVDMAに差し替えて、HLSコアを挿入するだけ。
元々のプロジェクトではYUYV形式でAXI4-StreamのDataWidthは16bit。
とりあえず情報を8bitに落とすHLSコアをつくって、1画素あたり8bitの情報を転送したい。

元のVivadoプロジェクトの確認

※元プロジェクトにはMIPI CSI2RX Subsystem IPが含まれており、LogiCore IPライセンスがないと合成やビットストリーム出力をすることができません。

1. DigilentgithubリポジトリGitHub - Digilent/Zybo-Z7-20-base-linuxからgit cloneする。

repo/vivado-libraryがgit submoduleになっている。回路で使用されているコアが別リポジトリに含まれており必要なので、以下コマンドでサブモジュールごとcloneする

git clone --recursive https://github.com/Digilent/Zybo-Z7-20-base-linux

Tools->Report->Report IP StatusからIP情報を更新、合成を行ってビットストリーム生成に成功することを確認。

Frame Buffer Write IPをVDMA IPに差し替え(ここまでは成功)

Vivado側の作業

元の回路にはFrame Buffer Write IPとよばれるIPが使われている。決まったビデオフォーマットにのSS2M(AXI4-Stream -> Memory Mapped)書き込みに特化したIPらしい。
Frame Buffer Write IPのフォーマットを変更してもDataWidthが8bitのフォーマットはなかったので、これをVDMAに差し替えて好きなデータ幅の転送ができるようにする。
とりあえずHLSコアを入れずに、VDMAに差し替えるだけを行う。
f:id:lp6m:20181022161304p:plain
axis_subset_converter_0とv_frmbuf_wr_0を削除して、VDMAを挿入
f:id:lp6m:20181022162054p:plain
この状態で合成・ビットストリーム生成を行う。
Export Hardwareを行い、Petalinuxプロジェクトにコピー。

Petalinuxプロジェクト側の作業

petalinux-config --get-hw-description <.hdfのあるディレクトリ>

HW情報をpetalinuxに読みこませる。
次にデバイスツリー(system_user.dtsi)を書き換える。FrameBuffer Write IPに対応する部分をコメントアウトして、amba_pl/video_capで指定しているDMAをFrameBuffer Write IPからVDMA IPに変更。
オリジナルとのdiffを載せておく。

10c10
< 		bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio root=/dev/mmcblk0p2 rw rootwait";
---
> 		bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
264c264
< /*&v_frmbuf_wr_0 {
---
> &v_frmbuf_wr_0 {
273c273
< };*/
---
> };
278c278
< 		dmas = <&axi_vdma_2 0>;
---
> 		dmas = <&v_frmbuf_wr_0 0>;
335,336d334
< 
<

これでPetalinuxのプロジェクトをビルドする。

petalinux-build

ここまでやってpetalinux-buildすると以前作成したカメラ画像取得プログラムが正常に動作した。

HLSコアの作成

YUYV16bitでは、1画素の情報は「YとU」あるいは「YとV」である(UとVは2画素に1つしか情報がなく、軽量)。いずれの場合もはじめの8bitを取り出す、すなわちYの値だけをとりだすコアを作成する。
Vivado HLSで1画素ずつの処理を行うサンプルが以下で公開されているので、それを参考にさせていただく。
github.com
yuyvyというモジュールを作成した。また、axiliteプロトコルしきい値を設定するといったことも将来的に行いたいので、char型のしきい値を入力として受けるようにした。
作成したコアのコードは以下リンク。
Extract Y 8bit data from YUYV 16bit data. · GitHub
実際に計算を行っているのは59行目の以下の部分だけ。YUYVからYの値を取り出して、valとのmaxを結果とする。特に意味はない。
コードの大部分はTLASTやTUSERなどの信号を扱うためのコード。解説されると簡単だけと自分で書くのは難しい・・

axis_writer.data = std::max((int)((axis_reader.data & 0xFF00) >> 8), (int)val);

高位合成の結果は以下の通りになった。まだ未熟で見方がわかってないが、特に問題はないらしい。。?
f:id:lp6m:20181022164438p:plain
Export RTLでIPとしてエクスポートを行った。

HLSコアを回路に挿入

Vivadoプロジェクトに戻り、IP CatalogにエクスポートしたHLS IPコアを追加する。
回路にHLS IPコアを挿入。
f:id:lp6m:20181022165240p:plain
HLSコアはs_axiliteプロトコルの入力があるので、メモリマップする必要がある。Address Editorでアドレスを割り当てる。
とりあえず、0x43c80000から64K割り当てた。
f:id:lp6m:20181022174510p:plain
この状態で合成・ビットストリーム生成を行う。
Export Hardwareを行い、Petalinuxプロジェクトにコピー。

Petalinuxプロジェクトの編集

先ほどと同様にpetalinux-config --get-hw-description <ディレクトリ>で新しい回路情報をプロジェクトにロードする。
ここから、Petalinuxプロジェクト側でデバイスツリーを書き換える。

Digilent Linux Kernelの話

注意しなければならないことが1つある。PetalinuxではLinuxカーネルの参照をgitリポジトリにすることができるのだが、
元のPetalinuxプロジェクトはDigilent Linux Kernelリポジトリの最新版ではなく、途中のコミットのものを参照している。
これを最新のものを参照するようにpetalinux-configで書き換えてしまうと動かなかった。
例えばMIPI CSI2RX Subsystemのデバドラは最新版使用しているPetalinuxが参照しているバージョンを見比べてみる。 旧版ではデバイスツリーのreset-gpioプロパティで指定したピンのリセットをxcsi2rxss_start_stream関数で行うが、最新版では行わない。
サンプルプロジェクトではMIPI CSI2RXSSのvideo_aresetnピンはGPIOに接続されており、これがリセットされないことでMIPI CSI2RXSSのSoft Resetがタイムアウトしてしまう。
(最新版のデバドラを使う場合は他の方法でvideo_aresetnをリセットしないといけなくなった)

まあ要は元々のままにしておけばいいのだけど、自分でカーネルをフォークする場合などは要注意。これで3日溶かした。

V4L2デバイスの話

HLSコアをV4L2のサブデバイスとして登録してくれるデバイスドライバxilinx-hls.cがDigilent Linux Kernelには存在する。
ソースコード
linux-digilent/xilinx-hls.c at 1496c680c6df2e3911feed13aa9663a851bf30e9 · Digilent/linux-digilent · GitHub
ドキュメント:
linux-digilent/xlnx,v-hls.txt at 1496c680c6df2e3911feed13aa9663a851bf30e9 · Digilent/linux-digilent · GitHub
これらを参考にして、デバイスツリーを編集する。

はじめの「やりたいこと」でも表している図の説明になるが、カメラ+画像処理がV4L2デバイス/dev/video0として認識されるのは、大体以下のような仕組み(のはずと理解したつもり・・・)

  • video_capが「xlnx,video」ドライバによって/dev/video0として認識される
  • xlnx,videoドライバはデバイスツリーで指定されている「エンドポイント」を辿って、サブデバイスを探す
  • PCam 5C、HLS IP、MIPI CSI2RX IPは各デバイスドライバによってV4L2サブデバイスとして登録される

 (各デバイスドライバは必要に応じてIPコアのリセットなどを適切に行ってくれる。ユーザアプリケーションはioctl()サービスコールを用いてドライバを呼び出すことができる)

バイスツリーの編集

バイスツリーの編集
そのまえに、Petalinuxで自動的に生成されるデバイスツリーをみる。(これはおそらくXilinx SDKで自動生成できるものと同じ)
petalinux-config --get-hw-descriptionでは回路に対応するデバイスツリーはなぜか生成されない。
とりあえずエラーがでてもいいので、petalinux-buildコマンドを叩くと回路に対応するデバイスツリーが更新される。
生成されるデバイスツリーは/components/plnx_workspace/device-tree/device-tree-generation/にある。pl.dtsiが回路に対応するデバイスツリー。
自動生成されたpl.dtsiを参考程度に貼っておく。
Dropbox - pl.dtsi
ユーザが記述するデバイスツリーsystem_user.dtsiはこの自動生成されたデバイスツリーに情報を付加・補完する形になっている。
それぞれのサブデバイスの接続情報がデバイスツリーにかかれているので、これらを編集する。
編集したデバイスツリーは以下。
Dropbox - system-user.dtsi
オリジナルのデバイスツリーとのdiffは以下の通り。

245c245
< 				remote-endpoint = <&hls0_in>;
---
> 				remote-endpoint = <&vcap_in>;
264c264
< /*&v_frmbuf_wr_0 {
---
> &v_frmbuf_wr_0 {
273c273
< };*/
---
> };
278c278
< 		dmas = <&axi_vdma_2 0>;
---
> 		dmas = <&v_frmbuf_wr_0 0>;
289,311d288
< 					remote-endpoint = <&hls0_out>;
< 				};
< 			};
< 		};
< 	};
< };
< 
< yuyvy_0: yuyvy@43c80000 {
< 		compatible = "xlnx,v-hls";
< 		reg = <0x43c80000 0x0024>, <0x43c80024 0xFFDC>;
< 		clocks = <&clkc 15>;
< 
< 		ports {
< 			#address-cells = <1>;
< 			#size-cells = <0>;
< 
< 			port@0 {
< 				reg = <0>;
< 
< 				xlnx,video-format = <XVIP_VF_YUV_422>;
< 				xlnx,video-width = <8>;
< 
< 				hls0_in: endpoint {
315,324d291
< 			port@1 {
< 				reg = <1>;
< 
< 				xlnx,video-format = <XVIP_VF_MONO_SENSOR>;
< 				xlnx,video-width = <8>;
< 
< 				hls0_out: endpoint {
< 					remote-endpoint = <&vcap_in>;
< 				};
< 			};
325a293
> 	};
367,368d334
< 
< 

HLS IPが1画素8bitのデータを出力する。これに相当するvideo-formatの値はXVIP_VF_MONO_SENSORのようだ。(UG934, 9ページ参照)

yuyvy_0: yuyvy@43c80000内のreg = <0x43c80000 0x0024>, <0x43c80024 0xFFDC>の意味がわからない。
ドキュメントによると

  • reg: Physical base address and length of the registers sets for the device.

The HLS core has two registers sets, the first one contains the core
standard registers and the second one contains the custom user registers.

らしいけど、standard registerとcustom user registerってなんのことだろう・・?とりあえずデフォルトの0x0024の範囲をstandard registerにしたけど。
HLS IPには64K割り当てたのでとりあえず合計0x100000になるようにした。

カーネルコンフィグの変更

元々のpetalinuxプロジェクトではHLS IPに対応するデバイスドライバが有効になっていない。

petalinux-config -c kernel

カーネルコンフィグを表示して、xlnx,hlsを有効(Build-In)にする。
f:id:lp6m:20181022175621p:plain

これでPetalinux側の作業も終わり。プロジェクトのビルドおよびFSBLとU-BOOTのビルドを行う。

petalinux-build 
petalinux-build -c fsbl
petalinux-build -c u-boot

BOOT.BINの作成

以下コマンドでBOOT.BINを作成する。

petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --fpga <bitファイルのパス> --u-boot --force

起動およびデバイス認識の確認

これまでどおりSDカードのFATでフォーマットした第1パーティションにimage.ubとBOOT.BINを,第2パーティションUbuntuのrootfsを入れる。
PCam 5Cを接続し、/dev/media0と/dev/video0が見えていればとりあえずデバイスドライバが正常にデバイスを登録できていることが確認できる。。

Ubuntuにはsudo apt-get install v4l-utilsでV4L-Utilsをインストールしておく。
ログイン後、sudo su -コマンドでスーパユーザに切り替えてから、V4L2サブデバイスのフォーマットの設定を行う。(本家と同じコマンド)

root@arm:~#
width=1920
height=1080
rate=15
media-ctl -d /dev/media0 -V '"ov5640 2-003c":0 [fmt:UYVY/'"$width"x"$height"'@1/'"$rate"' field:none]'
media-ctl -d /dev/media0 -V '"43c60000.mipi_csi2_rx_subsystem":0 [fmt:UYVY/'"$width"x"$height"' field:none]'

このコマンドで、デバイスツリーでフォーマットを指定していていなかった情報をセットしている。
V4L2デバイスが正しく認識、かつ正しくパラメータがセットされていることを以下のコマンドで確認する。

root@arm:~# media-ctl -p /dev/video0
Media controller API version 4.9.0

Media device information
------------------------
driver          xilinx-video
model           Xilinx Video Composite Device
serial          
bus info        
hw revision     0x0
driver version  4.9.0

Device topology
- entity 1: video_cap output 0 (1 pad, 1 link)
            type Node subtype V4L flags 0
            device node name /dev/video0
	pad0: Sink
		<- "43c80000.yuyvy":1 [ENABLED]

- entity 5: ov5640 2-003c (1 pad, 1 link)
            type V4L2 subdev subtype Sensor flags 0
            device node name /dev/v4l-subdev0
	pad0: Source
		[fmt:UYVY/1920x1080 field:none]
		-> "43c60000.mipi_csi2_rx_subsystem":1 [ENABLED]

- entity 7: 43c80000.yuyvy (2 pads, 2 links)
            type V4L2 subdev subtype Unknown flags 0
            device node name /dev/v4l-subdev1
	pad0: Sink
		[fmt:UYVY/1920x1080 field:none]
		<- "43c60000.mipi_csi2_rx_subsystem":0 [ENABLED]
	pad1: Source
		[fmt:Y8/1920x1080 field:none]
		-> "video_cap output 0":0 [ENABLED]

- entity 10: 43c60000.mipi_csi2_rx_subsystem (2 pads, 2 links)
             type V4L2 subdev subtype Unknown flags 0
             device node name /dev/v4l-subdev2
	pad0: Source
		[fmt:UYVY/1920x1080 field:none]
		-> "43c80000.yuyvy":0 [ENABLED]
	pad1: Sink
		[fmt:UYVY/1920x1080 field:none]
		<- "ov5640 2-003c":0 [ENABLED]

登録は成功しているようだ。特にHLS IPである43c80000.yuyvyのSourceのフォーマットは[fmt:Y8]となっており、1画素のデータが8bitのデータが出力されるということが登録されている。

カメラ撮影プログラムの修正

カメラ撮影プログラムは以前作成したプログラムを元に作成する。
HLSコアにはs_axilite経由の入力値があるので、これをセットする必要がある。HLSコアは先ほどのコマンドで/dev/v4l-subdev1に認識されていることが確認できた。
VIvadoHLSでExport RTLを行うと、IPコアのためのドライバが出力される。(/solution1/impl/ip/drivers/yuyv_v1_0/src)
この中にあるxyuyv_hw.hは以下の通り。

// CONTROL_BUS
// 0x00 : reserved
// 0x04 : reserved
// 0x08 : reserved
// 0x0c : reserved
// 0x10 : Data signal of val_r
//        bit 7~0 - val_r[7:0] (Read/Write)
//        others  - reserved
// 0x14 : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)

#define XYUYVY_CONTROL_BUS_ADDR_VAL_R_DATA 0x10
#define XYUYVY_CONTROL_BUS_BITS_VAL_R_DATA 8

ここから、物理アドレスの0x43c80000+オフセット0x10の位置に値を書き込めば、HLSコアの入力値valを設定できるということがわかる。
嬉しいことに、HLS IPのデバドラにはs_axilite経由の値をサービスコールを用いて書き込む機能がある。
xilinx-hls-common.hやxilinx-vip.hを含めてデバイスドライバソースコードを読んだところ。

struct buffer_addr_struct{
  void *start[FMT_NUM_PLANES];
  size_t length[FMT_NUM_PLANES];
} *buffers;


struct xilinx_axi_hls_register {
  __u32 offset;
  __u32 value;
};

struct xilinx_axi_hls_registers {
  __u32 num_regs;
  struct xilinx_axi_hls_register *regs;
};

#define XILINX_AXI_HLS_READ _IOWR('V', BASE_VIDIOC_PRIVATE+0, struct xilinx_axi_hls_registers)
#define XILINX_AXI_HLS_WRITE _IOW('V', BASE_VIDIOC_PRIVATE+1, struct xilinx_axi_hls_registers)

を記述しておき、

xilinx_axi_hls_register reg[1];
reg[0].offset = 0x10;
reg[0].value = 255;
xilinx_axi_hls_registers regs;
regs.num_regs = 1;
regs.regs = reg;
int fd2;
fd2 = open("/dev/v4l-subdev1", O_RDWR, 0);
if (fd2 == -1){
  std::cout << "Failed to open subvideo device." << std::endl;
  return 1;
}
if (-1 == xioctl(fd2, XILINX_AXI_HLS_WRITE, &regs)){
  std::cout << "Failed to set param via s_axilite" << std::endl;
  return 1;
}

を実行すれば値を書き込めそうだということがわかった。reg[0].offset = 0x10;の値は先程のxyuyv_hw.hを参考に設定した。

また、/dev/video0に設定するフォーマットは1画素8bitのフォーマットなので、

fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_GREY;

に変更する。

最終的に完成したプログラムのコードを以下に示す。
Dropbox - cam2.cc

実行・・失敗

実行してみたが、途中で止まってしまった。実行ログは以下の通り。

root@arm:/home/ubuntu# ./a.out
waiting in xioctl() 
waiting in xioctl() 
bus_info	: platform:video_cap:0
card		: video_cap output 0
driver	: xilinx-vipp
version	: 264448
waiting in xioctl() 
waiting in xioctl() 
reqbuf.count : 3
waiting in xioctl() 
buf.length : 1
buf.m.offset : 3196044192
buf.m.planes[j].length : 2073600
buffers[i].start[j] : 0xb6a9a000
waiting in xioctl() 
buf.length : 1
buf.m.offset : 3196044192
buf.m.planes[j].length : 2073600
buffers[i].start[j] : 0xb689f000
waiting in xioctl() 
buf.length : 1
buf.m.offset : 3196044192
buf.m.planes[j].length : 2073600
buffers[i].start[j] : 0xb66a4000
waiting in xioctl() 
waiting in xioctl()

具体的にはxioctl(fd, VIDIOC_STREAMON, &buf.type))を実行してカメラのキャプチャをスタートしようとすると、サービスコール呼び出しの実行がおわらず、待機状態になる。
バッファの確保はできているようだ。

わからないこと

  • なぜ取得できなかったのか・・

 いろいろ理由を考えているけど、わからない。

  • 前述のyuyvy_0: yuyvy@43c80000内のreg = <0x43c80000 0x0024>, <0x43c80024 0xFFDC>はどのように設定するのが正しいのか

この設定とカメラ取得プログラムのreg[0].offset = 0x10;は関係ありそう。デバイスドライバをもう少しきちんと読もう。

  • HLSコアはap_ctrl_noneになっているが、axilite経由の入力の値がセットされていない場合はどのように動作するのか?

デフォルト値(intなら0とか)が使用されるのかなと思っている。入力の値がセットされていないから計算が実行されない、なんていうことは多分ないと思っている。
(もしそうなら、カメラ取得プログラムで正しく入力値valが設定されていないせいでHLSの出力がでていない、というのも原因として考えられる。)
そうでないなら、axilite経由の値をうまくセットできていなかったとしても何らかの出力は出るのではないか・・?

  • Vivado上で配線にMark as Debugを設定し、ILAコアをいれた場合Linuxを動作したままHardware Manager上でILAデバッグができるのか?

これに関してはTwitter上でできるという話を聞いたが、やってみたが上手くいかない・・

  • タイミングの問題が原因?

これは見方をまだ理解していない。赤字なので何かヤバそう・間に合っていないとは思う。ただ、回路を変更する前から赤字はあった。値の変化はまだ確認していない。
f:id:lp6m:20181022215459p:plain


ブログが非常に長くなってしまった。何か原因として考えられることがあればご教示願います・・・