FPGA開発日記

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

Bitnamiで設定したRedmineをgithubと同期させる

個人の開発を記録するためにRedmineを導入したが、githubとの連携をまだ実現していなかった。

githubとの連携を実現するための情報はたくさんあるが、bitnamiで構築したRedminegithubはどのように連携すればよいのだろうか?調査してみた。

実現したいこととしては、githubに対してpushすると、自動的にRedmineリポジトリもアップデートして欲しい。

GithubRedmineを連携させる仕組み

ここでは、redmine_github_hookというプラグインを使用した。その名のとおりRedmineGithubを連携させるためのプラグインだ。

github.com

CentOS 6.8にインストールした場合は、Rubyのバージョンなどを上げないとうまく行かないようだったが、基本的にマニュアルどおりにインストールすることが出来る。 インストールが完了すると、Redmineを立ち上げて、http://redmineのページ/github_hookにアクセスしてきちんとメッセージが出るかを確認する。

f:id:msyksphinz:20170212025002p:plain

redmine_github_hookの仕組みはこうだ。まずgithubに対してpushが実行されると、webhookを通じて対象Redminegithub_hookに通知が発行される。 すると、Redmineの存在するサーバにgitリポジトリのクローンが作られており、それをアップデートさせる、という仕組みだ。

https://cloud.githubusercontent.com/assets/6480/3311503/3a789390-f6c5-11e3-804d-d5ca2562799f.png

