Vivado HLSのチュートリアルの続きをやっていこう。Introduction Lab.3だ。
今回は、FPGAのデザインに最適になるように、インタフェースの部分を調整するらしい。
まずは、CUI上で論理合成とシミュレーションまでを一通り終わらせる。これがベースデザインとなり、調整するようだ。
ベースデザインの合成とソリューションのコピー
vivado_hls –f run_hls.tcl
次に、デザインを起動して、GUI上での作業に移る。
vivado_hls –f run_hls.tcl
以下のように、ポートの変更をするようだ。
- ポートCにはRAMが接続される。
- ポートXはValid信号が接続される。
- ポートYはValid信号が接続される。
まずは、現在のソリューションをベースにして、新しいソリューションを作成する。
[Project]→[New Solution]を選択して、ソリューションのコピーを作成する。
入出力ポートの改良
入出力ポートに改造を施す。[Directive]タブを選択し、ポートcを右クリックして[Insert Directive]を選択する。
- [Directive]を[RESOURCE]に設定
- [core(optional)]を選択し、[RAM_1P_BRAM]を選択する。
- [Destination]と[Source File]に設定する。これにより、Cソース側にプリミティブが付加されることになる。
- 同様に、ポートX、ポートYにvalid信号を設定させる。 -- Optionsの[mode]に、[ap_vld]を設定する。これで、Valid信号が設定されるようになる。 -- [Destination]を[Source File]に設定する。
ディレクティブを追加することにより、ソースファイルに以下のような記述が追加された。
void fir ( data_t *y, coef_t c[N], data_t x ) { #pragma HLS INTERFACE ap_vld port=y #pragma HLS INTERFACE ap_vld port=x #pragma HLS RESOURCE variable=c core=RAM_1P_BRAM
pragmaにより、コンパイラに指定を追加するようだ。
合成の実行
[Solution]→[Run C Synthesis]→[Active Solution]をクリックする。
これで合成が実行される。合成結果を確認しよう。ベースデザインとのDiffを取るとどうなるだろう。
$ diff -y -W200 solution1/syn/verilog/fir.v solution2/syn/verilog/fir.v // ============================================================== // ============================================================== // RTL generated by Vivado(TM) HLS - High-Level Synthesis from C, C++ and SystemC // RTL generated by Vivado(TM) HLS - High-Level Synthesis from C, C++ and SystemC // Version: 2015.4 // Version: 2015.4 // Copyright (C) 2015 Xilinx Inc. All rights reserved. // Copyright (C) 2015 Xilinx Inc. All rights reserved. // // // =========================================================== // =========================================================== `timescale 1 ns / 1 ps `timescale 1 ns / 1 ps (* CORE_GENERATION_INFO="fir,hls_ip_2015_4,{HLS_INPUT_TYPE=c,HLS_INPUT_FLOAT=0,HLS_INPUT_FIXED=0 | (* CORE_GENERATION_INFO="fir,hls_ip_2015_4,{HLS_INPUT_TYPE=c,HLS_INPUT_FLOAT=0,HLS_INPUT_FIXED=0 module fir ( module fir ( ap_clk, ap_clk, ap_rst, ap_rst, ap_start, ap_start, ap_done, ap_done, ap_idle, ap_idle, ap_ready, ap_ready, y, y, y_ap_vld, y_ap_vld, c_address0, c_address0, c_ce0, c_ce0, c_q0, c_q0, x | x, > x_ap_vld ); );
ポートが追加されている。でも、良く見てみると、ポートYは最初からValid信号が追加されていたのか。 これはVivadoが自動的に追加していたと考えたほうが良いだろう。
デザインの解析
Analysisペインから、ステートマシンの様子を観察することができる。
- まず、Xポートから信号を読み込む。
- data(read)、つまりブロックRAMからのデータリードには、2サイクルかかることが分かる。
- 乗算には3サイクルかかっている。
- 全体では、1ループにC1からC7までの7サイクルが消費されている。
実際のデザインでは?
3サイクル消費されている乗算とか、HDLではどのように記述されているのだろう?生成されたHDLを探すと、その答えがある。
module fir_mul_32s_32s_32_3_Mul3S_0(clk, ce, a, b, p); input clk; input ce; input[32 - 1 : 0] a; // synthesis attribute keep a "true" input[32 - 1 : 0] b; // synthesis attribute keep b "true" output[32 - 1 : 0] p; reg signed [32 - 1 : 0] a_reg0; reg signed [32 - 1 : 0] b_reg0; wire signed [32 - 1 : 0] tmp_product; reg signed [32 - 1 : 0] buff0; assign p = buff0; assign tmp_product = a_reg0 * b_reg0; always @ (posedge clk) begin if (ce) begin a_reg0 <= a; b_reg0 <= b; buff0 <= tmp_product; end end endmodule
入力データをオペランドにセットするのに1サイクル、計算に1サイクルで、あれ、2サイクルしか消費されてなくない? で、その結果が、
fir_mul_32s_32s_32_3 #( .ID( 1 ), .NUM_STAGE( 3 ), .din0_WIDTH( 32 ), .din1_WIDTH( 32 ), .dout_WIDTH( 32 )) fir_mul_32s_32s_32_3_U0( .clk( ap_clk ), .reset( ap_rst ), .din0( c_load_reg_223 ), .din1( data1_reg_126 ), .ce( grp_fu_174_ce ), .dout( grp_fu_174_p2 ) ); ... always @ (posedge ap_clk) begin if ((ap_const_logic_1 == ap_sig_cseq_ST_st7_fsm_6)) begin tmp_6_reg_228 <= grp_fu_174_p2; end end
一回レジスタに入っているということなのかなあ、良く分からん。
デザインのスループットを上げるための最適化
以下のことを行って、デザインの性能を上げる。
- ループアンローリング
- シフトレジスタの実装
新しいソリューションを作成して、以下の最適化を行う。
ループアンローリング
ソースコードを開いた状態で、[Directive]ペインの、[Shift_Accum_Loop]を右クリックし、[Insert Directive]を選択する。 ダイアログが開くので、[UNROLL]ディレクティブを選択する。
シフトレジスタの実装
ソースコードを開いた状態で、[Directive]ペインの、[shift_reg]を右クリックし、[Insert Directive]を選択する。 [ARRAY_PARTITION]ディレクティブを選択する。
合成結果の比較
これまでに作成した、
- ベースデザイン
- ポート属性変更デザイン
- スループット最適化デザイン
の比較をすることができるらしい。
レイテンシや、リソースの比較などを行うことができる。