鬱猫のつぶやき

鬱猫が日々思っていることをつづります

ChiselのハードウェアデザインからVerilogのRTLを生成し、Treadleでシミュレーションを実行する

ものすごくタイトルが長くなってしまった。調べても意外と出てこなかったのでメモしておく。

VerilogのRTLを生成する方法

この記事を参考に以下のように書いてみた。

package GenVerilogTest

import chisel3._

class Top(in0Bits: Int, in1Bits: Int) extends Module {
  val io = IO(new Bundle {
    val in0 = Input(UInt(in0Bits.W))
    val in1 = Input(UInt(in0Bits.W))
    val out = Output(UInt((in0Bits + 1).W))
  })

  io.out := io.in0 + io.in1
}

object Elabolate extends App {
  chisel3.Driver.execute(args, () => new Top(32, 32)) match {
    case ChiselExecutionSuccess(Some(_), emittedStr, Some(firrtlExecutionResult)) => {
      firrtlExecutionResult match {
        case firrtl.FirrtlExecutionSuccess(_, compiledFirrtl) => {
          println(compiledFirrtl) // FirrtlからVerilogに変換した結果が表示される
          println("")
          println(emittedStr) // Firrtlが表示される
        }
        case firrtl.FirrtlExecutionFailure(message) =>
          throw new Exception(s"FirrtlBackend: Compile failed.")
        case _ =>
          throw new Exception("Problem with compilation")
      }
    }
  }
}

上記コードにおいてchisel3.Driver.execute(args, () => new Top(32, 32))においてVerilogとFirrtlの2つを生成している。生成されたファイルはbuild.sbtが置かれているディレクトリと同じ箇所に置かれる。

生成されたファイルは以下の通り。この程度ぐらいであればなんとか読めるレベルではある。

;buildInfoPackage: chisel3, version: 3.1.8, scalaVersion: 2.11.12, sbtVersion: 1.1.1, builtAtString: 2019-07-08 17:44:42.884, builtAtMillis: 1562607882884
circuit Top : 
  module Top : 
    input clock : Clock
    input reset : UInt<1>
    output io : {flip in0 : UInt<32>, flip in1 : UInt<32>, out : UInt<33>}
    
    node _T_11 = add(io.in0, io.in1) @[Top.scala 12:20]
    node _T_12 = tail(_T_11, 1) @[Top.scala 12:20]
    io.out <= _T_12 @[Top.scala 12:10]
