Posts Tagged with "Pipeline processor"

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

1段FIFOの検討

FIFOどうしを上位で接続する場合の結線を見ると、図573.1のようになっています。

図%%.1
図573.1 FIFO間の結線

上位ステージFIFOにデータが存在し(!empty)かつ下位ステージFIFOがフルでなければ(!full)上位ステージFIFOのDEQと下位ステージFIFOのENQが同時に実行されます。1段FIFOの実現性を考えると、下位のDEQが有れば上位に!fullになるように制御すれば良さそうです。

修正前の1サイクルおきの動作を示します。

図%-%.1
図573.1 1段FIFOの動作(修正前)

元の論理はemptyとfullは背反論理であり、

     assign FULL_N = !empty_reg;
     assign EMPTY_N = empty_reg ;

このようになっていましたが、これに対して、

     assign FULL_N = !empty_reg || DEQ;
     assign EMPTY_N = empty_reg ;

のように修正したシミュレーション結果を示します。

図%-%.2
図573.2 1段FIFOの動作(修正後)

このように正しく動作しました。なお、この論理はパイプラインFIFOと呼ばれるLFIFOと全く同じであり、ソースに対して

     FIFO#(int) ifs    <- mkLFIFO;

のようにパイプラインFIFOを生成しても全く同じ動作を行います。


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

posted by sakurai on December 8, 2022 #566

1段FIFOのシミュレーション

本来はFIFOは1段で良いはずですが、なぜ2段となっているのでしょうか?ここだけ見ると物量が倍になってしまいます。たいした物量ではないものの、1段で済ませないかを検討します。

前述のように、bscが出力するシミュレーションモデルではFIFO2という2段FIFOを使用しています。このモデルを1段FIFOに入れ替えたいのですが、bsimモデルは触ることができないので、verilogモデルのほうを修正します。上位において次のようにFIFO2を呼び出しているところをテキストエディタでFIFO1に修正します。

// submodule exs
FIFO2 #(.width(32'd32), .guarded(32'd1)) exs(.RST(RST_N),
                                             .CLK(CLK),
                                             .D_IN(exs$D_IN),
                                             .ENQ(exs$ENQ),
                                             .DEQ(exs$DEQ),
                                             .CLR(exs$CLR),
                                             .D_OUT(exs$D_OUT),
                                             .FULL_N(exs$FULL_N),
                                             .EMPTY_N(exs$EMPTY_N));

次にverilogシミュレーションを行うと、以下のような波形が得られます。

図%%.1
図566.1 PCパイプラインタイムチャート

1ステージのレイテンシが2サイクルとなってしまいます。

これは、emtpyやfullは1サイクルおきにアサートされ、そのため2サイクルに1度しかキューに投入できないためです。従ってスループットが半分、レイテンシが倍になってしまいます。FIFOの動作上、空でないと上位からは詰められず、一方で詰まってないと下位からは引き取れないので、交互動作になるのは当然です。

通常のパイプラインはエンキューとデキューが同時に行えるので、これは改善できるかもしれません。


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

posted by sakurai on December 6, 2022 #564

verilogシミュレーション

ついでにverilogでもシミュレーションしてみます。

$ bsc -verilog Tb.bsv
Verilog file created: mkTb.v
$ iverilog top.v mkTb.v -y /usr/local/lib/Bluespec/Verilog/ -o mkTb.exev
$ ./mkTb.exev
VCD info: dumpfile verilog.vcd opened for output.
------
------
 pc_if = 00000000
------
 pc_if = 00000004
 pc_id = 00000000
------
 pc_if = 00000008
 pc_id = 00000004
 pc_ex = 00000000
------
 pc_if = 0000000c
 pc_id = 00000008
 pc_ex = 00000004
 pc_ma = 00000000
------
 pc_if = 00000010
 pc_id = 0000000c
 pc_ex = 00000008
 pc_ma = 00000004
 pc_wb = 00000000
------
 pc_if = 00000014
 pc_id = 00000010
 pc_ex = 0000000c
 pc_ma = 00000008
 pc_wb = 00000004

のように正しくパイプラインが動作しました。また、gtkwaveで取得したタイムチャートを図564.1に示します。

gtkwファイルはここ

図%-%.1
図564.1 PCパイプラインタイムチャート

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

posted by sakurai on December 5, 2022 #563

bsimシミュレーション

このソースに対して、以下のようにコンパイル、bsimシミュレーションを実行します。

$ bsc -sim Tb.bsv; bsc -sim -e mkTb -o mkTb.exe
Elaborated module file created: mkTb.ba
Bluesim object created: mkTb.{h,o}
Bluesim object created: model_mkTb.{h,o}
Simulation shared library created: mkTb.exe.so
Simulation executable created: mkTb.exe
$ ./mkTb.exe -V
------
------
 pc_if = 0000
------
 pc_if = 0004
 pc_id = 0000
------
 pc_if = 0008
 pc_id = 0004
 pc_ex = 0000
------
 pc_if = 000c
 pc_id = 0008
 pc_ex = 0004
 pc_ma = 0000
------
 pc_if = 0010
 pc_id = 000c
 pc_ex = 0008
 pc_ma = 0004
 pc_wb = 0000
------
 pc_if = 0014
 pc_id = 0010
 pc_ex = 000c
 pc_ma = 0008
 pc_wb = 0004

のように正しくパイプラインが動作しました。また、gtkwaveで取得したタイムチャートを図563.1に示します。

図%-%.1
図563.1 PCパイプラインタイムチャート

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

posted by sakurai on December 3, 2022 #562

BSVのFIFO調査

BSVにおいて、パイプラインレジスタとしてFIFOを使用できるか調査します。まず、PCパイプラインを設計します。PCパイプラインとは、各ステージのPCを保持することで、例えば各ステージにおいてEIT(例外、割り込み、トラップ)が起きた場合、EITが起こった命令アドレスをハンドラに正しく伝えるためのものです。

Tb.bsv


import FIFO::*;

(* synthesize *)
module mkTb (Empty);

   Reg#(int) pc    <- mkReg(0);
   FIFO#(int) ifs    <- mkFIFO;
   FIFO#(int) ids    <- mkFIFO;
   FIFO#(int) exs    <- mkFIFO;
   FIFO#(int) mas    <- mkFIFO;
   FIFO#(int) wbs    <- mkFIFO;

   // <PC>
   rule pc_stage;
      if (pc > 100) $finish(0);
      $display("------");
      ifs.enq(pc);
      pc <= pc + 4;
   endrule

   // <IF>
   rule if_stage;
      let pc_if = ifs.first; ifs.deq;
      $display (" pc_if = %04h", pc_if);
      ids.enq (pc_if);
   endrule

   // <ID>
   rule id_stage;
      let pc_id = ids.first; ids.deq;
      $display (" pc_id = %04h", pc_id);
      exs.enq (pc_id);
   endrule

   // <EX>
   rule ex_stage;
      let pc_ex = exs.first; exs.deq;
      $display (" pc_ex = %04h", pc_ex);
      mas.enq (pc_ex);
   endrule

   // <MA>
   rule ma_stage;
      let pc_ma = mas.first; mas.deq;
      $display (" pc_ma = %04h", pc_ma);
      wbs.enq (pc_ma);
   endrule

   // <WB>
   rule wb_stage;
      let pc_wb = wbs.first; wbs.deq;
      $display (" pc_wb = %04h", pc_wb);
   endrule

endmodule: mkTb

ここで、<PC>は入力レジスタは無いため、ここではFIFOは使用しません(が、後で検討します)。単にPCレジスタを設置するだけです。これは<PC>(PCステージ)のPCということになります。通常はPC<=PC+4ですが、分岐時等ではオフセットが足されます。

各ステージでは、入力されたPCをデキューし、表示し、サイクルの終わりで次のステージにそのPCをエンキューします。

図%-%.1
図562.1 FIFOのDequeuとEnqueueの関係
従って、同一サイクルでエンキューしたものをデキューする(バイパス、パススルー)ことはできません。

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

posted by sakurai on October 28, 2022 #541

<PC>の必要性

従来例を再掲します。作成しようとするパイプラインロジックは大略、前の図540.1のとおりです。ただし図540.1では<PC>が明示されていません。

図%-%.1
図540.1 あるRISC-Vプロセッサのパイプライン図(再掲)

