Article #1146

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.
posted by sakurai on November 10, 2026 #1146

mkRegとmkCRegの違い

epochによるキャンセル制御では、exEpochとredirectをmkCRegで作りました。mkCRegは、mkRegと同じPreludeにある標準の部品です(BSC Libraries Reference Guide 2.4.2。mkRegは2.4.1)。どちらもフリップフロップは1つですが、同じサイクルの中で書いた値を、後のruleが読めるかどうかが違います。この違いを知らずにmkRegを使うと、エラーも警告も出ないまま、パイプラインの性能が半分になることがあります。

mkReg:読めるのはサイクルの始めの値

mkRegを読むと、サイクルの始めの値(FFの出力)が返ります。書いた値が見えるのは次のサイクルです。RISC-Vプロセッサの設計(9)のFIFOの図と同じ描き方をすると、1サイクルの箱の最初で読み、最後で書く形になります。

mkRegの読み書き
mkRegの読み書き

そのためbscは、同じサイクルに動く2つのruleを「読むrule → 書くrule」の順にしか並べません。

mkCReg:同じサイクルに書いた値を後のポートで読める

Reg#(Bool) epoch[2] <- mkCReg(2, False);   // ポート0, 1

mkCReg(n, 初期値)は、1つのFFにn個の口(ポート)を付けたものです。

  • ポートiで読むと、同じサイクルにポート0〜i−1で書かれた値が見えます。
  • サイクルの終わりにFFに入るのは、一番後ろのポートで書かれた値です。
  • ポート数の上限は5です。

mkCRegの読み書き
mkCRegの読み書き

ポート0で書いた値は、サイクルの途中でポート1から見えます。サイクルの最初で読み、最後で書くという箱の形は、ポート0とポート1をつなげたものです。

回路としては、FFの出力に、各ポートの書き込みを優先するMuxが直列に付いたものです。いわゆるバイパス付きのレジスタで、MITのArvindらはEHR(Ephemeral History Register)と呼んでいます。Verilogでは、bsc付属のプリミティブ(CRegN5.vなど)になります。

例:<EX>が書き、<ID>が読む

<EX>がepochを反転し、同じサイクルに<ID>がそれを読む場面を、最小の形にしました。<ID>から<EX>へはパイプラインFIFOでつないでいるので、ruleは<EX>(deq)→<ID>(enq)の順に並ぶ必要があります。<EX>はサイクル2でepochを反転します。

// mkRegの場合
Reg#(Bool) epoch <- mkReg(False);
FIFO#(int) q     <- mkPipelineFIFO;   // ID → EX

rule ex;
   q.deq;
   if (cyc == 2) epoch <= !epoch;      // 書く
endrule

rule id;
   q.enq(cyc);
   $display("cyc %0d id sees epoch=%0d", cyc, pack(epoch));   // 読む
endrule

mkCRegの場合は、宣言をReg#(Bool) epoch[2] <- mkCReg(2, False);にし、exではepoch[0]、idではepoch[1]を使います。それ以外は同じです。

bsimで動かした結果です(bsc 2025.07、Verilog(iverilog)でも同じ結果)。

サイクル mkRegの<EX> mkRegの<ID> mkCRegの<EX> mkCRegの<ID>
0 epoch=0 epoch=0
1 発火 発火 epoch=0
2 epoch=0 発火(反転) epoch=1
3 発火 発火 epoch=1
4 epoch=0 発火 epoch=1
5 発火 発火 epoch=1

mkCRegでは、<EX>と<ID>が毎サイクル動き、<EX>がサイクル2に反転した値を、同じサイクルの<ID>が読んでいます。

mkRegでは、<EX>と<ID>が1サイクルおきにしか動きません。パイプラインの流量が半分になっています。<EX>はサイクル2に動かないので、反転も起きていません。

mkRegで何が起きたか

2つのruleには、逆向きの順序の要求があります。

  • パイプラインFIFOは、<EX>(deq)→<ID>(enq)の順を要求します。
  • mkRegは、読む<ID>→書く<EX>の順を要求します。

1つのruleの中の読み書きは切り離せないので、rule ex(Dequeue, Write)とrule id(Read, Enqueue)を1サイクルの箱に並べてみると、次のようになります。

1サイクルの中のruleの並び
1サイクルの中のruleの並び

mkRegでは、どちらの順に並べても、どこかで順序の規則を破ります。両方を満たす順序はないので、bscは2つのruleを同じサイクルに並べられません。-show-rule-relで見ると、次のように両方向の衝突が出ます。

Scheduling info for rules "RL_ex" and "RL_id":
    <>
    conflict:
    calls to
      epoch.write vs. epoch.read
      ...

それでも、エラーも警告も出ません。FIFOが空なら<ID>だけ、満杯なら<EX>だけが動ける状態になり、2つのruleが交互に動くだけだからです。動作としては正しく、遅いだけなので、気づきにくい落とし穴です。

mkCRegにすると、<EX>がポート0で書き、<ID>がポート1で読むので、<EX>→<ID>の順で矛盾がなくなります。

使いどころ

mkCRegが要るのは、「先に動くruleが書き、同じサイクルに後のruleがそれを読む」場所です。このプロセッサでは、次の3つがそれにあたります。

レジスタ 書く(前のポート) 読む(後のポート)
exEpoch <EX>(0) <ID>(1)、<IF>(2)
redirect <EX>(0) <PC>(1)
PipeQのfull deq側(0) enq側(1)

PipeQのfullは、パイプライン制御をFIFOで考えるの、満杯でも同じサイクルにdeqがあればenqできる、という動作そのものです。mkPipelineFIFOも、中身は同じようにCRegで作られています。

注意点

  • ポートの番号は、ruleの順序に合わせます。先に動くruleに小さい番号を割り当てます。上の例でポートを逆(<EX>が1、<ID>が0)にすると、mkRegのときとまったく同じ結果になりました。1サイクルおきにしか動かず、やはり警告も出ません。
  • Muxが直列に入るので、ポートを増やすほど組合せ回路の経路が延びます。同じサイクルの受け渡しが本当に要る所だけに使います。
  • 1サイクル遅れてよい所は、mkRegで構いません。たとえば分岐予測の表のように、古い値を読んでも正しさが変わらないものです。

左矢前のブログ 次のブログ右矢

Leave a Comment

Your email address will not be published.

You may use Markdown syntax. If you include an ad such as http://, it will be invalidated by our AI system.

Please enter the numbers as they are shown in the image above.