きっかけ
私が社会人になって一年経つ頃,お金もたまってきたので,ずっと憧れていた自作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の仕様上,一部分だけでテストはできないっぽく「テストで使用しているモジュールがトップモジュールじゃないぞ」的なエラーが出ます.
まずヘッダファイルはこちら.
`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ファイル.
読み出しと書き込みは同時に行えます.
バースト転送にも対応しています.
`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しかないんですけど.
`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自作するしかなさそうなので作ることにします.