次の命令ストリームの起点であるPCは、基本的に

  1. 分岐が無ければ(有っても投機的に数命令は実行)、現PC+4
  2. PC相対分岐が有ればPC+オフセット
  3. 絶対分岐の場合はその値

を次のPCにセットします。この他にも割り込み、例外処理等で新PCを演算し、次のPCにセットします。これを<PC>を意識した図に書き直すと、図541.1のように判りやすい図となります。図540.1のように、PC演算器やレジスタが窮屈に<IF>の中に折れ曲がることは無く、ストレートフォワードに描かれています。<PC>の入力が<PC>であることも図541.1を見れば明らかです。

図%%.1
図541.1 <PC>の考え方に基づき書き直したパイプライン図

図541.1では分岐の場合は<EX>の結果レジスタがPCであることから元の<EX>と分岐先<PC>が重なり、その次のステージが分岐先<IF>となることが良くわかります。クロックベースが原則のパイプラインを記号で書けば、

   <PC><IF><ID><EX><WB>
          <PC><IF><ID><EX><WB>

となります。分岐先のPC計算を分岐元のEXで代行している様子が良く分かります。

一方、図540.1では<EX>の後にPCレジスタが入り、その次に<IF>ステージとなるので、記号で書くと、

   <IF><ID><EX><WB>
          <IF><ID><EX><WB>

となり、<IF>が湧き出す意味が不明です。<IF>の入力レジスタがPCで、分岐命令の<EX>でそれを生成しているのだからという説明になると思いますが、であればPCを明示したほうが分かりやすいのは言うまでもありません。

逆にPCの生成も含めたパイプラインを理解してしまえば頭の中でできるため、取り立てて<PC>の必要性は感じなくなるので、不思議とも思わないようになりますが、教育的とは言えません。そもそも最も重要なPCの更新ステージはどこなのかが図示されていません。これでは分岐命令の高速化、例えばBTB等による<PC>の物量増加も、どのステージの話なのか理解しづらくなります。

<PC>の制御法

<PC>の必要性が理解できたところで、過去記事でパイプライン制御論理を示しました。<PC>の上流が<PC>だからといって上流に出力するwait信号を自分自身に入れると永久にストールするので、自分自身に入れてはいけません。

また、図示されていませんが、PCレジスタ自体も同じパイプライン制御論理によりパイプラインを流します。その理由は、例外の際には分岐が発生しますが、戻りアドレスをスタックに格納します。その戻りアドレスを上記PCパイプラインから適宜取得するためです。


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

posted by sakurai on October 27, 2022 #540

PCステージ

PCステージ(<PC>)を設計します。と言うとなじみがありませんね。一般には存在しないステージなので。以降ではステージ記号を次のように定義します。例:PCステージ:=<PC>

一般にはパイプライン図は<IF>から始まっています。例えば図540.1のように。PCレジスタは<IF>の入力レジスタとして描かれています。良く見ると、本来の<PC>の演算器や結果レジスタは折り曲げられて、窮屈な恰好をしています。

図%%.1
図540.1 あるRISC-Vプロセッサのパイプライン図(引用元)

図540.2の黄色のステージ記号及び点線は弊社で追加しました。FDレジスタとは<IF>と<ID>の間のステージなので、<IF>の結果レジスタです。つまりFDレジスタの左側は全て<IF>です。この図でもPCレジスタは窮屈に折り曲げられています。

図%%.1
図540.2 あるRISC-Vプロセッサのパイプライン図(引用元)

この図のように<IF>の中にPCレジスタが書かれています。これが変であることに気づかれたでしょうか。パイプラインプロセッサは、パイプラインレジスタにより、組み合わせ回路の結果を次々に受けていくバケツリレー式であり、本来ステージの内部は組み合わせ回路で構成され、レジスタは無いはずです。

それでは<IF>の入力である命令アドレスはどこのステージで生成されるのでしょうか?それが<PC>です。つまり一般の図ではPCは<IF>の入力レジスタのように描かれていますが、実は他のステージと同様、<PC>の結果レジスタです。こう考えるとパイプラインの各ステージが統一的に理解できます。

