タイトルの通りです。
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';
最近のコメント