目次: Zephyr
Zephyrのテストに用いられるztestというフレームワークがあります。使い方は、下記の通りで、テスト用の関数を1つずつztest_unit_test() というマクロに渡します。
最終的にtest_sprintfはstruct unit_testの配列になり、配列をztest_run_test_suite() に渡すと最初のテストから順番に実行してくれるという仕組みです。ztestの使い方は主題ではないのでこのくらいにしておきます。
// zephyr/tests/lib/sprint/src/main.c
void test_main(void)
{
ztest_test_suite(test_sprintf, //★変数名★
ztest_unit_test(test_sprintf_double), //★テスト1つ目★
ztest_unit_test(test_sprintf_integer), //★テスト2つ目★
ztest_unit_test(test_vsprintf),
ztest_unit_test(test_vsnprintf),
ztest_unit_test(test_sprintf_string),
ztest_unit_test(test_sprintf_misc));
ztest_run_test_suite(test_sprintf);
}
今回はztestがテスト実行時に何を行うのかを調べ、浮動小数点数命令で例外が発生する原因を探します。まずは前半のztest_test_suite() マクロから。
// zephyr/subsys/testsuite/ztest/include/ztest_test.h
/**
* @brief Define a test suite
*
* This function should be called in the following fashion:.c
* ztest_test_suite(test_suite_name,
* ztest_unit_test(test_function),
* ztest_unit_test(test_other_function)
* );
*
* ztest_run_test_suite(test_suite_name);
* ```
*
* @param suite Name of the testing suite
*/
#define ztest_test_suite(suite, ...) \
static ZTEST_DMEM struct unit_test _##suite[] = { \
__VA_ARGS__, { 0 } \
}
struct unit_test {
const char *name;
void (*test)(void);
void (*setup)(void);
void (*teardown)(void);
uint32_t thread_options;
};
/**
* @brief Define a test function
*
* This should be called as an argument to ztest_test_suite.
*
* @param fn Test function
*/
#define ztest_unit_test(fn) \
ztest_unit_test_setup_teardown(fn, unit_test_noop, unit_test_noop)
/**
* @brief Define a test with setup and teardown functions
*
* This should be called as an argument to ztest_test_suite. The test will
* be run in the following order: @a setup, @a fn, @a teardown.
*
* @param fn Main test function
* @param setup Setup function
* @param teardown Teardown function
*/
#define ztest_unit_test_setup_teardown(fn, setup, teardown) { \
STRINGIFY(fn), fn, setup, teardown, 0 \
}
//// 展開例: ztest_unit_test(test_sprintf_double)
// fn = test_sprintf_double
ztest_unit_test_setup_teardown(test_sprintf_double, unit_test_noop, unit_test_noop)
// fn = test_sprintf_double, setup = unit_test_noop, teardown = unit_test_noop
{
name : STRINGIFY(test_sprintf_double),
test : test_sprintf_double,
setup : unit_test_noop,
teardown : unit_test_noop,
thread_options: 0
}
最初にちょこっと書いたとおり、ztest_test_suite() はstruct unit_testの配列宣言に展開され、ztest_unit_test() は配列の要素の初期化値に展開されます。
構造体unit_testにthread_optionsというメンバーがいます。このメンバーが浮動小数点数命令のサポートに重要な役割を果たします。ztest_unit_test() だと、常にthread_optionsは0になる、という点だけ覚えておいてくれればOKです。
目次: Zephyr
GitHubを見ていたらZephyrのMemberに招待されていました。メーリングリストに来ていたメール(メールのアーカイブへのリンク)によると、現状MAINTAINERS.ymlに記載されているCollaboratorを全員招待したらしいです。
MemberといってもIssueのタグや担当者を付け外しできるようになる程度で、これといった権限はありません。
目次: RISC-V
RISC-VではCPUが単精度浮動小数点F拡張や、倍精度浮動小数点D拡張の命令(flw, fldなど)に対応していても、mstatusのFSビットでFPUを有効にしていないと、命令実行時にIllegal Instruction例外が発生します。
QEMUはこのチェックをどこで行っているのかメモしておきます。
// qemu/target/riscv/translate.c
static void decode_opc(CPURISCVState *env, DisasContext *ctx, uint16_t opcode)
{
/* check for compressed insn */
if (extract16(opcode, 0, 2) != 3) {
if (!has_ext(ctx, RVC)) {
gen_exception_illegal(ctx);
} else {
ctx->pc_succ_insn = ctx->base.pc_next + 2;
if (!decode_insn16(ctx, opcode)) {
/* fall back to old decoder */
decode_RV32_64C(ctx, opcode);
}
}
} else {
uint32_t opcode32 = opcode;
opcode32 = deposit32(opcode32, 16, 16,
translator_lduw(env, ctx->base.pc_next + 2));
ctx->pc_succ_insn = ctx->base.pc_next + 4;
if (!decode_insn32(ctx, opcode32)) { //★この関数がfalseを返す★
gen_exception_illegal(ctx);
}
}
}
static void gen_exception_illegal(DisasContext *ctx)
{
generate_exception(ctx, RISCV_EXCP_ILLEGAL_INST); //★Illegal Instruction例外★
}
関数decode_insn32() で命令をデコードしfalseが返ってきたら、例外を発生させます。このdecode_insn32() という関数は自動生成されており、ビルドディレクトリの下にあります。RV64向けなら下記のようなパスです。
// (build_dir)/libqemu-riscv64-softmmu.fa.p/decode-insn32.c.inc
static bool decode_insn32(DisasContext *ctx, uint32_t insn)
{
switch (insn & 0x0000007f) {
...
case 0x00000007:
/* ........ ........ ........ .0000111 */
switch ((insn >> 12) & 0x7) {
...
case 0x3:
/* ........ ........ .011.... .0000111 */
/* ../target/riscv/insn32.decode:199 */
decode_insn32_extract_i(ctx, &u.f_i, insn);
if (trans_fld(ctx, &u.f_i)) return true; //★このifが成立「しない」★
break;
...
static void decode_insn32_extract_i(DisasContext *ctx, arg_i *a, uint32_t insn)
{
a->imm = sextract32(insn, 20, 12);
a->rs1 = extract32(insn, 15, 5);
a->rd = extract32(insn, 7, 5);
}
// qemu/target/riscv/insn_trans/trans_rvd.c.inc
static bool trans_fld(DisasContext *ctx, arg_fld *a)
{
REQUIRE_FPU; //★このマクロ内でreturn false★
REQUIRE_EXT(ctx, RVD);
TCGv t0 = tcg_temp_new();
gen_get_gpr(t0, a->rs1);
tcg_gen_addi_tl(t0, t0, a->imm);
tcg_gen_qemu_ld_i64(cpu_fpr[a->rd], t0, ctx->mem_idx, MO_TEQ);
mark_fs_dirty(ctx);
tcg_temp_free(t0);
return true;
}
// qemu/target/riscv/insn_trans/trans_rvf.c.inc
#define REQUIRE_FPU do {\
if (ctx->mstatus_fs == 0) \
return false; \
} while (0)
大体の仕組みは把握できましたので、実際にこの部分を通るかどうか見ましょう。
Illegal Instruction例外は実行中に何度も発生する例外ではありません。カーネル内で1度でも発生するとZephyrはログを出して止まります。すなわちQEMUも例外を発生させるのは1度だけです。
何度も通る処理だとブレーク条件が必要でややこしいですが、1度しか通らない処理なら単純にREQUIRE_FPUの行でブレークすれば良いです。条件ctx->mstatus_fs == 0が成立するかどうか簡単に確認できます。
$ gdb qemu/build/qemu-system-riscv64 (gdb) b trans_rvd.c.inc:23 Breakpoint 1 at 0x555555a858b8: file ../target/riscv/insn_trans/trans_rvd.c.inc, line 23. (gdb) r [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff440c700 (LWP 1675592)] [New Thread 0x7ffff3a89700 (LWP 1675593)] *** Booting Zephyr OS build zephyr-v2.4.0-2328-g4f33cf942643 *** Running test suite test_sprintf =================================================================== START - test_sprintf_double [Switching to Thread 0x7ffff3a89700 (LWP 1675593)] Thread 3 "qemu-system-ris" hit Breakpoint 1, trans_fld (ctx=0x7ffff3a883d0, a=0x7ffff3a88290) at ../target/riscv/insn_trans/trans_rvd.c.inc:23 23 REQUIRE_FPU; (gdb) l 18 * this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 static bool trans_fld(DisasContext *ctx, arg_fld *a) 22 { 23 REQUIRE_FPU; //★ここで止めた★ 24 REQUIRE_EXT(ctx, RVD); 25 TCGv t0 = tcg_temp_new(); 26 gen_get_gpr(t0, a->rs1); 27 tcg_gen_addi_tl(t0, t0, a->imm); (gdb) p ctx->mstatus_fs $1 = 0
ちょっとしたメモのつもりが長くなってしまいました。まあいいや。
目次: Zephyr
ひとまずF拡張とD拡張の双方を有効にしRV64IMAFDC(省略形だとRV64GCともいう)でビルドして、実行します。使用するのはtests/lib/sprintfで、その名の通りsprintf() のテストです。しかし実行するやいなや、エラーで吹き飛んでしまいます。
$ cmake -G Ninja -DBOARD=qemu_rv64_virt ../tests/lib/sprintf/ $ ninja $ ninja run [0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: riscv64 *** Booting Zephyr OS build zephyr-v2.4.0-2328-g4f33cf942643 *** Running test suite test_sprintf =================================================================== START - test_sprintf_double E: Exception cause Illegal instruction (2) E: Faulting instruction address = 0x80000cba E: ra: 0x80003836 gp: 0x00000000 tp: 0x00000000 t0: 0x00000000 E: t1: 0x00000000 t2: 0x00000000 t3: 0x00000000 t4: 0x00000000 E: t5: 0x00000000 t6: 0x00000000 a0: 0x8000bdb0 a1: 0x00000000 E: a2: 0x00000000 a3: 0x00000000 a4: 0x00000000 a5: 0x80006cb6 E: a6: 0x00000000 a7: 0x00000000 E: >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0 E: Current thread: 0x80009820 (unknown) E: Halting system
ログには非常に役立つ情報が出ています。エラーの理由はIllegal Instruction例外、アドレスは0x80000cbaだと言っています。なるほど。この付近を逆アセンブルします。
$ riscv64-zephyr-elf-objdump -drS build32/zephyr/zephyr.elf 0000000080000cb2 <test_sprintf_double>: { 80000cb2: 7121 addi sp,sp,-448 80000cb4: af22 fsd fs0,408(sp) sprintf(buffer, "%e", var.d); 80000cb6: 00006797 auipc a5,0x6 ★fld命令でIllegal Instruction例外★ 80000cba: ad27b407 fld fs0,-1326(a5) # 80006788 <__font_entry_end> 80000cbe: e2040653 fmv.x.d a2,fs0 80000cc2: 00007597 auipc a1,0x7 80000cc6: ad658593 addi a1,a1,-1322 # 80007798 <__clz_tab+0xf78> ...
QEMUのrv64 CPU指定は倍精度浮動小数D拡張命令に対応しているのに、なぜエラーになるのか?原因はztestというZephyrのテストフレームワークの仕組みによるものです。次回以降、エラーの原因を追います。
目次: Zephyr
RISC-V向けZephyrには、既存のボード設定がいくつか含まれていますが、私が持っているボード(SiFive HiFive1 Rev.B)やQEMU用の設定では、浮動小数点数命令(単精度浮動小数点数命令 = F拡張、倍精度浮動小数点数命令 = D拡張)は未サポートです。
実はQEMUは浮動小数点数命令をサポートしているCPUタイプもあります(-cpu rv32もしくは -cpu rv64)。せっかくサポートしているのに使わないのは勿体無いですよね?
新たにqemu_rv64_virtボードを追加し、ハードウェア浮動小数点数命令をサポートします。Zephyrには既に仕組みがあるので、特に難しくはありません。CPU_HAS_FPUとCPU_HAS_FPU_DOUBLE_PRECISIONをselectするだけです。
config BOARD_QEMU_RV64_VIRT
bool "QEMU RV64 virt target"
depends on SOC_QEMU_RV64_VIRT
select QEMU_TARGET
select 64BIT
select CPU_HAS_FPU #★F拡張に対応★
select CPU_HAS_FPU_DOUBLE_PRECISION #★D拡張に対応★
この場合RV64IMAFDCの意味になりますが、select CPU_HAS_FPUだけ指定して、D拡張だけ外したRV64IMAFCにすることもできます。しかしZephyr SDKのツールチェーンが対応していないため、下記のようにリンク時に猛烈にエラーが出て怒られます。
[105/110] Linking C executable zephyr/zephyr_prebuilt.elf FAILED: zephyr/zephyr_prebuilt.elf : && ccache zephyr-sdk/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc zephyr/CMakeFiles/zephyr_prebuilt.dir/misc/empty_file.c.obj -o zephyr/zephyr_prebuilt.elf -Wl,-T zephyr/linker.cmd -Wl,-Map=zephyr/build32/zephyr/zephyr_prebuilt.map -Wl,--whole-archive app/libapp.a zephyr/libzephyr.a zephyr/arch/common/libarch__common.a zephyr/arch/arch/riscv/core/libarch__riscv__core.a zephyr/lib/libc/minimal/liblib__libc__minimal.a zephyr/lib/posix/liblib__posix.a zephyr/subsys/testsuite/ztest/libsubsys__testsuite__ztest.a zephyr/drivers/serial/libdrivers__serial.a -Wl,--no-whole-archive zephyr/kernel/libkernel.a zephyr/CMakeFiles/offsets.dir/./arch/riscv/core/offsets/offsets.c.obj -L"zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0" -Lzephyr/build32/zephyr -lgcc -Wl,--print-memory-usage zephyr/arch/common/libisr_tables.a -mabi=lp64f -march=rv64imafc -Wl,--gc-sections -Wl,--build-id=none -Wl,--sort-common=descending -Wl,--sort-section=alignment -Wl,-u,_OffsetAbsSyms -Wl,-u,_ConfigAbsSyms -nostdlib -static -no-pie -Wl,-X -Wl,-N -Wl,--orphan-handling=warn && : zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_clzdi2.o): ABI is incompatible with that of the selected emulation: target emulation `elf32-littleriscv' does not match `elf64-littleriscv' zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: failed to merge target specific data of file zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_clzdi2.o) zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_ctzdi2.o): ABI is incompatible with that of the selected emulation: target emulation `elf32-littleriscv' does not match `elf64-littleriscv' zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: failed to merge target specific data of file zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_ctzdi2.o) zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_clz.o): ABI is incompatible with that of the selected emulation: target emulation `elf32-littleriscv' does not match `elf64-littleriscv' zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/../../../../riscv64-zephyr-elf/bin/ld: failed to merge target specific data of file zephyr-sdk/riscv64-zephyr-elf/bin/../lib/gcc/riscv64-zephyr-elf/10.2.0/libgcc.a(_clz.o) Memory region Used Size Region Size %age Used RAM: 49220 B 256 KB 18.78% IDT_LIST: 57 B 2 KB 2.78% collect2: error: ld returned 1 exit status ninja: build stopped: subcommand failed.
上記のエラーについてZephyr SDKを作っている人に聞いてみたところ「RV64IMAFCは本当に必要?」と逆に聞かれてしまいました。うーん、現状そんな変なCPUはこの世にないし、対応する理由も思いつきません。要らないですね。
日本政府は未だに東京オリンピックやる気満々らしく、内閣府の発表(国民の祝日について - 内閣府)によると、2020年に引き続いて来年2021年も、海の日、スポーツの日、山の日が本来と異なる日付に移動されるようです。
混乱しそうなのでこのサイトのカレンダー機能にも反映しておきました。カレンダー機能は便利で実装して良かったと思っている機能の一つですが、まさかこんなに東京オリンピック関係で祝日が蹂躙されるとは思っていませんでしたね。
目次: Zephyr
ZephyrのWatchdogのCollaboratorになりました。先日(2020年11月17日の日記参照)CollaboratorになったRISC-VもWatchdogも、元々メンテナーが不在の領域です。
発端はSiFive HiFive1を買った記念にWatchdogドライバを書いたら、割とあっさり動いたので、PRを送ったことです。送ったまでは良かったのですが、メンテナーが不在でした。またこのパターン……。
Zephyrは周辺ドライバが充実しているのが売りですが、メンテナーは不足気味です。今ならサブシステムのCollaboratorくらいなら、何の実績もなくても気前良く追加してくれます。
RTOS興味ある人はいかがですか?
< | 2020 | > | ||||
<< | < | 12 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | 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 | 31 | - | - |
合計:
本日: