FPGA開発日記

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

MIPS Openがリソース公開

リソースの一部を公開するとアナウンスしたMIPSだが、この度一部の資料が公開されたようだ。

www.mipsopen.com

ダウンロードできる資料も増えたようだ。

ダウンロードできる資料としては、

  • MIPS Open Architecture (仕様書)
    • Baseline 32 and 64-bit MIPS Instruction Set Architecture (ISA), Release 6
    • MIPS SIMD Extensions v1.0
    • MIPS DSP Extensions
    • MIPS Multi-Threading (MT)
    • MIPS MCU
    • microMIPS Architecture
    • MIPS Virtualization (VZ)
  • 開発ツール (IDE)
  • Open FPGA
    • MIPS Open Getting Started Package
    • MIPS Open FPGA Labs (Nexy A7 もしくは DE2-115 向け)
    • MIPS Open FPGA SOC
  • MIPS Open Cores
    • 2019/05/23追記。microAptiv uC Core
    • 2019/05/23追記。microAptiv uP Core
    • どちらもダウンロードした。猛烈なマニュアルの量で圧倒される。

という訳で、一部の資料とFPGAのリソースはダウンロードできるようだ。ただしユーザ登録が必要。 ユーザ登録をして、資料とIDEの環境は見てみると面白いかもしれない。

オリジナルLLVM Backendを追加しよう (22. テスト環境の調査)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181123/20181123225150.png

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

jonathan2251.github.io

関数コールもかなり動くようになってきて、これまでに多くのテストパタンを確認してきたのだが、リグレッションテストを作っておく必要があるように感じている。 LLVMのテストのインフラストラクチャーについて、どのようになっているのか調査した。

llvm.org

このあたりを見ていく。とりあえず、テスト環境を用意するためにはllvm-test-suiteを用意する必要があるらしい。

github.com

ビルドには、こちらを進めていく。

llvm.org

ビルドしたLLVM/home/msyksphinz/work/llvm/build-myriscvx/bin/clangを使用する。

git clone https://github.com/llvm/llvm-test-suite.git test-suite
mkdir test-suite-build
cd test-suite-build
cmake -DCMAKE_C_COMPILER=/home/msyksphinz/work/llvm/build-myriscvx/bin/clang \
        -C../test-suite/cmake/caches/O3.cmake \
        ../test-suite

makeを実行する。が、これが非常に時間がかかる。手元の環境では全く終わらなかった。何個か、テストを絞る必要があるのかもしれない。

make
...
[  5%] Building CXX object Bitcode/simd_ops/CMakeFiles/simd_ops_test_op_vpsubsw_358.dir/x86_scalar_tests/scalar_test_op_vpsubsw_358.bc.o
[  5%] Linking CXX executable simd_ops_test_op_vpsubsw_358
[  5%] Built target simd_ops_test_op_vpsubsw_358
Scanning dependencies of target simd_ops_test_op_vpsubb_350
[  5%] Building CXX object Bitcode/simd_ops/CMakeFiles/simd_ops_test_op_vpsubb_350.dir/x86_halide_runtime.bc.o
...

試しに、fftと名の付いているテストのみを実行してみる。これでも、非常に時間がかかる。

make fft -j6
[  0%] Built target timeit-target
[  0%] Built target build-HashProgramOutput.sh
[  0%] Built target fpcmp
[  0%] Built target build-timeit
[  0%] Building CXX object Bitcode/Regression/fft/CMakeFiles/fft.dir/modules/FftConvolve128x128xCZHalide_x86_64_linux_avx_c_plus_plus_name_mangling_no_runtime_sse41.bc.o
[  0%] Building CXX object Bitcode/Regression/fft/CMakeFiles/fft.dir/modules/FftConvolve128x128xCZHalide_x86_64_linux_c_plus_plus_name_mangling_no_runtime.bc.o
[  0%] Building CXX object Bitcode/Regression/fft/CMakeFiles/fft.dir/modules/FftConvolve128x128xCZHalide_x86_64_linux_c_plus_plus_name_mangling_no_runtime_sse41.bc.o
[  0%] Building CXX object Bitcode/Regression/fft/CMakeFiles/fft.dir/modules/FftConvolve128x128xRHalide.bc.o
[  0%] Building CXX object Bitcode/Regression/fft/CMakeFiles/fft.dir/modules/FftConvolve128x128xRHalide_x86_64_linux_avx_avx2_c_plus_plus_name_mangling_fma_no_runtime_sse41.bc.o
[  0%] Building CXX object Bitcode/Regression/fft/CMakeFiles/fft.dir/modules/FftConvolve128x128xRHalide_x86_64_linux_avx_c_plus_plus_name_mangling_no_runtime_sse41.bc.o
...

うーん、これはいつ終わるんだ?そして私のオリジナルアーキテクチャのテストパタンはどうやって追加したらいいんだ...?

RustでRISC-V命令セットシミュレータを作ろう (6. MMUの実装とテストパタンの実行)

f:id:msyksphinz:20190224185310p:plain

Rustで作るRISC-Vシミュレータ。基本的な形が出来上がった。トレースファイルも出せるようになったので、MMUを実装してテストパタンを実行して確認しよう。 MMUを動かすテストパタンは、rv{32,64}u{imfda}-v-xxxという形で表現されている。-v-というテストパタンが、MMUを使用するテストパタンだ。

MMUの実装自体は、C++で実装したものを流用している。ただし、Rustで実装するにあたりいろいろ修正しなければならない部分が存在した。

  • Rustでdecrementのfor文はどのように記述すればよいのか? MMUアルゴリズムの記述で、levelをderementしなければならない部分がある。とりあえず、あまり綺麗ではないけど以下のように記述した。 もう少し綺麗に書けないものだろうか...
        for level in range(0, init_level).rev() {
            let va_vpn_i: AddrType =
                (vaddr >> vpn_idx[level as usize]) & ((1 << vpn_len[level as usize]) - 1);
...

あとは、MMUのアクセス権限の確認はRead/Write/Execute(Fetch)でケースが異なるのだが、何となくmatchを使っている。この辺ももう少しRustっぽく書けないものだろうか...

        let allowed_access = match acc_type {
            MemAccType::Fetch => (i_type & 0x04) != 0,
            MemAccType::Write => ((i_type & 0x01) != 0) && ((i_type & 0x02) != 0),
            MemAccType::Read => {
                let mstatus: XlenType = self.m_csr.csrrs(CsrAddr::Mstatus, 0);
                let mxr: u8 = Self::extract_bit_field(
                    mstatus,
                    SYSREG_MSTATUS_MXR_MSB,
                    SYSREG_MSTATUS_MXR_LSB,
                ) as u8;
                ((i_type & 0x01) != 0) | ((mxr & (i_type & 0x04)) != 0)
            },
        };

とりあえずパタンをすべて流しなおした。今のところ、全体の9/94パタンがFAILなのでもう少しだ。

2019/03/28追記。ロード命令とストア命令の例外処理が抜けていた。例外処理を追加して無事にすべてのパタンが通過できた。

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と一致させながら検証を実行していかないといけない。これは後日。