Computer Architecture MIPS Implementation
MIPS.v
module MIPS(
input CLK
);
CLK만을 input으로 가지는 MIPS 모듈 선언. MIPS의 전체 구조를 구현하며, Instruction Memory, Control, Register, ALU, Data Memory간의 관계 또는 처리 과정의 틀
// IM
wire [31:0] Next_PC, Instr, Branch_addr;
reg [31:0] PC;
// Control
wire RegDst, RegWrite; // control signal
wire ALUSrc, MemWrite, MemRead, MemToReg; // control signal
wire [3:0] ALUOp;
wire PCSrc, JToPC, Branch; // mux control signal
wire [5:0] Opcode, Funct;
// Register
wire [31:0] Jump_addr;
wire [4:0] Read1, Read2, Reg_Write_addr;
wire [31:0] Branch_or_offset;
wire [31:0] Reg_Write_data, Read_data1, Read_data2;
// ALU
wire [31:0] ALU_B, ALU_result, Lw_Sw_offset;
wire zero;
// DM
wire [31:0] Read_or_Write_addr;
wire [31:0] DM_Write_data, DM_Read_data;
Instruction_Mem에 필요한 Next_PC, Instr, Branch_addr wire 선언, PC reg 선언.
Control에 필요한 RegDst, RegWrite, ALUStc, MemWrite, MemRead, MemToReg, ALUOp, JToPC, Branch, Opcode, Funct wire 선언
Register에 필요한 Jump_addr, Read1, Read2, Reg_Write_addr, Branch_or_offset, Reg_Write_data, Read_data1, Read_data2 wire 선언
ALU에 필요한 ALU_B, ALU_result, Lw_Sw_offset, zero wire 선언
Data_Mem에 필요한 Read_or_Write_addr, DM_Write_data, DM_Read_data wire 선언
assign Next_PC = PC + 32'd4;
assign Branch_addr = {Branch_or_offset[29:0], 2'b00};
assign PCSrc = (Branch && zero);
assign Opcode = Instr[31:26];
assign Funct = Instr[5:0];
assign Jump_addr = {Next_PC[31:28], {Instr[25:0], 2'b00}};
assign Read1 = Instr[25:21];
assign Read2 = Instr[20:16];
assign Reg_Write_addr = (RegDst) ? Instr[15:11] : Instr[20:16];
assign Branch_or_offset = (Instr[15] == 1) ? {16'hFFFF, Instr[15:0]} : {16'h0000, Instr[15:0]}; // sign extend.
assign Lw_Sw_offset = (Branch_or_offset[31] == 1) ? {2'b11, Branch_or_offset[31:2]} : {2'b00, Branch_or_offset[31:2]};
// if instruction is lw or sw, divide the offset by 4.
assign ALU_B = (Opcode == 6'b100011 || Opcode == 6'b101011) ? Lw_Sw_offset : ((ALUSrc) ? Branch_or_offset : Read_data2);
// if instruction is lw or sw, ALU_B is Lw_Sw_offset.
assign DM_Write_data = Read_data2;
assign Read_or_Write_addr = ALU_result;
assign Reg_Write_data = (MemToReg) ? DM_Read_data : ALU_result;
전 코드에서 wire로 변수들을 이어줄 준비를 했으니 이제 할당할 차례임.
assign Next_PC = PC + 32'd4;
PC값에 32비트 10진수 4를 더해줌 -> PC = PC + 4
assign Branch_addr = {Branch_or_offset[29:0], 2'b00};
{a, b}: 결합 연산
branch 할 때의 주소를 할당. Branch_or_offset의 상위 29비트를 2비트 shift함
assign PCSrc = (Branch && zero);
PCSrc control을 결정함. Branch AND zero
``assign Opcode = Instr[31:26];` R-Type에서 Opcode를 결정. Instruction의 상위 6비트
assign Funct = Instr[5:0];
R-Type에서 Funct를 결정. Instruction의 하위 6비트
assign Jump_addr = {Next_PC[31:28], {Instr[25:0], 2'b00}};
Jump 할 주소를 결정. Opcode를 제외한 명령어의 26비트를 2비트 shift 후 Next_PC의 상위 4비트에 결합
assign Read1 = Instr[25:21];
assign Read2 = Instr[20:16];
Read register 결정
assign Reg_Write_addr = (RegDst) ? Instr[15:11] : Instr[20:16];
Reg_Write_addr 결정. RegDst가 1이면(R-Type) rd가 0이면(I-Type) rt가 Reg_write에 들어감
assign Branch_or_offset = (Instr[15] == 1) ? {16'hFFFF, Instr[15:0]} : {16'h0000, Instr[15:0]};
Instr[15] == 1, 즉 2’complement일 때는 음수이므로 1로 sign extension, 아니면 0으로
assign Lw_Sw_offset = (Branch_or_offset[31] == 1) ? {2'b11, Branch_or_offset[31:2]} : {2'b00, Branch_or_offset[31:2]};
모르겟음
assign ALU_B = (Opcode == 6'b100011 || Opcode == 6'b10101) ? Lw_Sw_offset : ((ALUSrc) ? Branch_or_offset : Read_data2);
모르겟음
assign DM_Write_data = Read_data2;
I-Type일 때 rt가 Write data로 결정
assign Read_or_Write_addr = ALU_result;
Read address 와 Write address가 ALU_result로 결정
assign Reg_Write_data = (MemToReg) ? DM_Read_data : ALU_result;
MemToReg가 1이면 Register에 저장할 data를 메모리에서 가져온 값인 DM_Read_data이고 0이면 ALU에서 계산된 값인 ALU_result로 결정
Instruction_Mem Instr_mem(PC[8:2], Instr); // Asynchronous module.
/*
[8:0] [8:2]
0 000 0000 00 000 0000 => 0
4 000 0001 00 000 0001 => 1
8 000 0010 00 000 0010 => 2
12 000 0011 00 000 0011 => 3
16 000 0100 00 000 0100 => 4
20 000 0101 00 000 0101 => 5
24 000 0110 00 000 0110 => 6
28 000 0111 00 000 0111 => 7
*/
Instruction_Mem에서 input값으로 PC[8:2]와 Instr를 넣어줌. PC[8:2]는 4씩 끊어지는 PC값을 10진수로 1씩끊어지게 인코딩(?)하는 과정
Control control(Opcode, Funct, RegDst, RegWrite, ALUSrc, MemWrite, MemRead, MemToReg, JToPC, Branch, ALUOp); // Asynchronous module.
Register Reg(CLK, RegWrite, Read1, Read2, Reg_Write_addr, Reg_Write_data, Read_data1, Read_data2); // Synchronous module.
ALU alu(ALUOp, Read_data1, ALU_B, ALU_result, zero); // Asynchronous module.
Data_Mem DM(CLK, MemWrite, MemRead, Read_or_Write_addr[6:0], DM_Write_data, DM_Read_data); // Synchronous module.
Control, Register, ALU, Data_Mem 모듈에 Input값 넣어줌
Asynchronous vs Synchronous 차이 모르겟음
always @(posedge CLK)
begin
PC <= (JToPC) ? Jump_addr : ((PCSrc) ? (Branch_addr + Next_PC) : Next_PC);
end
initial
begin
PC = 32'd0;
end
endmodule
모르겟음… ㅡㅡ;;
Instruction_Mem.v
module Instruction_Mem(
input wire [6:0] addr,
output wire [31:0] data );
Instruction Memory 모듈 선언. Input으로 addr(주소)와 data(데이터)를 받음.
//parameter NMEM = 128; // Number of memory entries, not the same as the memory size
//parameter IM_DATA = "im_data.txt"; // file to read data from
reg [31:0] mem [0:127]; // 32-bit memory with 128 entries
128개의 entry를 가진 32비트 명령어 메모리 선언
initial
begin
// $readmemh(IM_DATA, mem, 0, NMEM-1);
mem[0] = 32'b100011_00000_10000_00000_00000_000000; // lw s0 0($zero) , s0 = 1
mem[1] = 32'b100011_00000_10001_00000_00000_000100; // lw s1 4($zero) , s1 = 0
mem[2] = 32'b000000_10000_10001_10010_00000_100000; // add s2 s0 s1 , s2 = 1
mem[3] = 32'b000000_10000_10001_10011_00000_100010; // sub s3 s0 s1 , s3 = 1
mem[4] = 32'b000000_10010_10011_10100_00000_100100; // and s4 s2 s3 , s4 = 1
mem[5] = 32'b000000_10010_10011_10101_00000_100110; // xor s5 s2 s3 , s5 = 0
mem[6] = 32'b000000_10101_10100_10110_00000_101010; // slt s6 s5 s4 , s6 = 1
mem[7] = 32'b000100_10101_10001_00000_00000_000001; // beq s5 s1 1 , s5 = s1 = 0
mem[8] = 32'b000010_00000_00000_00000_00000_001101; // j 13 , it is not executed at first, but is executed after the jump. (mem[12])
mem[9] = 32'b100011_00000_10111_00000_00000_001000; // lw s7 8($zero) , s7 = 0
mem[10]= 32'b101011_10111_10101_00000_00000_010000; // sw s5 16(s7) , mem[4] = 0
mem[11]= 32'b100011_10111_10000_00000_00000_010000; // lw s0 16(s7) , s0 = mem[4] = 0
mem[12]= 32'b000010_00000_00000_00000_00000_000011; // j 3
mem[13]= 32'b000000_10000_00000_10111_00000_100111; // nor s7 s0 $zero , s7 = 32'b1111_1111_1111_1111_1111_1111_1111_1111;
mem[14]= 32'b100011_00000_01000_00000_00000_101000; // lw t0 40($zero) , t0 = 32'b0101_0101_1010_1010_0101_0101_1010_1010;
mem[15]= 32'b100011_00000_01001_00000_00000_101100; // lw t1 44($zero) , t1 = 32'b0111_0111_1000_1000_0111_0111_1000_1000;
mem[16]= 32'b000000_01000_01001_01010_00000_100000; // add t2 t0 t1 , t2 = 32'b1100_1101_0011_0010_1100_1101_0011_0010; positive value (overflow)
mem[17]= 32'b000000_01000_01001_01011_00000_100010; // sub t3 t0 t1 , t3 = 32'b1101_1110_0010_0001_1101_1110_0010_0010; negative value
mem[18]= 32'b000000_01000_01001_01100_00000_100100; // and t4 t0 t1 , t4 = 32'b0101_0101_1000_1000_0101_0101_1000_1000;
mem[19]= 32'b000000_01000_01001_01101_00000_100110; // xor t5 t0 t1 , t5 = 32'b0010_0010_0010_0010_0010_0010_0010_0010;
mem[20]= 32'b000000_01010_01011_01110_00000_101010; // slt t6 t2 t3 , t6 = 32'd1;
end
simulation 할 때의 명령어를 instruction memory에 저장
assign data = mem[addr][31:0];
endmodule
data에 addr번째 명령어 저장(?)
Control.v
module Control(
input wire [5:0] Opcode, Funct,
output reg RegDst, RegWrite, ALUSrc, MemWrite, MemRead, MemToReg, JToPC, Branch,
output reg [3:0] ALUOp );
Control 모듈 선언. Input으로 Opcode, Funct를 가짐.
Output으로는 Control signal들인 RegDst, RegWrite, ALUSrc, MemWrite, MemRead, MemToReg, JToPC, Branch, ALUOp를 가짐.
always @(*)
begin
case (Opcode)
Opcode를 보고 control 설정
6'b000000 : begin // R Type - add, sub, and, or, slt
RegDst = 1; RegWrite = 1; ALUSrc = 0; MemWrite = 0; MemRead = 0; MemToReg = 0; JToPC = 0; Branch = 0;
if(Funct == 6'b100000) ALUOp = 4'b0010; // add
else if(Funct == 6'b100010) ALUOp = 4'b0110; // sub
else if(Funct == 6'b100100) ALUOp = 4'b0000; // and
else if(Funct == 6'b100101) ALUOp = 4'b0001; // or
else if(Funct == 6'b101010) ALUOp = 4'b0111; // slt
else if(Funct == 6'b011000) ALUOp = 4'b1000; // mult
else if(Funct == 6'b100110) ALUOp = 4'b1101; // xor
else if(Funct == 6'b100111) ALUOp = 4'b1100; // nor
else ALUOp = 4'b0000; // To avoid creating a latch
end
Opcode가 6’b000000일때, 즉 R-Type일때의 Control signal 설정. R-type일때는 Funct를 보고 어떤 기능을 하는지 알 수 있으므로 Funct에 따라 ALUOp 결정.
6'b100011 : begin // Load Word
RegDst = 0; RegWrite = 1; ALUSrc = 1; MemWrite = 0; MemRead = 1; MemToReg = 1; JToPC = 0; Branch = 0;
ALUOp = 4'b0010; // add for lw
end
6'b101011 : begin // Store Word
RegWrite = 0; ALUSrc = 1; MemWrite = 1; MemRead = 0; JToPC = 0; Branch = 0;
ALUOp = 4'b0010; // add for sw
RegDst = 0; MemToReg = 0; // To avoid creating a latch
end
6'b000100 : begin // Beq
RegWrite = 0; ALUSrc = 0; MemWrite = 0; MemRead = 0; JToPC = 0; Branch = 1;
ALUOp = 4'b0110; // sub for beq
RegDst = 0; MemToReg = 0; // To avoid creating a latch
end
6'b000010 : begin// Jump
RegWrite = 0; MemWrite = 0; MemRead = 0; JToPC = 1; Branch = 0;
RegDst = 0; ALUSrc = 0; MemToReg = 0; ALUOp = 4'b0000; // To avoid creating a latch
end
Simulation할 때, Instruction memory에서 lw, sw, beq, j 명령어와 위에서 정의한 R-Type 명령어만을 사용하므로 이것들의 Opcode에 대한 Control Signal 설정
default : begin // To avoid creating a latch
RegDst = 0; RegWrite = 0; ALUSrc = 0; MemWrite = 0; MemRead = 0; MemToReg = 0; JToPC = 0; Branch = 0;
ALUOp = 4'b0000;
end
endcase
end
모르겟음
initial
begin
RegDst = 0; RegWrite = 0; ALUSrc = 0; MemWrite = 0; MemRead = 0; MemToReg = 0; JToPC = 0; Branch = 0;
ALUOp = 4'b0000;
end
endmodule
Simulation할 때의 Control Signal 설정
Register.v
// `define DEBUG_CPU_REG 0
module Register(
input wire CLK,
input wire RegWrite, // control signal
input wire [4:0] read1, read2,
input wire [4:0] wrreg,
input wire [31:0] wrdata,
output reg [31:0] data1, data2 );
Register 모듈 선언. Input으로는 CLK, RegWrite, read1, read2, wrreg, wrdata를 가지고, Output으로는 data1, data2를 가짐.
reg [31:0] mem [0:31]; // 32-bit memory with 32 entries
32개의 entry를 가지는 32비트 메모리 선언
initial
begin
// $display("$v0, $v1, $t0, $t1, $t2, $t3, $t4, $t5, $t6, $t7");
//$monitor("%b, %b, %b, %b, %b, %b, %b, %b, %b, %b",
//mem[0][31:0], /* $zero */
//mem[16][31:0], /* $s0 */
//mem[17][31:0], /* $s1 */
//mem[18][31:0], /* $s2 */
//mem[19][31:0], /* $s3 */
//mem[20][31:0], /* $s4 */
//mem[21][31:0], /* $s5 */
//mem[22][31:0], /* $s6 */
//mem[23][31:0] /* $s7 */
//);
mem[0] = 32'd0; // $zero = 0;
end
register memory 출력(?)
always @(*)
begin
if (read1 == 5'd0) // $zero
data1 = 32'd0;
else if ((read1 == wrreg) && RegWrite)
data1 = wrdata;
else
data1 = mem[read1][31:0];
end
- if 절: read1이 $0(zero)일 때 data1에 0을 넣어줌
- else if 절: read1이 write register이고 RegWrite가 1일때 data1에 wrdata를 넣어줌 (read1이 write register일 때가 있나??)
- else 절: 두 경우 모두 아닐 때 read1의 주소에 있는 값을 data1에 넣어줌
always @(*)
begin
if (read2 == 5'd0) // $zero
data2 = 32'd0;
else if ((read2 == wrreg) && RegWrite)
data2 = wrdata;
else
data2 = mem[read2][31:0];
end
- if 절: read2가 $0(zero)일 때 data2에 0을 넣어줌
- else if 절: read2가 write register이고 RegWrite가 1일때 data2에 wrdata를 넣어줌
- else 절: 두 경우 모두 아닐 때 read2의 주소에 있는 값을 data2에 넣어줌
always @(posedge CLK)
begin
if (RegWrite && wrreg != 5'd0) // $zero can not be changed.
begin
// write a non $zero register
mem[wrreg] <= wrdata;
end
end
endmodule
모르겟음
ALU.v
module ALU(
input [3:0] ALUOp,
input [31:0] a, b,
output reg [31:0] out,
output zero );
ALU 모듈 선언. Input으로 ALUOp, a, b를 가지고 Output으로 out, zero를 가짐.
wire [31:0] sub_ab;
wire [31:0] add_ab;
wire [31:0] mult_ab;
wire oflow_add, oflow_sub, oflow, slt;
sub, add, mult, add overflow, sub overflow, overflow, slt wire 선언
assign zero = (0 == out); // true or false
assign sub_ab = a - b;
assign add_ab = a + b;
assign mult_ab = a * b;
- out이 0이면 zero는 1, out이 1이면 zero는 0
- sub, add, mult 계산
assign oflow_add = (a[31] == b[31] && add_ab[31] != a[31]) ? 1 : 0; // overflow
assign oflow_sub = (a[31] == b[31] && sub_ab[31] != a[31]) ? 1 : 0; // overflow, If the latter is greater in absolute value, oflow_sub is 1.
assign oflow = (ALUOp == 4'b0010) ? oflow_add : oflow_sub;
assign slt = oflow_sub ? ~(a[31]) : a[31];
overflow계산하는 과정. add와 sub 연산 시 overflow를 체크해 slt 연산 시 사용.
always @(*)
begin
case (ALUOp)
4'b0010 : out = add_ab; /* add */
4'b0000 : out = a & b; /* and */
4'b1000 : out = mult_ab; /* mult */
4'b1100 : out = ~(a | b); /* nor */
4'b0001 : out = a | b; /* or */
4'b0111 : out = {31'd0, slt}; /* slt */
4'b0110 : out = sub_ab; /* sub */
4'b1101 : out = a ^ b; /* xor */
default : out = 0;
endcase
end
endmodule
Sumulation 할 때, ALUOp에 따른 연산을 수행해 out에 값을 넣어줌.
Data_Mem.v
module Data_Mem(
input CLK,
input wire MemWrite, MemRead, // control signal
input wire [6:0] addr,
input wire [31:0] Write_Data,
output reg [31:0] Read_Data );
Data memory 모듈 선언. Input으로 CLK, MemWrite, MemRead, addr, Write_Data를 가지고, Output으로 Read_Data를 가짐
reg [31:0] mem [0:127]; // 32-bit memory with 128 entries
128개의 entry를 가지는 32비트 메모리 선언
always @(*)
begin
if(MemWrite == 0 && MemRead == 1) // lw
Read_Data = mem[addr][31:0];
else
Read_Data = Read_Data; // To avoid creating a latch
end
MemWrite가 0이고 MemRead가 1이면 lw 명령어를 수행할 때임
- if 절: Read_Data에 addr주소를 가진 값 넣어줌
- else절: latch 생성을 피한다(?)
always @(posedge CLK)
begin
if(MemWrite == 1 && MemRead == 0) // sw
mem[addr][31:0] <= Write_Data;
end
MemWrite가 1이고 MemRead가 0이면 sw 명령어를 수행할 때임
- if 절: addr주소를 가진 메모리에 Write_Data값을 넣어줌
integer i;
initial
begin
mem[0] = 32'd1; // for s0 = 1
mem[1] = 32'd0; // for s1 = 0
mem[2] = 32'd0; // for s7 = 0
mem[3] = 32'd0; // initialize
// mem[4]
mem[5] = 32'd0; // initialize
mem[6] = 32'd0; // initialize
mem[7] = 32'd0; // initialize
mem[8] = 32'd0; // initialize
mem[9] = 32'd0; // initialize
mem[10] = 32'b0101_0101_1010_1010_0101_0101_1010_1010; // 0x55aa_55aa
mem[11] = 32'b0111_0111_1000_1000_0111_0111_1000_1000; // 0x7788_7788
for (i=12; i<128; i = i+1) // initialize
begin
mem[i] = 32'd0;
end
end
endmodule
Simulation 할때 필요한 값들을 넣어줌
끗..