Posts Tagged with "FPGA"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.

RISC-Vの調査 (8)

posted by sakurai on June 19, 2020 #279

ループにおける効率調査

前稿のプログラムは命令試験プログラムだったので、キャッシュの効果が出ていませんでした。そのため、ここではループにおける効率の向上を調査します。合わせてgccの効率も調べます。

int main() {
  for (int j = 0; j < 100; j++) {
    for (int i = 0; i <= 9; i++) {
      *UART0_ADDR = '0'+i;
    }
    *UART0_ADDR = '\n';
  }
}

このようなループの関数を作成し、gccにより-O, -O2, -O3でコンパイルします。まず+v1で実行命令数を確認すれば、それぞれ、5617、5517、2422実行命令数となりました。

次に+v2でパイプラインの状況を確認します。

表279.1 ステージ毎のパイプラインの状態(-O)
状態 StageF StageD Stage1 Stage2 Stage3
BUSY 91 0 1,150 2,251 0
EMPTY 106 198 178 1,410 3,664
PIPE処理 9,187 9,186 8,056 5,723 5,720
合計 9,384

表279.2 ステージ毎のパイプラインの状態(-O2)
状態 StageF StageD Stage1 Stage2 Stage3
BUSY 88 0 23 2,259 0
EMPTY 106 172 170 284 2,544
PIPE処理 7,972 7,994 7,973 5,623 5,622
合計 8,166

表279.3 ステージ毎のパイプラインの状態(-O3)
状態 StageF StageD Stage1 Stage2 Stage3
BUSY 152 0 1,023 2,264 0
EMPTY 4 131 131 1,138 3,403
PIPE処理 5,672 5,697 4,674 2,426 2,425
合計 5,828

これらの表の合計欄がマシンサイクルを意味しています。従って、最適化度合いに対するCPIは、-O, -O2, -O3のそれぞれで1.67、1.48、2.41となりました。

表279.3の-O3においてはインナーループを展開しているため、命令キャッシュミスが若干増加しており、デコードステージ以下のパイプラインバブルが発生していますが、命令数をぐっと少なくしてマシンサイクルを縮めています。いずれも表264.1と比較すると、ループであるため命令キャッシュミスによるパイプラインストールがかなり少なくなっており、効率が向上しています。


左矢前のブログ 次のブログ右矢

RISC-Vの調査 (7)

posted by sakurai on June 18, 2020 #278

Bsimによるシミュレーション

$ ./exe_HW_sim +v1 +tohost

によりBsimのシミュレーションが実行されます。+v1で命令毎のトレース、+v2でパイプライン内容を含めたトレースが表示されます。さらに波形観測のためには

$ ./exe_HW_sim +v1 +tohost -V dump.vcd

のようにVCDファイルを指定します。これをGtkwaveにより開き、波形を観測してみます。\$80000000から開始しており、キャッシュラインバウンダリでのブロックインが観察されます。

図%%.1
図278.1 シミュレーション波形

2回目に実行する場合は基本的にキャッシュヒットし、1サイクルで動作しています。

図%%.2
図278.2 シミュレーション波形(続き)

BsimにはUARTが実装されているため、UARTに対して、\$68('h'),\$65('e'),\$6c('l'),\$6c('l'),\$6f('o'),\$21('!'),\$0a('lf')と出力されていることが確認できます。

図%%.3
図278.3 シミュレーション波形(続き)

左矢前のブログ 次のブログ右矢

RISC-Vの調査 (6)

posted by sakurai on June 17, 2020 #277

Bsimによるシミュレーション

QemuのシミュレーションからBsimのシミュレーションに切り替えます。ここで、Flute SoCではUARTのアドレスが異なっているため、テストプログラムのUARTアドレスを

<volatile char* UART0_ADDR = (char*)0x10000000;
>volatile char* UART0_ADDR = (char*)0xc0000000;

と変更した上でコンパイルします。

$ riscv32-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostartfiles -Tlink.lds -Wall -O2 hello.c -o hello.elf

ここで、リンカへの指示であるlink.ldsは以下のようにしています。

OUTPUT_ARCH("riscv")
ENTRY(main)

