自作PCを作ろう1 ～メモリを作る～

# きっかけ
私が社会人になって一年経つ頃，お金もたまってきたので，ずっと憧れていた自作PCにチャレンジしたことがあります．
自分でパーツを選んで組み立てるのは楽しかったですが，完成してから思いました．
「これ，『自作PC』って言ってるけど，組み立てただけだな…？」
ということで本シリーズでは本当の意味での「自作PC」にチャレンジします．
全くの思い付きから始めたことなので全体的な設計は全くしていないですし，どこまで作るのか？ 最終的なゴールも全く決まっていません．
欲を言えばコンソールとかまでやりたいですが，そんな知識はないのでLチカくらいで満足して終わるかもしれません．

# どう作るのか
「自作PC」といっても，0からの自作は無理です．半導体を生産したり加工したりする技術も知識もないですし，ALUあたりは設計が面倒そうだからやりたくないですし． 
ということでここでは，PYNQ Z2の力をお借りしてFPGA上にPCを作ることにします．

# メモリを作ろう
なにをするにもまずは簡単なところからチャレンジするべきです．というわけでCPUやGPU(やるのか？)と比べて比較的簡単なメモリから作り始めることにしました．
~~あるやん．PYNQ Z2にDDR3乗ってるやん．~~
いえ，これは「自作PC」なので，メモリもちゃんと自作しましょう．
こんなことしてるとISAとかOSとかまで自作する羽目になりそうな気がしますが，メモリはまだ余裕あるので頑張りましょう．
ちなみにメモリメモリと言ってますがRAMのことです．メインメモリです．FPGAはその特性上，電源を切ると情報が失われてしまうので，ROMに関しては最初から諦めています．
~~情報を保存できないマシンをPCと呼んでいいのか？~~

## 概要
メモリなので，本当は「この領域は使用中」とか「解放済み」とかやりたかったですが，その辺はCPUがやるんでしょうか？
よく分からないのでひとまずここでは，メモリ側では「保存領域を提供する」以上の仕事はしないことにします．
あとでCPUを作るときに変更するかもしれませんが，いったんこの仕様とします．

この仕様も後で変更するかもしれませんが，いったんメモリの容量は以下とします．

|用語|仕様|
|:-|:-|
|データバス|32ビット|
|アドレスバス|28ビット|
|容量|8ビットのレジスタ28本，約270MB|

一度に移動できるデータ量は32ビット．
アドレスバスで指定したアドレスからスタートした4バイト分を一気に取得することにします．
書き込むときも同様に，指定したアドレスからスタートして4バイト書き込みます．
が，常に4バイト読み込む・書き込むのは使い勝手が悪いので，4ビットのマスクを導入し，どのバイトを読み込む・書き込むのかを指定することにします．

```
31      23          15          7           0
|address|address + 1|address + 2|address + 3|
```

## ソース
本当に思いつきで始めたことなのでテストとかはしていないです．
というかVivadoの仕様上，一部分だけでテストはできないっぽく「テストで使用しているモジュールがトップモジュールじゃないぞ」的なエラーが出ます．

まずヘッダファイルはこちら．

