UCBの開発しているシミュレータSpikeは、RISC-Vの命令セットをシミュレーションすることのできるISSで、サイクル精度を出すことはできないが最初のアプリケーションのデバッグに有用なツールだ。
そして、RISC-Vにはカスタム命令 (custom0 - custom3)が用意されており、Rocket Chipの場合にはこの命令フィールドを用いてRoCCインタフェース上にアクセラレータを搭載することが出来る。
どうせ元はChiselなのだから、この辺をうまくマージすることが出来ないだろうかといろいろ調査していたのだが、どうやらRoCCインタフェースをSpikeシミュレータではシミュレーションすることが出来るようだ。
riscv-isa-simのリポジトリを眺めていると、RoCCの型が定義されており、そこでダミーのRoCCを定義してシミュレーションをすることが出来る。
- riscv-isa-sim/riscv/extension.h (https://github.com/riscv/riscv-isa-sim/blob/master/riscv/extension.h)
class extension_t { public: virtual std::vector<insn_desc_t> get_instructions() = 0; virtual std::vector<disasm_insn_t*> get_disasms() = 0; virtual const char* name() = 0; virtual void reset() {}; virtual void set_debug(bool value) {}; virtual ~extension_t(); void set_processor(processor_t* _p) { p = _p; } protected: processor_t* p; void illegal_instruction(); void raise_interrupt(); void clear_interrupt(); };
- riscv-isa-sim/riscv/rocc.h (https://github.com/riscv/riscv-isa-sim/blob/master/riscv/rocc.h)
struct rocc_insn_t { unsigned opcode : 7; unsigned rd : 5; unsigned xs2 : 1; unsigned xs1 : 1; unsigned xd : 1; unsigned rs1 : 5; unsigned rs2 : 5; unsigned funct : 7; }; union rocc_insn_union_t { rocc_insn_t r; insn_t i; }; class rocc_t : public extension_t { public: virtual reg_t custom0(rocc_insn_t insn, reg_t xs1, reg_t xs2); virtual reg_t custom1(rocc_insn_t insn, reg_t xs1, reg_t xs2); virtual reg_t custom2(rocc_insn_t insn, reg_t xs1, reg_t xs2); virtual reg_t custom3(rocc_insn_t insn, reg_t xs1, reg_t xs2); std::vector<insn_desc_t> get_instructions(); std::vector<disasm_insn_t*> get_disasms(); };
例えば、riscv-isa-sim/riscv/dummy_rocc.cc
にはサンプルのデザインが書いてある。これは、Rocket-ChipにおけるAccumulatorExample
のデザインそのままだね。
- riscv-isa-sim/dummy_rocc/dummy_rocc.cc (https://github.com/riscv/riscv-isa-sim/blob/master/dummy_rocc/dummy_rocc.cc)
reg_t custom0(rocc_insn_t insn, reg_t xs1, reg_t xs2) { reg_t prev_acc = acc[insn.rs2]; if (insn.rs2 >= num_acc) illegal_instruction(); switch (insn.funct) { case 0: // acc <- xs1 acc[insn.rs2] = xs1; break; case 1: // xd <- acc (the only real work is the return statement below) break; case 2: // acc[rs2] <- Mem[xs1] acc[insn.rs2] = p->get_mmu()->load_uint64(xs1); break; case 3: // acc[rs2] <- accX + xs1 acc[insn.rs2] += xs1; break; default: illegal_instruction(); } return prev_acc; // in all cases, xd <- previous value of acc[rs2] }