FPGA開発日記

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

RustでRISC-V命令セットシミュレータを作ろう (2. rv32ui-pのすべてのテストパタンをPassさせる)

f:id:msyksphinz:20190224185310p:plain

Rustで作るRISC-Vシミュレータ。基本的な形が出来上がったので、次に命令を追加してテストパタンをパスさせていく。 必要なのは命令を追加するだけなので、どんどん追加していく。 今回は特にハマるところはなく命令を追加していくことができたのだが、やはりオーバフローを検出してしまうので対応するのが面倒だ。 ひたすらWrappingを追加していった。

github.com

という訳で、rv32ui-p-xxxのテストパタンはすべてPASSできるようになった。これで基本的な形は完成なので、MMUとか、浮動小数点などの命令の対応を追加していこうと思う。

ついでに、リグレッションテストも追加していった。 リグレッションテストの環境は、各テストに対してcargo runを実行していくだけなので、単純に以下のようにしている。

TEST_PATTERNS = $(wildcard ./riscv-tests/isa/rv32ui-p-*.bin)
RUN_PATTERNS = $(addsuffix _run,$(notdir $(basename $(TEST_PATTERNS))))

.PHONY : all

all: $(RUN_PATTERNS)

%_run:
        @cargo run -q ./riscv-tests/isa/$(subst _run,.bin,$@) > /dev/null

これでテストを実行する。無事にPASSさせることができた。

$ make -j4
PASS : ./riscv-tests/isa/rv32ui-p-jal.bin
PASS : ./riscv-tests/isa/rv32ui-p-lhu.bin
PASS : ./riscv-tests/isa/rv32ui-p-slti.bin
PASS : ./riscv-tests/isa/rv32ui-p-sll.bin
PASS : ./riscv-tests/isa/rv32ui-p-sra.bin
PASS : ./riscv-tests/isa/rv32ui-p-sh.bin
PASS : ./riscv-tests/isa/rv32ui-p-blt.bin
PASS : ./riscv-tests/isa/rv32ui-p-and.bin
PASS : ./riscv-tests/isa/rv32ui-p-andi.bin
PASS : ./riscv-tests/isa/rv32ui-p-xor.bin
PASS : ./riscv-tests/isa/rv32ui-p-sltu.bin
PASS : ./riscv-tests/isa/rv32ui-p-bge.bin
PASS : ./riscv-tests/isa/rv32ui-p-jalr.bin
PASS : ./riscv-tests/isa/rv32ui-p-bne.bin
PASS : ./riscv-tests/isa/rv32ui-p-srai.bin
PASS : ./riscv-tests/isa/rv32ui-p-lw.bin
PASS : ./riscv-tests/isa/rv32ui-p-bltu.bin
PASS : ./riscv-tests/isa/rv32ui-p-lbu.bin
PASS : ./riscv-tests/isa/rv32ui-p-beq.bin
PASS : ./riscv-tests/isa/rv32ui-p-ori.bin
PASS : ./riscv-tests/isa/rv32ui-p-lui.bin
PASS : ./riscv-tests/isa/rv32ui-p-sltiu.bin
PASS : ./riscv-tests/isa/rv32ui-p-srli.bin
PASS : ./riscv-tests/isa/rv32ui-p-add.bin
PASS : ./riscv-tests/isa/rv32ui-p-slt.bin
PASS : ./riscv-tests/isa/rv32ui-p-lh.bin
PASS : ./riscv-tests/isa/rv32ui-p-fence_i.bin
PASS : ./riscv-tests/isa/rv32ui-p-slli.bin
PASS : ./riscv-tests/isa/rv32ui-p-xori.bin
PASS : ./riscv-tests/isa/rv32ui-p-addi.bin
PASS : ./riscv-tests/isa/rv32ui-p-bgeu.bin
PASS : ./riscv-tests/isa/rv32ui-p-srl.bin
PASS : ./riscv-tests/isa/rv32ui-p-sw.bin
PASS : ./riscv-tests/isa/rv32ui-p-simple.bin
PASS : ./riscv-tests/isa/rv32ui-p-auipc.bin
PASS : ./riscv-tests/isa/rv32ui-p-sb.bin
PASS : ./riscv-tests/isa/rv32ui-p-or.bin
PASS : ./riscv-tests/isa/rv32ui-p-lb.bin
PASS : ./riscv-tests/isa/rv32ui-p-sub.bin

