公開日:2021/02/27
title.png

15th Season


#15-4 CPUの動作設計

 マイコンを作るシリーズの最初のターゲットはCPU(前回命名、Moscoviumコア)でした。以下の様なブロック図で設計を進めています。

a153_cpublk.png

 引き続き、CPUコアの設計を続けます。

パイプライン

 設計方針では、「パイプライン方式ではなく、昔ながらのステートマシン方式」を選択しました。以下図に一般的な5段パイプラインの実行状態を示します。

a154_pipe5s.png

 上図では、横軸に時間の経過が取られていて、縦軸には実行する命令が上から順に並んでいます。1ステージはクロック立ち上がりエッジ間の1サイクルで実行します。従って、1個の命令には5サイクルの実行時間がかかります。
 しかし、IF,ID,EX,MA,WBの各ステージは1段づつずれて同時並列に実行されるため、実質的には各命令共、1サイクルで処理を終了しています。

※上図の場合、縦に7個の命令が、横に11サイクルで実行終了しているので、1命令当たりだと11サイクル/7命令=1.57サイクル/命令です。上図では、命令7個で止めているので、最後の命令7の5サイクル分が加算される分、1.0サイクル/命令を上回っています。命令数N→∞の極限では、1.0サイクル/命令となります。

 ただこの図は、よく教科書で見かける「理想的」なケースなのです。
 実際の命令に置き換えて考えてみると、例えば命令1のWBステージでレジスタr0にデータをセットする場合、命令2または3のEXで同じr0を操作しようとすると、WBの実行終了を待つ(ストール)必要が生じます。
 他にも分岐命令などを実行すると、分岐先アドレスから新たに命令をフェッチ(IFステージ)する必要が生じるので、パイプラインが乱れます。

Moscovium 2段パイプライン

 一方、下図が今設計を進めているCPUコア、Moscoviumの実行状態図です。

a154_pipemc.png

 何だかんだ、Moscoviumの実行形態も、2段パイプラインとなっています。……たったの2段を「パイプライン」と言えるかどうかは疑問ですが……。
 Moscoviumの実行ステージは、IDとEX(またはMA)が縮退して1つのステージを構成しています。一般的な5段パイプラインだと2つのステージに分割されている論理を、1つのステージ(1サイクル)に押し込んでいるので、信号の伝搬速度としては遅くなっていて、5段パイプラインに比べて実行クロック周波数を高速化できない欠点があります。


 上図は、以下のプログラムを実行した場合の様子です。命令セットのマニュアルはこちらです。

  pushw r0
  ldbiu r0,2
  loop:
  subi r0,1
  bne loop
  popw r0
  mov r0,r0


 動作としては、r0=2として、r0を減算(−1)しながら、ループ(loop:)しています。r0=2でスタートしているので、都合2回ループしたら抜けます。従って、上図4行目の分岐(bne)命令は「分岐」していますが、6行目のbne命令は分岐していません。

※Moscoviumの分岐命令は、条件が成立して分岐すると2サイクル、分岐しなかった場合は1サイクルで実行します。

 bne命令の条件が成立して分岐した時だけ、次命令のIFステージがストールしてbne命令の実行サイクル数が2サイクルになっていることが解ります。パイプラインが浅い分、乱れた時のオーバヘッドは最小限で済んでいます。

 また、がちがちのパイプラインに縛られないので、通常ID&EXステージであるべきところを飛ばして、ID&MAステージとして実行したり、柔軟にリソースを最適活用できるのも利点です。

命令の動作サンプル

 続いて、Moscoviumのブロック図を使って、具体的な処理の中身を見て行きます。こちらの命令セットマニュアルで説明してあるように、add命令やand命令などの単純な演算命令は1サイクルで実行が済んでしまって、何も説明することが無いので、少し複雑な動きをするjal命令を例に処理の流れを説明します。

 まず、jal命令ですが、サブルーチンへジャンプする命令です。サブルーチンの処理終了後、rtn命令でjal命令の次命令に戻ってくる必要があるので、ジャンプする前に、現在のプログラムカウンタpc値をスタックに積んでおく必要があります。
 この様な複雑な処理のため、jal命令には3サイクルの実行時間がかかります。

 2サイクル目、ID&MAステージではAバスとBバスを使っているのですが、3サイクル目WBステージでもAバスを使い、かつ、ALUを通って汎用レジスタ→pcへのデータ転送しています。このように、2サイクル目の処理と3サイクル目の処理はリソース(この場合はAバス)が競合しているため、1サイクル内で同時に実行することができず、jal命令の実行には3サイクルを要しています。

 例えば、MEMブロックへのレジスタデータ転送パスと、ALUへのレジスタデータ転送パスを独立して2系統持たせることが出来れば、メモリアクセスと汎用レジスタ→pc転送を1サイクル内で同時に処理できるようになるので、高速に(1サイクル短く)処理することが出来るようになります。
 一方、このようにどんどん専用リソースを追加して行くと、論理規模が大きく肥大化して行きます。

 今回のMoscoviumコアは、「教科書に載っている様なシンプルなCPU」を目指しているので、高速化のためのリソース多重化はスコープ外としています。最小限に絞ったリソースをうまく使って、各命令を処理していきます。


 では以降、CPU内の各ブロックにフォーカスを当てて、基本設計を進めて行きましょう。

