FPGA開発日記

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

RISC-Vのアドレス変換機能であるSV39の仕組みと実装

やっとこさ、RISC-VのMMUの実装が完了した。理解するまでに長かった。。。 一応、まとめておいた。RISC-VのMMUの変換の仕組みの概要を示している。

docs.google.com

RISC-Vのアドレス変換モードはいろいろあるのだが、ここではSv39と呼ばれるものを示している。Sv39は、

で構成されている。Sv39はRISC-Vのテストパタンにも利用されており、ISSの実装は必須だ。ここではTLBは実装していない想定のため、TLBへのアクセスは全てメモリアクセスとして実現している。

1. RISC-Vのアドレス変換機構の処理

f:id:msyksphinz:20160313155148p:plain

  1. まず、アドレス変換の起点となるのは、SPTBRというシステムレジスタだ。このレジスタに、MMUのための起点となるアドレスが格納されている。 また、Sv39にはLEVELSという数値が規定されている。これは、最大で何回のアドレス変換を実行するかということを示している。ページテーブルを何回巡ることができるかを示しており、SV39では3である。
  2. 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]として定義されている。
  3. 取得したページテーブルをpteとすると、pte.vフィールドが0である場合、これは正しいページテーブルではない。アドレス変換エラーを出力する。
  4. 正しいページテーブルである場合、今度はpte.typeフィールドを調査する。Typeフィールドにはページテーブルの種類を示してあり、アクセス権限もしくは、次のページテーブルへのポインタであることを示すこともある。 Typeフィールドが0もしくは1である場合、このページテーブルは次のページテーブルへのポインタであるので、LEVELSを1減算する。このとき、LEVELSの値が0よりも小さくなる(Sv39の場合は3回以上のアドレス変換が入る)場合にはアドレス変換ミスであるので、アドレスエラーを出力する。そうでない場合は、アドレスpte.ppn×PAGESIZEを起点アドレス(SPTBRの代わり)として、2.に戻る。
  5. Typeフィールドが2よりも大きい場合、そのアドレスは末端のページテーブルを示しているので、アドレス変換を行う。このとき、3回のアドレス変換をやり切ったか、そうでないかでアドレス変換の方法が若干異なる。 つまり、LEVELS=0まで減算し切っていれば、pte.ppnの全てのフィールドを物理アドレスとして使用する。そうでなければ、物理アドレスの一部はそのまま仮想アドレスのものを利用する。

  6. pa.pgoff = va.pgoff

  7. If i>0, then this is a superpage translation and pa.ppn[i-1:0]=va.vpn[i-1:0]
  8. pa.ppn[LEVES-1:i] = pte.ppn[LEVES-1:i]

2. アドレス変換処理のISSへの実装

github.com

さて、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なようだ。