目次: OpenCL
引き続き、独自アクセラレータのテンプレート実装pocl/lib/CL/devices/accelの細かな問題を調べます。次の問題はOpenCLの初期化です。clGetPlatformIDs() から初期化関数pocl_accel_init() に辿り着いたところでabort() が呼ばれクラッシュします。
| GENERAL | accel: accelerator at 0x1000 with 0 builtin kernels Could not open /dev/mem
テンプレート実装の意図としては /dev/memをopen() してメモリマップされたハードウェアのレジスタを読み書きしたいようです。今回は実際のハードウェア相手ではないので、レジスタの読み書きではなく /dev/memの代わりにバイナリファイルを開いてもらうように書き換えます。
// pocl/lib/CL/devices/accel/accel.cc
cl_int pocl_accel_init(unsigned j, cl_device_id dev, const char *parameters) {
...
POCL_MSG_PRINT_INFO("accel: accelerator at 0x%zx with %zu builtin kernels\n",
D->BaseAddress, D->SupportedKernels.size());
int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd == -1) {
POCL_ABORT("Could not open /dev/mem\n"); //★★このabortでクラッシュ
}
...
ファイル名を書き換えて突破するとデバイスが持っているメモリのサイズを取得し、メモリマップしようとする部分で怒られます。
| GENERAL | accel: accelerator at 0x1000 with 0 builtin kernels a.out: ../lib/CL/devices/accel/accel.cc:196: void MMAPRegion::Map(size_t, size_t, int): Assertion `Data != MAP_FAILED && "MMAPRegion mapping failed"' failed.
サイズを取得している箇所は下記のとおりです。
// pocl/lib/CL/devices/accel/accel.cc
cl_int pocl_accel_init(unsigned j, cl_device_id dev, const char *parameters) {
...
uint32_t ctrl_size = D->ControlMemory.Read32(ACCEL_INFO_CTRL_SIZE);
uint32_t imem_size = D->ControlMemory.Read32(ACCEL_INFO_IMEM_SIZE);
uint32_t dmem_size = D->ControlMemory.Read32(ACCEL_INFO_DMEM_SIZE);
uint32_t pmem_size = D->ControlMemory.Read32(ACCEL_INFO_PMEM_SIZE);
uint32_t max_region =
std::max(std::max(ctrl_size, imem_size), std::max(dmem_size, pmem_size));
D->InstructionMemory.Map(D->BaseAddress + max_region, imem_size, mem_fd);
...
バイナリファイルを書き換えて何か適当な値が読めるようにしてやりすごすか、面倒ならばD->ControlMemory.Write32(ACCEL_INFO_CTRL_SIZE, 0x2000); のように固定値を書いておくと次に進みます。今は実際のデバイスが相手ではないので、とりあえず先に進めて後で考えましょう。
目次: Linux
TwitterでGUIの話をしている人を見かけて思い出した話です。私はGNU/Debian LinuxのGUIをLightDM + GTKにして使っています。
GUIにこだわりはなく一時期デフォルトだったLightDMを使い続けているだけです。デフォルトになっただけあって、良くできていると思うし特に不満はないです……が、1点だけ言わせてもらえば、ウインドウサイズ変更の判定が厳しすぎませんか?
特に左辺、上辺、右辺が1pxしか反応してくれないので、マウス操作が非常にシビアです。手が震えてきます。
Twitterで上記の話をしていたところ、Alt + 右マウスボタンでウインドウサイズを変えられるよ、と教えてもらいました。今度からそうします。1pxに合わせるのは手が疲れる……。
目次: Zephyr
JTAGが繋がった(2021年6月5日の日記参照)記念にZephyrをHiFive Unleashedで動かしてみました。使うハードウェアはUARTだけ、コードもJTAGでITMにロードして動作させるだけなら楽勝だろと思いきや、全然UARTから文字が出力されず1日掛かってしまいました。
*** Booting Zephyr OS build zephyr-v2.6.0-39-g2bf63134e8f0 *** thread_a: Hello World from cpu 0 on hifive_unleashed! thread_b: Hello World from cpu 0 on hifive_unleashed! thread_a: Hello World from cpu 0 on hifive_unleashed! thread_b: Hello World from cpu 0 on hifive_unleashed! thread_a: Hello World from cpu 0 on hifive_unleashed! thread_b: Hello World from cpu 0 on hifive_unleashed!
原因はコアのPLL設定が間違っていて、コアクロックから分周して作るUARTのボーレートもおかしな周波数になっていたからです。
FU540のリセット直後は外部クロック源(33.33MHz)をそのまま使って動きます。起動後PLLを1GHz(33.33 / 1 * 120 / 4 = 999.9MHz)に設定し、コアクロックをPLL側に切り替えなければなりません。
PLLの設定はFSBLという2段目のブートローダーが行うので、通常はOSが気にする必要はありません。しかしRTOSは大抵ブートローダーを経由しませんから、ブートローダーに隠れた設定も拾ってきて実装しないと動かないことがあります。
ブートローダーがあって当たり前のリッチ系SoCでRTOSを動かそうとすると、大抵この「暗黙のうちに設定されている何か」が抜け落ちて動かなかったりおかしくなったり、何かと面倒が起きます。
幸いなことにSiFiveのブートローダーはコードが公開されており、完全ブラックボックスのSoCと比べれば、難易度は低い部類です。ありがたいですね。
メモ: 技術系の話はFacebookから転記しておくことにした。加筆。
目次: RISC-V
昔買ったHiFive Unleashedというボード、JTAGはちょっと特殊なコネクタが必要だと思っていて接続を諦めていました。ところが今日、ファンを掃除したあと動作確認をした際に、USB Serialと一緒にUSB JTAGも用意されていることに気づきました。
HiFive Unleashedはディスコンでもう手に入りませんし、後継機種のHiFive Unmatchedも買った今となっては、かなり今更感ありますが……。
ちょっと試したところ、簡単にOpenOCDが繋がりSMPモードにすると5コア(rv64imacなE51とrv64gcなU54 x 4)が見えました。
adapter speed 10000
adapter driver ftdi
ftdi_device_desc "Dual RS232-HS"
ftdi_vid_pid 0x0403 0x6010
ftdi_layout_init 0x0008 0x001b
ftdi_layout_signal nSRST -oe 0x0020 -data 0x0020
set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x20000913
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME -rtos hwthread
target create $_TARGETNAME.1 riscv -chain-position $_TARGETNAME -coreid 1
target create $_TARGETNAME.2 riscv -chain-position $_TARGETNAME -coreid 2
target create $_TARGETNAME.3 riscv -chain-position $_TARGETNAME -coreid 3
target create $_TARGETNAME.4 riscv -chain-position $_TARGETNAME -coreid 4
target smp $_TARGETNAME.0 $_TARGETNAME.1 $_TARGETNAME.2 $_TARGETNAME.3 $_TARGETNAME.4
init
halt
SiFiveのSoCはFU540も後継のFU740も命令セットの違うコアを混載するのが好きですね。混載は良いとしてせめて同じ命令セットにしてほしかったです。単純なSMPしか持ってないOSだと制御できないじゃん……。
メモ: 技術系の話はFacebookから転記しておくことにした。加筆。
目次: OpenCL
引き続き、独自アクセラレータのテンプレート実装pocl/lib/CL/devices/accelの細かな問題を調べます。デバイス数の取得の問題を回避すると、次はデバイスのパラメータを渡す問題に遭遇します。
// pocl/lib/CL/devices/accel/accel.cc
void pocl_accel_init_device_ops(struct pocl_device_ops *ops) {
ops->device_name = "accel"; //★★デバイス名はaccel
ops->init = pocl_accel_init;
...
// pocl/lib/CL/devices/devices.c
cl_int
pocl_init_devices ()
{
...
dev_index = 0;
/* Init infos for each probed devices */
for (i = 0; i < POCL_NUM_DEVICE_TYPES; ++i)
{
if (pocl_devices_init_ops[i] == NULL)
continue;
str_toupper (dev_name, pocl_device_ops[i].device_name); //★★dev_nameはデバイス名を大文字に変換したACCELになる
assert(pocl_device_ops[i].init);
for (j = 0; j < device_count[i]; ++j)
{
...
/* Check if there are device-specific parameters set in the
POCL_DEVICEn_PARAMETERS env. */
POCL_GOTO_ERROR_ON (
(snprintf (env_name, 1024, "POCL_%s%d_PARAMETERS", dev_name, j) //★★環境変数名を生成する箇所
< 0),
CL_OUT_OF_HOST_MEMORY, "Unable to generate the env string.");
errcode = pocl_devices[dev_index].ops->init (
j, &pocl_devices[dev_index], getenv (env_name));
...
実装ではpocl_accel_init() にて環境変数の値をパースしてデバイスのパラメータを取得します。環境変数名はデバイス番号によって変化しますが、0番目のデバイスであればPOCL_ACCEL0_PARAMETERSという名前になります。環境変数名は上記にあるとおりpocl_init_devices() で決めています。
困ったことに環境変数が見つからないとabort() してしまうので、環境変数には最低でも何か1つ数値を渡す必要があります。なお1つ目の値はレジスタ領域のベースアドレスだと解釈されるようです。
他の実装(pthreadとcuda)は環境変数を使わないので、同様の問題は存在しません。最終的にはaccelも環境変数に頼らない実装に変えていく必要がありますが、今はそのままにしておきます。
目次: OpenCL
独自アクセラレータのテンプレート実装pocl/lib/CL/devices/accelはデバイスタイプがCUSTOMになっているのが最大の難関ですが、その他にも色々問題があります。
最初に遭遇する問題はデバイス数を取得する処理のエラー処理が間違っていることです。現状のコードだとちょっと特殊な環境変数を渡さないと動きません。
// pocl/lib/CL/devices/devices.c
static unsigned device_count[POCL_NUM_DEVICE_TYPES];
...
cl_int
pocl_init_devices ()
{
...
/* Init operations */
for (i = 0; i < POCL_NUM_DEVICE_TYPES; ++i)
{
...
/* Probe and add the result to the number of probed devices */
assert(pocl_device_ops[i].probe);
device_count[i] = pocl_device_ops[i].probe(&pocl_device_ops[i]); //★デバイス数を取得する★
pocl_num_devices += device_count[i];
}
...
dev_index = 0;
/* Init infos for each probed devices */
for (i = 0; i < POCL_NUM_DEVICE_TYPES; ++i)
{
if (pocl_devices_init_ops[i] == NULL)
continue;
str_toupper (dev_name, pocl_device_ops[i].device_name);
assert(pocl_device_ops[i].init);
for (j = 0; j < device_count[i]; ++j) //★デバイス数42億と誤解したまま処理しようとしてクラッシュする★
{
// pocl/lib/CL/devices/accel/accel.cc
unsigned int pocl_accel_probe(struct pocl_device_ops *ops) {
//★POCL_DEVICESという環境変数が見つからないとき、-1というエラー値を返す★
//★本来エラー値である -1だが、デバイス数として解釈され42億になってしまう★
int env_count = pocl_device_get_env_count(ops->device_name);
return env_count;
}
// pocl/lib/CL/devices/devices.c
/**
* Get the number of specified devices from environment
*/
int pocl_device_get_env_count(const char *dev_type)
{
const char *dev_env = getenv(POCL_DEVICES_ENV);
char *ptr, *saveptr = NULL, *tofree, *token;
unsigned int dev_count = 0;
if (dev_env == NULL)
{
return -1; //★ここにくる★
}
ptr = tofree = strdup(dev_env);
while ((token = strtok_r (ptr, " ", &saveptr)) != NULL)
{
if(strcmp(token, dev_type) == 0)
dev_count++;
ptr = NULL;
}
POCL_MEM_FREE(tofree);
return dev_count;
}
このような実装になっておりaccelのデバイス数が42億(!)と解釈されてしまい、42億回デバイスを列挙しようとしてクラッシュします。バグのような気がしますけど、サンプル実装ですのであまり文句を言っても仕方ありません。
環境変数POCL_DEVICES="pthread -1 CUDA -1 accel 1" のようにデバイス数を明示的に渡せば回避可能です。最終的にはpocl_accel_probe() が正しくデバイス数を返すような実装を追加する必要があるでしょうが、この場は環境変数で切り抜けます。
目次: OpenCL
CPUでもGPUでもないデバイスでOpenCLを動かすとしたらどうしたら良いでしょうか?答えとしては、その1で紹介したとおり、CL_DEVICE_TYPE_ACCELERATORを実装すれば良いです。が、イチから作るのはとっても大変です。
素晴らしいことにpoclにはテンプレートらしき実装がpocl/lib/CL/devices/accelに用意されています。やりたいこととは微妙に違うことが後々わかりますが、イチから作るよりははるかにマシです。このテンプレートを改造しましょう。
テンプレートの名前はaccelでいかにもアクセラレータに見えますが、デバイスタイプはCL_DEVICE_TYPE_ACCELERATORではなくCL_DEVICE_TYPE_CUSTOMです。CUSTOMは「コンパイルが可能なデバイス」ではなく、ビルトインカーネルのみを実行するデバイスです。ユーザー定義のカーネルを実行することは考えられていません。
ユーザー定義カーネルが実行できることが独自アクセラレータの売りですから、何とかしてCUSTOMではなくACCELERATORになるように実装を改造する必要があります。これはなんとも先が長そうです……。
目次: ベンチマーク
(参考)コード一式はGitHubに置きました(GitHubへのリンク)
Linuxが動くRISC-Vボードを買ったので、RISC-V 64でもmemsetをやってみました。環境はボードがSiFive HiFive Unmatchedで、SoCがSiFive Freedom U740で、コアがU74-MCです。動作周波数は書いてないですね。OSはSiFive独自?環境のFreedom USDKです。メモリはDDR4-2400のようです(Schematics hifive-unmatched-schematics-v3.pdfより)。
特徴的な点は、
あと個人的に残念だった点としては、U74コアの速度です。前世代のHiFive Unleashedに搭載されていたU54コアはCortex-A53の足下にも及びませんでした(2019年5月27日の日記参照)。
U74はCortex-A72レベルとまでは言いませんが、Cortex-A53は超えてくると期待していましたが、少なくともmemsetに関しては負けています。半分くらいの速度しか出ていません……。
目次: RISC-V
SiFiveのHiFive Unmatchedを購入しました。現状、世界最速のLinuxが動作するRISC-V 64bit SoC とのことです。
ボードにはSDカードが付属しておりSiFive独自環境のFreedom USDKがインストールされています。ボード上にはUSB接続のシリアル端子があり、電源を入れればLinuxが起動し、ユーザroot、パスワードsifiveでログインできるようになっています。
ぱっと見はPCと同じmini-ITXマザーボードですけど、バックパネルを見るとSDカードの差し込み口、USBシリアル用のmicroB端子が出ていて、どちらかといえばSBC(シングルボードコンピュータ)です。PCっぽさがありません。
本当はグラフィックカードを装着してGUIを使うべきですが、昨今のグラフィックカード品薄&異常な値上がりのおかげで全く買う気が起きないので、しばらくシリアルコンソールで使おうと思います。
インストールされているカーネルは、
Linux unmatched 5.11.10 #1 SMP Wed Apr 7 17:37:34 UTC 2021 riscv64 riscv64 riscv64 GNU/Linux
でした。5.11はStableカーネルではあるものの、既にEOLです。まあ、開発用ボードだしこんなもんか。
Crowd Supplyから購入しました。本体 $679, 消費税が7,100円、合計で7万円くらいでした。HiFive Unleashedほどではないにせよ、SBCにしては良いお値段です。
UPSが米国→日本まで持ってきて、国内はクロネコヤマトが運びます。受け取りの際に、消費税を着払いでクロネコに払う必要があります。私は消費税のことを忘れていて、何だこの金は??と混乱しました。Unleashedのときと全く同じでした。海外からものを買うことがほとんどなくて、消費税の存在をすぐ忘れちゃうんですよね……。
< | 2021 | > | ||||
<< | < | 06 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | - | - | - |
合計:
本日: