Debugging VexRiscv, on TinyFPGA-BX, by OpenOCD + FT232R + GDB.
嬉しかったので、思わず欲張ってしまい、ブログのタイトルにキーワードを 4つも納めてしまいました。
先日、TinyFPGA-BX 上に RISC-V のソフトコアプロセッサ VexRiscv を載せて動かすところまで御紹介しましたが、今日は OpenOCD + gdb で VexRiscv 上のコードをデバッグしてみることにしました。Dolu1990 (Charles Papon) さんの OpenOCD VexRiscv ポーティングでは、FT2232H チップを使った JTAG インターフェイスを使っているようですが、私の手元にはなく(近いうちに購入しようかと思っているのですが)、今回は手元にあった FTDI FT232R チップのブレークアウトを試してみることにしました。
皆様の工作箱でも、FT2232H あるいは FT232H のボードは無くても、FT232R のボードだったら転がっている、という方はあるのではないかと思います。例えばこんなの(SparkFun 製)です。
最近はマイコンボードに FTDI チップが載っているものが多いので、あまり活躍せずに工具箱で埃をかぶっていたりするのではないでしょうか。(工具箱の中じゃ埃は付かないか…)
OpenOCD のドキュメントを当たってみると、FT232R でも JTAG デバッグできるようなので試してみましょう。
OpenOCD for VexRiscv
まず最初に、VexRiscv 対応の OpenOCD をビルドします。私は、Git タグ 92c0542 のバージョンを Mac OS X(El Capitan 10.11.6)でビルドしましたが、少しコードに手を入れる必要がありました。Linux 上であればそのままビルドできるかも知れません。
パッチを以下に示します。
diff --git a/src/target/vexriscv-csrs.h b/src/target/vexriscv-csrs.h
index 94097253..6ff8b0b3 100644
--- a/src/target/vexriscv-csrs.h
+++ b/src/target/vexriscv-csrs.h
@@ -1,5 +1,5 @@
#ifndef __VEXRISCV_CSR_H__
-#define __VEXROSCV_CSR_H__
+#define __VEXRISCV_CSR_H__
struct vexriscv_core_reg_init {
const char *name;
@@ -1758,4 +1758,4 @@ const struct vexriscv_core_reg_init vexriscv_init_reg_list[] = {
},
};
-#endif /* __VEXROSCV_CSR_H__ */
+#endif /* __VEXRISCV_CSR_H__ */
diff --git a/src/target/vexriscv.c b/src/target/vexriscv.c
index 1359248e..b6eac5cb 100644
--- a/src/target/vexriscv.c
+++ b/src/target/vexriscv.c
@@ -40,6 +40,10 @@
#define FALSE 0
#define TRUE 1
+#include <libkern/OSByteOrder.h>
+#define htobe32(x) OSSwapHostToBigInt32(x)
+#define be32toh(x) OSSwapBigToHostInt32(x)
+
struct BusInfo{
uint32_t flushInstructionsSize;
uint32_t *flushInstructions;
@@ -1268,7 +1272,7 @@ static int vexriscv_write_memory(struct target *target, target_addr_t address,
if(size == 4 && count > 4){
//use 4 address registers over a range of 16K in order to reduce JTAG usage
int maxAddressReg = 4;
- uint32_t numAddressReg = MIN(maxAddressReg, (count * size - 1) / 4096 + 1);
+ uint32_t numAddressReg = MIN(maxAddressReg, (int) ((count * size - 1) / 4096 + 1));
if(count * size > 4096*numAddressReg){
if(vexriscv_write_memory(target,address,size,numAddressReg*4096/size,buffer)) return ERROR_FAIL;
if(vexriscv_write_memory(target,address+4096*numAddressReg,size,count-4096/size*numAddressReg,buffer+4096*numAddressReg)) return ERROR_FAIL;
続いてビルドです。
$ ./bootstrap
$ ./configure --enable-ft232r --enable-dummy --prefix=~/openocd
$ make
$ make install
のような感じでしょうか。--enable-ft232r がポイントです。なお余談ですが、make 時にドキュメントを生成しようとしてエラーが出る場合、make MAKEINFO=true とかすると、エラーを避けられます。
OpenOCD を動かす
早速、FT232R ブレークアウトを USB で繋いで動かしてみましょう。cpu0.yaml ファイルが必要なので、前回、私が GitHub に上げた Murax ビルドで試してみます。(なおこのとき、まだ JTAG の配線はしていません。ブレークアウトを繋いだだけです。)
$ cd どこかの/VexRiscv/scripts/Murax/iCE40-tinyfpga-bx
$ make
$ ~/openocd/bin/openocd -f interface/ft232r.cfg -c 'set MURAX_CPU0_YAML ../../../cpu0.yaml' -f target/murax.cfg
私が試したときは、以下のようなエラーになってしまいました。
Open On-Chip Debugger 0.10.0+dev-01223-g92c05420-dirty (2020-04-02-11:10)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'jtag'
adapter speed: 1000 kHz
../../../cpu0.yaml
adapter speed: 800 kHz
adapter_nsrst_delay: 260
jtag_ntrst_delay: 250
Info : set servers polling period to 50ms
Error: unable to claim interface
これは、FTDI チップ用の VCP(仮想 COM ポート)ドライバがロードされており、先に FT232R に接続してしまっているために起きる現象です。ドライバを(一時的に)アンロードするには次のようにします。(繰り返しますが、Mac OS で動かしています。Windows の方は、御自身でお調べください。スミマセン)
$ sudo kextunload /System/Library/Extensions/AppleUSBFTDI.kext
ここでもし、「Can’t unload なんとか」みたいなカーネルエラーが出た場合には、一度 Mac OS をリブートすると直ることが多いようです。(どうも、ドライバを何かが握ったままになってしまっていて、unload できないらしい。)
そして再度 openocd を起動すると、今度は無事に立ち上がりました。
Open On-Chip Debugger 0.10.0+dev-01223-g92c05420-dirty (2020-04-02-11:10)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'jtag'
adapter speed: 1000 kHz
../../../cpu0.yaml
adapter speed: 800 kHz
adapter_nsrst_delay: 260
jtag_ntrst_delay: 250
Info : set servers polling period to 50ms
Info : clock speed 750 kHz
Error: JTAG scan chain interrogation failed: all ones
Error: Check JTAG interface, timings, target power, etc.
(略)
ただし、今はまだ VexRiscv と JTAG の結線をしていないので、JTAG のスキャンチェーンを見つけられないと言ってエラーになってしまいます。しかしこれは想定通りです。
JTAG を結線する
まず最初の注意点は、FT232R が 3.3V で動いていることの確認です。上述の SparkFun 社製品では小さなスイッチで 3.3V を選ぶことができるようになっています。
一度、FT232R ボードの USB ケーブルを外します。そして、OpenOCD のドキュメントを読みながら配線します。以下に、ピンアサインメントを引用します。カッコの中は FT232RL チップのピン番号のようです。なお、VexRiscv の JTAG では、TRST と SRST は無いので無視して大丈夫です。
- RXD (5) – TDI
- TXD (1) – TCK
- RTS (3) – TDO
- CTS (11) – TMS
- DTR (2) – TRST
- DCD (10) – SRST
なおこれ以外に、GND ピン同士を接続します。(下記の写真では緑色のワイヤです。同じパソコンから USB 接続している場合は GND は共通になる可能性が高いですが、ちゃんと接続しておきましょう。)
参考までに、前回の私の GitHub で紹介した Murax 構成では、PCF(pin constraint)で次のようにピンを割り当てています。
set_io io_jtag_tck A2 set_io io_jtag_tdi A1 set_io io_jtag_tdo B1 set_io io_jtag_tms C2
以下に、私の配線例を示します。(黄色いテープは、別名「マスキングテープハック」 🙂 です。)
早速動かしてみる
それでは動かしてみましょう。TinyFPGA-BX ボードとFT232R ボードを USB ケーブルでパソコンに繋ぎます。(その際、もう一度 FT232R ボードの電源電圧が 3.3V であるか確認します。心配な方はデジタルマルチメーターなどでチェックしたほうが良いでしょう。)
ここで、TinyFPGA ボードのフラッシュ ROM には先日の Murax が書き込まれていて、さらに tinyprog -b コマンドでブートされていることを確認します。(TinyFPGA ボードの USB コネクタがパソコンでなく USB 電源に接続されている場合には、tinyprog コマンドは不要です。)
そうしたらもう一度、上述のように openocd コマンドを起動します。
Open On-Chip Debugger 0.10.0+dev-01223-g92c05420-dirty (2020-04-02-11:10)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'jtag'
adapter speed: 1000 kHz
../../../cpu0.yaml
adapter speed: 800 kHz
adapter_nsrst_delay: 260
jtag_ntrst_delay: 250
Info : set servers polling period to 50ms
Info : clock speed 750 kHz
Info : JTAG tap: fpga_spinal.bridge tap/device found: 0x10001fff (mfg: 0x7ff (), part: 0x0001, ver: 0x1)
Info : Listening on port 3333 for gdb connections
requesting target halt and executing a soft reset
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
無事に起動しましたね!
ロードするプログラムを用意する
早速 GDB を動かしてみたいところですが、GDB でロードする ELF ファイルを用意しなくてはいけません。そのためには、こちらにある Git リポジトリからサンプルコードのソースを取得してビルドするのが近道です。なお、GCC や GDB 等のツールチェインは、そのリポジトリのトップにある README を参考にして /opt/riscv にインストールしておいてください。
$ cd どこかの/VexRiscvSocSoftware/projects/murax/demo
$ make
GDB を立ち上げる
続いて GDB を立ち上げます。(gdb) というプロンプトが出たら、target remote :3333 というコマンドを入力します。
$ cd どこかの/VexRiscv/scripts/Murax/iCE40-tinyfpga-bx
$ /opt/riscv/bin/riscv64-unknown-elf-gdb 上でビルドした/VexRiscvSocSoftware/projects/murax/demo/build/demo.elf
GNU gdb (SiFive GDB 8.3.0-2019.08.0) 8.3
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin17.7.0 --target=riscv64-unknown-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://github.com/sifive/freedom-tools/issues>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /Users/yokoyama/.../VexRiscvSocSoftware/projects/murax/demo/build/demo.elf...
(gdb) target remote :3333
Remote debugging using :3333
crtStart () at src/crt.S:6
6 j crtInit
(gdb)
素晴らしい〜! いとも簡単に動いてしまいました。Charles Papon さん、すげー!
少し GDB をいじってみる
少しだけ、GDB コマンドを入力してみましょう。
(gdb) tb main
Temporary breakpoint 1 at 0x800002d4: file src/main.c, line 7.
(gdb) c
Continuing.
Program stopped.
main () at src/main.c:7
7 volatile uint32_t a = 1, b = 2, c = 3;
(gdb) search LED
32 GPIO_A->OUTPUT = (GPIO_A->OUTPUT & ~0x3F) | ((GPIO_A->OUTPUT + 1) & 0x3F); //Counter on LED[5:0]
(gdb) b 32
Breakpoint 2 at 0x800003dc: file src/main.c, line 32.
(gdb) c
Continuing.
Program stopped.
main () at src/main.c:32
32 GPIO_A->OUTPUT = (GPIO_A->OUTPUT & ~0x3F) | ((GPIO_A->OUTPUT + 1) & 0x3F); //Counter on LED[5:0]
(gdb) c
Continuing.
Program stopped.
main () at src/main.c:32
32 GPIO_A->OUTPUT = (GPIO_A->OUTPUT & ~0x3F) | ((GPIO_A->OUTPUT + 1) & 0x3F); //Counter on LED[5:0]
(gdb) info reg
ra 0x80000310 0x80000310 <main+76>
sp 0x80000660 0x80000660
gp 0x80000c88 0x80000c88
tp 0x0 0x0
t0 0x0 0
t1 0x0 0
t2 0x0 0
fp 0x80000690 0x80000690
s1 0x0 0
a0 0xf0020040 -268304320
a1 0x80000484 -2147482492
a2 0x0 0
a3 0x80 128
a4 0xc350 50000
a5 0xc34f 49999
a6 0x0 0
a7 0x0 0
s2 0x0 0
s3 0x0 0
s4 0x0 0
s5 0x0 0
s6 0x0 0
s7 0x0 0
s8 0x0 0
s9 0x0 0
s10 0x0 0
s11 0x0 0
t3 0x0 0
t4 0x0 0
t5 0x0 0
t6 0x0 0
pc 0x800003dc 0x800003dc <main+280>
(gdb) x/32x 0x80000000
0x80000000 : 0x0b00006f 0x00000013 0x00000013 0x00000013
0x80000010 <crtStart+16>: 0x00000013 0x00000013 0x00000013 0x00000013
0x80000020 : 0xfe112e23 0xfe512c23 0xfe612a23 0xfe712823
0x80000030 <trap_entry+16>: 0xfea12623 0xfeb12423 0xfec12223 0xfed12023
0x80000040 <trap_entry+32>: 0xfce12e23 0xfcf12c23 0xfd012a23 0xfd112823
0x80000050 <trap_entry+48>: 0xfdc12623 0xfdd12423 0xfde12223 0xfdf12023
0x80000060 <trap_entry+64>: 0xfc010113 0x3a4000ef 0x03c12083 0x03812283
0x80000070 <trap_entry+80>: 0x03412303 0x03012383 0x02c12503 0x02812583
(gdb)
ソフト屋としては、GDB が動いて初めて「これでソフトを開発できるぞ!」という気分になるのではないでしょうか!? 今日はここまでにしておきましょう。