前回 の記事でアセンブラ でLチカ、Hello world はできました。
今回はC言語 で試してみました。
実用性を求めるとC記述でも動作することが重要ですが、なによりもハーバードアーキテクチャ の場合、Load/Store動作のデバッグ になると思います。関数にジャンプする時、main関数にreturnする時、容赦無くLoad/Storeの連発をするからです。
2,3個Load/store命令が連続しているようなコードでは動作(実際はしていないのだけれどもタイミングの功罪で動いているように見える)していても、数十回連続すると動作しない事態が起こり得ます。
Cでは容赦無くLoad/Store行うので、良い試験になります。
いままでアセンブラ のみ使っていたためまともな環境を構築していませんでした。
※ビルドに必要なパッケージ(automake autotools-dev curl などなど)はすでに導入済みとします。
git clone https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain
./configure --prefix=/opt/riscv --with-arch=rv32i
sudo make ここで、/opt/の権限によりますがsudo makeしないと不可解なエラーになります。permission deniedって出ればわかりやすいのですが。
.bashrcなどでPATHを通したら完成です。
$ riscv32-unknown-elf-gcc -v
Using built-in specs.
COLLECT_GCC=riscv32-unknown-elf-gcc
COLLECT_LTO_WRAPPER=/opt/riscv/libexec/gcc/riscv32-unknown-elf/10.2.0/lto-wrapper
Target: riscv32-unknown-elf
Configured with: /mnt/c/riscv-gnu-toolchain/riscv-gcc/configure --target=riscv32-unknown-elf --prefix=/opt/riscv --disable-shared --disable-threads --enable-languages=c,c++ --with-system-zlib --enable-tls --with-newlib --with-sysroot=/opt/riscv/riscv32-unknown-elf --with-native-system-header-dir=/include --disable-libmudflap --disable-libssp --disable-libquadmath --disable-libgomp --disable-nls --disable-tm-clone-registry --src=.././riscv-gcc --disable-multilib --with-abi=ilp32 --with-arch=rv32i --with-tune=rocket 'CFLAGS_FOR_TARGET=-Os -mcmodel=medlow' 'CXXFLAGS_FOR_TARGET=-Os -mcmodel=medlow'
Thread model: single
Supported LTO compression algorithms: zlib
gcc version 10.2.0 (GCC)
リンカ・スタートアップの準備
参考サイト。みつきん(id:mickey_happygolucky )さんのブログです。
mickey-happygolucky.hatenablog.com
ほぼそのままですが、.text領域と.bss 領域がことなりますので、それを書き換え、stackの最終アドレスが0x7fff(ただし4-byte-alignment)になるようにスタートアップコードをなおしました。良記事でした。ありがとうございます。
linker.ld
OUTPUT_ARCH( "riscv" )
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
.text.init : { *(.text.init) }
.tohost : { *(.tohost) }
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
_end = .;
}
---------ここから:2021年1月18日加筆修正---------
スタートアップコードは少し修正しています。今回作成したRV32IはPrivillaged ISAの規格通 り、Machine-mode timer(mtime)を64bitで実装しています。
下位32bitをget_timel()、上位32bitをget_timeh()で取得できるようにしました。
utils.S
.section .text.init;
.global _start
_start:
lui sp, 0x08
call main
j .
.global dummy
dummy:
ret
.global put32
put32:
sw x11,(x10)
ret
.global get32
get32:
lw x10,(x10)
ret
.global get_timel
get_timel:
csrr x10, time
ret
.global get_timeh
get_timeh:
csrr x10, timeh
ret
Cを記述する
簡単なLチカコードです。こちらもみつきんさんのコードとあまり変わりませんが、タイマーmtimeを使用しています。
コードを記述する前にKyogenRVの現時点で最新のタグが付されているバージョンをcloneします。
git clone http://github.com/panda5mt/KyogenRV -b 0.2.6 --depth 1
make sdk make sdk
でVerilog 生成、テストコードのhexファイル生成、今回のテスト用Cコードコンパイル を一括で行います。
Scala /chisel環境のない方は実行しない方がいいと思います。すでにコンパイル 済みのhexファイルが消えるので。
後述しますが、今回のCプロジェクトをコンパイル するだけの場合はmake c_all
を実行します。
KyogenRVプロジェクトルート/src/sw/main.c
を記述します。
github.com
void put32(unsigned int , unsigned int );
unsigned int get32(unsigned int );
unsigned int get_timel(void );
unsigned int get_timeh(void );
void dummy (void );
#define GPIO_BASE 0x8000
#define XTAL_FREQ_KHZ 60000
void wait_ms(unsigned int msec){
volatile unsigned int oldtime;
oldtime = get_timel();
while ((get_timel()-oldtime) < XTAL_FREQ_KHZ * msec);
}
int main(int argc, char *argv[]) {
while (1 ) {
wait_ms(500 );
put32(GPIO_BASE, 0x55 );
wait_ms(500 );
put32(GPIO_BASE, 0xAA );
}
return 0 ;
}
標準IOもなにもインクルードしていない状態です。これで動作します。
プロジェクトのルートに戻り、下記を実行します
make c_all KyogenRVプロジェクトルート/src/sw/blinker.hex
が生成されたhexです。
Quartus Primeで使用するインテル hexを生成する場合は
プロジェクトのルートに戻り、下記を実行します
./mk_intel_hex.py KyogenRVプロジェクトルート/fpga/chisel_generated/blinker_intel.hex
が生成されたインテル hexです。
前回 のようにPlatformDesignerでon-chip ramの初期値に設定し、コンパイル することでFPGA 上で動作します。
動作風景
VIDEO youtu.be
CPUデバッグ 時にはアセンブラ でみた方が良いと思います。
make c_reverse で逆アセンブル できます。
どのようなコードになっているか見ることができます。
セクション .text.init の逆アセンブル:
00000000 <_start >:
0: 00008137 lui sp ,0x8
4 : 0a8000ef jal ra ,ac <main >
8 : 0000006 f j 8 <_start +0x8 >
0000000 c <dummy >:
c : 00008067 ret
00000010 <put32 >:
10 : 00b52023 sw a1 ,0(a0 )
14 : 00008067 ret
00000018 <get32 >:
18 : 00052503 lw a0 ,0(a0 )
1 c : 00008067 ret
00000020 <get_timel >:
20 : c0102573 rdtime a0
24 : 00008067 ret
00000028 <get_timeh >:
28 : c8102573 rdtimeh a0
2 c : 00008067 ret
セクション .text の逆アセンブル:
00000030 <wait_ms >:
30 : fd010113 addi sp ,sp ,-48
34 : 02112623 sw ra ,44 (sp )
38 : 02812423 sw s0 ,40 (sp )
3 c : 03010413 addi s0 ,sp ,48
40 : fca42e23 sw a0 ,-36 (s0 )
44 : fddff0ef jal ra ,20 <get_timel >
48 : 00050793 mv a5 ,a0
4 c : fef42623 sw a5 ,-20 (s0 )
50 : 00000013 nop
54 : fcdff0ef jal ra ,20 <get_timel >
58 : 00050713 mv a4 ,a0
5 c : fec42783 lw a5 ,-20 (s0 )
60 : 40 f706b3 sub a3 ,a4 ,a5
64 : fdc42703 lw a4 ,-36 (s0 )
68 : 00070793 mv a5 ,a4
6 c : 00579793 slli a5 ,a5 ,0x5
70 : 40 e787b3 sub a5 ,a5 ,a4
74 : 00279793 slli a5 ,a5 ,0x2
78 : 00e787b3 add a5 ,a5 ,a4
7 c : 00479713 slli a4 ,a5 ,0x4
80 : 40 f70733 sub a4 ,a4 ,a5
84 : 00571793 slli a5 ,a4 ,0x5
88 : 00078713 mv a4 ,a5
8 c : 00070793 mv a5 ,a4
90 : fcf6e2e3 bltu a3 ,a5 ,54 <wait_ms +0x24 >
94 : 00000013 nop
98 : 00000013 nop
9 c : 02 c12083 lw ra ,44 (sp )
a0 : 02812403 lw s0 ,40 (sp )
a4 : 03010113 addi sp ,sp ,48
a8 : 00008067 ret
000000 ac <main >:
ac : fe010113 addi sp ,sp ,-32
b0 : 00112 e23 sw ra ,28 (sp )
b4 : 00812 c23 sw s0 ,24 (sp )
b8 : 02010413 addi s0 ,sp ,32
bc : fea42623 sw a0 ,-20 (s0 )
c0 : feb42423 sw a1 ,-24 (s0 )
c4 : 1 f400513 li a0 ,500
c8 : f69ff0ef jal ra ,30 <wait_ms >
cc : 05500593 li a1 ,85
d0 : 00008537 lui a0 ,0x8
d4 : f3dff0ef jal ra ,10 <put32 >
d8 : 1 f400513 li a0 ,500
dc : f55ff0ef jal ra ,30 <wait_ms >
e0 : 0aa00593 li a1 ,170
e4 : 00008537 lui a0 ,0x8
e8 : f29ff0ef jal ra ,10 <put32 >
ec : fd9ff06f j c4 <main +0x18 >
---------ここまで:2021年1月18日加筆修正---------
この分量であれば、アセンブラ でも追いかけられるレベルです。
SignalTap片手に波形を追いかけながらCPUデバッグ をしました。
問題点:riscv-testsがChiselシミュレーション上で失敗する
今回の修正でchisel-iotester上ではriscv-testsが失敗するようになってしまいました。
もちろんCyclone10LP FPGA 上では正しく動作します。
理由(だけ)は簡単です。メモリやメモリマップ上のAvalon -MMスレーブの挙動をChiselで完全にはシミュレートできていないからです。
スレーブデバイス のwaitrequest含めたAvalon -MMの挙動をChiselで完全シミュレートする価値があるかというと、目的と手段の入れ替わりが生じているように思えます。今のところその意義を感じていません。そんなことするのであれば一旦Verilog を生成してQuartusでBFMテストに流すべきと思います。
対処としては、下記のいずれか選択をすると思われます
riscv-testsのChisel上でのシミュレーションは諦める(エラーのまま放置)。
シミュレーションはパスできるようにする:AvalonMMに対応したバスブリッジを別ファイルで用意し、シミュレーション時は当該ファイルを除外、HDL生成時は含めて生成する
後者の方がスマートですね。あとはやる気の問題かな。