(https://github.com/koppen/redmine_github_hook より画像抜粋)

これらの操作は、詰まるところがRedmineが実施している。

多くのredmine_github_hooksの解説記事では、Redmineサーバ上に配置するgitリポジトリのクローンを、マシン内でgitを引くための専用アカウントや、redmineを実行しているアカウントであるapacheを設定している。 しかし、Bitnamiで設定したRedmineの場合はどうだろうか?

ps aux | grep httpd
daemon    1976  0.0  0.3 394960  1756 ?        S    02:48   0:00 /opt/redmine-3.3.1-0/apache2/bin/httpd.bin -f /opt/redmine-3.3.1-0/apache2/conf/httpd.conf

daemonというユーザが実行している。さらにdaemonについて調べると、

less /etc/passwd
...
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...

ホームディレクトリが/sbinか、、、なかなか難しいな。/sbin/だとsshキーを作ったとしても/sbin/.ssh/を置くのはちょっと厳しいかもしれない。別の場所を作っておこう。ここでは/home/daemon/とした。

less /etc/passwd
...
daemon:x:2:2:daemon:/home/daemon:/sbin/nologin
...

/home/daemon/.sshgithubとの連携を行うためのsshキーを作成し、githubと連携させた。このためには、いったんdaemonユーザに切り替わる必要がある。

sudo -s -u daemon
(daemonユーザにて) ssh-keygen -t rsa

Redmine用サーバにgitリポジトリのミラーをcloneする

Redmineサーバにgitリポジトリのミラーをcloneし、Redmineと同期させる。ここでは、まずいったんdaemonユーザにsuし、リポジトリをクローンした。

sudo -s -u daemon
cd /opt/repos
git clone --mirror git@github.com:msyksphinz/swimmer_iss.git
bash-4.1$ pwd
/opt/repos
bash-4.1$ ls -ltr
total 4
drwxr-xr-x 7 daemon daemon 4096 Feb 12 01:20 swimmer_iss.git

Redmineを設定して、リポジトリを参照させる。

f:id:msyksphinz:20170212031100p:plain

GithubのWebhookに、リポジトリのpushを通知させる

最後に、gitリポジトリにpushがなされた場合、対象のRedmineに通知が発行されるようにしたい。 これにより、Redmineサーバ側のリポジトリがfetchされ、情報がアップデートされる仕組みだ。

githubリポジトリ設定欄から「Webhook」の項目を設定する。http://redmineサーバ名/redmine/github_hook?project_id=xxxというフォーマットで指定する。xxxの部分は、Redmineのプロジェクトの識別子を記入する。

f:id:msyksphinz:20170212031411p:plain

f:id:msyksphinz:20170212031633p:plain

これでRedmineとのリポジトリ連携が完了した。ためしに、githubに向けてpushを通知してみよう。

echo test >> test && git commit -a -m "Func: add test5" && git push origin temp

雑なコミットだな!Redmine側には何か通知が行っているだろうか?Redmineのproduction.logを確認しよう。

Started POST "/redmine/github_hook?project_id=magnetor-1" for 192.30.252.34 at 2017-02-12 01:20:46 +0900
Processing by GithubHookController#index as */*
  Parameters: {"ref"=>"refs/heads/temp", "before"...
...
  Current user: anonymous
  GithubHook: Redmine repository updated: swimmer_iss (Git: 5311.4ms, Redmine: 456.5ms)
Completed 200 OK in 5794ms (Views: 2.0ms | ActiveRecord: 71.8ms)

GithubHookが働いて、リポジトリのアップデートがうまく行ったようだ。

f:id:msyksphinz:20170212032147p:plain

RISC-Vのメモリ仮想化の方式について (v1.9.1編)

自作ISSのテストパタンが通らなくなってきた!

特に仮想メモリを使っているパタンが通らなくなった。大昔に自作ISSに仮想アドレスから物理アドレスの変換を実装していたのだが、どうやら仕様がv.1.9.1で変わっているらしい。 もう一度チェックしてみよう。

ちなみに、前回の解説はこちら。

msyksphinz.hatenablog.com

ここから、いろんな仕様が変更されている。

sptbr(Supervisor Page-Table Base Register)の仕様について

f:id:msyksphinz:20170210005726p:plain

v1.9.1には、sptbrの仕様変更が行われている。これまでは上位ビットにアドレス変換のベースアドレスが格納されていたが、sptbrのベースアドレスは下位(PPN)に移動されている。 Sv32の仕様ならば、PAGESIZE=2^12を乗算してベースアドレスとしている。

ページテーブルエントリの仕様について

ページテーブルの変換では、メモリ上(あるいはTLB)上に存在するアドレス情報を探索していくが、ページテーブルのエントリの仕様も変更されている。

f:id:msyksphinz:20170210010955p:plain

属性ビットがv.1.9.1では増強されている。

  • V=Valid Bit. ページテーブルエントリが有効であることを示す。
  • R=Read Bit. ページが読み込み可能であることを示す。
  • W=Write Bit. ページが書き込み可能であることを示す。
  • X=Executable Bit. ページがフェッチ可能であることを示す。 ちなみに、RWXがどれも1がセットされていない場合は、次のページテーブルへのポインタページとなっている。
  • U=UserMode Bit. ユーザモードのソフトウェアはU=1のページテーブルでないとアクセスできない。
  • G=Global Mapping Bit. グローバルマッピング。すべてのアドレス空間に存在していることを示す。
  • A=Access Bit. アクセス済みビット
  • D=Dirty Bit. 書き込み済みビット

アドレス変換処理の仕様変更について

v.1.7の場合

  1. i=LEVELS-1 (LEVEL=2)からスタートする。つまり、上位の変換ブロックから変換を始める。
  2. va.vpn[i]×PTSIZE(=4)+sptbr のアドレスを計算し(これが変換テーブルのアドレスとなる。つまり、va.vpn[i]をインデックスとして変換テーブルを参照している)、ページテーブルエントリ(=pte)を参照する。
  3. pte.Vビットを確認し、0であれば無効テーブルエントリなのでアドレスエラーを出力する。
  4. pte.type が2以上であれば、テーブルの参照の終端まで行ったことになるので、ステップ5に進む。ここでpte.typeの意味は以下のように定義されているようだ (Privileged Reference Manual参照)。

f:id:msyksphinz:20160115022437p:plain

つまり、Type=0 or 1であれば次のテーブルへの参照となるが、それ以外は最終的なページテーブルとなる。 ここでテーブルのアクセスエラーを確認する。 Type=0,1のときは次のアドレスを計算する。a=pte.ppn×PAGESIZEとし(つまりベースアドレスを更新する)、再度ステップ2に進む。また、i<0となるとこれもテーブル不良のためアドレスエラーとなる。

  1. ステップ5まで来たということは、i>=0の間にページテーブルの終端に到達したことを意味する。ここでpte.typeのアクセス権限を確認し、アクセス権限が足りなければエラーとなる。 そうでなければ、以下の方針でアドレス変換を実行する。

  2. オフセットはそのまま利用する。 pa.pgoff = va.pgoff

  3. i>0(=つまり、インデックスの参照を0まで使い切らない)場合はi-1から0まで(つまり到達しなかったレベル)は、仮想アドレスをそのまま物理アドレスとして利用する。 pa.ppn[i-1:0]=va.vpn[i-1:0]。
  4. それ以外のインデックスはページテーブルを参照して変換する。pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i]

v.1.9.1の場合

  1. i=LEVELS-1 (LEVEL=2)からスタートする。つまり、上位の変換ブロックから変換を始める。
  2. va.vpn[i]×PTSIZE(=4)+sptbr.PPN×PAGESIZE(=212) のアドレスを計算し(これが変換テーブルのアドレスとなる。つまり、va.vpn[i]をインデックスとして変換テーブルを参照している)、ページテーブルエントリ(=pte)を参照する。
  3. pte.Vビット、pte.Rビット、pte.Wビットを確認し、pte.V=0、もしくはpte.R=0&pte.W=1であれば無効テーブルエントリなのでアドレスエラーを出力する。
  4. pte.R=1, pte.X=1であれば、テーブルの参照の終端まで行ったことになるので、ステップ5に進む。そうでなければ、a=pte.ppn×PAGESIZEとして2.に戻る。

  5. ステップ5まで来たということは、i>=0の間にページテーブルの終端に到達したことを意味する。以下の方針でアドレス変換を実行する。

  6. オフセットはそのまま利用する。 pa.pgoff = va.pgoff

  7. i>0(=つまり、インデックスの参照を0まで使い切らない)場合はi-1から0まで(つまり到達しなかったレベル)は、仮想アドレスをそのまま物理アドレスとして利用する。 pa.ppn[i-1:0]=va.vpn[i-1:0]。
  8. それ以外のインデックスはページテーブルを参照して変換する。pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i]

