FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

ヘネパタ第6版はRISC-Vで全面刷新?

Amazonで調べていたら、どうやら次のヘネパタが発売されるのを発見した。

Computer Architecture, Sixth Edition: A Quantitative Approach (The Morgan Kaufmann Series in Computer Architecture and Design)

Computer Architecture, Sixth Edition: A Quantitative Approach (The Morgan Kaufmann Series in Computer Architecture and Design)

2017年12月発売のようだが、重要なのはこれまでMIPSで開設されていたプロセッサモデルが、RISC-Vに変更になるようだ。

パタヘネもすでにRISC-V Editionになっているが、いよいよMIPSに見切りをつけたのか、まあ著者がRISC-Vの仕様策定に関わっているのは大きいよなあ。。。

また、内容についてもだいぶ書き換かわっているようだ。第6版では、「Domain Specific Architectures」についても言及が入っている (第5版では、Warehouse scale computingや、GPGPUについても言及が追記されていた)。

It now features examples from the RISC-V (“RISC Five”) instruction set architecture, a modern RISC instruction set developed and designed to be a free and openly adoptable standard.

以下Amazonの説明から抜粋。

  • Includes a new chapter on domain-specific architectures, explaining how they are only path forward for improved performance and energy efficiency given the end of Moore’s Law and Dinnard scaling. Features first publication of several DSAs from industry.
  • Features extensive updates to the chapter on warehouse-scale computing, with first public information on the newest Google WSC.
  • Updates to other chapters include new material dealing with the use of stacked DRAM; data on the performance of new Nvidia Pascal GPU vs new AVX/512 Intel Skylake CPU; and extensive additions to content covering multicore architecture and organization.
  • Trademark Putting It All Together sections appear near the end of every chapter, providing real-world technology examples that demonstrated the principles covered in each chapter.
  • Includes review appendices in the printed text and additional reference appendices available online
  • Includes updated and improved case studies and exercises.

また、Elsevierのサイトはこちら。

www.elsevier.com

VivadoでIPを生成する方法の調査(tclを使った生成方法)

f:id:msyksphinz:20170715133001p:plain

Zynqを使ったカスタムデザインのIPが作りたくて、IPの生成方法をいろいろ調査しているのだが、どうもGUIを操作するものばかりでつまらない。

GUIの操作方法なんて一瞬で忘れてしまうので、tclとmakefileで一気に作れるような環境を用意しておきたい。

いろいろ調査していて、結局ZedBoardのIP生成ディレクトリのスクリプトにたどり着いた。

github.com

こちらのリポジトリは"project"と"library"のディレクトリに分かれており、libraryはVivadoのIPを生成するようなスクリプトが用意されている。

例えば、axi_clkgenというIPを作るためには、リポジトリ内でaxi_clkgenのリポジトリに移動してmakeを叩くだけである。

$ cd library/axi_clkgen
$ make
rm -rf *.cache *.data *.xpr *.log component.xml *.jou xgui *.ip_user_files *.srcs *.hw *.sim .Xil
vivado -mode batch -source axi_clkgen_ip.tcl  >> axi_clkgen_ip.log 2>&1

axi_clkgen_ip.tclを見てみると、以下のような構造になっている。各種サポート関数群 (adi_xxx)は、 adi_ip.tclに格納されているので、これは必要ならば参照する。

source ../scripts/adi_env.tcl
source $ad_hdl_dir/library/scripts/adi_ip.tcl

adi_ip_create axi_clkgen
adi_ip_files axi_clkgen [list \
  "$ad_hdl_dir/library/common/ad_rst.v" \
  "$ad_hdl_dir/library/xilinx/common/ad_mmcm_drp.v" \
  "$ad_hdl_dir/library/common/up_axi.v" \
  "$ad_hdl_dir/library/common/up_clkgen.v" \
  "axi_clkgen_constr.xdc" \
  "axi_clkgen.v" ]

adi_ip_properties axi_clkgen

ipx::remove_bus_interface {clk} [ipx::current_core]
ipx::associate_bus_interfaces -busif s_axi -clock s_axi_aclk [ipx::current_core]

set_property driver_value 0 [ipx::get_ports *clk2* -of_objects [ipx::current_core]]

adi_ip_constraints axi_clkgen [list \
  "axi_clkgen_constr.xdc" ]

ipx::save_core [ipx::current_core]

