「車輪の再実装」って言葉が好き(実践はできてない)

git bisectを用いてsyscallテーブルの表記変更コミットを探す

linuxシステムコールを追加しようと思ったりすると、 arch/x86/entry/syscalls/syscall_64.tbl にsys*と書くと思っていたのですが、最近は__x64_sys*という表記に変わっていることに気づきました。

例えばLinux v4.15ではシステムコールテーブルは以下の様になっています。

# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The abi is "common", "64" or "x32" for this file.
#
0   common  read            sys_read
1   common  write           sys_write
2   common  open            sys_open
3   common  close           sys_close
4   common  stat            sys_newstat
5   common  fstat           sys_newfstat
6   common  lstat           sys_newlstat
7   common  poll            sys_poll
8   common  lseek           sys_lseek
9   common  mmap            sys_mmap
10  common  mprotect        sys_mprotect

ただ最近(v5.0)このテーブルを見直したら以下のように変更されていました。

#
# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls
#
# The abi is "common", "64" or "x32" for this file.
#
0   common  read            __x64_sys_read
1   common  write           __x64_sys_write
2   common  open            __x64_sys_open
3   common  close           __x64_sys_close
4   common  stat            __x64_sys_newstat
5   common  fstat           __x64_sys_newfstat
6   common  lstat           __x64_sys_newlstat
7   common  poll            __x64_sys_poll
8   common  lseek           __x64_sys_lseek
9   common  mmap            __x64_sys_mmap
10  common  mprotect        __x64_sys_mprotect

git bisect というコマンドを思い出し、良い練習問題だと思って調べてみることにしました。

manのドキュメントの言葉を借りると、git bisectは

git-bisect - Use binary search to find the commit that introduced a bug

すなわち、ある段階まで動いていたコードにどの段階でバグが入り込んだかを調べる機能です。

テストスクリプトを走らせて*12分探索で該当コミットを探してくれるので、一つずつさかのぼって確認するのと比べ、高速で探索が可能です。 今回はバグではありませんが、以下のテストスクリプト(test.sh)を使い、__x64という表記が混入する場所を探ることにしました。

#!/bin/sh
set -eu

egrep "\ssys_open" arch/x86/entry/syscalls/syscall_64.tbl

git bisect ではまず、該当コミットがある範囲を決めます。

git bisect run <bad/new-commit> <good/old-commit>

今回、v5.0ではx64_sys*という表記となり、v4.15ではsys*という表記であることがわかっているので、以下のように実行しました。

git bisect start v5.0 v4.15

Bisecting: 44096 revisions left to test after this (roughly 16 steps)
[93b9bcdf9fbcb683d4e8c44ee8cec0989053d4de] btrfs: remove unused parameter from btrfs_parse_subvol_options

「だいたい16ステップで終わるよ」と教えてくれるのは便利。

次に、実際に探索を行いたいと思います。

git bisect run ./test.sh
running ./test.sh
Bisecting: 22106 revisions left to test after this (roughly 15 steps)
[38047d5c269bbdedf900fc86954913f3dffa01f1] Merge tag 'driver-core-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 10700 revisions left to test after this (roughly 14 steps)
[135c5504a600ff9b06e321694fbcac78a9530cd4] Merge tag 'drm-next-2018-06-06-1' of git://anongit.freedesktop.org/drm/drm
running ./test.sh
Bisecting: 5696 revisions left to test after this (roughly 13 steps)
[a72db42cee37a43f8a40e1f47358ac86921ad8e4] Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
running ./test.sh
Bisecting: 2942 revisions left to test after this (roughly 12 steps)
[28da7be5ebc096ada5e6bc526c623bdd8c47800a] Merge tag 'mailbox-v4.17' of git://git.linaro.org/landing-teams/working/fujitsu/integration
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 1471 revisions left to test after this (roughly 11 steps)
[5504ed29692faad06ea74c4275e96a8ffc83a1e1] mm/hmm: do not differentiate between empty entry or missing directory
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 733 revisions left to test after this (roughly 10 steps)
[80a17a5f501ea048d86f81d629c94062b76610d4] Merge tag 'apparmor-pr-2018-04-10' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 373 revisions left to test after this (roughly 9 steps)
[ba2b137d10bafc3cc514e52172b549e64a5402fb] Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 193 revisions left to test after this (roughly 8 steps)
[f0d98d85831bf1a3b1f56f8c14af60797aaca536] Merge tag 'scsi-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi
running ./test.sh
Bisecting: 71 revisions left to test after this (roughly 7 steps)
[174e719439b8224d7cedfbdd9529de396cac01ff] Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 30 revisions left to test after this (roughly 5 steps)
[9fb71c2f230df44bdd237e9a4457849a3909017d] Merge branch 'x86-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
running ./test.sh
Bisecting: 18 revisions left to test after this (roughly 4 steps)
[6b0a02e86c293c32a50d49b33a1f04420585d40b] Merge branch 'x86-pti-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 9 revisions left to test after this (roughly 3 steps)
[5ac9efa3c50d7caff9f3933bb8a3ad1139d92d92] syscalls/core, syscalls/x86: Clean up compat syscall stub naming convention
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[d94a155c59c98c19b98ee949eaab6a0312bbd6be] x86/cpu: Prevent cpuinfo_x86::x86_phys_bits adjustment corruption
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 2 revisions left to test after this (roughly 1 step)
[c76fc98260751e71c884dc1a18a07e427ef033b5] syscalls/x86: Adapt syscall_wrapper.h to the new syscall stub naming convention
running ./test.sh
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[d5a00528b58cdb2c71206e18bd021e34c4eab878] syscalls/core, syscalls/x86: Rename struct pt_regs-based sys_*() to __x64_sys_*()
running ./test.sh
d5a00528b58cdb2c71206e18bd021e34c4eab878 is the first bad commit
commit d5a00528b58cdb2c71206e18bd021e34c4eab878
Author: Dominik Brodowski <linux@dominikbrodowski.net>
Date:   Mon Apr 9 12:51:44 2018 +0200

    syscalls/core, syscalls/x86: Rename struct pt_regs-based sys_*() to __x64_sys_*()
    
    This rename allows us to have a coherent syscall stub naming convention on
    64-bit x86 (0xffffffff prefix removed):
    
     810f0af0 t            kernel_waitid    # common (32/64) kernel helper
    
     <inline>            __do_sys_waitid    # inlined helper doing actual work
     810f0be0 t          __se_sys_waitid    # C func calling inlined helper
    
     <inline>     __do_compat_sys_waitid    # inlined helper doing actual work
     810f0d80 t   __se_compat_sys_waitid    # compat C func calling inlined helper
    
     810f2080 T         __x64_sys_waitid    # x64 64-bit-ptregs -> C stub
     810f20b0 T        __ia32_sys_waitid    # ia32 32-bit-ptregs -> C stub[*]
     810f2470 T __ia32_compat_sys_waitid    # ia32 32-bit-ptregs -> compat C stub
     810f2490 T  __x32_compat_sys_waitid    # x32 64-bit-ptregs -> compat C stub
    
        [*] This stub is unused, as the syscall table links
            __ia32_compat_sys_waitid instead of __ia32_sys_waitid as we need
            a compat variant here.
    
    Suggested-by: Ingo Molnar <mingo@kernel.org>
    Signed-off-by: Dominik Brodowski <linux@dominikbrodowski.net>
    Cc: Al Viro <viro@zeniv.linux.org.uk>
    Cc: Andrew Morton <akpm@linux-foundation.org>
    Cc: Andy Lutomirski <luto@kernel.org>
    Cc: Borislav Petkov <bp@alien8.de>
    Cc: Brian Gerst <brgerst@gmail.com>
    Cc: Denys Vlasenko <dvlasenk@redhat.com>
    Cc: Josh Poimboeuf <jpoimboe@redhat.com>
    Cc: Linus Torvalds <torvalds@linux-foundation.org>
    Cc: Peter Zijlstra <peterz@infradead.org>
    Cc: Thomas Gleixner <tglx@linutronix.de>
    Link: http://lkml.kernel.org/r/20180409105145.5364-4-linux@dominikbrodowski.net
    Signed-off-by: Ingo Molnar <mingo@kernel.org>

:040000 040000 3f963b85ebd00a03e36f36f6417684a64da0b8dc e83d401c538f34cb271d6c0ae3c8563f43db6a5f M  arch
bisect run success

うまく行きました。

これで、ちょうど”バグ”が入り込んだコミット。 すなわちここでは_x64_sys*という表記に変わったコミットにチェックアウトした状態となっています。 git diff HEAD~とすると

前略
-0      common  read                    sys_read
-1      common  write                   sys_write
-2      common  open                    sys_open
-3      common  close                   sys_close
-4      common  stat                    sys_newstat
-5      common  fstat                   sys_newfstat
中略
+0      common  read                    __x64_sys_read
+1      common  write                   __x64_sys_write
+2      common  open                    __x64_sys_open
+3      common  close                   __x64_sys_close
+4      common  stat                    __x64_sys_newstat
+5      common  fstat                   __x64_sys_newfstat
後略

となっていたので、このコミット(d5a00528b58cdb2c71206e18bd021e34c4eab878)で間違いなさそうです。

さて、このコミットがどのバージョンかですが、 git merge-base --is-ancestor v4.16 HEAD に成功して、git merge-base --is-ancestor v4.17 HEADに失敗したのでv4.17から変更が入っていそうです。

おわり

*1:このコミットは良い、悪いをスクリプトではなく手動で指定することもできそうです

カーネルモジュールのロードに失敗した話(insmod が Operation not permittedでコケる)。

結論

セキュアブート環境下*1では、カーネルモジュールに署名をつけましょう

経緯

卒論の関係でカーネルモジュールを書いていて、「さて実機で動かすぞ!」という段階でコケました。 自分が悪いコードを書いたせいかと思い、よく転がっていそうな単純なモジュールを書いても以下のようにコケる...

$ sudo insmod hello.ko
 insmod: ERROR: could not insert module hello.ko: Operation not permitted

解決策を調べると、insmodでうまくいかないなら、depmodしてmodprobeを使うといいとか書いてあって試しても、うまく行かない...

最終的には How to sign things for Secure Boot に書いてあるとおり、署名をつけたら成功しました。

(結局カーネルモジュールは動いたものの、思うような結果が得られず卒論には使えなかった...)

*1:厳密な条件は違いそう

gccで本当に使われているデフォルトリンカスクリプトを手に入れる。

結論

ld --verboseではなく、gcc -Wl,--verboseを使いましょう。

環境

$ gcc --version
gcc (Ubuntu 8.2.0-7ubuntu1) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ ld --version                 
GNU ld (GNU Binutils for Ubuntu) 2.31.1
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) a later version.
This program has absolutely no warranty.

$ ld --verbose

というようなコマンドを実行するとデフォルトリンカスクリプトが手に入る。というような記述をしばしば見かけます。(リンカ・ローダ実践開発テクニックとか)

このスクリプトを指定して生成した実行バイナリと、リンカスクリプトを指定しないで生成した実行バイナリは等しいものになると思いますよね?

という訳で、得られたリンカスクリプト*1 (default.ldsとします。)を用いて、以下を実行します。

$ gcc -o hoge hoge.c
$ gcc -o hoge_with_lds hoge.c -T default.lds
$ diff hoge hoge_with_lds 
Binary files hoge and hoge_with_lds differ 

実行結果が期待通りにならない...

恐らくld --verboseで得たリンカスクリプトが違うんだろう... *2

ではどうすれば実際に使われているスクリプトを得られるかというと、コンパイル時に-Wlオプションを使って、直接リンカに--verboseオプションを渡すとうまくいきます。

$ gcc  -Wl,--verbose

これで得られたスクリプトを用いて再度コンパイル&差分を確認すると、無事差分がなくなりました。

ちなみに実際のスクリプトの差を確認したら以下のようになりました。

$ diff default.lds default_gcc.lds
1c1
< /* Script for -z combreloc -z separate-code: combine and sort reloc sections with separate code segment */
---
> /* Script for -pie -z combreloc -z now -z relro -z separate-code: position independent executable, combine & sort relocs with separate code segment */
14c14
<   PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
---
>   PROVIDE (__executable_start = SEGMENT_START("text-segment", 0)); . = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
155,157c155,156
<   .got            : { *(.got) *(.igot) }
<   . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
<   .got.plt        : { *(.got.plt)  *(.igot.plt) }
---
>   .got            : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) }
>   . = DATA_SEGMENT_RELRO_END (0, .);
240a240
> 

どうやら オプションごとにデフォルトリンカスクリプトは異なるらしい。

gccコマンドを使用して入手した方は -pie -z combreloc ... 向けみたいですね。

そういえば、ある時から(確かUbuntuでは)位置独立実行形式(PIE)がデフォルトになったと聞いた気がするな...

*1:実際の ld --verbose の出力はメタ情報等を含むのでいい感じに整形してください

*2:gccは内部?で色々なコマンドを実行しているので、この情報だけでは ld --verbose で得たリンカスクリプトが間違っていることが原因とは断定できない気はします

RISC-Vについて(CPU実験その2)

この記事はIS17er Advent Calendar 2017 - Adventarの3日目の記事として書かれました。 「日本語で書かれたRISC-V命令一覧が欲しいな」と思ったのがこの記事を書いたモチベーションなので、基本的にはCPU実験の班員向けに書いたRISC-Vの基本命令RV32Iのまとめの流用です。 おもしろみのない記事になってしまいました...

アーキテクチャを選ぶ

CPU実験では自分たちで作るプロセッサ及びコンパイラが対応するISA*1も自分たちで策定します。
課題であるレイトレーシング用プログラムmin-rtがOCamlのサブセットであるmin-camlで書かれているので、既存のmin-camlコンパイラが対応するPowerPCSPARCを参考にする班*2や、コピュータアーキテクチャの名著であるパタヘネ本で引用されているMIPSアーキテクチャをベースとする班が多いようですが 、 僕の所属するIS17er第1班では主に僕の独断と偏見でRISC-VというアーキテクチャをベースとしたISAを使用することにしました。

なぜRISC-Vなのか

RISC-VはRISCという言葉の生みの親でもありパタヘネ本の著者でもあるパターソン博士の属するUCバークレー発ののRISCアーキテクチャです。

などの理由でRISC-Vアーキテクチャを採用しました。

RISC-Vの基本構成

今回の実験では32bitの基本命令及び単精度FPU命令からなるRV32IFをベースにしました。

RV32Iの各命令について

RV32Iの命令エンコーディングは下記表に示す5つのタイプに分けられます。
f:id:progrunner17:20171203211238p:plain

上記の様に即値が複雑になっているのは、できるだけ同じビット位置に同じデータを入れることでハードウェアが簡単になるからだそうです。*5

命令はopcodeによって処理を行う演算器が指定され、さらにfunct3、及びfunct7によって細かい演算が指定されます。
例えばadd命令はオペコード 0110011でALUを用いてrs1とrs2の演算が行われることを指定し、funct3:000で ALUに加減演算を指定し、funct7で減算と区別されるという感じです。

RISC-V 命令一覧(算術論理演算まで)

短時間で書いたので抜けが多くミスもあると思います。 あくまで公式の仕様であるSpecifications - RISC-V Foundationを読む参考にしていただけたらと思います。

lui

  • オペコード 0110111
  • funct3: 即値として使用されるためなし
  • 命令形式: lui rd, imm
  • 命令意味: load upper immediate
  • 疑似コード rd = imm << 12,pc++
  • 即値タイプ: U

レジスタの上位20bitに即値を入れ、下位12bitは0埋め。 oriやaddiと組み合わせて任意の32bitの即値を得るのに使える。

auipc

  • オペコード 0010111
  • funct3: 即値として使用されるためなし
  • 命令形式: auipc rd, imm
  • 命令意味: add upper immediate to pc
  • 疑似コード rd = pc + (imm<<12),pc++
  • 即値タイプ: U

luiの結果をpcに足した値を得る。jalやbranch系命令では即値の範囲が狭いので、遠くへ相対ジャンプをしたい時に使える。

jal

  • オペコード 1101111
  • funct3: 即値として使用されるためなし
  • 命令形式: jal rd, imm
  • 命令意味: jump and link
  • 疑似コード rd = pc + 1 ,pc += imm
  • 即値タイプ: J

専用のリンクレジスタがないので、rdに指定したレジスタに戻りアドレスを入れてPC相対ジャンプをする。 基本的にx1が使われることがRISC-Vのspecの推奨らしい。戻りアドレスが不要な場合はx0を使うとよい