例えば分岐命令では分岐先アドレス計算をする必要があり、それは必ず<ID>の後になります。図540.2にもPC calculationとありますが、それがパイプライン中の2個目の<PC>です。そして分岐条件が確定した後に<IF>が実行されるので、<PC>は明示したほうが判りやすいです。

一般的に<PC>が無視される理由としては、有名な教科書がIF, ID, EX, MEM, WBの5段で書かれている、からなのかもしれませんが、本来は

<PC><IF><ID><EX><MA><WB>

の6段パイプです。このことは過去記事でも指摘しています。

次の質問です。<PC>の前のステージは何でしょうか?答えは<PC>です。分岐しない限り<PC>は次の<PC>を生成し、1サイクル毎に次々にストリームを生み出します。

一方、例えば分岐命令等のようにパイプラインの途中に<PC>が出現することがあり、複数現れる場合でも命令ストリームの開始PCの計算ですから、いきなり別の命令ストリームの<IF>が現れるよりは判りやすいと思います。無から有は湧いてきません。

割り込みや例外を考える時には一層重要です。割り込みレベルやマスクや例外の種類等の情報を総合して、まずPCがどうなるかを決定します。PCさえ確定すれば、後は<IF>以降のパイプラインを普通に流せば良いだけです。つまり常に<PC>が命令ストリームの起点となります。

本稿で述べたことはエンジニアとしては意識しなくても、設計できるし、見方を変えて設計が変わるわけではないので、哲学に属する話かもしれません。

しかし、設計思想として大事な話なので強調しています。この思想のメリットもあり、例えば<PC>においてPCアドレスを命令アドレスとして命令メモリに流し、<PC>と<IF>の間のクロックで命令アドレスをラッチし、<IF>として命令メモリからデータを流し、プロセッサは<IF>と<ID>の間のクロックで命令データをラッチする、というフローが一般的ですが、<PC>を<IF>の中に混ぜると<IF>が複雑になります。一方この考え方であれば、ラッチベースの動作がすっきりします。


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

posted by sakurai on October 25, 2022 #536

デコーダのソースの一部を示します。

