Rustで作るRISC-Vシミュレータ。基本的な形が出来上がった。MMUの変換処理は、まだデバッグ中だ。しかしデバッグするためには、トレースデータを出力できるようにする必要がある。 トレースデータを出力するためには、RISC-Vシミュレータが実行した情報をトレースに記憶し、最後にまとめて出力する機能が必要だ。 これはこれまでC++で実装していたのだが、これをRustで実装しよう。
トレースデータのデータ構造は、以下のように設計する。まだ作ったばかりなので、ラッピング関数は用意しておらず、すべてのメンバにアクセスできるようになっている。修正しなきゃ。。。
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