Skip to content

Instantly share code, notes, and snippets.

@BeMg
Last active January 14, 2024 16:34
Show Gist options
  • Save BeMg/78ba92b1f2052a238124e96241218c2b to your computer and use it in GitHub Desktop.
Save BeMg/78ba92b1f2052a238124e96241218c2b to your computer and use it in GitHub Desktop.
spike tutorial

Spike tutorial

前言

spike 是 risc-v 的模擬器,讓 risc-v 的指令可以跑在任意的機器上,而不需要處理器有支援。本文主要在於紀錄如何在spike原有的基礎上,加上新的指令,以及如何測試新的指令。

大致上的 spike 流程為

c source code -> riscv-gcc -> riscv object code -> spike -> pk -> run on cpu

安裝

在安裝 spike 前,請安裝整個 risc-v tools。risc-v tools 的內容物如下:

  • riscv-gnu-toolchain
    • 就是gcc gdb g++ objdump 等等程式,會產生出符合 riscv 的機器指令,與對於這份指令的一些工具
  • riscv-fesvr
    • 待補
  • riscv-isa-sim
    • spike 的本體,相關程式放在這裡
  • riscv-opcodes
    • 定義指令的 opcode 與生成相對應的 MASK 與 MATCH
    • 如何生成的原理待補
  • riscv-pk
    • 姑且認為在處理來自於 spike 的 system call
  • riscv-tests
    • 待補

指令

sudo apt-get install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev libusb-1.0-0-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev device-tree-compiler pkg-config libexpat-dev
git clone https://github.com/riscv/riscv-tools.git
git submodule update --init --recursive
export RISCV=/path/to/install/riscv/toolchain
./build.sh

要記得加入 export RISCV=/where/RISCV/install/in

Spike 介紹

spike 就是用軟體模擬硬體(處理器)的行為,包含

  • 指令解碼
  • 記憶體操作
  • 維護暫存器
  • debug 模式 -> 用起來很像小的gdb
  • 執行指令行為
  • 其它

新增指令

新增指令最主要需要的是,要怎麼使得 spike 知曉你的新指令的行為與編碼。

行為

新指令的行為都會被定義在 riscv-isa-sim/riscv/insn中,形式上會被命名為 [instruction name].h。實際上在描述指令的行為就是在寫C++,只是在於絕大多數的指令有牽涉到regmem,所以 spike 會使用大量的 marco 去隱藏背後對於 regmem 操作的行為與化簡複雜度,對於第一次看到的人來說就會是一個艱鉅的挑戰。

那些marco被定義在 riscv-isa-sim/riscv/decode.h

如果你所需要的指令需要新的暫存器,我們後面會說明如何在 spike 的當前的基礎上,加入全新的暫存器。

編碼

大多數的指令都有屬於自己獨特的編碼,這些編碼由 riscv 規格書所規範。其規範如下圖

言下之意就是有些指令的編碼沒有,這種情況常常發生在指令寄生於其他指令之上,舉例來說 nop 就寄生在 addi 之上

opcode 另有定義,詳細可以看看 riscv spec 第十九章。

編碼的部份,我們要從riscv-opcodes/opcodes中新增,經過 parser-opcodes 就會轉成 encoding.h

把這份encodeing.h放置到riscv-isa-sim/riscv/下即可。

我們會需要的內容有

  • #define MATCH_[instruction name] 0x100b
  • #define MASK_[instruction name] 0x707f
  • DECLARE_INSN(XXX, MATCH_XXX, MASK_XXX)
cat opcodes opcodes-rvc-pseudo opcodes-rvc opcodes-custom | ./parse-opcodes -c

有一個取巧的辦法,可以用 grep 直接取的你新增的指令加入到encoding.h就好了

最後在 riscv-isa-sim/riscv/riscv.mk.in 新增指令,然後重新建構一次即可。

測試新指令

我們在 spike 所新增的指令只有 spike 知曉編碼與行為而

  • 編譯器不知道這個指令所以不會使用他在 code generation
  • 組譯器不知道這個指令所以不會翻譯指令為 machine code

最大的問題其實是,重新建置編譯的時間太久了。編譯一次要三十分鐘起跳怎麼受得了。

我們只好使用最原始的方法,手刻指令編碼。

手刻編碼

手刻編碼最重要的是要理解編碼的格式與需要的暫存器位置。

setvl為例