どうやら必要なのは、

  1. adi_ip_createでIPを生成する。
  2. adi_ip_filesコンパイル対象のファイル群を指定する。
  3. adi_ip_propertiesコンパイル対象プロジェクトのプロパティを設定する。
  4. 各種バスや製薬の設定。
  5. adi_ip_constraintsで制約条件の設定。
  6. `ipx::save_core でIPの保存。

上記の構成を真似て、オリジナルのIPを作ってみる。ベースはまずはGUI生成したものからとってる来るが、インタフェースを合わせて合成できるようにする。

github.com

注意する点としては adi_ip_propertiesで設定するインタフェースの信号名と合わせておいた方がよい。

  • adi_ip.tcl
proc adi_ip_properties {ip_name} {

  adi_ip_properties_lite $ip_name

  ipx::infer_bus_interface {\
    s_axi_awvalid \
    s_axi_awaddr \
    s_axi_awprot \
    s_axi_awready \
    s_axi_wvalid \
...
  • blockram_test_v1_0.v
    // Ports of Axi Slave Bus Interface S00_AXI
    input wire                                  s_axi_aclk,
    input wire                                  s_axi_aresetn,
    input wire [C_S00_AXI_ID_WIDTH-1 : 0]        s_axi_awid,
    input wire [C_S00_AXI_ADDR_WIDTH-1 : 0]  s_axi_awaddr,
    input wire [7 : 0]                            s_axi_awlen,
    input wire [2 : 0]                            s_axi_awsize,
    input wire [1 : 0]                            s_axi_awburst,
    input wire                                  s_axi_awlock,
    input wire [3 : 0]                            s_axi_awcache,
    input wire [2 : 0]                            s_axi_awprot,
    input wire [3 : 0]                            s_axi_awqos,
    input wire [3 : 0]                            s_axi_awregion,
    input wire [C_S00_AXI_AWUSER_WIDTH-1 : 0]    s_axi_awuser,

最初、Verilog記述の方がs00_axi_...という記述で全くコンパイルが通らなかったので不思議に思っていたのだが、s_axi_...という記述に変更したら上手く行くようになった。

次に、これをVivadoに取り込んで、IPとして利用できるようにする。

RISC-VプロセッサHiFive1で機械学習コードを動作させる(1. コンパイル)

HiFive1ボードはRISC-Vが動作する(おそらく世界で唯一商用の?)プロセッサボードである。このHiFive1ボードの仕様は以下のようになっている。

f:id:msyksphinz:20170325154038p:plain

  • Microcontroller: SiFive Freedom E310 (FE310)
    • SiFive E31 Coreplex
    • 32bit RV32IMAC (整数演算のみ、乗除算ハードウェア回路付属、コンパクト命令付属)
    • Speed: 320+ MHz
    • Performance: 1.61 DMIPs/MHz, 2.73 Coremark/MHz
    • Memory: 16 KB Instruction Cache, 16 KB Data Scratchpad
    • Other Features: Hardware Multiply/Divide, Debug Module, Flexible Clock Generation with on-chip oscillators and PLLs
  • SPI Controllers/HW CS Pins: 1/3
  • External Interrupt Pins: 19
  • External Wakeup Pins: 1
  • Flash Memory: 128 Mbit Off-Chip (ISSI SPI Flash)

というわけで、フラッシュメモリが128Mbitも付属しているのが大きいと個人的に思っている。ここにどうにかして学習データを流し込んで、機械学習のプログラムを動作させたいのだ。

train_twolayernet (MNIST向け機械学習ネットワーク (16bit 固定小数点版))のコンパイル

自作したMNIST動作用の機械学習ネットワークは、固定小数点ライブラリlibfixmathを使うようになっている。

libfixmathをRISC-V向けにコンパイルするためには、オリジナルのlibfixmathに対して以下の変更を加えている。

diff --git a/libfixmath/Makefile b/libfixmath/Makefile
index b284590..27cceb9 100644
--- a/libfixmath/Makefile
+++ b/libfixmath/Makefile
@@ -4,14 +4,19 @@ LIB =
 SRC = .
 INC =

+TARGET=/home/msyksphinz/work/freedom-e-sdk/work/build/riscv-gnu-toolchain/riscv64-unknown-elf/prefix/bin/riscv64-unknown-elf-
+
 #Compiler settings
-CPP = gcc
-CC = gcc
-AS = gcc
-LD = gcc
-AR = ar
-CPP_FLAGS = -O2 $(INC) -Wall -Wextra -c
-CC_FLAGS  = -O2 $(INC) -Wall -Wextra -c
+CPP = $(TARGET)gcc
+CC     = $(TARGET)gcc
+AS     = $(TARGET)gcc
+LD     = $(TARGET)gcc
+AR     = $(TARGET)ar
+
+DEFINES += FIXMATH_NO_CACHE
+
+CPP_FLAGS = -O2 $(INC) -Wall -Wextra -c -std=gnu11 -O3 -g  -g -march=rv32imac -mabi=ilp32 -mcmodel=medany $(addprefix -D, $(DEFINES))
+CC_FLAGS  = -O2 $(INC) -Wall -Wextra -c -std=gnu11 -O3 -g  -g -march=rv32imac -mabi=ilp32 -mcmodel=medany $(addprefix -D, $(DEFINES))
 AS_FLAGS  = $(CC_FLAGS) -D_ASSEMBLER_
 LD_FLAGS = -Wall

MNISTの訓練データをRISC-Vバイナリに直接埋め込む方法

HiFive1にはファイルシステムも入っていないしOSも動かないので、これまで使っていたread/write系の関数は動作しない。これを補うために、あらかじめトレーニングデータをRISC-Vの形式に変換して、リンク時に埋め込むという作業が必要になる。

これはバイナリをオブジェクトファイルに変換する必要がある。これにはobjcopyコマンドを使用する。

普通のやつらの下を行け: objcopy で実行ファイルにデータを埋め込む - bkブログ

上記を使用して、トレーニングファイルを以下のようにMakefileを使ってオブジェクトファイルに変換を行った。

%-ubyte.o: %-ubyte
    $(OBJCOPY) -I binary -O elf32-littleriscv -B riscv --rename-section .data=.rodata $(notdir $<) $(notdir $@)

github.com

これをコンパイルすると、デフォルトではファイルの大きさが大きすぎてセクションに入らないので、以下のエラーが出る。

/home/msyksphinz/work/freedom-e-sdk/work/build/riscv-gnu-toolchain/riscv64-unknown-elf/prefix/lib/gcc/riscv64-unknown-elf/7.1.1/../../../../riscv64-unknown-elf/bin/ld: train_twolayernet セク ション `.data' は領域 `ram' 内に入りません
/home/msyksphinz/work/freedom-e-sdk/work/build/riscv-gnu-toolchain/riscv64-unknown-elf/prefix/lib/gcc/riscv64-unknown-elf/7.1.1/../../../../riscv64-unknown-elf/bin/ld: section .stack VMA [0000000080003800,0000000080003fff] overlaps section .data VMA [0000000080000000,0000000083468297]
/home/msyksphinz/work/freedom-e-sdk/work/build/riscv-gnu-toolchain/riscv64-unknown-elf/prefix/lib/gcc/riscv64-unknown-elf/7.1.1/../../../../riscv64-unknown-elf/bin/ld: 領域 `ram' が 0 バイト 溢れました

objdump -hでセクションを確認すると、.dataが非常に大きいことが分かる。

software/train_twolayernet/train-images-idx3-ubyte.o:     ファイル形式 elf32-littleriscv

セクション:
索引名          サイズ      VMA       LMA       File off  Algn
  0 .data         02cdc610  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, DATA

.dataセクションはflash.ldsによって以下のように定義されている。

  • ./bsp/env/freedom-e300-hifive1/flash.lds
MEMORY
{
  flash (rxai!w) : ORIGIN = 0x20400000, LENGTH = 512M
  ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 16K
}

.dataセクションはramに配置されており、16Kしかない。イメージデータをどうにかしてflashデータにコピーするしかない。これには、あらかじめobjcopyコマンドでrename-sectionを指定する。

%-ubyte.o: %-ubyte
    $(OBJCOPY) -I binary -O elf32-littleriscv -B riscv --rename-section .data=.rodata $(notdir $<) $(notdir $@)

これで、すべての学習データがflashセクションに移動した。

software/train_twolayernet/train_twolayernet:     ファイル形式 elf32-littleriscv

セクション:
索引名          サイズ      VMA       LMA       File off  Algn
  0 .init         0000007e  20400000  20400000  00001000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .text         0000dfca  20400080  20400080  00001080  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .rodata       034685dc  2040e050  2040e050  0000f050  2**3
                  CONTENTS, ALLOC, LOAD, DATA
  3 .eh_frame     00000068  2387662c  2387662c  0347762c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .lalign       00000000  23876694  23876694  034789f8  2**0
                  CONTENTS
  5 .dalign       00000000  80000000  80000000  034789f8  2**0
                  CONTENTS
  6 .data         000009f8  80000000  23876694  03478000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
  7 .bss          00000040  800009f8  800009f8  00000000  2**2
                  ALLOC
  8 .stack        00000800  80003800  80003800  00000000  2**0
                  ALLOC
  9 .debug_frame  00001258  00000000  00000000  034789f8  2**2
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_info   00010107  00000000  00000000  03479c50  2**0
                  CONTENTS, READONLY, DEBUGGING
 11 .debug_abbrev 00003dbe  00000000  00000000  03489d57  2**0
                  CONTENTS, READONLY, DEBUGGING
 12 .debug_loc    000158a5  00000000  00000000  0348db15  2**0
                  CONTENTS, READONLY, DEBUGGING
 13 .debug_aranges 000004c0  00000000  00000000  034a33c0  2**3
                  CONTENTS, READONLY, DEBUGGING
 14 .debug_ranges 00001ff8  00000000  00000000  034a3880  2**0
                  CONTENTS, READONLY, DEBUGGING
 15 .debug_line   00008d84  00000000  00000000  034a5878  2**0
                  CONTENTS, READONLY, DEBUGGING
 16 .debug_str    000023a6  00000000  00000000  034ae5fc  2**0
                  CONTENTS, READONLY, DEBUGGING
 17 .comment      0000001a  00000000  00000000  034b09a2  2**0
                  CONTENTS, READONLY

ここまでで一応バイナリを作成することができたのだが、アップロード時にHiFive1でエラーが出ている。これは解析する必要があるなあ。

$ sudo make upload PROGRAM=hello
[sudo] msyksphinz のパスワード:
work/build/openocd/prefix/bin/openocd -f bsp/env/freedom-e300-hifive1/openocd.cfg & \
/home/msyksphinz/work/freedom-e-sdk/work/build/riscv-gnu-toolchain/riscv64-unknown-elf/prefix/bin/riscv64-unknown-elf-gdb software/hello/hello --batch -ex "set remotetimeout 240" -ex "target extended-remote localhost:3333" -ex "monitor reset halt" -ex "monitor flash protect 0 64 last off" -ex "load" -ex "monitor resume" -ex "monitor shutdown" -ex "quit" && \
echo "Successfully uploaded 'hello' to freedom-e300-hifive1."
Open On-Chip Debugger 0.10.0-dev (2017-07-17-09:46)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
adapter speed: 10000 kHz
Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'.
Info : ftdi: if you experience problems at higher adapter clocks, try the command "ftdi_tdo_sample_edge falling"
Info : clock speed 10000 kHz
Info : JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (<unknown>), part: 0x0e31, ver: 0x1)
Info : Examined RISCV core; XLEN=32, misa=0x40001105
Error: couldn't bind gdb to socket: Address already in use

HiFive1ボードの自作プログラムのアップロード方法

HiFive1ボードはRISC-Vが動作する(おそらく世界で唯一商用の?)プロセッサボードである。 このボードを使っていくつか試行してみたいことがあるのだが、久しぶりに立ち上げたので少しリハビリの意味も兼ねて整理しておく。

プログラムの開発には、SiFiveが公開しているSDKを使用する。SDKには、自動的にフラッシュに書き込むプログラムも入っており、これを使うと試行が簡単になる。

github.com

とりあえず、hello world的なプログラムだけ動作させておく。ディスクに余裕のある人は、make toolsでHiFive1用の構成のGCCを作っていた方がよいように思う。

make tools

プログラムのコンパイルは、make softwareで可能だ。これでコンパイル、アップロード可能なプログラムはsoftwareディレクトリ以下で定義されているものだ。

$ ls -1 software/
coremark
coreplexip_welcome
demo_gpio
dhrystone
double_tap_dontboot
global_interrupts
hello
led_fade
local_interrupts
performance_counters
smp

コンパイルする。

$ cat software/hello/hello.c
#include <stdio.h>

int main()
{
  puts("hello world!\n");

  return 0;
}
$ make software PROGRAM=hello BOARD=freedom-e300-hifive1

プログラムのアップロードを行う。

$ sudo make upload PROGRAM=hello                                                                                                                                                [36/365]
work/build/openocd/prefix/bin/openocd -f bsp/env/freedom-e300-hifive1/openocd.cfg & \
/home/msyksphinz/work/freedom-e-sdk/work/build/riscv-gnu-toolchain/riscv64-unknown-elf/prefix/bin/riscv64-unknown-elf-gdb software/hello/hello --batch -ex "set remotetimeout 240" -ex "target extended-remote localhost:3333" -ex "monitor r
eset halt" -ex "monitor flash protect 0 64 last off" -ex "load" -ex "monitor resume" -ex "monitor shutdown" -ex "quit" && \
echo "Successfully uploaded 'hello' to freedom-e300-hifive1."
Open On-Chip Debugger 0.10.0-dev (2017-07-15-14:57)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
adapter speed: 10000 kHz
Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'.
Info : ftdi: if you experience problems at higher adapter clocks, try the command "ftdi_tdo_sample_edge falling"
Info : clock speed 10000 kHz
Info : JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (<unknown>), part: 0x0e31, ver: 0x1)
Info : Examined RISCV core; XLEN=32, misa=0x40001105
riscv.cpu: target state: halted
halted at 0x2040036a due to debug interrupt
Info : accepting 'gdb' connection on tcp/3333
Info : Found flash device 'issi is25lp128' (ID 0x0018609d)
0x2040036a in __wrap__exit (code=0) at /home/msyksphinz/work/freedom-e-sdk/bsp/libwrap/sys/_exit.c:12
12        write(STDERR_FILENO, "\n", 1);
Info : JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (<unknown>), part: 0x0e31, ver: 0x1)
JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (<unknown>), part: 0x0e31, ver: 0x1)
riscv.cpu: target state: halted
riscv.cpu: target state: halted
halted at 0x2040036a due to debug interrupt
halted at 0x2040036a due to debug interrupt
cleared protection for sectors 64 through 255 on flash bank 0
cleared protection for sectors 64 through 255 on flash bank 0
Info : JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (<unknown>), part: 0x0e31, ver: 0x1)
riscv.cpu: target state: halted
halted at 0x2040036a due to debug interrupt
Loading section .init, size 0x78 lma 0x20400000
Loading section .text, size 0xba2e lma 0x20400078
Loading section .rodata, size 0xd2c lma 0x2040baa8
Loading section .eh_frame, size 0x68 lma 0x2040c7d4
Loading section .data, size 0x9d0 lma 0x2040c83c
Info : Padding image section 0 with 2 bytes
Warn : keep_alive() was not invoked in the 1000ms timelimit. GDB alive packet not sent! (5180). Workaround: increase "set remotetimeout" in GDB
Warn : keep_alive() was not invoked in the 1000ms timelimit. GDB alive packet not sent! (5013). Workaround: increase "set remotetimeout" in GDB
riscv.cpu: target state: halted
halted at 0x80000004 due to software breakpoint
riscv.cpu: target state: halted
halted at 0x80000004 due to software breakpoint
riscv.cpu: target state: halted
halted at 0x80000004 due to software breakpoint
riscv.cpu: target state: halted
halted at 0x80000004 due to software breakpoint
riscv.cpu: target state: halted
halted at 0x80000004 due to software breakpoint
...

UARTからの出力を確認したい場合は、sudo screen /dev/ttyUSB1 115200で確認すること。

f:id:msyksphinz:20170715163831p:plain

RocketChipの足回りを理解する(4. RISC-V FrontEnd Server)

ところで、RISC-V Rocket-Chipをシミュレーションするとき、何気なくmakeを叩いて、

make CONFIG=DefaultConfig output/rv64ui-p-add.out

とかして、勝手に実行されているけど、よく考えると具体的にどのようにして動作しているのか分からない。これを理解するためには、RocketChipがどのようにコンパイルされ、実行されているのかについて知らなければならない。

これを理解するためには、RISC-V FrontEnd Server (fesvr)について理解しなければならない。

RISC-V Rocket Chipが動作するまでの仕組み

まず、VCSシミュレータで実行する場合を見てみる。手元にVCSが実行できないのでmakeの中身を参照しながらだが、

cd vsim
make CONFIG=DefaultConfig

とすると、Makefile群として以下が生成されるはずだ。

これらに、RocketChipを理解するための秘密が隠されている。

必要なファイル群

Makefragを参照すると、以下のファイルが必要なことが分かる。jtagはとりあえず無視してもOKだが、SimDTM.v, SimDTM.ccというのはRocketChipの全体制御を管理しているものだ。

bb_vsrcs = \
    $(base_dir)/vsrc/jtag_vpi.v \
    $(base_dir)/vsrc/plusarg_reader.v \
    $(base_dir)/vsrc/ClockDivider2.v \
    $(base_dir)/vsrc/ClockDivider3.v \
    $(base_dir)/vsrc/AsyncResetReg.v \

sim_vsrcs = \
    $(generated_dir)/$(long_name).v \
    $(generated_dir)/$(long_name).behav_srams.v \
    $(base_dir)/vsrc/$(TB).v \
    $(base_dir)/vsrc/SimDTM.v \
    $(bb_vsrcs)

# C sources

sim_csrcs = \
    $(base_dir)/csrc/SimDTM.cc \
    $(base_dir)/csrc/jtag_vpi.c

また、VCSコンパイルオプションに、RISC-Vのライブラリが指定されていることが分かる。これがRISC-Vのフロントエンドサーバfesvrだ。

VCS_OPTS = -notice -line +lint=all,noVCDE,noONGS,noUI -error=PCWM-L -timescale=1ns/10ps -quiet \
    +rad +v2k +vcs+lic+wait \
    +vc+list -CC "-I$(VCS_HOME)/include" \
    -CC "-I$(RISCV)/include" \
    -CC "-std=c++11" \
    -CC "-Wl,-rpath,$(RISCV)/lib" \
    $(RISCV)/lib/libfesvr.so \
    -sverilog \
    +incdir+$(generated_dir) \
    +define+CLOCK_PERIOD=1.0 $(sim_vsrcs) $(sim_csrcs) \
    +define+PRINTF_COND=$(TB).printf_cond \
    +define+STOP_COND=!$(TB).reset \
    +define+RANDOMIZE_MEM_INIT \
    +define+RANDOMIZE_REG_INIT \
    +define+RANDOMIZE_GARBAGE_ASSIGN \
    +define+RANDOMIZE_INVALID_ASSIGN \
    +libext+.v \

RocketChipのシミュレーション時フロー

前回紹介したように、RocketChipの構造は以下のようになっている。

f:id:msyksphinz:20170712013910p:plain

TestDriver.v

シミュレーションのオプションを管理する部分。$value$plusargsの処理などはここで行われている。TestDriver内でTestHarnessがインスタンスされている。

TestHarness

TestHarnessの中にインスタンスされているものを具体的に列挙すると、

  • ExampleRocketTop : RocketChip本体に当たる。
  • SimAXIMem : mem_axi4 が接続されているRAMにあたる。おそらく通常の命令、データアクセスに当たる。
  • SimAXIMem_1 : mmio_axi4が接続されているRAMにあたる。MMIOのデータアクセスに当たる。
  • SimDTM : RTLのコントロール部になる。

ここではとりあえずRocketChipの本体とか、SimAXIMemはほおっておいて、SimDTMについて着目する。

SimDTM.vを参照する

SimDTM.vを参照してみる。

module SimDTM(
  input clk,
  input reset,

...

      __exit = debug_tick(
        __debug_req_valid,
        __debug_req_ready,
        __debug_req_bits_addr,
        __debug_req_bits_op,
        __debug_req_bits_data,
        __debug_resp_valid,
        __debug_resp_ready,
        __debug_resp_bits_resp,
        __debug_resp_bits_data
      );

このdebug_tick()が、毎サイクル実行されるというわけだ。このdebug_tick()

  if (!dtm) {
    s_vpi_vlog_info info;
    if (!vpi_get_vlog_info(&info))
      abort();
      dtm = new dtm_t(filter_argv_for_dtm(info.argc, info.argv));
  }

として定義されており、このdtm_tfesvrに定義されている。このdtmriscv-tools/riscv-fesvr/fesvr/dtm.ccに定義されており、ここではコンストラクタを呼んでいるので、

dtm_t::dtm_t(const std::vector<std::string>& args)
  : htif_t(args)
{
  start_host_thread();
}

...

htif_t::htif_t(const std::vector<std::string>& args)
  : mem(this), entry(DRAM_BASE), sig_addr(0), sig_len(0),
    tohost_addr(0), fromhost_addr(0), exitcode(0), stopped(false),
    syscall_proxy(this)
{
  signal(SIGINT, &handle_signal);
  signal(SIGTERM, &handle_signal);
  signal(SIGABRT, &handle_signal); // we still want to call static destructors

  size_t i;

ここで、最初にload_program()が呼ばれることで、ELFファイルがロードされる。

void htif_t::load_program()
{
  std::string path;
  if (access(targs[0].c_str(), F_OK) == 0)
    path = targs[0];
  else if (targs[0].find('/') == std::string::npos)
  {
    std::string test_path = PREFIX TARGET_DIR + targs[0];
    if (access(test_path.c_str(), F_OK) == 0)
      path = test_path;
  }

  if (path.empty())
    throw std::runtime_error("could not open " + targs[0]);

  std::map<std::string, uint64_t> symbols = load_elf(path.c_str(), &mem, &entry);

  if (symbols.count("tohost") && symbols.count("fromhost")) {
    tohost_addr = symbols["tohost"];
    fromhost_addr = symbols["fromhost"];
  } else {
    fprintf(stderr, "warning: tohost and fromhost symbols not in ELF; can't communicate with target\n");
  }

  // detect torture tests so we can print the memory signature at the end
  if (symbols.count("begin_signature") && symbols.count("end_signature"))
  {
    sig_addr = symbols["begin_signature"];
    sig_len = symbols["end_signature"] - sig_addr;
  }
}

RocketChipの足回りを理解する(3. BootROM)

RocketChipのリセットが解除されると、まずはBootROMへのフェッチが始まる。 BootROMは、TestDriver/TestHarness/ExampleRocketTop/bootromに格納されている。

まずはこの周辺の波形を見てみよう。最初にBootROMにアクセスするのは、0x00010040 がアクセスされている。そもそも、このリセットベクタはどこから来てるのかね?

f:id:msyksphinz:20170715113801p:plain

RISCVPlatform.scalaにリセット時の初期値が書いてあった。

/** Adds a boot ROM that contains the DTB describing the system's coreplex. */
trait HasPeripheryBootROM extends HasSystemNetworks with HasCoreplexRISCVPlatform {
  val bootrom_address = 0x10000
  val bootrom_size    = 0x10000
  val bootrom_hang    = 0x10040
  private lazy val bootrom_contents = GenerateBootROM(coreplex.dtb)
  val bootrom = LazyModule(new TLROM(bootrom_address, bootrom_size, bootrom_contents, true, peripheryBusConfig.beatBytes))

  bootrom.node := TLFragmenter(peripheryBusConfig.beatBytes, cacheBlockBytes)(peripheryBus.node)
}

/** Coreplex will power-on running at 0x10040 (BootROM) */
trait HasPeripheryBootROMModuleImp extends LazyMultiIOModuleImp {
  val outer: HasPeripheryBootROM
  outer.coreplex.module.io.resetVector := UInt(outer.bootrom_hang)
}

ちなみにこのbootromなのだが、Verilogの実装を見てみると、初期値がそのまま記述してある。

scalaでの記述はこちら。rocket-chip/src/main/scala/coreplex/Config.scalaを参照のこと。

class BaseCoreplexConfig extends Config ((site, here, up) => {
  case PAddrBits => 32
  case PgLevels => if (site(XLen) == 64) 3 /* Sv39 */ else 2 /* Sv32 */
  case ASIdBits => 0
  case XLen => 64 // Applies to all cores
  case ResetVectorBits => site(PAddrBits)
  case MaxHartIdBits => log2Up(site(RocketTilesKey).size)
  case BuildCore => (p: Parameters) => new Rocket()(p)
  case RocketCrossing => SynchronousCrossing()
  case RocketTilesKey =>  Nil
  case DMKey => DefaultDebugModuleConfig(site(XLen))
  case PLICKey => PLICParams()
  case ClintKey => ClintParams()
  case CBusConfig => TLBusConfig(beatBytes = site(XLen)/8)
  case L1toL2Config => TLBusConfig(beatBytes = site(XLen)/8) // increase for more PCIe bandwidth
  case BootROMFile => "./bootrom/bootrom.img"
  case BroadcastConfig => BroadcastConfig()
  case BankedL2Config => BankedL2Config()
  case CacheBlockBytes => 64
})

初期値が直打ちしてある。

  assign _GEN_1 = 10'h1 == index ? 32'h1f41413 : 32'h10041b;
  assign _GEN_2 = 10'h2 == index ? 32'hf1402573 : _GEN_1;
  assign _GEN_3 = 10'h3 == index ? 32'h597 : _GEN_2;
  assign _GEN_4 = 10'h4 == index ? 32'h7458593 : _GEN_3;
  assign _GEN_5 = 10'h5 == index ? 32'h8402 : _GEN_4;
  assign _GEN_6 = 10'h6 == index ? 32'h0 : _GEN_5;
  assign _GEN_7 = 10'h7 == index ? 32'h0 : _GEN_6;
  assign _GEN_8 = 10'h8 == index ? 32'h0 : _GEN_7;
  assign _GEN_9 = 10'h9 == index ? 32'h0 : _GEN_8;
  assign _GEN_10 = 10'ha == index ? 32'h0 : _GEN_9;
  assign _GEN_11 = 10'hb == index ? 32'h0 : _GEN_10;
  assign _GEN_12 = 10'hc == index ? 32'h0 : _GEN_11;
  assign _GEN_13 = 10'hd == index ? 32'h0 : _GEN_12;
  assign _GEN_14 = 10'he == index ? 32'h0 : _GEN_13;
  assign _GEN_15 = 10'hf == index ? 32'h0 : _GEN_14;
  assign _GEN_16 = 10'h10 == index ? 32'hf1402573 : _GEN_15;
  assign _GEN_17 = 10'h11 == index ? 32'h597 : _GEN_16;
  assign _GEN_18 = 10'h12 == index ? 32'h3c58593 : _GEN_17;
  assign _GEN_19 = 10'h13 == index ? 32'h10500073 : _GEN_18;
...

RocketChipの足回りを理解する(2. TileLinkについて)

2017/09/05 TileLink Specification Ver.1.7 Draftが公開されていたので、追記しています。

RISC-V実装のRocket-Chipでは、足回りのインタフェースとしてTileLinkというインタフェースを使っている。ところがこれ、どれだけ検索しても詳細が出てこない。TileLinkとは何なのだろう?

資料については、以下のページぐらいしか出てこない。これを読み解くしか、TileLinkの仕様を理解するのは不可能なようだ。

TileLink 0.3.3 Specification

2017/09/05 TileLink Specification Ver.1.7が公開されていた。以下から詳細資料を参照することができる。

www.sifive.com

また、以下の論文も参照されている。

  • Manager-Client Pairing: A Framework for Implementing Coherence Hierarchies

http://tinker.cc.gatech.edu/pdfs/MICRO44_Jesse_Beu.pdf

TileLinkの接続イメージ

以下は上記解説サイトからの引用。

f:id:msyksphinz:20170713020608p:plain

大まかにAXIなどのバスネットワークと変わらないと思っている。

f:id:msyksphinz:20170905014652p:plain (https://www.sifive.com/documentation/tilelink/tilelink-spec/ より引用)

さて、以下は解説文を少しずつ翻訳しながら理解していくことにする。

イントロダクション

TileLinkはオンチップメモリ階層内での特定のキャッシュコヒーレンシポリシを実装したキャッシュコヒーレンストランザクションのために設計されたプロトコルである。

TileLinkアーキテクチャ

TileLinkのレベル

TileLinkは大きく2つのレベルに分けられる。

  • TL-UL (TileLink Uncached Lightweight) : シンプルなメモリ読み書き、バースト読み書きはサポートしない。
  • TL-UH (TileLink Uncached Heavyweight) : TL-ULに加えて、いくつかのヒントオペレーション、アトミックオペレーションとバーストアクセスなどをサポートする。
  • TL-C (TileLink Cached) : TL-UH に加えて、コヒーレントキャッシュのサポートを加えている。

f:id:msyksphinz:20170905015044p:plain (https://www.sifive.com/documentation/tilelink/tilelink-spec/ より引用)

エージェント

TileLinkはクライアント・マネージャアーキテクチャコヒーレンスプロトコル内に参加しているエージェントは、以下のどちらかである。

  • クライアントがキャッシュブロックにリクエストを行う。
  • マネージャがキャッシュブロックのパーミッションとデータの伝搬を監視する。

クライアントはキャッシュ、DMAエンジン、もしくはコヒーレントメモリドメインに参加している他のコンポーネントであり、実際にデータをローカルにコピーするかどうに関わらない。 マネージャはアウターレベルのキャッシュコントローラであり、ディレクトリ方式、もしくはバスののようなブロードきゃしゅとメディアである。 マルチレベルのメモリ階層では、特定のキャッシュコントローラはクライアント(階層内のさらに外側のキャッシュ)もしくはマネージャ(プロセッサにより近いキャッシュ)としての機能を持っている。

チャネル

TileLinkの持つ5つの独立したチャネルについて。 これらのチャネルは同一の物理リンクを利用してマルチプレクスをすることができる。 しかしデッドロックを防ぐためにチャネル間で優先度を定められており、これらを厳密に守ることが求められる。 チャネルにはメタデータとデータコンポーネントが含まれている。

  • Channel-A : 特定のアドレス領域への操作リクエストを発行する。
  • Channel-D : リクエスト元に対するデータの応答を返す。
  • Channel-B (TL-C専用) : マスタエージェントによりキャッシュされているアドレスへのリクエストを送信する。キャッシュデータのライトバック操作のリクエストなど。
  • Channel-C (TL-C専用) : Channe-Bのリクエストに対する応答データか書き込み応答を返す。
  • Channel-E (TL-C専用) : キャッシュブラックなどのブロック転送の最終応答に使用される。

これらのチャネルはクライアントからマネージャ、マネージャからクライアントへと接続される。クライアントからクライアントへは接続されない。 パーミッションは「Finish >> Grant >> Release >> Probe >> Acquire」である。

トランザクションフロー

TileLinkにより管理されるキャッシュブロックには、2タイプのトランザクションが存在する。

タイプ1.

  • クライアントがAcquireをマネージャに送信する
  • マネージャは必要なProbeをクライアントに送信する。
  • マネージャはリリースした全てのProbeに対するReleaseを待つ。
  • マネージャは必要ならば後続のメモリにアクセスを行う。
  • 必要なデータもしくはパーミッションを手に入れると、オリジナルのリクエストもとに対してGrantを送信する。
  • Grantを受信すると、オリジナルのクライアントはマネージャに対してFinishを送信しトランザクションを完了する。

タイプ2.

  • クライアントはマネージャに対してリリースを送信し、これが自発的な動作であることを指定する。
  • マネージャは必要ならば後続のメモリに対して通信を行う。
  • マネージャはGrantを利用して、トランザクションのAcknowledgeを通知する。

並列性

TileLinkha、特定のチャネルのP2P通信に対してオーダリングに対して規定は存在しない。 従って、システム上のエージェントが並列性を保持する必要がある。

  • マネージャは既に別のクライアントによってインフライトである場合には(以降に議論するように、2つのトランザクションをどのようにマージするのか方法が分かっている場合を除く)リクエストを許可してはならない。
  • クライアントがアウトスタンディングな自発的なトランザクションを持っているならば、ライトバックの完了をAcknowledgeするマネージャからのGrantを受信しないと、Probeリクエストに対するReleaseを返信することができない。

チャネル信号について

物理的なチャネルの詳細は定義しておらず、各チャネルでのトランザクションを伝えるための信号群を定義しておけばよい。

関連記事