FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

RustでRISC-V命令セットシミュレータを作ろう (5. シミュレータのトレースデータ出力機構の実装)

f:id:msyksphinz:20190224185310p:plain

Rustで作るRISC-Vシミュレータ。基本的な形が出来上がった。MMUの変換処理は、まだデバッグ中だ。しかしデバッグするためには、トレースデータを出力できるようにする必要がある。 トレースデータを出力するためには、RISC-Vシミュレータが実行した情報をトレースに記憶し、最後にまとめて出力する機能が必要だ。 これはこれまでC++で実装していたのだが、これをRustで実装しよう。

github.com

トレースデータのデータ構造は、以下のように設計する。まだ作ったばかりなので、ラッピング関数は用意しておらず、すべてのメンバにアクセスできるようになっている。修正しなきゃ。。。

pub struct TraceInfo {
    pub m_trace_type: TraceType,
    pub m_trace_size: u32,
    pub m_trace_addr: AddrType,
    pub m_trace_value: XlenType,
    pub m_trace_memresult: MemResult, /* Memory Access Result */
}
pub struct Tracer {
    pub m_priv: PrivMode,
    pub m_vmmode: VMMode,
    pub m_executed_pc: AddrType,
    pub m_executed_phypc: AddrType,
    pub m_inst_hex: InstType,
    pub m_step: u32,

    pub m_trace_info: Vec<TraceInfo>,
}

m_trace_infoレジスタリード・レジスタライト・メモリアクセスのトレースを格納していく。TraceInfo構造体を、ベクトルにしてPushする形で実行する。

    fn read_reg(&mut self, reg_addr: RegAddrType) -> XlenType {
        let ret_val: XlenType;

...
        let mut read_reg_trace = TraceInfo::new();
        read_reg_trace.m_trace_type = TraceType::XRegRead;
        read_reg_trace.m_trace_addr = reg_addr as AddrType;
        read_reg_trace.m_trace_value = ret_val;
        read_reg_trace.m_trace_memresult = MemResult::NoExcept;

        self.m_trace.m_trace_info.push(read_reg_trace);
...
    fn read_bus_word(&mut self, addr: AddrType) -> XlenType {
        // let result: MemResult;
        // let phy_addr: AddrType;
        let (_result, phy_addr) = self.convert_virtual_address(addr, MemAccType::Fetch);
...
        let mut read_mem_trace = TraceInfo::new();

        read_mem_trace.m_trace_type = TraceType::MemRead;
        read_mem_trace.m_trace_addr = addr;
        read_mem_trace.m_trace_value = ret_val;
        read_mem_trace.m_trace_memresult = MemResult::NoExcept;

        self.m_trace.m_trace_info.push(read_mem_trace);

これを命令実行毎に最後にまとめて出力する。これでトレースデータとする。

    fn print_trace(&mut self) {
        print!("{:10}:", self.m_step);
        print!(
            "{}:",
            match self.m_priv {
                PrivMode::User => "U",
                PrivMode::Supervisor => "S",
...
                VMMode::Sv39 => "Sv39",
                VMMode::Sv48 => "Sv48",
                VMMode::Sv57 => "Sv57",
                VMMode::Sv64 => "Sv64",
            }
        );
        print!("{:08x}:{:08x}:", self.m_executed_pc, self.m_inst_hex);

        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}<={:08x} ",
...

                _ => {}
            }
        }
        println!("  // DASM({:08x})", self.m_inst_hex);
    }

最後はDASM()で締める。DASM(命令の16進数コード)を出力しておけば、spike-dasmによって命令名に変換して表示できるので、楽だからだ。

         0:M:Bare:80000000:00c0006f:  // DASM(00c0006f)

これが、spke-dasmをPipeすることにより、

         0:M:Bare:80000000:00c0006f:  // j       pc + 0xc

このように変換される。最初のデバッグにはとても便利。最終的にはオリジナルのトレース出力に変えるけれども。

このようなトレースデータが出力される。デバッグがはかどる。

         0:M:Bare:80000000:00c0006f:  // j       pc + 0xc
         1:M:Bare:8000000c:00000297:x05<=8000000c   // auipc   t0, 0x0
         2:M:Bare:80000010:ffc28293:x05=>8000000c x05<=80000008   // addi    t0, t0, -4
         3:M:Bare:80000014:30529073:x05=>80000008   // csrw    mtvec, t0
         4:M:Bare:80000018:00006117:x02<=80006018   // auipc   sp, 0x6
         5:M:Bare:8000001c:35010113:x02=>80006018 x02<=80006368   // addi    sp, sp, 848
         6:M:Bare:80000020:f14022f3:x00=>00000000 x05<=00000000   // csrr    t0, mhartid
         7:M:Bare:80000024:00c29293:x05=>00000000 x05<=00000000   // slli    t0, t0, 12
         8:M:Bare:80000028:00510133:x02=>80006368 x05=>00000000 x02<=80006368   // add     sp, sp, t0
         9:M:Bare:8000002c:34011073:x02=>80006368   // csrw    mscratch, sp
        10:M:Bare:80000030:3b1020ef:x01<=80000034   // jal     pc + 0x2bb0
        11:M:Bare:80002be0:00008067:x01=>80000034   // ret
        12:M:Bare:80000034:00003517:x10<=80003034   // auipc   a0, 0x3
        13:M:Bare:80000038:bb050513:x10=>80003034 x10<=80002be4   // addi    a0, a0, -1104
        14:M:Bare:8000003c:2050206f:  // j       pc + 0x2a04
        15:M:Bare:80002a40:f14027f3:x00=>00000000 x15<=00000000   // csrr    a5, mhartid
        16:M:Bare:80002a44:14079a63:x15=>00000000 x00=>00000000   // bnez    a5, pc + 340
        17:M:Bare:80002a48:00001797:x15<=80003a48   // auipc   a5, 0x1
        18:M:Bare:80002a4c:5b878793:x15=>80003a48 x15<=80004000   // addi    a5, a5, 1464