目次: OpenOCD
OpenOCDにRISC-Vの独自(もしくは標準に準拠しているものの新しすぎるなど)のCSR(Control and Status Register)を定義してアクセスする方法をメモしておきます。
前回はexpose_csrsを使って独自のレジスタを定義しました。この機能はOpenOCDの改造が不要で手軽な反面、2つの問題があります。1つ目の問題はSMPモードだと使えないことで、SMPモードと併用すると下記のように怒られCSRにアクセスできません。
Warn : Register csrNNN does not exist in riscv.cpu.0, which is part of an SMP group where this register does exist.
2つ目の問題点は名前がわかりづらいことです。csrNNNのようなほぼ番号同然の名前を暗記するのは正直言って辛いですよね。
CSR名を新たに追加するにはOpenOCDにパッチを当てて、再ビルドする必要があります。OpenOCDのビルド方法は以前書きました(2023年6月28日の日記参照)のでそちらに任せるとして、今回はパッチについて紹介しましょう。
前回同様、題材はRNMI CSRを使います。RISC-V Privileged Architectures V20211203(RISC-V Instruction Set Manual の2023-05-23のリリースページからダウンロードできます)を見ると、RNMIでは4つのCSRが定義されています。
書き起こしておくと、
となります。OpenOCDを変更すべき箇所はsrc/target/riscv/encoding.hというヘッダファイルだけです。
diff --git a/src/target/riscv/encoding.h b/src/target/riscv/encoding.h
index c2da4e676..6c3f9cc12 100644
--- a/src/target/riscv/encoding.h
+++ b/src/target/riscv/encoding.h
@@ -2992,6 +2992,10 @@
#define CSR_PMPADDR61 0x3ed
#define CSR_PMPADDR62 0x3ee
#define CSR_PMPADDR63 0x3ef
+#define CSR_MNSCRATCH 0x740
+#define CSR_MNEPC 0x741
+#define CSR_MNCAUSE 0x742
+#define CSR_MNSTATUS 0x744
#define CSR_MSECCFG 0x747
#define CSR_TSELECT 0x7a0
#define CSR_TDATA1 0x7a1
@@ -4714,6 +4718,10 @@ DECLARE_CSR(pmpaddr60, CSR_PMPADDR60)
DECLARE_CSR(pmpaddr61, CSR_PMPADDR61)
DECLARE_CSR(pmpaddr62, CSR_PMPADDR62)
DECLARE_CSR(pmpaddr63, CSR_PMPADDR63)
+DECLARE_CSR(mnscratch, CSR_MNSCRATCH)
+DECLARE_CSR(mnepc, CSR_MNEPC)
+DECLARE_CSR(mncause, CSR_MNCAUSE)
+DECLARE_CSR(mnstatus, CSR_MNSTATUS)
DECLARE_CSR(mseccfg, CSR_MSECCFG)
DECLARE_CSR(tselect, CSR_TSELECT)
DECLARE_CSR(tdata1, CSR_TDATA1)
CSR番号とDECLARE_CSRを追加するだけで良いみたいです。さすがOpenOCD便利な作りですね。
前回同様、RNMIを実装しているCPUの例としてNSITEXE NS31を用いてRNMI CSRを読み出してみましょう。
(gdb) info reg mnscratch mnepc mncause mnstatus mnscratch 0x0 0 mnepc 0x0 0 mncause 0x80000000 -2147483648 mnstatus 0x8 8
無事読み出すことができました。やはり名前が付いているとわかりやすいですね。
目次: RISC-V
RISC-Vは最後発の命令セットだけあって、従来の命令セットで評判の悪かった部分は改善されている場合が多いです。しかし人の作るものですからミスや見落としはあります。
例としてわかりやすいのがRV64I命令セットの32bit unsignedと64bit unsigned加算処理です。下記のようなコードを書いたとします。
unsigned int __attribute__((noinline)) something(int n)
{
return n * n;
}
int test(unsigned int num)
{
unsigned long long sum = 0;
for (int i = 0; i < num; i++) {
sum += something(i); //32bit unsigned + 64bit unsigned
}
return sum;
}
下記のようにコンパイルします。RV64GCはRV64IMAFDCの略(M: Multiplication and Division, A: Atomic, F: Single-Precision Floating-Point, D: Double-Precision Floating-Point, C: Compressed)です。RV64I以外のMAFDCの各命令も出てきますが、話題と関係ないので気にしないでください。RV64GCが基本的な命令セットくらいの認識でOKです。
$ riscv64-unknown-elf-gcc -g -O2 -march=rv64gc -mabi=lp64d -c -o rv64gc.o a.c
逆アセンブルを見ると変なシフト命令(アドレス24, 26)が2つ出力されます。
000000000000001a <.L5>:
sum += something(i);
1a: 8522 mv a0,s0 # s0: i, s1: sum
1c: 00000097 auipc ra,0x0
20: 000080e7 jalr ra # call something()
24: 1502 slli a0,a0,0x20 # a0: something() の返り値
26: 9101 srli a0,a0,0x20 # shift x 2で上位32ビットを0埋め
for (int i = 0; i < num; i++) {
28: 2405 addiw s0,s0,1
sum += something(i);
2a: 94aa add s1,s1,a0 # shift x 2 + add
for (int i = 0; i < num; i++) {
2c: ff2417e3 bne s0,s2,1a <.L5>
RV64Iには32bit unsigned向けの加算命令がなく、32bit unsignedを64bit unsignedにゼロ拡張してから加算する必要があるためです。さらに悲しいことにゼロ拡張する命令もなく、32bit左シフト命令+32bit右論理シフト命令でゼロ拡張するヘボい処理になります。
シフト命令2つくらい何だというのか?ケチケチするなよ?という感覚が普通かもしれませんが、余計な命令が出ると特にローエンドのCPUでは性能への影響が無視できません。どうやらCoreMarkのような典型的なベンチマークにも影響が出ていたようで、
// GitHub: sifive/benchmark-coremark
// freedom-metal/core_portme.h
typedef signed short ee_s16;
typedef unsigned short ee_u16;
typedef signed int ee_s32;
typedef double ee_f32;
typedef unsigned char ee_u8;
typedef signed int ee_u32; //★★★u32なのに "signed" intになっている★★★
typedef signed long ee_u64; //★★★u64なのに "signed" longになっている★★★
#if __riscv_xlen == 32
typedef ee_u32 ee_ptr_int;
#else
typedef ee_u64 ee_ptr_int;
#endif
typedef signed int ee_size_t;
RISC-Vの盟主たるSiFiveすらも「unsigned型をsigned型にすりかえて性能を上げるぞい!」というCoreMarkハックを行っていた(該当箇所へのリンク)ほどです……。
当然RV64Iのまずい点はRISC-Vの方々も気づいており、B拡張(Bit-manipulation extensions)を追加したときに上記の問題は修正されました(RISC-V Bitmanipulation extension規格書へのリンク)。
B拡張はZba, Zbb, Zbc, Zbsの4つがあります。
この中のZba拡張にて32bit unsigned加算命令であるadd.uw命令が追加されました。他にも1, 2, 3bitシフト&加算命令なんかも追加されています。unsigned加算や1, 2, 3bitシフト&加算は配列の要素のアドレスを計算する際に頻出で、Address generation instructionsというグループ名にしたのでしょう。
Zba拡張を使うとどのように改善されるか確認します。
$ riscv64-unknown-elf-gcc -g -O2 -march=rv64gc_zba -mabi=lp64d -c -o rv64gcb.o a.c
逆アセンブルを見ると変なシフト命令は消滅し、新たにadd.uw命令が出力されていることが分かると思います。
000000000000001a <.L5>:
sum += something(i);
1a: 8522 mv a0,s0 # s0: i, s1: sum
1c: 00000097 auipc ra,0x0
20: 000080e7 jalr ra # call something()
for (int i = 0; i < num; i++) {
24: 2405 addiw s0,s0,1
sum += something(i);
26: 089504bb add.uw s1,a0,s1 # shift x 2は消滅、add.uwのみ
for (int i = 0; i < num; i++) {
2a: ff2418e3 bne s0,s2,1a <.L5>
めでたしめでたし。なんですけど、人によっては色々言いたいこともあると思います。Bit-manipulationとAddress generation全然関係ないぞ?とかね。
しかし冒頭にも書いたとおり、何事も最初から完璧なものはないです。命令セットが汚くなっていくのはRISC-Vが実用段階に入った証であり、むしろ良いことだと個人的には思います。
目次: RISC-V
先日6月14日に発売開始されたRenesas RZ/Five(R9A07G043F01GBG)搭載のボードAP-RZFV-0A(製品紹介サイトへのリンク)を購入しました。
SoCはRenesas製で、CPUコアは台湾Andes TechnologyのAX45MP(製品紹介サイトへのリンク)です。
ボードだけ購入するとACアダプタが付属していません。全部入りが良ければLinux開発キットを併せて購入すると良いかと思います。
秋月にて5VセンタープラスのACアダプタATS012S-W050U(製品紹介サイトへのリンク)を購入しました。700円です。安い。何も考えず2A出力のアダプタにしましたが、出力が足りなかったらまた考えます。
ボード上のJTAG端子CN3を見ると、ハーフピッチと呼ばれる1.27mm間隔の10ピンヘッダでした。このヘッダに接続できるJTAGケーブルを持っていないので、Strawberry LinuxにてJTAG変換基盤ARM-JTAG-20-10(製品紹介サイトへのリンク)を購入しました。800円くらいです。これも安い。
電源に関してはACアダプタ以外にも、安定化電源やUSBから取る方法(ジャンパ端子J18を半田付けでショートさせる必要あり)があります。ボードのマニュアルをご参照ください。
JTAGコネクタCN3にJTAG変換基盤ARM-JTAG-20-10のハーフピッチコネクタを接続します。ケーブルの赤い線が1番ピン側(ボード上の白い三角マークがある、LANコネクタなどがある方)です。写真も載せておきます。
ボードのマニュアルを見るとわかりますが、SoCにDEBUGENという端子があり [1]DebugModeと [0]NormalMode(出荷時設定)があるそうです。ボード上のDIPスイッチ(SW2の3番)をOFFにするとDebugModeに切り替わりJTAGが繋がるようになります。
お持ちのJTAGによって設定がやや違いますが、SEGGER J-Linkの場合はOpenOCDの設定ファイルはこんな感じです。
adapter speed 1000 reset_config trst_and_srst set _CHIPNAME riscv set _DAP_TAPID 0x1000563d jtag newtap $_CHIPNAME dap -irlen 5 -ircapture 0x01 -irmask 0x03 \ -expected-id $_DAP_TAPID set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME.0 riscv -chain-position $_CHIPNAME.dap -coreid 0
設定をファイルに保存したら、J-Linkの設定ファイルとともに指定して起動しましょう。
$ sudo ./src/openocd -c 'bindto 0.0.0.0' -f tcl/interface/jlink.cfg -f ~/openocd_renesas_rzv.cfg Open On-Chip Debugger 0.11.0+dev-00551-gaad871805 (2022-01-16-22:30) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'. 0 Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : J-Link V11 compiled Jul 3 2020 10:47:34 Info : Hardware version: 11.00 Info : VTarget = 1.812 V Info : clock speed 1000 kHz Info : JTAG tap: riscv.dap tap/device found: 0x1000563d (mfg: 0x31e (Andes Technology Corporation), part: 0x0005, ver: 0x1) Info : datacount=4 progbufsize=8 Info : Examined RISC-V core; found 1 harts Info : hart 0: XLEN=64, misa=0x800000000094312d Info : starting gdb server for riscv.cpu.0 on 3333 Info : Listening on port 3333 for gdb connections
無事CPUが認識されました。よきかなよきかな。
目次: 自宅サーバー
この日記システムをPHPの最新バージョンPHP 8に対応させました。
きっかけはさくらインターネットから「PHP 5を使うのは危険だよ」というメールが来たことです。PHP 5.6のEOLは2018年なので5年くらい放置していたんですね。さすがにサボりすぎました。
PHP 5とPHP 7は互換性が保たれていて(PHP 6は欠番らしい)おり1文字も変更することなくPHPのバージョンアップに対応できました。ところがPHP 8は古い機能を色々と廃止したようで全く動きませんでした。
PHP 8で動かなくなった機能達のエラーメッセージや直し方(正しいかどうか知らない)を順不同で紹介したいと思います。
PHP Fatal error: Uncaught Error: Call to undefined function get_magic_quotes_gpc()
この関数は説明を見ても常にfalseを返すと書いてあり、もはや存在しようがしなかろうが呼ぶ意味がありません。この関数を呼んでいるコードごと消しました。
クラスのコンストラクタが曲者でした。PHP 7までクラスと同名の関数がコンストラクタ扱いされましたが、PHP 8から __construct() がコンストラクタ扱いされます。この変更の影響であらゆるクラスの初期化が実行されなくなって、訳のわからないエラーを引き起こしました。デバッグが一番面倒くさかったです。
PHP Fatal error: Array and string offset access syntax with curly braces is no longer supported
PHP 8では波括弧 {} によるオフセットの指定が廃止されたので、角括弧 [] に置き換える必要があります。難しくはないですが、地味に使っている箇所が多く修正が大変でした……。
PHP Fatal error: Uncaught Error: Call to undefined function each()
PHP 8ではeach() 関数が廃止されました。これも複数ヶ所で使っていて修正が面倒でした。
// 修正前
reset($array);
list($a, $b) = each($array);
// 修正後
reset($array);
$a = key($array);
$b = current($array);
上記のように先頭のキーと値を取り出すためだけに使っていたので、単純にkey() とcurrent() 関数で書き換えました。
PHP Fatal error: Uncaught TypeError: mb_strrpos(): Argument #3 ($offset) must be of type int, string given
PHP 7はmb_strrpos() 関数の3番目の引数(offsetの位置)にencodeを指定してもエラーにならなかったのですが、PHP 8ではoffset, encodeを指定しないとエラーになるようです。PHPは良くわかりませんなあ。
目次: 自宅サーバー
自宅のファイルサーバー兼WebサーバーでPHPが動かなくなっていました。
素のPHP 8すら動かないので、おそらくPHP 5の寿命が尽きたときにPHP周りの設定が全部吹っ飛んで動かなくなったと思われます。自宅のWebサーバーは自宅から見えないので、長らく気づいていませんでした……。あれまあ。
まずはPHP CGIや日記システムで使っているGDやmbstringをインストールします。
# apt-get install php-cgi php-gd libapache2-mod-php8.2 php8.2-mbstring
Apache 2の設定ファイルを変更してPHP 8.2を有効にします。
# cd /etc/apache2/mods-enabled # ln -s ../mods-available/php8.2.conf # ln -s ../mods-available/php8.2.load # systemctl restart apache2
Apache 2はユーザーディレクトリといって /home/username/public_html/ ディレクトリに置いたファイルが、URL /~username/ で見える仕組みがありまして、日記システムはユーザーディレクトリに配置しています。
初期状態のphp8.2.confだとユーザーディレクトリの配下にあるPHPスクリプトは動きません。わざと無効化してあります。
# /etc/apache2/mods-enabled/php8.2.conf
#...略...
# Running PHP scripts in user directories is disabled by default
#
# To re-enable PHP in user directories comment the following lines
# (from <IfModule ...> to </IfModule>.) Do NOT set it to On as it
# prevents .htaccess files from disabling it.
<IfModule mod_userdir.c>
<Directory /home/*/public_html>
php_admin_flag engine Off
</Directory>
</IfModule>
コメントにある通りIfModuleタグごと全てコメントにして、Apache 2を再起動しましょう。これできっとPHP 8が動くはずです。
< | 2023 | > | ||||
<< | < | 07 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | - | 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 | - | - | - | - | - |
合計:
本日: