FPGA開発日記

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

QEMUに入門してみる(17. CSR操作命令が実行される手順の解析)

とりあえずQEMUのブートコードだけ動かしたいので、次はCSR命令を実装しなければならない。RISC-VのCSR命令がQEMUでどのように実行されているかを解析しよう。

まず、CSR命令をinsn32.decodeに追加する。これでCSR命令のデコーダが自動的に追加され、trans_xxx関数を追加する必要性が発生する。

  • qemu/target/myriscvx/insn32.decode
@csr     ............   .....  ... ..... .......               %csr     %rs1 %rd

csrrw    ............     ..... 001 ..... 1110011 @csr
csrrs    ............     ..... 010 ..... 1110011 @csr
csrrc    ............     ..... 011 ..... 1110011 @csr
csrrwi   ............     ..... 101 ..... 1110011 @csr
csrrsi   ............     ..... 110 ..... 1110011 @csr
csrrci   ............     ..... 111 ..... 1110011 @csr
  • qemu/target/myriscvx/insn_trans/trans_rvi.inc.c
static bool trans_csrrw(DisasContext *ctx, arg_csrrw *a)
{
  TCGv source1, csr_store, dest, rs1_pass;
  MYRISCVX_OP_CSR_PRE;
  gen_helper_csrrw(dest, cpu_env, source1, csr_store);
  MYRISCVX_OP_CSR_POST;
  return true;
}

で、このtrans_csrrw()の中身だが、TCGvというのはTiny Code Generatorの変数であるとして、MYRISCVX_OP_CSR_PREMYRISCVX_OP_CSR_POSTはマクロである。すぐ上にその定義があるが、正直良く意味が分からない。

#define MYRISCVX_OP_CSR_PRE do {                   \
    source1 = tcg_temp_new();                   \
    csr_store = tcg_temp_new();                 \
    dest = tcg_temp_new();                      \
    rs1_pass = tcg_temp_new();                  \
    gen_get_gpr(source1, a->rs1);               \
    tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next); \
    tcg_gen_movi_tl(rs1_pass, a->rs1);          \
    tcg_gen_movi_tl(csr_store, a->csr);         \
    gen_io_start();                             \
  } while (0)

#define MYRISCVX_OP_CSR_POST do {                  \
    gen_set_gpr(a->rd, dest);                   \
    tcg_gen_movi_tl(cpu_pc, ctx->pc_succ_insn); \
    exit_tb(ctx);                               \
    ctx->base.is_jmp = DISAS_NORETURN;          \
    tcg_temp_free(source1);                     \
    tcg_temp_free(csr_store);                   \
    tcg_temp_free(dest);                        \
    tcg_temp_free(rs1_pass);                    \
  } while (0)

gen_helper_csrrw()だが、これは一応関数定義上どこにも存在しないので最初は少しびっくりする。実はマクロで定義されており、

  • qemu/target/myriscvx/helper.h
/* Special functions */
DEF_HELPER_3(csrrw, tl, env, tl, tl)
DEF_HELPER_4(csrrs, tl, env, tl, tl, tl)
DEF_HELPER_4(csrrc, tl, env, tl, tl, tl)

このDEF_HELPERマクロがgen_helper_csrrw()を定義している。このマクロは最終的にtcg_gen_callN(HELPER(csrrw))を呼び出している。このHELPE()マクロは、

#define HELPER(name) glue(helper_, name)

なので、結局helper_csrrw()を呼び出していることになる。これはqemu/target/myriscvx/op_helper.cに定義した。

  • qemu/target/myriscvx/op_helper.c
target_ulong helper_csrrw(CPUMYRISCVXState *env, target_ulong src,
                          target_ulong csr)
{
  target_ulong val = 0;
  if (myriscvx_csrrw(env, csr, &val, src, -1) < 0) {
    myriscvx_raise_exception(env, MYRISCVX_EXCP_ILLEGAL_INST, GETPC());
  }
  return val;
}
f:id:msyksphinz:20200722012716p:plain
trans_csrrs()が動作する手順

C言語で記述してあるmyriscvx_csrrw()を呼び出す。これはqemu/target/myriscvx/csr.cに記述した。

/*
 * myriscvx_csrrw - read and/or update control and status register
 *
 * csrr   <->  myriscvx_csrrw(env, csrno, ret_value, 0, 0);
 * csrrw  <->  myriscvx_csrrw(env, csrno, ret_value, value, -1);
 * csrrs  <->  myriscvx_csrrw(env, csrno, ret_value, -1, value);
 * csrrc  <->  myriscvx_csrrw(env, csrno, ret_value, 0, value);
 */

int myriscvx_csrrw(CPUMYRISCVXState *env, int csrno, target_ulong *ret_value,
                   target_ulong new_value, target_ulong write_mask)
{
  int ret;
  target_ulong old_value;
...
    

で、ここで命令実行に失敗していたのでGDBを当てて観察していたのだが、CSR拡張のフラグチェックの部分をまじめに実装していなかったので命令実行に失敗していたらしい。とりあえず省略。

  /* ensure the CSR extension is enabled. */
  // if (!cpu->cfg.ext_icsr) {
  //   return -1;
  // }

QEMUにおいてRISC-VのCSRがどのように実装してあるかという話だが、CSRのオペレーションを各CSRで定義し、これをリスト化してある。

/* CSR function table */
static myriscvx_csr_operations csr_ops[];

/* CSR function table constants */
enum {
    CSR_TABLE_SIZE = 0x1000
};

CSRのオペレーションはとりあえず最小限必要なmhartidだけ用意した。

/* Control and Status Register function table */
static myriscvx_csr_operations csr_ops[CSR_TABLE_SIZE] = {
  [CSR_MHARTID] =             { any,  read_mhartid                        },
};

read_mhartid()という関数を定義する。これがMHARTIDレジスタを読み出す動作になる。

static int read_mhartid(CPUMYRISCVXState *env, int csrno, target_ulong *val)
{
    *val = env->mhartid;
    return 0;
}

これで一応CSR命令は通過することが確認できた。でもまだ命令トレースが出てこないなあ。どのように出力されているのか確認したい。

QEMU 5.0.0 monitor - type 'help' for more information
(qemu) ----------------
IN:
Priv: 3; Virt: 0
0x0000000000001000:
OBJD-T: 9702000093850202732540f1

----------------
IN:
Priv: 3; Virt: 0
0x000000000000100c:
OBJD-T: 83b2820167800200

----------------
IN:
Priv: 3; Virt: 0
0x0000000080000000:
OBJD-T: 6f00c004

----------------
...