结构体大小对于流水打拍的影响 - 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_ina_out)的大小调整为 256 - 即与 192 位大小最接近的 2 的幂值(并在 log 日志文件中报告此行为,如下所示)。

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)

这其中隐含的意义是在写入结构数据并输出时,首次写入将在一个周期内把 24 字节写入第一个缓冲器,但第二次写入将必须把 8 字节写入第一个缓冲器内的剩余 8 字节,然后将 16 字节写入第二个缓冲器,这样就会生成 2 次写入,如下图所示。

图 1. 对齐错误的写入周期

这将导致函数 write() 中的 WRITE 循环的 II 发生 II 违例,因为它需要 II=2 而不是 II=1。读取时也会发生类似的行为,因此 read() 函数同样会发生 II 违例,因为它需要 II=2。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 字节),或者使用下表所示的其他替代方法。这将允许调度器在 READ/WRITE 循环内按 II=1 来调度读/写。

表 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 类型说明符来为变量和用户定义的类型指定定制对齐方式。