|
4 |
RISC-Vプロセッサの設計 (31) |
epochによるキャンセル制御の動機
分岐が成立すると、分岐命令より後ろで既にフェッチした命令を取り消す必要があります。Pipeline processorの設計(19)では、下位段のキャンセル信号C(S+)で後段へのvalidを落とす形でした。この方式では、取り消しの対象となる全段にキャンセル信号を配線し、同じサイクルで無効化します。
FIFOで段をつないだBSVの構成では、もう少し簡単な方法があります。各命令に1bitの「epoch」を付けて流し、分岐を解決する段でだけepochを照合します。
仕組み
epochレジスタを<PC>と<EX>に1つずつ置きます。
- <PC>は、出す命令にその時点のpcEpochを付ける。
- <EX>は、届いた命令のepochが自分のexEpochと一致しなければ、その命令をinvalidにする。
- <EX>で分岐が成立したら、exEpochを反転し、<PC>へ分岐先を送る(redirect)。
- <PC>はredirectを受けたらpcEpochを反転し、分岐先から新しいepochで出し直す。
epochは、redirectの回数を数える世代番号の最下位ビットだと考えると分かりやすいです。
タイミング
0100番地の分岐命令が成立し、0200番地へ飛ぶ例です。括弧内は各命令に付いているepochで、<PC>列の括弧がその時点のpcEpochです。exEpoch列は<EX>が持つレジスタの値です。
| サイクル | PC | IF | ID | EX | exEpoch | 動作 |
|---|---|---|---|---|---|---|
| 1 | 0100(0) | 0 | ||||
| 2 | 0104(0) | 0100(0) | 0 | |||
| 3 | 0108(0) | 0104(0) | 0100(0) | 0 | ||
| 4 | 0200(1) | 0108(0) | 0104(0) | 0100(0) | 0→1 | 分岐成立。exEpochを反転、redirect |
| 5 | 0204(1) | 0200(1) | 0108(0) | 0104(0) | 1 | epoch不一致(0≠1)でinvalid |
| 6 | 0208(1) | 0204(1) | 0200(1) | 0108(0) | 1 | epoch不一致(0≠1)でinvalid |
| 7 | 020c(1) | 0208(1) | 0204(1) | 0200(1) | 1 | epoch一致。分岐先を実行 |
パイプラインFIFOでは下流のruleが先に並ぶので、<EX>が出したredirectを同じサイクルの<PC>が受け取れます(サイクル4)。分岐ペナルティは2サイクルです。この表は、下のコードをbsc 2025.07でコンパイルし、bsimとiverilogの両方で同じ結果になることを確認しています。
1bitで足りる理由
exEpochが2回目に反転するのは、新epochの有効な分岐命令が<EX>に着いたときです。その命令は<PC>がepochを反転した後に出したものです。FIFOでは追い越しが起きないので、それより前に出た旧epochの命令はすべて<EX>を通り過ぎています。
つまり、パイプライン中に同時に存在する世代は「旧」と「新」の最大2つです。世代番号の上位ビットは不要で、LSBだけで区別できます。条件は、redirectを出す段が1つであることと、段間が順序を保つFIFOであることです。
BSVの例
Reg#(Bool) pcEpoch <- mkReg(False);
Reg#(Bool) exEpoch <- mkReg(False);
RWire#(Addr) redirect <- mkRWire;
// <EX>
rule ex_stage;
let m = exs.first;
if (!ex_wait) begin
exs.deq;
Maybe#(EX2MA) out = tagged Invalid;
if (m matches tagged Valid .x &&& x.epoch == exEpoch) begin
let r = exec(x); // 演算と分岐判定
if (r.taken) begin
exEpoch <= !exEpoch;
redirect.wset(r.target);
end
out = tagged Valid r.toMA;
end
mas.enq(out);
end
else
mas.enq(tagged Invalid);
endrule
// <PC>
rule pc_stage (!pc_wait);
Addr p = pc;
Bool e = pcEpoch;
if (redirect.wget matches tagged Valid .t) begin
p = t;
e = !pcEpoch;
pcEpoch <= e;
end
ifs.enq(tagged Valid PC2IF { pc: p, epoch: e });
pc <= p + 4;
endrule
redirect.wsetを<EX>で書き、<PC>でwgetするので、bscは<EX>を<PC>より先にスケジュールします。パイプラインFIFOで決まる順序とも矛盾しません。
注意点
- 取り消される命令は、<EX>に着くまでは普通に流れます。そのため<EX>より前の段でアーキテクチャ状態を変えてはいけません。既存の構成では<IF>/<ID>にそうした副作用はありません。
- 上の例では、pc_waitとredirectが同じサイクルに来るとredirectを取りこぼします。実際には、redirectをレジスタに保持する必要があります。
- redirectを出す段が複数ある場合(MAでの例外、BTBによるID段での予測修正など)は、段ごとに1bitのepochを持たせます。
- キャンセル信号方式と比べると、配線は1bitの付加と比較回路1個で済みます。代わりに、取り消しは<EX>に着いた時点で遅れて行われます。