Vivado-HLSを使って簡単なRISC-V CPUを作ってみている。
まずは性能を度外視して動くものを作る。 次に、性能を向上させるための様々な試行を実施する。 まずは、標準的なテストパタンをPassさせるために命令を追加していこう。
これまでは、CPUのクラスは存在していたが、実装がまとまっていなかったので、CPUクラスにまとめ上げていく。
CPUの実行は以下のループにまとめ上げることにした。
do { u_rv32_cpu.fetch_inst(); u_rv32_cpu.decode_inst(); u_rv32_cpu.execute_inst(); } while (!u_rv32_cpu.is_finish_cpu());
基本的に上記の3段にまとめた。
まず、fetch_inst()
は命令をフェッチする。
void rv32_cpu::fetch_inst () { m_inst = m_data_mem[m_pc >> 2]; }
次に、decode_inst()
は命令をデコードする。
void rv32_cpu::decode_inst () { switch(m_inst & 0x7f) { case 0x0f : { switch ((m_inst >> 12) & 0x07) { case 0b000 : m_dec_inst = FENCE; break; case 0b001 : m_dec_inst = FENCE_I; break; default : m_dec_inst = NOP; break; ...
最後に、execute_inst()
は命令実行のステージだ。
void rv32_cpu::execute_inst() { ... case AUIPC : { XLEN_t imm = ExtendSign (ExtractBitField (m_inst, 31, 12), 19); imm = SExtXlen(imm << 12) + m_pc & 0x0fff; write_reg(m_rd, imm); break; } case LW : { uint32_t addr = read_reg(m_rs1) + ((m_inst >> 20) & 0xfff); XLEN_t reg_data = mem_access(LOAD, read_reg(m_rs1), addr); write_reg(m_rd, reg_data); break; ...
RTL実装だと5段のパイプラインステージなどになるのだろうが、今回はVivado-HLSの高位合成なので適当に実装していく。あとでパイプラインを切って行けば良いか。
CPUのクラスには以下のようなメソッドを追加して、機能を1つのクラスにまとめ上げた。
XLEN_t mem_access (memtype_t op, uint32_t data, uint32_t addr); RegAddr_t get_rs1_addr (Inst_t inst) ; RegAddr_t get_rs2_addr (Inst_t inst) ; RegAddr_t get_rd_addr (Inst_t inst) ; XLEN_t read_reg(RegAddr_t addr); void write_reg(RegAddr_t addr, XLEN_t data); XLEN_t csrrw (uint16_t addr, XLEN_t data); XLEN_t csrrs (uint16_t addr, XLEN_t data); XLEN_t csrrc (uint16_t addr, XLEN_t data); // Utilitity for decoder uint32_t ExtendSign (uint32_t data, uint32_t msb); uint32_t ExtractBitField (uint32_t hex, uint32_t left, uint32_t right); uint32_t ExtractUJField (uint32_t hex); uint32_t ExtractIField (uint32_t hex); uint32_t ExtractSBField (uint32_t hex); uint32_t ExtractSHAMTField (uint32_t hex); inline XLEN_t SExtXlen (uint32_t hex) { return (hex << 32) >> 32; } inline uint32_t UExtXlen (uint32_t hex) { return (hex << 32) >> 32; } void fetch_inst (); void decode_inst (); void execute_inst(); bool is_finish_cpu() { return m_dec_inst == WFI; }
これでテストを実行した。とりあえず命令が進むようになったが、Cシミュレーション時にSegmentation Faultで落ちてしまう。 テストをすべて通すためには、もう少し解析が必要だ。
$ spike-dasm < cpu.log [00000000] : 04c0006f j pc + 0x4c [0000004c] : f1402573 csrr a0, mhartid x10 <= 00000000 [00000050] : 00051063 bnez a0, pc + 0 [00000054] : 00000297 auipc t0, 0x0 x05 <= 00000054 [00000058] : 01028293 addi t0, t0, 16 x05 <= 00000064 [0000005c] : 30529073 csrw mtvec, t0 [00000060] : 18005073 csrwi satp, 0 [00000064] : 00000297 auipc t0, 0x0 x05 <= 00000064 [00000068] : 02028293 addi t0, t0, 32 x05 <= 00000084 [0000006c] : 30529073 csrw mtvec, t0 [00000070] : 800002b7 lui t0, 0x80000 ...