概要#
quartus ソフトウェアを使用し、verilog 言語で構造化された動作記述方法を採用して、単周期 CPU モデルの設計とシミュレーションを行い、モデルの正常な動作を実現しました。
キーワード: 全体、システム
第 1 章 原理とモデル設計#
1.1 実験に関する原理#
フォン・ノイマン型コンピュータの動作原理#
- コンピュータは、コントローラ、演算器、メモリ、入力デバイス、出力デバイスの 5 つの部分で構成されています。
- プログラムとデータは、バイナリコード形式で区別なくメモリに格納され、格納位置はアドレスによって決定されます。
- コントローラは、メモリに格納された命令列(プログラム)に基づいて動作し、プログラムカウンタによって命令の実行を制御します。
-
単周期 CPU:1 つの命令の実行が 1 つのクロックサイクル内で完了し、その後次の命令の実行が始まることを指します。つまり、1 つの命令は 1 つのクロックサイクルで完了します。
-
命令システム:コンピュータのすべての命令の集合を指します。
-
命令周期:命令の取得、分析からその命令の実行が完了するまでに必要な時間であり、異なる命令では命令周期の長さが異なる場合があります。
-
単周期 CPU が命令を処理する手順:
命令取得 -> 命令デコード -> 命令実行 -> メモリアクセス -> 結果書き戻し
1.2 モデル設計#
構造ブロック図#
制御回路図#
対応モジュールと機能#
InstructionMemory: 命令メモリで、入力されたアドレスに基づいてメモリから対応する命令を取得します。
CU: 制御ユニットで、命令を分析し、何を操作すべきかを決定し、決定されたタイミングに従って対応する部品に制御信号を送ります。
Register: データレジスタ(図 1.2.4)で、alu の演算データとメモリから取得したデータを一時的に保存し、キャッシュの役割を果たします。単周期内では同時に読み書きできないため、1 段のキャッシュ設計が追加されています。
ALU: 算術論理ユニット(図 1.2.5)で、入力されたオペコードとデータに基づいて、対応する算術演算と論理演算を行います。
memRam: データメモリ(図 1.2.6)で、データを保存します。
PC: プログラムカウンタで、値を取得する操作を行い、最初の命令の実行を完了し、その後 PC に基づいて次の命令のアドレスを取得します。PC 内のアドレスは自動的に 1 加算されるか、転送命令によって次の命令のアドレスが与えられます。
第 2 章 原理とモデル設計#
2.1 オペコード形式#
今回の実験では固定長エンコーディングを採用し、機械命令は 16 ビットエンコーディングであるため、実際のニーズに基づいて以下のオペコードを設計しました:
`define ADD 4'b0000 //算術加
`define INC 4'b0001 //算術加1
`define NOT 4'b0010 //論理非
`define AND 4'b0011 //論理与
`define OR 4'b0100 //論理或
`define SLF 4'b0101 //データ左移
`define SRF 4'b0110 //データ右位
`define JMP 4'b0111 //無条件転移
`define STO 4'b1000 //メモリ書き込み、データ保存
`define LAD 4'b1001 //メモリ読み込み、データ取得
`define MOV 4'b1010 //データ転送
`define HAT 4'b1111 //停止
2.2 命令と形式#
=> 算術演算命令#
(1) ADD rd, rs, rt
0000 | rd (4 ビット) | rs (4 ビット) | rt (4 ビット) |
---|---|---|---|
機能:rd <- rs + rt (算術加)
(2) INC rt, rs
0001 | rt (4 ビット) | rs (4 ビット) | 0000 (未使用) |
---|---|---|---|
機能:rt <- rs (算術加 1)
=> 論理演算命令#
(3) NOT rt, rs
0010 | rt (4 ビット) | rs (4 ビット) | 0000 (未使用) |
---|---|---|---|
機能:rt <- !rs (論理非演算)
(4) AND rd, rs, rt
0011 | rd (4 ビット) | rs (4 ビット) | rt (4 ビット) |
---|---|---|---|
機能:rd <- rs & rt (論理与演算)
(5) OR rd, rs, rt
0100 | rd (4 ビット) | rs (4 ビット) | rt (4 ビット) |
---|---|---|---|
機能:rd <- rs | rt (論理或演算)
=> シフト命令#
(6) SLF rd, rs, rt
0101 | rd (4 ビット) | rs (4 ビット) | rt (4 ビット) |
---|---|---|---|
機能:rd <- rs<<rt (左移)
(7) SRF rd, rt, rs
0110 | rd (4 ビット) | rs (4 ビット) | rt (4 ビット) |
---|---|---|---|
機能:rd <- rs>>rt (右移)
=> メモリの読み書き命令#
(8) STO rt, rs
1000 | rt (3 ビット) | 0 (未使用) | rs (4 ビット) | 0000 (未使用) |
---|---|---|---|---|
機能:レジスタからデータをメモリに書き込む
(9) LAD rt, rs
1001 | rt (4 ビット) | rs (3 ビット) | 00000 (未使用) |
---|---|---|---|
機能:メモリからデータをレジスタに読み込む
=> 無条件転移命令#
(10) JMP
0111 | 0000 (未使用) | 転移命令アドレス (8 ビット) |
---|---|---|
機能:指定された命令アドレスにジャンプする
=> 停止命令#
(11) HLT
1111 | 000000000000 (未使用) |
---|---|
機能:停止し、PC の値は変わらない
第 3 章 モデルの実装とテスト#
3.1 Verilog プログラム設計#
headfile.v#
`ifndef HEADFILE_H_
`define ADD 4'b0000 //算術加
`define INC 4'b0001 //算術加1
`define NOT 4'b0010 //論理非
`define AND 4'b0011 //論理与
`define OR 4'b0100 //論理或
`define SLF 4'b0101 //データ左移
`define SRF 4'b0110 //データ右位
`define JMP 4'b0111 //無条件転移
`define STO 4'b1000 //メモリ書き込み、データ保存
`define LAD 4'b1001 //メモリ読み込み、データ取得
`define MOV 4'b1010 //データ転送
`define HAT 4'b1111 //停止
`define rg0 4'b0000 //レジスタ0
`define rg1 4'b0001 //レジスタ1
`define rg2 4'b0010 //レジスタ2
`endif
alu.v#
`timescale 1ns / 1ps
`include "headfile.v"
//ALU、論理演算と算術演算を行う
module alu(op, a, b, n, f);
input [3:0] op, n;
input [7:0] a, b;
output [7:0] f;
reg [7:0] f;
always@(*)
begin
case(op)
`ADD: f = a + b;
`INC: f = a + 1;
`NOT: f = ~a;
`AND: f = a&b;
`OR: f = a|b;
`SLF: f = a<<n;
`SRF: f = a>>n;
default: f = 8'b00000000;
endcase
end
endmodule
memRam.v#
`timescale 1ns / 1ps
`include "headfile.v"
//メモリ
module memRam(data,wren,address,inclock,outclock,q);
parameter wordsize = 8;
parameter memsize = 8;
parameter addr = 3; //3ビットアドレス線
input [wordsize-1:0] data;
input [addr-1:0] address;
input wren,inclock,outclock;
output [wordsize-1:0] q;
reg [wordsize-1:0] q;
reg [wordsize-1:0] ram [memsize-1:0];
integer i;
initial
begin//初期化
for(i=0; i<8; i=i+1)
ram[i]=8'b00000000;
ram[0]=8'b00000010; //0番目に2を書き込む
end
always@(posedge inclock) //inclockの立ち上がりでトリガ
begin
if(~wren)
ram[address] = data; //wrenが低電平のとき、dataを対応するアドレスに書き込む
end
always@(posedge outclock) //outclockの立ち上がりでトリガ
begin
if(wren)
q = ram[address]; //wrenが高電平のとき、対応するアドレスのデータを読み出す
end
endmodule
Register.v#
`timescale 1ns / 1ps
`include "headfile.v"
//レジスタ
module Register(clk,data,wren,inaddr,outaddr1,outaddr2,
regtoalu1,regtoalu2,regtomemaddr,regtomem,
memtoregwren,memtoregaddr,memtoregdata);
input [7:0] data;
input [3:0] inaddr,outaddr1,outaddr2,regtomemaddr,memtoregaddr;
input wren,clk,memtoregwren;
output [7:0] regtoalu1,regtoalu2,regtomem,memtoregdata;
reg [7:0] regmem [15:0];
reg lwren,lmemtoregwren;
reg [3:0] linaddr,lmemtoregaddr;
reg [7:0] ldata,lmemtoregdata;
integer i;
initial
begin//初期化
lwren = 1'b0;
lmemtoregwren = 1'b0;
for(i=0; i<16; i=i+1)
regmem[i]=8'b00000000;
end
always@(posedge clk) //キャッシュ
begin
lwren <= wren;
linaddr <= inaddr;
ldata <= data;
lmemtoregwren <= memtoregwren;
lmemtoregaddr <= memtoregaddr;
lmemtoregdata <= memtoregdata;
end
always@(*)
begin
if(lwren)
regmem[linaddr] <= ldata; //dataを対応するアドレスに書き込む
if(lmemtoregwren)
regmem[lmemtoregaddr] <= lmemtoregdata;
end
assign regtoalu1 = regmem[outaddr1]; //レジスタから値を取得
assign regtoalu2 = regmem[outaddr2];
assign regtomem = regmem[regtomemaddr];
endmodule
InstructionMemory.v#
`timescale 1ns / 1ps
`include "headfile.v"
//命令を保存する
module InstructionMemory(A, RD);
input [7:0] A;
output [15:0] RD;
reg [15:0] IM [29:0];
assign RD = IM[A];//アドレスに基づいて内容を即座に取得
//実行成功後、メモリの1から7の位置のデータは:2,3,5,2,3,253,250,126であるべき
initial begin
IM[0] = {`LAD, `rg0, 3'b000, 5'b00000}; //メモリの0番目からデータをレジスタrg0に読み込む、rg0 = 2
/*-----------------------------------------------------------------*/
IM[1] = {`INC, `rg1, `rg0, 4'b0000}; //レジスタrg0のデータに算術加1を行い、結果をrg1に移す、rg1 = rg0 + 1 = 2 + 1 = 3
IM[2] = {`STO, 3'b001, 1'b0, `rg1, 4'b0000}; //レジスタrg1のデータをメモリの1番目に保存、3
/*-----------------------------------------------------------------*/
IM[3] = {`ADD, `rg2, `rg1, `rg0}; //レジスタrg0とrg1の数を加算し、結果をrg2に保存、rg2=rg0+rg1=2+3=5
IM[4] = {`STO, 3'b010, 1'b0, `rg2, 4'b0000}; //レジスタrg2のデータをメモリの2番目に保存、5
/*-----------------------------------------------------------------*/
IM[5] = {`JMP, 4'b0000, 8'b00000111}; //7番目の命令にジャンプ
IM[6] = {`HAT, 12'b000000000000}; //ジャンプが成功しなかった場合、停止
/*-----------------------------------------------------------------*/
IM[7] = {`AND, `rg2, `rg1, `rg0}; //レジスタrg0とrg1の数を論理与演算し、結果をrg2に保存、rg2 = rg1 & rg0 = 00000011 & 00000010 = 00000010(2)
IM[8] = {`STO, 3'b011, 1'b0, `rg2, 4'b0000}; //レジスタrg2のデータをメモリの3番目に保存、2
/*-----------------------------------------------------------------*/
IM[9] = {`OR, `rg2, `rg1, `rg0}; //レジスタrg0とrg1の数を論理或演算し、結果をrg2に保存、rg2 = rg1 | rg0 = 00000011 | 00000010 = 00000011(3)
IM[10] = {`STO, 3'b100, 1'b0, `rg2, 4'b0000}; //レジスタrg2のデータをメモリの4番目に保存、3
/*-----------------------------------------------------------------*/
IM[11] = {`NOT, `rg2, `rg0, 4'b0000}; //レジスタrg0のデータに論理非演算を行い、結果をrg2に保存、rg2 = ~rg0 = ~00000010 = 11111101(253)
IM[12] = {`STO, 3'b101, 1'b0, `rg2, 4'b0000}; //レジスタrg2のデータをメモリの5番目に保存、253
/*-----------------------------------------------------------------*/
IM[13] = {`SLF, `rg0, `rg2, 4'b0001}; //rg2のデータを左に1ビットシフトし、結果をrg0に保存、rg0 = rg2<<1 = 11111101<<1 = 11111010(250)
IM[14] = {`STO, 3'b110, 1'b0, `rg0, 4'b0000}; //レジスタrg0のデータをメモリの6番目に保存、250
/*-----------------------------------------------------------------*/
IM[15] = {`SRF, `rg1, `rg2, 4'b0001}; //rg2のデータを右に1ビットシフトし、結果をrg1に保存、rg1 = rg2>>1 = 11111101>>1 = 01111110(126)
IM[16] = {`STO, 3'b111, 1'b0, `rg1, 4'b0000}; //レジスタrg2のデータをメモリの7番目に保存、126
/*-----------------------------------------------------------------*/
IM[17] = {`HAT, 12'b000000000000}; //停止
IM[18] = 16'b0000000000000000;
IM[19] = 16'b0000000000000000;
IM[20] = 16'b0000000000000000;
IM[21] = 16'b0000000000000000;
IM[22] = 16'b0000000000000000;
IM[23] = 16'b0000000000000000;
IM[24] = 16'b0000000000000000;
IM[25] = 16'b0000000000000000;
IM[26] = 16'b0000000000000000;
IM[27] = 16'b0000000000000000;
IM[28] = 16'b0000000000000000;
IM[29] = 16'b0000000000000000;
end
endmodule
CU.v#
`timescale 1ns / 1ps
`include "headfile.v"
//異なる命令に基づいてデータを分配する制御
module CU(
input [15:0] instr,
output enable,
output reg [3:0] regoutaddr1,
output reg [3:0] regoutaddr2,
output reg [3:0] reginaddr,
output reg [3:0] regtomemaddr,
output reg [3:0] memtoregaddr,
output reg [3:0] aluop,
output reg [3:0] alun,
output reg memwren,
output reg memtoregwren,
output reg [2:0] memaddr,
output reg [7:0] pcnextaddr,
output reg pcnext,
output reg pcflag,
output reg regwren);
wire [3:0] op;
assign op = instr[15:12];
initial
begin
regwren = 1'b0;
memtoregwren <= 1'b0;
memwren = 1'b1;
pcnext = 1'b0;
pcflag = 1'b0;
end
always@(*)
begin
if((op == `ADD)||(op == `AND)||(op == `OR))
begin
aluop <= instr[15:12];
regoutaddr1 <= instr[3:0];
regoutaddr2 <= instr[7:4];
regwren <= 1'b1;
reginaddr <= instr[11:8];
end
else if((op == `SLF)||(op == `SRF))
begin
aluop <= instr[15:12];
alun <= instr[3:0];
regoutaddr1 <= instr[7:4];
regwren <= 1'b1;
reginaddr <= instr[11:8];
end
else if((op == `INC)||(op == `NOT))
begin
aluop <= instr[15:12];
regoutaddr1 <= instr[7:4];
regwren <= 1'b1;
reginaddr <= instr[11:8];
end
else if((op == `STO))
begin
regtomemaddr <= instr[7:4];
memaddr <= instr[11:9];
memwren <= 1'b0;
end
else if((op == `LAD))
begin
memaddr <= instr[7:5];
memwren <= 1'b1;
memtoregaddr <= instr[11:8];
memtoregwren <= 1'b1;
end
else
begin
regwren <= 1'b0;
memtoregwren <= 1'b0;
//memwren <= 1'b1;
end
end
always@(*)
begin
if((op == `JMP))
begin
pcnextaddr <= instr[7:0];
pcnext <= 1'b1;
pcflag <= 1'b1;
end
else
pcnext <= 1'b0;
end
assign enable = ~(op == `HAT);
endmodule
CPU_top.v#
`timescale 1ns / 1ps
//トップレベルモジュール、各モジュールを接続するためのもの
module CPU_top(
input clk,
input reset,
output [7:0] OPT_PC
);
reg [7:0] PC;
wire [15:0] instr;
wire [7:0] aluout;
wire [3:0] alun;
wire [3:0] aluop;
wire regwren,enable,memwren,memtoregwren,pcnext,pcflag;
wire [2:0] memaddr;
wire [3:0] memtoregaddr;
wire [3:0] Reginaddr;
wire [3:0] Regoutaddr1;
wire [3:0] Regoutaddr2;
wire [3:0] regtomemaddr;
wire [7:0] Registerout1;
wire [7:0] Registerout2;
wire [7:0] memtoregdata;
wire [7:0] regtomem;
wire [7:0] NextPC;
wire [7:0] pcnextaddr;
initial begin
PC = 8'b00000000;
end
InstructionMemory IM(
.A(PC),
.RD(instr)
);
CU m0(
.instr(instr),
.enable(enable),
.regoutaddr1(Regoutaddr1),
.regoutaddr2(Regoutaddr2),
.reginaddr(Reginaddr),
.regtomemaddr(regtomemaddr),
.memtoregaddr(memtoregaddr),
.aluop(aluop),
.alun(alun),
.memwren(memwren),
.memtoregwren(memtoregwren),
.memaddr(memaddr),
.pcnextaddr(pcnextaddr),
.pcnext(pcnext),
.pcflag(pcflag),
.regwren(regwren)
);
Register R(
.clk(clk),
.data(aluout),
.wren(regwren),
.inaddr(Reginaddr),
.outaddr1(Regoutaddr1),
.outaddr2(Regoutaddr2),
.regtoalu1(Registerout1),
.regtoalu2(Registerout2),
.regtomemaddr(regtomemaddr),
.regtomem(regtomem),
.memtoregwren(memtoregwren),
.memtoregaddr(memtoregaddr),
.memtoregdata(memtoregdata)
);
alu A(
.op(aluop),
.a(Registerout1),
.b(Registerout2),
.n(alun),
.f(aluout)
);
memRam M(
.data(regtomem),
.wren(memwren),
.address(memaddr),
.inclock(clk),
.outclock(clk),
.q(memtoregdata)
);
assign NextPC = (pcnext) ? pcnextaddr : (PC + 1'b1); //指定アドレスにジャンプするか、次の命令を実行するかを判断
always@(posedge clk) //タイミング、各サイクルでPCの値が変化
begin
if(reset)
PC <= 0;
else
begin
if(enable)
PC <= NextPC;
else
PC <= PC; //停止命令、PCの値は変わらない
end
end
assign OPT_PC = PC;
endmodule
3.2 テストプログラム#
CPU_test.v#
`timescale 1ns / 1ps
module CPU_test(OPT_PC);
output [7:0] OPT_PC;
reg clk;
reg reset;
CPU_top uut(
.clk(clk),
.reset(reset),
.OPT_PC(OPT_PC)
);
//実行成功後、メモリの1から7の位置のデータは:2,3,5,2,3,253,250,126であるべき
initial begin
clk = 0;
reset = 1; //CPUを初期化
#100;
reset = 0;
$display(" pc: instr : ALUR :rg0:rg1:rg2: m0: m1: m2: m3: m4: m5: m6: m7");
$monitor("%d:%b:%b:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d",
uut.PC, uut.instr, uut.aluout, uut.R.regmem[0], uut.R.regmem[1], uut.R.regmem[2],
uut.M.ram[0], uut.M.ram[1], uut.M.ram[2], uut.M.ram[3],
uut.M.ram[4], uut.M.ram[5], uut.M.ram[6], uut.M.ram[7] );
#2000 $stop;
end
always #50 clk = ~clk;
endmodule
3.3 モデルの実行プログラムの過程の分析#
シミュレーションによって得られた波形図#
コンソール出力#
命令の分析#
-
命令 0:メモリの 0 番目からデータをレジスタ rg0 に読み込み、rg0=2、メモリの 0 番目は 2
-
命令 1:レジスタ rg0 のデータに算術加 1 を行い、結果を rg1 に移す、rg1 = rg0+1=2+1=3
-
命令 2:レジスタ rg1 のデータをメモリの 1 番目に保存、メモリの 1 番目は 3
-
命令 3:レジスタ rg0 と rg1 の数を加算し、結果を rg2 に保存、rg2=rg0+rg1=2+3=5
-
命令 4:レジスタ rg2 のデータをメモリの 2 番目に保存、メモリの 2 番目は 5
-
命令 5:7 番目の命令にジャンプ
-
命令 6:ジャンプが成功しなかった場合、停止
-
命令 7:レジスタ rg0 と rg1 の数を論理与演算し、結果を rg2 に保存、rg2=00000011&00000010=00000010
-
命令 8:レジスタ rg2 のデータをメモリの 3 番目に保存、メモリの 3 番目は 2
-
命令 9:レジスタ rg0 と rg1 の数を論理或演算し、結果を rg2 に保存、rg2=
00000011|00000010=00000011 -
命令 10:レジスタ rg2 のデータをメモリの 4 番目に保存、メモリの 4 番目は 3
-
命令 11:レジスタ rg0 のデータに論理非演算を行い、結果を rg2 に保存、rg2=~00000010=11111101(253)
-
命令 12:レジスタ rg2 のデータをメモリの 5 番目に保存、メモリの 5 番目は 253
-
命令 13:rg2 のデータを左に 1 ビットシフトし、結果を rg0 に保存、rg0=rg2<<1=11111101<<1=11111010(250)
-
命令 14:レジスタ rg0 のデータをメモリの 6 番目に保存、メモリの 6 番目は 250
-
命令 15:rg2 のデータを右に 1 ビットシフトし、結果を rg1 に保存、rg1=rg2>>1=11111101>>1=01111110(126)
-
命令 16:レジスタ rg2 のデータをメモリの 7 番目に保存、メモリの 7 番目は 126
-
命令 17:停止
結果分析#
- 命令 0-2:算術加 1 をテストし、計算 2+1=3 を行い、3 をメモリの 1 番目に保存
- 命令 3-4:加算をテストし、計算 2+3=5 を行い、5 をメモリの 2 番目に保存
- 命令 5-6:ジャンプ命令をテスト
- 命令 7-8:論理与演算をテストし、計算 00000011 & 00000010 を行い、3 をメモリの 2 番目に保存
- 命令 9-10:論理或演算をテストし、計算 00000011 | 00000010 を行い、4 をメモリの 3 番目に保存
- 命令 11-12:論理非演算をテストし、計算~00000010 を行い、253 をメモリの 5 番目に保存
- 命令 13-14:左シフトをテストし、11111101<<1 を行い、250 をメモリの 6 番目に保存
- 命令 15-16:右シフトをテストし、11111101>>1 を行い、126 をメモリの 7 番目に保存
波形図(図 3.3.1)から、命令 5 から命令 7 にジャンプし、ジャンプ命令が成功したことが確認でき、命令 17 以降の各サイクルで PC の値が変わらないことがわかります。
コンソールの出力(図 3.3.2)から、メモリ内の数値が 2,3,5,2,3,253,250,126 であり、命令の分析と一致していることが確認できます。