ZedBoard Linux (6) [FPGA]
FPGA側にDMA付けてみます。
まずは読むだけのメモリダンプ。
ZedBoardにはアナログRGBのD-SUBコネクタが付いてます。ここに指定したアドレスのデータをメモリダンプさせます。要はフレームバッファー。むか~しのマイコン時代から使われている、画像メモリでプログラムを動かしてデバッグしよう、方式です。
なんでHDMIがあるのにわざわざアナログRGBなんてレガシーな物を…って話ですが、レガシーだからこそ。レガシーなインターフェースって無手順で相手の都合を考えず一方的にデータを送りつけても良い、という使い方ができます。デバッグ当初のまだいろいろ立ち上がってない環境では楽ちんです。相変わらずUART(RS-232C)が使われ続ける理由と似てると思います。
12bitBGRなので、画素のサイズは16bit/pixelにします。4bitは無視。
そして書くだけのメモリフィラー。
こちらはボード上に簡単なデータ入力装置が無いので(マイク端子が楽ですが)指定したアドレスから指定したサイズのメモリにカウンターの値を無理矢理書き込むマスターにしてみます。もしLinuxの管理領域に書き込んだら即暴走。先に確保した128MB以降全部埋めてもLinuxが動き続けるのを確認します。本来はカメラのデータとか音声データを流し込みます。
あと、DMAでメモリから読み出したデータをアナログRGBのタイミングに載せるペリフェラルも用意します。これは昔からAvalon(AlteraのNios)で使ってる物をAPBに焼き直しました。タイミングのパラメータをX.Org(XFree86)のmodelineの値と同じ値が使えるようになってます。が、ちゃんと解像度可変にするにはピクセルクロックも可変にしないとならないので、結局固定です(笑)。まぁモニタの対応周波数内で動かせますがあまり意味ないです。周波数は100MHzにしてます。
とりあえずtopデザイン。tab=4そのままなので見にくいかも。
こう見ると長いですが、ほとんど同じ名前の信号をつなぐだけになってます。SystemVerilogのInterface記述やQsys、XPSといったツールが出てきたのもわかりますね。手で書いてたら無駄ですもん。コピペとマクロでやってても面倒くさい。(記述が無駄に冗長なのは半自動的だからです。)
DMA本体やVGAインターフェースもそのうち公開すると思いますが、複数のモジュールに別れてるので結構面倒くさい。どういう公開方法が楽かな?バージョン管理にgit使ってるのでそのままgithubとかに上げるのが良いのかな?ライセンスとかどうするのが良いんでしょ…オープンソースなハードウェアのライセンスってGPLとかCCとかで良いのかな?
モジュールに別れてますが、意外に小さいと思います。
VGA DMA:約270行
AXI Read シーケンサー:約240行
メモリフィル・DMA:約330行
AXI Write シーケンサー:約340行
VGAインターフェース:約280行
その他100~150行ぐらいの細かいファイルが5~6個
という構成。機能削ればこの程度です。
VGAのピン配置をZedBoardのマスターucfから持ってきて、コンパイルします。Sliceの使用率は6%程度。(そのうち約半分はXilinxのAPB関連のIP。しかもその中でまた新たにCritical Warning出てるしw。)
できあがったbitストリームを差し替えてBOOT.BINを作成。他のLinux関連のファイルはそのままです。立ち上げてもなにも変わりません。Linuxにはペリフェラル増えたとか通知してませんし。
PC側でSDKを立ち上げて、Linuxアプリを作成します。
レジスタ構成とかはまた今度として、こんな感じでプログラム作ってLinuxに転送し実行すると画面に画が出てきます。
Fill DMAがカウンター値を書き込んでるので、格子状のベタ画像になります。Linuxは動作し続けてるので、Fill DMAが書き込んだのはLinux管理外の領域となってるようです。Fill DMAの先頭アドレスを0x0000_0000とかにしてLinuxが管理してるはずのメモリ領域に書き込ませると、ちゃんと暴走します。VGA DMAの先頭アドレスをLinuxの領域にしてなにか操作すると画像が変わるのがわかります。
Linuxのどこかの管理領域。
Linuxでも絶対アドレスを指定したポインタによるアクセスって意外と簡単にできるんですね。知らんかったです。デバイスドライバとか書かなくて済むのでデバッグ時は楽です。デバイスドライバ書くとかpcore形式にしてXPSに登録するとか、これがうれしいのは繰り返し使うときで、一回しか使わないときは手間なんですよね。
これでまずはメモリを分けてLinuxの管理領域外のメモリを用意し、FPGAから直接操作する基本的な構成ができました。CPUのキャッシュとの関連とか帯域制御とかまだ色々ありそうですが、必要最低限は用意できたと思います。多分他のプラットフォーム(MBとかNiosIIとか)でも同じ方法で行けるはずです。
Linuxが使えるメモリが減ってしまい、自由度が下がるのが問題ですが、LinuxのページングモデルにFPGAロジックを摺り合わせるよりは楽だと思います。
まずは読むだけのメモリダンプ。
ZedBoardにはアナログRGBのD-SUBコネクタが付いてます。ここに指定したアドレスのデータをメモリダンプさせます。要はフレームバッファー。むか~しのマイコン時代から使われている、画像メモリでプログラムを動かしてデバッグしよう、方式です。
なんでHDMIがあるのにわざわざアナログRGBなんてレガシーな物を…って話ですが、レガシーだからこそ。レガシーなインターフェースって無手順で相手の都合を考えず一方的にデータを送りつけても良い、という使い方ができます。デバッグ当初のまだいろいろ立ち上がってない環境では楽ちんです。相変わらずUART(RS-232C)が使われ続ける理由と似てると思います。
12bitBGRなので、画素のサイズは16bit/pixelにします。4bitは無視。
そして書くだけのメモリフィラー。
こちらはボード上に簡単なデータ入力装置が無いので(マイク端子が楽ですが)指定したアドレスから指定したサイズのメモリにカウンターの値を無理矢理書き込むマスターにしてみます。もしLinuxの管理領域に書き込んだら即暴走。先に確保した128MB以降全部埋めてもLinuxが動き続けるのを確認します。本来はカメラのデータとか音声データを流し込みます。
あと、DMAでメモリから読み出したデータをアナログRGBのタイミングに載せるペリフェラルも用意します。これは昔からAvalon(AlteraのNios)で使ってる物をAPBに焼き直しました。タイミングのパラメータをX.Org(XFree86)のmodelineの値と同じ値が使えるようになってます。が、ちゃんと解像度可変にするにはピクセルクロックも可変にしないとならないので、結局固定です(笑)。まぁモニタの対応周波数内で動かせますがあまり意味ないです。周波数は100MHzにしてます。
とりあえずtopデザイン。tab=4そのままなので見にくいかも。
/************************ * top ************************/ `timescale 1ns / 1ps `default_nettype none //************************************************************************************************** // module top ( // ============================================== // Hard Wired inout wire [53:0] MIO , input wire PS_SRSTB , input wire PS_CLK , input wire PS_PORB , inout wire DDR_Clk , inout wire DDR_Clk_n , inout wire DDR_CKE , inout wire DDR_CS_n , inout wire DDR_RAS_n , inout wire DDR_CAS_n , output wire DDR_WEB , inout wire [ 2:0] DDR_BankAddr , inout wire [14:0] DDR_Addr , inout wire DDR_ODT , inout wire DDR_DRSTB , inout wire [31:0] DDR_DQ , inout wire [ 3:0] DDR_DM , inout wire [ 3:0] DDR_DQS , inout wire [ 3:0] DDR_DQS_n , inout wire DDR_VRN , inout wire DDR_VRP , // ============================================== // VGA output wire VGA_HS , output wire VGA_VS , output wire [ 4:1] VGA_B , output wire [ 4:1] VGA_G , output wire [ 4:1] VGA_R ); //////////////////////////// // // Property parameter blen = 16 ; // burst length parameter qdep = 8 ; // queue depth = 2**qdep parameter Xsymb = 1'bX ; // invalid Symbol // Video parameter vodw = 12 ; // VGA Out Data Width parameter vsdw = 16 ; // Video Stream Data Width parameter cntw = 11 ; // Counter Width // AXI parameter axiw = 6 ; // ID parameter axaw = 32 ; // Address parameter axdw = 64 ; // Data parameter axmw = axdw/8 ; // Mask parameter axrw = 2 ; // Response parameter axlw = 4 ; // Length parameter axsw = 3 ; // Size // AXI master (static) parameter axbw = 2 ; // Burst parameter axcw = 4 ; // Cache parameter axpw = 3 ; // Prot parameter axkw = 2 ; // Lock parameter axqw = 4 ; // QoS // APB Slave parameter apaw = 32 ; // Address parameter apdw = 32 ; // Data parameter apsw = 4 ; // Slaves //////////////////////////// // Clock wire FCLK_CLK0; // 66.6MHz wire FCLK_CLK1; // 100MHz wire FCLK_CLK2; // 166.6MHz wire FCLK_CLK3; // 200Mhz //////////////////////////// // EMIO GPIO wire [63:0] EMIO_GPIO_I; wire [63:0] EMIO_GPIO_O; wire [63:0] EMIO_GPIO_T; assign EMIO_GPIO_I = EMIO_GPIO_O ^ EMIO_GPIO_T; //////////////////////////// // APB wire apb_pclk ; // clock wire apb_presetn ; // reset_n wire [apaw-1:0] apb_paddr ; // address wire [apsw-1:0] apb_psel ; // select wire apb_penable ; // enalbe wire apb_pwrite ; // write wire [apdw-1:0] apb_pwdata ; // w data wire [apdw-1:0] apb_prdata [apsw-1:0] ; // r data wire [apsw-1:0] apb_pready ; // ready wire [apsw-1:0] apb_pslverr ; // error assign apb_pready = apb_psel; // no-wait assign apb_pslverr = {apsw{1'b0}}; // no-error assign apb_prdata[3] = apb_paddr; // unused slave //////////////////////////// // AXI HP wire axi_hp_aclk ; // // Address Write wire axi_hp_awready ; // wire axi_hp_awvalid ; // wire [axiw-1:0] axi_hp_awid ; // wire [axaw-1:0] axi_hp_awaddr ; // wire [axlw-1:0] axi_hp_awlen ; // wire [axsw-1:0] axi_hp_awsize ; // wire [axbw-1:0] axi_hp_awburst ; // static wire [axcw-1:0] axi_hp_awcache ; // static wire [axpw-1:0] axi_hp_awprot ; // static wire [axkw-1:0] axi_hp_awlock ; // static wire [axqw-1:0] axi_hp_awqos ; // static // Write Data wire axi_hp_wready ; // wire axi_hp_wvalid ; // wire [axiw-1:0] axi_hp_wid ; // wire axi_hp_wlast ; // wire [axdw-1:0] axi_hp_wdata ; // wire [axmw-1:0] axi_hp_wstrb ; // // Write Response wire axi_hp_bready ; // wire axi_hp_bvalid ; // wire [axiw-1:0] axi_hp_bid ; // wire [axrw-1:0] axi_hp_bresp ; // // Address Read wire axi_hp_arready ; // wire axi_hp_arvalid ; // wire [axiw-1:0] axi_hp_arid ; // wire [axaw-1:0] axi_hp_araddr ; // wire [axlw-1:0] axi_hp_arlen ; // wire [axsw-1:0] axi_hp_arsize ; // wire [axbw-1:0] axi_hp_arburst ; // static wire [axcw-1:0] axi_hp_arcache ; // static wire [axpw-1:0] axi_hp_arprot ; // static wire [axkw-1:0] axi_hp_arlock ; // static wire [axqw-1:0] axi_hp_arqos ; // static // Read Data wire axi_hp_rready ; // wire axi_hp_rvalid ; // wire [axiw-1:0] axi_hp_rid ; // wire axi_hp_rlast ; // wire [axdw-1:0] axi_hp_rdata ; // wire [axrw-1:0] axi_hp_rresp ; // //////////////////////////// // clock,reset wiring wire a_reset; wire c_clock; wire c_reset; assign a_reset = ~apb_presetn; // assign c_clock = FCLK_CLK1; // assign c_reset = 1'b0; // assign axi_hp_aclk = c_clock; // //////////////////////////// // Video wire v_hsync ; // H Sync wire v_vsync ; // V Sync wire [vodw-1:0] v_data ; // data wire tx_kick ; // kick wire tx_ready ; // ready wire tx_valid ; // valid wire [vsdw-1:0] tx_data ; // data assign VGA_HS = v_hsync; assign VGA_VS = v_vsync; assign {VGA_R,VGA_G,VGA_B} = v_data; //////////////////////////// // VGA I/F vga_if #( // Property .vodw (vodw ), // parameter vodw = 12 , // VGA Out Data Width .vsdw (vsdw ), // parameter vsdw = 16 , // Video Stream Data Width .cntw (cntw ), // parameter cntw = 11 , // Counter Width // APB Slave .apaw (apaw ), // parameter apaw = 32 , // Address .apdw (apdw ) // parameter apdw = 32 // Data ) voif ( // ============================================== // common .a_reset (a_reset ), // input wire a_reset , // reset .c_clock (c_clock ), // input wire c_clock , // clock .c_reset (c_reset ), // input wire c_reset , // reset // ============================================== // VGA .v_hsync (v_hsync ), // output wire v_hsync , // H Sync .v_vsync (v_vsync ), // output wire v_vsync , // V Sync .v_data (v_data ), // output wire [vodw-1:0] v_data , // data // ============================================== // Transmit Stream + kick .tx_kick (tx_kick ), // output wire tx_kick , // kick .tx_ready (tx_ready ), // output wire tx_ready , // ready .tx_valid (tx_valid ), // input wire tx_valid , // valid .tx_data (tx_data ), // input wire [vsdw-1:0] tx_data , // data // ============================================== // APB .apb_pclk (apb_pclk ), // input wire apb_pclk , // clock .apb_presetn (apb_presetn ), // input wire apb_presetn , // reset_n .apb_paddr (apb_paddr ), // input wire [apaw-1:0] apb_paddr , // address .apb_psel (apb_psel [0] ), // input wire apb_psel , // select .apb_penable (apb_penable ), // input wire apb_penable , // enalbe .apb_pwrite (apb_pwrite ), // input wire apb_pwrite , // write .apb_pwdata (apb_pwdata ), // input wire [apdw-1:0] apb_pwdata , // data .apb_prdata (apb_prdata [0] ) // output reg [apdw-1:0] apb_prdata , // data ); //////////////////////////// // VGA DMA vga_dma #( // Property .vsdw (vsdw ), // parameter vsdw = 16 , // Video Stream data width .blen (blen ), // parameter blen = 16 , // burst length .qdep (qdep ), // parameter qdep = 8 , // queue depth = 2**qdep // AXI master .axiw (axiw ), // parameter axiw = 6 , // ID .axaw (axaw ), // parameter axaw = 32 , // Address .axdw (axdw ), // parameter axdw = 64 , // Data .axmw (axmw ), // parameter axmw = axdw/8 , // Mask .axrw (axrw ), // parameter axrw = 2 , // Response .axlw (axlw ), // parameter axlw = 4 , // Length .axsw (axsw ), // parameter axsw = 3 , // Size // AXI master (static) .axbw (axbw ), // parameter axbw = 2 , // Burst .axcw (axcw ), // parameter axcw = 4 , // Cache .axpw (axpw ), // parameter axpw = 3 , // Prot .axkw (axkw ), // parameter axkw = 2 , // Lock .axqw (axqw ), // parameter axqw = 4 , // QoS // APB Slave .apaw (apaw ), // parameter apaw = 32 , // Address .apdw (apdw ), // parameter apdw = 32 , // Data // misc .Xsymb (Xsymb ) // parameter Xsymb = 1'bX // invalid Symbol ) vdma ( // ============================================== // common .a_reset (a_reset ), // input wire a_reset , // reset .c_clock (c_clock ), // input wire c_clock , // clock .c_reset (c_reset ), // input wire c_reset , // reset // ============================================== // Transmit Stream + kick .tx_kick (tx_kick ), // input wire tx_kick , // kick .tx_ready (tx_ready ), // output wire tx_ready , // ready .tx_valid (tx_valid ), // input wire tx_valid , // valid .tx_data (tx_data ), // input wire [vsdw-1:0] tx_data , // data // ============================================== // Address Read .axi_arready (axi_hp_arready ), // input wire axi_arready , // .axi_arvalid (axi_hp_arvalid ), // output wire axi_arvalid , // .axi_arid (axi_hp_arid ), // output wire [axiw-1:0] axi_arid , // .axi_araddr (axi_hp_araddr ), // output wire [axaw-1:0] axi_araddr , // .axi_arlen (axi_hp_arlen ), // output wire [axlw-1:0] axi_arlen , // .axi_arsize (axi_hp_arsize ), // output wire [axsw-1:0] axi_arsize , // .axi_arburst (axi_hp_arburst ), // output wire [axbw-1:0] axi_arburst , // static .axi_arcache (axi_hp_arcache ), // output wire [axcw-1:0] axi_arcache , // static .axi_arprot (axi_hp_arprot ), // output wire [axpw-1:0] axi_arprot , // static .axi_arlock (axi_hp_arlock ), // output wire [axkw-1:0] axi_arlock , // static .axi_arqos (axi_hp_arqos ), // output wire [axqw-1:0] axi_arqos , // static // Read Data .axi_rready (axi_hp_rready ), // output wire axi_rready , // .axi_rvalid (axi_hp_rvalid ), // input wire axi_rvalid , // .axi_rlast (axi_hp_rlast ), // input wire axi_rlast , // .axi_rid (axi_hp_rid ), // input wire [axiw-1:0] axi_rid , // .axi_rdata (axi_hp_rdata ), // input wire [axdw-1:0] axi_rdata , // .axi_rresp (axi_hp_rresp ), // input wire [axrw-1:0] axi_rresp , // // ============================================== // APB .apb_pclk (apb_pclk ), // input wire apb_pclk , // clock .apb_presetn (apb_presetn ), // input wire apb_presetn , // reset_n .apb_paddr (apb_paddr ), // input wire [apaw-1:0] apb_paddr , // address .apb_psel (apb_psel [1] ), // input wire apb_psel , // select .apb_penable (apb_penable ), // input wire apb_penable , // enalbe .apb_pwrite (apb_pwrite ), // input wire apb_pwrite , // write .apb_pwdata (apb_pwdata ), // input wire [apdw-1:0] apb_pwdata , // data .apb_prdata (apb_prdata [1] ) // output reg [apdw-1:0] apb_prdata , // data ); //////////////////////////// // Filler fill_dma #( // Property .blen (blen ), // parameter blen = 16 , // burst length .qdep (qdep ), // parameter qdep = 8 , // queue depth = 2**qdep // AXI master .axiw (axiw ), // parameter axiw = 6 , // ID .axaw (axaw ), // parameter axaw = 32 , // Address .axdw (axdw ), // parameter axdw = 64 , // Data .axmw (axmw ), // parameter axmw = axdw/8 , // Mask .axlw (axlw ), // parameter axlw = 4 , // Length .axsw (axsw ), // parameter axsw = 3 , // Size .axrw (axrw ), // parameter axrw = 2 , // Response // AXI master (static) .axbw (axbw ), // parameter axbw = 2 , // Burst .axcw (axcw ), // parameter axcw = 4 , // Cache .axpw (axpw ), // parameter axpw = 3 , // Prot .axkw (axkw ), // parameter axkw = 2 , // Lock .axqw (axqw ), // parameter axqw = 4 , // QoS // APB Slave .apaw (apaw ), // parameter apaw = 32 , // Address .apdw (apdw ), // parameter apdw = 32 , // Data // misc .Xsymb (Xsymb ) // parameter Xsymb = 1'bX // invalid Symbol ) filler ( // ============================================== // common .a_reset (a_reset ), // input wire a_reset , // reset .c_clock (c_clock ), // input wire c_clock , // clock .c_reset (c_reset ), // input wire c_reset , // reset // ============================================== // Address Write .axi_awready (axi_hp_awready ), // input wire axi_awready , // .axi_awvalid (axi_hp_awvalid ), // output wire axi_awvalid , // .axi_awid (axi_hp_awid ), // output wire [axiw-1:0] axi_awid , // .axi_awaddr (axi_hp_awaddr ), // output wire [axaw-1:0] axi_awaddr , // .axi_awlen (axi_hp_awlen ), // output wire [axlw-1:0] axi_awlen , // .axi_awsize (axi_hp_awsize ), // output wire [axsw-1:0] axi_awsize , // .axi_awburst (axi_hp_awburst ), // output wire [axbw-1:0] axi_awburst , // static .axi_awcache (axi_hp_awcache ), // output wire [axcw-1:0] axi_awcache , // static .axi_awprot (axi_hp_awprot ), // output wire [axpw-1:0] axi_awprot , // static .axi_awlock (axi_hp_awlock ), // output wire [axkw-1:0] axi_awlock , // static .axi_awqos (axi_hp_awqos ), // output wire [axqw-1:0] axi_awqos , // static // Write Data .axi_wready (axi_hp_wready ), // input wire axi_wready , // .axi_wvalid (axi_hp_wvalid ), // output wire axi_wvalid , // .axi_wlast (axi_hp_wlast ), // output wire axi_wlast , // .axi_wid (axi_hp_wid ), // output wire [axiw-1:0] axi_wid , // .axi_wdata (axi_hp_wdata ), // output wire [axdw-1:0] axi_wdata , // .axi_wstrb (axi_hp_wstrb ), // output wire [axmw-1:0] axi_wstrb , // // Write Response .axi_bready (axi_hp_bready ), // output wire axi_bready , // .axi_bvalid (axi_hp_bvalid ), // input wire axi_bvalid , // .axi_bid (axi_hp_bid ), // input wire [axiw-1:0] axi_bid , // .axi_bresp (axi_hp_bresp ), // input wire [axrw-1:0] axi_bresp , // // ============================================== // APB .apb_pclk (apb_pclk ), // input wire apb_pclk , // clock .apb_presetn (apb_presetn ), // input wire apb_presetn , // reset_n .apb_paddr (apb_paddr ), // input wire [apaw-1:0] apb_paddr , // address .apb_psel (apb_psel [2] ), // input wire apb_psel , // select .apb_penable (apb_penable ), // input wire apb_penable , // enalbe .apb_pwrite (apb_pwrite ), // input wire apb_pwrite , // write .apb_pwdata (apb_pwdata ), // input wire [apdw-1:0] apb_pwdata , // data .apb_prdata (apb_prdata [2] ) // output reg [apdw-1:0] apb_prdata , // data ); //////////////////////////// // PS (* BOX_TYPE = "user_black_box" *) ps_module ps ( // ============================================== // Hard Wired .processing_system7_0_MIO (MIO ), .processing_system7_0_PS_SRSTB_pin (PS_SRSTB ), .processing_system7_0_PS_CLK_pin (PS_CLK ), .processing_system7_0_PS_PORB_pin (PS_PORB ), .processing_system7_0_DDR_Clk (DDR_Clk ), .processing_system7_0_DDR_Clk_n (DDR_Clk_n ), .processing_system7_0_DDR_CKE (DDR_CKE ), .processing_system7_0_DDR_CS_n (DDR_CS_n ), .processing_system7_0_DDR_RAS_n (DDR_RAS_n ), .processing_system7_0_DDR_CAS_n (DDR_CAS_n ), .processing_system7_0_DDR_WEB_pin (DDR_WEB ), .processing_system7_0_DDR_BankAddr (DDR_BankAddr ), .processing_system7_0_DDR_Addr (DDR_Addr ), .processing_system7_0_DDR_ODT (DDR_ODT ), .processing_system7_0_DDR_DRSTB (DDR_DRSTB ), .processing_system7_0_DDR_DQ (DDR_DQ ), .processing_system7_0_DDR_DM (DDR_DM ), .processing_system7_0_DDR_DQS (DDR_DQS ), .processing_system7_0_DDR_DQS_n (DDR_DQS_n ), .processing_system7_0_DDR_VRN (DDR_VRN ), .processing_system7_0_DDR_VRP (DDR_VRP ), // ============================================== // EMIO .processing_system7_0_GPIO_I_pin (EMIO_GPIO_I ), .processing_system7_0_GPIO_O_pin (EMIO_GPIO_O ), .processing_system7_0_GPIO_T_pin (EMIO_GPIO_T ), // ============================================== // Clock .processing_system7_0_FCLK_CLK0_pin (FCLK_CLK0 ), .processing_system7_0_FCLK_CLK1_pin (FCLK_CLK1 ), .processing_system7_0_FCLK_CLK2_pin (FCLK_CLK2 ), .processing_system7_0_FCLK_CLK3_pin (FCLK_CLK3 ), // ============================================== // APB .axi_apb_bridge_0_M_APB_PCLK_pin (apb_pclk ), .axi_apb_bridge_0_M_APB_PRESETN_pin (apb_presetn ), .axi_apb_bridge_0_M_APB_PADDR_pin (apb_paddr ), .axi_apb_bridge_0_M_APB_PSEL_pin (apb_psel ), .axi_apb_bridge_0_M_APB_PENABLE_pin (apb_penable ), .axi_apb_bridge_0_M_APB_PWRITE_pin (apb_pwrite ), .axi_apb_bridge_0_M_APB_PWDATA_pin (apb_pwdata ), .axi_apb_bridge_0_M_APB_PREADY_pin (apb_pready ), .axi_apb_bridge_0_M_APB_PRDATA_pin (apb_prdata[0] ), .axi_apb_bridge_0_M_APB_PRDATA2_pin (apb_prdata[1] ), .axi_apb_bridge_0_M_APB_PRDATA3_pin (apb_prdata[2] ), .axi_apb_bridge_0_M_APB_PRDATA4_pin (apb_prdata[3] ), .axi_apb_bridge_0_M_APB_PSLVERR_pin (apb_pslverr ), // ============================================== // AXI HP0 .processing_system7_0_S_AXI_HP0_ARREADY_pin (axi_hp_arready ), .processing_system7_0_S_AXI_HP0_AWREADY_pin (axi_hp_awready ), .processing_system7_0_S_AXI_HP0_BVALID_pin (axi_hp_bvalid ), .processing_system7_0_S_AXI_HP0_RLAST_pin (axi_hp_rlast ), .processing_system7_0_S_AXI_HP0_RVALID_pin (axi_hp_rvalid ), .processing_system7_0_S_AXI_HP0_WREADY_pin (axi_hp_wready ), .processing_system7_0_S_AXI_HP0_BRESP_pin (axi_hp_bresp ), .processing_system7_0_S_AXI_HP0_RRESP_pin (axi_hp_rresp ), .processing_system7_0_S_AXI_HP0_BID_pin (axi_hp_bid ), .processing_system7_0_S_AXI_HP0_RID_pin (axi_hp_rid ), .processing_system7_0_S_AXI_HP0_RDATA_pin (axi_hp_rdata ), .processing_system7_0_S_AXI_HP0_ACLK_pin (axi_hp_aclk ), .processing_system7_0_S_AXI_HP0_ARVALID_pin (axi_hp_arvalid ), .processing_system7_0_S_AXI_HP0_AWVALID_pin (axi_hp_awvalid ), .processing_system7_0_S_AXI_HP0_BREADY_pin (axi_hp_bready ), .processing_system7_0_S_AXI_HP0_RREADY_pin (axi_hp_rready ), .processing_system7_0_S_AXI_HP0_WLAST_pin (axi_hp_wlast ), .processing_system7_0_S_AXI_HP0_WVALID_pin (axi_hp_wvalid ), .processing_system7_0_S_AXI_HP0_ARBURST_pin (axi_hp_arburst ), .processing_system7_0_S_AXI_HP0_ARLOCK_pin (axi_hp_arlock ), .processing_system7_0_S_AXI_HP0_ARSIZE_pin (axi_hp_arsize ), .processing_system7_0_S_AXI_HP0_AWBURST_pin (axi_hp_awburst ), .processing_system7_0_S_AXI_HP0_AWLOCK_pin (axi_hp_awlock ), .processing_system7_0_S_AXI_HP0_AWSIZE_pin (axi_hp_awsize ), .processing_system7_0_S_AXI_HP0_ARPROT_pin (axi_hp_arprot ), .processing_system7_0_S_AXI_HP0_AWPROT_pin (axi_hp_awprot ), .processing_system7_0_S_AXI_HP0_ARADDR_pin (axi_hp_araddr ), .processing_system7_0_S_AXI_HP0_AWADDR_pin (axi_hp_awaddr ), .processing_system7_0_S_AXI_HP0_ARCACHE_pin (axi_hp_arcache ), .processing_system7_0_S_AXI_HP0_ARLEN_pin (axi_hp_arlen ), .processing_system7_0_S_AXI_HP0_ARQOS_pin (axi_hp_arqos ), .processing_system7_0_S_AXI_HP0_AWCACHE_pin (axi_hp_awcache ), .processing_system7_0_S_AXI_HP0_AWLEN_pin (axi_hp_awlen ), .processing_system7_0_S_AXI_HP0_AWQOS_pin (axi_hp_awqos ), .processing_system7_0_S_AXI_HP0_ARID_pin (axi_hp_arid ), .processing_system7_0_S_AXI_HP0_AWID_pin (axi_hp_awid ), .processing_system7_0_S_AXI_HP0_WID_pin (axi_hp_wid ), .processing_system7_0_S_AXI_HP0_WDATA_pin (axi_hp_wdata ), .processing_system7_0_S_AXI_HP0_WSTRB_pin (axi_hp_wstrb ) ); endmodule // //************************************************************************************************** `default_nettype wire
こう見ると長いですが、ほとんど同じ名前の信号をつなぐだけになってます。SystemVerilogのInterface記述やQsys、XPSといったツールが出てきたのもわかりますね。手で書いてたら無駄ですもん。コピペとマクロでやってても面倒くさい。(記述が無駄に冗長なのは半自動的だからです。)
DMA本体やVGAインターフェースもそのうち公開すると思いますが、複数のモジュールに別れてるので結構面倒くさい。どういう公開方法が楽かな?バージョン管理にgit使ってるのでそのままgithubとかに上げるのが良いのかな?ライセンスとかどうするのが良いんでしょ…オープンソースなハードウェアのライセンスってGPLとかCCとかで良いのかな?
モジュールに別れてますが、意外に小さいと思います。
VGA DMA:約270行
AXI Read シーケンサー:約240行
メモリフィル・DMA:約330行
AXI Write シーケンサー:約340行
VGAインターフェース:約280行
その他100~150行ぐらいの細かいファイルが5~6個
という構成。機能削ればこの程度です。
VGAのピン配置をZedBoardのマスターucfから持ってきて、コンパイルします。Sliceの使用率は6%程度。(そのうち約半分はXilinxのAPB関連のIP。しかもその中でまた新たにCritical Warning出てるしw。)
できあがったbitストリームを差し替えてBOOT.BINを作成。他のLinux関連のファイルはそのままです。立ち上げてもなにも変わりません。Linuxにはペリフェラル増えたとか通知してませんし。
PC側でSDKを立ち上げて、Linuxアプリを作成します。
(相変わらずエラーチェック無しのいい加減プログラム…) #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> typedef unsigned int uint32; typedef unsigned short uint16; const uint32 param[] = {1280, 1360, 1496, 1712, 960, 961, 964, 994}; タイミング inline void putPixel(uint16 *fb, int x, int y, uint16 c) { fb[y*param[0]+x] = c; 直接書き込みとか } … #define MAP_SIZE 4096UL レジスタ空間のサイズ #define FB_SIZE 1024UL*1024UL*(512-128) Linuxが取った128MB以外全部 … main() /dev/mem開いて直接アドレスでマッピング fd = open("/dev/mem", O_RDWR | O_SYNC); uint32 *voif = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x72800000); VGA I/F uint32 *vdma = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x60A00000); VGA DMA uint32 *fill = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x60A20000); Filler uint16 *fb = mmap(0, FB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x08000000); フレームバッファー close(fd); vdma[4] = 0x08000000; ベースアドレス=128MB vdma[5] = width*height*2; 転送サイズ=画像サイズ voif[0x08] = param[0]; voif[0x09] = param[1]; voif[0x0a] = param[2]; voif[0x0b] = param[3]; voif[0x0c] = param[4]; voif[0x0d] = param[5]; voif[0x0e] = param[6]; voif[0x0f] = param[7]; パラメータ設定 vdma[1] = 0x00000001; DMAスタート voif[0] = 0x0000000b; VGAスタート fill[4] = 0x08000000; 128MB先頭から fill[5] = 384*1024*1024; 384MB一気書き込みとか fill[1] = 0x00000001; fill[1] = 0x00000000; 線引いたり円描いたり line(fb, width-1, 0, 0, height-1, 0xffff); circle(fb, width/2, height/2, 100, 0xffff); 最後に開放 munmap(voif, MAP_SIZE); munmap(vdma, MAP_SIZE); munmap(fill, MAP_SIZE); munmap(fb , FB_SIZE);
レジスタ構成とかはまた今度として、こんな感じでプログラム作ってLinuxに転送し実行すると画面に画が出てきます。
Fill DMAがカウンター値を書き込んでるので、格子状のベタ画像になります。Linuxは動作し続けてるので、Fill DMAが書き込んだのはLinux管理外の領域となってるようです。Fill DMAの先頭アドレスを0x0000_0000とかにしてLinuxが管理してるはずのメモリ領域に書き込ませると、ちゃんと暴走します。VGA DMAの先頭アドレスをLinuxの領域にしてなにか操作すると画像が変わるのがわかります。
Linuxのどこかの管理領域。
Linuxでも絶対アドレスを指定したポインタによるアクセスって意外と簡単にできるんですね。知らんかったです。デバイスドライバとか書かなくて済むのでデバッグ時は楽です。デバイスドライバ書くとかpcore形式にしてXPSに登録するとか、これがうれしいのは繰り返し使うときで、一回しか使わないときは手間なんですよね。
これでまずはメモリを分けてLinuxの管理領域外のメモリを用意し、FPGAから直接操作する基本的な構成ができました。CPUのキャッシュとの関連とか帯域制御とかまだ色々ありそうですが、必要最低限は用意できたと思います。多分他のプラットフォーム(MBとかNiosIIとか)でも同じ方法で行けるはずです。
Linuxが使えるメモリが減ってしまい、自由度が下がるのが問題ですが、LinuxのページングモデルにFPGAロジックを摺り合わせるよりは楽だと思います。
2013-10-19 11:11
nice!(0)
トラックバック(0)
Facebook コメント