FPGA開発日記

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

「アジャイルサムライ」を(とりあえず)読了した

アジャイルサムライ−達人開発者への道−

アジャイルサムライ−達人開発者への道−

アジャイルプロジェクトというのに昔から興味があって、「アジャイルサムライ」というのを読み始めていた。

アジャイルサムライについては前に少しブログで触れたけれども、この度やっと一通り読み終えたので、全体を通して思ったことをまとめておきたい。

msyksphinz.hatenablog.com

お客との対話が常に重要

アジャイルサムライでは、要求や締め切りをミートさせるために、お客と常に話し合うことが大事だとまず説いていた。 プロジェクトにお客を巻き込むのだと。 そして、短いスパンでリリースを繰り返して行き、顧客の思うものと、最終成果物との差分をなくしていくことが重要だ。 また、常にプロジェクトのありのままをお客に見てもらうことにより、プロジェクトに問題が発生しても正しい解決策を導き出すことが出来るようになる。

これ、ハードウェアや半導体開発でも同じことが言えるだろう。ハードウェア開発者にとって、お客というのは実際にチップを使うメーカーであったり、 同じプロジェクト内にいるソフトウェア技術者だったりする。

ソフトウェア技術者に対して、細かくFPGAで最新ハードウェアをリリースしチェックしてもらうことは、常にゴールをぶれさせないようにするためであったり、 後に述べる「継続的インテグレーション」のために必要なことだと思う。

面白いツールの紹介「インセプションデッキ」

よくプロジェクトの途中に、自分の役割が良く分からなくなったり、目標がぼやけてしまうことがある。

そのためにまずはアジャイルプロジェクトでは「このプロジェクトの視界を遮る難しい問題」についてあえて取り上げ、プロジェクトをスムーズに進めるための 手助けとする。これはエンジニアにとって、「今自分の仕事は何のためにあるのか」という重要なモチベーションを維持するのに役に立つ。

f:id:msyksphinz:20170128194911p:plain

面白いツールの紹介「バーンダウンチャート」

プロジェクトの進捗度合い、ベロシティが今どの程度なのか?それを見積もるためのツールとしてバーンダウンチャートが紹介されていた。

どの程度タスクをこなしたかにより、現在のベロシティと完了見積もりをたてることが出来る。

タスクが増えたことにより、見積もりが変わった場合にはベロシティを考え直し、プロジェクトの終了見積もりを立て直す。

f:id:msyksphinz:20170128200956p:plain

おまけ:第V部の個人メモ

第5部については、読みながらRedmineにメモを取っていったので、それを貼っておこうと思う。

第12章 ユニットテスト:動くことが分かる

  • バグを修正する前に、失敗するテストを書く。

テストコードをたくさん書くことの利点

  • すばやいフィードバックが得られる
  • きわめて低コストにリグレッションテストを実行できる
  • デバッグ時間を大幅に削減できる
  • 自信を持ってデプロイできる

一見ユニットテストを書くのが難しそうな項目(ランダムテストであることを確認するテストなど)でも、アサーションなどを適用することにより、ある程度チェックすることが出来る。

第13章 リファクタリング:技術的負債の返済

  • 新機能の追加はしないし、バグの修正もしない。もっと分かりやすくする。
  • コードの意図をつかみやすくしたり、変更がしやすくなるように設計を改善する→リファクタリング

第14章 テスト駆動開発

ごく短いサイクルを繰り返しながら少しずつソフトウェアを設計していく。

  1. まずは失敗するユニットテストを書く
  2. テストに成功するコードを書く
  3. リファクタリングする

以上を繰り返す。

  • ルールその1。失敗するテストをひとつ書くまでは、新しいコードを一切書かない。
  • ルールその2。「危なっかしいところ」をすべてテストする。

第15章 継続的インテグレーション:リリースに備える

  • 常にリリースできるような状態を維持する。
  • チェックイン手順を習慣付ける
  • ビルドの作業単位を短くし、短いスパンで継続的インテグレーションできるように心がける。

RISC-VのJavascriptエミュレータはC言語から生成されている

前回の続き。RISC-Vエミュレータであるriscvemuのコンパイルオプションを調べていたのだが、やっと意味が分かってきた。

riscvemuのディレクトリにはjs/が入っており、RISC-VのJavascriptエミュレータが入っている。 最初はC言語からこのJavascript記述を呼び出しているのかと思ったが、その逆だった。C言語の記述から、Javascriptのファイルを生成していたのだ。

考えてみれば、これは当然のことだ。EmscriptenC言語記述からJavascriptを生成するものだ。なのでWebで動作するRISC-VエミュレータEmscriptenで生成しているのも納得できる。

Makefile.jsに生成スクリプトが格納されていた。

# build Javascript version of riscvemu
EMCC=emcc
EMCFLAGS=-O2 -Wall -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -MMD -fno-strict-aliasing
#EMCFLAGS+=-Werror
EMCFLAGS+=-DMAX_XLEN=64
EMLDFLAGS=-g -O3 -s TOTAL_MEMORY=536870912 --memory-init-file 0 --closure 0 -s NO_EXIT_RUNTIME=1 -s "EXPORTED_FUNCTIONS=['_console_queue_char','_main']"

all: js/riscvemu.js

JS_OBJS=riscvemu.js.o softfp.js.o ide.js.o

js/riscvemu.js: $(JS_OBJS)
        $(EMCC) $(EMLDFLAGS) -o $@ $(JS_OBJS)

%.js.o: %.c
        $(EMCC) $(EMCFLAGS) -c -o $@ $<

以下で実行してみた。

$ make -f Makefile.js
emcc -O2 -Wall -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -MMD -fno-strict-aliasing -DMAX_XLEN=64 -c -o riscvemu.js.o riscvemu.c
INFO:root:generating system asset: is_vanilla.txt... (this will be cached in "/home/vagrant/.emscripten_cache/is_vanilla.txt" for subsequent builds)
INFO:root: - ok
INFO:root:(Emscripten: Running sanity checks)
riscvemu.c:495:1: warning: unused function 'phys_read_u8' [-Wunused-function]
PHYS_MEM_READ_WRITE(8, uint8_t)
^
riscvemu.c:486:25: note: expanded from macro 'PHYS_MEM_READ_WRITE'
static inline uint_type phys_read_u ## size(RISCVCPUState *s, target_ulong addr) \
                        ^
<scratch space>:9:1: note: expanded from here
phys_read_u8
^
1 warning generated.
emcc -O2 -Wall -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -MMD -fno-strict-aliasing -DMAX_XLEN=64 -c -o softfp.js.o softfp.c
emcc -O2 -Wall -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -MMD -fno-strict-aliasing -DMAX_XLEN=64 -c -o ide.js.o ide.c
emcc -g -O3 -s TOTAL_MEMORY=536870912 --memory-init-file 0 --closure 0 -s NO_EXIT_RUNTIME=1 -s "EXPORTED_FUNCTIONS=['_console_queue_char','_main']" -o js/riscvemu.js riscvemu.js.o softfp.js.o ide.js.o
INFO:root:generating system library: libc.bc... (this will be cached in "/home/vagrant/.emscripten_cache/asmjs/libc.bc" for subsequent builds)

INFO:root: - ok
INFO:root:generating system library: dlmalloc.bc... (this will be cached in "/home/vagrant/.emscripten_cache/asmjs/dlmalloc.bc" for subsequent builds)
INFO:root: - ok
warning: Output contains some very large functions (3671 lines in _riscv_cpu_interp64), consider building source files with -Os or -Oz, and/or trying OUTLINING_LIMIT to break them up (see settings.js; note that the parameter there affects AST nodes, while we measure lines here, so the two may not match up)

RISCVEMU(高速RISC-Vエミュレータ)には2つのビルドモードがある(Emscriptenの調査)

RISCVEMUは高速RISC-Vエミュレータだが、その実装の仕組みを探っていると、どうやら2つのビルドモードがありそうだということが分かった。

msyksphinz.hatenablog.com

RISCVEMUはLinuxも立ち上げることが出来る高速エミュレータだが、その実装の仕組みを探っていると、大きくソースコード#defineにより分割されていることが分かった。

  • risvemu.c
#ifdef EMSCRIPTEN
#include "list.h"
#include <emscripten.h>
#else
#include <getopt.h>
#include <termios.h>
#endif

EMSCRIPTENというのは何だろうと思ったが、どうやらC言語Javascriptを接続するためのツールらしい。

デフォルトでは、このEMSCRIPTENはオフになっており、C言語でごりごりに記述されたRISCVエミュレータが起動する。

  • riscvemu_template.h
        switch(opcode) {
#ifdef CONFIG_EXT_C
        C_QUADRANT(0)
            pc_next = (intx_t)(s->pc + 2);
...
            }
            if (rd != 0)
                s->reg[rd] = val;
            break;
