パイプラインでの構造体のサイズの影響 - 2023.2 日本語

Vitis 高位合成ユーザー ガイド (UG1399)

Document ID
UG1399
Release Date
2023-12-18
Version
2023.2 日本語

関数インターフェイスで使用される構造体のサイズは、ループ本体のインターフェイスにアクセスできる関数のループのパイプライン処理に悪影響を与える可能性があります。2 つの M_AXI インターフェイスを持つ次のコード例を参照してください。

struct A { /* Total size = 192 bits (32 x 6) or 24 bytes */
    int s_1;
    int s_2;
    int s_3;
    int s_4;
    int s_5;
    int s_6;
};
 
void read(A *a_in, A buf_out[NUM]) {
READ:
    for (int i = 0; i < NUM; i++)
    {
        buf_out[i] = a_in[i];
    }
}
 
void compute(A buf_in[NUM], A buf_out[NUM], int size) {
COMPUTE:
    for (int j = 0; j < NUM; j++)
    {
        buf_out[j].s_1 = buf_in[j].s_1 + size;
        buf_out[j].s_2 = buf_in[j].s_2;
        buf_out[j].s_3 = buf_in[j].s_3;
        buf_out[j].s_4 = buf_in[j].s_4;
        buf_out[j].s_5 = buf_in[j].s_5;
        buf_out[j].s_6 = buf_in[j].s_6 % 2;
    }
}
  
void write(A buf_in[NUM], A *a_out) {
    WRITE:
    for (int k = 0; k < NUM; k++)
    {
        a_out[k] = buf_in[k];
    }
}
 
void dut(A *a_in, A *a_out, int size)
{
#pragma HLS INTERFACE m_axi port=a_in bundle=gmem0
#pragma HLS INTERFACE m_axi port=a_out bundle=gmem1
    A buffer_in[NUM];
    A buffer_out[NUM];
  
#pragma HLS dataflow
    read(a_in, buffer_in);
    compute(buffer_in, buffer_out, size);
    write(buffer_out, a_out);
}

上記の例では、構造体 A のサイズが 192 ビットであり、2 のべき乗ではありません。前述のように、AXI4 インターフェイスすべてのデフォルトのサイズは 2 です。Vitis HLS は 2 つの M_AXI インターフェイス (a_in および a_out) のサイズを自動的に 256 (192 ビットのサイズに最も近い 2 のべき乗) に設定します。これは、次に示すログ ファイルにレポートされます。

INFO: [HLS 214-241] Aggregating maxi variable 'a_out' with compact=none mode in 
256-bits (example.cpp:49:0)
INFO: [HLS 214-241] Aggregating maxi variable 'a_in' with compact=none mode in 256-bits 
(example.cpp:49:0)

これは、構造体データを書き出すときに、最初の書き込みは 1 サイクルで最初のバッファーに 24 バイトを書き込みますが、2 回目の書き込みは、最初のバッファーの残りの 8 バイトに 8 バイトを書き込んでから、2 つ目のバッファーに 16 バイトを書き込むので、2 つの書き込みになるということを意味します (次の図を参照)。

図 1. アライメントのされない書き込みサイクル

これにより、II = 1 ではなく II = 2 が必要になるため、write() 関数の WRITE ループの II に II 違反が発生します。読み出し時にも同様の動作が発生します。このため、read() 関数でも II = 2 が必要なので、II 違反が発生します。Vitis HLS は、read() および write() 関数の II 違反に対して次の警告メッセージを表示します。

WARNING: [HLS 200-880] The II Violation in module 'read_r' (loop 'READ'): Unable 
to enforce a carried dependence constraint (II = 1, distance = 1, offset = 1) between 
bus read operation ('gmem0_addr_read_1', example.cpp:23) on port 'gmem0' (example.cpp:23) 
and bus read operation ('gmem0_addr_read', example.cpp:23) on port 'gmem0' (example.cpp:23). 

WARNING: [HLS 200-880] The II Violation in module 'write_Pipeline_WRITE' (loop 'WRITE'): 
Unable to enforce a carried dependence constraint (II = 1, distance = 1, offset = 1) 
between bus write operation ('gmem1_addr_write_ln44', example.cpp:44) on port 'gmem1' 
(example.cpp:44) and bus write operation ('gmem1_addr_write_ln44', example.cpp:44) on 
port 'gmem1' (example.cpp:44).

このような II の問題を修正するには、8 バイトの追加バイトで構造体 A をパディングして、常に一度に 256 ビット (32 バイト) を書き込むか、次の表に示すほかの方法を使用します。これにより、スケジューラは、II=1 で READ/WRITE ループの読み出し/書き込みをスケジュールできるようになります。

表 1. 構造体のアライメント
コード ブロック 詳細
struct A {  
    int s_1;                                                
    int s_2;                                                
    int s_3;
    int s_4;
    int s_5;
    int s_6;
    int pad_1;
    int pad_2;
};
必要なパディング要素を追加して、構造体の合計サイズを 256 ビット (32 x 8) または 32 バイトとして定義します。
struct A {                        
    int s_1;                                                
    int s_2;                                                
    int s_3;
    int s_4;
    int s_5;
    int s_6;
 } __attribute__ ((aligned(32)));  
標準の __aligned__ 属性を使用します。
struct alignas(32) A {
    int s_1;                                                
    int s_2;                                                
    int s_3;
    int s_4;
    int s_5;
    int s_6;
 }
C++ 標準 alignas 型指示子を使用して、変数とユーザー定義型のカスタム アライメントを指定します。