コマンドラインからSlackに通知を送るツールslackcatを使って長いジョブを忘れないようにする

半導体設計者あるあるだが、一回に流れるジョブが長すぎて、別の作業をしているとそのジョブが完了していても忘れることがある。 つまり長いジョブを流す場合、ジョブが完了したときに通知が来ればよいのだが、いちいちメールを飛ばすようにしているとメールボックスが汚くなってしまい気持ちが悪い。

そこで便利なのがSlackだろう。 業務で直接Slackは使っていないものの、デスクトップアプリやスマートフォンと連携して便利だし、単純にチャットルームに書き込まれてポップアップが上がるだけなのでメールボックスが汚くなることもない。 つまり、コマンドラインからSlackが扱えるようになれば良い、という訳だ。

いろいろツールを探すと、slackcatというツールがあるようだ。

github.com

インストールは簡単だ。後で捕捉するが最新版を使う方がよい。

curl -Lo slackcat https://github.com/bcicen/slackcat/releases/download/v1.6/slackcat-1.6-$(uname -s)-amd64
sudo mv slackcat /usr/local/bin/
sudo chmod +x /usr/local/bin/slackcat

つぎに、連携するSlackのアカウントを設定する。

slackcat --configure

Slackの画面がウェブブラウザ上で表示され、どのアカウントと連携するかを設定する。http://slackcat.chat/configureを開いてアカウントの連携を行い、最後に表示されたトークンを ${HOME}/.config/slackcat/config に張り付ける。これで設定完了となる。

基本的な使い方は、以下のようにすることで連携したSlackのワークスペース上に上にメッセージが表示される。

echo Hello | slackcat -c general -t -s
f:id:msyksphinz:20190301233131p:plain

このとき、体裁としては"msyksphinz"から"msyksphinz"にメッセージを送ったことになるため、ポップアップは出現しない。 ポップアップを出したければ、ユーザ名を指定して"msyksphinz"以外からメッセージが送られたことにすればよい。

echo Hello Slackcat | slackcat -c general -t -s -u msyksphinz_bot
f:id:msyksphinz:20190301233345p:plain

という訳で、以下のようなスクリプトを組んで、Slackに通知したいジョブを実行するためのWrapperを作った。

  • sc.sh
#!/bin/sh

argv=$@

echo $argv

${argv}
echo "Finished
Time = `date`
HostName = `hostname`
DIR = `pwd`
${argv}" | slackcat -c general -t -s -u job_botname
````

以下のようにすれば、コマンド終了時にSlackに通知される。これでジョブの完了をすぐに知ることができるようになった。

sc.sh make

Vivado-HLSを使って高位合成でCPUを作ってみる(8. RISC-VのテストベンチをすべてPassさせる)

Vivado-HLSを使って簡単なRISC-V CPUを作ってみている。

すべてのテストパタンを通したいのだが、バスをどのように作ればよいのか迷っている。

これまでは32bitでバスを作っていた。これは通常のLW/SW命令だとよいのだが、HalfWord, Byteのロードストアでは問題となる。 そこで、バスそのものを8bitに絞り、そこからアクセスサイズの分だけメモリに読み書きを行う、というのはどうだろう?

uint32_t mem[128];

// Store Word
mem[addr >> 2] = data;
// Store Halfword
uint16_t *mem_hword = (uint16_t *)mem;
mem_hword[addr >> 1] = data;
// Store Byte
uint8_t *mem_bword = (uint8_t *)mem;
mem_hword[addr] = data;

残念ながら、この方法は上手くいかなかった。Cosimulationの際に以下のようなエラーが出る。 どうやらこのようなキャストは許されないようだ。

INFO: [XFORM 203-602] Inlining function 'rv32_cpu::is_finish_cpu' into 'cpu_hls' (cpu_hls.cpp:19) automatically.
INFO: [XFORM 203-602] Inlining function 'rv32_cpu::get_tohost' into 'cpu_hls' (cpu_hls.cpp:21) automatically.
ERROR: [SYNCHK 200-41] rv32_cpu.cpp:25: unsupported pointer reinterpretation from type 'rv32_cpu' to type 'i32*' on variable 'data_mem'.
INFO: [SYNCHK 200-10] 1 error(s), 0 warning(s).
ERROR: [HLS 200-70] Synthesizability check failed.
command 'ap_source' returned error code
    while executing
"source script.tcl"