メモリアクセスMEMブロック

 メモリアクセスMEMブロックのブロック図は以下の様になります。

a154_mem.png

 MEMブロックは、構造としては簡単です。
 MEMブロックはCPUの外に接続されたROMやRAM、I/Oレジスタなどのメモリにアクセスするための、「メモリバス🚌」アクセスを司っています。

メモリバス仕様

 ここで、メモリバスのアクセス仕様を確定しておきます。まず、メモリバスに使われる信号名とその仕様を示します。

 はい、そうです。勿論、ビッグエンディアンです。

メモリバスアクセスタイミング

 以下、リードおよびライトバスサイクルのアクセスタイミング例です。

a154_mem_bus.png

 リード/ライトアクセスとも、1サイクルで読み書きが終了する高速メモリの場合、brdy=0となる黄色部分のサイクルは一切挿入されません。

レジスタファイルRGFブロック

 レジスタファイルRGFブロック内には、プログラマモデルで決定したCPUのレジスタを実装します(下図)。

a154_rgf.png

※後日拡張したレジスタなどが更新された最新のブロック図となっています。

 バンク内のブロック図は以下図の通りです。

a154_rgf_bank.png

 バンクbankブロック内に汎用レジスタ×8個を格納しています。A,Bバス出力は、bank内で1系統に縮退してから出力します。設計としては、bankブロックを追加/削除することで、容易に実装バンク数を変更可能な構成にしておきます。

算術論理演算ユニットALU

 算術論理演算ユニットALUには、加減算器art、論理演算器log、シフト演算器sft(バレルシフタ)が実装されています(下図)。

a154_alu.png

 CTLブロックからのコマンドに基づいてAバス値とBバス値の演算を施し、Cバスに出力します。以下表に演算コマンドをまとめておきます。

ブロック コマンド名 コード 演算
ALU NOP 4'h0 no operation: C=0x0000
art ADD 4'h1 add: C=A + B
SUB 4'h2 subtract: C=A - B
ADC 4'h3 add with carry: C=A + B + cin
SBB 4'h4 subtract with borrow: C=A - B - cin
log AND 4'h5 bitwise and: C=A&B
OR 4'h6 bitwise or: C=A | B
EOR 4'h7 bitwise exclusive or: C=A ^ B
NOT 4'h8 bitwise not: C=~A
OUTA 4'h9 through: C=A
CEND 4'ha change endian: C=(A<<8) | (A>>8)
LDIS 4'hb load signed byte: C=int8(B&0xff)
LDIL 4'hc load low byte: C=(A&0xff00) | B&0xff
LDIH 4'hd load high byte: C=(B<<8) | (A&0xff)
sft ASFT 4'he arithmetical shift: C=int16(A)<<int5(B)
LSFT 4'hf logical shift: C=uint16(A)<<int5(B)

フェッチFCHブロック

 フェッチFCHブロックは、プログラムメモリから命令コードをフェッチするブロックです(下図)。

a154_fch.png

 フェッチバスから命令コードを読み出します。
 メモリバスとは異なりフェッチバスは、指定したアドレス(16ビット境界)からワードデータを読み出すだけですので、このバスはレイテンシ0のメモリとなっています。つまり、CPUクロックclk↑でアドレスfadr[15:0]が更新されると、次のclk↑で有効データがfdat[15:0]から読み出すことが出来るメモリです。

 プログラムカウンタpcはフェッチする度にインクリメント(+2)して次々命令を読み出す必要があるので、pc+2した結果をRGFブロックのpcに戻すパスを持っています。pcはリセット解除後、0x0000に初期化されているので、Moscoviumは0x0000番地から命令を実行開始します。
 読み出したデータは命令レジスタir(Instruction Register)に取り込み、CTLブロックでデコードするため、送信します。


 CPUブロック図の中で、残るはCTLブロックのみとなりました。CTLブロックについては次回解説して、CPUの設計を終了したいと思います。


btn_arduino.png

btn_prev.png btn_next.png

home