Altera SDK for OpenCL解体新書
ホストとデバイスの関係
2016.06.12(金)
@Vengineer
ホストとデバイスの関係(PCIeデバイス)
PCIeデバイスの場合は、下記のような構成になる
CPU
ホスト側の
メモリ
デバイス側の
メモリ
Kernel−A
Kernel−B
Kernel−C
DMA
PCIe PCIe
DMA
Altera SDK for OpenCLでのBSPとは?
CPU
ホスト側の
メモリ
Kernel−A
Kernel−B
Kernel−C
DMA
PCIe PCIe
DMABSPに相当する部分
デバイス側の
メモリ
MIF
生成されるHDLコード
CPU
ホスト側の
メモリ
デバイス側の
メモリ
Kernel−A
Kernel−B
Kernel−C
DMA
PCIe PCIe
DMAaocが生成するHDLコード
MIF
例題:vector addition
https://www.olcf.ornl.gov/tutorials/opencl-vector-addition/
double a[N], b[N], c[N];
vector_add( a, b, c, N );
void vector_add( double* a, double* b, double* c,
const unsigned int n )
{
for( unsigned int i=0 ; i<n ; i++ )
c[i] = a[i] + b[i];
}
デバイス側のOpenCLコード
const char *kernelSource =
#pragma OPENCL EXTENSION cl_khr_fp64 : enable
_kernel void vecAdd(__global double *a, __global double *b,
             __global double *c, const unsigned int n)
{
//Get our global thread ID
int id = get_global_id(0);
//Make sure we do not go out of bounds
if (id < n)
c[id] = a[id] + b[id];
}
ホスト側のCコード(メモリの割当)
h_a = (double*)malloc(bytes);
h_b = (double*)malloc(bytes);
h_c = (double*)malloc(bytes);
d_a = clCreateBuffer(context, CL_MEM_READ_ONLY , bytes, NULL, NULL);
d_b = clCreateBuffer(context, CL_MEM_READ_ONLY , bytes, NULL, NULL);
d_c = clCreateBuffer(context, CL_MEM_WRITE_ONLY , bytes, NULL, NULL);
ホスト側のCコード(実行部)
err = clEnqueueWriteBuffer(queue, d_a, CL_TRUE, 0, bytes, h_a, 0, NULL,
NULL);
err |= clEnqueueWriteBuffer(queue, d_b, CL_TRUE, 0, bytes, h_b, 0, NULL,
NULL);
err = clSetKernelArg(kernel, 0, sizeof(cl_mem), &d_a);
err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &d_b);
err |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &d_c);
err |= clSetKernelArg(kernel, 3, sizeof(unsigned int), &n);
err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &globalSize,
&localSize,
0, NULL,
NULL);
clFinish(queue);
ホスト側のCコード(メモリの開放)
clReleaseMemObject(d_a);
clReleaseMemObject(d_b);
clReleaseMemObject(d_c);
free(h_a);
free(h_b);
free(h_c);
処理フロー
1) ホスト側のメモリからデバイス側のメモリにデータを移動
clEnqueueWriteBuffer
2) カーネルへのパラメータ設定
  clSetKernelArg
3) カーネルを実行
clEnqueueTask / clEnqueuNDRangeKernel
4) カーネルの終了待ち
clFinish
5) デバイス側のメモリからホスト側のメモリにデータを移動
  clEnqueueReadBuffer
clEnqueuWriteBuffer
ホスト側のメモリからデバイス側のメモリへデータを移動する
CPU
ホスト側の
メモリ
デバイス側の
メモリ
Kernel−B
DMA
PCIe PCIe
DMA
clSetKernelArg
カーネルへのパラメータ設定
CPU
ホスト側の
メモリ
デバイス側の
メモリ
Kernel−B
DMA
PCIe PCIe
DMA
clEnqueuTask / clEnqueueNDRageKernel
カーネルの実行
CPU
ホスト側の
メモリ
デバイス側の
メモリ
Kernel−B
DMA
PCIe PCIe
DMA
clFinish
カーネルの終了待ち
CPU
ホスト側の
メモリ
デバイス側の
メモリ
Kernel−B
DMA
PCIe PCIe
DMA
clEnqueuReadBuffer
デバイス側のメモリからホスト側のメモリへデータを移動する
CPU
ホスト側の
メモリ
デバイス側の
メモリ
Kernel−B
DMA
PCIe PCIe
DMA
実行時間
CPUの実行時間に比べて、FPGAの実行時間が短くならないと
FPGAでのアクセレーションは意味が無い!
FPGAの実行時間 = 1) + 2) + 3) + 4) + 5)
 1) ホスト側のメモリからデバイス側のメモリにデータを移動
 2) カーネルへのパラメータ設定
 3) カーネルを実行
 4) カーネルの終了待ち
 5) デバイス側のメモリからホスト側のメモリにデータを移動