SECTIONS
{
    . = 0x80000000;
    .text.startup : { *(.text.startup) }
    .text   : {
            _start = .;
            *(.text);
            _text_end = .;
            exit = .;
         }
    .rodata : { *(.rodata) }
    .sdata   : { *(.sdata) }
    .bss    : { *.(.bss) }
    /DISCARD/ : { *(.comment);
          *(.riscv.attributes);
          *(.strtab*);
          *(.shstrtab);
        }
    . = ALIGN(8);
     = . + 0x4000;
    sp_top = .;
}

前稿で作成したelfを、Bsimによるシミュレーションが可能なhexファイルに変換します。これはFlute/Tests/elf_to_hex/elf_to_hexで行います。

$ ../Flute/Tests/elf_to_hex/elf_to_hex hello.elf Mem.hex
c_mem_load_elf: hello.elf is a 32-bit ELF file
Section .text.startup   : addr         80000000 to addr         8000006c; size 0x      6c (= 108) bytes
Section .sdata          : addr         80000068 to addr         80000070; size 0x       8 (= 8) bytes
Section .comment        : Ignored
Section .riscv.attributes: Ignored
Section .symtab         : Searching for addresses of '_start', 'exit' and 'tohost' symbols
Writing symbols to:    symbol_table.txt
    No 'exit' label found
    No 'tohost' symbol found
Section .strtab         : Ignored
Section .shstrtab       : Ignored
Min addr:                    80000000 (hex)
Max addr:                    80000073 (hex)
Writing mem hex to file 'Mem.hex'
Subtracting 0x80000000 base from addresses

作成されたhello.hexの内容は以下のようになっています。逆アセンブルリストと比較して概ね良さそうです。

@0000000    // raw_mem addr;  byte addr: 00000000
00c6802306c007130707a68300d7002306500613068006930707a703800007b7    // raw_mem addr 00000000;  byte addr 00000000
0707a70300d7002306f006930707a70300e680230707a68300e680230707a683    // raw_mem addr 00000001;  byte addr 00000020
000057b706c7a703800007b700e7802300a007130707a78300d7002302100693    // raw_mem addr 00000002;  byte addr 00000040
00000000000000000000000010000000001000000000806700f7102355578793    // raw_mem addr 00000003;  byte addr 00000060
@07fffff    // last raw_mem addr;  byte addr: 0fffffe0
000000000000000000000000000000000000000000000000000000000000000    // raw_mem addr 007fffff;      byte addr 0fffffe0

左矢前のブログ 次のブログ右矢

RISC-Vの調査 (5)

posted by sakurai on June 15, 2020 #276

開発環境のインストール

開発環境のインストールを行います。最初にct-ngをインストールしようとしたのですが、RV32ではうまく行きませんでした。そこで、この記事を参考にインストールを実施しました。make中にインストールもしているので、あらかじめ/opt/riscvを作成しておき、記事中にもあるように自分が書き込み可能にしておくのが良さそうです。

qemuについてもこの記事のとおりにインストールしますが、

ERROR: glib-2.48 gthread-2.0 is required to compile QEMU

このようなエラーが出ました。私はFedora32を使用しているので、

$ sudo dnf install glib2 glib2-devel pixman-devel

と不足しているパッケージを追加したところ、configureが通ったので、makeします。

テストプログラム実行

早速、テストプログラムhello.cをコンパイルして実行してみます。Qemuを前提とするプログラムで、QemuのUartとシミュレーション停止アドレスが定義されています。

volatile char* UART0_ADDR = (char*)0x10000000;
volatile short* VIRT_TEST_ADDR = (short*)0x100000;

void _start(void) __attribute__((section(".text.startup")));

void _start(void) {
  *UART0_ADDR = 'h';
  *UART0_ADDR = 'e';
  *UART0_ADDR = 'l';
  *UART0_ADDR = 'l';
  *UART0_ADDR = 'o';
  *UART0_ADDR = '!';
  *UART0_ADDR = '\n';
  *VIRT_TEST_ADDR = 0x5555;
}

gccによりコンパイルし、qemuによりシミュレーションを行います。

$ riscv32-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostartfiles -Tlink.lds -Wall -O2 hello.c -o hello.elf
$ qemu-system-riscv32 -nographic -M virt -m 4096 -serial mon:stdio -bios none -kernel hello.elf
hello!

