« ET2013へのご来場ありがとうございました | トップページ | ZYNQのPLからPSへ大規模DMA »

2013.11.24

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がよさそうです。

考えている構成は次のようになります。

Axi_master_1

32bGP AXI Master Ports(CPUがマスタで、コントローラはスレーブ)を通じて、転送先のメモリアドレスや長さなどの条件を設定し、計測データをHigh Performance 32b/64b Slave Ports(コントローラがマスタで、DDR3メモリがスレーブ)に入れるというわけです。

つまり、AXIマスタを作らなければなりません。実際にやってみて、MITOUJTAGのBLOGANA(簡易FPGA内蔵ロジアナ。ChipScopeみたいなもの)機能を使って波形を見てみました。

Axi_master_2

要するに、

  1. 書き込み先のアドレスをAWADDRに出力し
  2. 書き込みたいビート数(ワード数)をAWLENに出力する。AWLENの値は書き込みたい長さ-1である。(つまり16ワード書き込みたいならば0x0fを与える)
  3. AWREADYが'1'であるならばスレーブがアドレス受け入れ可能なので、AWVALIDを'1'にアサートする
  4. WREADYが'1'であるならばスレーブがデータ受け入れ可能なので、WVALIDを'1'にアサートするとともに、WDATAに32bitのデータを与える。そのときWSTRBには"1111"を与えると全バイトが書き込まれる。マスクしたい場合は当該バイトを0にする。
  5. 最後の1ワードを書き込むときには、WLASTを'1'にする。
  6. しばらくして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';

|

« ET2013へのご来場ありがとうございました | トップページ | ZYNQのPLからPSへ大規模DMA »

コメント

こんにちは。
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

コメントを書く



(ウェブ上には掲載しません)




« ET2013へのご来場ありがとうございました | トップページ | ZYNQのPLからPSへ大規模DMA »