FPGA開発日記

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

RustでRISC-V命令セットシミュレータを作ろう (8. デバッグ・トレース機能の整理)

f:id:msyksphinz:20190224185310p:plain:w400

Rustを使ったRISC-Vシミュレータの調整を行っている。デバッグ・トレース機能の調整を行った。

ISSを使ってテストプログラムをデバッグする場合、いくつかの情報を出力してプログラムの動作を監視する必要がある。例えば、ISSからは以下の情報が出てくると役に立ちそうだ。

  • 実行中のプログラムカウンタ
  • 実行する命令の機械語情報
  • 実行する命令のアセンブリ
  • 実行する命令の動作
    • レジスタの読み込み・書き込み情報
    • メモリアクセス情報

これらの情報を標準出力に出して、デバッグに役立てていく。

トレースクラスの構成

まずはCPUを表現するstructに、トレースを格納するためのstructを追加する。

  • src/riscv64_core.rs
pub struct Riscv64Env {
    // m_bitmode: RiscvBitMode,
    pub m_pc: AddrT,
    m_previous_pc: AddrT,
    m_regs: [Xlen64T; 32],
...

    pub m_trace: Tracer,
...

Tracer構造体は、命令実行後の各種情報を格納するための構造体だ。

  • src/riscv_tracer.rs
pub struct Tracer {
    pub m_priv: PrivMode,          // 現在のマシン実行モード
    pub m_vmmode: VMMode,          // 現在の仮想アドレスモード
    pub m_executed_pc: AddrT,      // 命令フェッチアドレス(仮想アドレス)
    pub m_executed_phypc: AddrT,   // 命令フェッチアドレス(物理アドレス)
    pub m_inst_hex: InstT,         // 実行命令の機械語
    pub m_dec_inst: Option<RiscvInstId>, // デコード後の命令
    pub m_step: u32,               // 命令の実行ステップ数
    pub m_trace_info: Vec<TraceInfo>,  // 命令の動作トレース

    pub m_inst_operand_map: HashMap<RiscvInstId, OperandInfo>, // 命令トレースの情報を格納するHash
}

この中で注目しなければならないのはm_trace_infoメンバ変数だ。 これはTraceInfoという、命令の動作トレースを保持する情報を格納している。 このメンバ変数はベクトルであり複数の情報を格納することができる。

pub struct TraceInfo {
    pub m_trace_type: TraceType,
    pub m_trace_size: u32,
    pub m_trace_addr: AddrT,
    pub m_trace_value: Xlen64T,
    pub m_trace_memresult: MemResult,
}
f:id:msyksphinz:20200202220333p:plain:w400
RISC-Vシミュレータ。トレース機能の構成。

命令の動作の度に、この動作をトレースに追加していく。

  • src/riscv64_insts.rs
            RiscvInstId::ADDI => {
                let rs1_data = self.read_reg(rs1);
                let imm_data = Self::extract_ifield(inst);
                let reg_data: Xlen64T = rs1_data.wrapping_add(imm_data);
                self.write_reg(rd, reg_data);
            }
  • src/riscv64_core.rs
    fn read_reg(&mut self, reg_addr: RegAddrT) -> Xlen64T {
...
        let mut read_reg_trace = TraceInfo::new();
        read_reg_trace.m_trace_type = TraceType::XRegRead;
        read_reg_trace.m_trace_addr = reg_addr as AddrT;
        read_reg_trace.m_trace_value = ret_val;
        read_reg_trace.m_trace_memresult = MemResult::NoExcept;
        // トレース構造体に情報をpushする。
        self.m_trace.m_trace_info.push(read_reg_trace);
...
    }
    fn write_reg(&mut self, reg_addr: RegAddrT, data: Xlen64T) {
        if reg_addr != 0 {
            let mut write_reg_trace = TraceInfo::new();

            write_reg_trace.m_trace_type = TraceType::XRegWrite;
            write_reg_trace.m_trace_addr = reg_addr as AddrT;
            write_reg_trace.m_trace_value = data;
            write_reg_trace.m_trace_memresult = MemResult::NoExcept;
            // トレース構造体に情報をpushする。
            self.m_trace.m_trace_info.push(write_reg_trace);
        }
   }

命令の実行後に、このトレース情報を出力していく。 下記の例ではトレースの種類がレジスタ書き込みの場合、[整数レジスタアドレス]<=[書き込みデータ]のトレースデータを出力している。

  • src/riscv_tracer.rs
impl RiscvTracer for Tracer {
    ...
    fn print_trace(&mut self) {
        for trace_idx in 0..self.m_trace_info.len() {
            match self.m_trace_info[trace_idx].m_trace_type {
                TraceType::XRegWrite => {
                    print!(
                        "x{:02}<={:016x} ",
                        self.m_trace_info[trace_idx].m_trace_addr,
                        self.m_trace_info[trace_idx].m_trace_value
                    );
                }
             ...
x14=>00000000200000cf x13=>0000000080005944 (80005ff8)<=200000cf
--------------------- --------------------- --------------------
レジスタ読み込みトレース  レジスタ書き込みトレース  メモリ書き込みトレース