module Top( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [31:0] io_in0, // @[:@6.4]
  input  [31:0] io_in1, // @[:@6.4]
  output [32:0] io_out // @[:@6.4]
);
  wire [32:0] _T_11; // @[Top.scala 12:20:@8.4]
  wire [31:0] _T_12; // @[Top.scala 12:20:@9.4]
  assign _T_11 = io_in0 + io_in1; // @[Top.scala 12:20:@8.4]
  assign _T_12 = io_in0 + io_in1; // @[Top.scala 12:20:@9.4]
  assign io_out = {{1'd0}, _T_12}; // @[Top.scala 12:10:@10.4]
endmodule

また、FirrtlとVerilogへの変換結果は文字列としても受け取ることができる。

object Simulator {
  // buildした結果のFirrtlとVerilogの2つをタプルとして返す
  def build(args: Array[String]): (String, String)= {
    chisel3.Driver.execute(args, () => new Top(32, 32)) match {
      case ChiselExecutionSuccess(Some(_), firrtlSource, Some(firrtlExecutionResult)) => {
        firrtlExecutionResult match {
          case firrtl.FirrtlExecutionSuccess(_, verilogSource) => {
            (verilogSource, firrtlSource)
          }
          case firrtl.FirrtlExecutionFailure(message) =>
            throw new Exception(s"FirrtlBackend: Compile failed.")
          case _ =>
            throw new Exception("Problem with compilation")
        }
      }
    }
  }
 
  def main(args: Array[String]): Unit = {
    // .. Simulatorの定義
  }
}

上記コードのbuild関数では、戻り値の1つめがVerilogのコード、2つめがFirrtlのコードに対応する。

次に、Firrtlのコードを使ってシミュレーターを起動してみる。Treadleを使うと以下のように書くことができる。

class SimulatorOptionsManager extends TreadleOptionsManager

object Simulator {
  // buildした結果のFirrtlとVerilogの2つをタプルとして返す
  def build(args: Array[String]): (String, String)= {
    // .. 上のコードを参考
  }

  def main(args: Array[String]): Unit = {
    val (_, firrtlSource) = build(args)
    val optionsManager = new SimulatorOptionsManager // simulatorを作成する際のオプションを指定できるみたいなのだが、正直良くわかっていない

    optionsManager.setTargetDirName("simulator_run_dir")

    val simulator = TreadleTester(firrtlSource, optionsManager) // 生成されたFirrtl自体を渡す

    val maxCycles = 100
    var cycles = 0
    println("Running...")

    simulator.poke("io_in0", 1) // io.in0 ではなく、 io_in0 のようにアンダースコアでつないで表現する必要があるので注意
    simulator.poke("io_in1", 2)
    while (simulator.peek("io_out") != 12 && cycles < maxCycles) {
      simulator.step(1)
      cycles += 1
      simulator.poke("io_in0", 10)
      simulator.poke("io_in1", 2)
      println(s"Simulated $cycles cycles")
      val out = simulator.peek("io_out")
      println(s"output is $out")
    }
  }
}

以下のような出力が出てくるはず。

[info] Compiling 1 Scala source to /mnt/src/GenVerilogTest/target/scala-2.11/classes ...
[warn] there were three feature warnings; re-run with -feature for details
[warn] one warning found
[info] Done compiling.
[info] Packaging /mnt/src/GenVerilogTest/target/scala-2.11/chisel-module-template_2.11-3.1.1.jar ...
[info] Done packaging.
[info] Running GenVerilogTest.Sumulator 
[info] [0.001] Elaborating design...
[info] [0.689] Done elaborating.
Total FIRRTL Compile Time: 240.4 ms
Total FIRRTL Compile Time: 12.9 ms
file loaded in 0.051272257 seconds, 7 symbols, 3 statements
Running...
Simulated 1 cycles
output is 12
[success] Total time: 3 s, completed 2019/08/03 15:52:56
[IJ]sbt:chisel-module-template> 

Chisel3でメモリにテキストデータを読み込む際の手順

やり方がよくわからず結構苦戦した。一応ドキュメントにそれっぽい記載はあるんだけども。

chisel3.util.experimental.loadMemoryFromFileという関数を使うのが簡単。例えば、

class InstMemory(memfile: String) extends Module {
  val io = IO(new Bundle{
    val read_addr = Input(UInt(4.W))

    val read_data = Output(UInt(8.W))
  })

  val memory = Mem(16, UInt(8.W))
  loadMemoryFromFile(memory, memfile) // memoryにデータを読み込む

  io.read_data := memory(io.read_addr)
}
class MemoryTester extends ChiselFlatSpec {
  "InstMemory" should s"read data correctly" in {
    Driver(() => new InstMemory("src/test/resources/raw/test.txt"), "treadle") {
      m => new MemoryUnitTest(m)
    } should be (true)
  }
}

みたいな感じで使う。ここでtest.txtはデータの最小単位を改行で区切って並べたものとする。以下のような感じ。

00
01
02
03
04
05
06
07
08
09
0a
0b
0c
0d
0e
0f

こうすると、memoryの第0要素から第15要素に0から15までがそれぞれ埋められる。

ちなみに試した限りでは、この方法だとバイナリファイルを読み込むことができない。以下のようなエラーが出てくる。

[info]   treadle.executable.TreadleException: loading memory memory[0] <=  : error: Zero length BigInteger

実行ファイルとかを読み込む際には他の方法を使うしかなさそうである。

RISC-V Linuxでptrace使う場合の注意点

「たのしいバイナリの歩き方」の本やバイナリの歩き方に掲載されているptraceのサンプルプログラムが、RISC-V QEMU上で動いているLinuxではどうもうまく動かない。その原因を調査していた。

うまく動かないと悩んでいたコードはバイナリの歩き方に掲載されていたもので以下の通り。PTRACE_SINGLESTEPで実行が進まない上、PTRACE_GETREGSでそれっぽいレジスタの値を取得できないという問題に遭遇していた。(ちなみに、手元のx86実機マシンでは正しく以下のサンプルコードは動くことは確認済み。)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <errno.h>

void err(char *str)
{
    fprintf(stderr, "ERROR: %s\n", str);
}

void target(char *argv[], char *argp[])
{
    if(ptrace(PTRACE_TRACEME, 0, NULL, NULL) != -1)
        execve(argv[0], argv, argp);
    else
        err("PTRACE_TRACEME");
    exit(0);
}

void controler(int pid)
{
    int status;
    struct user_regs_struct regs;

    while(1){
        waitpid(pid, &status, 0);
        if(WIFEXITED(status))
            break;
        ptrace(PTRACE_GETREGS, pid, 0, &regs);
        printf("%08x: \n", (unsigned int)regs.eip);
        ptrace(PTRACE_SINGLESTEP, pid, 0, NULL);
    }
}

int exec_prog(char *argv[], char *argp[])
{
    int pid;

    switch(pid = fork())
    {
    case 0:
        target(argv, argp);
        break;
    case -1:
        err("FORK");
        break;
    default:
        controler(pid);
        break;
    }
    return 0;
}

int main(int argc, char *argv[], char *argp[])
{
    if(argc < 2){
        fprintf(stderr, "%s <args>\n", argv[0]);
        return 1;
    }

    argv++;
    exec_prog(argv, argp);
    return 0;
}

結論だけ述べると

  • PTRACE_GETREGS PTRACE_SETREGS PTRACE_SINGLESTEPを指定してptraceを呼び出しても、そもそも実装されていないので必ずEIOがエラーとして返る。
  • アタッチしているプロセスのレジスタの情報を取得/設定する場合には PTRACE_GETREGSET PTRACE_SETREGSET を用いる必要がある。
  • PTRACE_SINGLESTEPは使えず、代替手段もない。PTRACE_SYSCALLPTRACE_CONTを使い、特定の場所で止めたいのであれば、止めたい箇所にebreakを埋め込む。

である。

調査概要

ここからLinux kernelのソースコードを落としてくる。今回5.2.3を落としてきた。

落としてきたLinux kernelのソースコードを展開し、arch/riscv/kernel/ptrace.cソースコードをみる。該当する箇所は以下の通り。

long arch_ptrace(struct task_struct *child, long request,
         unsigned long addr, unsigned long data)
{
    long ret = -EIO;

    switch (request) {
    default:
        ret = ptrace_request(child, request, addr, data);
        break;
    }

    return ret;
}

問答無用でptrace_requestへ飛ぶコードが書かれている。 ptrace_requestkernel/ptrace.cで定義されている。

int ptrace_request(struct task_struct *child, long request,
           unsigned long addr, unsigned long data)
{
    bool seized = child->ptrace & PT_SEIZED;
    int ret = -EIO;
    kernel_siginfo_t siginfo, *si;
    void __user *datavp = (void __user *) data;
    unsigned long __user *datalp = datavp;
    unsigned long flags;

    switch (request) {
    case PTRACE_PEEKTEXT:
    case PTRACE_PEEKDATA:
        return generic_ptrace_peekdata(child, addr, data);
    case PTRACE_POKETEXT:
    case PTRACE_POKEDATA:
        return generic_ptrace_pokedata(child, addr, data);

#ifdef PTRACE_OLDSETOPTIONS
    case PTRACE_OLDSETOPTIONS:
#endif
    case PTRACE_SETOPTIONS:
        ret = ptrace_setoptions(child, data);
        break;
    case PTRACE_GETEVENTMSG:
        ret = put_user(child->ptrace_message, datalp);
        break;
        
    // 長いので省略

この中にはPTRACE_GETREGSが含まれていなかった。どうりでレジスタの値が取得できないわけだ。

ちなみに、x86の場合だとarch_ptrace関数の中に、PTRACE_GETREGSに該当する処理が記述されており、アタッチしているプロセスのレジスタを取得する処理が書かれている。

 case PTRACE_GETREGS:  /* Get all gp regs from the child. */
        return copy_regset_to_user(child,
                       task_user_regset_view(current),
                       REGSET_GENERAL,
                       0, sizeof(struct user_regs_struct),
                       datap);

同じようにPTRACE_SINGLESTEPについても、該当するソースコードを見てみる。

int ptrace_request(struct task_struct *child, long request,
           unsigned long addr, unsigned long data)
{
    bool seized = child->ptrace & PT_SEIZED;
    int ret = -EIO;
    kernel_siginfo_t siginfo, *si;
    void __user *datavp = (void __user *) data;
    unsigned long __user *datalp = datavp;
    unsigned long flags;

    switch (request) {
    case PTRACE_PEEKTEXT:
    case PTRACE_PEEKDATA:
        return generic_ptrace_peekdata(child, addr, data);
    case PTRACE_POKETEXT:
    case PTRACE_POKEDATA:
    
    // .. 長いので省略
    
    
#ifdef PTRACE_SINGLESTEP
    case PTRACE_SINGLESTEP:
#endif
#ifdef PTRACE_SINGLEBLOCK
    case PTRACE_SINGLEBLOCK:
#endif
#ifdef PTRACE_SYSEMU
    case PTRACE_SYSEMU:
    case PTRACE_SYSEMU_SINGLESTEP:
#endif
    case PTRACE_SYSCALL:
    case PTRACE_CONT:
        return ptrace_resume(child, request, data);
        
    // .. 長いので省略

PTRACE_SINGLESTEPが選択された際には、ptrace_resumeが呼ばれる。

static int ptrace_resume(struct task_struct *child, long request,
             unsigned long data)
{
    bool need_siglock;

    if (!valid_signal(data))
        return -EIO;

    if (request == PTRACE_SYSCALL)
        set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
    else
        clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);

#ifdef TIF_SYSCALL_EMU
    if (request == PTRACE_SYSEMU || request == PTRACE_SYSEMU_SINGLESTEP)
        set_tsk_thread_flag(child, TIF_SYSCALL_EMU);
    else
        clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);
#endif

    if (is_singleblock(request)) {
        if (unlikely(!arch_has_block_step()))
            return -EIO;
        user_enable_block_step(child);
    } else if (is_singlestep(request) || is_sysemu_singlestep(request)) { // <-- single stepの際に実行される部分
        if (unlikely(!arch_has_single_step())) // arch_has_single_step マクロが asm/ptrace.h に定義されている場合には1となるが、それ以外は0になる。
            return -EIO;
        user_enable_single_step(child);
    } else {
        user_disable_single_step(child);
    }
    
    // .. 省略

arch/riscv/include/asm/ptrace.hを見てみると、arch_has_single_stepは定義されていない。つまり、PTRACE_SINGLESTEPを引数に設定して実行したとしても、-EIOのエラーが返ってくるだけである。

RISC-VのLinux、ptraceの実装に関してはまだまだ不完全なところが多い。

ちなみにアタッチしているプロセスのレジスタの情報を取得する場合には、

    struct user_regs_struct regs = {0};
    struct iovec iov;
    iov.iov_len = sizeof(regs);
    iov.iov_base = &regs;
    int ret = ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov);
    printf("%llx: \n", regs.pc);

のように書けばできる。

QEMU RISC-V Fedora の Dockerfileを更新した

RISC-V を簡単に試すことができるDockerfileを作成した。Qiitaの記事からDockerfileの更新したもの。Docker Hubから利用できる。

前回からの変更点として、ブートローダにbblが使われなくなっていることがあげられる。READMEにも以下のような記載がある。

This image introduces bootflow changes. We use now OpenSBI -> U-Boot (S-mode)
[extlinux] -> kernel. The kernel and initramfs are wrapped in U-Boot container
for now. This should change later this year once kernel header patches are
merged in kernel and U-Boot.

OpenSBI -> U-Boot -> kernel の順番で起動するようになったとのこと。

これに伴い、QEMUFedoraを起動する際のコマンドが変わった。

qemu-system-riscv64 -nographic -machine virt -smp 4 -m 2G -kernel fw_payload-uboot-qemu-virt-smode.elf -device virtio-blk-device,drive=hd0 -drive file=Fedora-Developer-Rawhide-20190703.n.0-sda.raw,format=raw,id=hd0 -device virtio-net-device,netdev=usernet -netdev user,id=usernet,hostfwd=tcp::10000-:22

-kernelにはfw_payload-uboot-qemu-virt-smode.elfファームウェアとして指定する。

OSの起動に成功すると以下のような標準出力が表示される。

OpenSBI v0.4 (Jul  3 2019 10:27:18)
   ____                    _____ ____ _____
  / __ \                  / ____|  _ \_   _|
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
 | |__| | |_) |  __/ | | |____) | |_) || |_
  \____/| .__/ \___|_| |_|_____/|____/_____|
        | |
        |_|

Platform Name          : QEMU Virt Machine
Platform HART Features : RV64ACDFIMSU
Platform Max HARTs     : 8
Current Hart           : 2
Firmware Base          : 0x80000000
Firmware Size          : 112 KB
Runtime SBI Version    : 0.1

PMP0: 0x0000000080000000-0x000000008001ffff (A)
PMP1: 0x0000000000000000-0xffffffffffffffff (A,R,W,X)


U-Boot 2019.07-rc4 (Jul 02 2019 - 11:46:47 +0000)

CPU:   rv64imafdcsu
Model: riscv-virtio,qemu
DRAM:  2 GiB
In:    uart@10000000
Out:   uart@10000000
Err:   uart@10000000
Net:   
Warning: virtio-net#1 using MAC address from ROM
eth0: virtio-net#1
Hit any key to stop autoboot:  0 

Device 0: QEMU VirtIO Block Device
            Type: Hard Disk
            Capacity: 8192.0 MB = 8.0 GB (16777216 x 512)
... is now current device
Scanning virtio 0:1...
Found /extlinux/extlinux.conf
Retrieving file: /extlinux/extlinux.conf
576 bytes read in 10 ms (55.7 KiB/s)
Ignoring unknown command: ui
Ignoring malformed menu command:  autoboot
Ignoring malformed menu command:  hidden
Ignoring unknown command: totaltimeout
Fedora-Developer-Rawhide-20190703.n.0 Boot Options.
1:  Fedora-Developer-Rawhide-20190703.n.0 (5.2.0-0.rc7.git0.1.0.riscv64.fc31.riscv64)
Enter choice: 1:    Fedora-Developer-Rawhide-20190703.n.0 (5.2.0-0.rc7.git0.1.0.riscv64.fc31.riscv64)
Retrieving file: /uInitrd-5.2.0-0.rc7.git0.1.0.riscv64.fc31.riscv64
15447501 bytes read in 89 ms (165.5 MiB/s)
Retrieving file: /uImage-5.2.0-0.rc7.git0.1.0.riscv64.fc31.riscv64
7603947 bytes read in 48 ms (151.1 MiB/s)
append: ro root=UUID=3b75927f-d59f-4b04-96ce-823ead0046bc rhgb quiet LANG=en_US.UTF-8
## Booting kernel from Legacy Image at 84000000 ...
   Image Name:   5.2.0-0.rc7.git0.1.0.riscv64.fc3
   Image Type:   RISC-V Linux Kernel Image (gzip compressed)
   Data Size:    7603883 Bytes = 7.3 MiB
   Load Address: 80200000
   Entry Point:  80200000
   Verifying Checksum ... OK
## Loading init Ramdisk from Legacy Image at 88300000 ...
   Image Name:   initramfs
   Image Type:   RISC-V Linux RAMDisk Image (gzip compressed)
   Data Size:    15447437 Bytes = 14.7 MiB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
## Flattened Device Tree blob at ff76dd40
   Booting using the fdt blob at 0xff76dd40
   Uncompressing Kernel Image ... OK
   Using Device Tree in place at 00000000ff76dd40, end 00000000ff771d99

Starting kernel ...

以前と比較してOSの起動にかなり時間がかかる。気長に待つのが良い。

教育用RISC-V Davis In-Order CPUの論文を読む

Davis In-Order CPUというカリフォルニア大学デービス校のコンピューターアーキテクチャの授業で使われている、RISC-V ISAを実装したCPUがある。少し前に論文として上がっていたので読んでみた。

論文のPDFはこちら

カリフォルニア大学デービス校では長い間、コンピューターアーキテクチャの授業ではLogisimが使われていたが、学生から非常に不評だったとのこと。論文には、学生からのアンケートのコメントと思わしき文章が掲載されていた。

  • “there isn’t much documentation on Logisim online”
  • “I hate logisim with a passion.”

そこで、Chiselを使って設計したDINO CPUを使い、Chiselを教える講義に変更したとのこと。 そして実際に 2クォーター分講義を実施したところ、評価が高かったそうだ。

  • “Very challenging but rewarding course. Please keep using Chisel in the future!”

また、そこでChiselの知識を身に着け、企業のインターンシップに参加した学生もいたらしい。

Additionally, after teaching this course for the first time, 
one student reported that they received an internship 
offer because they had Chisel experience

講義では座学に加え、4つの課題が出され評価がくだされる。

  • ALU Control Unitの実装 (Chiselとハードウェア記述言語になれさせる目的のため)
  • アプリケーションを動かせるようにするために、CPUの全体を設計
  • パイプラインの実装
  • パイプラインに分岐予測を実装

この課題の内容はすべてGitHubに掲載されており、リポジトリをクローンすればカリフォルニア大学デービス校の学生でなくとも取り組むことができるみたいだ。

DINO CPUについては、以下の特徴を備えたCPUであるとのこと。

  • シンプルなデザインで、教育用途に作られており、わかりやすいコードを心がけて実装されている。
  • RISC-V ISA(RV32I)が実装されており、Cのプログラムをコンパイルして実行することができる
    • CSR命令 ecall ebreakは実装していない
    • 将来的にはこれらの命令をサポートすることにフォーカスするとのこと

基本的にはPatterson & HennessyRISC-V Editionに掲載されているCPUをベースに作られているが、Patterson & Hennessyの本ではRV32Iのうち命令のエンコード形式がR形式の一部しか実装されていないのに対し、DINOはそれ以外の特権命令をのぞくほぼすべての命令を実装しているのが大きな違いとなっている。

デジタル回路について予備知識がない人にもChiselが理解できるようにマテリアルを追加したということも書いてあった。Chisel Bootcampなどの資料はVerilogなどの他のHDLの知識を必要としているところがあったのは確かにそのとおり。

However, most of the Chisel documentation targets graduate 
students and professionals with architecture and digital design background. 
Therefore, we supplemented this documentation with our own slimmed down 
documentation which only contained details required for completing the DINO 
CPU assignments.

Chiselからハードウェア設計を学ぶ上で、DINO CPUは最適かもしれない。

講義ノートもすべて公開されているみたいだ。良い時代になったものだ。

Zephyr OSを使ってみた

Zephyr OSというリアルタイムOSを使うことになったので、その環境構築時の手順のメモ。

SDKを導入し、環境変数を設定する。

$ wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.10.1/zephyr-sdk-0.10.1-setup.run
$ ./zephyr-sdk-0.10.1-setup.run -- -d ~/zephyr-sdk-0.10.1

環境変数に設定しておくために、.bashrcに以下の設定を記入しておく。

export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
export ZEPHYR_SDK_INSTALL_DIR=${HOME}/zephyr-sdk-0.10.1
$ pip3 install --user west
$ west init zephyrproject
$ cd zephyrproject
$ west update
$ sudo pip3 install --user -r zephyr/scripts/requirements.txt

ビルドを行う。

$ cd zephyr
$ source zephyr-env.sh
$ west build -b litex_vexriscv samples/subsys/shell/shell_module
-- west build: build configuration:
       source directory: /mnt/src/zephyrproject/zephyr/samples/subsys/shell/shell_module
       build directory: /mnt/src/zephyrproject/zephyr/build (created)
       BOARD: litex_vexriscv (origin: command line)
-- west build: generating a build system
Zephyr version: 1.14.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.6.8", minimum required is "3.4") 
-- Selected BOARD litex_vexriscv
-- Found west: /home/tsunekoh/.local/bin/west (found suitable version "0.5.7", minimum required is "0.5.6")
-- Loading /mnt/src/zephyrproject/zephyr/boards/riscv32/litex_vexriscv/litex_vexriscv.dts as base
-- Overlaying /mnt/src/zephyrproject/zephyr/dts/common/common.dts
Parsing Kconfig tree in /mnt/src/zephyrproject/zephyr/samples/subsys/shell/shell_module/Kconfig
Loaded configuration '/mnt/src/zephyrproject/zephyr/boards/riscv32/litex_vexriscv/litex_vexriscv_defconfig'
Merged configuration '/mnt/src/zephyrproject/zephyr/samples/subsys/shell/shell_module/prj.conf'
Configuration saved to '/mnt/src/zephyrproject/zephyr/build/zephyr/.config'
-- Cache files will be written to: /home/tsunekoh/.cache/zephyr
-- The C compiler identification is GNU 8.3.0
-- The CXX compiler identification is GNU 8.3.0
-- The ASM compiler identification is GNU
-- Found assembler: /mnt/src/zephyr-sdk/riscv32-zephyr-elf/bin/riscv32-zephyr-elf-gcc
-- Performing Test toolchain_is_ok
-- Performing Test toolchain_is_ok - Success
Including module: esp-idf in path: /mnt/src/zephyrproject/modules/hal/esp-idf/zephyr
Including module: fatfs in path: /mnt/src/zephyrproject/modules/fs/fatfs
Including module: qmsi in path: /mnt/src/zephyrproject/modules/hal/qmsi
Including module: cypress in path: /mnt/src/zephyrproject/modules/hal/cypress
Including module: silabs in path: /mnt/src/zephyrproject/modules/hal/silabs
Including module: st in path: /mnt/src/zephyrproject/modules/hal/st
Including module: stm32 in path: /mnt/src/zephyrproject/modules/hal/stm32
Including module: libmetal in path: /mnt/src/zephyrproject/modules/hal/libmetal
Including module: lvgl in path: /mnt/src/zephyrproject/modules/lib/gui/lvgl
Including module: mbedtls in path: /mnt/src/zephyrproject/modules/crypto/mbedtls
Including module: mcumgr in path: /mnt/src/zephyrproject/modules/lib/mcumgr
Including module: nffs in path: /mnt/src/zephyrproject/modules/fs/nffs
Including module: open-amp in path: /mnt/src/zephyrproject/modules/lib/open-amp
Including module: segger in path: /mnt/src/zephyrproject/modules/debug/segger
Including module: tinycbor in path: /mnt/src/zephyrproject/modules/lib/tinycbor
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/src/zephyrproject/zephyr/build
-- west build: building application
[1/114] Preparing syscall dependency handling

[109/114] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
             RAM:       69936 B       256 MB      0.03%
        IDT_LIST:          41 B         2 KB      2.00%
[114/114] Linking C executable zephyr/zephyr.elf

ここで、dtcのコンパイラのバージョンが古く、cmakeに失敗する場合がある。その場合には、新規にdtcを入れ直す。ソースコードを落としてきて、dtcをビルドし /usr/local/bin あたりに配置すれば良い。 dtcのソースコードここからダウンロードできる。

新しいバージョンのdtcを入れたにもかかわらずビルドに失敗する場合には、zephyr sdkのdtcを参照しにいっている可能性がある。その場合には、zephyr sdkのdtcの名前を適当に変更すれば良い。

Renodeでシミュレーション

実際に起動するのかを確かめたいのだが、残念ながら実機環境を持っていない。仕方ないので、Renodeでシミュレーションできるかどうかを確かめてみる。

Renodeからリリースビルドを落としてきて、プラットフォームごとに用意されたインストーラーを起動すればインストール自体は問題なくできるはず。

ターミナルを開き、renodeを起動する。

$ cd /mnt/src/zephyrproject/zephyr/build # <-- 先程のzephyrのビルドされたものがディレクトリに移動
$ renode

ターミナルが開くので、先程のビルド時に指定したボード名を指定し、ELFをロードしシミュレーションをスタートする。

(monitor) mach create "litex-vexriscv"
(litex-vexriscv) machine LoadPlatformDescription @platforms/cpus/litex_vexriscv.rep
l
(litex-vexriscv) sysbus LoadELF @zephyr.elf
(litex-vexriscv) showAnalyzer sysbus.uart
(litex-vexriscv) start
Starting emulation...
(litex-vexriscv) 

terminalが開き、kernel versionとか叩くとzephyrのバージョンを表示することができる。

今回、RISC-V Getting Started Guildeを大いに参考にした。

spike-dasmの逆アセンブル結果がおかしい件について

risv-isa-simに付随しているspike-dasmの逆アセンブル結果におかしい点を見つけた。

$ echo "DASM(0x00000000)" | spike-dasm
c.addi4spn s0, sp, 0

と表示されるんだが、これ unimp が表示されるのが適切。 c.addi4spn に関しては即値が0以外であることが、RISC-V ISA の仕様書にも書いてある。

C.ADDI16SP adds the non-zero sign-extended 6-bit immediate to the value in the stack pointer 

ちなみに、objdump や radare2 は正しく出してくれた。

$ rasm2 -a riscv -d "0x0000" 
unimp

すでに、GitHubでもイシューとして上がっているけども、長い間修正されていないみたいだ。

spike-dasmを使う場合には注意を。