ZYNQでFPGA(PL)からARM(PS)のDDR3メモリへDMA転送
タイトルの通りです。
ZYNQでFPGA(PL)からARM(PS)のDDR3メモリへDMA転送することができるようになりました。
いま、ある種の計測器を作っています。FPGAに入ってきた計測データがBRAMに溜まっていきます。ある程度溜まったらそれを一気にPSのDDR3メモリに吐き出すようにしたいのですが、CPUの動作を介さずにハードウェア的にメモリ転送を自動的に実行したいと思います。
いわばDMAのような感じですが、AXIのバスの動作を使ってバスに自動的にやらせたいわけです。
ZYNQのAXIのポートには、①32bGP AXI Master Ports 2個, ②32bGP AXI Slave Ports 2個, ③High Performance 32b/64b Slave Ports 4個, ④64b AXI ACP Slave Port、のようにいくつかあります。
FPGA(PL)から、PSのDDR3メモリにアクセスするにはHigh Performance Slave Portsがよさそうです。
考えている構成は次のようになります。
32bGP AXI Master Ports(CPUがマスタで、コントローラはスレーブ)を通じて、転送先のメモリアドレスや長さなどの条件を設定し、計測データをHigh Performance 32b/64b Slave Ports(コントローラがマスタで、DDR3メモリがスレーブ)に入れるというわけです。
つまり、AXIマスタを作らなければなりません。実際にやってみて、MITOUJTAGのBLOGANA(簡易FPGA内蔵ロジアナ。ChipScopeみたいなもの)機能を使って波形を見てみました。
要するに、
- 書き込み先のアドレスをAWADDRに出力し
- 書き込みたいビート数(ワード数)をAWLENに出力する。AWLENの値は書き込みたい長さ-1である。(つまり16ワード書き込みたいならば0x0fを与える)
- AWREADYが'1'であるならばスレーブがアドレス受け入れ可能なので、AWVALIDを'1'にアサートする
- WREADYが'1'であるならばスレーブがデータ受け入れ可能なので、WVALIDを'1'にアサートするとともに、WDATAに32bitのデータを与える。そのときWSTRBには"1111"を与えると全バイトが書き込まれる。マスクしたい場合は当該バイトを0にする。
- 最後の1ワードを書き込むときには、WLASTを'1'にする。
- しばらくしてBVALID='1',BRESP="00"を確認したら終了
です。
さて、各種の信号に何を与えればよいかは、XILINXのAXIガイド(UG761)を読んでも正直言ってよくわかりません。ARM社からダウンロードできるIHI0022E_amba_axi_and_ace_protocol_spec.pdfという文章を読まなければ、結局のところわかりません。
ARM社の文章を読んでわかったことは、次のとおりです。
- AWSIZEは、1ワードのサイズです。000は8bit、001は16bit、010は32bit、011は64bit、100は128bitです。AXIのポートを32bitで使っているので、ここでは axim_AWSIZE_i <= "010"; としておきます。
- AWBURSTはバースト時のアドレスの変化方法を指定します。"00"だとFIXEDといってアドレスがインクリメントしないモード、"01"はINCRといってアドレスが1つずつ増えていくモード、"10"はWRAPといって、よくわかりません(笑)。
"01"のINCRにすればよいでしょう。 - AWCACHEは"0011"; にします。この意味は「Normal Non-cacheable Bufferable」だそうです。
- AWPROTは保護モードだそうですが、"000"で良いようです。
また、DDR3メモリに書き込むだけであれば、リード動作は不要なのでリード系の入力新語うにはすべて0を与えておきます。、
ARADDR <= (others => '0'); ARLEN <= (others => '0'); ARSIZE <= (others => '0'); ARBURST <= (others => '0'); ARCACHE <= (others => '0'); ARPROT <= (others => '0'); ARVALID <= '0'; RREADY <= '0';
これでAXI Masterの信号がすべて片付きました。
私の作ったコントローラは、C言語から、
volatile unsigned long *tmp = (volatile unsigned long *)(XPAR_AXI_EXT_SLAVE_CONN_0_S_AXI_RNG00_BASEADDR); regbase[16] = (unsigned long)dmabuf; // 格納先のメモリアドレス regbase[17] = len; // ワード単位 regbase[18] = 1; // DMAライト開始
とすればDMAが発行されるようになっています。
さて、ZYNQのややこしいところはキャッシュの存在です。DMAで転送されたデータのCPUから読み出すには、Xil_DCacheInvalidateRangeという関数を使って、キャッシュの中のデータが古いからDRAMから読み直すようにという指示を与えなければなりません。そうしないと、せっかくDMAで更新されたデータではなく古いキャッシュの内容が読み出されてしまいます。
int len = 16; Xil_DCacheInvalidateRange(dmabuf,len*sizeof(int)); for(i=0;i<len;i++) { printf("data(%d) = %08lx\r\n",i,dmabuf[i]); }
これでOKです。
忘れないように今日作ったソースを書いておきます。
signal axim_ARESETN_o : std_logic; signal axim_AWADDR_i : std_logic_vector(31 downto 0); signal axim_AWLEN_i : std_logic_vector(7 downto 0); signal axim_AWSIZE_i : std_logic_vector(2 downto 0); signal axim_AWBURST_i : std_logic_vector(1 downto 0); signal axim_AWCACHE_i : std_logic_vector(3 downto 0); signal axim_AWPROT_i : std_logic_vector(2 downto 0); signal axim_AWVALID_i : std_logic; signal axim_AWREADY_o : std_logic; signal axim_WDATA_i : std_logic_vector(31 downto 0); signal axim_WSTRB_i : std_logic_vector(3 downto 0); signal axim_WLAST_i : std_logic; signal axim_WVALID_i : std_logic; signal axim_WREADY_o : std_logic; signal axim_BRESP_o : std_logic_vector(1 downto 0); signal axim_BVALID_o : std_logic; signal axim_BREADY_i : std_logic; signal axim_ARADDR_i : std_logic_vector(31 downto 0); signal axim_ARLEN_i : std_logic_vector(7 downto 0); signal axim_ARSIZE_i : std_logic_vector(2 downto 0); signal axim_ARBURST_i : std_logic_vector(1 downto 0); signal axim_ARCACHE_i : std_logic_vector(3 downto 0); signal axim_ARPROT_i : std_logic_vector(2 downto 0); signal axim_ARVALID_i : std_logic; signal axim_ARREADY_o : std_logic; signal axim_RDATA_o : std_logic_vector(31 downto 0); signal axim_RRESP_o : std_logic_vector(1 downto 0); signal axim_RLAST_o : std_logic; signal axim_RVALID_o : std_logic; signal axim_RREADY_i : std_logic; signal axim_remain : std_logic_vector(7 downto 0); signal axim_state : std_logic_vector(1 downto 0); ・・・ axi_ext_master_conn_0_S_AXI_AWADDR_pin => axim_AWADDR_i, axi_ext_master_conn_0_S_AXI_AWLEN_pin => axim_AWLEN_i, axi_ext_master_conn_0_S_AXI_AWSIZE_pin => axim_AWSIZE_i, axi_ext_master_conn_0_S_AXI_AWBURST_pin => axim_AWBURST_i, axi_ext_master_conn_0_S_AXI_AWCACHE_pin => axim_AWCACHE_i, axi_ext_master_conn_0_S_AXI_AWPROT_pin => axim_AWPROT_i, axi_ext_master_conn_0_S_AXI_AWVALID_pin => axim_AWVALID_i, axi_ext_master_conn_0_S_AXI_AWREADY_pin => axim_AWREADY_o, axi_ext_master_conn_0_S_AXI_WDATA_pin => axim_WDATA_i, axi_ext_master_conn_0_S_AXI_WSTRB_pin => axim_WSTRB_i, axi_ext_master_conn_0_S_AXI_WLAST_pin => axim_WLAST_i, axi_ext_master_conn_0_S_AXI_WVALID_pin => axim_WVALID_i, axi_ext_master_conn_0_S_AXI_WREADY_pin => axim_WREADY_o, axi_ext_master_conn_0_S_AXI_BRESP_pin => axim_BRESP_o, axi_ext_master_conn_0_S_AXI_BVALID_pin => axim_BVALID_o, axi_ext_master_conn_0_S_AXI_BREADY_pin => axim_BREADY_i, axi_ext_master_conn_0_S_AXI_ARADDR_pin => axim_ARADDR_i, axi_ext_master_conn_0_S_AXI_ARLEN_pin => axim_ARLEN_i, axi_ext_master_conn_0_S_AXI_ARSIZE_pin => axim_ARSIZE_i, axi_ext_master_conn_0_S_AXI_ARBURST_pin => axim_ARBURST_i, axi_ext_master_conn_0_S_AXI_ARCACHE_pin => axim_ARCACHE_i, axi_ext_master_conn_0_S_AXI_ARPROT_pin => axim_ARPROT_i, axi_ext_master_conn_0_S_AXI_ARVALID_pin => axim_ARVALID_i, axi_ext_master_conn_0_S_AXI_ARREADY_pin => axim_ARREADY_o, axi_ext_master_conn_0_S_AXI_RDATA_pin => axim_RDATA_o, axi_ext_master_conn_0_S_AXI_RRESP_pin => axim_RRESP_o, axi_ext_master_conn_0_S_AXI_RLAST_pin => axim_RLAST_o, axi_ext_master_conn_0_S_AXI_RVALID_pin => axim_RVALID_o, axi_ext_master_conn_0_S_AXI_RREADY_pin => axim_RREADY_i ・・・ process(plclk) begin if(plclk'event and plclk = '1') then if(axis_reset = '0') then axim_WVALID_i <= '0'; axim_AWVALID_i <= '0'; else if(axis_wren(16) = '1') then axim_AWADDR_i <= axis_wdata; end if; if(axis_wren(17) = '1') then axim_AWLEN_i <= axis_wdata(7 downto 0) - 1; axim_remain <= axis_wdata(7 downto 0) - 1; end if; axim_AWSIZE_i <= "010"; -- 32bit? axim_AWBURST_i <= "01"; -- increment axim_AWCACHE_i <= "0011"; -- Normal Non-cacheable Bufferable axim_AWPROT_i <= "000"; case axim_state is when "00" => axim_WVALID_i <= '0'; axim_AWVALID_i <= '0'; axim_WSTRB_i <= "0000"; axim_WLAST_i <= '0'; axim_BREADY_i <= '1'; if(axis_wren(18) = '1') then axim_WDATA_i <= axis_wdata; axim_state <= "01"; end if; when "01" => axim_AWVALID_i <= '1'; if(axim_AWREADY_o = '1') then axim_state <= "10"; end if; when "10" => axim_AWVALID_i <= '0'; if(axim_WREADY_o = '1') then axim_remain <= axim_remain - 1; axim_WSTRB_i <= "1111"; axim_WVALID_i <= '1'; axim_WDATA_i <= axim_WDATA_i + 1; else axim_WVALID_i <= '0'; end if; if(axim_remain = 0) then axim_WLAST_i <= '1'; axim_state <= "11"; end if; when others => axim_WVALID_i <= '0'; axim_WSTRB_i <= "0000"; axim_WLAST_i <= '0'; axim_state <= "00"; end case; end if; end if; end process; axim_ARADDR_i <= (others => '0'); axim_ARLEN_i <= (others => '0'); axim_ARSIZE_i <= (others => '0'); axim_ARBURST_i <= (others => '0'); axim_ARCACHE_i <= (others => '0'); axim_ARPROT_i <= (others => '0'); axim_ARVALID_i <= '0'; axim_RREADY_i <= '0';
| 固定リンク
コメント
こんにちは。
zynqを用いて大学で研究を行っている学生です。
最近 zynq にふれ勉強している最中なのですが、
「ZYNQでFPGA(PL)からARM(PS)のDDR3メモリへDMA転送」
がまさに研究で行いたいことなのですが、まだ初心者のためなかなか理解が追いつかず、かなり苦戦しています。
行おうとしている事は PL で信号処理をさせその出力 1bit をDMA を用いて PS に転送を行う事です。
よろしければ PS デザインを参考にさせていただけないでしょうか?
使用機器は ZedBoard(Zynq-7000 All Programmable SoC搭載)を使用しています。
投稿: deta | 2016.01.12 13:18
DMA転送が研究ではないですよね?
デザインは一般向けの公開はしていないので・・
投稿: なひたふ | 2016.01.13 05:50
Xil_DCacheInvalidateRange()についてハマっていました。
こちらの記事を参考にしたところ、AXI CDMAでのDMA転送に成功いたしました。
どうもありがとうございました。
投稿: 7of9 | 2021.12.28 11:07