やっとこさ、RISC-VのMMUの実装が完了した。理解するまでに長かった。。。 一応、まとめておいた。RISC-VのMMUの変換の仕組みの概要を示している。
RISC-Vのアドレス変換モードはいろいろあるのだが、ここではSv39と呼ばれるものを示している。Sv39は、
- 物理アドレス50ビット
- 仮想アドレス39ビット
で構成されている。Sv39はRISC-Vのテストパタンにも利用されており、ISSの実装は必須だ。ここではTLBは実装していない想定のため、TLBへのアクセスは全てメモリアクセスとして実現している。
1. RISC-Vのアドレス変換機構の処理
- まず、アドレス変換の起点となるのは、SPTBRというシステムレジスタだ。このレジスタに、MMUのための起点となるアドレスが格納されている。 また、Sv39にはLEVELSという数値が規定されている。これは、最大で何回のアドレス変換を実行するかということを示している。ページテーブルを何回巡ることができるかを示しており、SV39では3である。
- SPTBRを利用して、SPTBR+va.vpn[i]×PTESIZEにアクセスする。PTESIZEはページテーブルエントリのサイズであり、Sv39では64である。また、va.vpn[i]は仮想アドレスのフィールドを示しており、va.vpn[2]=Field[38:30],va.vpn[1]=Field[29:21],va.vpn[0]=Field[20:12]として定義されている。
- 取得したページテーブルをpteとすると、pte.vフィールドが0である場合、これは正しいページテーブルではない。アドレス変換エラーを出力する。
- 正しいページテーブルである場合、今度はpte.typeフィールドを調査する。Typeフィールドにはページテーブルの種類を示してあり、アクセス権限もしくは、次のページテーブルへのポインタであることを示すこともある。 Typeフィールドが0もしくは1である場合、このページテーブルは次のページテーブルへのポインタであるので、LEVELSを1減算する。このとき、LEVELSの値が0よりも小さくなる(Sv39の場合は3回以上のアドレス変換が入る)場合にはアドレス変換ミスであるので、アドレスエラーを出力する。そうでない場合は、アドレスpte.ppn×PAGESIZEを起点アドレス(SPTBRの代わり)として、2.に戻る。
Typeフィールドが2よりも大きい場合、そのアドレスは末端のページテーブルを示しているので、アドレス変換を行う。このとき、3回のアドレス変換をやり切ったか、そうでないかでアドレス変換の方法が若干異なる。 つまり、LEVELS=0まで減算し切っていれば、pte.ppnの全てのフィールドを物理アドレスとして使用する。そうでなければ、物理アドレスの一部はそのまま仮想アドレスのものを利用する。
pa.pgoff = va.pgoff
- If i>0, then this is a superpage translation and pa.ppn[i-1:0]=va.vpn[i-1:0]
- pa.ppn[LEVES-1:i] = pte.ppn[LEVES-1:i]
2. アドレス変換処理のISSへの実装
さて、Sv39の機能を実装した。キーとなるのはConvertVirtualAddressという関数だ。
Addr_t RiscvEnv::ConvertVirtualAddress (Addr_t vaddr, MemAccType acc_type) { UDWord_t pte_base; CSRReadNoTrace (SYSREG_ADDR_SPTBR, &pte_base); Addr_t phy_addr = vaddr & 0x0fff; DWord_t pte_val; if (GetVmMode() == Vm_Sv39 && GetPrivMode() == PrivSupervisor) { int level; Addr_t pte_addr; for (level = 2; level > -1; level--) { Byte_t *p_pte; p_pte = reinterpret_cast<Byte_t *>(&pte_val); Addr_t va_vpn_i = static_cast<DWord_t>(vaddr) >> (12 + 9 * level) & 0x1ff; pte_addr = pte_base + va_vpn_i * 8; LoadMemoryDebug (pte_addr, Size_DWord, p_pte); memcpy (&pte_val, p_pte, 4); // std::cout << "<Page Table Convert : " << std::hex << std::setw(16) << std::setfill('0') << pte_addr // << "(PTE_BASE = " << std::hex << std::setw(16) << std::setfill('0') << pte_base << "," // << "v_vpn_i = " << std::hex << std::setw(16) << std::setfill('0') << va_vpn_i << ")" // << " => " << std::hex << std::setw(16) << std::setfill('0') << pte_val << ">\n"; pte_val |= 0x20; if (!(pte_val & 0x01)) { std::cout << "<Page Table Error : " << std::hex << std::setw(16) << std::setfill('0') << pte_addr << " is not valid Page Table. Generate Exception>\n"; GenerateException (Except_InstAccessFault); break; } if ((pte_val & 0x01) && ((pte_val >> 1) & 0x0f) >= 2) { if (IsAllowedAccess ((pte_val >> 1) & 0x0f, acc_type, GetPrivMode())) { if (acc_type == WriteMemType) { pte_val |= 0x40; } break; } else { std::cout << "<Access Fault : Tried to Access to Page " << ((pte_val >> 1) & 0x0f) << '\n'; GenerateException (Except_StoreAccessFault); break; } break; } UWord_t vpn_mask = (0x1ff << (level * 9 + 10)); pte_base = (pte_val >> 10) << 12; // std::cout << std::hex << " <PyhAddr* = " << std::hex << std::setw(16) << std::setfill('0') << phy_addr << ">\n"; } for (; level >= 0; level--) { UWord_t vpn_mask = (0x1ff << (level * 9 + 12)); phy_addr |= (vaddr & vpn_mask); // std::cout << std::hex << " <PyhAddr_ = " << std::hex << std::setw(16) << std::setfill('0') << phy_addr << ">\n"; } Byte_t *pte_byte = reinterpret_cast<Byte_t *>(&pte_val); StoreMemory (pte_addr, Size_DWord, pte_byte); // std::cout << std::hex << " <PyhAddr = " << std::hex << std::setw(16) << std::setfill('0') << phy_addr << ">\n"; } else { phy_addr = vaddr; } return phy_addr; }
3. テストパタン実行結果
Total Test time (real) = 9.24 sec The following tests FAILED: 1 - rv32mi-p-csr (Failed) 8 - rv32mi-p-timer (Failed) 9 - rv32si-p-csr (Failed) 47 - rv32ui-pm-lrsc (Failed) 125 - rv64mi-p-csr (Failed) 126 - rv64mi-p-dirty (Failed) 130 - rv64mi-p-mcsr (Failed) 134 - rv64mi-p-timer (Failed) 136 - rv64si-p-csr (Failed) 190 - rv64ui-p-amomax_d (Failed) 194 - rv64ui-p-amomin_d (Failed) 230 - rv64ui-pm-lrsc (Failed) 233 - rv64ui-p-mulhsu (Failed) 273 - rv64ui-pt-amomax_d (Failed) 277 - rv64ui-pt-amomin_d (Failed) 315 - rv64ui-pt-mulhsu (Failed) 359 - rv64ui-v-amomax_d (Failed) 363 - rv64ui-v-amomin_d (Failed) 401 - rv64ui-v-mulhsu (Failed) Errors while running CTest
殆どのパタンがパスするようになった。実装はこれでOKなようだ。