仕方がないので、LWの場合は8ビットのメモリに対して4回アクセスするような記述に直した。 このあたり、AXIにうまくフィットさせるためにはどのような記述をすればよいのだろう?

          case SIZE_BYTE  : {
            return (m_data_mem[addr] & 0x0ff);
          }
          case SIZE_HWORD : {
            return ((m_data_mem[addr + 1] << 8) |
                    (m_data_mem[addr + 0] << 0)) & 0x0ffff;
          }
          case SIZE_WORD : {
            return ((XLEN_t)(m_data_mem[addr + 3]) << 24) |
                   ((XLEN_t)(m_data_mem[addr + 2]) << 16) |
                   ((XLEN_t)(m_data_mem[addr + 1]) <<  8) |
                   ((XLEN_t)(m_data_mem[addr + 0]) <<  0);
          }

という訳で、一応rv32ui-p-xxxのテストパタンをすべて通すようにした。一応、すべて通るようになった。

github.com

make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-srai TEST_PATTERN=rv32ui-p-srai.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-lw TEST_PATTERN=rv32ui-p-lw.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-fence_i TEST_PATTERN=rv32ui-p-fence_i.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-lbu TEST_PATTERN=rv32ui-p-lbu.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-jalr TEST_PATTERN=rv32ui-p-jalr.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-beq TEST_PATTERN=rv32ui-p-beq.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-ori TEST_PATTERN=rv32ui-p-ori.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-simple TEST_PATTERN=rv32ui-p-simple.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-sltiu TEST_PATTERN=rv32ui-p-sltiu.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-sll TEST_PATTERN=rv32ui-p-sll.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-bgeu TEST_PATTERN=rv32ui-p-bgeu.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-jal TEST_PATTERN=rv32ui-p-jal.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-add TEST_PATTERN=rv32ui-p-add.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-lui TEST_PATTERN=rv32ui-p-lui.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-srli TEST_PATTERN=rv32ui-p-srli.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-sw TEST_PATTERN=rv32ui-p-sw.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-xori TEST_PATTERN=rv32ui-p-xori.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-lh TEST_PATTERN=rv32ui-p-lh.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-auipc TEST_PATTERN=rv32ui-p-auipc.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-srl TEST_PATTERN=rv32ui-p-srl.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-sh TEST_PATTERN=rv32ui-p-sh.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-or TEST_PATTERN=rv32ui-p-or.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-lb TEST_PATTERN=rv32ui-p-lb.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-slt TEST_PATTERN=rv32ui-p-slt.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-sub TEST_PATTERN=rv32ui-p-sub.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-slli TEST_PATTERN=rv32ui-p-slli.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-and TEST_PATTERN=rv32ui-p-and.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-lhu TEST_PATTERN=rv32ui-p-lhu.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-bltu TEST_PATTERN=rv32ui-p-bltu.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-sb TEST_PATTERN=rv32ui-p-sb.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-addi TEST_PATTERN=rv32ui-p-addi.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-slti TEST_PATTERN=rv32ui-p-slti.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-sra TEST_PATTERN=rv32ui-p-sra.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-blt TEST_PATTERN=rv32ui-p-blt.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-andi TEST_PATTERN=rv32ui-p-andi.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-xor TEST_PATTERN=rv32ui-p-xor.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-sltu TEST_PATTERN=rv32ui-p-sltu.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-bge TEST_PATTERN=rv32ui-p-bge.hex > /dev/null
make -f ../common/csim.mk HLS_SOLUTION=rv32ui-p-bne TEST_PATTERN=rv32ui-p-bne.hex > /dev/null

次に、性能確認と拡張を行っていく。

オリジナルLLVM Backendを追加しよう (24. グローバル変数の対応)

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

jonathan2251.github.io

第6章はグローバル変数をサポート。 グローバル変数のサポートには4種類があって、それぞれどのような処理が必要なのか見ていく。

  • リロケーションモード : static, Smallsection : 未使用
  • リロケーションモード : static, Smallsection : 使用
  • リロケーションモード : PIC, Smallsection : 未使用
  • リロケーションモード : PIC, Smallsection : 使用

グローバル変数

これまではローカル変数についてみてきたが、今回はグローバル変数のアクセス変換について学ぶ。

