・ar37425.zip を解凍して、ar37425/axi_stream フォルダをテンプレートとして、カメラ・インターフェース用のAXI4-Stream IP (mt9d111_inf_axi_stream.vhd) を作る。 mt9d111_inf_axi_stream.vhd の entity を下に示す。(2013/06/01:修正)
entity mt9d111_inf_axi_stream is generic( -- Master AXI Stream Data Width C_M_AXIS_DATA_WIDTH : integer range 8 to 1024 := 24 ); port ( s2mm_aclk : out std_logic; s2mm_prmry_reset : in std_logic; s2mm_fsync : out std_logic;
-- Master Stream Ports -- m_axis_aresetn : out std_logic; m_axis_tdata : out std_logic_vector(C_M_AXIS_DATA_WIDTH-1 downto 0); m_axis_tstrb : out std_logic_vector((C_M_AXIS_DATA_WIDTH/8)-1 downto 0); m_axis_tvalid : out std_logic; m_axis_tready : in std_logic; m_axis_tlast : out std_logic;
init_done : in std_logic; -- PS部の初期化終了
-- MT9D111 Camera Interface pclk_from_pll : in std_logic; -- PLLからMT9D111のxck に出力するクロック pclk : in std_logic; -- MT9D111からのピクセルクロック入力 xck : out std_logic; -- MT9D111へのピクセルクロック出力 href : in std_logic; vsync : in std_logic; cam_data : in std_logic_vector(7 downto 0); standby : out std_logic; -- STANDBY出力(ディスエーブル、0固定) pfifo_overflow : out std_logic; -- pfifo overflow pfifo_underflow : out std_logic -- pfifo underflow );
まずはカメラ・インターフェイスIPをVideo In to AXI4-Stream IPを使用して作ることにした。 ここで問題となるのは、カメラからの信号には、HSYNC(水平同期信号)とVSYNC(垂直同期信号)が無いことだ。 line_valid と frame_valid があるので、カウンタを入れて、適当な所で同期信号を生成しようと思う。カメラのフレーム関連のパラメータは、”MT9D111のお勉強4”の Frame Time の図に書いてある。
Video Input信号には、DEとData in、Vblank, Hblank, Vsync, Hsync, Video Clock がある。 DEはline_valid 信号が使える。Vblank はframe_valid を反転させれば良い。Hblankもline_valid を反転させる。問題はVsync とHsync で、これは適当に生成する必要がある。
AXI4-Stream のクロックは、Video In to AXI4-Stream IPの中で非同期FIFOでビデオ信号のクロックと切り離されているので、どのような動作周波数にしても問題がないが、100MHzくらいにしておこうと思う。
(2013/05/28:大幅修正)
実際にVerilog HDLファイルを修正していたところ、カメラのデータは1バイトずつ送られて来るのだが、それを2つ集めて1つのRGB 565のピクセルとしていることを思い出した。これでは、Video In to AXI4-Stream IPにPCLKでビデオデータとして転送するとこは不可能だということに気がついた。Video In to AXI4-Stream IPを使用するには、転送クロックを半分に落として、データ同期用の非同期FIFOを置いて、新たにビデオ信号を生成する必要がある。負担が大きいため、Video In to AXI4-Stream IPは使用しないで、そのフォーマットでカメラ・インターフェースIPから24ビットRGBを直接AXI4-Stream バスで出力することにした。
(2013/05/26追記) なるべく既存のIPは使うという方針で行くことにしているので、XPSのVideo and Image Processing カテゴリのIPを見ると、AXI4-Stream to Video Out とVideo In to AXI4-Stream があった。この2つはVideo Timing Controller のビデオ信号とAXI4-Stream を変換するIPだった。
Figure 1-1に示された様に、ビデオ信号をAXI4-Stream に変換するIPのようだ。カメラからの信号はこのビデオ信号に完全には適合しないが、適当な幅の HSYNC と VSYNC を作って入れれば行けるのではないか?と思う。カメラ・インターフェイスをVideo In to AXI4-Streamのビデオ信号に変換するカスタムIPを設定することにする。 (2013/05/28:修正)カメラ・インターフェースIPからVideo in to AXI4 Stream のフォーマットのAXI4-Streamバスで出力するカスタムIPを作ることにした。詳しくは、”カメラ、ビデオ表示カスタムIPを AX4-Stream に変更2(カメラ・インターフェースIP)”を参照のこと。
AXI VDMA Video Transfer ・データはシステムメモリからストリームまたは現在操作対象のフレームと垂直サイズ、水平サイズ、ストライドの開始アドレスで定義されているシステムメモリにストリームで転送される。 ・下の図に、システムメモリに記憶された一般的なビデオ画像を示す。画像メモリの一部分を表示している。 ・Horizontal Sizeのバイト数分のVertical Sizeのラインが、システムメモリのビデオ・フレームのスタート・アドレスの番地から転送される。
VDMAを行う前に、descriptor pointer registers と DMA control registers をセットアップする。下に最低限の手順を示す。
1.チャネルの CURDESC_PNTRレジスタに有効なポインタを書く (Offset 0x08 for MM2S and 0x38 for S2MM)。
2.チャネルの DMACRレジスタにコントロール情報を書く (Offset 0x00 for MM2S and 0x30 for S2MM) 。いろいろな設定があるが、DMACR.RS=1でDMAが開始される。DMAのステータス・レジスタのHalt解除(DMASR.Halted = 0)にはタイムラグがあるそうだ。
3.チャネルの TAILDESC_PNTRレジスタに有効なポインタを書く (Offset 0x10 for MM2S and 0x40 for S2MM)。ディスクリプタをフェッチし、そして処理してチャネルをスタートする。
(重要) On the S2MM channel, new video line size and number of video lines need to change following the assertion of s2mm_prmtr_update or undefined results occur. そのまま引用。S2MMチャネルでは、画像のラインサイズやライン数を変更した後に、s2mm_prmtr_update がアサートされるか、定義されていない結果が起こると言っているのだろうか?(英語に自信が無いので、すみません)
3.Frame DelayとストライドをFRMDLY_STRIDEレジスタに設定する (Offset 0x58 for MM2S and 0xA8 for S2MM)。 ビットマップ・ディスプレイ・コントローラで、1ピクセルRGB合計4バイトで表される。 ストライドは、SVGAの横ピクセル800ピクセル X 4バイト = 3,200を設定する。(94ページのAXI VDMA Video Transfer、Figure 3‐1: Example Video Image Transfer を参照)描画領域は1面しか用意しないため。図1参照。 図1 画像データの描画領域
4.有効な水平サイズをHSIZEレジスタに設定する。 (Offset 0x54 for MM2S and 0xA4 for S2MM)
5.有効な垂直サイズをVSIZEレジスタに設定する。 (Offset 0x50 for MM2S and 0xA0 for S2MM)
画像データを転送するチャネルがスタートする。
(重要) On the S2MM channel, new video line size and number of video lines need to change following the assertion of s2mm_prmtr_update or undefined results occur. そのまま引用。S2MMチャネルでは、画像のラインサイズやライン数を変更した後に、s2mm_prmtr_update がアサートされるか、定義されていない結果が起こると言っているのだろうか?(英語に自信が無いので、すみません)
Updating Video Transfer Information (91ページ)
Register Direct Mode (C_INCLUDE_SG = 0)で、描画している最中に、画像のパラメータやスタート・アドレスをいじることができる。垂直サイズ・レジスタに書くと、フレーム境界で反映される。 変更された画像パラメータをAXI VDMAが使うと、各チャネルの pmrtr_update 出力 ( mm2s_prmtr_update and s2mm_prmtr_update ) がアサートされる。 AXI VDMAが動作中に画像パラメータを変更するときは、初期化の時と同様に設定する。
1.変更したいチャネルに、任意の順序で Frame Delay, Stride, and Horizontal Size を書く。
Qsysも使いやすそうでしたが、やはり接続が1次元です。見やすさは、2次元で回路図のようにIPを接続できるXilinxのVivado IP Integrator の方が見やすそうでした。使ったことがなくてビデオで見ただけですし、あくまで見やすいという観点だけです。(参考、動画です。Targeting Zynq Using Vivado IP Integrator) そうそうARM Development Studio 5 (DS-5) Altera Edition ツールキットとSignalTap II が協調して検証できるのは素晴らしいですね。こういう検証環境を使ってみたいです。Web EditonではDS-5のリモートデバックのみで、USBブラスタは使えないとのことでした。実質、Linuxのみデバックできるということでしょうかね?
評価ボードですが、4種類紹介がありました。 1.Cyclone V SoC 開発キット 値段は、$1,595なので高価ですが、DS-5がUSBブラスタで使えるライセンスが付いているそうです。このボードはHPS部とFPGA部にDDR3が付いているそうです。
2.Helio(ヘリオ) ボード ~Cyclone V SoC ベーシックボード~ 25,800円(税別)だそうです。このボードは、FPGA部にDDR3は載っていませんが欲しいです。1.のアルテラ純正ボードとBSP(Board Support Package)互換だそうです。同じSDカードでLinux起動していました。
## Generics for VHDL or Parameters for Verilog PARAMETER C_M_AXI_ADDR_WIDTH = 32, DT = integer, ASSIGNMENT = CONSTANT PARAMETER C_DISPLAY_START_ADDRESS = 0x1A000000, DT = std_logic_vector(31 downto 0)
## Ports PORT aclk = "", DIR = I, SIGIS = CLK PORT aresetn = "", DIR = I, SIGIS = RST PORT camera_fb_start_addr = "", DIR = O, VEC = [(C_M_AXI_ADDR_WIDTH-1):0] PORT bitmapd_fb_start_addr = "", DIR = O, VEC = [(C_M_AXI_ADDR_WIDTH-1):0] PORT frame_valid_1d = "", DIR = I PORT display_enable = "", DIR = I END
always @(posedge clk) begin if (reset) state <= wait_write; else begin case (state) wait_write : if (write_req_p) state <= writing; writing : if (write_req_p) state <= writing; else if (frame_valid_p) state <= wait_display; wait_display : if (display_req_p) state <= displaying; displaying : if (display_req_p) state <= displaying; else if (display_enable_p) state <=wait_write; endcase end end
// synthesis translate_off reg [12*8:1] FB_STATE;
always @(state) begin case (state) wait_write: FB_STATE <= "WAIT_WRITE"; writing: FB_STATE <= "WRITING"; displaying : FB_STATE <= "DISPLAYING"; wait_display : FB_STATE <= "WAIT_DISPLAY"; endcase end // synthesis translate_on endmodule
assign reset = ~aresetn; // Sychronization always @(posedge aclk) begin if (reset) begin fv_1d_1d <= 1'b0; fv_1d_2d <= 1'b0; fv_1d_3d <= 1'b0; de_1d <= 1'b0; de_2d <= 1'b0; de_3d <= 1'b0; end else begin fv_1d_1d <= frame_valid_1d; fv_1d_2d <= fv_1d_1d; fv_1d_3d <= fv_1d_2d; de_1d <= display_enable; de_2d <= de_1d; de_3d <= de_2d; end end
always @(posedge aclk) begin if (reset) fv_1d_p <= 1'b0; else begin if (fv_1d_2d==1'b0 && fv_1d_3d==1'b1) // falling edge fv_1d_p <= 1'b1; else fv_1d_p <= 1'b0; end end
always @(posedge aclk) begin if (reset) de_p <= 1'b0; else begin if (de_2d==1'b0 && de_3d==1'b1) // falling edge de_p <= 1'b1; else de_p <= 1'b0; end end
always @(posedge aclk) begin reset_1b <= reset; end
// write_req_p_1, write_req_p_2, write_req_p_3 always @(posedge aclk) begin if (reset) begin write_req_p_1 <= 1'b0; write_req_p_2 <= 1'b0; write_req_p_3 <= 1'b0; camera_fb_start_addr <= FIRST_FB_ADDRESS; end else if (~reset & reset_1b) begin // reset falling edge write_req_p_1 <= 1'b1; write_req_p_2 <= 1'b0; write_req_p_3 <= 1'b0; end else if (fv_1d_2d==1'b0 && fv_1d_3d==1'b1) begin // falling edge if (state_1 == wait_write) begin write_req_p_1 <= 1'b1; write_req_p_2 <= 1'b0; write_req_p_3 <= 1'b0; camera_fb_start_addr <= FIRST_FB_ADDRESS; end else if (state_2 == wait_write) begin write_req_p_1 <= 1'b0; write_req_p_2 <= 1'b1; write_req_p_3 <= 1'b0; camera_fb_start_addr <= SECOND_FB_ADDRESS; end else if (state_3 == wait_write) begin write_req_p_1 <= 1'b0; write_req_p_2 <= 1'b0; write_req_p_3 <= 1'b1; camera_fb_start_addr <= THIRD_FB_ADDRESS; end else if (state_1==writing && state_2!=wait_write && state_3!=wait_write) begin write_req_p_1 <= 1'b1; write_req_p_2 <= 1'b0; write_req_p_3 <= 1'b0; camera_fb_start_addr <= FIRST_FB_ADDRESS; end else if (state_2==writing && state_1!=wait_write && state_3!=wait_write) begin write_req_p_1 <= 1'b0; write_req_p_2 <= 1'b1; write_req_p_3 <= 1'b0; camera_fb_start_addr <= SECOND_FB_ADDRESS; end else if (state_3==writing && state_2!=wait_write && state_3!=wait_write) begin write_req_p_1 <= 1'b0; write_req_p_2 <= 1'b0; write_req_p_3 <= 1'b1; camera_fb_start_addr <= THIRD_FB_ADDRESS; end else begin write_req_p_1 <= 1'b0; write_req_p_2 <= 1'b0; write_req_p_3 <= 1'b0; end end else begin write_req_p_1 <= 1'b0; write_req_p_2 <= 1'b0; write_req_p_3 <= 1'b0; end end
// display_req_p_1, display_req_p_2, display_req_p_3 always @(posedge aclk) begin if (reset) begin display_req_p_1 <= 1'b0; display_req_p_2 <= 1'b0; display_req_p_3 <= 1'b0; bitmapd_fb_start_addr <= FIRST_FB_ADDRESS; end else if (de_2d==1'b0 && de_3d==1'b1) begin // falling edge if (state_1 == wait_display) begin display_req_p_1 <= 1'b1; display_req_p_2 <= 1'b0; display_req_p_3 <= 1'b0; bitmapd_fb_start_addr <= FIRST_FB_ADDRESS; end else if (state_2 == wait_display) begin display_req_p_1 <= 1'b0; display_req_p_2 <= 1'b1; display_req_p_3 <= 1'b0; bitmapd_fb_start_addr <= SECOND_FB_ADDRESS; end else if (state_3 == wait_display) begin display_req_p_1 <= 1'b0; display_req_p_2 <= 1'b0; display_req_p_3 <= 1'b1; bitmapd_fb_start_addr <= THIRD_FB_ADDRESS; end else if (state_1==displaying && state_2!=wait_display && state_3!=wait_display) begin display_req_p_1 <= 1'b1; display_req_p_2 <= 1'b0; display_req_p_3 <= 1'b0; bitmapd_fb_start_addr <= FIRST_FB_ADDRESS; end else if (state_2==displaying && state_1!=wait_display && state_3!=wait_display) begin display_req_p_1 <= 1'b0; display_req_p_2 <= 1'b1; display_req_p_3 <= 1'b0; bitmapd_fb_start_addr <= SECOND_FB_ADDRESS; end else if (state_3==displaying && state_1!=wait_display && state_2!=wait_display) begin display_req_p_1 <= 1'b0; display_req_p_2 <= 1'b0; display_req_p_3 <= 1'b1; bitmapd_fb_start_addr <= THIRD_FB_ADDRESS; end else begin display_req_p_1 <= 1'b0; display_req_p_2 <= 1'b0; display_req_p_3 <= 1'b0; end end else begin display_req_p_1 <= 1'b0; display_req_p_2 <= 1'b0; display_req_p_3 <= 1'b0; end end
// First Frame Buffer State triple_fb_state first_fb_state ( .clk(aclk), .reset(reset), .write_req_p(write_req_p_1), .display_req_p(display_req_p_1), .frame_valid_p(fv_1d_p), .display_enable_p(de_p), .state(state_1) );
// Second Frame Buffer State triple_fb_state second_fb_state ( .clk(aclk), .reset(reset), .write_req_p(write_req_p_2), .display_req_p(display_req_p_2), .frame_valid_p(fv_1d_p), .display_enable_p(de_p), .state(state_2) );
// Third Frame Buffer State triple_fb_state third_fb_state ( .clk(aclk), .reset(reset), .write_req_p(write_req_p_3), .display_req_p(display_req_p_3), .frame_valid_p(fv_1d_p), .display_enable_p(de_p), .state(state_3) );
8. 5.にLチカAXI4 Lite Slave IPをLEDの数分だけ搭載する。スイッチ押したことを知らせるAXI4 Master IPは1個とする。AXI4 Master IPにレジスタ領域(AXI4 Lite Slave)を用意して、MicroBlazeも再実装して、レジスタに各LチカAXI4 Lite Slave IPのアドレスを設定する。押したスイッチによって、レジスタに格納されたアドレスにメッセージを送る。
always @(posedge clk) begin if (reset) state <= wait_write; else begin case (state) wait_write : if (write_req) state <= writing; writing : if (write_req) state <= writing; else if (wtime_is_zero) state <= wait_display; wait_display : if (display_req) state <= displaying; displaying : if (display_req) state <= displaying; else if (dtime_is_zero) state <=wait_write; endcase end end
// synthesis translate_off reg [12*8:1] FB_STATE;
always @(state) begin case (state) wait_write: FB_STATE <= "WAIT_WRITE"; writing: FB_STATE <= "WRITING"; displaying : FB_STATE <= "DISPLAYING"; wait_display : FB_STATE <= "WAIT_DISPLAY"; endcase end // synthesis translate_on endmodule
always @(posedge clk) begin if (reset) wtime <= write_life_cycle - 1; else begin if (wtime == 24'd0) wtime <= write_life_cycle - 1; else wtime <= wtime - 24'd1; end end assign wtime_is_zero = (wtime == 24'd0) ? 1'b1 : 1'b0;
always @(posedge clk) begin if (reset) dtime <= display_life_cycle - 1; else begin if (dtime == 24'd0) dtime <= display_life_cycle - 1; else dtime <= dtime - 24'd1; end end assign dtime_is_zero = (dtime == 24'd0) ? 1'b1 : 1'b0;
always @(posedge clk) begin reset_1b <= reset; end
always @* begin if (~reset & reset_1b) begin write_req_1 <= 1'b1; write_req_2 <= 1'b0; write_req_3 <= 1'b0; end else if (wtime_is_zero) begin if (state_1 == wait_write) begin write_req_1 <= 1'b1; write_req_2 <= 1'b0; write_req_3 <= 1'b0; end else if (state_2 == wait_write) begin write_req_1 <= 1'b0; write_req_2 <= 1'b1; write_req_3 <= 1'b0; end else if (state_3 == wait_write) begin write_req_1 <= 1'b0; write_req_2 <= 1'b0; write_req_3 <= 1'b1; end else if (state_1==writing && state_2!=wait_write && state_3!=wait_write) begin write_req_1 <= 1'b1; write_req_2 <= 1'b0; write_req_3 <= 1'b0; end else if (state_2==writing && state_1!=wait_write && state_3!=wait_write) begin write_req_1 <= 1'b0; write_req_2 <= 1'b1; write_req_3 <= 1'b0; end else if (state_3==writing && state_2!=wait_write && state_3!=wait_write) begin write_req_1 <= 1'b0; write_req_2 <= 1'b0; write_req_3 <= 1'b1; end else begin write_req_1 <= 1'b0; write_req_2 <= 1'b0; write_req_3 <= 1'b0; end end else begin write_req_1 <= 1'b0; write_req_2 <= 1'b0; write_req_3 <= 1'b0; end end
always @* begin if (dtime_is_zero) begin if (state_1 == wait_display) begin display_req_1 <= 1'b1; display_req_2 <= 1'b0; display_req_3 <= 1'b0; end else if (state_2 == wait_display) begin display_req_1 <= 1'b0; display_req_2 <= 1'b1; display_req_3 <= 1'b0; end else if (state_3 == wait_display) begin display_req_1 <= 1'b0; display_req_2 <= 1'b0; display_req_3 <= 1'b1; end else if (state_1==displaying && state_2!=wait_display && state_3!=wait_display) begin display_req_1 <= 1'b1; display_req_2 <= 1'b0; display_req_3 <= 1'b0; end else if (state_2==displaying && state_1!=wait_display && state_3!=wait_display) begin display_req_1 <= 1'b0; display_req_2 <= 1'b1; display_req_3 <= 1'b0; end else if (state_3==displaying && state_1!=wait_display && state_2!=wait_display) begin display_req_1 <= 1'b0; display_req_2 <= 1'b0; display_req_3 <= 1'b1; end else begin display_req_1 <= 1'b0; display_req_2 <= 1'b0; display_req_3 <= 1'b0; end end else begin display_req_1 <= 1'b0; display_req_2 <= 1'b0; display_req_3 <= 1'b0; end end