Rustを使ったRISC-Vシミュレータの調整を行っている。デバッグ・トレース機能の調整を行った。
もうひとつの機能は、命令の機械語からニーモニックを出力する機能だ。
実はこの機能は必須の機能ではない。
spike-dasm
というriscv-toolsを使えば、逆アセンブルの情報は命令の機械語から出力することができる。
$ echo "DASM(6ae6ba23)" | spike-dasm sd a4, 1716(a3)
これでも良いのだが、せっかくなのでシミュレータ自体にも逆アセンブルの機能を持たせておきたい。 これを実現するために、各命令のニーモニックの情報と、オペランドに関する情報をインプットした。
まずはC++で実装したRISC-Vシミュレータの情報をベースにして、命令のニーモニック、オペランドの数、それぞれのオペランドの種類と機械語のどのフィールドから抽出すべきかの情報を格納する。
src/riscv_inst_mnemonic.rs
pub fn get_riscv_inst_mnemonic(dec_inst: RiscvInstId) -> String { match dec_inst { ... RiscvInstId::ADDI => format!("addi @,@,@"), ...
ニーモニックに関する情報だ。
命令IDに対して命令オペコードの情報、そしていくつのオペランドを使用するかについて記述している。
@
の部分がオペランドになり、これは命令トレースを出力する段階で置き換える。
次に、オペランドの情報だが以下のような構造体を作っている。
src/riscv_inst_operand.rs
pub struct OperandInfo { pub m_size: u32, // オペランド情報のサイズ pub m_type_lst: [OperandType; 256], // オペランドの種類 pub m_msb_lst: [u32; 256], // オペランド情報を切り出す命令のMSB pub m_lsb_lst: [u32; 256], // オペランド情報を切り出す命令のLSB pub m_connect: [bool; 256], // 次の情報と結合するか? }
impl Tracer { pub fn format_operand(&mut self) { ... { let mut inst_operand = OperandInfo::new(); // InstId_t::INST_ID_ADDI inst_operand.m_size = 3; inst_operand.m_type_lst[0] = OperandType::TypeXReg; inst_operand.m_msb_lst[0] = 11; inst_operand.m_lsb_lst[0] = 7; inst_operand.m_connect[0] = false; // ["r[11:7]", "r[19:15]", "h[31:20]"] inst_operand.m_type_lst[1] = OperandType::TypeXReg; inst_operand.m_msb_lst[1] = 19; inst_operand.m_lsb_lst[1] = 15; inst_operand.m_connect[1] = false; // ["r[11:7]", "r[19:15]", "h[31:20]"] inst_operand.m_type_lst[2] = OperandType::TypeHex; inst_operand.m_msb_lst[2] = 31; inst_operand.m_lsb_lst[2] = 20; inst_operand.m_connect[2] = false; // ["r[11:7]", "r[19:15]", "h[31:20]"] self.m_inst_operand_map.insert(RiscvInstId::ADDI, inst_operand); }
これらの情報は命令のディスアセンブル時に使用する。
上記のaddi @,@,@
から、@
に到達するとオペランドデータベースから当該命令のオペランド情報を取得し、文字列に変換して@
の代わりに出力する。
match self.m_dec_inst { Some(id) => { let inst_str = get_riscv_inst_mnemonic(id); let operand_info = self.m_inst_operand_map.get(&id); match operand_info { Some(operand_info) => { let mut at_index = 0; let mut consume_idx = 0; for c in inst_str.chars() { if c == '@' { let msb = operand_info.m_msb_lst[at_index]; let lsb = operand_info.m_lsb_lst[at_index]; let mask = (1 << (msb - lsb + 1)) - 1; let opr_val = (self.m_inst_hex >> lsb) & mask; match operand_info.m_type_lst[at_index] { OperandType::TypeXReg => { print!("x{:02}", opr_val); consume_idx = consume_idx + 3; }, ... _ => panic!("Unknown operand type {:?}", operand_info.m_type_lst[at_index] as u32), } at_index = at_index + 1; } else {
これにより、命令トレース中にシミュレータ自身が逆アセンブルした情報を出力することができる。
ffc28293:addi x05,x05,0xffc
最終的に、以下のようなトレースデータを出力することができるようになった。
17:M:Bare:800028dc:00001697:auipc x13,0x00001 :x13<=00000000800038dc 18:M:Bare:800028e0:72468693:addi x13,x13,0x724 :x13=>00000000800038dc x13<=0000000080004000 19:M:Bare:800028e4:00002717:auipc x14,0x00002 :x14<=00000000800048e4 20:M:Bare:800028e8:71c70713:addi x14,x14,0x71c :x14=>00000000800048e4 x14<=0000000080005000 21:M:Bare:800028ec:00c6d693:srli x13,x13,0x0c :x13=>0000000080004000 x13<=0000000000080004 22:M:Bare:800028f0:00c75713:srli x14,x14,0x0c :x14=>0000000080005000 x14<=0000000000080005 23:M:Bare:800028f4:00003797:auipc x15,0x00003 :x15<=00000000800058f4 24:M:Bare:800028f8:70c78793:addi x15,x15,0x70c :x15=>00000000800058f4 x15<=0000000080006000 25:M:Bare:800028fc:00a69693:slli x13,x13,0x0a :x13=>0000000000080004 x13<=0000000020001000