組み込みシステムにおけるアドレスの考え方 (VMA, LMAについて)

ちょっと話が逸れるが、今回は組み込みシステムにおけるVMAとLMAの考え方についてまとめておく。

例えばリンカスクリプトなどを組み上げるとき、「CPUから見たアドレス」と「外部からデータをRAMに配置するときにみる領域」が違うことがある。 例えば、こんな場合はどうだ。まずは0x0000_0000のROMにCPUが実行するべき命令とデータを置き、CPUがまずはそれらの情報を0x3000_0000から始まるRAMにコピーし、そこからフェッチして実行するとしたら? プログラムのアドレスはどこに設定して置けばよいのだろう?

  • マイコンの外部からデータを流し込む時:流し込みアドレスを0x0000_0000から始めたい(外部バスから見た場合、RAMが0x0000_0000にあるので)
  • プログラムをCPUが実行するとき:RAMにコピーされているので0x3000_0000から始まるアドレスで進めたい

f:id:msyksphinz:20170209005337p:plain

  • VMA(Virtual Memory Address) : プログラムが実際に実行するときに参照されるアドレス。通常はリンカスクリプトを記述するときは、このアドレスをベースに記述する。上記の例だと0x3000_0000として配置される。
  • LMA(Load Memory Address) : プログラムをロードするときに参照されるアドレス。外部からプログラムをロードするときに参照されるアドレス。上記の例だと0x0000_0000に相当する。

実際にはどのようなことが起きるのか(RISC-Vプログラムの例)

例えば、RISC-Vのプログラムを考えてみよう。RISC-Vはリセット時のフェッチアドレスが0x8000_0000に設定されている。このため、普通にプログラムをコンパイルしてobjdumpすると、0x8000_0000から配置されるが、 外部にRAMを配置している場合、0x8000_0000(つまり2GB空間から!)のRAMなんてものは存在しない。だいたいは0x8000_0000を先頭にRAMが置かれており、プログラムをロードするバスからは0x0000_0000から見えていたりする。

今自分が作っているRISC-V自作CPUだってそうだ。最初にフェッチリクエストがくるのは0x8000_0000だけれども、そんな高い領域まで存在するRAMは置けないので、0x8000_0000にマップする形で0x0000_0000からRAMを配置している。

riscv_cpu (
 .fetch_addr(w_fetch_addr),
 ...
);

assign ram_addr = w_fetch_addr - 0x8000_0000;   // Offsetを切り落とす。

ram_16k (
 .addr(ram_addr),  // アドレスは8000_0000のアクセスが0x0000_0000になるように
 ...
);

この場合、リンカスクリプトVMA=0x8000_0000, LMA=0x0000_0000を設定してみよう。

