FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

RustでRISC-V命令セットシミュレータを作ろう (5. シミュレータのトレースデータ出力機構の実装)

f:id:msyksphinz:20190224185310p:plain

Rustで作るRISC-Vシミュレータ。基本的な形が出来上がった。MMUの変換処理は、まだデバッグ中だ。しかしデバッグするためには、トレースデータを出力できるようにする必要がある。 トレースデータを出力するためには、RISC-Vシミュレータが実行した情報をトレースに記憶し、最後にまとめて出力する機能が必要だ。 これはこれまでC++で実装していたのだが、これをRustで実装しよう。

github.com

トレースデータのデータ構造は、以下のように設計する。まだ作ったばかりなので、ラッピング関数は用意しておらず、すべてのメンバにアクセスできるようになっている。修正しなきゃ。。。

pub struct TraceInfo {
    pub m_trace_type: TraceType,
    pub m_trace_size: u32,
    pub m_trace_addr: AddrType,
    pub m_trace_value: XlenType,
    pub m_trace_memresult: MemResult, /* Memory Access Result */
}
pub struct Tracer {
    pub m_priv: PrivMode,
    pub m_vmmode: VMMode,
    pub m_executed_pc: AddrType,
    pub m_executed_phypc: AddrType,
    pub m_inst_hex: InstType,
    pub m_step: u32,

    pub m_trace_info: Vec<TraceInfo>,
}

m_trace_infoレジスタリード・レジスタライト・メモリアクセスのトレースを格納していく。TraceInfo構造体を、ベクトルにしてPushする形で実行する。

    fn read_reg(&mut self, reg_addr: RegAddrType) -> XlenType {
        let ret_val: XlenType;

...
        let mut read_reg_trace = TraceInfo::new();
        read_reg_trace.m_trace_type = TraceType::XRegRead;
        read_reg_trace.m_trace_addr = reg_addr as AddrType;
        read_reg_trace.m_trace_value = ret_val;
        read_reg_trace.m_trace_memresult = MemResult::NoExcept;

        self.m_trace.m_trace_info.push(read_reg_trace);
...
    fn read_bus_word(&mut self, addr: AddrType) -> XlenType {
        // let result: MemResult;
        // let phy_addr: AddrType;
        let (_result, phy_addr) = self.convert_virtual_address(addr, MemAccType::Fetch);
...
        let mut read_mem_trace = TraceInfo::new();

        read_mem_trace.m_trace_type = TraceType::MemRead;
        read_mem_trace.m_trace_addr = addr;
        read_mem_trace.m_trace_value = ret_val;
        read_mem_trace.m_trace_memresult = MemResult::NoExcept;

        self.m_trace.m_trace_info.push(read_mem_trace);

これを命令実行毎に最後にまとめて出力する。これでトレースデータとする。

    fn print_trace(&mut self) {
        print!("{:10}:", self.m_step);
        print!(
            "{}:",
            match self.m_priv {
                PrivMode::User => "U",
                PrivMode::Supervisor => "S",
...
                VMMode::Sv39 => "Sv39",
                VMMode::Sv48 => "Sv48",
                VMMode::Sv57 => "Sv57",
                VMMode::Sv64 => "Sv64",
            }
        );
        print!("{:08x}:{:08x}:", self.m_executed_pc, self.m_inst_hex);

        for trace_idx in 0..self.m_trace_info.len() {
            match self.m_trace_info[trace_idx].m_trace_type {
                TraceType::XRegWrite => {
                    print!(
                        "x{:02}<={:08x} ",
...

                _ => {}
            }
        }
        println!("  // DASM({:08x})", self.m_inst_hex);
    }

最後はDASM()で締める。DASM(命令の16進数コード)を出力しておけば、spike-dasmによって命令名に変換して表示できるので、楽だからだ。

         0:M:Bare:80000000:00c0006f:  // DASM(00c0006f)

これが、spke-dasmをPipeすることにより、

         0:M:Bare:80000000:00c0006f:  // j       pc + 0xc

このように変換される。最初のデバッグにはとても便利。最終的にはオリジナルのトレース出力に変えるけれども。

このようなトレースデータが出力される。デバッグがはかどる。

         0:M:Bare:80000000:00c0006f:  // j       pc + 0xc
         1:M:Bare:8000000c:00000297:x05<=8000000c   // auipc   t0, 0x0
         2:M:Bare:80000010:ffc28293:x05=>8000000c x05<=80000008   // addi    t0, t0, -4
         3:M:Bare:80000014:30529073:x05=>80000008   // csrw    mtvec, t0
         4:M:Bare:80000018:00006117:x02<=80006018   // auipc   sp, 0x6
         5:M:Bare:8000001c:35010113:x02=>80006018 x02<=80006368   // addi    sp, sp, 848
         6:M:Bare:80000020:f14022f3:x00=>00000000 x05<=00000000   // csrr    t0, mhartid
         7:M:Bare:80000024:00c29293:x05=>00000000 x05<=00000000   // slli    t0, t0, 12
         8:M:Bare:80000028:00510133:x02=>80006368 x05=>00000000 x02<=80006368   // add     sp, sp, t0
         9:M:Bare:8000002c:34011073:x02=>80006368   // csrw    mscratch, sp
        10:M:Bare:80000030:3b1020ef:x01<=80000034   // jal     pc + 0x2bb0
        11:M:Bare:80002be0:00008067:x01=>80000034   // ret
        12:M:Bare:80000034:00003517:x10<=80003034   // auipc   a0, 0x3
        13:M:Bare:80000038:bb050513:x10=>80003034 x10<=80002be4   // addi    a0, a0, -1104
        14:M:Bare:8000003c:2050206f:  // j       pc + 0x2a04
        15:M:Bare:80002a40:f14027f3:x00=>00000000 x15<=00000000   // csrr    a5, mhartid
        16:M:Bare:80002a44:14079a63:x15=>00000000 x00=>00000000   // bnez    a5, pc + 340
        17:M:Bare:80002a48:00001797:x15<=80003a48   // auipc   a5, 0x1
        18:M:Bare:80002a4c:5b878793:x15=>80003a48 x15<=80004000   // addi    a5, a5, 1464

Chiselでビットコインマイナーを設計してみる(3. Scala版とVerilog版の検証と速度比較)

f:id:msyksphinz:20180702001454p:plain

ChiselでSHA256の回路を設計するプロジェクト、Scalaでの検証用コードと、Chiselで書いたハードウェアを検証する。

Chiselで書いたハードウェアは、SHA256のアルゴリズムで64回分ループを回す部分を展開し、パイプライン化した。 これにより、1サイクル毎にSHA256を計算できるマイニング用の回路として使用できる。

f:id:msyksphinz:20190324214550p:plain
今回開発したSHA256回路の概要
f:id:msyksphinz:20190324212837p:plain
SHA256のパイプライン回路

SHA256回路の検証は、Scala側のコードとChiselのコードを突き合せる形で行う。 しかし、Scalaのテストはシーケンシャルに記述する方法しか紹介されておらず、うまく検証コードを記述することができない。

SHA256回路の検証コードは未完成だが、とりあえず最初の32回分のSHA256の計算を突き合せて、一致することが確認できた。

ChiselコードとVerilogコードの検証速度比較

Chiselの速度を確かめるために、Verilatorを用いてChiselから生成されたVerilogコードのシミュレーションと、Chiselの状態でVerilogを使用せずに、Scalaの環境だけを使用した検証での速度を比較した。

  • Scala環境 : Chiselのみ : Total time: 70[s]
  • Verilator環境、波形なし : Total time: 427[s]
  • Verilator環境、波形あり : Total time: 438[s]

Chiselのコードの方が速い。もっとも、ChiselからVerilogへの変換と、Verilatorのコンパイルに時間がかかっているように見えるが。。。

オリジナルLLVM Backendを追加しよう (21. Function Callの実装)

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

jonathan2251.github.io

第9章は、関数コールの実装だ。関数コールの実装は量が多すぎてまだ理解が及んでいないが、とりあえずJumpLinkの実装が難しい。

  • lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
/// Arithmetic Instructions (3-Operand, R-Type)
def JALR : JumpFR<0b1100111, 0b000, "jalr", GPR>;
// def : InstAlias<"jr $rs1", (JALR ZERO, GPR:$rs1, 0)>;
def RET  : RetBase<GPR>;

関数コールには基本的にJALR命令を使うことになると思うので、MYRISCVXJmpLinkを定義して、この上で命令を組み立てていくことにする。

  • lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
def SDT_MYRISCVXJmpLink : SDTypeProfile<0, 2, [SDTCisVT<1, iPTR>]>;
...
// Call
def MYRISCVXJmpLink : SDNode<"MYRISCVXISD::JmpLink",SDT_MYRISCVXJmpLink,
                         [SDNPHasChain, SDNPOutGlue, SDNPOptInGlue,
                         SDNPVariadic]>;
...
def : Pat<(MYRISCVXJmpLink GPR:$rd, (i32 tglobaladdr:$dst)),
          (JALR GPR:$rd, tglobaladdr:$dst)>;
def : Pat<(MYRISCVXJmpLink GPR:$rd, (i32 texternalsym:$dst)),
          (JALR GPR:$rd, texternalsym:$dst)>;

ここでテスト中に悩んでしまっているのだが、ch9_1.cppを実行すると以下のようにエラーが発生した。

./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm ch9_1.bc -o -
Selecting: t48: ch = MYRISCVXISD::Ret t47, Register:i32 $a0, t47:1
Selecting: t47: ch,glue = CopyToReg t45, Register:i32 $a0, t46
Selecting: t46: i32,ch = load<(dereferenceable load 4 from %ir.a)> t45, FrameIndex:i32<1>, undef:i32
Selecting: t45: ch = store<(store 4 into %ir.a)> t43:1, t43, FrameIndex:i32<1>, undef:i32
Selecting: t43: i32,ch,glue = CopyFromReg t42, Register:i32 $a0, t42:1
Selecting: t42: ch,glue = callseq_end t41, TargetConstant:i32<24>, TargetConstant:i32<0>, t41:1
Selecting: t41: ch,glue = MYRISCVXISD::JmpLink t29, Register:i32 $gp, Register:i32 $a0, Register:i32 $a1, Register:i32 $gp, RegisterMask:Untyped, t39:1
LLVM ERROR: Cannot select: t41: ch,glue = MYRISCVXISD::JmpLink t29, Register:i32 $gp, Register:i32 $a0, Register:i32 $a1, Register:i32 $gp, RegisterMask:Untyped, t39:1
  t31: i32 = Register $gp
  t35: i32 = Register $a0
...

どうもMYRISCVXISD::JmpLinkの命令が作れないのか?

        addi    x2, x2, -8
        sw      x0, 4(x2)
opcode = 126
Pseudo opcode found in EmitInstruction()
UNREACHABLE executed at /home/masayuki/others/riscv/llvm/llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.cpp:120!
Stack dump:

以下のようにスタックダンプが出て、Pseudo命令をプリントしてしまいそうな場合は、以下の生成されたファイルをチェックする。

  • build-myriscvx/lib/Target/MYRISCVX/MYRISCVXGenInstrInfo.inc.tmp
namespace MYRISCVX {
  enum {
    PHI = 0,
...
    G_BLOCK_ADDR    = 125,
    ADJCALLSTACKDOWN    = 126,
    ADJCALLSTACKUP  = 127,
    CPRESTORE   = 128,
...

この場合は、ADJCALLSTACKDOWN, ADJCALLSTACKUPが疑似命令であるので、これを削除しなければならない。このためには、eliminateCallFramePseudoInstrを追加する。

diff --git a/lib/Target/MYRISCVX/MYRISCVXFrameLowering.cpp b/lib/Target/MYRISCVX/MYRISCVXFrameLowering.cpp
index e6f64bfb642..78cdaed47d5 100644
--- a/lib/Target/MYRISCVX/MYRISCVXFrameLowering.cpp
+++ b/lib/Target/MYRISCVX/MYRISCVXFrameLowering.cpp
@@ -95,3 +95,11 @@ bool MYRISCVXFrameLowering::hasFP(const MachineFunction &MF) const {
       MFI.hasVarSizedObjects() || MFI.isFrameAddressTaken() ||
       TRI->needsStackRealignment(MF);
 }
+
+
+// Eliminate ADJCALLSTACKDOWN, ADJCALLSTACKUP pseudo instructions
+MachineBasicBlock::iterator MYRISCVXFrameLowering::
+eliminateCallFramePseudoInstr(MachineFunction &MF, MachineBasicBlock &MBB,
+                              MachineBasicBlock::iterator I) const {
+  return MBB.erase(I);
+}
diff --git a/lib/Target/MYRISCVX/MYRISCVXFrameLowering.h b/lib/Target/MYRISCVX/MYRISCVXFrameLowering.h
index 8e605a1c204..14fd90f6544 100644
--- a/lib/Target/MYRISCVX/MYRISCVXFrameLowering.h
+++ b/lib/Target/MYRISCVX/MYRISCVXFrameLowering.h
@@ -31,6 +31,11 @@ namespace llvm {
     static const MYRISCVXFrameLowering *create(const MYRISCVXSubtarget &ST);

     bool hasFP(const MachineFunction &MF) const override;
+
+    MachineBasicBlock::iterator
+    eliminateCallFramePseudoInstr(MachineFunction &MF,
+                                  MachineBasicBlock &MBB,
+                                  MachineBasicBlock::iterator I) const override;
   };

   /// Create MYRISCVXFrameLowering objects.
diff --git a/lib/Target/MYRISCVX/MYRISCVXInstrInfo.cpp b/lib/Target/MYRISCVX/MYRISCVXInstrInfo.cpp
index 0b7b40ec6ed..f86d5780f00 100644
--- a/lib/Target/MYRISCVX/MYRISCVXInstrInfo.cpp
+++ b/lib/Target/MYRISCVX/MYRISCVXInstrInfo.cpp
@@ -29,7 +29,9 @@ void MYRISCVXInstrInfo::anchor() {}

 //@MYRISCVXInstrInfo {
 MYRISCVXInstrInfo::MYRISCVXInstrInfo(const MYRISCVXSubtarget &STI)
-    : Subtarget(STI) {}
+    :
+    MYRISCVXGenInstrInfo(MYRISCVX::ADJCALLSTACKDOWN, MYRISCVX::ADJCALLSTACKUP),
+    Subtarget(STI) {}

 const MYRISCVXInstrInfo *MYRISCVXInstrInfo::create(MYRISCVXSubtarget &STI) {
   return llvm::createMYRISCVXSEInstrInfo(STI);

これでch9_1.cppコンパイルして実行した。無事に命令が生成できたようだ。

./bin/clang -c -target mips ../lbdex/input/ch9_1.cpp -emit-llvm
./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm ch9_1.bc -o -
_Z5sum_iiiiiii:
        .frame  $x8,16,$x1
        .mask   0x00000000,0
        .set    noreorder
        .cpload $t9
        .set    nomacro
# %bb.0:                                # %entry
        lui     x10, %hi(_gp_disp)
        addi    x10, x10, %lo(_gp_disp)
        addi    x2, x2, -16
        lw      x12, 36(x2)
        lw      x12, 32(x2)
        lw      x12, 28(x2)
        lw      x12, 24(x2)
        sw      x10, 12(x2)
        sw      x11, 8(x2)
        lui     x10, %got_hi(gI)
        add     x10, x10, x3
        lw      x10, %got_lo(gI)(x10)
        lw      x10, 0(x10)
        lw      x11, 12(x2)
        add     x10, x10, x11
        lw      x11, 8(x2)
        add     x10, x10, x11
        lw      x11, 24(x2)
        add     x10, x10, x11
        lw      x11, 28(x2)
        add     x10, x10, x11
        lw      x11, 32(x2)
        add     x10, x10, x11
        lw      x11, 36(x2)
        add     x10, x10, x11
        sw      x10, 4(x2)
        lw      x10, 4(x2)
        addi    x1, x10, 0
        addi    x2, x2, 16
        jalr    x1
        .set    macro
        .set    reorder
        .end    _Z5sum_iiiiiii
...
main:
        .frame  $x8,32,$x1
        .mask   0x00000000,0
        .set    noreorder
        .cpload $t9
        .set    nomacro
# %bb.0:                                # %entry
        lui     x10, %hi(_gp_disp)
        addi    x10, x10, %lo(_gp_disp)
        addi    x2, x2, -32
        sw      x0, 28(x2)
        addi    x10, x0, 6
        sw      x10, 20(x2)
        addi    x10, x0, 5
        sw      x10, 16(x2)
        addi    x10, x0, 4
        sw      x10, 12(x2)
        addi    x10, x0, 3
        sw      x10, 8(x2)
        lw      x3, %call16(_Z5sum_iiiiiii)(x3)
        addi    x10, x0, 1
        addi    x11, x0, 2
        jalr    x3
        sw      x10, 24(x2)
        lw      x10, 24(x2)
        addi    x1, x10, 0
        addi    x2, x2, 32
        jalr    x1
        .set    macro
        .set    reorder
        .end    main
$func_end1:
f:id:msyksphinz:20190324150547j:plain

Chiselでビットコインマイナーを設計してみる(2. ScalaでSHA256の検証プログラムを書く)

f:id:msyksphinz:20180702001454p:plain

SHA256の回路をChiselで開発しようプロジェクト、とりあえず基本的な回路は記述したので、次に検証用のテストコードを書いていく。

検証用のテストコードはScalaで書く。その方がChiselと親和性が良いし、検証が行いやすいと思う。

という訳で、とりあえずScalaでSHA256のアルゴリズムを記述してみた。

ハマったのはScalaにはUnsigned Intが存在しないこと。とりあえず、BigIntで記述してそれを最後にすべて0xffffffffULで切り取ることでUnsigned Intと同等の処理になるように変換した。

  def scala_test() {
    val Ks = Array[BigInt](
...
    )

    val data = Array[BigInt](BigInt(0x61626380L), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

    def Rot(x: BigInt, rot: Int): BigInt = {
      return ((x << (32-rot)) | (x >> rot)) & BigInt(0x0ffffffffL)
    }
    def SIGMA_0(x: BigInt): BigInt = {
      return (Rot(x, 2) ^ Rot(x, 13) ^ Rot(x, 22)) & BigInt(0x0ffffffffL)
    }
    def SIGMA_1(x: BigInt): BigInt = {
      return (Rot(x, 6) ^ Rot(x, 11) ^ Rot(x, 25)) & BigInt(0x0ffffffffL)
    }
    def S_0(x: BigInt): BigInt = {
      return (Rot(x, 7) ^ Rot(x, 18) ^ (x >> 3)) & BigInt(0x0ffffffffL)
    }
    def S_1(x: BigInt): BigInt = {
      return (Rot(x, 17) ^ Rot(x, 19) ^ (x >> 10)) & BigInt(0x0ffffffffL)
    }
    def CH(x: BigInt, y: BigInt, z: BigInt): BigInt = {
      return ((x & y) ^ (~x & z)) & BigInt(0x0ffffffffL)
    }
    def MAJ(x: BigInt,y: BigInt, z: BigInt): BigInt = {
      return ((x & y) ^ (x & z) ^ (y & z)) & BigInt(0x0ffffffffL)
    }

    val w = new Array[BigInt](64)
    for (t <- 0 until 16) {
      w(t) = data(t)
    }
    for (t <- 16 until 64) {
      w(t) = S_1(w(t-2)) + w(t-7) + S_0(w(t-15)) + w(t-16)
    }

    var a: BigInt = BigInt(0x06a09e667L)
    var b: BigInt = BigInt(0x0bb67ae85L)
    var c: BigInt = BigInt(0x03c6ef372L)
    var d: BigInt = BigInt(0x0a54ff53aL)
    var e: BigInt = BigInt(0x0510e527fL)
    var f: BigInt = BigInt(0x09b05688cL)
    var g: BigInt = BigInt(0x01f83d9abL)
    var h: BigInt = BigInt(0x05be0cd19L)

    for (t <- 0 until 1) {
      val t1 = (h + SIGMA_1(e) + CH(e, f, g) + Ks(t) + w(t)) & BigInt(0x0ffffffffL)
      val t2 = (SIGMA_0(a) + MAJ(a, b, c)) & BigInt(0x0ffffffffL)

      h = g
      g = f
      f = e
      e = (d + t1) & BigInt(0x0ffffffffL)
      d = c
      c = b
      b = a
      a = (t1 + t2) & BigInt(0x0ffffffffL)

      printf("a = %x, b = %x, c = %x, d = %x, ", a, b, c, d)
      printf("e = %x, f = %x, g = %x, h = %x, ", e, f, g, h)
      printf("\n")
    }
  }

とりあえずここまでは良いが、次にChiselと一致させながら検証を実行していかないといけない。これは後日。

Chiselでビットコインマイナーを設計してみる(1. SHA256コアの開発)

f:id:msyksphinz:20180702001454p:plain

Vivado-HLSで観察していたビットコインマイニング回路の開発、面白そうなのでChiselに移植してみることにした。

勘違いしがちだがChiselは高位合成システムではなく、基本的にRTLの記述レベルを維持しつつ、Scala言語の良いところを取り入れてよりソフトウェアっぽく書くという方法なので、まずはSystem Verilogで書かれたSHA256回路をChiselに移植しても何の問題もない。

まずは、System Verilogで書かれたSHA256回路をChiselに簡単に移植してみることにした。

github.com

参考にしたのは上記のリポジトリで、まずはSHA256の回路をChiselで書き直してみる。 どうもオリジナルの回路は256ビットとか512ビットとか、大きなベクトルを使って設計してある。 ただ、Chiselはこういうビット処理は苦手だし、どうせ32ビット単位で処理されるんだろうって分かっていたので、32ビットの配列を組み上げる形にインタフェースを変更した。

class sha256_pipe2_base (STAGES: Int = 64) extends Module {
  val io = IO (new Bundle {
    val i_state = Input(Vec(8, UInt(32.W)))
    val i_data = Input(Vec(16, UInt(32.W)))
    val out = Output(Vec(8, UInt(32.W)))
  })

SHA256の変換回路は、functionを使って書き直す。こっちの方がデバッグし易いと思ったからだ。

  def E0(x: UInt): UInt = {
    return Cat(x(1,0), x(31,2)) ^ Cat(x(12,0), x(31,13)) ^ Cat(x(21,0), x(31,22))
  }
  def E1(x: UInt): UInt = {
    return Cat(x(5,0), x(31,6)) ^ Cat(x(10,0), x(31,11)) ^ Cat(x(24,0), x(31,25))
  }

32ビット単位でのビットシフトも、以下のようにfor文を使って記述している。

      data_buf := data(i-1)
      for (j <- 0 until 15) {
        data(i)(j) := data_buf(j+1)
      }
      data15_p1 := S1(data(i-1)(15))                                          // 3
      data15_p2(i) := data15_p1     // 1

とりあえず、ざっくりとChiselに移植して、マイニング用(?、あまりマイニング用なSHA256回路が分かっていないので、もう少し勉強が必要だ)の回路を構成してみた。

class sha256_pipe130 extends Module {
  val io = IO(new Bundle {
    val state = Input(Vec(8, UInt(32.W)))
    val state2 = Input(Vec(8, UInt(32.W)))
    val data = Input(Vec(16, UInt(32.W)))
    val hash = Output(Vec(8, UInt(32.W)))
  })
  val out = Wire(Vec(8, UInt(32.W)))

  val hash_r = Reg(Vec(8, UInt(32.W)))

  val P = Module(new sha256_pipe2_base (64))
  P.io.i_state := io.state
  P.io.i_data := io.data
  out := P.io.out

  for (i <- 0 until 8) {
    hash_r(i) := io.state2(i) + out(i)
    io.hash(i) := hash_r(i)
  }
}

これで、まずはVerilog回路を生成してみる。

sbt 'runMain sha_256.sha256_pipe130'

まずは生成できた。インタフェースは以下のように配列で用意される。解析はちょっと面倒だけど...

module sha256_pipe130( // @[:@8503.2]
  input         clock, // @[:@8504.4]
  input         reset, // @[:@8505.4]
  input  [31:0] io_state_0, // @[:@8506.4]
  input  [31:0] io_state_1, // @[:@8506.4]
  input  [31:0] io_state_2, // @[:@8506.4]
...
  input  [31:0] io_state2_0, // @[:@8506.4]
  input  [31:0] io_state2_1, // @[:@8506.4]
  input  [31:0] io_state2_2, // @[:@8506.4]
...
  input  [31:0] io_data_0, // @[:@8506.4]
  input  [31:0] io_data_1, // @[:@8506.4]
  input  [31:0] io_data_2, // @[:@8506.4]
...
  output [31:0] io_hash_0, // @[:@8506.4]
  output [31:0] io_hash_1, // @[:@8506.4]
  output [31:0] io_hash_2, // @[:@8506.4]
...

LLVM 8.0がリリースされたのでビルド試行とオリジナル実装の8.0への移行

f:id:msyksphinz:20181123225150p:plain

releases.llvm.org

LLVM 8.0がリリースされたので、さっそくダウンロードしてビルドしてみた。

これまで通り、GitHub経由でダウンロードしていたので、タグをrelease_80にアップデートするだけである。

$ cmake -G Ninja -DCMAKE_CXX_COMPILER=${HOME}/others/usr/bin/clang++ -DCMAKE_C_COMPILER=${HOME}/others/usr/bin/clang -DCMAKE_BUILD_TYPE="Debug" -DLLVM_TARGETS_TO_BUILD="Mips" -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="RISCV" ../llvm-myriscvx
$ cmake --build .

一応、問題なくビルドはできたようだ。一応llcのバージョンを確認してみると、riscv32とriscv64がサポートされている!素晴らしい。

$ ./bin/llc --version
LLVM (http://llvm.org/):
  LLVM version 8.0.0
  DEBUG build with assertions.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: haswell

  Registered Targets:
    mips     - MIPS (32-bit big endian)
    mips64   - MIPS (64-bit big endian)
    mips64el - MIPS (64-bit little endian)
    mipsel   - MIPS (32-bit little endian)
    riscv32  - 32-bit RISC-V
    riscv64  - 64-bit RISC-V

ついでに、これまでLLVM 7.0ベースで作業をしていたオリジナルRISC-V実装の対応作業を、LLVM 8.0に移行してビルドできるか試行してみよう。

現在の作業ブランチであるmyriscvx/implmyriscvx80/implに変更し、ベースとなるブランチをrelease_70からrelease_80へ移行しよう。これにはgit rebase --ontoを使用する。

git rebase --onto release_80 origin/release_70 myriscvx80/impl

これでベースとなるブランチがrelease_80に変更された。これでビルド試行を行う。

/home/msyksphinz/work/riscv/llvm/llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXTargetMachine.cpp:82:25: error: no matching function for call to 'getEffectiveCodeModel'
                        getEffectiveCodeModel(CM), OL),
                        ^~~~~~~~~~~~~~~~~~~~~

getEffectiveCodeModelに多少の変更が加わっているようだ。この部分だけ変更する。

diff --git a/lib/Target/MYRISCVX/MYRISCVXTargetMachine.cpp b/lib/Target/MYRISCVX/MYRISCVXTargetMachine.cpp
index bfa94e6041f..7112b6b5a8a 100644
--- a/lib/Target/MYRISCVX/MYRISCVXTargetMachine.cpp
+++ b/lib/Target/MYRISCVX/MYRISCVXTargetMachine.cpp
@@ -56,7 +56,8 @@ static Reloc::Model getEffectiveRelocModel(Optional<CodeModel::Model> CM,
 }


-static CodeModel::Model getEffectiveCodeModel(Optional<CodeModel::Model> CM) {
+static CodeModel::Model getEffectiveCodeModel(Optional<CodeModel::Model> CM,
+                                              CodeModel::Model Default) {
   if (CM)
     return *CM;
   return CodeModel::Small;
@@ -79,7 +80,7 @@ MYRISCVXTargetMachine::MYRISCVXTargetMachine(const Target &T, const Triple &TT,
     //- Default is big endian
     : LLVMTargetMachine(T, computeDataLayout(TT, CPU, Options), TT, CPU, FS, Options,
                         getEffectiveRelocModel(CM, RM),
-                        getEffectiveCodeModel(CM), OL),
+                        getEffectiveCodeModel(CM, CodeModel::Small), OL),
       TLOF(make_unique<MYRISCVXTargetObjectFile>()),
       ABI(MYRISCVXABIInfo::computeTargetABI()),
       DefaultSubtarget(TT, CPU, FS, *this) {

これで一応ビルドが完了した。多少の変更は必要なようだ。

Vivado-HLSを用いたビットコインマイニング回路 (1. サンプルプログラムの試行)

ビットコインといえば一昨年くらいから急に盛り上がりだして、猫も杓子もビットコインという状態になりかけたけれども急にまた暴落して皆辞めてしまったという悲しい技術ではあるが、私も流行に乗ってビットコインについて勉強した手前、なにか自分で手を動かすアウトプットをしたいと思っている。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

Vivado-HLSの高位合成の試行ではCPUを作ってみたけれども、いまいち納得できるデザインを作ることができなかったので、気分を変えてビットコイン・マイニングの回路をVivado-HLSで作ってみたらどうだろう、と思って調べてみたら既にやっている人がいた。

github.com

さっそくダウンロードして実行してみる。Makefileは用意されていなかったので、適当にリンクを張ってVivado HLSを立ち上げた。

git clone https://github.com/stiggy87/ZynqBTC.git
cd ZynqBTC
vivado_hls script.tcl

これだと一発目で失敗するので、

cd zynqBTC/solution2
ln -s ../../directives.tcl
cd -

directives.tclをリンクするとうまくいった。

vivado_hls script.tcl

ちなみに、中身は非常にあっけない。単純にSHAのアルゴリズムが記述してあるだけだ。 Vivado-HLSはやはり便利だ。

   g = ctx_transform->state[6];
   h = ctx_transform->state[7];

   for (i = 0; i < 64; ++i) {
      t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
      t2 = EP0(a) + MAJ(a,b,c);
      h = g;
      g = f;
      f = e;
      e = d + t1;
      d = c;
      c = b;
      b = a;
      a = t1 + t2;
   }

   ctx_transform->state[0] += a;
   ctx_transform->state[1] += b;
   ctx_transform->state[2] += c;

合成結果をとりあえずVivado-HLSで見てみた。良く分からないけれども、とりあえずCosimulationで落ちているので見てみる必要があるかな。

f:id:msyksphinz:20190320011021p:plain