1.初めに
当記事はコンピュータアーキテクチャ1年目の若造が書いた記事です。温かい目で見守ってください。
当記事は鳥羽商船高等専門学校 Advent Calendar 2025 15日目の記事です。
2.私は誰?
- 鳥羽商船高等専門学校23s、高専ロボコン3年目(ロボコン要素は一切出てきません)
- コンピュータアーキテクチャガチ初心者(一通りのロジック回路がわかる程度)
3.ISA(命令セットアーキテクチャ)「VITA」について
CPUを自作するにあたって、既存のISAを利用したCPUを作成するのもいいですが、せっかくなので自分でISAを定義することにしました。(こんなことを思ってしまったせいで苦しむ事になる)
ISAの名前はVITAです。歯が唇を優しく噛む『ヴィ』に、舌が優しく歯に当たる『タ』でヴィタと読みます。
V,I,T,A四つのレジスタがあることに由来しています。
VITAは8bitのデータを扱うことができ、命令長も8bitです。
レジスタ内訳
| 名称 | 説明 |
|---|---|
| Vreg(Vacancy Register) | 常に値が0のレジスタ |
| Ireg(Immediate Register) | レジスタ1(即値命令の出力先) |
| Treg(Threshold Register) | レジスタ2(分岐命令の閾値) |
| Areg(Answer Register) | レジスタ3(演算命令の出力先) |
命令一覧
| オペコード | 名称 |
|---|---|
| 0000 | ADD |
| 0001 | SUB |
| 0010 | MUL |
| 0011 | DIV |
| 0100 | AND |
| 0101 | OR |
| 0110 | SLL |
| 0111 | SRL |
| 1000 | IMM |
| 1001 | NIMM |
| 1010 | JAL |
| 1011 | JRL |
| 1100 | BEQ |
| 1101 | BLT |
| 1110 | LOAD |
| 1111 | STORE |
4.命令の解説
演算命令
0000~0111までのオペコードは演算命令となっており、ビットフィールドは
| 7~4bit | 3~2bit | 1~0bit |
|---|---|---|
| オペコード(OPC) | 読み込みレジスタ1アドレス(rs1) | 読み込みレジスタ2アドレス(rs2) |
となっている。
演算結果は自動的にAレジスタに格納される。
即値命令
1000、1001はそれぞれIMM、NIMMとなっており、IRegに4bitの即値を格納することができる。
ビットフィールドは
| 7~4bit | 3~0bit |
|---|---|
| オペコード(OPC) | 即値(IMM) |
となっている。
IMMは正の値を、NIMMは負の値を格納することができる。
ジャンプ命令
1010、1011はそれぞれJAL、JRLであり、どちらもRSの中身へジャンプした後RDに元のPCの値を書き込む命令である。
ビットフィールドは
| 7~4bit | 3~2bit | 1~0bit |
|---|---|---|
| オペコード(OPC) | 読み込みレジスタアドレス(RS) | 書き込みレジスタアドレス(RD) |
となっている。
JALは絶対ジャンプなのでPC<=RSであり、JRLは相対ジャンプなのでPC<=RS+PCである。
分岐命令
1100、1101はそれぞれBEQ、BLTとなっており、RSの値をTRegの値と比較し、条件に合っていた場合にRSの値+即値のアドレスにジャンプする命令である。
ビットフィールドは
| 7~4bit | 3~2bit | 1~0bit |
|---|---|---|
| オペコード(OPC) | 読み込みレジスタアドレス(RS) | 即値(IMM) |
となっており、即値命令と違い即値が2bitである。
BEQはRS=TRegの場合にジャンプし、BLTはRS<TRegの場合にジャンプする。
メモリ命令
1110はLOAD、1111はSTOREである。
LOAD命令のビットフィールドは
| 7~4bit | 3~2bit | 1~0bit |
|---|---|---|
| オペコード(OPC) | 読み込みレジスタアドレス(RS) | 書き込みレジスタアドレス(RD) |
であり、RAMのRS番目にあるデータをRDに格納する命令である。
STORE命令のビットフィールドは
| 7~4bit | 3~2bit | 1~0bit |
|---|---|---|
| オペコード(OPC) | 読み込みレジスタ1アドレス(RS1) | 読み込みレジスタ2アドレス(RS2) |
であり、RAMのRS1番目にRS2のデータを格納する命令になっている。
人力でアセンブリする(アセンブラ未作成)
imm 0001 → 10000001
add a i → 00000111
store v a → 11110011
imm 1110 → 10001110
load i t → 11100110
imm 1111 → 10001111
store i a → 11110111
beq v 00 → 11000000
5.作成したVerilogファイル
このISAが必要最低限動くCPUを作成したので以下にVerilogファイルを張り付けておきます。ここおかしいよ!とかあったら教えていただけると幸いです。
CPU
module VitaCPU (
input wire i_clk,
input wire i_rst,
input wire i_button,
output reg [5:0] o_led
);
logic [7:0] r_pc;//プログラムカウンタ値保持
wire [7:0] instr;//命令
wire [1:0] rs1_addr;//rs1アドレス
wire [1:0] rs2_addr;//rs2アドレス
wire[1:0] rd_addr;//rdアドレス
wire rdF;//rd書き込みフラグ
wire memF;//RAM書き込みフラグ
wire[3:0] ALU_con;//ALU制御信号
wire [7:0] rs1_data;//rs1データ
wire[7:0] rs2_data;//rs2データ
wire[7:0] ALU_data;//ALUデータ
wire[7:0] RAM_data;//RAMデータ
wire[7:0] rd_data;
wire[3:0] opc;
wire[7:0] data_a;
wire[7:0] data_b;
wire[7:0] imm;
always @(posedge i_clk) begin
if(i_rst) begin
r_pc <= 8'h00;
end else if(opc == 4'hA)begin
r_pc <= ALU_data;
end else if(opc == 4'hB)begin
r_pc <= r_pc + ALU_data;
end else if((opc == 4'hC) && (rs2_data == rs1_data))begin
r_pc <= rs1_data + imm;
end else if((opc == 4'hD) && (rs2_data > rs1_data))begin
r_pc <= rs1_data + imm;
end else begin
r_pc <= r_pc +8'h01;
end
end
function [7:0] sel_rd_data;
input [3:0] i_opc;
input [7:0] i_RAM_data;
input [7:0] i_ALU_data;
input [7:0] i_pc;
input button;
begin
case(i_opc)
4'hE : begin
if(i_ALU_data == 8'h0E) begin
sel_rd_data = {7'b0000000,button};
end else begin
sel_rd_data = i_RAM_data;
end
end
4'hA : sel_rd_data = i_pc + 8'h01;
4'hB : sel_rd_data = i_pc + 8'h01;
default : sel_rd_data = i_ALU_data;
endcase
end
endfunction
assign rd_data = sel_rd_data(opc , RAM_data , ALU_data , r_pc , i_button);
ROM VitaROM(
.i_addr(r_pc),
.o_instr(instr)
);
Decoder VitaDecoder(
.i_instr (instr),
.rs1_addr(rs1_addr),
.rs2_addr(rs2_addr),
.rdF(rdF),
.memF(memF),
.ALU_con(ALU_con),
.rd_addr(rd_addr),
.o_opc(opc),
.imm(imm)
);
RegisterFile VitaRegFile(
.i_clk(i_clk),
.rs1_addr(rs1_addr),
.rs1_data(rs1_data),
.rs2_addr(rs2_addr),
.rs2_data(rs2_data),
.rd_addr(rd_addr),
.rd_data(rd_data),
.rdF(rdF)
);
assign data_b = (opc[3:0] == 4'hF ) ? 4'h0 : rs2_data;
assign data_a = (opc[3:0] == 4'h8 | opc[3:0] == 4'h9) ? imm : rs1_data;
ALU VitaALU(
.ina(data_a),
.inb(data_b),
.ctrl(ALU_con),
.out(ALU_data)
);
RAM VitaRAM(
.i_clk(i_clk),
.i_addr(ALU_data),
.iF(memF),
.i_data(rs2_data),
.o_data(RAM_data)
);
always @(posedge i_clk) begin
if(i_rst)begin
o_led <= 6'b000000;
end else if(memF && ALU_data == 8'h0F) begin
o_led <= rs2_data;
end else begin
o_led <= o_led;
end
end
endmodule
ALU
module ALU(
input wire [7:0] ina,//入力A
input wire [7:0] inb,//入力B
input wire [3:0] ctrl,//制御信号
output wire [7:0] out//出力
);
function [7:0] ALU;
input [7:0] ina;
input [7:0] inb;
input [3:0] ctrl;
begin
case(ctrl)
4'h0 : ALU = ina + inb ;
4'h1 : ALU = ina - inb ;
4'h2 : ALU = ina * inb ;
4'h3 : ALU = ina / inb ;
4'h4 : ALU = ina | inb ;
4'h5 : ALU = ina & inb ;
4'h6 : ALU = ina << inb ;
4'h7 : ALU = ina >> inb ;
default : ALU = ina + inb;
endcase
end
endfunction
assign out = ALU(ina,inb,ctrl);
endmodule
Decoder
module Decoder (
input wire [7:0] i_instr,//命令
output wire [3:0] o_opc,//オペコード
output wire [1:0] rs1_addr,//rs1アドレス
output wire [1:0] rs2_addr,//rs2アドレス
output wire rdF,//rd書き込みフラグ
output wire [1:0] rd_addr,//rdアドレス
output wire memF,//RAM書き込みフラグ
output wire [3:0] ALU_con,//ALU制御信号
output wire [7:0] imm//即値入れ
);
assign o_opc = i_instr[7:4];
function [1:0] get_rs1_addr;//レジスタアドレス取得
input [7:0] i_instr;
begin
if(i_instr[7:4] == 4'hE)begin
get_rs1_addr = i_instr[3:2];
end else if(i_instr[7:4] == 4'hF)begin
get_rs1_addr = i_instr[3:2];
end else if(i_instr[7:4] <= 4'h7)begin
get_rs1_addr = i_instr[3:2];
end else if(i_instr[7:4] == 4'h8 | i_instr[7:4] == 4'h9)begin
get_rs1_addr = 2'b00;
end else if(i_instr[7:4] == 4'hA | i_instr[7:4] == 4'hB)begin
get_rs1_addr = i_instr[3:2];
end else if(i_instr[7:4] == 4'hC | i_instr[7:4] == 4'hD)begin
get_rs1_addr = i_instr[3:2];
end else begin
get_rs1_addr = 2'b00;
end
end
endfunction
assign rs1_addr = get_rs1_addr(i_instr);
function [1:0] get_rs2_addr;//レジスタアドレス取得
input [7:0] i_instr;
begin
if(i_instr[7:4] == 4'hE)begin
get_rs2_addr = 2'b00;
end else if(i_instr[7:4] == 4'hF)begin
get_rs2_addr = i_instr[1:0];
end else if(i_instr[7:4] <= 4'h7)begin
get_rs2_addr = i_instr[1:0];
end else if(i_instr[7:4] == 4'h8 | i_instr[7:4] == 4'h9)begin
get_rs2_addr = 2'b00;
end else if(i_instr[7:4] == 4'hA | i_instr[7:4] == 4'hB)begin
get_rs2_addr = 2'b00;
end else if(i_instr[7:4] == 4'hC | i_instr[7:4] == 4'hD)begin
get_rs2_addr = 2'b10;
end else begin
get_rs2_addr = 2'b00;
end
end
endfunction
assign rs2_addr = get_rs2_addr(i_instr);
function [1:0] get_rd_addr;//レジスタアドレス取得
input [7:0] i_instr;
begin
if(i_instr[7:4] == 4'hE)begin
get_rd_addr = i_instr[1:0];
end else if(i_instr[7:4] == 4'hF)begin
get_rd_addr = 2'b00;
end else if(i_instr[7:4] <= 4'h7)begin
get_rd_addr = 2'b11;
end else if(i_instr[7:4] == 4'h8 | i_instr[7:4] == 4'h9)begin
get_rd_addr = 2'b01;
end else if(i_instr[7:4] == 4'hA | i_instr[7:4] == 4'hB)begin
get_rd_addr = i_instr[1:0];
end else if(i_instr[7:4] == 4'hC | i_instr[7:4] == 4'hD)begin
get_rd_addr = 2'b00;
end else begin
get_rd_addr = 2'b00;
end
end
endfunction
assign rd_addr = get_rd_addr(i_instr);
function get_rdF;//rdフラグ生成
input [7:0] i_instr;
begin
if(i_instr[7:4] == 4'hE)begin
get_rdF = 1;
end else if(i_instr[7:4] == 4'hF)begin
get_rdF = 0;
end else if(i_instr[7:4] <= 4'h7)begin
get_rdF = 1;
end else if(i_instr[7:4] == 4'h8 | i_instr[7:4] == 4'h9)begin
get_rdF = 1;
end else if(i_instr[7:4] == 4'hA | i_instr[7:4] == 4'hB)begin
get_rdF = 1;
end else begin
get_rdF = 0;
end
end
endfunction
assign rdF = get_rdF(i_instr);
function [7:0] get_imm;
input [7:0] i_instr;
begin
if(i_instr[7:4] == 4'h8 | i_instr[7:4] == 4'h9)begin
get_imm = {4'h0,i_instr[3:0]};
end else if(i_instr[7:4] == 4'hC | i_instr[7:4] == 4'hD)begin
get_imm = {6'h0,i_instr[1:0]};
end else begin
get_imm = 0;
end
end
endfunction
assign imm = get_imm(i_instr);
function get_memF;//RAMフラグ生成
input [7:0] i_instr;
begin
if(i_instr[7:4] == 4'hE)begin
get_memF = 0;
end else if(i_instr[7:4] == 4'hF) begin
get_memF = 1;
end else if(i_instr[7:4] <= 4'h7)begin
get_memF = 0;
end else if(i_instr[7:4] == 4'h8 | i_instr[7:4] == 4'h9)begin
get_memF = 0;
end else begin
get_memF = 0;
end
end
endfunction
assign memF = get_memF(i_instr);
function [3:0] get_ALU_con;//ALU制御信号生成
input [7:0] i_instr;
begin
if(i_instr[7:4] == 4'hE)begin
get_ALU_con = 4'h0;
end else if(i_instr[7:4] == 4'hF)begin
get_ALU_con = 4'h0;
end else if(i_instr[7:4] == 4'h7)begin
get_ALU_con = i_instr[7:4];
end else if(i_instr[7:4] == 4'h8)begin
get_ALU_con = 4'h0;
end else if(i_instr[7:4] == 4'h9)begin
get_ALU_con = 4'h1;
end else begin
get_ALU_con = 4'h0;
end
end
endfunction
assign ALU_con = get_ALU_con(i_instr);
endmodule
RegFile
module RegisterFile(
input wire i_clk,//クロック
input wire[1:0] rs1_addr,//rs1アドレス
output wire[7:0] rs1_data,//rs1データ
input wire[1:0] rs2_addr,//rs2アドレス
output wire[7:0] rs2_data,//rs2データ
input wire[1:0] rd_addr,//rdアドレス
input wire[7:0] rd_data,//rdデータ
input wire rdF//rd書き込みフラグ
);
logic[7:0] Vreg =0;//Vレジスタ(常に値が0のレジスタ)
logic[7:0] Ireg = 0;//Iレジスタ(即値命令の出力先)
logic[7:0] Treg = 0;//Tレジスタ(分岐命令の閾値)
logic[7:0] Areg = 0;//Aレジスタ(演算命令の出力先)
function [7:0] get_rs1_data;
input [1:0] rs1_addr;
begin
if(rs1_addr == 00)begin
get_rs1_data = 8'h00;
end else if(rs1_addr == 2'b01)begin
get_rs1_data = Ireg;
end else if(rs1_addr == 2'b10)begin
get_rs1_data = Treg;
end else if(rs1_addr == 2'b11)begin
get_rs1_data = Areg;
end
end
endfunction//rs1データ取り出し
function [7:0] get_rs2_data;
input [1:0] rs2_addr;
begin
if(rs2_addr == 00)begin
get_rs2_data = 8'h00;
end else if(rs2_addr == 2'b01)begin
get_rs2_data = Ireg;
end else if(rs2_addr == 2'b10)begin
get_rs2_data = Treg;
end else if(rs2_addr == 2'b11)begin
get_rs2_data = Areg;
end
end
endfunction//rs2データ取り出し
assign rs1_data = get_rs1_data(rs1_addr);
assign rs2_data = get_rs2_data(rs2_addr);
always @(posedge i_clk) begin//rdデータ書き込み
if(rdF) begin//i_clk立ち上がりタイミングで書き込みフラグを確認
if(rd_addr == 00)begin
Vreg <= 8'h00;//書き込み
end else if(rd_addr == 2'b01)begin
Ireg <= rd_data;
end else if(rd_addr == 2'b10)begin
Treg <= rd_data;
end else if(rd_addr == 2'b11)begin
Areg <= rd_data;
end
end else begin
Vreg <= Vreg;
Ireg <= Ireg;
Treg <= Treg;
Areg <= Areg;//フラグが立っていないので書き込まない
end
end
endmodule
RAM
module RAM (
input wire i_clk,//クロック
input wire [7:0] i_addr,//アドレス
input wire iF,//書き込みフラグ
input wire[7:0] i_data,//入力データ
output wire[7:0] o_data//出力データ
);
logic [7:0] mem[255:0];//8bitレジスタ256個生成
assign o_data = mem[i_addr];
always @(posedge i_clk) begin
if(iF) begin
mem[i_addr] <= i_data;
end
else begin
mem[i_addr] <= mem[i_addr];
end
end
endmodule
ROM(カウントアッププログラム書き込み済み)
module ROM(
input wire [7:0] i_addr,//アドレス
output wire [7:0] o_instr//中身
);
wire [7:0] mem[9:0];//8bit命令メモリ作成
assign o_instr = mem[i_addr[7:0]];//命令吐き出し
assign mem[0] =8'h81 ;//ここに機械語
assign mem[1] =8'h07 ;//ここに機械語
assign mem[2] =8'hF3 ;//ここに機械語
assign mem[3] =8'h8E ;//ここに機械語
assign mem[4] =8'hE6 ;//ここに機械語
assign mem[5] =8'h8F ;//ここに機械語
assign mem[6] =8'hF7 ;//ここに機械語
assign mem[7] =8'hC0 ;//ここに機械語
assign mem[8] =8'hB0 ;//ここに機械語
assign mem[9] =8'h00 ;//ここに機械語
endmodule
テストベンチ
module VitaCPU_tb;
logic i_clk = 1'b0;
logic i_rst = 1'b0;
logic i_button = 1'b0;
wire [5:0] o_led;
always #1 begin
i_clk <= ~i_clk;
end
initial begin
$dumpfile("wave.vcd");
$dumpvars(0,VitaCPU_tb);
end
VitaCPU CPU(
.i_clk(i_clk),
.i_rst(i_rst),
.o_led(o_led),
.i_button(i_button)
);
initial begin
i_rst = 1'b1;
#2
i_rst = 1'b0;
#100
i_button = 1'b1;
#100
i_button = 1'b0;
#20
$finish;
end
endmodule
Tang nano 9k用ラッパー
module Star_Jumper(
input wire i_clk,
input wire i_rst,
input wire i_button,
output wire [5:0] o_led
);
reg clk = 1'b0;
wire rst;
wire button;
wire [5:0] led;
reg [31:0] counter = 32'h00000001;
assign rst = ~i_rst;
assign button = ~i_button;
assign o_led = ~led;
always @ (posedge i_clk )begin
if(counter == 32'd1350000)begin
counter <= 32'h00000000;
clk <= ~clk;
end else begin
counter <= counter +32'h00000001;
clk <= clk;
end
end
VitaCPU CPU(
.i_clk(clk),
.i_rst(rst),
.i_button(button),
.o_led(led)
);
endmodule
6.あとがき
稚拙な文章ですがここまでお読みいただきありがとうございます。これCPU設計じゃなくてISA設計じゃね...?
VITAという名前には元ネタがありますので是非元ネタの方もプレイしてください。
↓元ネタのゲーム