SECTIONS
{
        .text.reset 0x80000000 : AT(0x00000000) {
                *(.text.reset)
        }
        .text   0x80001000 : {
                *(.text)
        }
        .rodata : {
                *(.rodata.*) *(.rodata*)
...

AT(...)で記述されているのがLMAだ。これでコンパイルしてみる。objdumpしても、VMAで表記されているのが分かるだろう。

Disassembly of section .text.reset:

80000000 <_start>:
80000000:       00007197                auipc   gp,0x7
80000004:       90818193                addi    gp,gp,-1784 # 80006908 <_gp>
80000008:       0000a117                auipc   sp,0xa
8000000c:       11010113                addi    sp,sp,272 # 8000a118 <_sp>

80000010 <format_regs>:
80000010:       000000b3                add     ra,zero,zero
80000014:       00000233                add     tp,zero,zero
...

ところが、objdump -hで実行してみると、LMAとVMAが異なっていることが分かる。

$ riscv64-unknown-elf-objdump -h coremark.bin

coremark.bin:     file format elf32-littleriscv

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text.reset   000000a4  80000000  00000000  00001000  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .text         00003784  80001000  00001000  00002000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .text.startup 0000078c  80004784  00004784  00005784  2**2
...

下記のエントリを使ってLMAをベースにreadmemh用のファイルを作ってみると、以下のようになる。

sourceware.org

@00000000 110101130000a1179081819300007197   // 0x8000_0000
@00000001 00000333000002b300000233000000b3   // 0x8000_0010
@00000002 00000533000004b300000433000003b3   // 0x8000_0020
@00000003 00000733000006b300000633000005b3   // 0x8000_0030
@00000004 00000933000008b300000833000007b3   // 0x8000_0040
@00000005 00000b3300000ab300000a33000009b3   // 0x8000_0050
@00000006 00000d3300000cb300000c3300000bb3   // 0x8000_0060
@00000007 00000f3300000eb300000e3300000db3   // 0x8000_0070
...

RAMに置かれる場合は0x0000から始まるように配置されるが、CPUからフェッチされるときは0x8000_0000になっている。絶対アドレスのジャンプとかは、0x8000_0000をベースにして計算されるようになっている。

Intelの2.5D FPGA @ ISSCC

Intelがチップ間2.5D通信を採用したFPGAISSCCで発表した。

www.eetimes.com

まだ詳細は出て来ていないが、基本技術はEMIB(Embedded Multi-die Interconnect Bridge)というものらしい。

【後藤弘茂のWeekly海外ニュース】超広帯域メモリの採用を可能にするIntelの新パッケージング技術「EMIB」 - PC Watch

チップ間を2.5D接続で実現するためには、一般的にはインターポーザーを介して接続する必要がある。しかしインターポーザーでの接続は一般的には難しく、 HBMなどの2.5D接続技術がなかなか安定して供給できないのは、インターポーザーの設計が難しいとも言われている(らしい)。

www.4gamer.net

Intelの技術は、インターポーザーを使わずにパッケージの上位層を使ってダイ同士を接続するものだ。

f:id:msyksphinz:20170208213310p:plain

(http://www.eetimes.com/document.asp?doc_id=1331317&image_number=1 の画像より抜粋)

ISSCCの記事によると、ダイ同士を24個のトランシーバーで接続し、1ピンあたり2Gbit/sで動作、ビットあたり1.2pJの電力効率ということらしい。 4つの28GHzのSERDESを積んでいるようだ。

RISC-V. Open Hardware for Your Open Source Software @ FOSDEM17

FOSDEM17 というイベントでRISC-Vについての講演があるらしい。

FOSDEMというイベントはこれまで知らなかったのだが、"FOSDEM is a free event for software developers to meet, share ideas and collaborate"ということで、 主にオープンソースツールについての発表やイベントが行われる場ということらしい。

f:id:msyksphinz:20170206003513p:plain

FOSDEM 2017 - Home

2/5のアーキテクチャのセッションとしてRISC-Vについての講演が行われるようだ。ビデオも公開されたら見てみたい。

FOSDEM 2017 - Architectures

ちなみに、発表の概要については発表前にArun Thomasのインタビューがあるので、大体そこで何を言いたいのかが分かる。 今回は学界的な発表ではなくて、招待講演のような、ざっくりとした概要をしゃべるものと思われるね。

https://fosdem.org/2017/interviews/arun-thomas/

RISC-VのSpike-ISSを使ったFreeRTOSの起動ルーティンの解析(3)

f:id:msyksphinz:20170201002310j:plain

前回の続きで、ROMにはさらにいくつかの情報を挿入しなければならない。0x1020から挿入しなければならないのが、以下のRISC-Vのコンフィグレーションに関する情報だ。

前回の記事で紹介した以下のものになる。

platform {
  vendor ucb;
  arch spike;
};
rtc {
  addr 0x40000000;
};
ram {
  0 {
    addr 0x80000000;
    size 0xaef00000;
  };
};
core {
  0 {
    0 {
      isa rv64imafdc;
      timecmp 0x40000008;
      ipi 0x40001000;
    };
  };
};

これをまずは文字列として定義し、それをROMの初期値としてコピーする。

github.com

  std::stringstream s;
  s << std::hex <<
      "platform {\n" \
      "  vendor ucb;\n" \
      "  arch spike;\n" \
      "};\n" \
      "rtc {\n" \
      "  addr 0x40000000;\n" \
      "};\n" \
      "ram {\n" \
      "  0 {\n" \
      "    addr 0x80000000;\n" \
      "    size 0xaef00000;\n" \
      "  };\n" \
      "};\n" \
      "core {\n" \
      "  0 {\n" \
      "    0 {\n" \
      "      isa rv64ima;\n" \
      "      timecmp 0x40000008;\n" \
      "      ipi 0x40001000;\n" \
      "    };\n" \
      "  };\n" \
      "};\n";

  std::string config_string = s.str();
  memcpy (&m_memory[0x20], config_string.c_str(), config_string.size());

最後にmemcpyをして、ROMの当該位置に値を挿入した。

結構やっつけだが、これでROMについては完成。

さらにSpike-ISSとの不一致部分について調査していたのだが、ここから先はタイマ割り込みに入ってしまった。。。さてどうやって実現しようか。

RISC-VのSpike-ISSを使ったFreeRTOSの起動ルーティン解析(2)

f:id:msyksphinz:20170201002310j:plain

前回の続き。Spike-ISSには通常のDRAM以外に別のメモリが貼り付けられていることが分かった。

今回焦点を当てたいのは0x1000に貼り付けられているROMらしきものだ。sim.ccには、以下のような記述でROMが生成されていた。

  uint32_t reset_vec[8] = {
    0x297 + DRAM_BASE - DEFAULT_RSTVEC, // reset vector
    0x00028067,                         //   jump straight to DRAM_BASE
    0x00000000,                         // reserved
    0,                                  // config string pointer
    0, 0, 0, 0                          // trap vector
  };
  reset_vec[3] = DEFAULT_RSTVEC + sizeof(reset_vec); // config string pointer

  config_string = s.str();

  rom.insert(rom.end(), config_string.begin(), config_string.end());
  rom.resize((rom.size() / align + 1) * align);

  boot_rom.reset(new rom_device_t(rom));
  bus.add_device(DEFAULT_RSTVEC, boot_rom.get());

config_string--dump-config-stringで出てくるアレだ。

platform {
  vendor ucb;
  arch spike;
};
rtc {
  addr 0x40000000;
};
ram {
  0 {
    addr 0x80000000;
    size 0xaef00000;
  };
};
core {
  0 {
    0 {
      isa rv64imafdc;
      timecmp 0x40000008;
      ipi 0x40001000;
    };
  };
};

ただし、その前に4byte x8=32byteほど、特殊な情報が貼り付けられている。reset_vecで定義されているところだ。 ダンプしてみると以下のようになる。

7ffff29f
00028067
00000000
00001020
00000000
00000000
00000000
00000000

なるほど、0x100cを参照したときに返された値はこの0x1020だったのか。これは分からない。。。

という訳でROM領域を急遽実装した。

github.com

  • module_rom.cpp
MemResult ModuleRom::LoadData (Addr_t addr, Size_t size, Word_t *data)
{
  memcpy (data, &m_memory[addr], size);
  m_env->DebugPrint ("<ROM: Load (%08x)=>%08x, %d\n", addr, *data, size);
  m_env->GetTrace()->RecordTraceMemRead (addr, *data, Size_Word);

  return MemResult::MemNoExcept;
}

ModuleRom::ModuleRom (EnvBase *env, FILE *dbgfp)
{
  m_env = env;

  uint32_t init[8] = {0x7ffff29f,
                      0x00028067,
                      0x00000000,
                      0x00001020,
                      0x00000000,
                      0x00000000,
                      0x00000000,
                      0x00000000};
  for (int i = 0; i < 8; i++) {
    memcpy (&m_memory[i*4], &init[i], 4);
  }
}

とりあえず問題の場所は解決しt。次にまだSpikeと一致しないところがあるから直さないと。。。