ホストとデバイスの関係(SoCデバイス)
SoCデバイスの場合は、下記のような構成になる
CPU
ホスト側の
メモリ
Kernel−A
Kernel−B
Kernel−C
PCIeデバイスでは必要だった、
データ移動のためのDMAがない
ホスト側のCコード(メモリの割当)
cl_mem d_a = clCreateBuffer(context,CL_MEM_ALLOC_HOST_PTR, bytes, NULL, NULL);
cl_mem d_b = clCreateBuffer(context,CL_MEM_ALLOC_HOST_PTR, bytes, NULL, NULL);
cl_mem d_c = clCreateBuffer(context,CL_MEM_ALLOC_HOST_PTR, bytes, NULL, NULL);
// clEnqueuWriteBuffer/clEnqueueReadBufferの代わりに
clEnqueueMapBufferが必要(ホスト側のメモリをマップする)
double *h_a = (double*)clEnqueueMapBuffer (queue, d_a, CL_TRUE, CL_MAP_READ,
0, bytes, 0, NULL, NULL );
double *h_b = (double*)clEnqueueMapBuffer (queue, d_b, CL_TRUE, CL_MAP_READ,
0, bytes, 0, NULL, NULL );
double *h_c = (double*)clEnqueueMapBuffer (queue, d_c, CL_TRUE, CL_MAP_WRITE,
0, bytes, 0, NULL, NULL );
ホスト側のCコード(実行部)
err = clSetKernelArg(kernel, 0, sizeof(cl_mem), &d_a);
err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &d_b);
err |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &d_c);
err |= clSetKernelArg(kernel, 3, sizeof(unsigned int), &n);
err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &globalSize,
&localSize,
0, NULL,
NULL);
clFinish(queue);
ホスト側のCコード(メモリの開放)
// clEnqueueUnMapMemObjectが必要
clEnqueueUnmapMemObject(queue, d_a, h_a, 0, NULL, NULL );
clEnqueueUnmapMemObject(queue, d_b, h_b, 0, NULL, NULL );
clEnqueueUnmapMemObject(queue, d_c, h_c, 0, NULL, NULL );
clReleaseMemObject(d_a);
clReleaseMemObject(d_b);
clReleaseMemObject(d_c);
// ホスト側のメモリ開放は必要なし
メモリ割当の違い
PCIeデバイス
 ホスト側のメモリ割当
 デバイス側のメモリ割当
  ....
 デバイス側のメモリ開放
 ホスト側のメモリ開放
SoCデバイス
 デバイス側のメモリ割当
 ホスト側のメモリマッピング
  ....
 ホスト側のメモリアンマッピング
 デバイス側のメモリ開放
SoCデバイスでは、CMAを使う
CPU
ホスト側の
メモリ
Kernel−A
Kernel−B
Kernel−C
ホスト側のメモリの一部を
デバイス側のメモリとして使用し、
CPU側からアクセスできるようにマップする
Linuxをブートするときに、
引数として、cma=128M のようにして
事前に割り当てる
CMA(Continuous Memory Allocator)
ただし、CPUからは非キャッシュ領域
参考資料)、https://aelseb.wordpress.com/2015/04/11/contiguous-memory-on-arm-and-cache-
coherency/
実行時間
CPUの実行時間に比べて、FPGAの実行時間が短くならないと
FPGAでのアクセレーションは意味が無い!
FPGAの実行時間 = 1) + 2) + 3) + 4) + 5)
 1) CPUがCMA領域(非キャッシュ領域)のメモリにデータを書き込む
 2) カーネルへのパラメータ設定
 3) カーネルを実行
 4) カーネルの終了待ち
 5) CPUがCMA領域(非キャッシュ領域)のメモリからデータを読み込む
おしまい

Altera SDK for OpenCL解体新書 : ホストとデバイスの関係