分岐予測は設計経験がないうえにいろいろ試行錯誤しながら作っているのだが、いくつか設計メモを残しておこう。
まずそもそもフェッチの動作だが、大きく分け3つのステージに分けている。
s0ステージ
s1ステージ
- タグRAMから読みだしたタグ情報を用いて命令キャッシュのヒット/ミスを決定します。
s2ステージ
- s1ステージの結果、命令キャッシュがヒットしていればヒットした命令を取得します。
- そうでなければ命令キャッシュは読み出しをBusy状態に入り、外部に対して命令キャッシュリクエストを送出します。
で、基本的に命令フェッチハザードはS2ステージで検出してロールバックするようにしている。命令フェッチで発生するハザードは大きく分けて
- 命令キャッシュミス
- TLBミス
- 後段の命令バッファFull
これらの時にいったん命令フェッチはストップさせるようになっている。これらの検出はすべてS2ステージで、設計簡略化とハザードのシンクロのためこうしている。
で、同じように分岐予測器も動かしているのだが、
- s0ステージ : BTBとBimodalにリクエストを行う
- s1ステージ : BTBのアドレスヒット判定
- s2ステージ : フェッチした命令に分岐予測命令があり、BTBとBimodalの予測に基づいて次のフェッチアドレス変更
となっており、つまり3サイクルかけているのでs2ステージですぐさま命令フェッチアドレスを更新したとしても1サイクルバブルが空く。 BTBのためのSRAMがどれだけ早く動くか謎だが、まあs2ステージで予測を反映させてもいいのかもしれない。
RASの話に戻ると、RASも同様にs2ステージでCALL/RET命令をキャッシュライン上から抜き出してRASエントリを更新している。 いくつかやり方を試行したのだが、例えばRASのインデックスが現在Nだとすると、
- CALL命令(A)を発見するとRASインデックスをN+1にアップデートすると同時に、そのフェッチパケット自体にN+1を渡す(つまり戻りアドレスはN+1に格納されるぞという意味)
- そしてエントリN+1にRASの戻りアドレス(CALL命令のアドレス+4)を格納する
- 例えば別のCALL命令(B)が投機的にフェッチされると、RASのインデックスはN+2となり同様にフェッチパケットにN+2が渡される
- 最初のCALL命令(A)がコミットされると、その時点でRASのインデックスは命令自体が持っているRASインデックスN+1に戻る(つまり、この時点でRET命令がフェッチされるとN+1のエントリのアドレスを使ってね、となる)
- 同様に次のCALL命令(B)がコミットされるとRASのインデックスはN+2に置き換わる
- 万が一CALL命令(B)が投機失敗でフラッシュされる場合、RASのインデックスはN+1のままとなる。
- RET命令をフェッチすると、N+1の値を取り出してフェッチアドレスを更新する
という仕組みだ。で、これを四苦八苦しながら実装してVerilatorで動かしているのだが、いかんせんVerilatorのコンパイルに1回30分以上かかっているので全然進まない。オープンソースEDAのみでの開発はつらいなあ。