Qemuによりシミュレーションができました。また、逆アセンブラはobjdumpであり、以下の方法でアセンブルリストが確認できます。

$ riscv32-unknown-elf-objdump -D hello.elf
hello.elf:     ファイル形式 elf32-littleriscv

セクション .text.startup の逆アセンブル:

80000000 <_start>:
80000000:       800007b7                lui     a5,0x80000
80000004:       0707a703                lw     a4,112(a5) # 80000070 
80000008:       06800693                li      a3,104
8000000c:       06500613                li      a2,101
80000010:       00d70023                sb      a3,0(a4)
80000014:       06c7a683                lw      a3,108(a5)
80000018:       06c00713                li      a4,108
8000001c:       00c68023                sb      a2,0(a3)
80000020:       06c7a683                lw      a3,108(a5)
80000024:       00e68023                sb      a4,0(a3) 
:

左矢前のブログ 次のブログ右矢

RISC-Vの調査 (4)

posted by sakurai on June 12, 2020 #275

シミュレーションモデルの波形を観測してみます。テストプログラムは、\$80000000から始まっていますが、シミュレーションは\$1000から始まっています。これはBootROMのようです。BootROMのブロックインが19サイクルかかっています。

図275.1はGtkwaveによるシミュレーション波形で、pcをオレンジ色で塗っています。

図%%.1
図275.1 シミュレーション波形
BootROMの処理が終わり、\$80000000にジャンプし、すぐに\$8000004cにジャンプしています。ここでpcだけを見ると、\$80000000が20サイクル、\$8000004cが24サイクルも継続しています。Linuxが動作するMMUを持つプロセッサであるためやむを得ないとはいえ、キャッシュアクセスレイテンシが大きいように思われます。
図%%.2
図275.2 シミュレーション波形(続き)
図275.2はテストプログラム実行中で、このような波形が続きますが、24サイクルのブロックインに引き続き、8命令を1サイクル実行しています。

左矢前のブログ 次のブログ右矢

RISC-Vの調査 (3)

posted by sakurai on June 11, 2020 #274

前々稿の記事において、シミュレーションモデルが生成できましたが、これを実行させてみます。

テストプログラムは、add命令単体テストで、

80000000 <_start>:
80000000:       04c0006f                j       8000004c 
8000004c :

8000004c:       f1402573                csrr    a0,mhartid
80000050:       00051063                bnez    a0,80000050 
80000054:       00000297                auipc   t0,0x0
80000058:       01028293                addi    t0,t0,16 # 80000064 
8000005c:       30529073                csrw    mtvec,t0
80000060:       18005073                csrwi   satp,0
:

80000100 :
80000100:       00000093                li      ra,0
80000104:       00000113                li      sp,0
80000108:       00208f33                add     t5,ra,sp
8000010c:       00000e93                li      t4,0
80000110:       00200193                li      gp,2
80000114:       4ddf1663                bne     t5,t4,800005e0 

80000118 :
80000118:       00100093                li      ra,1
8000011c:       00100113                li      sp,1
80000120:       00208f33                add     t5,ra,sp
80000124:       00200e93                li      t4,2
80000128:       00300193                li      gp,3
8000012c:       4bdf1a63                bne     t5,t4,800005e0 
:

のように構成されています。Bsimによるシミュレーションは、+v1で命令トレースが、+v2でパイプラインステージの内容を表示させるようになっています。+v2で実行させた結果をgrepにより計数してみると、表274.1のようになりました。パイプラインの各ステージの出力ステータスの意味は、以下のとおりです。

  • EMPTY --- 入力が来ないためアイドルとなっている
  • BUSY --- 入力があるが、処理中で出力がレディではない
  • PIPE --- 入力があり、パイプラインは正常な出力を行っている
  • NONPIPE --- 入力があり、出力は例外的な場合(トラップ等)

表274.1 ステージ毎のパイプラインの状態
状態 StageF StageD Stage1 Stage2 Stage3
BUSY 1,118 0 23 26 0
EMPTY 22 1,094 959 1,373 1,409
PIPE処理 743 789 901 484 474
合計 1,883