グローバル変数のDAG変換は、過去に学んだDAG変換とは異なる。 バックエンドのC++コードが、llc -relocation-moelオプションに基づいてIR DAGノードを作成する。 一方で、他のDAGはIR DAGを直接マシンコードのDAGに変換する。 マシンコードのIR DAGはIR DAGの入力ファイルに基づく(例外的に、 Chapter3_4で使用するRetLR疑似命令は除く)。加えて、バックエンドを持っている場合は、アセンブリディレクティブ(マクロ)に関連するグローバル変数のための関数を出力するためのマシン命令について考えておく必要がある。

Chapter6_1グローバル変数をサポートしている。ch6_1.cppコンパイルしてみよう。

  • リロケーションモード : static, Smallsection使用

    • ch6_1.static.small.s
 ori  x10, x3, %gp_rel(gI)
    lw   x10, 0(x10)
    sw   x10, 4(x2)
    lw   x10, 4(x2)
...
    .type gI,@object              # @gI
    .globl    gI
    .p2align  2
gI:
    .4byte  100                     # 0x64
    .size gI, 4

あれ?x3はどこから来たんだ?なんか問題がありそうな気がする。

  • ch6_1.static.large.s
 lui  x10, %hi(gI)
    ori  x10, x10, %lo(gI)
    lw   x10, 0(x10)
    sw   x10, 4(x2)
    lw   x10, 4(x2)
...
    .type gI,@object              # @gI
    .globl    gI
    .p2align  2
gI:
    .4byte  100                     # 0x64
    .size gI, 4

こちらはSmallsectionを使わないので、アドレスを2命令を使って生成している。

  • ch6_1.pic.small.s
 lw   x10, %got(gI)(x3)
    lw   x10, 0(x10)
    sw   x10, 4(x2)
    lw   x10, 4(x2)

こちらはGOTを使って生成する。そしてSmallsectionなのでアドレス生成も1命令で。

  • ch6_1.pic.large.s
 lui  x10, %got_hi(gI)
    add  x10, x10, x3
    lw   x10, %got_lo(gI)(x10)
    lw   x10, 0(x10)
    sw   x10, 4(x2)
    lw   x10, 4(x2)

こちらはGOTを使って、さらにアドレスを2命令で生成している。

Vivado-HLSを使って高位合成でCPUを作ってみる(7. Cosimulationで波形をダンプして解析する)

Vivado-HLSを使って簡単なRISC-V CPUを作ってみている。

s_axiliteとm_axiの違いで動作しなくなってしまい苦労していたが、色々調べていると、#pragmaでいろんなところにm_axiを入れていけば解決することが分かり、無事にデザインを合成することができた。

github.com

diff --git a/vivado_hls/cpu_hls/rv32_cpu.cpp b/vivado_hls/cpu_hls/rv32_cpu.cpp
index ec7ca3e..3bbafc0 100644
--- a/vivado_hls/cpu_hls/rv32_cpu.cpp
+++ b/vivado_hls/cpu_hls/rv32_cpu.cpp
@@ -6,6 +6,7 @@
 rv32_cpu::rv32_cpu(uint32_t *data_mem, Addr_t tohost_addr, Addr_t fromhost_addr)
     : m_data_mem(data_mem), m_tohost_addr(tohost_addr), m_fromhost_addr(fromhost_addr)
 {
+#pragma HLS INTERFACE m_axi port=data_mem bundle=mem
diff --git a/vivado_hls/cpu_hls/cpu_hls.cpp b/vivado_hls/cpu_hls/cpu_hls.cpp
index edeaf15..5f2e330 100644
--- a/vivado_hls/cpu_hls/cpu_hls.cpp
+++ b/vivado_hls/cpu_hls/cpu_hls.cpp
@@ -7,6 +7,7 @@

 XLEN_t cpu_hls (uint32_t mem[MEMORY_SIZE])
 {
+#pragma HLS INTERFACE m_axi port=mem bundle=mem
   rv32_cpu u_rv32_cpu (mem, 0x1000, 0x1004);
   uint32_t test_counter = 0;

これで一応Cosimulationまで動作するようになった。ばんざい! サイクル数を確認すると1200サイクルくらいだ。前回よりはよっぽど改善している。 それでも、55命令を実行するのに1200サイクルは遅いなあ。

Cosimulationの際に、波形を取るようにしたので波形をダンプしてみた。

f:id:msyksphinz:20190226232546p:plain
Vivado-HLSによるCosimulationの波形確認

うーん、フェッチがかなりとびとびになっている。これは性能出ないわな。

大きいのは次のフェッチまでの時間が空きすぎていること。FIFOの中にフェッチリクエストが入っているままだけど、これはどのような仕組みになっているんだ?

f:id:msyksphinz:20190226233836p:plain
Vivado-HLS RISC-V CPUのステージ解析

Vivado-HLSを使って高位合成でCPUを作ってみる(6. m_axiとs_axiliteの謎)

Vivado-HLSを使って簡単なRISC-V CPUを作ってみている。

テストがこんなに遅いのはなんでだ?デザインに根本的な間違いがあるのではないかと思いいろいろ見直していたのだが、あれ、よく見るとCPUのバスがスレーブの設定になってるじゃないか。

  • directives.tcl
set_directive_interface -mode s_axilite -bundle slv0 "cpu_hls"
set_directive_interface -mode s_axilite -bundle slv0 "cpu_hls" mem

これはダメだろう。CPUが能動的にバスを操作しなければならない。 以下のように変更した。

set_directive_interface -mode m_axi -bundle mst0 "cpu_hls"
set_directive_interface -mode m_axi -bundle mst0 "cpu_hls" mem

これで論理合成を仕掛けた。すると以下のエラーが発生した。

WARNING: [XFORM 203-631] Renaming function 'rv32_cpu::csrrw' to 'csrrw.2' (./rv32_cpu.hpp:101:12)
WARNING: [XFORM 203-631] Renaming function 'rv32_cpu::csrrs' to 'csrrs.2' (./rv32_cpu.hpp:104:12)
ERROR: [XFORM 203-801] Cannot specify interface mode m_axi on a scalar argument 'data' for function 'csrrw.1' (rv32_csr.cpp:5:35).
ERROR: [XFORM 203-801] Cannot specify interface mode 'm_axi' on a non-pointer argument 'data' for function 'csrrw.1' (rv32_csr.cpp:5:35). Please use a pointer (or reference) instead.
ERROR: [XFORM 203-801] Cannot specify interface mode m_axi on a scalar argument 'imm' (./rv32_csr.hpp:14) for function 'csrrw' (./rv32_csr.hpp:14).
ERROR: [XFORM 203-801] Cannot specify interface mode 'm_axi' on a non-pointer argument 'imm' (./rv32_csr.hpp:14) for function 'csrrw' (./rv32_csr.hpp:14). Please use a pointer (or reference) instead.
ERROR: [XFORM 203-801] Cannot specify interface mode m_axi on a scalar argument 'data' for function 'csrrs.1' (rv32_csr.cpp:32:35).

うーん、csrrwというオブジェクトはCSRレジスタ群を管理するためのコードだ。確かに、ポインタを使わずに書いている。

ただし、ポインタを使わないのは私の配慮で、Vivado-HLSでポインタを多用すると論理合成できないのではないかと恐れたからだ。

  • rv32_csr.cpp
...
    case CsrAddr_mcycle    : return m_mcycle    .csrrw(data);
    case CsrAddr_minstret  : return m_minstret  .csrrw(data);
    case CsrAddr_mimpid    : return m_mimpid    .csrrw(data);
    case CsrAddr_marchid   : return m_marchid   .csrrw(data);
...

ちなみにポインタを使うように変更しても同様のエラーが発生した。Vivado-HLSのエラーを見てもよく分からない。XFORM 203-801の情報があまりないなあ。 これはどうやって対処すればよいのだろう?分からん。

(続く)

オリジナルLLVM Backendを追加しよう (23. オブジェクトファイル形式の対応)

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

jonathan2251.github.io

Chapter-5を進めていこう。これまではアセンブラ形式のみを対応させていたが、今度はオブジェクトファイルも生成しなくてはならない。

主に追加するファイルは以下だ。結構量が多い。写経というか、コピペになってしまっているが、とりあえず動くものを作っていこう。

  • MYRISCVXAsmBackend.cpp
  • MYRISCVXMCCodeEmitter.cpp
  • MYRISCVXMCExpr.cpp
  • MYRISCVXELFObjectWriter.cpp
  • MYRISCVXTargetStreamer.cpp

なんだか大量にファイルが追加されるが、要するにターゲットを追加する、ということらしい。 ELFObjectWriterというターゲットを定義してこれを追加する。おそらく一番肝になっているのは以下のコード。

  • lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCTargetDesc.cpp
//@2 {
extern "C" void LLVMInitializeMYRISCVXTargetMC() {
  for (Target *T : {&TheMYRISCVX32Target, &TheMYRISCVX64Target}) {
...
    // Register the MC Code Emitter
    TargetRegistry::RegisterMCCodeEmitter(*T,
                                          createMYRISCVXMCCodeEmitterEL);

    // Register the asm backend.
    TargetRegistry::RegisterMCAsmBackend(*T,
                                         createMYRISCVXAsmBackendEL32);

さらに、写経をしながらコードを追加していく。以下のコードはCpu0ではNOPとSHLが同じのためケアをする必要があるらしいが、RISC-Vではそんなことはないので割愛。面倒なので削除しておく。

/// encodeInstruction - Emit the instruction.
/// Size the instruction (currently only 4 bytes)
void Cpu0MCCodeEmitter::
encodeInstruction(const MCInst &MI, raw_ostream &OS,
                  SmallVectorImpl<MCFixup> &Fixups,
                  const MCSubtargetInfo &STI) const
{
...
  // Check for unimplemented opcodes.
  // Unfortunately in CPU0 both NOT and SLL will come in with Binary == 0
  // so we have to special check for them.
  unsigned Opcode = MI.getOpcode();
  if ((Opcode != Cpu0::NOP) && (Opcode != Cpu0::SHL) && !Binary)
    llvm_unreachable("unimplemented opcode in encodeInstruction()");

あとはLLVMの仕様が微妙に変わっていて少しずつコンパイルが通らないのを修正する。 基本的にMIPSの実装を参考にしながら、古くなっている部分を修正していった。

コンパイルを通して、さっそく実験してみる。

./bin/clang -c -target mips-unknown-linux-gnu ../lbdex/input/ch4_1_math.cpp -emit-llvm
./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=obj ch4_1_math.bc -o ch4_1_math.myriscvx.o
...

Function : _Z9test_mathv
<--------->
$a1 = LW %stack.9.i, 0 :: (dereferenceable load 4 from %ir.i)
FrameIndex : 9
spOffset : -40
stackSize : 56
Offset : 16
<--------->

Function : _Z9test_mathv
<--------->
$a1 = LW %stack.13.i1, 0 :: (dereferenceable load 4 from %ir.i1)
FrameIndex : 13
spOffset : -56
stackSize : 56
Offset : 0

大量にデバッグメッセージが出てくるが、なんとか終了したようだ。 とりあえず正しく生成できているか確認しよう。

$ objdump -s ch4_1_math.myriscvx.o
ch4_1_math.myriscvx.o:     file format elf32-little

Contents of section .text:
 0000 130181fc 13005500 232a4503 13002500  ......U.#*E...%.
 0010 23280503 1300b5ff 2326c502 032a4503  #(......#&...*E.
 0020 03a80503 3305b500 23248502 032a4503  ....3...#$...*E.
 0030 03a80503 3305b500 23224502 032a4503  ....3...#"E..*E.
 0040 03a80503 3305b500 23200502 032a4503  ....3...# ...*E.
 0050 13152500 232ec501 0326c502 13151500  ..%.#....&......
 0060 2326c500 032a4503 13552500 232c8501  #&...*E..U%.#,..
f:id:msyksphinz:20190226004748p:plain
llcで生成したMYRISCVXオブジェクトファイルのダンプ

アセンブリと比較して、何となくそれっぽい命令が出力で来ているように思う。

gccが生成したものと比較して一致しているか確認してみたい。

riscv64-unknown-elf-gcc -c -march=rv64imafd ../lbdex/input/ch4_1_math.cpp
riscv64-unknown-elf-objdump -d ch4_1_math.o 
Contents of section .text:
 0000 130101fb 23348104 13040105 93075000  ....#4........P.
 0010 2326f4fe 93072000 2324f4fe 9307b0ff  #&.... .#$......
 0020 2322f4fe 0327c4fe 832784fe bb07f700  #"...'...'......
 0030 2320f4fe 0327c4fe 832784fe bb07f740  # ...'...'.....@
 0040 232ef4fc 0327c4fe 832784fe bb07f702  #....'...'......
 0050 232cf4fc 8327c4fe 9b972700 232af4fc  #,...'....'.#*..
 0060 832744fe 9b971700 2328f4fc 8327c4fe  .'D.....#(...'..
 0070 9bd72740 2326f4fc 832744fe 9bd7e701  ..'@#&...'D.....

ビット反転とかなく、特に問題はなさそう。とりあえずオブジェクトファイルは出力できるようになったので良しとする。