L1データキャッシュは、L1命令キャッシュと違って書き込みにも対応する必要がある。もちろん、LSUもReadとWriteを同様に対応しなければならないのだが、いくつかの問題にぶち当たった。
一般的なLSUの構成
一般的なLSUの構成は、Hisa Ando氏の本にあるように、以下のような構成を取るものが一般的である。
- 作者: Hisa Ando
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2011/06/24
- メディア: 単行本(ソフトカバー)
- 購入: 2人 クリック: 260回
- この商品を含むブログ (5件) を見る
LSUは、データの一貫性を保つために、ロードとストアの順番について制限がある。 - (基本的に)ストアは順番に実行する。 - (命令的に)ロードの前にストアを実行する。
このようにしないと、同じアドレスにおいてストア命令がロード命令に追い抜かされてしまう場合が発生し、データがメモリに書き込まれる前にロードをしてしまい、誤ったデータをロードしてしまう可能性がある。
上記のロードキューとストアキューを別々にした場合にも当該の問題が発生する可能性がある。 つまり、ロード側のキューが掃けるのが先の場合、ストア命令よりもロード命令が先に実行される可能性があり、その結果データが正しく読み込まれない場合が発生する。
この場合に備えて、ストア側のキューからロード側のキューへフォワーディングする機構が存在する(メモリディスアンビギュエーション)。
しかし、そもそもこの上記の図は、メモリに対してロードとストアが同時に発行できることが前提だ。では、L1キャッシュがロードとストアを1つのポートから実行しなければならない場合、どのように実装すべきか?
L1キャッシュの口がロードとストアで共通の場合
この場合、そもそも上記のようにロードキューとストアキューを別々に分けるのはナンセンスということになる。 どちらのキューからリクエストを出すのか選別しなければならなないし、それを実現しながらメモリの一貫性を保つ必要がある。
そうすると、一つのメモリキューからリクエストを出すのだが、これがやっかいだ。
下記はそのようなロードストアキューを表現してみた。この場合、インデックスは昇順で進んでいき、Index=2のストア命令を実行中。ただしコミットはIndex=3まで確定。 それより先の命令はまだコミット未確定ということになる。
ストア命令は、メモリ(まずはL1キャッシュ)にリクエストを出すのは、その命令が確定するまで、つまりコミットが発生しないと進めることができない。 一方でロード命令は、メモリにリクエストを出すのはコミットを待つ必要がない。このため、自由にメモリへのリクエストを出しつつ、フラッシュで破棄される場合はリオーダバッファをクリアするだけで良い、という訳だ。
そうすると、パイプラインフラッシュが発生した場合に、どこまで命令キューを元に戻すかという問題がある。
- ストア命令が完了して、ストア処理を実行中の場合→ストアロードキューの先頭ポインタを元に戻す必要はない。ストアは命令コミット確定後の操作のため、そのまま実行し続ける
- ロード命令が実行されている場合→当該ロード命令は破棄すべき。確定したコミット位置まで戻す必要あり。
そうすると、結局上記のExeIndexをどこまで戻せば良いのか?ということになる。きちんと定式化できたとしても、実装するのはかなり複雑になるのではないか。
一つの解決策
そもそもロード命令とストア命令の実行が確定するタイミングが上記の1.と2.で異なる。 これが問題なのであって、ロード命令であっても、ロードオペランドが確定した時点で(メモリアクセスを実行する前でも)、仮のコミットを発生してしまう。
このコミットが発生すると、ロード命令が確定され、フラッシュ対象から外れる。 このフラッシュ対象から外れたロード命令はかならず実行されるため、実行タイミングとしてはストア命令と同一になり、キューの管理が楽になる。
二つめの解決策
最初からロードとストアのキューを分け、メモリディスアンビギュエーションを実装する。
さらに、L1キャッシュの制御ステートマシンを複製し、メモリのリード用ポートとライト用ポートを分離する。
これによりL1キャッシュはロードとストアを別々のステートマシンで同時に処理できるようになる。
問題は、タグメモリとデータメモリを、デュアルポート化して、ロードとストアを同時に処理できるようにならなければならない。
追記: 二つめの解決策はダメだ。もし同時にミスを検出した場合、やはりどこかで外部のインタフェースのためにリクエストを一本に統一する必要がある。 結局どこで一本に統一するかの違いだけであって、一つめの解決策が実装のし易さについても有 利な気がする。