この表だけでもいろいろなことが推測されます。
  • 試験命令数は473命令だったので、CPI(Cycles Per Instruction)は3.98となりました。命令キャッシュミスのためにかなり大きく(悪く)なっています。
  • マシンサイクル1,883サイクルのうち、実際に処理しているのは半分くらいであり、主なパイプラインストールは命令キャッシュによるものだと考えられます。
  • StageFがBUSYの分だけStageD以降がEMPTYとなり、空いています。すなわち約1,100サイクルがパイプラインバブルとなっています。
  • Stage1に対してStage2のPIPE処理が半分なのは、ロードストア命令が半分(レジスタとロードストアが1:1)くらいだからかもしれません。
  • StageDに対してStage1のPIPE処理が増加しているのは、マルチサイクル命令のためかもしれません。

これだけ見るとかなり効率が悪そうですが、対象がテストプログラムでループが無いため、基本的にキャッシュミスが頻発します。一般のアプリケーションのようにループがあれば、ずっと効率が向上するはずです。


左矢前のブログ 次のブログ右矢

RISC-Vの調査 (2)

posted by sakurai on June 10, 2020 #273

Fluteの階層構造

CPUの階層構造を図273.1に示します。5段のパイプラインは図273.1に示すように、"F"(命令Fetchステージ), "D"(命令デコードステージ), "1"(命令実行ステージ), "2"(メモリアクセス、長レインテンシステージ), "3"(ライトバックステージ)とステージ名が付けられています。なぜステージ"1”,"2",...,"5"でないかと言えば、3段パイプラインのPiccoloとステージを共用しているため書かれていました。Piccoloではパイプラインステージは”1", "2", "3"と名付けられており、Fluteでは、Piccoloの"1"を"F", ”D”, "1"に分解したようです。

図%%.1
図273.1 Flute CPU階層構造

各モジュールがmk〇〇と名付けられているのはBSVのお作法です。モジュールとそのインスタンスは一対nの関係にあり、モジュール名はいわばテンプレート名を意味するため、モジュールからインスタンスがmakeされるということを表しています。例えば上図のmkMMU_Cacheはどちらも同じモジュールですが、それぞれ命令用とデータ用に2個インスタンシエートしています。

キャッシュは上記のように、パラメタライズ可能な命令キャッシュ、データキャッシュの2つがあります。その他に、分岐予測器があります。


左矢前のブログ 次のブログ右矢

RISC-Vの調査

posted by sakurai on June 9, 2020 #272

BluespecのFluteプロセッサ

BluespecのBSVが読めるようになったところで、引き続いてFluteプロセッサの調査を行います。FluteはBluespecの開発したRISC-Vアーキテクチャの5段パイプラインRISC CPUです。それだけでなく、仮想記憶をサポートしているため、Linuxが動作します。(ページ)テーブルウォークはMMU内のハードウェアが実行します。ソースではステートベース設計のFSMで実装されていました。

Githubからダウンロード

$ git clone https://github.com/bluespec/Flute.git

としてGithubからダウンロードします。

RV32ACIMUアーキテクチャのBsimモデルの作成

アーキテクチャには各種ありますが、比較的軽いもの、例えば32bit、Floating無しのマイクロアーキテクチャを選択します。

$ cd builds/RV32ACIMU_Flute_bluesim

として、ターゲットディレクトリに移行します。

$ make all

と実行すると、

:
Simulation shared library created: exe_HW_sim.so
Simulation executable created: ./exe_HW_sim
INFO: linked bsc-compiled objects into Bluesim executable
$ 

このように、bsvで書かれたソースファイルがbscによりコンパイルされ、シミュレーションモデルであるexe_HW_simが生成されます。

RV32ACIMUアーキテクチャのBsimモデルの試験

$ make test

により、出来上がったシミュレーションモデルが、RISC-Vのテストスイートによりテストされます。

$ make test
make -C  ../../Tests/elf_to_hex
make[1]: Entering directory '/home/sakurai/src/bsv/riscv/Flute/Tests/elf_to_hex'
make[1]: 'elf_to_hex' is up to date.
make[1]: Leaving directory '/home/sakurai/src/bsv/riscv/Flute/Tests/elf_to_hex'
../../Tests/elf_to_hex/elf_to_hex  ../../Tests/isa/rv32ui-p-add  Mem.hex
c_mem_load_elf: ../../Tests/isa/rv32ui-p-add is a 32-bit ELF file
Section .text.init      : addr         80000000 to addr         80000604; size 0x     604 (= 1540) bytes
Section .tohost         : addr         80001000 to addr         80001048; size 0x      48 (= 72) bytes
Section .riscv.attributes: Ignored
Section .symtab         : Searching for addresses of '_start', 'exit' and 'tohost' symbols
Writing symbols to:    symbol_table.txt
    No 'exit' label found
