前回、RTLの検証方法について少しまとめ、ISSを使ってL1キャッシュのシミュレーションを行う方法について少し考えた。
キャッシュの一般的な構成
キャッシュは、一般的には以下のような構造を取っている。
構成されるのは主に2つのメモリ、タグ用のRAMとデータ用のRAMだ。ここでは記述していないがセットアソシアティブのキャッシュだと、ウェイセレクト用のRAMも存在していたりする。
- タグRAM : 当該キャッシュに格納されているデータが、どのアドレスに属しているかを示す。
- データRAM : データそのもの
キャッシュのマッピング方式には様々な種類があるが、主に3種類のマッピングが存在する。これらは大学生用のコンピュータアーキテクチャの書籍を参考にして欲しいが、
- ダイレクトマッピング : メモリアドレスから直接キャッシュのライン位置が算出される。シンプル
- フルアソシアティブ : メモリアドレスとキャッシュのライン位置は関係無い。もっとも複雑にマッピングを行うことができるが、複雑。
- セットアソシアティブ : メモリアドレスからキャッシュの位置は決定されるが、そこに複数のウェイが存在しており、同じキャッシュラインに該当しても複数のメモリアドレスのデータを格納できる。ダイレクトマッピングとフルアソシアティブの中間のようなマッピング方式。
例えば、4kBのキャッシュを考え、1ラインあたり16Byte格納できるキャッシュを考える。4*1024Byteのキャシュなので、逆算するとラインの数は256である。
すると、アドレスを以下のように分割して考えることができるだろう。
まず、1ラインあたり16Byteなので、アドレスのうち下位の4ビットは省略される。次に、256エントリのラインを持っているので、アドレスビットの[12:4]
までの情報はラインの参照に使用される。
従って、タグとして格納されるのは上位の12ビットから先ということになるだろう。
キャッシュの構成をISSでシミュレーションする
キャッシュの構成をISSでシミュレーションするといっても、何をシミュレーションすれば良いのか。検討の結果、以下の2項目がシミュレーションできれば良いということになる。
- 当該メモリアクセスがキャッシュにヒットするか(既にキャッシュに格納されているか)
- 当該メモリアクセスがキャッシュの正しいエントリに格納されているか
という訳で、シミュレーションしなければならないのはタグだけになる。データはISSが勝手にロードしてくるのでシミュレーションする必要はない。というか、既にISSとの一致検証で動作をチェックできるため、ここでは省略する。
C++の実装としては、以下を用意した。
/* * L1-Dcache implementation */ const uint32_t L1D_SIZE = 4096; // 4096Byte const uint32_t L1D_WORDS = 4 * 4; // 16Byte const uint32_t L1D_LENGTH = L1D_SIZE / L1D_WORDS; const Addr_t L1D_ENTRY_MASK = L1D_LENGTH-1; const Addr_t L1D_ADDR_MASK = ~(L1D_LENGTH-1); std::unique_ptr<bool []> m_dcache_en; std::unique_ptr<Addr_t []> m_dcache_addr; uint32_t MaskL1DIndex (Addr_t addr) { const uint32_t addr_mask_length = ceilf(logf(L1D_WORDS) / logf(2)); return (addr >> addr_mask_length) & L1D_ENTRY_MASK; } uint32_t MaskL1DAddr (Addr_t addr) { const uint32_t addr_mask_length = ceilf(logf(L1D_WORDS) / logf(2)); return addr & (L1D_ADDR_MASK << addr_mask_length); }
さらに、ロードストアが発生した場合に、どのタグにアクセスされるのかを計算でシミュレーションし、ログとして出力する。
if (FLAGS_sim_l1d) { Addr_t l1d_addr = MaskL1DAddr (addr); Addr_t l1d_entry_idx = MaskL1DIndex (addr); if (!m_dcache_en[l1d_entry_idx]) { // L1D corresponding entry is empty m_dcache_addr[l1d_entry_idx] = l1d_addr; m_dcache_en [l1d_entry_idx] = true; DebugPrint ("[L1D:E:%08x,%03d]\n", l1d_addr, l1d_entry_idx); } else { if (m_dcache_addr[l1d_entry_idx] == l1d_addr) { // L1D corresponding entry is HIT DebugPrint ("[L1D:H:%08x,%03d]\n", l1d_addr, l1d_entry_idx); } else { // L1D corresponding entry is MISS DebugPrint ("[L1D:R:%08x-->%08x,%03d]\n", m_dcache_addr[l1d_entry_idx], l1d_addr, l1d_entry_idx); m_dcache_addr[l1d_entry_idx] = l1d_addr; } } } result = LoadMemory (ConvertVirtualAddress (addr, MemAccType::ReadMemType), size, data);
結果、以下のようにログが出力される。
39:M:MBar:[00003314][P00003314] 04e10513 : addi r10,r02,0x04e (37,11) r02=>0000bf78 r10<=0000bfc6 [L1D:E:0000c000,000] 40:M:MBar:[00003318][P00003318] 08112623 : sw r01,0x08c(r02) r02=>0000bf78 r01=>00000290 (0000c004)<=00000290 [L1D:H:0000c000,000] 41:M:MBar:[0000331c][P0000331c] 08812423 : sw r08,0x088(r02) r02=>0000bf78 r08=>00003308 (0000c000)<=00003308 [L1D:E:0000b000,255] 42:M:MBar:[00003320][P00003320] 08912223 : sw r09,0x084(r02) r02=>0000bf78 r09=>00000000 (0000bffc)<=00000000 [L1D:H:0000b000,255] 43:M:MBar:[00003324][P00003324] 09212023 : sw r18,0x080(r02) r02=>0000bf78 r18=>00000000 (0000bff8)<=00000000 [L1D:H:0000b000,255] 44:M:MBar:[00003328][P00003328] 07312e23 : sw r19,0x07c(r02) r02=>0000bf78 r19=>00000000 (0000bff4)<=00000000 [L1D:H:0000b000,255] 45:M:MBar:[0000332c][P0000332c] 07412c23 : sw r20,0x078(r02) r02=>0000bf78 r20=>00000000 (0000bff0)<=00000000 [L1D:E:0000b000,254]
L1D
に続いて主力されるのが、キャッシュへのヒットミス判定結果(H:ヒット、E:エンプティのためフィル発生、R:ミスのためリプレース)結果、次にタグの情報、最後にどのエントリに格納されたかを示すインデックスだ。
これだけ実装できれば、とりあえずRTLの検証には使えるだろう。次はRTLを実装していこう。