Zephyr の HiFive Unmatched と HiFive Unleashed へのポーティングが本家の main にマージ(※)されました。2.7.0 のマージウインドウが終わるまで、メンテナーチームは忙しそうだったので、もうしばらく放置かな?と思っていましたが、昨日突然マージされました。
これは以前トライしていた Zephyr の HiFive Unleashed への移植(2021年 6月 6日の日記参照)に加え、HiFive Unmatched への移植(2021年 8月 20日の日記参照)も追加したものです。RISC-V はメンテナーが少ないこともあって割と放置気味になりがちなんですよね……。
ポーティングといっても、最低限の設定+とりあえず起動するだけで、ほぼ何にも使えない(UART と SPI くらいしか動きません)から、まだまだこれからだな。うん。
(※)最近は master という単語はダメだねってことで、既存のリポジトリも master → main に置き換えられたプロジェクトが増えてます。
メモ: 技術系の話は Facebook から転記しておくことにした。
普段 PC で何か作業する場合 Windows のノート PC を使っていて、マイコンボードでの実験や開発は Linux のデスクトップ PC にリモートアクセスして行っています。通常の作業には申し分ない性能と使い勝手が実現できています。
が、ゲームとなると話がやや違ってきます。ゲームは大抵 Windows を要求してくるので、我が家の唯一の Windows PC であるノート PC で遊ぶしかないんですけども、最近の 3D を多用したゲームは重すぎて、そのうち GPU が燃える(物理的に)んじゃないかと心配になってきます。
しばらく前に在宅勤務環境を整えた(2021年 2月 12日の日記参照)こともあり、机近辺に新たな PC を置けそうな場所ができました。Windows をインストールしたゲーム用 PC を新たに作りたいところですが、最近はあまりにも GPU が高すぎて購入に踏み切れません……。
いわゆるミドルエンドと呼ばれる TDP 200W 以下(※)クラスは、今だと Radeon RX 6600 XT もしくは GeForce RTX 3060 辺りだと思うんですが、Radeon はそもそも売り切れていて手に入りませんし、RTX 3060 はお値段が 6万円オーバーとあまりにも高すぎます。
GPU の値段はまだまだ下がらないようですし、諦めて買うしかないんですかねえ?
(※)補助電源 8pin x 1 くらいのカードをイメージしてます。GPU に供給可能な電力は PCIe カードエッジ(75W)+ 補助電源 8pin x 1(150W)= Max 225W です。
C 言語の math.h ヘッダには、円周率πを表すマクロ M_PI が定義されています。しかしこのマクロ、コンパイラに -std=c99 や c11 を指定すると使えなくなるんですね。C 言語通には常識かもしれませんが、個人的にハマったのでメモしておきます。
#include <math.h>
int main(void)
{
return M_PI;
}
$ gcc -Wall -std=c99 a.c a.c: In function ‘main’: a.c:5:9: error: ‘M_PI’ undeclared (first use in this function) 5 | return M_PI; | ^~~~ a.c:5:9: note: each undeclared identifier is reported only once for each function it appears in
もし c99 や c11 でも M_PI を使いたい場合は、math.h をインクルードする前に _DEFAULT_SOURCE(_GNU_SOURCE でも良いです)を define すると使えるようになります。
#define _DEFAULT_SOURCE
#include <math.h>
int main(void)
{
return M_PI;
}
使えるようになりました。
RISC-V 向け Zephyr の新しいコンテキストスイッチ(CONFIG_USE_SWITCH=y)を実装しているのですが、浮動小数点演算つまり FPU を使うスレッドを生成するとハングします。調べてみると、私が実装している場所の外にある、新しいコンテキストスイッチ(zephyr/kernel/include/kswap.h の do_swap() 関数)の実装が今まで(CONFIG_USE_SWITCH=n)と違うように見えます。まだ確証はないですけど。
従来の処理 arch_swap() では、現在のスレッド(_kernel.current)がコンテキストスイッチ(arch_swap() 内のシステムコール)の内部で旧 → 新に置き換えます。つまり current は切替「前」のスレッドを指している状態でコンテキストスイッチが始まります。
ところが do_swap() の場合、現在のスレッド(_kernel.current)がコンテキストスイッチ(arch_switch() 関数)を呼ぶ「前」に旧 → 新スレッドに置き換えます。つまり current は切替「後」のスレッドを指している状態でコンテキストスイッチが始まります。
RISC-V Zephyr(他のアーキテクチャも同じかな?)では FPU 使えるスレッドと使えないスレッドを使い分けることができます。コンテキストスイッチ処理では、current スレッドが FPU を使うか使わないかにより処理を変えています。
私が実装したコンテキストスイッチも当然同じように実装したのですが……。先ほど説明したように do_swap() は current を切替「後」のスレッドに設定するため、こんな悲劇が起きます。
原因の一端は掴めたものの、どうして他のアーキテクチャは困っていないのか?do_swap() の実装は意図的なのか?良くわかりません……。
前回(2021年 8月 26日の日記参照)同様に自治体の接種会場に行きました。ワクチンは当然同じでファイザー製です。
前回同様に看護師を始めとした医療従事者の皆様は非常に親切かつ効率的に働いていました。ありがてぇ。
問診の先生はやっぱりお疲れモードな雰囲気でした。無理はしないでください……。
前回はワクチンを打ってしばらく経ってから肩が痛くなりましたが、今回は打った直後から肩が痛いです。明日はどうなるんだろうか、これ……。
目次: RISC-V - まとめリンク
RISC-V には code model という概念があり、ざっくり言うとメモリアクセスやジャンプの際に参照するアドレスの作り方を指定します。medlow と medany の 2つがあり medlow がデフォルトです。
詳しくは GCC のマニュアル RISC-V Options (Using the GNU Compiler Collection (GCC)) や SiFive のエンジニアによる解説 All Aboard, Part 4: The RISC-V Code Models - SiFive を読んでいただくのが良いかと思いますが、ここではどんなときにエラーになるかに重点を置いて、いくつか例を挙げたいと思います。
モデル medlow は 32bit 絶対値でアドレスを指定します。具体的には lui 命令とロード命令などの 12ビットオフセットを使います。lui 命令とは 20ビットの immediate を 12ビット左シフトして、符号拡張する命令のことです。
lui a1, 0x12345 # 12ビットシフトされた値 0x12345000 が a1 に格納される ld a0, 0x678(a1) # アドレス a1 + 0x678 = 0x12345000 + 0x678 から a0 に値をロードする
このようなコードが生成されます。絶対値は -2GB 〜 +2GB までしか生成できませんので、全てのシンボルが範囲に収まっている必要があります。32bit アドレスを使っている場合は全てのアドレス範囲をカバーできますが、64bit アドレスを使っている場合は 0x00000000_00000000〜0x00000000_7fffffff または 0xffffffff_80000000〜0xffffffff_ffffffff のアドレス範囲にシンボルを配置しなければなりません。範囲外にシンボルを配置しようとすると、
// a.c
extern volatile int *hoge;
void _start(void)
{
*hoge = 1;
}
/* a.ld */
OUTPUT_ARCH("riscv")
ENTRY(_start)
SECTIONS
{
PROVIDE(hoge = 0x100000000); /* 0x1_00000000 は medlow の範囲外 */
}
$ riscv64-zephyr-elf-gcc -march=rv64gc -Wall -g -mabi=lp64d -mcmodel=medlow -nostdlib -T a.ld a.c --save-temp a.o: in function `_start': test-medany/a.c:5:(.text+0x6): relocation truncated to fit: R_RISCV_HI20 against symbol `hoge' defined in *ABS* section in a.out collect2: error: ld returned 1 exit status
リンカーがエラーを出します。hoge のアドレスを変更し -2GB 〜 +2GB の範囲(0xffffffff_80000000 や 0x00000000_70000000 など)にするとリンクが通ります。
もう 1つの medany は、PC 相対でアドレス指定します。具体的にはアドレスの場合は auipc 命令と addi 命令、ジャンプの場合は auipc 命令と jalr 命令の 12ビットオフセットを使います。auipc 命令とは、20ビットの immediate を 12ビット左シフトして、符号拡張したあと PC に加算する命令のことです。
PC が 0x1_40000000 付近で hoge が 0x1_a89abcd0 だとすると、
アドレスの場合 140000006: auipc a5,0x689ac # 12ビットシフトされた値 0x689ac000 + PC 0x1_40000006 = 0x1_a89ac006 が a5 に格納される 14000000a: addi a5,a5,-822 # 0x1_a89ac006 - 822 = 0x1_a89abcd0 = hoge のアドレスが a5 に格納される ジャンプの場合 140000008: auipc ra,0x689ac # 12ビットシフトされた値 0x689ac000 + PC 0x1_40000008 = 0x1_a89ac008 が ra に格納される 14000000c: jalr -824(ra) # ra 0x1_a89ac006 - 824 = 0x1_a89abcd0 = hoge のアドレスにジャンプする
このようなコードが生成されます。相対アドレスは PC の現在地 -2GB 〜 +2GB までしか生成できません。medlow モデルより対応できる範囲は広がったものの、いかなるアドレスでも対応できるわけではないです。例えばコード領域とデータ領域をあまりにも遠くすると、
// a.c
extern volatile int *hoge;
void _start(void)
{
*hoge = 1;
}
/* a.ld */
OUTPUT_ARCH("riscv")
ENTRY(_start)
MEMORY
{
TEXT(rx) : ORIGIN = 0x0000000140000000, LENGTH = 0x10000
}
SECTIONS
{
/* hoge をコード領域から 2GB 以上離して配置する */
PROVIDE(hoge = 0x1c89abcd0);
/* コード領域を 0x1_40000000 にする */
.text : {
*(.text*);
} > TEXT
}
$ riscv64-zephyr-elf-gcc -march=rv64gc -Wall -g -mabi=lp64d -mcmodel=medany -nostdlib -T a.ld a.c --save-temp a.o: in function `_start': test-medany/a.c:5:(.text+0x6): relocation truncated to fit: R_RISCV_PCREL_HI20 against symbol `hoge' defined in *ABS* section in a.out collect2: error: ld returned 1 exit status
シンボル hoge が位置するアドレスは絶対値 -2GB 〜 +2GB の範囲外であり、コード領域からも離れているため PC 相対 -2GB 〜 +2GB の範囲外でもあります。よって medlow モデルでも medany モデルでもリンクエラーとなります。
何 kHz の音まで聞こえるかテストするサイト、聞こえチェック | Panasonic が、以前 Twitter でちょっと話題になりました。
私の場合 15kHz までは聞こえますが、それ以上(17kHz, 19kHz)は全く聞こえません。鳴ってんのか?これ??
まずブラウザの影響を排除するため、上記のサイトから音源をダウンロードします。Wav ではなく MP3 ファイルでした。
直接オーディオプレイヤーで聞いても 15kHz 以外は聞こえません。ブラウザのせいじゃなかった。私の耳は全くあてにならないので、オシロスコープにご登場願います。
19kHz 再生時の波形(グラフはキャプチャし忘れて 17kHz のまま。右下の周波数表示が 19kHz を示している)
いやあ、バッチリ綺麗に Sin 波が鳴ってます。私は全く聞こえませんね、これが老いかぁ……。
まれに xterm の 256 色指定エスケープシーケンスに対応していない端末があって vim の表示が変な色になってしまいます。チェック用のスクリプトを作っておきました。単純に背景色を変更するエスケープシーケンスと、空白文字、色を元に戻すエスケープシーケンスを連打するだけです。
#!/bin/sh
ESC_ORG="\e[0m"
print_colors()
{
for i in ${*};
do
printf " %3d\e[%dm " ${i} ${i};
echo -n ${ESC_ORG}
done
echo
}
print_xterm_colors()
{
for i in ${*};
do
printf " %3d\e[48;5;%dm " ${i} ${i};
echo -n ${ESC_ORG}
done
echo
}
echo "System colors (ESC[Nm):"
print_colors `seq 40 47`
echo
echo "xterm 256 colors (ESC[48;5;Nm):"
for i in `seq 0 8 248`;
do
j=`expr ${i} + 7`
print_xterm_colors `seq ${i} ${j}`
done
実行するとこんな感じになります。
対応していない端末だとこうなりますと言いたいところでしたが、対応していない端末が見当たりませんでした。前はあった気がするんだけどなあ……?
目次: RISC-V - まとめリンク
メイン CPU からサブ CPU を起こすとき基本的には、
RAM の初期値が不定であると仮定すると、サブ CPU が下手にポーリングすると、不定値によって条件が成立してしまい、メイン CPU からの起動司令がないのに勝手に起動してしまう事態に陥ります。
先程書いた基本的な構造を素直に書くとこんなコードになるでしょう。
/* メイン CPU は HARTID=8, サブ CPU は HARTID=0...3 とする */
#define HARTID_MAIN 8
#define HARTID_SUB_START 0
#define HARTID_SUB_END 3
#define HARTID_MAX 9
struct {
int boot_wait;
int boot_done;
} init_core[HARTID_MAX] = {};
int get_hartid(void)
{
int i;
__asm__ volatile("csrr %0, mhartid" : "=r"(i));
return i;
}
/* メイン CPU が実行する */
void boot_main(void)
{
for (int i = HARTID_SUB_START; i < HARTID_SUB_END; i++) {
init_core[i].boot_wait = 1;
}
}
/* サブ CPU が実行する */
void boot_sub(void)
{
int hartid = get_hartid();
while (!init_core[hartid].boot_wait) {
/* busy loop */
}
}
残念ながらこのコードは正常に動作しません。共有 RAM つまり init_core[hartid].boot_wait の値が起動直後から != 0 だったとき、boot_sub() は boot_main() からの起動司令を待つことなく起動してしまうからです。
共有 RAM の不定値に対処する方法を考えます。基本的にはサブ CPU が変数を初期化(boot_wait = 0)してから待ちに入れば良いのですが、新たな問題が生じます。メイン CPU とサブ CPU の実行順序はどちらが先という保証はないため、
以上の順で実行されるとメイン CPU 側の起動司令が消されてしまい、ハングアップする可能性があります。この問題の回避のため、変数を 1つ追加し、サブ CPU のブートが終わるまで、メイン CPU は繰り返し起動司令を送るように変更します。
先程書いた基本的な構造を素直に書くとこんなコードになるでしょう。
/* メイン CPU は HARTID=8, サブ CPU は HARTID=0...3 とする */
#define HARTID_MAIN 8
#define HARTID_SUB_START 0
#define HARTID_SUB_END 3
#define HARTID_MAX 9
struct {
int boot_wait;
int boot_done;
} init_core[HARTID_MAX] = {};
int get_hartid(void)
{
int i;
__asm__ volatile("csrr %0, mhartid" : "=r"(i));
return i;
}
/* メイン CPU が実行する */
void boot_main(void)
{
for (int i = HARTID_SUB_START; i < HARTID_SUB_END; i++) {
init_core[i].boot_done = 0;
while (!init_core[i].boot_done) {
init_core[i].boot_wait = 1;
}
}
}
/* サブ CPU が実行する */
void boot_sub(void)
{
int hartid = get_hartid();
init_core[hartid].boot_wait = 0;
init_core[hartid].boot_done = 0;
while (!init_core[hartid].boot_wait) {
/* busy loop */
}
init_core[hartid].boot_done = 1;
}
残念ながらこのコードも正常に動作しません。共有 RAM への値の反映が他の CPU に即座に見えること(アトミック性)を暗に期待しているからです。
今日のマルチコアシステムでは、boot_wait = 0 としたときに、他の CPU にも即座に同じ値が見えているとは限りません。主な要因としては、
などがあります。通常の変数への代入、参照が他の CPU に即座に値が見えないことにより、おかしくなるパターンはいくつか考えられそうですが、ありがちなパターンとして、
以上の順で実行されるとメイン CPU 側が起動司令を送らないまま、サブ CPU 側も何もできずハングアップする可能性があります。この問題の回避のため、通常の変数への代入、参照ではなく他の CPU にも値が見えるように初期化、代入(アトミックアクセスする)必要があります。
従来 C 言語でアトミックアクセスを行うためには、実装対象アーキテクチャの知識やアセンブラの記述を必要とするなど、やや困難が伴いました。ですが C11 でアトミックアクセス用の定義 stdatomic.h が追加されたことで、アトミックアクセスはかなり楽になりました。素敵ですね。
ひとまず速度を全く気にせず、全てのアクセスをアトミックアクセスに入れ替えると、こんなコードになるでしょう。
/* メイン CPU は HARTID=8, サブ CPU は HARTID=0...3 とする */
#define HARTID_MAIN 8
#define HARTID_SUB_START 0
#define HARTID_SUB_END 3
#define HARTID_MAX 9
struct {
atomic_int boot_wait;
atomic_int boot_done;
} init_core[HARTID_MAX] = {};
int get_hartid(void)
{
int i;
__asm__ volatile("csrr %0, mhartid" : "=r"(i));
return i;
}
/* メイン CPU が実行する */
void boot_main(void)
{
for (int i = HARTID_SUB_START; i < HARTID_SUB_END; i++) {
atomic_store(&init_core[i].boot_done, 0);
while (!atomic_load(&init_core[i].boot_done)) {
atomic_store(&init_core[i].boot_wait, 1);
}
}
}
/* サブ CPU が実行する */
void boot_sub(void)
{
int hartid = get_hartid();
atomic_store(&init_core[hartid].boot_wait, 0);
atomic_store(&init_core[hartid].boot_done, 0);
while (!atomic_load(&init_core[hartid].boot_wait)) {
/* busy loop */
}
atomic_store(&init_core[hartid].boot_done, 1);
}
C11 のアトミックアクセスは何も指定しない場合、一番制限の強い(= 確実に他の CPU に見えるものの、アクセス速度は遅い)memory_order_seq_cst アクセスになります。マルチコアのブートを行うにあたって、常に制限が強いアクセスは必要ありませんが、とりあえずこれで動くはず。
日記を漁って携帯の遍歴を書き出してみました。日記を書く習慣がなかった頃の機種や時期は不明です。
(基本的には)長く使っていた機種は気に入っていた機種です。ガラケー時代はいずれも良い機種で、バッテリーが死ぬまで使ってました。最後の P-03B だけ 1年しか使っていませんが、不満があったわけではなく、知人に携帯を譲るため手放しました。たしか。
スマホ時代は国内メーカーの質は明らかに落ちました。SO-02C はソツなく良かったんですけど、ストレージが少なすぎで買い替え直前は容量不足で挙動不審でした。SH-01F は性能良いものの、電池がなくなるのが早く、本体が熱すぎでした。この機種で懲りて Android ハイエンド機を買わなくなりました。
今になって調べてみたところ、この 2機種はマシな部類だったようで、富士通 ARROWS のように「カイロ機能搭載」「電話ができない」「メールがこない」など、怨嗟にまみれたレビューが未だに残っている機種もあります。悲惨です。
日本だけ異常に iPhone 普及率が高い理由って、国内メーカーが 2010 年代初頭にやらかしたから……!?と思ってしまいました……。
メモ: 技術系の話は Facebook から転記しておくことにした。
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2021.
Powered by PHP 5.2.17.
using GD bundled (2.0.34 compatible)(png support.)