Instruction Fetch モジュールの作成


1. はじめに

今回はプロセッサのうち,instruction fetch 部を設計記述する. instruction fetch とは,プログラムカウンタに従って, メモリから命令を読み出す動作である. まず,命令が格納されているメモリをどのようにモデル化 するかを考え,次にプログラムカウンタに従ってメモリを読む機構, 分岐命令に従ってプログラムカウンタを書き換える機構を考える.

2. Instructio fetch に必要な機能

残念ながら,どんな設計でも自動的に行えるような定式化された 方法というものはなく,ある程度試行錯誤を行うものである. しかし,試行錯誤を少なくするような考え方というものは有る. 一つはトップダウン設計の考え方であり, LSI 設計であれば,まずモジュールに必要な機能を書きだし, これを細分化していく. もちろん細分化していく課程で不都合が起こり, 上位の設計を修正することも有るだろう. しかし,一応の指針とは成るはずである.

2.1 機能の細分化

instruction fetch を行うのに必要な機能とは何であろうか? 既に述べたように instruction fetch というのは, プログラムカウンタに従ってメモリから命令を読み込む, また分岐命令に従ってプログラムカウンタを書き換える, という動作である.

これをもう少し書き下し,ハードウエアとして実現しやすいようにすると (各々が並列に動作することを考慮して機能を細分化すると), 次のようになる.

単純に考えると,これだけである. ただし,ストールが起こった場合には, いくつかの動作を停止して問題が生じないようにしなければならないし, 分岐が起こった場合には既に読み込んだ命令を無効としなければならない. これらを考慮した制御を行い,メモリへ接続すれば instruction fetch が実現できるはずである.

2.2 instructio fetch モジュールに必要な要素

必要な機能が書き出せたら, これを実現するために必要な入出力ポートと の部品を考える. 例えば,メモリを読むのだから,少なくともアドレスを主力し, データ(命令)を入力するであろう. また,後段へ読み込んだ命令を出力するであろう. 内部に必要な部品を考えると, プログラムカウンタには D-FF によるレジスタとカウントアップ回路が 必要であろう. このようにして,各々の機能に必要なポートと部品を書きだしていく. 例えば次のようになるはずである. このあたりまでくれば,あとは初期化とストール, 分岐時の条件分岐(セレクタ)を記述すれば良いと見当が付くであろう.

3. verilog-HDL とメモリ

ディジタル回路の設計者にとって,メモリは実に厄介な代物の一つである. 高集積度が求められるメモリは,出来合いのゲートを並べて作る 訳にはいかず,回路や素子やレイアウトを含めた 専門家による設計が必要である. したがってメモリの挙動を正確に verilog-HDL で記述しようとしても, メモリについての専門的な知識が無ければ無理である.

このため,一般的に製造者から提供されるメモリマクロを使用する. このうようなマクロには,遅延時間を始めとした物理的な特性を, ある程度シミュレートする機能が盛り込まれている. また,自動的にテストを行う回路を内蔵している事もある.

今回は詳細なモデルを使用する必要はないが, LSI を設計するときにメモリを使いたくなったら, まず LSI ベンダのメモリマクロを捜すと思っておくと良い.

4. 非同期メモリと同期メモリ

以前は非同期メモリが多かった. 高密度に集積されたメモリにクロックを分配するより, 外側で適当なタイミングを計って読み書きする方が経済的であったのである. しかし,高速化に伴ってタイミングを計るのが難しくなってきた. そこで,全てをクロックを基準に動作させることにして, 細かいタイミングの違いはメモリ内部で吸収することになった. SDRAM や DDR-SDRAM はその例である. LSI 内部でも事情はあまり変わらない.LSI 上にも非同期メモリと 同期メモリを作ることができるが,集積度の向上に伴って, メモリアクセスに要する時間が,相対的に大きくなりつつある. これを隠蔽するにはメモリアクセスをパイプライン化するより無く, 同期メモリを使う方が良い. 今後ますます,非同期メモリが使われる事は少なくなっていくであろう.

本演習では同期メモリを用いることにする.

5. 命令専用メモリ

単純化のため, 金星プロセッサでは命令専用メモリとデータ専用メモリを用いることにする. 一見変な構成だが,命令専用キャッシュとデータ専用キャッシュを用いていると 考えれば,さほど理不尽でもない. キャッシュコントロール用の回路と,共通のバスインタフェースを付加すれば, 一般的なプロセッサとして発展可能である.

ここでは,リスト1 に示すような同期メモリを命令メモリとして用いることにする. これはシミュレーションに用いることだけを考えて記述した、単純なもので 二次元配列を用いてメモリセルアレイを表現している。 $readmemh はシミュレーション時にメモリへ値をセットするものである。 詳しい $readmemh の仕様については、 verilog-HDL 入門 10.7.3 を参照してほしい. もちろん、このようなシミュレーション用に 書かれたモジュールは論理合成することができない。 実際に LSI を設計するときは LSI ベンダの提供するメモリマクロへ 置き換えることを想定する。
リスト1 のメモリは読み書き1ポートずつを持っている. このようなメモリを 1 read/1 write メモリと表現することがある.

※ただし,今回の instruction fetch では書き込みポートは使用しない.

    リスト1. 1 read/1 write 同期メモリ
    (a) メモリモジュール
    module DP_mem32x64k(clk,
                        A,
                        W,
                        D,
                        Q
                        );
       parameter WORD = 32;
       parameter ADDR = 16;
       parameter LEN = 65535;
    
       input              clk;
       input [ADDR -1: 0] A;
       input              W;
       input [WORD -1: 0] D;
       output [WORD -1: 0] Q;
    
       reg [WORD -1: 0]    mem_bank [0: LEN];
    
       reg [WORD -1: 0]    o_reg;
    
       assign              Q = o_reg;
    
       // read memory
       // write memory
       always @(posedge clk) // or negedge rst)
         begin
            if (W == 1'b1)
              mem_bank[A] <= D;
            else
              o_reg <= mem_bank[A];
         end
    
       initial begin
          $readmemh("../mem/mem.dat", mem_bank);
       end
    endmodule // DP_mem32x64k

    (b) mem.dat 記述例
    @0000_0000 0000_0000
    @0000_0001 0000_0001
    @0000_0002 0000_0002
    @0000_0003 0000_0003
    @0000_0004 0000_0004
    @0000_0005 0000_0005
    @0000_0006 0000_0006
    @0000_0007 0000_0007
    @0000_0008 0000_0008
    @0000_0009 0000_0009
    @0000_000a 0000_000a
    @0000_000b 0000_000b

6 課題

6.1 instruction fetch モジュールの作成

2節の考察を参考に instruction fetch モジュールを作成し, メモリモジュールと接続する.instruction fetch モジュール 内からメモリを呼び出すのではなく, 上位のモジュールから instruction fetch モジュールと メモリモジュールを呼び出す形にする. ストールと分岐にも対応する. 前回作成したパイプラインモジュールをひな形にすると 作成しやすい.

6.2 instructio fetch モジュールの RTL シミュレーション

作成したモジュールを検証する. まず,リスト1 の(b) のようなメモリファイルを作成し, リセット後にこれらが順に読み込まれて後段へ出力される様を観測する. 次にストールを起こし,正しく停止再開ができることを確認する. 最後に分岐が起こった場合を考慮し, 正しい分岐先アドレスの値が読み込まれているか, 不正な命令が出力されていないかを確認する. 同期メモリを用いているので,メモリの読み出しに 1 サイクルよけいに かかることに留意して検証を勧める.