Section .strtab         : Ignored
Section .shstrtab       : Ignored
Min addr:                    80000000 (hex)
Max addr:                    80001047 (hex)
   :
================================================================
Bluespec RISC-V standalone system simulation v1.2
Copyright (c) 2017-2019 Bluespec, Inc. All Rights Reserved.
================================================================
INFO: watch_tohost = 1, tohost_addr = 0x80001000
1: top.soc_top.mem0_controller_axi4_deburster::AXI4_Deburster.rl_reset
2:top.soc_top.rl_reset_start_initial ...
3: Core.rl_cpu_hart0_reset_from_soc_start
================================================================
CPU: Bluespec  RISC-V  Flute  v3.0 (RV32)
Copyright (c) 2016-2020 Bluespec, Inc. All Rights Reserved.
================================================================
6: D_MMU_Cache: cache size 8 KB, associativity 2, line size 32 bytes (= 8 XLEN words)
6: I_MMU_Cache: cache size 8 KB, associativity 2, line size 32 bytes (= 8 XLEN words)
512: top.soc_top.core.cpu.rl_reset_complete: restart at PC = 0x1000
514: Near_Mem_IO_AXI4.set_addr_map: addr_base 0x2000000 addr_lim 0x200c000
514: Core.rl_cpu_hart0_reset_complete
515: Mem_Controller.set_addr_map: addr_base 0x80000000 addr_lim 0x90000000
515:top.soc_top.rl_reset_complete_initial
instret:0  PC:0x1000  instr:0x297  priv:3
instret:1  PC:0x1004  instr:0x2028593  priv:3
instret:2  PC:0x1008  instr:0xf1402573  priv:3
  :
instret:471  PC:0x80000044  instr:0xfc3f2023  priv:3
instret:472  PC:0x80000048  instr:0xff9ff06f  priv:3
instret:473  PC:0x80000040  instr:0x1f17  priv:3
2396: Mem_Controller.rl_process_wr_req: addr 0x80001000 () data 0x1
PASS
2397: top:.rl_terminate: soc_top status is 0x1 (= 0d1)
Simulation speed: 2396 cycles, 60915008 nsecs  = 39333 cycles/sec

テストスイートのelfを、elf_to_hexにより、asciiのメモリイメージファイルに変換しています。

これはadd命令単体のテストですが、全てのテストを行うには、以下のように実行します。

$ make isa_tests

と実行すると、

 :
Worker 1: Test: rv32um-p-mul PASS [So far: total 67, executed 34, PASS 34, FAIL 0]
Worker 0 executed 33 tests, of which 33 passed
Worker 1 executed 34 tests, of which 34 passed
Total tests: 67 tests
Executed:    67 tests
PASS:        67 tests
FAIL:        0 tests
Finished running regressions; saved logs in Logs/

のように出力され、67個全てのテストが実行され、全てパスしたことが表示されます。


左矢前のブログ 次のブログ右矢

ゲームFSMとサウンドFSMの連携

posted by sakurai on June 5, 2020 #271

Ultra96においてBSVで開発

元々Verilog版では、コマンドバッファに書き込むだけで特に何もしなくても動作していました。今回BSVで再設計する際に、サウンドを4chとし、取りこぼしを避けるために考えたのがサウンドキュー(FIFO)でした。

図%%.1
図271.1 サウンドキュー

これはVivadoのFIFOジェネレータで作成したため、最小段数でもかなり深く1024段程度となっています。 実験したところ、確かに取りこぼしは無いのですが、一方、サウンドがゲームとズレて行き、まるでサウンドレコーダのような動作になってしまいました。そのため、FIFOを1段に修正しました。FIFOジェネレータでは1段のFIFOは作成できないのでVerilogで記述しました。1段のためFIFOと呼ぶのはおかしいのでコマンドバッファと呼ぶことにします。

コマンドバッファには、ゲームFSMからコマンドが来たことを示すフラグemptyを設け、書き込むと!emptyとなるようにします。サウンドFSMからは!emptyの時に新たにコマンドが来たと判断し、コマンドを読んだ後にemptyに変更します。

図%%.2
図271.2 1段バッファに変更

Artyボード移植後

Ultra96ではこれで動作していたのですが、Artyボードに移植後に自機増加音が無視されることに気づきました。サウンドFSMが取りこぼしているのだと推測し、再考すると、ゲームFSMが不必要に速いことに気づきました。DSOを接続して調べたところ、96.4%がウェイトだと判明したので、これを1MHzに落としたところ、動作するようでした。

ところが実験すると、依然として自機増加音(コマンドNo.9)が無視されるようです。そこで、ILAを接続して、

  • サウンドコマンド
  • サウンドFSMステート
  • コマンドバッファemtpy
  • サウンドFSM内部フラグ(fNO9)

を観測しました。最後の内部フラグfNO9は自機増加音がプリエンプトされないように割込みを禁止するためのフラグで、コマンドNo.9を受け付けた際にTrueになる信号です。

図%%.3
図271.3 ILA波形(NG)
図271.3はゲームFSMクロックを2MHzとして取得したものですが、フラグfNO9がTrueにならず、コマンドNo.9を無視しています。その原因は、サウンドFSMが受け取る前に次のコマンドNo.4を上書きしているためです。

従って、コマンドの書き込みの際にemptyである場合のみ書き込み、!emptyの場合は捨てる処理を行います(図271.4のマゼンタ矢印の処理を追加)。

図%%.4
図271.4 両側でemptyを確認するように修正
このように修正したところ、No.9の次のコマンドが!empty(=buffer full)のため捨てられることにより、図271.5のように受け付けられるようになりました。
図%%.5
図271.5 ILA波形(OK)
FIFOではないので、原理上取りこぼしは防げないものの、実用上これで動作するようです。

左矢前のブログ 次のブログ右矢

posted by sakurai on June 4, 2020 #270

QSPIフラッシュへの書き込み

通常ではVivadoからPROGRAM AND DEBUG⇒Open Hardware Manager⇒Open Target⇒Auto Connectとし、Program DeviceによりJTAG経由でFPGAにビットストリームを焼きこみます。しかしながらこれだと電源断によりFPGAのSRAM内容が消えてしまいます。また、FPGAプログラミング用のPCが常に必要です。オンボードFlashにデータを焼きこめばPCを持ち運ぶ必要がなく、電源onでアプリケーションが立ち上がるため、Flashのプログラミングを行います。

binファイルの作成

最初にFlashへ書き込むデータファイルであるbinファイルを用意します。これは、Tools⇒Setting(歯車マーク)⇒Project Settings⇒Bitstream画面で行います。この画面を開くと複数のチェックボックスが表示されます。その中の、-bin_file*のチェックボックスにチェックします。

図%%.1
図270.1 bin_fileにチェック

これを行ってから、通常どおりPROGRAM AND DEBUG⇒Generate Bitstreamを実施するとWrite Bitstreamが完了しますが、同時にbinファイルが生成されています。場所はbitファイルと同じところで'プロジェクト/プロジェクト.runs/impl_1/'です。

binファイルの焼きこみ

binファイルができたら、Add Configuration Memoryにより、Add Configuration Memory Device画面が開きます。Flashデバイスの選択が可能なので、この中で"s25fl128sxxxxx0"を選択します。Search窓にs25fl128を入力すれば、候補が3つ現れますがその真ん中です。

図%%.2
図270.2 FLASHデバイスの選択
選択したらOKをクリックします。するとプログラミングが始まり、30秒程でプログラミングが完了します。

実行

リファレンスマニュアルにはJP1でプログラミングモードが決まるとあります。JP1の位置がどちらでもJTAGからは書き込めるとのことです。初期状態はJP1はショートで、SPI-FLASHのモードとなっており、そのまま電源のOFF⇒ONでSpace Invadersが立ち上がりました。

図%%.3
図270.3 JTAG接続なしにSpace Invadersが動作

左矢前のブログ 次のブログ右矢


ページ: