FPGA開発日記

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

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

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

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

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

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

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

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

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

っていう

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

Verilogの慣性遅延と伝播遅延

最近までハードウェア設計における慣性遅延と伝播遅延について知らなかったのでメモ。

VHDLでは、明確に慣性遅延と伝播遅延についての文法があるらしい。Verilogでは結構ややこしい。

まずは例題

以下のようなVerilogを記述した。それぞれinertial_ff, transport_ffの値はどうなる?

module delay_model;
  reg clk;

  reg [ 7: 0] ff;
  reg [ 7: 0] transport_ff, inertial_ff2;
  wire [ 7: 0] inertial_ff;

  always @ (posedge clk) begin
    ff <= $random;
  end
  assign #300 inertial_ff = ff;
  always @ (ff) #300 inertial_ff2 <= ff;
  always @ (ff) transport_ff <= #300 ff;

  initial begin
    clk = 1'b1;
    forever begin
      #(100) clk = ~clk;
    end
  end

  initial begin
    #2000;
    $finish;
  end

endmodule // delay_model

答え

f:id:msyksphinz:20170120001038p:plain

200unitで変化する値を、300unit遅延つけて伝播した場合、inertial_ffは値が飛び飛びに取得され、transport_ffは値が全て遅れて取得された。

慣性遅延(inertial delay)について

慣性遅延、伝播遅延について、文法的に解釈するならば、Veritakの解説が非常に分かりやすい。

  • 6.1 Transport とInertial Delay

http://japanese.sugawara-systems.com/tutorial/verilog/newpage18.htm

先ほどの記述

  always @ (ff) #300 inertial_ff2 <= ff;

300unit経ってからinertial_ff2への代入が実行されると解釈される。このため、ffの値は300unit後の値がinertial_ffに伝播される。

このため、ffが変化してから300unit後の値がinertial_ff2に入っていることが分かる。inertial_ffのほうはなんだかよく分からない値が入ってしまっている。

伝播遅延(transport delay)について

伝播遅延のほうは

  always @ (ff) transport_ff <= #300 ff;

いったんffの値がキューイングされ、300unit後に代入が実行される。このため、300unit後にtranspose_ffに代入されるのは300unit前のffの値となる。

f:id:msyksphinz:20170120002145p:plain

FreeRTOSとZynqの勉強(アドレスマップについて)

そういえばFreeRTOSの勉強をする前に、Zynqについても基本的なアドレスマップについてちゃんと調査したことがなかった。

Zynqのアドレスマップは、Xilinxの資料を調べると以下のようになっていた。

  • Zynq-7000 All Programmable SoC Technical Reference Manual

f:id:msyksphinz:20170119013955p:plain

ARMは基本的に0x0からブートすると聞いているので、0x0000_0000 - 0x0000_FFFF にはオンチップメモリが接続されている。また、DDR-SDRAMが接続されるのは0010_0000 - 3FFF_FFFFとなっている。

とすると、FreeRTOSのメモリマップはどうなっているかというと、前回のDemoプログラムのlscript.ldを開いてみると、

f:id:msyksphinz:20170119014242p:plain

グラフィカルに表示されるなあ。すごい。

基本的にテキスト領域もDDR-SDRAMの領域に貼り付けられているようだ。リセットとか、ブートローダとかはどのようになっているのかちょっと分からないが、一応マッピングは行えている。

FreeRTOSをZedBoardで動作させる(1. チュートリアルを実行する)

以前、FreeRTOSを調査したときは、RISC-Vのターゲットとして調査したものだった。ところが、調査を進めると普通にFreeRTOSはZynqチップにも移植されており、Cortex-A9でも動作させることが出来るらしい。

msyksphinz.hatenablog.com

今回はRTOSの勉強の最初のステップとして、ZedBoardのメインCPUであるARM Cortex-A9上でFreeRTOSを動作させるステップについて調査する。

1. FreeRTOS ソースコードのダウンロード

FreeRTOSのソースコードは、以下の公式ウェブサイトからダウンロードできる。

www.freertos.org

上記のページから、ダウンロードサイトをクリックし、"Click to download the latest official release from SourceForge"をクリックするとSourceForgeのサイトに接続されるのでそれでダウンロードできる。

www.freertos.org

ダウンロードしたらzipファイルを回答する。自己解答形式で圧縮されている。

2. ZC702用のディレクトリをXilinx SDKで開く

提供されているデモディレクトリはZC702用のものだが、ZedBoard向けにも動作させることが出来る。

FreeRTOSv9.0.0\FreeRTOS\Demo\CORTEX_A9_Zynq_ZC702\がそのディレクトリだ。

XilinxSDKを開いて、当該ディレクトリをインポートする。今回は、Xilinx SDK 2016.3を利用した。

f:id:msyksphinz:20170117020107p:plain

Import Projectで、上記のZC702用のディレクトリを指定する。自動的にプロジェクトが認識され、取り込まれる。

f:id:msyksphinz:20170117020029p:plain

Finishをクリックすると、プロジェクトが取り込まれた作業画面になる。

3. ZedBoardにFreeRTOSをダウンロードし、動作させる

まず、TeraTermか何かでシリアルコンソールを用意しておく。ZedBoardに接続されているポートで、転送速度は115200を選択しておく。

f:id:msyksphinz:20170117020610p:plain

Xilinx SDKに戻り、Project ExplorerからRTOSDemoを右クリックし、Debug AsDebug Configurationを選択する。

f:id:msyksphinz:20170117020221p:plain

そのままDebugボタンでデバッグが開始される。いったんmain()の中でブレークポイントにより停止するのだが、それを確認してからResumeを実行する。

f:id:msyksphinz:20170117020436p:plain

TeraTermを確認すると、FreeRTOSが立ち上がったのが確認できた。

f:id:msyksphinz:20170117020813p:plain

ためしにtask-statsなどと入力すると、タスク一覧らしきものが出てきた。これはps aux的なものだろうか?

f:id:msyksphinz:20170117020916p:plain