```verilog
`ifndef RAM_SVH
`define RAM_SVH

package ram_p;
    // 列挙体
    typedef enum logic [1:0] {   // メモリ読み出し・書き込み成否
        NONE,          // 初期状態
        SUCCESS        // 成功
    } code_t;
    typedef enum logic [1:0] {   // メモリ読み出し・書き込み状態
        IDLE,          // 待機状態
        EXECUTE,       // データ読み出し・書き込みの実行
        RESPONSE       // データ読み出し・書き込みのレスポンスを返す
    } state_t;

    // 変数型
    typedef logic [27:0] address_bus_t;      // アドレスバス幅
    typedef logic [31:0] data_bus_t;         // データバス幅
    typedef logic [ 3:0] mask_t;             // 読み込み・書き込みマスク
    typedef logic [27:0][ 7:0] memory_t;     // メモリのデータ全体
endpackage

// インターフェース定義
interface ram_read_if;     // メモリ読み込みインターフェース
    ram_p::address_bus_t address;
    ram_p::data_bus_t    data;
    ram_p::mask_t        mask;
    logic                valid;
    logic                ready;
    logic                last;
    ram_p::code_t        code;

    modport slave(
        input  address,
        output data,
        input  mask,
        input  valid,
        output ready,
        output last,
        output code
    );

    modport master(
        output address,
        input  data,
        output mask,
        output valid,
        input  ready,
        input  last,
        input  code
    );
endinterface
interface ram_write_if;     // メモリ書き込みインターフェース
    ram_p::address_bus_t address;
    ram_p::data_bus_t    data;
    ram_p::mask_t        mask;
    logic                valid;
    logic                ready;
    logic                last;
    ram_p::code_t        code;

    modport slave(
        input  address,
        input  data,
        input  mask,
        input  valid,
        output ready,
        input last,
        output code
    );

    modport master(
        output address,
        output data,
        output mask,
        output valid,
        input  ready,
        output last,
        input  code
    );
endinterface

`endif
```

svファイル．
読み出しと書き込みは同時に行えます．
バースト転送にも対応しています．

```verilog
`include "ram.svh"

module ram_sv import ram_p::*; (
    input  logic clk,
    input  logic resetn,

    // メモリデータ読み出し
    ram_read_if.slave ram_read,

    // メモリデータ書き込み
    ram_write_if.slave ram_write
    );

    // メモリに保存されるデータ
    memroy_t memory_data = 0;

    // メモリ読み出し・書き込み状態
    state_t ram_read_state = IDLE;
    state_t ram_write_state = IDLE;

    always_ff @(posedge clk) begin
        // リセット
        if (!resetn) begin
            // IO
            ram_read.data <= 32'b0;
            ram_read.ready <= 1'b0;
            ram_read.code <= ram_p::NONE;
            ram_write.ready <= 1'b0;
            ram_write.code <= ram_p::NONE;

            // 内部変数
            memory_data <= 0;
            ram_read_state <= IDLE;
            ram_write_state <= IDLE;
        end
        // メモリ実行
        else begin
            // メモリデータ読み出し
            case (ram_read_state)
                // 待機
                IDLE: begin
                    ram_read.code <= NONE;
                    // 読み出し命令を検知
                    if (ram_read.valid) begin
                        ram_read_state <= EXECUTE;
                    end
                end

                // メモリ読み込み実行
                EXECUTE: begin
                    if (ram_read.valid) begin
                        ram_read.ready <= 1'b1;
                        // マスクに応じて読み込み
                        foreach (ram_read.mask[i]) begin
                            if (ram_read.mask[i])
                                // 32ビットのうち，8ビット分を出力
                                ram_read.data[31 - i*8:24 - i*8] <= memory_data[ram_read.address + i];
                            else
                                ram_read.data[31 - i*8:24 - i*8] <= 8'h00;
                        end
                        // 読み込みラスト？
                        if (ram_read.last) begin
                            ram_read_state <= RESPONSE;
                        end
                    end else begin
                        ram_read.ready <= 1'b0;
                    end
                end

                // メモリ読み込みの実行結果を返す
                RESPONSE: begin
                    ram_read.ready <= 1'b0;
                    ram_read.code <= SUCCESS;
                    ram_read_state <= IDLE;
                end
            endcase

            // メモリデータ書き込み
            case (ram_write_state)
                // 待機
                IDLE: begin
                    ram_write.code <= NONE;
                    // 書き込み命令を検知
                    if (ram_write.valid) begin
                        ram_write_state <= EXECUTE;
                    end
                end

                // メモリ書き込み実行
                EXECUTE: begin
                    if (ram_write.valid) begin
                        ram_write.ready <= 1'b0;
                        // マスクに応じて書き込み
                        foreach (ram_write.mask[i]) begin
                            if (ram_write.mask[i])
                                // 32ビットのうち，8ビット分を入力
                                memory_data[ram_write.address + i] <= ram_write.data[31 - i*8:24 - i*8];
                            else
                                memory_data[ram_write.address + i] <= 8'h00;
                        end
                        // 書き込みラスト？
                        if (ram_write.last) begin
                            ram_write_state <= RESPONSE;
                        end
                    end else begin
                        ram_write.ready <= 1'b0;
                    end
                end

                // メモリ書き込みの実行結果を返す
                RESPONSE: begin
                    ram_write.ready <= 1'b0;
                    ram_write.code <= SUCCESS;
                    ram_write_state <= IDLE;
                end
            endcase
        end
    end

endmodule
```

マザーボードも作りました，まだRAMしかないんですけど．

```verilog
`include "ram.svh"

module mother_board_sv(
    input  logic clk,
    input  logic resetn
    );

    // メモリ読み込み・書き込みインターフェース
    ram_read_if  ram_read();
    ram_write_if ram_write();

    ram_sv ram_sv_0 (
        .clk(clk), .resetn(resetn),
        // メモリデータ読み込み
        .ram_read(ram_read),
        // メモリデータ書き込み
        .ram_write(ram_write)
    );

endmodule
```

# 次回の目標
こうして見るとやっぱりISA自作するしかなさそうなので作ることにします．