GNU Tools と言う WEB ページ
お恥ずかしながら、最近見つけたのですが、Open Source Tools for Renesas と言う WEB ページがある事に気がつきました。
以前、ルネサスは、KPIT と言う企業と提携しており、そこが GNU 関係の開発環境をサポートしていました。
それが、無くなり、しばらくは、gcc-4.8.3 ベースのコンパイラを保守していたのは知っていましたが、今更、そんな古い gcc を使う気にならず、スルーしていました。
正規の gcc を取得して、それを自分でビルドして、アプリケーションをコンパイルして、十分な性能が出ていたので、あまり追及していませんでした。
ところが、RX72N の登場で、状況が一変します。
- RXv3 コアの採用
- 倍精度浮動小数点演算命令
- TFU(三角関数演算器)の搭載
これらの機能は、正規の gcc ツリーにある、RX マイコン用コードだけではサポートされていません。
※これは、ルネサス社が、gcc のソースツリーに機能改善したコードをマージしない事が原因なのですが、その真意は判りません。
そんな時、上記 WEB ページで「登録」すれば、gcc-8.3.0 ベースのツールチェインがダウンロード出来る事が判り、試してみました。
※gcc-8.3.0 は C++17 に対応しており、自分のフレームワークをコンパイル可能です。
このページは、「CyberTHOR Studios Limited」が保守、管理していて、ルネサス製マイコン用 GNU ツールチェインをサポートするのが目的のようです。
ツールのダウンロードとインストール
「登録」が面倒なものの、ツールをダウンロードしてインストール出来ました。
ツールチェインは、Windows(多分 MinGW 環境による)版と Linux 版があり、ソースコードもダウンロードできます。
OS-X 環境の場合、Linux 版がある事から、ビルド出来るものと思います。
インストールには、登録メールアドレスに起因した「アクティベーションコード」が必要で、登録の際、メールで送られます。
※「登録」にはある程度の個人情報の提出が必要で(これは KPIT の場合もそうだった)、それが何故必要なのか判りません。
自分の場合、MSYS2 環境の CUI 環境で動かす事が「マスト」なので、以下のように、「.bash_profile」にパスを追加しました。
# rx-elf path
# PATH=$PATH:/usr/local/rx-elf/bin
PATH=$PATH:/C/'Program Files (x86)'/'GCC for Renesas RX 8.3.0.202002-GNURX-ELF'/rx-elf/rx-elf/bin
これで、MSYS2 のコンソールからでも、このコンパイラを利用可能です。
このツールチェインは以下の GNU ツールをアーカイブした物です。
- binutils_rx_2.24_2020q2
- gcc_rx_8.3.0_2020q2
- newlib_rx_3.1.0_2020q2
- gdb_rx_7.8.2_2020q2
このツール群は、ルネサスの IDE 環境(e2studio)などで、エミュレータによりデバッグ環境が充実するものと思います。
rx-elf-gcc の --target-help を実行すると、以下の表示が出ます。
% rx-elf-gcc --target-help
The following options are target specific:
-fpu Enable the use of RX FPU instructions. This is
the default.
-m32bit-doubles Stores doubles in 32 bits. This is the default.
-m64bit-doubles Store doubles in 64 bits.
-mallow-string-insns Enables or disables the use of the SMOVF, SMOVB,
SMOVU, SUNTIL, SWHILE and RMPA instructions.
Enabled by default.
-mas100-syntax Generate assembler output that is compatible with
the Renesas AS100 assembler. This may restrict
some of the compiler's capabilities. The default
is to generate GAS compatible syntax.
-mbig-endian-data Data is stored in big-endian format.
-mcpu= Specify the target RX cpu type.
-mdfpu Enable the use of RX DFPU instructions.
-mgcc-abi Enable the use of the old, broken, ABI where all
stacked function arguments are aligned to 32-bits.
-mint-register= Specifies the number of registers to reserve for
interrupt handlers.
-misa= Specify RX ISA version.
-mjsr Always use JSR, never BSR, for calls.
-mlarge-function-growth= Permited value of the limit growth of large
function (in percent).
-mlittle-endian-data Data is stored in little-endian format.
(Default).
-mlra Enable the use of the LRA register allocator.
-mmax-constant-size= Maximum size in bytes of constant values allowed
as operands.
-mno-balign Do not use .balign
-mpid Enables Position-Independent-Data (PID) mode.
-mrelax Enable linker relaxation.
-mrx-abi Enable the use the standard RX ABI where all
stacked function arguments are naturally aligned.
This is the default.
-mrxpeephole Coremark improvement.
-mrxv2-fsqrt Enable to use of FSQRT hardware instruction for
RXv2 instruction set
-msave-acc-in-interrupts Specifies whether interrupt functions should save
and restore the accumulator register.
-msim Use the simulator runtime.
-msmall-data-limit= Maximum size of global and static variables which
can be placed into the small data area.
-mtfu= Enable the use of RX TFU instructions.
-mwarn-multiple-fast-interrupts Warn when multiple, different, fast interrupt
handlers are in the compilation unit.
-nofpu Disable the use of RX FPU instructions.
Assembler options
=================
Use "-Wa,OPTION" to pass "OPTION" to the assembler.
RX specific command line options:
--mbig-endian-data
--mlittle-endian-data [default]
--m32bit-doubles [default]
--m64bit-doubles
--muse-conventional-section-names
--muse-renesas-section-names [default]
--msmall-data-limit
--mrelax
--mpid
--mint-register=<value>
--mcpu=<rx100|rx200|rx230|rx600|rx610|rx64m|rx66T|rx71m|rx72T>
--misa=<v1|v2|v3>
--mno-allow-string-insns --mgcc-abi
--mrx-abi [default]
Linker options
==============
Use "-Wl,OPTION" to pass "OPTION" to the linker.
elf32rx:
--build-id[=STYLE] Generate build ID note
-z common-page-size=SIZE Set common page size to SIZE
-z defs Report unresolved symbols in object files.
-z execstack Mark executable as requiring executable stack
-z max-page-size=SIZE Set maximum page size to SIZE
-z muldefs Allow multiple definitions
-z noexecstack Mark executable as not requiring executable stack
--no-flag-mismatch-warnings Don't warn about objects with incompatible
endian or dsp settings
--flag-mismatch-warnings Warn about objects with incompatible
endian, dsp or ABI settings
--ignore-lma Ignore segment LMAs [default]
(for Renesas Tools compatibility)
--no-ignore-lma Don't ignore segment LMAs
ここで、「-mdfpu」、「-mtfu=」の RX72N にとって重要な二つのオプションがあります。
Renesas GNU-RX 8.3.0 のオプションの違い
Renesas 版を使う場合に、厄介なのは、正規ツリーの gcc とオプションが異なる点です。
※正規の gcc では、大雑把に言うと、RXv1 コアしかサポートされていないと言えますが、それでも、通常のアプリケーションでも、そんなに性能低下する事なく、アプリケーションを動かせます。
また、binutils は、最新版を使う事で、RXv3 の命令をアセンブル可能なので、C++/C ソースコードにアセンブラ命令を直で書く事が出来るので、FreeRTOS などでも何とかなっていました。
とりあえず、Makefile 中の、RX マイコンのオプションを修正して、 自分のプロジェクト全体を、リリースビルド、デバッグビルドでコンパイルしてみました。
-mcpu= オプションには、RX72N は無いのですが、以下のオプションで、実質、RX72N 用となります。
「-mcpu=<rx100|rx200|rx230|rx600|rx610|rx64m|rx66T|rx71m|rx72T>」
-misa=v3
コンパイルは問題無く、全て通ったので、幾つかのアプリケーションを動かしてみました。
これも当然ながら、問題無く動作しました。
ここまで来ると、Renesas GNU-RX を使う事に何らハードルは無いものと思えます。
Makefile のオプションを GNU-RX 用に修正すると、正規の gcc の場合にコンパイルが出来ないので、Makefile の制御文を考えてみました。
# rx-elf-gcc compiler version check
TARGET_ISA_TEXT := $(shell rx-elf-gcc --target-help | grep ISA)
# AS_DEFS = --defsym NOT_USER=1
ifeq ($(TARGET_ISA_TEXT), )
# gcc-7.5.0 current gcc source build
CC_DEFS = -mcpu=rx600 -Wa,-mcpu=rxv2
CP_DEFS = -mcpu=rx600
else # Renesas GNU-RX gcc
CC_DEFS = -misa=v3
# CP_DEFS = -misa=v3 -mdfpu -mtfu=intrinsic,mathlib
CP_DEFS = -misa=v3 -mtfu=intrinsic,mathlib
# CP_DEFS = -misa=v3 -mdfpu
endif
これは、「--target-help」を実行して、その中に「ISA」の文字列があれば、Renesas 版と判断するものです。
Renesas GNU-RX 8.3.0 の最適化性能
いつもの RAYTRACE_sample を動かすと、少しだけ「速い」事に気がつきました。
※正規ツリーの gcc-7.5.0 では、「361ms」
そこで、簡単なプログラムをコンパイルして、アセンブルリストを見てみました。
以下は、LED 点滅のメインループです。
typedef device::PORT<device::PORT0, device::bitpos::B1> LED;
while(1) {
utils::delay::milli_second(250);
LED::P = 0;
utils::delay::milli_second(250);
LED::P = 1;
}
ここで、LED のポートに、「1」、「0」を書く部分に注目しました。
gcc-7.5.0 の場合、以下のようなコードになります。
ffc0029c: cc 3e mov.b [r3], r14
ffc0029e: 75 42 fa mov.l #250, r2
ffc002a1: 75 2e fe and #-2, r14
ffc002a4: c3 3e mov.b r14, [r3]
ffc002bc: cc 3e mov.b [r3], r14
ffc002be: 65 1e or #1, r14
ffc002c0: c3 3e mov.b r14, [r3]
論理演算が使用されていますが、割と普通です。
GNU-RX 8.3.0 では
ffc00278: f0 38 bclr #0, [r3].b
ffc00294: f0 30 bset #0, [r3].b
ビット操作命令になっています。
※これは、gcc-4.8.x のツールチェインで、既に実装済みでした。
多分、上記以外にも、コアの違いによる専用命令など、割と細かい部分で、最適化が行われて、「高速化」したものと思います。
倍精度浮動小数点命令
次に、「-mdfpu」を調べてみました。
-misa=v3 -mdfpu
「-mdfpu」のオプションは、コアが RXv3 である必要があり、「-misa=v3」も必要です。
以下の簡単なプログラム
double a = 0.0;
while(1) {
utils::delay::milli_second(250);
LED::P = 0;
utils::delay::milli_second(250);
LED::P = 1;
a += 0.1;
utils::format("%5.4f\n") % static_cast<float>(a);
}
で、アセンブラ命令を見てみると・・・
ffc003d2: fc c9 08 12 10 dmov.D 72[r0], dr1
ffc003d7: f9 03 00 9a 99 99 99 dmov.L #0x9999999a, drl0
ffc003de: f9 03 02 99 99 b9 3f dmov.L #0x3fb99999, drh0
ffc003e5: 76 90 00 11 dadd dr1, dr0, dr1
ffc003e9: fc 79 08 12 10 dmov.D dr1, 72[r0]
上記のように、「倍精度浮動小数点命令」が使われています。
※ 64 ビットの IEEE-754 で定数 0.1 の表現は、0x3fb9'9999'9999'999a となります。
※ 64 ビットの定数をレジスターにロードするコストは大きいようです・・・
TFU(三角関数演算器)の利用
RX72N には、TFU(三角関数演算器)が内蔵されています。
ですが、仕様は公開されていません、コンパイラのサポートが必要です。
また、GNU-RX で TFU を有効にする場合の解説を見つけられなかったので、GNU-RX のソースコードを取得して、その部分を少し読んでみました。
gcc のソースコード「gcc/config/rx/rx.opt」を見ると・・・
EnumValue
Enum(rx_tfu_types) String(intrinsic) Value(RX_INTRINSIC)
EnumValue
Enum(rx_tfu_types) String(intrinsic,mathlib) Value(RX_MATHLIB)
とあり、「-mtfu=」の定数として、
-mtfu=intrinsic
-mtfu=intrinsic,mathlib
のどちらかを設定すれば良さそうだと判ります。
「gcc/config/rx/rx.c」で、
if (TARGET_TFU)
{
ADD_RX_TFU_BUILTIN (INIT, "__init_tfu", void);
ADD_RX_BUILTIN3 (SINCOSF, "sincosf", void, float, float_ptr, float_ptr);
ADD_RX_BUILTIN4 (ATAN2HYPOTF, "atan2hypotf", void, float, float, float_ptr, float_ptr);
if (rx_tfu_type == RX_MATHLIB)
{
ADD_RX_BUILTIN1 (SINF, "sinf", float, float);
ADD_RX_BUILTIN1 (COSF, "cosf", float, float);
ADD_RX_BUILTIN2 (ATAN2F, "atan2f", float, float, float);
ADD_RX_BUILTIN2 (HYPOTF, "hypotf", float, float, float);
}
}
となっており、上記 API が使える事が判ります。
色々調べると、この API はビルトイン関数として登録されるようです。
関数のプロトタイプは、以下のようになっています。
// -mtfu=intrinsic
void __init_tfu(void);
void __builtin_rx_sincosf(float, float*, float*);
void __builtin_rx_atan2hypotf(float, float, float*, float*);
// -mtfu=intrinsic,mathlib
float __builtin_rx_sinf(float);
float __builtin_rx_cosf(float);
float __builtin_rx_atan2f(float, float);
float __builtin_rx_hypotf(float, float);
これら API の仕様は、C の数学ライブラリに準拠するようです。
このプロトタイプ宣言は、「-mtfu=」を設定する事で内部的に「有効」になります。
これら、ビルトイン関数を利用する場合、多分、事前に初期化関数を呼んでおく必要があるようです。
※ビルトイン関数が有効になると、内蔵変数「__TFU」も有効になります。
#if definrd(__TFU)
__init_tfu();
#endif
後は、普通に関数を呼べば良いものと思います。
float a = vtx::get_pi<float>() * 0.25f;
float si, co;
__builtin_rx_sincosf(a, &si, &co);
utils::format("%8.7f: %8.7f, %8.7f\n") % a % si % co;
a = vtx::get_pi<float>() * 1.75f;
si = __builtin_rx_sinf(a);
co = __builtin_rx_cosf(a);
utils::format("%8.7f: %8.7f, %8.7f\n") % a % si % co;
0.7853982: 0.7071068, 0.7071067
5.4977875: -0.7071068, 0.7071067
倍精度浮動小数点演算、TFU のパフォーマンス
ルネサスの HP に以下の資料を見つけました。
RXv3 CPU搭載RXファミリ 数値計算用ライブラリ関数ベンチマーク
むすび
これで、最新の RX72N を使い、GNU-RX gcc-8.3.0 で、十分な性能を出す土台が揃ったと言えます。
今後、他のオプションについても、色々研究していこうと思います。
それにしても、これら gcc への追加機能を、正規の gcc ソースツリーにマージしないのは何故なのか疑問は残ります・・