FPGA開発日記

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

Vivado-HLSを使って高位合成でCPUを作ってみる(3. CPUクラスへの実装まとめ上げとテストベンチ実行)

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
...
f:id:msyksphinz:20190215011547p:plain