TinyFPGA-BX 上の VexRiscv を FT232R チップで gdb する

投稿者: | 2020年4月2日

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 が動いて初めて「これでソフトを開発できるぞ!」という気分になるのではないでしょうか!? 今日はここまでにしておきましょう。

お問い合わせはお気軽に

お問い合わせを頂いた後、継続して営業活動をしたり、ニュースレター等をお送りしたりすることはございません。
御返答は 24時間以内(営業時間中)とさせて頂いております。もし返答が届かない場合、何らかの事情でメールが不達となっている可能性がございます。大変お手数ですが、別のメールアドレス等で督促頂けますと幸いです。