genRules(
   switch(in_instr,
      when(pat(n(7'b0000000), v, v, n(3'b000), v, n(7'b0110011)), fadd),
      when(pat(n(7'b0100000), v, v, n(3'b000), v, n(7'b0110011)), fsub),
      when(pat(               v, v, n(3'b000), v, n(7'b0010011)), faddi),
      when(pat(n(7'b0000000), v, v, n(3'b111), v, n(7'b0110011)), fand),
      when(pat(n(7'b0000000), v, v, n(3'b110), v, n(7'b0110011)), ffor),
      when(pat(n(7'b0000000), v, v, n(3'b100), v, n(7'b0110011)), fxor),
      when(pat(               v, v, n(3'b111), v, n(7'b0010011)), fandi),
      when(pat(               v, v, n(3'b110), v, n(7'b0010011)), fori),
      when(pat(               v, v, n(3'b100), v, n(7'b0010011)), fxori),
      when(pat(               v, v, n(3'b010), v, n(7'b0000011)), flw),
      when(pat(            v, v, v, n(3'b010), v, n(7'b0100011)), fsw),
      when(pat(n(7'b0000000), v, v, n(3'b001), v, n(7'b0110011)), fsll),
      when(pat(n(7'b0000000), v, v, n(3'b101), v, n(7'b0110011)), fsrl),
      when(pat(n(7'b0100000), v, v, n(3'b101), v, n(7'b0110011)), fsra),
      when(pat(n(7'b0000000), v, v, n(3'b001), v, n(7'b0010011)), fslli),
      when(pat(n(7'b0000000), v, v, n(3'b101), v, n(7'b0010011)), fsrli),
      when(pat(n(7'b0100000), v, v, n(3'b101), v, n(7'b0010011)), fsrai),
      when(pat(n(7'b0000000), v, v, n(3'b010), v, n(7'b0110011)), fslt),
      when(pat(n(7'b0000000), v, v, n(3'b011), v, n(7'b0110011)), fsltu),
      when(pat(               v, v, n(3'b010), v, n(7'b0010011)), fslti),
      when(pat(               v, v, n(3'b011), v, n(7'b0010011)), fsltiu),
      when(pat(v, v, v, v, n(3'b000), v, v, n(7'b1100011)), fbeq),
      when(pat(v, v, v, v, n(3'b001), v, v, n(7'b1100011)), fbne),
      when(pat(v, v, v, v, n(3'b100), v, v, n(7'b1100011)), fblt),
      when(pat(v, v, v, v, n(3'b101), v, v, n(7'b1100011)), fbge),
      when(pat(v, v, v, v, n(3'b110), v, v, n(7'b1100011)), fbltu),
      when(pat(v, v, v, v, n(3'b111), v, v, n(7'b1100011)), fbgeu),
      when(pat(v, v, v, v, v, n(7'b1101111)), fjal),
      when(pat(               v, v, n(3'b000), v, n(7'b1100111)), fjalr),
      when(pat(               v, v, n(7'b0110111)), flui),
      when(pat(               v, v, n(7'b0010111)), fauipc),
      when(pat(               v, v, n(3'b001), v, n(7'b1110011)), fcsrrw),
      when(pat(               v, v, n(3'b101), v, n(7'b1110011)), fcsrrwi),
      when(pat(               v, v, n(3'b010), v, n(7'b1110011)), fcsrrs),
      when(pat(               v, v, n(3'b110), v, n(7'b1110011)), fcsrrsi),
      when(pat(               v, v, n(3'b011), v, n(7'b1110011)), fcsrrc),
      when(pat(               v, v, n(3'b111), v, n(7'b1110011)), fcsrrci),
      when(pat(n(25'b0), n(7'b1110011)), fecall)
   ) // switch
);

これは1ステップ目のデコーダステップであり、ここでビットパターン、例えばaddi命令とのマッチが取れれば、2ステップ目として個別の関数、例えばfaddiが呼び出されます。一例であるfaddiを示せば、

function Action faddi(Bit#(12) imm, Bit#(5) rs1, Bit#(5) rd) = 
   action
      Int#(32) immSext = signExtend(unpack(imm));
      if (immSext == 0)
         $display("time %4t -   mv\t%s,%s", $time, regname(rd), regname(rs1));
      else if (rs1 == 0)
         $display("time %4t -   li\t%s,%0d", $time, regname(rd), immSext);
      else
         $display("time %4t -   addi\t%s,%s,%0d", $time, regname(rd), regname(rs1), immSext);
   endaction;

RISC-Vにおいてaddi命令はイミディエイトがゼロの場合はmv命令として使用され、逆にソースレジスタにゼロレジスタを指定すれば、イミディエイトロード(li)命令として働きます。これらはプロセッサの設計的には不要な処理ですが、逆アセンブラのシンタックスシュガーとして実装しました。

2ステップ目の処理として、各種関数を命令数だけ並べる必要があります。


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

posted by sakurai on October 24, 2022 #535

BSV実行結果(ハードウェアからの出力)と逆アセンブルリストをサイドバイサイドに並べた図を535.1に示します。分岐関係を除いて一致していることが分かります。BSV実行出力にアドレスやデータを表示することは簡単ですが、逆アセンブラを作成しているわけではないので、それらの表示機能は実装していません。

図%%.1
図535.1 実行結果と逆アセンブルリスト

この後も「はじめてのCPU自作」にあるようにECALLやCSR操作命令等を実装し、ひととおりriscv-testが通るデコーダまで作成が完了しました。ところで論理合成ツールには論理圧縮機能が含まれるため、出力されたデコーダ論理を合成前に圧縮する必要はありません。


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

posted by sakurai on October 21, 2022 #534

コンパイルしたオブジェクトファイルの逆アセンブルリストは以下のとおりです。

図%%.1
図534.1 逆アセンブルリスト

これを「ハードウェアインタプリター」にかけたら以下のような実行結果となりました。

図%%.2
図534.2 インタプリター実行結果

addi命令のイミディエイトをゼロにすることでmv命令としたり、逆にレジスタをゼロレジスタとすることでli命令としたり、「インタプリター」の出力を細工し、逆アセンブルリストと合わせています。ただし、PCを実装していないので、PC相対命令のラベルは一致していません。このようにPC相対を除き、逆アセンブル結果とほぼ合わせることができました。


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


ページ: