1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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レジスタに格納される。

即値命令

10001001はそれぞれIMMNIMMとなっており、IRegに4bitの即値を格納することができる。
ビットフィールドは

7~4bit 3~0bit
オペコード(OPC) 即値(IMM)

となっている。
IMMは正の値を、NIMMは負の値を格納することができる。

ジャンプ命令

10101011はそれぞれJALJRLであり、どちらもRSの中身へジャンプした後RDに元のPCの値を書き込む命令である。
ビットフィールドは

7~4bit 3~2bit 1~0bit
オペコード(OPC) 読み込みレジスタアドレス(RS) 書き込みレジスタアドレス(RD)

となっている。
JALは絶対ジャンプなのでPC<=RSであり、JRLは相対ジャンプなのでPC<=RS+PCである。

分岐命令

11001101はそれぞれBEQBLTとなっており、RSの値をTRegの値と比較し、条件に合っていた場合にRSの値+即値のアドレスにジャンプする命令である。
ビットフィールドは

7~4bit 3~2bit 1~0bit
オペコード(OPC) 読み込みレジスタアドレス(RS) 即値(IMM)

となっており、即値命令と違い即値が2bitである。
BEQRS=TRegの場合にジャンプし、BLTRS<TRegの場合にジャンプする。

メモリ命令

1110LOAD1111STOREである。
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
VitaCPU.sv
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
ALU.sv
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
Decoder.sv
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
RegisterFile.sv
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
RAM.sv
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(カウントアッププログラム書き込み済み)
ROM.sv
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
テストベンチ
VitaCPU_tb.sv
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用ラッパー
Star_Jumper.sv
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という名前には元ネタがありますので是非元ネタの方もプレイしてください。
↓元ネタのゲーム

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?