#if XLEN >= 64
        case 0x3b: /* OP-32 */
            imm = insn >> 25;
            val = s->reg[rs1];
            val2 = s->reg[rs2];
            if (imm == 1) {
                funct3 = (insn >> 12) & 7;
                switch(funct3) {
                case 0: /* mulw */
                    val = (int32_t)((int32_t)val * (int32_t)val2);
                    break;
                case 4:/* divw */
                    val = div32(val, val2);
                    break;
                case 5:/* divuw */
                    val = (int32_t)divu32(val, val2);
                    break;
                case 6:/* remw */
                    val = rem32(val, val2);
                    break;
                case 7:/* remuw */
                    val = (int32_t)remu32(val, val2);
                    break;
                default:
                    goto illegal_insn;
                }
            } else {
                if (imm & ~0x20)
                    goto illegal_insn;
                funct3 = ((insn >> 12) & 7) | ((insn >> (30 - 3)) & (1 << 3));
                switch(funct3) {
                case 0: /* addw */
                    val = (int32_t)(val + val2);
                    break;
                case 0 | 8: /* subw */

EMSCRIPTENを有効にすると、Javascriptでの実装が有効になるみたいなのだが、このためにはEmscriptenのインストールが必要になる。

qiita.com

Download and install — Emscripten 1.36.14 documentation

emscripten.h — Emscripten 1.36.14 documentation

上記のウェブサイトの指示の元にインストールしてみたのだが、上手くいかなかった。

/emsdk_portable$ sudo ./emsdk install latest
Installing SDK 'sdk-master-64bit'..
Installing tool 'clang-master-64bit'..
Repository 'https://github.com/kripken/emscripten-fastcomp/' already cloned to directory '/home/vagrant/work/emsdk_portable/clang/fastcomp/src', skipping.
Fetching latest changes to the branch 'master' for '/home/vagrant/work/emsdk_portable/clang/fastcomp/src'...
Already up-to-date.
Successfully updated and checked out branch 'master' on repository '/home/vagrant/work/emsdk_portable/clang/fastcomp/src'
Current repository version: "Fri, 23 Dec 2016 15:47:53 -0800 881bd352731d21c7117ad7e2ece347aacae83965"
Repository 'https://github.com/kripken/emscripten-fastcomp-clang/' already cloned to directory '/home/vagrant/work/emsdk_portable/clang/fastcomp/src/tools/clang', skipping.
Fetching latest changes to the branch 'master' for '/home/vagrant/work/emsdk_portable/clang/fastcomp/src/tools/clang'...
Already up-to-date.
Successfully updated and checked out branch 'master' on repository '/home/vagrant/work/emsdk_portable/clang/fastcomp/src/tools/clang'
Current repository version: "Fri, 23 Dec 2016 15:47:47 -0800 60a7e9a9c22b67309e5b1258d38fadfa481a25d3"
Running CMake: ['cmake', '-G', 'Unix Makefiles', '-DCMAKE_BUILD_TYPE=Release', '-DPYTHON_EXECUTABLE=/usr/bin/python', '-DLLVM_TARGETS_TO_BUILD=X86;JSBackend', '-DLLVM_INCLUDE_EXAMPLES=OFF', '-DCLANG_INCLUDE_EXAMPLES=OFF', '-DLLVM_INCLUDE_TESTS=OFF', '-DCLANG_INCLUDE_TESTS=OFF', '/home/vagrant/work/emsdk_portable/clang/fastcomp/src']
CMake Error at CMakeLists.txt:3 (cmake_minimum_required):
  CMake 3.4.3 or higher is required.  You are running version 3.2.2


-- Configuring incomplete, errors occurred!
CMake invocation failed due to exception!
Working directory: /home/vagrant/work/emsdk_portable/clang/fastcomp/build_master_64
Command '['cmake', '-G', 'Unix Makefiles', '-DCMAKE_BUILD_TYPE=Release', '-DPYTHON_EXECUTABLE=/usr/bin/python', '-DLLVM_TARGETS_TO_BUILD=X86;JSBackend', '-DLLVM_INCLUDE_EXAMPLES=OFF', '-DCLANG_INCLUDE_EXAMPLES=OFF', '-DLLVM_INCLUDE_TESTS=OFF', '-DCLANG_INCLUDE_TESTS=OFF', '/home/vagrant/work/emsdk_portable/clang/fastcomp/src']' returned non-zero exit status 1
Installation failed!

まずはCMakeの最新版をダウンロードしてきた。

wget https://cmake.org/files/v3.7/cmake-3.7.2-Linux-x86_64.tar.gz

CMakeを最新版にアップデートしてemsdk insntall latestを実行すると前に進みだしたようだ。

これにはもうちょっと時間がかかる。今日は時間切れ。

setjmpとlongjmp はどのように実現されているのか

setjump(), longjump()について基本的なことを調査するために、まずは以下のページなどを読んで勉強した。

http://www.nurs.or.jp/~sug/soft/super/longjmp.htm

(制限はあるが)どのような場所からでも、setjump()を実行した場所に戻ってくることが出来る。これにより、例外処理もどきのようなものが作れる。

#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>

jmp_buf jmp_div;

void divide_test (int a, int b)
{
  if (b == 0) {
    longjmp(jmp_div, 1);
  }
  int div = a / b;
  printf ("divide_test (%d, %d) = %d\n", a, b, div);
  return;
}


int main()
{
  if (setjmp(jmp_div) == 0) {
    divide_test (223, 7);
    divide_test (571, 13);
    divide_test (311, 0);
  } else {
    fprintf (stderr, "divide_test failure\n");
    return EXIT_FAILURE;
  }

  return 0;
}

上記のプログラムはdivide_test()という除算を実行する関数を三回呼び出している。 ただし三回目はゼロで除算するため、本来は実行してはいけない演算だ。

このプログラムでは、setjmp()により現在の位置を記憶し、divide_test()内でlongjmp()が呼ばれた場合(この場合ゼロで除算する判定に引っかかった場合)に、setjmp()の場所に戻ってくる。

さらにsetjmp()の戻り値がlongjmp()により設定されており(この場合は1)、条件判定として利用できる。

これだけでは関数の戻り値として制御すればよいではないか、ということになる。 setjmp()longjmp()の威力を発揮するのはどのような領域だろうかといろいろ探っていたのだが、スレッドもどきのような物も作れるらしい。

http://blog.bugyo.tk/lyrical/archives/572

じゃあ、これをどのようにしてアセンブラは実現しているのか。

ちなみにこれ、アセンブラ言語じゃないと実現は不可能なことは簡単に分かる。コンパイルした結果を観察することで、どのようにして実現されているのかを観察してみる。

RISC-VのGCCを利用して上記のプログラムをコンパイルし、中身を見てみよう。

riscv64-unknown-elf-gcc -o longjump_test.riscv longjump_test.c
riscv64-unknown-elf-objdump -D longjump_test.riscv | less

setjmp()が呼ばれる。

   101d8:       01010413                addi    s0,sp,16
   101dc:       93818513                addi    a0,gp,-1736 # 1b968 <jmp_div>
   101e0:       328000ef                jal     10508 <setjmp>

まず、main()の中でsetjmp()が呼ばれる。setjmp()の中身は実はこうなっている。

0000000000010508 <setjmp>:
   10508:       00153023                sd      ra,0(a0)
   1050c:       00853423                sd      s0,8(a0)
   10510:       00953823                sd      s1,16(a0)
   10514:       01253c23                sd      s2,24(a0)
   10518:       03353023                sd      s3,32(a0)
   1051c:       03453423                sd      s4,40(a0)
   10520:       03553823                sd      s5,48(a0)
   10524:       03653c23                sd      s6,56(a0)
   10528:       05753023                sd      s7,64(a0)
   1052c:       05853423                sd      s8,72(a0)
   10530:       05953823                sd      s9,80(a0)
   10534:       05a53c23                sd      s10,88(a0)
   10538:       07b53023                sd      s11,96(a0)
   1053c:       06253423                sd      sp,104(a0)
   10540:       003026f3                frsr    a3
   10544:       08853027                fsd     fs0,128(a0)
   10548:       08953427                fsd     fs1,136(a0)
   1054c:       09253827                fsd     fs2,144(a0)
   10550:       09353c27                fsd     fs3,152(a0)
   10554:       0b453027                fsd     fs4,160(a0)
   10558:       0b553427                fsd     fs5,168(a0)
   1055c:       0b653827                fsd     fs6,176(a0)
   10560:       0b753c27                fsd     fs7,184(a0)
   10564:       0d853027                fsd     fs8,192(a0)
   10568:       0d953427                fsd     fs9,200(a0)
   1056c:       0da53827                fsd     fs10,208(a0)
   10570:       0db53c27                fsd     fs11,216(a0)
   10574:       06d53c23                sd      a3,120(a0)
   10578:       00000513                li      a0,0
   1057c:       00008067                ret

現在のレジスタの内容を(コンテキスト)を退避しているだけである。同様に、longjmp()は以下のようになる。

0000000000010580 <longjmp>:
   10580:       00053083                ld      ra,0(a0)
   10584:       00853403                ld      s0,8(a0)
   10588:       01053483                ld      s1,16(a0)
   1058c:       01853903                ld      s2,24(a0)
   10590:       02053983                ld      s3,32(a0)
   10594:       02853a03                ld      s4,40(a0)
   10598:       03053a83                ld      s5,48(a0)
   1059c:       03853b03                ld      s6,56(a0)
   105a0:       04053b83                ld      s7,64(a0)
   105a4:       04853c03                ld      s8,72(a0)
   105a8:       05053c83                ld      s9,80(a0)
   105ac:       05853d03                ld      s10,88(a0)
   105b0:       06053d83                ld      s11,96(a0)
   105b4:       06853103                ld      sp,104(a0)
   105b8:       07853683                ld      a3,120(a0)
   105bc:       08053407                fld     fs0,128(a0)
   105c0:       08853487                fld     fs1,136(a0)
   105c4:       09053907                fld     fs2,144(a0)
   105c8:       09853987                fld     fs3,152(a0)
   105cc:       0a053a07                fld     fs4,160(a0)
   105d0:       0a853a87                fld     fs5,168(a0)
   105d4:       0b053b07                fld     fs6,176(a0)
   105d8:       0b853b87                fld     fs7,184(a0)
   105dc:       0c053c07                fld     fs8,192(a0)
   105e0:       0c853c87                fld     fs9,200(a0)
   105e4:       0d053d07                fld     fs10,208(a0)
   105e8:       00369073                fssr    a3
   105ec:       0015b513                seqz    a0,a1
   105f0:       00b50533                add     a0,a0,a1
   105f4:       00008067                ret

これはsetjmp()によって作成したコンテキストを元に戻しているだけである。非常に単純!

setjmp()はreturn address register (ra)を保存しており、これはsetjmp()内では「setjmp()を呼び出したプログラムの直後」に設定されている。longjmp()によってコンテキストが戻され、ret命令が実行されるので、longjmp()が終了するとsetjmp()の実行直後に戻されるというわけだ。

また、2番目の引数(a1レジスタ)を戻り値に設定しているため、longjmp()の引数がsetjmp()の戻り値に設定されていることも分かる。

この仕組みは、言われてみれば確かにまったく驚くにあたらない。

ただし、一見複雑そうな動きをするsetjmp()longjmp()も、このような非常に単純なアセンブリコードで実現されているかと考えると、ちょっと自分の頭の硬さに嫌気が差してくる次第だ。

FreeRTOS-RISCVの起動シーケンスを追いかける(1)

FreeRTOSの話題の続き。前回FreeRTOSのRISC-V版を少しエミュレータで試してみたものの、何も動作せずに終わっていたのだった。 (そしてそれを特に原因究明せず放置していた。)

msyksphinz.hatenablog.com

それではあまりにもつまらないので、自作ISSを使ってFreeRTOSの起動シーケンスをトレースしていきたい。 どうやってトレースするかというと、僕の自作RISC-V ISSは階層トレースモードが付いているので、関数ジャンプなどの場所を検知して関数のとび先をログに示してくれる。

命令トレースと関数トレースを使って、どのように実行されるのかについて調査していこう。

FreeRTOS-RISCVが立ち上がる仕組み

RISC-Vのリセットアドレスは通常だと0x8000_0000だ。ここからまずは汎用レジスタ等のフォーマットが始まり、各種メモリの初期化処理などが入り始める。

riscv64-unknown-elf-objdump -D riscv-spike.elf > riscv-spike.dmp

Disassembly of section .text:

0000000080000000 <boot>:
    80000000:   00002fb7                lui     t6,0x2
    80000004:   800f8f9b                addiw   t6,t6,-2048
    80000008:   300f9073                csrw    mstatus,t6
    8000000c:   0340006f                j       80000040 <_mstart>
    80000010:   00000013                nop
...

ここまでの命令トレースを追いかけると、以下のようになった。main()に入ると、今度はいろんな初期化処理が始まる。

<FunctionCall 108121 vSyscallInit(0x800075b4)>
  <FunctionCall 108128 main(0x80007edc)>
    <FunctionCall 108132 vCreateBlockTimeTasks(0x8000806c)>
      <FunctionCall 108138 xQueueGenericCreate(0x80001510)>
        <FunctionCall 108158 pvPortMalloc(0x80006d78)>
          <FunctionCall 108163 vTaskSuspendAll(0x800036b4)>
          <Return: vTaskSuspendAll>
          <FunctionCall 108177 prvHeapInit(0x8000705c)>
          <Return: prvHeapInit>
          <FunctionCall 108308 xTaskResumeAll(0x800036d8)>
            <FunctionCall 108316 vTaskEnterCritical(0x80005108)>
            <Return: vTaskEnterCritical>
            <FunctionCall 108339 vTaskExitCritical(0x80005148)>
            <Return: vTaskExitCritical>
          <Return: xTaskResumeAll>
        <Return: pvPortMalloc>
        <FunctionCall 108375 xQueueGenericReset(0x800013f0)>
          <FunctionCall 108384 vTaskEnterCritical(0x80005108)>
...

とりあえず画面に何か表示させたいんだけどどうしたらいいのかな?調査してみると、printf()があるらしい。 これは中身を見てみると、普通に文字列フォーマットを作って、それをputch()に渡している。

  • arch/clib.c
int printf(const char* fmt, ...)
{
        va_list ap;
        va_start(ap, fmt);

        vFormatPrintString((void*) putchar, 0, fmt, ap);

        va_end(ap);
        return 0; // incorrect return value, but who cares, anyway?
}

static void vFormatPrintString(void (*putch)(int, void**), void **putdat,
                        const char *fmt, va_list ap)
{
        register const char* p;
        const char* last_fmt;
        register int ch;
        unsigned long long num;
        int base, lflag, width, precision;
        char padc;

        while (1) {
                while ((ch = *(unsigned char *) fmt) != '%') {
                        if (ch == '\0')
                                return;
                        fmt++;
                        putch(ch, putdat);
                }
                fmt++;

                // Process a %-escape sequence
...

putch()の中身はというと、vFormatPrintStringを呼ぶ際に関数ポインタとしてputchar()を指定しているため、結局putchar()を読んでいる。

        vFormatPrintString((void*) putchar, 0, fmt, ap);
...
int putchar(int ch)
{
        static char buf[64] __attribute__((aligned(64)));
        static int buflen = 0;

        buf[buflen++] = ch;

        if (ch == '\n' || buflen == sizeof(buf)) {
                syscall(SYS_write, 1, (long) buf, buflen);
                buflen = 0;
        }

        return 0;
}

改行に到達するか、bufの最大容量まで到達するとシステムコールを呼び出し書き込みを行う。それ以外は、とりあえずbufに一文字ずつ貯めていく。一文字ずつシステムコールが発生しないようにする工夫だ。

システムコールを実行し、デバイスにアクセスするための権限を獲得する。

  • arch/clib.c
                syscall(SYS_write, 1, (long) buf, buflen);
  • 実行時の命令トレース
    189132:M:MBar:[80007564][P80007564] 01013503 : ld         r10,r02,0x010        r02=>000000008003b8a8 (000000008003b8b8)=>00000001 r10<=0000000000000001
    189133:M:MBar:[80007568][P80007568] 00813583 : ld         r11,r02,0x008        r02=>000000008003b8a8 (000000008003b8b0)=>80039800 r11<=0000000080039800
    189134:M:MBar:[8000756c][P8000756c] 00013603 : ld         r12,r02,0x000        r02=>000000008003b8a8 (000000008003b8a8)=>0000000c r12<=000000000000000c
ECALL from Machine
    189135:M:MBar:[80007570][P80007570] 00000073 : ecall                           mepc<=0000000080007570 mcause<=000000000000000b mtvec=>0000000080000164 mstatus=>0000000000001800 mstatus<=000000000000c006 pc<=0000000080000164
    189136:M:MBar:[80000164][P80000164] ff810113 : addi       r02,r02,0xff8        r02=>000000008003b8a8 r02<=000000008003b8a0
    189137:M:MBar:[80000168][P80000168] 00513023 : sd         r02,r05,0x000        r02=>000000008003b8a0 r05=>0000000000000000 (000000008003b8a0)<=00000000
    189138:M:MBar:[8000016c][P8000016c] 342022f3 : csrrs      r05,0x342,r00        r00=>0000000000000000 mcause=>000000000000000b r05<=000000000000000b
    189139:M:MBar:[80000170][P80000170] fc02cce3 : blt        r05,r00,0x7e         r05=>000000000000000b r00=>0000000000000000
    189140:M:MBar:[80000174][P80000174] 00013283 : ld         r05,r02,0x000        r02=>000000008003b8a0 (000000008003b8a0)=>00000000 r05<=0000000000000000
    189141:M:MBar:[80000178][P80000178] 00810113 : addi       r02,r02,0x008        r02=>000000008003b8a0 r02<=000000008003b8a8
    189142:M:MBar:[8000017c][P8000017c] f0810113 : addi       r02,r02,0xf08        r02=>000000008003b8a8 r02<=000000008003b7b0
    189143:M:MBar:[80000180][P80000180] 00113423 : sd         r02,r01,0x008        r02=>000000008003b7b0 r01=>0000000080007738 (000000008003b7b8)<=80007738
    189144:M:MBar:[80000184][P80000184] 00213823 : sd         r02,r02,0x010        r02=>000000008003b7b0 r02=>000000008003b7b0 (000000008003b7c0)<=8003b7b0

このときのシステムコールの処理だが、arch/syscalls.cに記述がある。

  • arch/syscalls.c
static long prvSyscallToHost(long which, long arg0, long arg1, long arg2)
{
        volatile uint64_t magic_mem[8] __attribute__((aligned(64)));
    uint64_t oldfromhost;
        magic_mem[0] = which;
        magic_mem[1] = arg0;
        magic_mem[2] = arg1;
        magic_mem[3] = arg2;
        __sync_synchronize();
    tohost = (long) magic_mem;
    do
    {
        oldfromhost = fromhost;
        fromhost = 0;
    } while (oldfromhost == 0);
    return magic_mem[0];
}

do-whileでfromhostの条件が変わるまでループしているが、自作ISSにそんな機能は無いので、ここを改良する必要がありそうだ。

fromhost, tohostの場所はアドレスでマッピングされており、

less riscv-spike.nm
...

00000000800399c0 B fromhost
...
0000000080039a00 B tohost
...

と、見た感じかなり変動しそうな場所に配置されているので、これも工夫してうまく処理できるようにしなければならない。

RocketChip/BOOMプロセッサの波形ダンプの方法

RISC-VのUC Berkeley実装であるRocketChip/BOOMはエミュレーション時にVerilatorを使用している。

基本的な命令トレースなどは出力することが出来るが、波形を出力するためのオプションが存在する。

make CONFIG=BOOMConfig output/rv64ui-p-add.vcd

テストベンチの名前に.vcdを付加したオプションで実行すると、波形ダンプを出力しながらシミュレーションが実行される。 ただしdebug用の新しい実行バイナリを作成し、再コンパイルには結構時間がかかる。

$ make CONFIG=BOOMConfig output/coremark.riscv.o3.vcd
mkdir -p /home/vagrant/rocket-chip/emulator/generated-src-debug/rocketchip.BOOMConfig
/home/vagrant/rocket-chip/emulator/verilator/install/bin/verilator --cc --exe --top-module TestHarness +define+PRINTF_COND=\$c\(\"verbose\",\"\&\&\"\,\"done_reset\"\) +define+STOP_COND=\$c\(\"done_reset\"\) --assert --output-split 20000
-Wno-STMTDLY --x-assign unique -I/home/vagrant/rocket-chip/vsrc -O3 -CFLAGS "-O1 -std=c++11 -I/home/vagrant/riscv/include -I/home/vagrant/rocket-chip/csrc -DVERILATOR -include /home/vagrant/rocket-chip/csrc/verilator.h" -Mdir /home/vagra
nt/rocket-chip/emulator/generated-src-debug/rocketchip.BOOMConfig  --trace \
-o /home/vagrant/rocket-chip/emulator/emulator-rocketchip-BOOMConfig-debug /home/vagrant/rocket-chip/emulator/generated-src-debug/rocketchip.BOOMConfig.v /home/vagrant/rocket-chip/csrc/emulator.cc /home/vagrant/rocket-chip/csrc/SimDTM.cc
 -LDFLAGS " -L/home/vagrant/riscv/lib -Wl,-rpath,/home/vagrant/riscv/lib -L/home/vagrant/rocket-chip/emulator -lfesvr -lpthread" \
-CFLAGS "-I/home/vagrant/rocket-chip/emulator/generated-src-debug -include /home/vagrant/rocket-chip/emulator/generated-src-debug/rocketchip.BOOMConfig/VTestHarness.h -include /home/vagrant/rocket-chip/emulator/generated-src-debug/consts
.BOOMConfig.h"
...

$ ls -lt
-rw-r--r-- 1 vagrant vagrant     1942 Oct 20 15:19 Makefile
drwxr-xr-x 4 vagrant vagrant     4096 Oct 20 17:05 verilator
drwxrwxr-x 3 vagrant vagrant     4096 Nov  2 12:08 generated-src
-rw-rw-r-- 1 vagrant vagrant    27281 Nov  2 12:15 make.log
-rw-rw-r-- 1 vagrant vagrant     3630 Dec 15 12:15 Makefrag-verilator
-rwxrwxr-x 1 vagrant vagrant  3335952 Dec 15 13:21 emulator-rocketchip-BOOMConfig
drwxrwxr-x 3 vagrant vagrant     4096 Jan 22 14:30 generated-src-debug
-rwxrwxr-x 1 vagrant vagrant 19424096 Jan 22 15:13 emulator-rocketchip-BOOMConfig-debug
drwxrwxr-x 2 vagrant vagrant    20480 Jan 22 16:24 output

生成された波形は、output/テストベンチ名.vcdに保存されている。Windowsにコピーして眺めてみた。

f:id:msyksphinz:20170123022459p:plain

一応取得できているようだ。ただしVCDだけでは波形デバッグは難しいよね。ちゃんとしたソースコードとの対応が取りたい。。。

エンジニアだってあなたのことが嫌いなわけじゃない

megamouth.hateblo.jp

読んだ。すごく良い記事だと思った。エンジニアの試行回路をトレースされているようで、読みながら「分かるーーー!!」ってなってしまった。

(僕の知る限り)新しいプロジェクトが始まるとき、不完全なものを作ろうとするエンジニアなんて一人もいない。 プロジェクトのキックオフ時には、完璧に書かれた計画書が存在し、顧客を満足させる性能、綿密に組まれた役割分担、隙のないリリースフロー、、、このプロジェクト、失敗する余地なんてあるわけがないじゃないか! スケジュール的にはちょっと厳しいけど。

ただし、上記のエントリにもあるとおり、いろんな要素でプロジェクトの状態は一刻一刻と変化していく。僕らは最初は完璧な戦艦を作るつもりだったんだ。 だけど、実際には部品が足りないことが判明したり、マネージャーが砲台をもう一個追加しろと言ったと思ったら重過ぎるから何とかしろ(何とかしろって何だよ!)とか言ってきたり、、、 スケジュールは遅れる要因しかないけど何故かマネージャがミーティングから戻ってくると納期が早まっていたり、、、

いろんな要因が絡まってきて、僕らが作ろうとしてきた完璧な戦艦は、いつの間にか砲台が削られ、世界トップレベルだったはずの性能は削られ、そこそこ(あるいはそれ以下!)のものになってしまったり、だんだん自分が何のために戦艦を作っているのかわからなくなってくる。実はこの瞬間がエンジニアにとって一番危ないんだ。

マネージャや経営者の皆さん、覚えていて欲しい。エンジニアは「モチベーション」を失ったとき、最大限に生産性が落ちる。

でも、僕らは黙っている。言うことは言うけど、基本的にマネージャの決定事項に反論することは少ない。 会社の経営方針や顧客との信頼確保のためにやむを得ず決定したことだってあるだろう。 エンジニアはマネージャの決定したことが、ただマネージャの自分勝手で決められたことではないことだって知っている。

マネージャや経営者の皆さん、覚えていて欲しい。エンジニアは皆さんが思っている以上に、皆さんの立場を理解している。

納期をもっと短く出来ないか、顧客に言われたこの機能を追加できないか、そう言ってくるマネージャの雰囲気で、エンジニアは意外と事の重大さに気がついている。

だいたい、マネージャのいないプロジェクトなんて想像してごらんよ。いつまで経ってもそのプロジェクトは終了しないよ。 誰かがどこかで線を引かないと、完璧を求めるエンジニアはいつまでも機能追加、機能改善を求めて突き進んでしまう。

https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sagrada_Familia_02.jpg/1280px-Sagrada_Familia_02.jpg

サグラダ・ファミリア - Wikipedia

#僕もエンジニアだけど、実装したときは完璧だと思ってる物でも、数ヶ月して見直したらどうしてもリファクタリングしたくなっちゃうんだよね。

半導体畑出身な僕としては、ひとつの製品を長々と検討・開発し続け、経営者もフラフラとはっきりとしない経営方針を続けた結果、潰れかけた半導体企業を知っている。 上記のたとえ話をするならば、「戦艦大和」を皆で一生懸命作ろうとして、いつまで経っても完成しないアレだ。

toyokeizai.net

Facebookのオフィスには「Done is better than perfect」という言葉が壁に貼ってあるそうな。 これ、僕の理解(間違っているかもしれないけど)では、締め切りドリブンで動け、という意味だと理解している。

f:id:msyksphinz:20170121020346p:plain

この締め切りを決めるのはマネージャでありリーダであり、これを逃すと商機を逃したりする。 マネージャだって馬鹿じゃないんだから、理由もなく納期を短縮したりなんかしない。

強力なリーダーと、エンジニアの信頼関係

じゃあエンジニアに無理を承知でやってもらうためにはどうしたら良いのか? 僕は、マネージャとエンジニアとの信頼関係が構築されていれば、それが可能だと考えている。

マネージャが細かくエンジニアの状態を把握し(何も日報を出せといっているわけじゃない!)、それに伴ってエンジニアの要求もきちんと聞いてくれる、 マネージャがエンジニアに言うことを聞いて欲しいように、エンジニアだってマネージャに言うことを聞いて欲しい。一方通行では、信頼関係は築けない。 たまには飲み会でざっくばらんに愚痴を言い合ってもいいかもしれない(僕はあまり飲み会が好きじゃないけど)。

そうしてマネージャとエンジニアとの信頼関係、つまり「このマネージャだったら、無理は言うけど結果的に商談を成功に導いてくれる」と思われたり、「このエンジニアだったら、プロジェクトの成功のために一生懸命作業してくれる」と思われたりすると、多少難しい問題に直面しても、じゃあ何とかしましょう、ってなるんじゃないかね?

エンジニアは、マネージャの方針や考え方がブレることを恐れている。 昨日言ったことが意味もなく今日変わっていたり、昨日約束してくれたことを覚えていなかったりすると「この人大丈夫かな?」ってなり、いまいち信用できなくなったりする。

だから、マネージャには堂々としていて欲しい。「みんな俺の方針について来いよ!」って言われると、意外とエンジニアはついて来てくれるものなんだよ。

っていう

理想の世界が実現できれば幸せなんだけど、実際にはそうも行かないもんだねえ。