hello_afuより少し複雑なサンプルとしてDMAを使ったサンプルに手をいれてみることにします.DMAを使ったサンプルはユーザロジックがQsysを使ってデザインされています.そこで,今回はQsys上に新しいコンポーネントを追加する開発フローを試してみます.
おおまかな開発の流れ
おおまかには次のような流れで開発をします.
- ベースとするデザイン(今回はdma_afu)をビルドする
- Quartusでビルドしたデザインを開いて,モジュール構成を確認する
- ユーザロジックのQsysファイルをQsysで開く
- Qsysで新しいコンポーネントを作成
- Qsysで作成したコンポーネントの中身を実装
- 変更後のデザインをビルドする
dma_afuを合成して構成を確認
dma_afuは,(1)ソフトウェアでホストPCからPACボード上のメモリにデータを転送,(2)DMAエンジンで転送されたデータを別のメモリにコピー,(3)コピーされたデータをホストPCから読み出す,というサンプルです.
ビルド手順どおり,一度ビルドします.
% source ~/data/inteldevstack/init_env.sh # パスはIntel Dev Stackインストール先による
% cd ${OPAE_PLATFORM_ROOT}/hw/samples/dma_afu
% afu_synth_setup --source hw/rtl/filelist.txt build_synth
% cd build_synth
% ${OPAE_PLATFORM_ROOT}/bin/run.sh
合成が終わると,Quartusで,build/dcp.qpfを開いてモジュール構成を確認することができます.Project Navigatorにdcp_topしか表示されていない時は(多分,そうだと思う),afu_fitをafu_synthに変更するとモジュール構成が確認できます.
ここではgreen_bs→platform_shim_ccip_std_afu→afu_inst→u0の下のdma_test_system.qsysが,処理本体に相当しています.
Qsysでdma_test_system.qsysファイルを開く
新しいモジュールを処理本体に追加するために,dma_test_system.qsysをQsysで開きます.
Qsysで新しいコンポーネントを作成する
Qsysで新しいコンポーネントを作る方法の一つは,ファイルメニューのNew Component…で開くコンポーネント作成ダイアログを使う方法です.ファイル作成ダイアログは次のような感じ.
このダイアログで,Avalon MM-Slave I/F,クロック,リセットを持つコンポーネントを作ります.
作成したインターフェースを持ったVerilogモジュールの雛形を作ることができます.
インターフェースをセット,ファイルを作ればコンポーネントの作成は完了.
新しいコンポーネントをQsysブロックにくみこむ
IPカタログに作成したコンポーネントがリストアップされるので,選択→ダブルクリックしてQsysファイルに組み込みます.今回は,ソフトウェアからのアクセスを受け付ける dma_test_system_mm_bridge_0 のAvalon MM-Masterに,作成したコンポーネントのAvalon MM-Slaveをぶら下げて,ソフトウェアから読み書きできるようにします.
すでに登録されているコンポーネントとアドレスで区別するために,適切なアドレスを設定する必要があります.
作成したコンポーネントの中身を実装する
作成した雛形ファイルを開いて,ロジックを実装します.たとえば,こんな感じにしてみました.
`timescale 1 ps / 1 ps
module miyo_test_component (
input wire [3:0] avalon_slave_1_address, // avalon_slave.address
input wire avalon_slave_1_read, // .read
output wire [63:0] avalon_slave_1_readdata, // .readdata
input wire avalon_slave_1_write, // .write
input wire [63:0] avalon_slave_1_writedata, // .writedata
input wire clock_sink_clk, // clock_sink.clk
input wire reset_sink_reset // reset_sink.reset
);
// TODO: Auto-generated HDL template
reg [63:0] d0, d1;
reg [63:0] q;
assign avalon_slave_1_readdata = q;
always @(posedge clock_sink_clk)
begin
if (avalon_slave_1_write == 1'b1) begin
case(avalon_slave_1_address)
4'h0 : d0 <= avalon_slave_1_writedata * 2;
4'h2 : d1 <= avalon_slave_1_writedata * 3;
endcase
end
end
always @(posedge clock_sink_clk)
begin
if (avalon_slave_1_read == 1'b1) begin
case(avalon_slave_1_address)
4'h0 : q <= d0;
4'h2 : q <= d1;
endcase
end
end
endmodule
もう一度ビルドする
QsysでGenerate,Quartusで一度Elaborate & Synthesisを通しておきましょう.Elaborate & Synthesisが通ったら,ターミナルに戻ってビルドします.が,ビルドする前に,新しく作成したコンポーネント用のファイルをビルドスクリプトに読んでもらう必要がありますので,ファイルセット定義を修正する必要があります.
% cd ${OPAE_PLATFORM_ROOT}/hw/samples/dma_afu
で作業フォルダを移動して,hw/rtl/filelist.txt を開きます.追加したコンポーネント名に応じたipファイルを,filelist.txtに追加します.たとえば,私の場合は, qsys/${OPAE_PLATFORM_FPGA_FAMILY}/ip/dma_test_system/dma_test_system_miyo_test_component_0.ip でした.これを,filelist.txtの末尾に追加しました.
dma_afu.json
# CCI-P avmm shim
QI:BBB_ccip_avmm/hw/par/ccip_avmm_addenda.qsf
SI:BBB_ccip_avmm/hw/sim/ccip_avmm_sim_addenda.txt
# MPF
+define+MPF_PLATFORM_DCP_PCIE=1
QI:BBB_cci_mpf/hw/par/qsf_cci_mpf_PAR_files.qsf
SI:BBB_cci_mpf/hw/sim/cci_mpf_sim_addenda.txt
...略...
qsys/${OPAE_PLATFORM_FPGA_FAMILY}/ip/msgdma_bbb/msgdma_bbb_pipe_stage_0.ip
qsys/${OPAE_PLATFORM_FPGA_FAMILY}/ip/msgdma_bbb/msgdma_bbb_pipe_stage_m.ip
qsys/${OPAE_PLATFORM_FPGA_FAMILY}/ip/msgdma_bbb/msgdma_bbb_magic_number_rom_0.ip
qsys/${OPAE_PLATFORM_FPGA_FAMILY}/ip/dma_test_system/dma_test_system_miyo_test_component_0.ip
ファイルリストを更新したら,
% afu_synth_setup --force --source hw/rtl/filelist.txt build_synth
% cd build_synth
% ${OPAE_PLATFORM_ROOT}/bin/run.sh
と,ビルドスクリプトを実行します.
動作確認
ソフトウェア側も修正が必要です.追加したレジスタに読み書きできるように,
uint64_t data;
res = fpgaWriteMMIO64(afc_h, 0, 0x80, 0x1212121212121212);
res = fpgaReadMMIO64(afc_h, 0, 0x80, &data);
printf("[test] Reading Scratch Register (Byte Offset=%08x) = %08lx\n", 0x80, data);
res = fpgaWriteMMIO64(afc_h, 0, 0x90, 0x3434343434343434);
res = fpgaReadMMIO64(afc_h, 0, 0x90, &data);
printf("[test] Reading Scratch Register (Byte Offset=%08x) = %08lx\n", 0x90, data);
res = fpgaReadMMIO64(afc_h, 0, 0x80, &data);
printf("[test] Reading Scratch Register (Byte Offset=%08x) = %08lx\n", 0x80, data);
res = fpgaReadMMIO64(afc_h, 0, 0x90, &data);
printf("[test] Reading Scratch Register (Byte Offset=%08x) = %08lx\n", 0x90, data);
というようにソフトウェアのコード ${OPAE_PLATFORM_ROOT}/hw/samples/dma_afu/sw/dma_afu_test.c に追加したコンポーネントにアクセスするコードを追加します.アドレスはバイト単位で指定します.コードの修正が終わったら,makeでソフトウェアはビルドできます.
% cd ${OPAE_PLATFORM_ROOT}/hw/samples/dma_afu/sw
% make
合成が終わったbitファイル(gbsファイル)をFPGAにアップロードして,実行します.
% fpgaconf ../build_synth/*.gbs
% ./dma_afu_test
実行すると,DMAテストの後で,
[test] Reading Scratch Register (Byte Offset=00000080) = 2424242424242424
[test] Reading Scratch Register (Byte Offset=00000090) = 9c9c9c9c9c9c9c9c
[test] Reading Scratch Register (Byte Offset=00000080) = 2424242424242424
[test] Reading Scratch Register (Byte Offset=00000090) = 9c9c9c9c9c9c9c9c
のように
0x80
(PAC上ではレジスタアドレス0
)に0x1212121212121212
を書いて読むと,2倍された0x2424242424242424
が読める0x90
(PAC上ではレジスタアドレス2
)に0x3434343434343434
を書いて読むと,3倍された0x9c9c9c9c9c9c9c9c
が読める
と,正しく動作することが確認できました.