jalr

  • オペコード 1100111
  • funct3: 000
  • 命令形式: jalr rd, rs1, imm
  • 命令意味: jump and link register
  • 疑似コード rd = pc + 1 ,pc = rs1 + imm
  • 即値タイプ: I

rs1で指定したレジスタの示すアドレスへジャンプする。 luiやaddi、で即値を生成して絶対ジャンプや、auipcの結果を用いて相対ジャンプする際に使える。

BRANCH 系命令

  • オペコード 1100011
  • 即値タイプ: B

beq

  • funct3: 000
  • 命令形式: beq rs1, rs2, pc + imm
  • 命令意味: branch equal
  • 疑似コード if(rs1 == rs2)then pc += imm else pc++

bne

  • funct3: 001
  • 命令形式: bne rs1, rs2, pc + imm
  • 命令意味: branch not equal
  • 疑似コード if(rs1 != rs2)then pc += imm else pc++

blt

  • funct3: 100
  • 命令形式: blt rs1, rs2, pc + imm
  • 命令意味: branch less than
  • 疑似コード if(rs1 < rs2) then pc += imm else pc++

bge

  • funct3: 101
  • 命令形式: bge rs1, rs2, pc + imm
  • 命令意味: branch greater equal
  • 疑似コード if(rs1 >= rs2)then pc += imm else pc++

bltu

  • funct3: 110
  • 命令形式: bltu rs1, rs2, pc + imm
  • 命令意味: branch less than unsigned
  • 疑似コード if(rs1 < rs2) then pc += imm else pc++

bgeu

  • funct3: 111
  • 命令形式: bgeu rs1, rs2, pc + imm
  • 命令意味:branch greater equal unsigned
  • 疑似コード if(rs1 >= rs2)then pc += imm else pc++

LOAD系命令

  • オペコード 0000011
  • 即値タイプ: I

lb

  • funct3: 000
  • 命令形式: lb rd, offset(rs1)
  • 命令意味: load byte
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある1バイトを符号拡張した値をrdで指定したレジスタにセットする.

lh

  • funct3: 001
  • 命令形式: lh rd, offset(rs1)
  • 命令意味: load half word
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある2バイトを符号拡張した値をrdで指定したレジスタにセットする.

lw

  • funct3: 010
  • 命令形式: lw rd, offset(rs1)
  • 命令意味: load word
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある1ワードを符号拡張した値をrdで指定したレジスタにセットする.

lbu

  • funct3: 100
  • 命令形式: lbu rd, offset(rs1)
  • 命令意味: load byte unsigned
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある1バイトをゼロ拡張した値をrdで指定したレジスタにセットする.

lhu

  • funct3: 101
  • 命令形式: lhu rd, offset(rs1)
  • 命令意味: load half word unsigned
  • 疑似コード rd = mem[rs1+offset]

メモリのrs1+offset番地にある2バイトをゼロ拡張した値をrdで指定したレジスタにセットする.

STORE系命令

  • オペコード 0100011
  • 即値タイプ: S

sb

  • funct3: 000
  • 命令形式: sb rs2, offset(rs1)
  • 命令意味: store byte
  • 疑似コード mem[rs1+offset] = rs2,pc++

メモリのrs1+offset番地にLSBをセットする。

sh

  • funct3: 001
  • 命令形式: sh rs2, offset(rs1)
  • 命令意味: store half word
  • 疑似コード mem[rs1+offset] = rs2,pc++

メモリのrs1+offset番地に下位2バイトをセットする

sw

  • funct3: 010
  • 命令形式: sw rs2, offset(rs1)
  • 命令意味: store word
  • 疑似コード mem[rs1+offset] = rs2 ,pc++

メモリのrs1+offset番地に1wordをセットする。

OP-IMM系命令(即値算術論理演算)

  • オペコード 0010011
  • 即値タイプ: I

addi

  • funct3: 000
  • 命令形式: addi rd, rs1, imm
  • 命令意味: add immediate
  • 疑似コード rd = rs1 + imm ,pc++

即値を負の値に設定することでsubiも兼ねる。

slti

  • funct3: 010
  • 命令形式: slti rd, rs1, imm
  • 命令意味: set less than imm
  • 疑似コード rd = (rs1 < imm) ? 1 : 0 ,pc++

sltiu

  • funct3: 011
  • 命令形式: sltiu rd, rs1, imm
  • 命令意味: set less than unsigned imm
  • 疑似コード rd = (rs1 < imm) ? 1 : 0 ,pc++

xori

  • funct3: 100
  • 命令形式: xori rd, rs1, imm
  • 命令意味: xor immediate
  • 疑似コード rd = rs1 ^ imm ,pc++

ori

  • funct3: 110
  • 命令形式: ori rd, rs1, imm
  • 命令意味: or immediate
  • 疑似コード rd = rs1 |imm ,pc++

andi

  • funct3: 111
  • 命令形式: andi rd, rs1, imm
  • 命令意味: and immediate
  • 疑似コード rd = rs1 & imm ,pc++

slli

  • funct3: 001
  • 命令形式: slli rd, rs1, imm
  • 命令意味: shift left logical imm
  • 疑似コード rd = rs1 << imm ,pc++
  • 即値タイプ: I(5bit)

srli

  • funct3: 101
  • 命令形式: srli rd, rs1, imm
  • 命令意味: shift rifht logical imm
  • 疑似コード rd = rs1 >> imm ,pc++
  • 即値タイプ: I(5bit)

srai

  • funct3: 同上
    • funct7: 0100000
  • 命令形式: srai rd, rs1, imm
  • 命令意味: shift right arithmetic imm
  • 疑似コード rd = rs1 >>> imm ,pc++
  • 即値タイプ: I(5bit)

OP系命令

  • オペコード 0110011
  • 即値タイプ: R

add

  • funct3: 000
  • 命令形式: add rd, rs1, rs2
  • 命令意味: add
  • 疑似コード rd = rs1 + rs2 ,pc++

sub

  • funct3: 同上
    • funct7: 0100000
  • 命令形式: sub rd, rs1, rs2
  • 命令意味: sub
  • 疑似コード rd = rs1 - rs2 ,pc++

sll

  • funct3: 001
  • 命令形式: sll rd, rs1, rs2
  • 命令意味: shift left logical
  • 疑似コード rd = rs1 << rs2 ,pc++

slt

  • funct3: 010
  • 命令形式: slt rd, rs1, rs2
  • 命令意味: set less than
  • 疑似コード rd = (rs1 < rs2) ? 1:0 ,pc++

sltu

  • funct3: 011
  • 命令形式: sltu rd, rs1, rs2
  • 命令意味: set less than unsigned
  • 疑似コード rd = (rs1 < rs2) ? 1:0 ,pc++

xor

  • funct3: 100
  • 命令形式: xor rd, rs1, rs2
  • 命令意味: xor
  • 疑似コード rd = rs1 ^ rs2 ,pc++

srl

  • funct3: 101
  • 命令形式: srl rd, rs1, rs2
  • 命令意味: shift right logical
  • 疑似コード rd = rs1 >> rs2 ,pc++

sra

  • funct3: 同上
    • funct7: 0100000
  • 命令形式: sra rd, rs1, rs2
  • 命令意味: shift right arithmetic
  • 疑似コード rd = rs1 >>> rs2 ,pc++

or

  • funct3: 110
  • 命令形式: or rd, rs1, rs2
  • 命令意味: or
  • 疑似コード rd = rs1 |rs2 ,pc++

and

  • funct3: 111
  • 命令形式: and rd, rs1, rs2
  • 命令意味: and
  • 疑似コード rd = rs1 & rs2 ,pc++

最後に

今回も適当に書いてしまって反省...
時間ができたらまた詳しく書き直そうかと思います。

*1:Instruction Set Architecture: CPUが扱う命令のニーモニックや、エンコーディング、動作等

*2:min-camlはx86にも対応していますが、x86CISCでありハードウェアが複雑になるので採用する班は少ないようです。中にはx86コンパイラOCamlフルスクラッチしている同期もいますが(笑).

*3:オープンソースが優れているというより、僕がオープンソースが好きなので

*4:x0は0にハードワイヤード

*5:簡単の意味の名言は避けさせてもらいます。

Vivado入門1(CPU実験その1)

この記事は IS17er Advent Calendar 2017 - Adventarの1日目の記事として書かれました。  

CPU実験とは

東大理学部情報科学科では名物講義のひとつにCPU実験というものがあります。