setvl <dest> <src>
[imm12 12bit] [src 5bit] [func 3bit] [dest 5bit] [opecode 7bit]

而使用的暫存器就與你實際的程式被編譯器所安排的如何了,我建議是把執行檔 objdump 出來看看你要的變數被安排到那一個暫存器,然後在根據那個暫存器去撰寫編碼。暫存器的對應圖如下:

x0 對應的號碼是 0b00000 x1 為 0b00001 依此類推

Spike 新增 暫存器

有些時候會需要新的暫存器去儲存資料,不論是程式的狀態抑或是純粹的資料。這個部份在於spike 都在 riscv-isa-sim/riscv/processor.h 中的 class state_t 中。多數的暫存器皆為reg_t 實際上就是 int64

對於新增的暫存器,我們會需要新的marco去操作它,可以將這些marco 定義在 riscv-isa-sim/riscv/decode.h 中。

用定義 marco 的方式去操作暫存器實際上只是為了美觀與維持一致的風格,直接去存取也不是不行

Risc-v vector extension in spike

前言

紀錄vector extension的設計與實作,協助更快的理解spike與vector reg的用法。

  • vector register
    • vector register 結構
    • vector register read write method
  • vector extension instruction format
    • opecode
    • Have been implement instruction
    • format

Vector Register

對於 vector extension 我們需要專門的暫存器去儲存,姑且在 riscv 規格書中有規定。只是沒有很詳盡。我們就暫時的將其宣告於 spike 之中。下圖為 riscv 規格書中的規範

整理一下之後針對於vector extension 列於下表:

  • v0-31
    • vector data register,就存資料,現在都是以存整數為主
  • vp0-7
    • vector predicate register 待補
  • vl
    • vector length 一個CSR 描述現在處理的vector的長度
  • 其他一堆CSR
    • 待補

實際上的實作

所有的暫存器都被存放在,riscv-isa-sim/riscv/processor.h

reg_t vl; //vector length register
regfile_t<vreg_t, NVPR, false> VPR; // vector data register

因為vector data register 是一串的值,且 spike 原本就有定義好 data registerread write method 於 regfile_t 中。

// regfile_t define decode.h
template <class T, size_t N, bool zero_reg>
class regfile_t
{
public:
  void write(size_t i, T value)
  {
    if (!zero_reg || i != 0)
      data[i] = value;
  }
  const T& operator [] (size_t i) const
  {
    return data[i];
  }
private:
  T data[N];
};

vreg_t 是我針對於vector 性質所自行定義的struct,其關注的點為vector的重點在於儲存一串數字。所以此struct中包含了一個陣列。

struct vreg_t{
    reg_t data[32];
};

姑且暫定最大長度的限制為32

對於 vector register 的 read and write

這部份的實作存在 riscv-isa-sim/riscv/decode.h中,事實上就是一連串的marco應用。下面會列出大致上這些marco背後運作的原理,以及我針對於vector register所增加的marco運作方式。

Read

#define RS1 READ_REG(insn.rs1())
#define RS2 READ_REG(insn.rs2())
#define READ_REG(reg) STATE.XPR[reg]

這部份的使用,通常會從RS1RS2開始,這裡是一個抽象化的體現,代表這個指令要取得為在 register source 1 的值(詳情請看 RISCV spec 第十九章)。我們稍微追蹤一下code的行為。

RS1 -> READ_REG(insn_rs1()) -> STATE.XPR[insn_rs1()]

實際行為待補

Write

#define WRITE_RD(value) WRITE_REG(insn.rd(), value)

實際行為待補

vector register read & write

#define WRITE_VL(value) STATE.vl = value

#define VRS1 READ_VREG(insn.rs1())
#define VRS2 READ_VREG(insn.rs2())
#define WRITE_VRD(value) WRITE_VREG(insn.rd(), value)
#define READ_VREG(reg) STATE.VPR[reg]
#define WRITE_VREG(reg, value) STATE.VPR.write(reg, value)

自己參悟一下

Vector extension instruction format

指令列表

Name Type Fromat encoding [31:0]
setvl I setvl <rd> <rs1> [000000000000][RS1][000][RD][0001011]
vld I vld <rd> <rs1> [000000000000][RS1][001][RD][0001011]
vadd I vadd <rd> <rs1> [000000000000][rs1][002][RD][0001011]
vst I vst <rd> <rs1> [000000000000][RS1][003][RD][0001011]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment