3.3 DMA転送によるデータの受け渡し
出典: PS3 Linux Information Site / Cell/B.E.のパワーを体験しよう
PPEプログラムとSPEプログラムの間のデータの受け渡しには、DMA転送と呼ばれるデータ転送方法が利用されます。ここでは、DMA転送の概要について解説し、DMA転送を利用してPPEプログラムとSPEプログラムの間でデータを受け渡す方法について解説します。
3.3.1 DMA転送の概要
第1.2節で解説したとおり、SPEはLSと呼ばれる専用のローカルメモリを搭載しており、SPEはLS以外のメモリに直接アクセスすることができません。そのため、PPEプログラムが利用するメインメモリ上のデータを、SPEプログラムが利用するためには、何らかの手段を用いてメインメモリとLSとの間でそのデータを転送する必要があります。
DMA (Direct Memory Access) 転送とは、CPUを介さずに周辺装置とメモリとの間で高速にデータ転送する手段です。Cellでは、DMA転送を利用してメインメモリとLS間のデータ転送をおこないます。DMA転送の実行は、基本的にSPEプログラムで制御されます。SPEプログラムが、メインメモリとLSとの間でDMA転送を実行する手順は以下のとおりです。
(1) DMA転送命令の発行
(2) メインメモリとLS間のDMA転送の実行
(3) DMA転送の完了待ち
手順 (1)~(3) を図に示すと、図 3.4のようになります。
手順 (1) では、SPEプログラムがMFCに対してDMA転送命令の発行をおこないます。DMA転送の実行は、MFC上のDMAコントローラと呼ばれるユニットが担当します。
手順 (2) では、DMA転送命令を受け取ったMFCが、DMAコントローラを介してDMA転送を開始します。SPEプログラムは、DMA転送命令を発行した後、MFCに実際のデータ転送は任せて、引き続きプログラムの実行を継続することができます。
SPEプログラム側でDMA転送の完了を保証したい場合には、DMA転送の完了を待たなければいけません。手順 (3) では、SPEプログラムがMFCによるDMA転送の完了を確認しています。
DMA転送の完了を確認すると、SPEプログラムは引き続き必要な処理を継続して実行していきます。
SPUがMFCに対して処理を要求する時や情報を取得する時は、チャネルと呼ばれるインタフェースを利用します。DMA転送命令の発行や、DMA転送の完了確認などでも、実際にチャネルが利用されています。チャネルは、目的に応じてさまざまな種類が存在します。チャネル・インタフェースに関する詳しい解説は、「Cell Broadband Engine Architecture」の第9章を参照してください。
3.3.2 DMA転送の基本的なプログラミング
ここでは、DMA転送の基本的なプログラミング方法について解説します。リスト (3-2) は、メインメモリからLSへのDMA転送を実行しているものです。
リスト (3-2) DMA転送の実行 (SPE用プログラム)
1 spu_mfcdma64(&abs_params, mfc_ea2h(argp), mfc_ea2l(argp), 2 sizeof(abs_params_t), tag, MFC_GET_CMD); 3 spu_writech(MFC_WrTagMask, 1 << tag); 4 spu_mfcstat(MFC_TAG_UPDATE_ALL);
(1) DMA転送命令の発行
まず、spu_mfcdma64()関数を用いて、MFCに対してDMA転送命令を発行します。
spu_mfcdma64()関数は、メインメモリとLS上の指定されたメモリ領域の間で、指定されたバイト数分のデータを、指定された方向に転送するようDMA転送命令を発行します。
なお、メインメモリとLSは、異なるアドレス空間を持ち、PPEプログラム側が利用するメインメモリのアドレスを実効アドレス (EA: Effective Address) と呼び、SPEプログラム側が利用するLSのアドレスをローカルストレージアドレス (LSA: Local Storage Address) と呼びます。
API定義 (3-7) spu_mfcdma64()関数のプロトタイプ宣言
void spu_mfcdma64(volatile void *lsa, unsigned int eahi, unsigned int ealow,
unsigned int size, unsigned int tagid, unsigned int cmd);
| 第1引数 | lsa | LS側のDMA転送に利用するメモリ領域の先頭アドレス (LSA) を指定します。 |
| 第2引数 | eahi | メインメモリ側のDMA転送に利用するメモリ領域の先頭アドレス (EA) 上位32ビットを指定します。 |
| 第3引数 | ealow | メインメモリ側のDMA転送に利用するメモリ領域の先頭アドレス (EA) 下位32ビットを指定します。 |
| 第4引数 | size | DMA転送するデータのサイズを指定します。 |
| 第5引数 | tagid | DMA転送用のタグ番号を0~31の範囲で指定します。このタグを用いて、DMA転送の完了を確認します。 |
| 第6引数 | cmd | DMA転送の方向を指定します。
メインメモリからLSへデータを転送 (GET) する場合はMFC_GET_CMDを指定します。 LSからメインメモリへデータを転送 (PUT) する場合はMFC_PUT_CMDを指定します。 |
なお、第1引数lsa、第2引数eahiおよび第3引数ealowに指定する、LSとメインメモリのDMA転送で利用するメモリ領域の先頭アドレス は、それぞれ16バイト境界に揃えられている必要があります。また、第4引数sizeに指定する、DMA転送するデータのサイズは、16バイトの倍数である必要があり、最大で16Kバイトまで指定することができます。これらは、Cellのアーキテクチャ上の制約で、それぞれの引数に適切な値が指定されない場合、正常にDMA転送が実行されません。DMA転送における制約については、第4.2節で詳しく解説します。
なお、64ビットの実効アドレスから、第2引数eahi、第3引数ealowで指定する上位32ビットと下位32ビットを得るためには、それぞれmfc_ea2h()マクロとmfc_ea2l()マクロを利用できます。
API定義 (3-8) mfc_ea2h()マクロのマクロ定義
#define mfc_ea2h(ea) (unsigned int)((unsigned long long)(ea)>>32)
| 第1引数 | ea | メインメモリ上の実効アドレス |
API定義 (3-9) mfc_ea2l()マクロのマクロ定義
#define mfc_ea2l(ea) (unsigned int)(ea)
| 第1引数 | ea | メインメモリ上の実効アドレス |
spu_mfcdma64()関数は、DMA転送命令を発行した後、DMA転送の完了を待たずに、呼び出し元に戻ってきます。実際のDMA転送はSPUを介さずにDMAコントローラが実行しているので、SPEプログラムはDMA転送の完了待ちを別途おこなう必要があります。用例 (3-9) にspu_mfcdma64()関数の使用例を示します。
用例 (3-9) DMA転送命令の発行
spu_mfcdma64(&spe_params, mfc_ea2h(argp), mfc_ea2l(argp),
sizeof(spe_prog_arg_t), tag, MFC_GET_CMD);
(2) メインメモリとLS間のDMA転送の実行
MFCは、SPEプログラムからDMA転送命令を受け取ると、DMA転送の方向に応じて、メインメモリとLS間でデータ転送を実行します。この処理は、SPEプログラムとは非同期で実行されます。
(3) DMA転送の完了待ち
spu_mfcdma64()関数は、DMA転送命令を発行した後、DMA転送の完了を待たずに、呼び出し元に戻ってくるため、SPEプログラム側ではDMA転送の完了を確認する必要があります。DMA転送の完了は、spu_writech()マクロ、spu_mfcstat()関数を用いて確認します。
spu_writech()マクロは、チャネル・インタフェースを介して、SPUからMFCへデータを渡すためのマクロです。
API定義 (3-10) spu_writech()マクロのマクロ定義
#define spu_writech(imm, ra) si_wrch((imm), si_from_uint(ra))
| 第1引数 | imm | チャネルを指定します。 |
| 第2引数 | ra | MFCに渡すデータを指定します。 |
DMA転送の完了を待つ場合は、まずspu_writech()マクロを利用して、DMA転送の完了を待つタグ番号をMFCへ伝えます。データを渡すチャネルは、WrTagMaskチャネルを利用します。また、DMA転送の完了を待つためのタグ番号に対応したビットフラグをデータとして渡します。DMA転送のタグ番号としては、spu_mfcdma64()関数の第5引数tagidで指定したタグ番号を使います。
指定したDMA転送のタグに対して、DMA転送が完了すると、タグの更新がおこなわれます。spu_mfcstat()関数を用いることで、タグの更新状況を確認できます。
API定義 (3-11) spu_mfcstat()関数のプロトタイプ宣言
unsigned int spu_mfcstat(unsigned int type);
| 第1引数 | type | DMA転送のタグの更新状況を確認する方法を指定します。ここでは、MFC_TAG_UPDATE_ALLを指定します。 |
DMA転送の完了を確認する際は、基本的にspu_writech()マクロとspu_mfcstat()関数を併用して呼び出してください。用例 (3-10) にDMA転送の完了を待つ際のspu_writech()マクロとspu_mfcstat()関数の使用例について示します。
用例 (3-10) DMA転送の完了待ち
spu_writech(MFC_WrTagMask, 1 << tag); spu_mfcstat(MFC_TAG_UPDATE_ALL);
ここまでが、DMA転送の基本的な実行方法となります。
3.3.3 DMA転送を利用した絶対値計算プログラム
それでは、実際にDMA転送を利用した典型的なプログラミング方法について例題を用いて解説していきます。リスト (3-3)、リスト (3-4) は、演習問題 (2-3) で出題した絶対値計算プログラムを、DMA転送を利用してSPEプログラムで計算するように書き換えたプログラムの一部です。
リスト (3-3) 絶対値計算プログラム (PPE用プログラム) (一部抜粋)
1 abs_params.ea_in = (unsigned long) in; 2 abs_params.ea_out = (unsigned long) out; 3 abs_params.size = SIZE; 4 5 ret = spe_context_run(spe, &entry, 0, &abs_params, NULL, &stop_info);
リスト (3-4) 絶対値計算プログラム (SPE用プログラム) (一部抜粋)
1 /* DMA Transfer 1 : GET input/output parameters */
2 spu_mfcdma64(&abs_params, mfc_ea2h(argp), mfc_ea2l(argp),
3 sizeof(abs_params_t), tag, MFC_GET_CMD);
4 spu_writech(MFC_WrTagMask, 1 << tag);
5 spu_mfcstat(MFC_TAG_UPDATE_ALL);
6
7 /* DMA Transfer 2 : GET input data */
8 spu_mfcdma64(in_spe, mfc_ea2h(abs_params.ea_in), mfc_ea2l(abs_params.ea_in),
9 abs_params.size * sizeof(float), tag, MFC_GET_CMD);
10 spu_writech(MFC_WrTagMask, 1 << tag);
11 spu_mfcstat(MFC_TAG_UPDATE_ALL);
12
13 /* Calculate absolute values */
14 for (i = 0; i < abs_params.size; i++) {
15 if (in_spe[i] > 0) {
16 out_spe[i] = in_spe[i];
17 } else {
18 out_spe[i] = in_spe[i] * -1;
19 }
20 }
21
22 /* DMA Transfer 3 : PUT output data */
23 spu_mfcdma64(out_spe, mfc_ea2h(abs_params.ea_out), mfc_ea2l(abs_params.ea_out),
24 abs_params.size * sizeof(float), tag, MFC_PUT_CMD);
25 spu_writech(MFC_WrTagMask, 1 << tag);
26 spu_mfcstat(MFC_TAG_UPDATE_ALL);
まず、DMA転送を利用したSPEプログラムの基本的な手順は、以下のとおりです。
(1) DMA転送を利用してメインメモリからデータを取得する (GET)
(2) データを処理する
(3) DMA転送を利用してメインメモリへ処理結果を返す (PUT)
また、SPEプログラムからは最初にメインメモリ上のどの領域にどのようなデータが存在するのかは直接知ることはできません。そこで、PPEプログラム側ではSPEプログラム側で処理させたいデータの情報 (入出力データの実効アドレスやデータサイズ) をパラメータ・セットとしてメインメモリ上に格納しておき、spe_context_run()関数の第4引数に、そのパラメータ・セットの実効アドレスを渡しておきます。
この絶対値計算プログラムでは、SPEプログラム側で絶対値を計算させるために、図 3.5に示すように3回のDMA転送を実行します。
(1) 入出力パラメータのDMA転送 (GET)
(2) 入力データのDMA転送 (GET)
(3) 出力データのDMA転送 (PUT)
1回目のDMA転送では、SPEプログラムで処理する入出力データのパラメータ・セットabs_paramsをLSへDMA転送 (GET) します。このパラメータ・セットには、2回目と3回目のDMA転送に必要なメインメモリ側の実効アドレスと計算で利用されるデータのサイズが格納されています。
2回目のDMA転送では、パラメータ・セットに格納されている入力データの実効アドレスea_inを利用して、メインメモリ上の入力データをLS上のデータ領域in_speにDMA転送 (GET) します。SPEプログラムは、DMA転送された入力データに対して必要な処理をおこないます。この例題では、絶対値を計算します。
3回目のDMA転送では、パラメータ・セットに格納されている出力先の実効アドレスea_outを利用して、スカラ配列out_speに格納された絶対値の計算結果をメインメモリ上の出力先データ領域へDMA転送 (PUT) します。
spe_context_run()関数からSPEプログラムへパラメータを渡す以外にも、MFCの機能を利用することでSPEプログラムにパラメータを渡す方法がいくつかあります。MFCの機能については、第4.1節でさらに詳しく解説します。
3.3.4 例題プログラムの解説
それでは、例題プログラムのソースコード全体について解説します。
例題プログラム (3-3) DMA転送を利用した絶対値計算プログラム (PPE用プログラム)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <libspe2.h>
4
5 #define SIZE (64)
6
7 float in[SIZE] __attribute__((aligned(16))) = { 1, -2, 3, -4, 5, -6, 7, -8,
8 9, -10, 11, -12, 13, -14, 15, -16,
9 17, -18, 19, -20, 21, -22, 23, -24,
10 25, -26, 27, -28, 29, -30, 31, -32,
11 33, -34, 35, -36, 37, -38, 39, -40,
12 41, -42, 43, -44, 45, -46, 47, -48,
13 49, -50, 51, -52, 53, -54, 55, -56,
14 57, -58, 59, -60, 61, -62, 63, -64 };
15 float out[SIZE] __attribute__((aligned(16)));
16
17 typedef struct {
18 unsigned long long ea_in;
19 unsigned long long ea_out;
20 unsigned int size;
21 int pad[3];
22 } abs_params_t;
23
24 abs_params_t abs_params __attribute__((aligned(16)));
25
26 int main(int argc, char **argv)
27 {
28 int i;
29 int ret;
30
31 spe_context_ptr_t spe;
32 spe_program_handle_t *prog;
33 unsigned int entry;
34 spe_stop_info_t stop_info;
35
36 prog = spe_image_open("abs_spe.elf");
37 if (!prog) {
38 perror("spe_image_open");
39 exit(1);
40 }
41
42 spe = spe_context_create(0, NULL);
43 if (!spe) {
44 perror("spe_context_create");
45 exit(1);
46 }
47
48 ret = spe_program_load(spe, prog);
49 if (ret) {
50 perror("spe_program_load");
51 exit(1);
52 }
53
54 abs_params.ea_in = (unsigned long) in;
55 abs_params.ea_out = (unsigned long) out;
56 abs_params.size = SIZE;
57
58 entry = SPE_DEFAULT_ENTRY;
59 ret = spe_context_run(spe, &entry, 0, &abs_params, NULL, &stop_info);
60 if (ret < 0) {
61 perror("spe_context_run");
62 exit(1);
63 }
64
65 ret = spe_context_destroy(spe);
66 if (ret) {
67 perror("spe_context_destroy");
68 exit(1);
69 }
70
71 ret = spe_image_close(prog);
72 if (ret) {
73 perror("spe_image_close");
74 exit(1);
75 }
76
77 for (i = 0; i < SIZE; i++) {
78 printf("out[%02d]=%0.0f\n", i, out[i]);
79 }
80
81 return 0;
82 }
| 17行目~22行目 | SPEプログラムに渡すパラメータ・セットのデータ型abs_params_tを定義します。DMA転送ではデータサイズを16バイトの倍数で扱う必要があるためpad[3]でパディングをおこない32バイトデータにします。バイトサイズ、アラインメントに関する詳細な解説は第4.2節でおこないます。 |
| 54行目~56行目 | SPEプログラムに渡すパラメータ (スカラ配列in、outの実効アドレスと配列のサイズ) を設定します。 |
| 59行目 | spe_context_run()関数の第4引数として、SPEプログラムへ渡すパラメータabs_paramsの実効アドレスを指定します。 |
例題プログラム (3-4) DMA転送を利用した絶対値計算プログラム (SPE用プログラム)
1 #include <stdio.h>
2 #include <spu_intrinsics.h>
3 #include <spu_mfcio.h>
4
5 #define MAX_BUFSIZE (128)
6
7 float in_spe[MAX_BUFSIZE] __attribute__((aligned(16)));
8 float out_spe[MAX_BUFSIZE] __attribute__((aligned(16)));
9
10 typedef struct {
11 unsigned long long ea_in;
12 unsigned long long ea_out;
13 unsigned int size;
14 int pad[3];
15 } abs_params_t;
16
17 abs_params_t abs_params __attribute__((aligned(16)));
18
19 int main(unsigned long long spe, unsigned long long argp, unsigned long long envp)
20 {
21 int i;
22 int tag = 1;
23
24 /* DMA Transfer 1 : GET input/output parameters */
25 spu_mfcdma64(&abs_params, mfc_ea2h(argp), mfc_ea2l(argp),
26 sizeof(abs_params_t), tag, MFC_GET_CMD);
27 spu_writech(MFC_WrTagMask, 1 << tag);
28 spu_mfcstat(MFC_TAG_UPDATE_ALL);
29
30 /* DMA Transfer 2 : GET input data */
31 spu_mfcdma64(in_spe, mfc_ea2h(abs_params.ea_in), mfc_ea2l(abs_params.ea_in),
32 abs_params.size * sizeof(float), tag, MFC_GET_CMD);
33 spu_writech(MFC_WrTagMask, 1 << tag);
34 spu_mfcstat(MFC_TAG_UPDATE_ALL);
35
36 /* Calculate absolute values */
37 for (i = 0; i < abs_params.size; i++) {
38 if (in_spe[i] > 0) {
39 out_spe[i] = in_spe[i];
40 } else {
41 out_spe[i] = in_spe[i] * -1;
42 }
43 }
44
45 /* DMA Transfer 3 : PUT output data */
46 spu_mfcdma64(out_spe, mfc_ea2h(abs_params.ea_out), mfc_ea2l(abs_params.ea_out),
47 abs_params.size * sizeof(float), tag, MFC_PUT_CMD);
48 spu_writech(MFC_WrTagMask, 1 << tag);
49 spu_mfcstat(MFC_TAG_UPDATE_ALL);
50
51 return 0;
52 }
| 2行目~3行目 | SPEプログラム専用の組み込み関数やマクロなどを利用する時に必要なヘッダファイル”spu_intrinsics.h”と”spu_mfcio.h”をインクルードします。 |
| 25行目~26行目 | パラメータ・セットabs_paramsをメインメモリからLSへ転送するようDMA転送命令を発行します。 |
| 27行目~28行目 | DMA転送の完了を待ちます。 |
| 31行目~32行目 | メインメモリ上の入力データをabs_params.ea_inで指定された実効アドレスから変数in_speに転送するようDMA転送命令を発行します。 |
| 33行目~34行目 | DMA転送の完了を待ちます。 |
| 37行目~43行目 | 絶対値を計算し、変数out_speに格納します。 |
| 46行目~47行目 | 変数out_speのデータをabs_params.ea_outで指定された実効アドレスへ転送するようDMA転送命令を発行します。 |
| 48行目~49行目 | DMA転送の完了を待ちます。 |
DMA転送を利用した絶対値計算プログラムのソースコードは以下のリンクからダウンロードできます。
ファイルダウンロード:abs.tar.gz