CPU実験の説明は歴代の先輩方が様々説明を書いてくださっているので説明は割愛します。

Redirecting…
CPU実験2016年度D班コア係(CPU実験でマルチコア) - sueki743's blog
adventar.org

なぜVivado入門なのか

CPU実験ではFPGAを使ってCPUを作ります。

その際に用いるボードが昨年度からXilinx社製のものに更新され 、その統合開発環境であるVivadoに悩まされたので、本記事を書くことにしました。

各種バージョン

  • IDE :Vivado v2016.4(64bit) Ubuntu

  • ボード: Xilinx社製Kintex Ultrascale(KCU105)

 

今回やること

LEDをチカチカさせよう!!

  • Vivadoプロジェクトの作成
  • IPインテグレーター(ブロックデザイン)及びIPコアの使用*1
  • クロックの生成
  • LEDの使用*2
  • 合成(シンセサイズ)、配置配線(インプリメンテーション)、及びビットストリームの生成*3
  • FPGAボードのコンフィギュレーション*4

Vivadoプロジェクトの作成

f:id:progrunner17:20171201223735p:plain

 Vivadoを起動すると以下のようなウィンドウが表示されるので、
まずはプロジェクトを作成します。

f:id:progrunner17:20171201155144p:plain プロジェクト名とプロジェクトの保存場所を指定します.
この時 Create project subdirectoryのチェックを外すと指定したフォルダにVivadoが生成する大量のファイルが広がることになるので、外さないことを推奨します。

f:id:progrunner17:20171201223658p:plain

プロジェクトの種類を指定します。
ここでは基本的なプロジェクトであるRTL Projectを選択してください。 Do not specify sources at this timeのチェックを外すと使用するソースファイルを指定できますが、後から指定できるのでチェックをつけたままにしてください。

f:id:progrunner17:20171201223714p:plain
Boardsタブから使用するFPGAボードを指定します。 *5

これで設定は終わりです。最後に確認ウィンドウが表示されるのでfinishを選択するとプロジェクトが生成されます。

f:id:progrunner17:20171201224058p:plain

プロジェクトができました。

IPインテグレーター(ブロックデザイン)の使用

f:id:progrunner17:20171201224118p:plain
Vivadoには様々な機能がありますが、ウィンドウ左側に表示されているFlow Navigatorに表示されているコマンドを上から下に実行して行くことがプロジェクトの基本的な流れになります。

まずはじめに、Create Block Design*6を実行してください。 f:id:progrunner17:20171201225436p:plain
プロジェクト名は基本的にデフォルトで大丈夫なのですが、今回はtest_block_designと名付けました。

f:id:progrunner17:20171201225700p:plain

こんな感じでブロックデザインが生成されます。

クロックの生成

f:id:progrunner17:20171201225910p:plain
画面中央にあるboard タブからsystem differential clockのいづれかをドラック&ドロップでブロックデザインに追加します。*7

f:id:progrunner17:20171201230117p:plain

これでクロックを生成するIPコア(Clocking Wizard)が生成されました
クロック周波数はデフォルトで100MHzに設定されています。

IPコアの使用

さて、今回の目標であるLチカのためにはクロックの周波数は速すぎて人の目に追えません。
そこでバイナリカウンターを用いていい感じに遅い周波数を取り出したいと思います。
verilog等で直接書いてもいいですが、今回はXilinxが提供しているモジュール(IPコア)を使用したいと思います。

f:id:progrunner17:20171201230321p:plain

IPコアは右クリックかブロックデザインサイドバーから追加できます。 検索ウィンドウにbinaryと入力すればバイナリカウンターが見つかると思うので追加してください。

f:id:progrunner17:20171201231045p:plain

追加できたら上記のようにドラッグしてバイナリカウンターにクロックをつなげてください。

f:id:progrunner17:20171201231310p:plain デフォルトでは0~216-1までカウントできますが、これでもまだ速すぎるのでバイナリカウンターをクリックして設定ウィンドウ開いてIPコアに変更を加えます。

f:id:progrunner17:20171201231544p:plain とりあえず28bit分くらい使えば228/100MHz > 1Hzとなりいい感じでチカチカが見えると思います。

LEDの使用(物理制約及びポートの使用)

それではLEDに繋がるポートを追加します。
ブロックデザイン上で右クリックしてポートの追加を選択してください. f:id:progrunner17:20171201231758p:plain

今回使用するKCU105のボードにはユーザー用に8個のLEDがあるので上記のように設定してください。

f:id:progrunner17:20171201231904p:plain

led用のポートが生成されました。 ただ、このままではバイナリカウンターの出力が28bitに対しledの入力が8bitなのでsliceと呼ばれるIPコアを追加して出力の一部を取り出します。

f:id:progrunner17:20171201232057p:plain

上記のように設定してください。

f:id:progrunner17:20171201232243p:plain

こんな感じに各モジュールを繋いでブロックデザイン上部にあるrun connection automationを実行してブロックデザインでの基本的な作業は終了です 。

ソースファイル(制約)の追加

先ほどブロックデザインに追加したledポートですがこのままでは実際のLEDに紐付けされていないので使用できません。 それを解決するために制約ファイルと呼ばれるファイルを追加してポートを指定します。 Flow Navigator(左サイドバー)からadd sources を実行してください。

f:id:progrunner17:20171201232906p:plain

add or crate constraints をチェックしてください。

f:id:progrunner17:20171201233000p:plain

create fileを選択し制約ファイルを作ります。 ここでの制約ファイル名は特に重要ではないですが今回はled.xdc*8とします。

f:id:progrunner17:20171201233024p:plain

制約ファイルを追加したらウィンドウ中央からled.xdcをダブルクリックしてテキストエディタを開き下記の設定項目を入力してください。*9

set_property PACKAGE_PIN AP8 [get_ports "led[0]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[0]"]
set_property PACKAGE_PIN H23 [get_ports "led[1]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[1]"]
set_property PACKAGE_PIN P20 [get_ports "led[2]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[2]"]
set_property PACKAGE_PIN P21 [get_ports "led[3]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[3]"]
set_property PACKAGE_PIN N22 [get_ports "led[4]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[4]"]
set_property PACKAGE_PIN M22 [get_ports "led[5]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[5]"]
set_property PACKAGE_PIN R23 [get_ports "led[6]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[6]"]
set_property PACKAGE_PIN P23 [get_ports "led[7]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[7]"]

これで制約ファイルがの設定が終わりました。

HDLwrapperの生成

f:id:progrunner17:20171201233325p:plain

最後に、ウィンドウ中央のSources - Hierarchyタブを開きブロックデザインを右クリックしてcreat HDL wrapper を実行してください。

合成、配置配線及びビットストリームの生成

FPGAに書き込むために合成、配置配線及びビットストリームの生成*10を行います。

f:id:progrunner17:20171201233705p:plain

Flow Navigatorから run synthesizeを実行

f:id:progrunner17:20171201234009p:plain

しばらく待つとこの様なポップアップが現れるのでrun implementation を実行します。

f:id:progrunner17:20171201234409p:plain

generate bitstreamを実行
これでFPGAに書き込むファイルが生成できました。

f:id:progrunner17:20171201234435p:plain

ボードに電源をつなぎ、JTAGポートとPCをマイクロusbケーブルでつなぎFlownavigatorからハードウェアマネージャーを開き auto connectを行うとPCとFPGAが繋がります。 右クリックでprogram deviceを実行するとこんな感じでLEDがチカチカ光り始めるはずです。

f:id:progrunner17:20171201235140g:plain

最後に

もしやる気があれば次はBlock memoryやverilogなどの使い方を書きます。

*1:今回はclock wizard,binary counter, sliceを用いた

*2:ポート及び物理制約の使用

*3:要はFPGAボードへ書き込む準備

*4:実際にFPGAに書き込みを行うこと

*5:おそらく基本設定でボードを指定するとこの選択ができるようになると思うのですが、その設定方法を知らないので割愛します。

*6:ブロクデザインはGUIを使用した設計ツールです

*7:clocking wizardもIPコアの一つなのでadd IPから追加することも可能ですが、この方法で追加することで制約ファイルを自分で書くことなくクロックを簡単に生成することができます。

*8:xdcは制約ファイルの拡張子

*9:制約ファイルの物理制約はボード毎に異なりますKCU105の場合このリンクのPDFの末尾の方に設定例が乗っています。

*10:かなり適当に例えると推論、リンク、コンパイルみたいな感覚です。