CPU
RSIC
Multiplexor
Driver
ALU
Controller
Register
Memory
Counter
RISC
CPU
CPU (Central Processing Unut) : 중앙 처리 장치
- CPU는 4단계의 사이클로 명령어를 처리한다.
1. Fetch : 명령어 인출 - CPU에서 명령어를 읽어온다.
2. Decode : 명령어 해독 - 읽어온 명령어를 해독한다.
3. Execute : 명령어 실행 - 해독한 명령어를 실행한다.
4. Store : 결과 저장 - 계산 결과를 저장한다.
- CPU에는 특수한 목적을 가지고 있는 레지스터들이 있다.
1. AR(Address Register) : PC에 저장된 명령어가 주소 버스로 이동하기 전에 일시 저장한다.
2. PC(Program Counter) : 다음 명령어의 주소를 가지고 있다.
3. IR(Instruction Register) : 가장 최근에 인출된 명령어를 저장한다 -> 현재 실행중인 명령어에 해당한다.
4. DR(Data Register) : (피연산자)Operand를 저장한다.
RSIC
Reduced Instruction Set Computing의 약자로, '명령어 집합 축소 컴퓨팅'을 의미한다.
RISC 아키텍처는 컴퓨터 프로세서 설계의 한 유형으로, 간단하고 기본적인 명령어 세트를 사용하여 빠르고 효율적인 작동을 목표로 한다.
RISC 프로세서는 작은 명령어 세트를 가지고 있고, 각 명령어가 매우 기본적인 동작을 수행한다.
일반적으로 RISC 프로세서는 명령어 길이가 일정하고, 명령어의 수가 적으며, 복잡한 명령어를 가지지 않는다. 이러한 특징들은 명령어 실행 속도를 향상시키고, 프로세서를 더 단순하게 만들어 전력 소모를 줄이는 데 도움이 된다.
대표적인 RISC 아키텍처에는 ARM, MIPS, PowerPC 등이 있다.
CPU 설계 최종본
Multiplexor
만약 sel이 1'b0이면 in0의 값이 mux_out로 나가고, sel이 1'b1이면 in1의 값이 mux_out으로 나간다.
mux.v
module multiplexor #(parameter WIDTH = 5) (
input [WIDTH-1:0] in0,
input [WIDTH-1:0] in1,
input sel,
output reg [WIDTH-1:0] mux_out
);
always @(in0 or in1 or sel) begin
if(sel == 1'b0)
mux_out <= in0;
else
mux_out <= in1;
end
endmodule
mux_tb.v
module multiplexor_test;
localparam WIDTH=5;
reg sel ;
reg [WIDTH-1:0] in0 ;
reg [WIDTH-1:0] in1 ;
wire [WIDTH-1:0] mux_out;
multiplexor
#(
.WIDTH ( WIDTH )
)
multiplexor_inst
(
.sel ( sel ),
.in0 ( in0 ),
.in1 ( in1 ),
.mux_out ( mux_out )
);
task expect;
input [WIDTH-1:0] exp_out;
if (mux_out !== exp_out) begin
$display("TEST FAILED");
$display("At time %0d sel=%b in0=%b in1=%b mux_out=%b",
$time, sel, in0, in1, mux_out);
$display("mux_out should be %b", exp_out);
$finish;
end
else begin
$display("At time %0d sel=%b in0=%b in1=%b, mux_out=%b",
$time, sel, in0, in1, mux_out);
end
endtask
initial begin
sel=0; in0=5'h15; in1=5'h00; #1 expect (5'h15);
sel=0; in0=5'h0A; in1=5'h00; #1 expect (5'h0A);
sel=1; in0=5'h00; in1=5'h15; #1 expect (5'h15);
sel=1; in0=5'h00; in1=5'h0A; #1 expect (5'h0A);
$display("TEST PASSED");
$finish;
end
endmodule
Driver
만약 data_en이 High면 data_in의 값이 data_out 값으로 나가고, data_en이 Low면 data_out 값이 z(High impedance)값을 가진다.
driver.v
module driver #(parameter WIDTH = 8) (
input [WIDTH-1:0] data_in,
input data_en,
output [WIDTH-1:0] data_out
);
assign data_out = (data_en) ? data_in : 8'hzz;
endmodule
driver_tb.v
module driver_test;
localparam WIDTH=8;
reg data_en ;
reg [WIDTH-1:0] data_in ;
wire [WIDTH-1:0] data_out ;
driver
#(
.WIDTH ( WIDTH )
)
driver_inst
(
.data_en ( data_en ),
.data_in ( data_in ),
.data_out ( data_out )
);
task expect;
input [WIDTH-1:0] exp_out;
if (data_out !== exp_out) begin
$display("TEST FAILED");
$display("At time %0d data_en=%b data_in=%b data_out=%b",
$time, data_en, data_in, data_out);
$display("data_out should be %b", exp_out);
$finish;
end
else begin
$display("At time %0d data_en=%b data_in=%b data_out=%b",
$time, data_en, data_in, data_out);
end
endtask
initial begin
data_en=1'b0; data_in=8'hXX; #1 expect (8'hZZ);
data_en=1'b1; data_in=8'h55; #1 expect (8'h55);
data_en=1'b1; data_in=8'hAA; #1 expect (8'hAA);
$display("TEST PASSED");
$finish;
end
endmodule
ALU
a_is_zero는 단일 비트이고, in_a가 0일 때 a_is_zero는 1이 되고, in_a가 1일 때 a_is_zero는 0이 된다.
output에 해당하는 alu_out은 아래 opcode 표에 따라서 동작한다.
Opcode/Instruction | Opcode Encoding | Operation | Output |
HLT | 000 | PASS A | in_a => alu_out |
SKZ | 001 | PASS A | in_a => alu_out |
ADD | 010 | ADD | in_a + in_b => alu_out |
AND | 011 | AND | in_a & in_b => alu_out |
XOR | 100 | XOR | in_a ^ in_b => alu_out |
LDA | 101 | PASS B | in_b => alu_out |
STO | 110 | PASS A | in_a => alu_out |
JMP | 111 | PASS A | in_a => alu_out |
alu.v
module alu #(parameter WIDTH = 8) (
input [WIDTH-1:0] in_a,
input [WIDTH-1:0] in_b,
input [2:0] opcode,
output reg [WIDTH-1:0] alu_out,
output reg a_is_zero
);
localparam HLT = 3'b000;
localparam SKZ = 3'b001;
localparam ADD = 3'b010;
localparam AND = 3'b011;
localparam XOR = 3'b100;
localparam LDA = 3'b101;
localparam STO = 3'b110;
localparam JMP = 3'b111;
always @* begin
if(in_a == 0) begin
a_is_zero <= 1;
end
else begin
a_is_zero <= 0;
end
end
always @* begin
case(opcode)
HLT : alu_out <= in_a;
SKZ : alu_out <= in_a;
ADD : alu_out <= in_a + in_b;
AND : alu_out <= in_a & in_b;
XOR : alu_out <= in_a ^ in_b;
LDA : alu_out <= in_b;
STO : alu_out <= in_a;
JMP : alu_out <= in_a;
endcase
end
endmodule
alu_tb.v
module alu_test;
localparam WIDTH=8;
localparam PASS0=0, PASS1=1, ADD=2, AND=3, XOR=4, PASSB=5, PASS6=6, PASS7=7;
reg [ 2:0] opcode ;
reg [WIDTH-1:0] in_a ;
reg [WIDTH-1:0] in_b ;
wire a_is_zero ;
wire [WIDTH-1:0] alu_out ;
alu
#(
.WIDTH ( WIDTH )
)
alu_inst
(
.opcode ( opcode ),
.in_a ( in_a ),
.in_b ( in_b ),
.a_is_zero ( a_is_zero ),
.alu_out ( alu_out )
);
task expect;
input exp_zero;
input [WIDTH-1:0] exp_out;
if (a_is_zero !== exp_zero || alu_out !== exp_out) begin
$display("TEST FAILED");
$display("At time %0d opcode=%b in_a=%b in_b=%b a_is_zero=%b alu_out=%b",
$time, opcode, in_a, in_b, a_is_zero, alu_out);
if (a_is_zero !== exp_zero)
$display("a_is_zero should be %b", exp_zero);
if (alu_out !== exp_out)
$display("alu_out should be %b", exp_out);
$finish;
end
else begin
$display("At time %0d opcode=%b in_a=%b in_b=%b a_is_zero=%b alu_out=%b",
$time, opcode, in_a, in_b, a_is_zero, alu_out);
end
endtask
initial begin
opcode=PASS0; in_a=8'h42; in_b=8'h86; #1 expect (1'b0, 8'h42);
opcode=PASS1; in_a=8'h42; in_b=8'h86; #1 expect (1'b0, 8'h42);
opcode=ADD; in_a=8'h42; in_b=8'h86; #1 expect (1'b0, 8'hC8);
opcode=AND; in_a=8'h42; in_b=8'h86; #1 expect (1'b0, 8'h02);
opcode=XOR; in_a=8'h42; in_b=8'h86; #1 expect (1'b0, 8'hC4);
opcode=PASSB; in_a=8'h42; in_b=8'h86; #1 expect (1'b0, 8'h86);
opcode=PASS6; in_a=8'h42; in_b=8'h86; #1 expect (1'b0, 8'h42);
opcode=PASS7; in_a=8'h42; in_b=8'h86; #1 expect (1'b0, 8'h42);
opcode=PASS7; in_a=8'h00; in_b=8'h86; #1 expect (1'b1, 8'h00);
$display("TEST PASSED");
$finish;
end
endmodule
Controller
Controller는 clk 라이징 엣지에 동작한다.
rst는 High 신호에 동기화 된다.
zero는 CPU 계산이 zero일 때 1이고, 그렇지 않으면 0인 입력이다.
Opcode는 3 비트의 input이고, 아래 테이블을 따른다.
Opcode/Instruction | Opcode Encoding | Operation | Output |
HLT | 000 | PASS A | in_a => alu_out |
SKZ | 001 | PASS A | in_a => alu_out |
ADD | 010 | ADD | in_a + in_b => alu_out |
AND | 011 | AND | in_a & in_b => alu_out |
XOR | 100 | XOR | in_a ^ in_b => alu_out |
LDA | 101 | PASS B | in_b => alu_out |
STO | 110 | PASS A | in_a => alu_out |
JMP | 111 | PASS A | in_a => alu_out |
Output | Function |
se | select |
rd | memory read |
ld_ir | load instruction register |
halt | halt |
inc_pc | increment program counter |
ld_ac | load accumulator |
ld_pc | load program counter |
wr | memory write |
data_e | data enable |
Controller.v
module controller (
input zero,
input [2:0] phase,
input [2:0] opcode,
output reg sel,
output reg rd,
output reg ld_ir,
output reg halt,
output reg inc_pc,
output reg ld_ac,
output reg wr,
output reg ld_pc,
output reg data_e
);
localparam INST_ADDR = 3'b000;
localparam INST_FETCH = 3'b001;
localparam INST_LOAD = 3'b010;
localparam IDLE = 3'b011;
localparam OP_ADDR = 3'b100;
localparam OP_FETCH = 3'b101;
localparam ALU_OP = 3'b110;
localparam STORE = 3'b111;
localparam HLT = 3'b000;
localparam SKZ = 3'b001;
localparam ADD = 3'b010;
localparam AND = 3'b011;
localparam XOR = 3'b100;
localparam LDA = 3'b101;
localparam STO = 3'b110;
localparam JMP = 3'b111;
always @* begin
case(phase)
INST_ADDR : begin
sel <= 1; rd <= 0; ld_ir <= 0; halt <= 0; inc_pc <= 0; ld_ac <= 0; ld_pc <= 0; wr <= 0; data_e <= 0;
end
INST_FETCH : begin
sel <= 1; rd <= 1; ld_ir <= 0; halt <= 0; inc_pc <= 0; ld_ac <= 0; ld_pc <= 0; wr <= 0; data_e <= 0;
end
INST_LOAD : begin
sel <= 1; rd <= 1; ld_ir <= 1; halt <= 0; inc_pc <= 0; ld_ac <= 0; ld_pc <= 0; wr <= 0; data_e <= 0;
end
IDLE : begin
sel <= 1; rd <= 1; ld_ir <= 1; halt <= 0; inc_pc <= 0; ld_ac <= 0; ld_pc <= 0; wr <= 0; data_e <= 0;
end
OP_ADDR : begin
sel <= 0; rd <= 0; ld_ir <= 0; halt <= (opcode == HLT ? 1 : 0); inc_pc <= 1; ld_ac <= 0; ld_pc <= 0; wr <= 0; data_e <= 0;
end
OP_FETCH : begin
sel <= 0; rd <= (opcode == ADD || opcode == AND || opcode == XOR || opcode == LDA ? 1 : 0); ld_ir <= 0; halt <= 0; inc_pc <= 0; ld_ac <= 0; ld_pc <= 0; wr <= 0; data_e <= 0;
end
ALU_OP : begin
sel <= 0; rd <= (opcode == ADD || opcode == AND || opcode == XOR || opcode == LDA ? 1: 0); ld_ir <= 0; halt <= 0; inc_pc <= ((opcode == SKZ ? 1 : 0) && zero); ld_ac <= 0; ld_pc <= (opcode == JMP ? 1 : 0); wr <= 0; data_e <= (opcode == STO ? 1 : 0);
end
STORE : begin
sel <= 0; rd <= (opcode == ADD || opcode == AND || opcode == XOR || opcode == LDA ? 1 : 0); ld_ir <= 0; halt <= 0; inc_pc <= 0; ld_ac <= (opcode == ADD || opcode == AND || opcode == XOR || opcode == LDA ? 1 : 0); ld_pc <= (opcode == JMP ? 1 : 0); wr <= (opcode == STO ? 1 : 0); data_e <= (opcode == STO ? 1 : 0);
end
endcase
end
endmodule
Controller_test.v
module controller_test;
localparam integer HLT=0, SKZ=1, ADD=2, AND=3, XOR=4, LDA=5, STO=6, JMP=7;
reg [2:0] opcode ;
reg [2:0] phase ;
reg zero ; // accumulator is zero
wire sel ; // select instruction address to memory
wire rd ; // enable memory output onto data bus
wire ld_ir ; // load instruction register
wire inc_pc ; // increment program counter
wire halt ; // halt machine
wire ld_pc ; // load program counter
wire data_e ; // enable accumulator output onto data bus
wire ld_ac ; // load accumulator from data bus
wire wr ; // write data bus to memory
controller controller_inst
(
.opcode ( opcode ),
.phase ( phase ),
.zero ( zero ),
.sel ( sel ),
.rd ( rd ),
.ld_ir ( ld_ir ),
.inc_pc ( inc_pc ),
.halt ( halt ),
.ld_pc ( ld_pc ),
.data_e ( data_e ),
.ld_ac ( ld_ac ),
.wr ( wr )
);
task expect;
input [8:0] exp_out;
if ({sel,rd,ld_ir,inc_pc,halt,ld_pc,data_e,ld_ac,wr} !== exp_out) begin
$display("\nTEST FAILED");
$display("time\topcode phase zero sel rd ld_ir inc_pc halt ld_pc data_e ld_ac wr");
$display("====\t====== ===== ==== === == ===== ====== ==== ===== ====== ===== ==");
$display("%0d\t%d %d %b %b %b %b %b %b %b %b %b %b",
$time, opcode, phase, zero, sel, rd, ld_ir, inc_pc, halt, ld_pc,
data_e, ld_ac, wr);
$display("WANT\t %b %b %b %b %b %b %b %b %b",
exp_out[8],exp_out[7],exp_out[6],exp_out[5],exp_out[4],exp_out[3],exp_out[2],exp_out[1],exp_out[0]);
$finish;
end
endtask
initial begin
zero=0;
$write("Testing opcode HLT phase"); opcode=HLT;
$write(" 0"); phase=0; #1 expect (9'b100000000); // sel
$write(" 1"); phase=1; #1 expect (9'b110000000); // sel, rd
$write(" 2"); phase=2; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 3"); phase=3; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 4"); phase=4; #1 expect (9'b000110000); // inc_pc, halt
$write(" 5"); phase=5; #1 expect (9'b000000000); //
$write(" 6"); phase=6; #1 expect (9'b000000000); //
$write(" 7"); phase=7; #1 expect (9'b000000000); //
$write("\n");
$write("Testing opcode SKZ phase"); opcode=SKZ;
$write(" 0"); phase=0; #1 expect (9'b100000000); // sel
$write(" 1"); phase=1; #1 expect (9'b110000000); // sel, rd
$write(" 2"); phase=2; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 3"); phase=3; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 4"); phase=4; #1 expect (9'b000100000); // inc_pc
$write(" 5"); phase=5; #1 expect (9'b000000000); //
$write(" 6"); phase=6; #1 expect (9'b000000000); //
zero=1; #1 expect (9'b000100000); // inc_pc
$write(" 7"); phase=7; #1 expect (9'b000000000); //
$write("\n");
$write("Testing opcode ADD phase"); opcode=ADD;
$write(" 0"); phase=0; #1 expect (9'b100000000); // sel
$write(" 1"); phase=1; #1 expect (9'b110000000); // sel, rd
$write(" 2"); phase=2; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 3"); phase=3; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 4"); phase=4; #1 expect (9'b000100000); // inc_pc
$write(" 5"); phase=5; #1 expect (9'b010000000); // rd
$write(" 6"); phase=6; #1 expect (9'b010000000); // rd
$write(" 7"); phase=7; #1 expect (9'b010000010); // rd, ld_ac
$write("\n");
$write("Testing opcode AND phase"); opcode=AND;
$write(" 0"); phase=0; #1 expect (9'b100000000); // sel
$write(" 1"); phase=1; #1 expect (9'b110000000); // sel, rd
$write(" 2"); phase=2; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 3"); phase=3; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 4"); phase=4; #1 expect (9'b000100000); // inc_pc
$write(" 5"); phase=5; #1 expect (9'b010000000); // rd
$write(" 6"); phase=6; #1 expect (9'b010000000); // rd
$write(" 7"); phase=7; #1 expect (9'b010000010); // rd, ld_ac
$write("\n");
$write("Testing opcode XOR phase"); opcode=XOR;
$write(" 0"); phase=0; #1 expect (9'b100000000); // sel
$write(" 1"); phase=1; #1 expect (9'b110000000); // sel, rd
$write(" 2"); phase=2; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 3"); phase=3; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 4"); phase=4; #1 expect (9'b000100000); // inc_pc
$write(" 5"); phase=5; #1 expect (9'b010000000); // rd
$write(" 6"); phase=6; #1 expect (9'b010000000); // rd
$write(" 7"); phase=7; #1 expect (9'b010000010); // rd, ld_ac
$write("\n");
$write("Testing opcode LDA phase"); opcode=LDA;
$write(" 0"); phase=0; #1 expect (9'b100000000); // sel
$write(" 1"); phase=1; #1 expect (9'b110000000); // sel, rd
$write(" 2"); phase=2; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 3"); phase=3; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 4"); phase=4; #1 expect (9'b000100000); // inc_pc
$write(" 5"); phase=5; #1 expect (9'b010000000); // rd
$write(" 6"); phase=6; #1 expect (9'b010000000); // rd
$write(" 7"); phase=7; #1 expect (9'b010000010); // rd, ld_ac
$write("\n");
$write("Testing opcode STO phase"); opcode=STO;
$write(" 0"); phase=0; #1 expect (9'b100000000); // sel
$write(" 1"); phase=1; #1 expect (9'b110000000); // sel, rd
$write(" 2"); phase=2; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 3"); phase=3; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 4"); phase=4; #1 expect (9'b000100000); // inc_pc
$write(" 5"); phase=5; #1 expect (9'b000000000); //
$write(" 6"); phase=6; #1 expect (9'b000000100); // data_e
$write(" 7"); phase=7; #1 expect (9'b000000101); // data_e, wr
$write("\n");
$write("Testing opcode JMP phase"); opcode=JMP;
$write(" 0"); phase=0; #1 expect (9'b100000000); // sel
$write(" 1"); phase=1; #1 expect (9'b110000000); // sel, rd
$write(" 2"); phase=2; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 3"); phase=3; #1 expect (9'b111000000); // sel, rd, ld_ir
$write(" 4"); phase=4; #1 expect (9'b000100000); // inc_pc
$write(" 5"); phase=5; #1 expect (9'b000000000); //
$write(" 6"); phase=6; #1 expect (9'b000001000); // ld_pc
$write(" 7"); phase=7; #1 expect (9'b000001000); // ld_pc
$display("\nTEST PASSED");
$finish;
end
endmodule
Register
data_in과 data_out은 둘 다 8bit signal을 가진다.
rst는 active high에서 동기화 된다.
register는 clk rising edge를 기준으로 동작한다.
만약 load가 1(high) 신호면 data_in의 데이터는 data_out으로 전달됩니다.
그렇지 않으면 data_out의 현재값이 레지스터에 남는다.
reigster.v
module register #(parameter WIDTH = 8) (
input [WIDTH-1:0] data_in,
input load,
input clk,
input rst,
output reg [WIDTH-1:0] data_out
);
always @(negedge clk or posedge rst) begin
if(rst) begin
data_out <= 8'h00;
end
else begin
if(load == 1) begin
data_out <= data_in;
end
end
end
endmodule
register_test.v
module register_test;
localparam WIDTH=8;
reg clk ;
reg rst ;
reg load ;
reg [WIDTH-1:0] data_in;
wire [WIDTH-1:0] data_out;
register
#(
.WIDTH ( WIDTH )
)
register_inst
(
.clk ( clk ),
.rst ( rst ),
.load ( load ),
.data_in ( data_in ),
.data_out ( data_out )
);
task expect;
input [WIDTH-1:0] exp_out;
if (data_out !== exp_out) begin
$display("TEST FAILED");
$display("At time %0d rst=%b load=%b data_in=%b data_out=%b",
$time, rst, load, data_in, data_out);
$display("data_out should be %b", exp_out);
$finish;
end
else begin
$display("At time %0d rst=%b load=%b data_in=%b data_out=%b",
$time, rst, load, data_in, data_out);
end
endtask
initial repeat (5) begin #5 clk=1; #5 clk=0; end
initial @(negedge clk) begin
rst=0; load=1; data_in=8'h55; @(negedge clk) expect (8'h55);
rst=0; load=1; data_in=8'hAA; @(negedge clk) expect (8'hAA);
rst=0; load=1; data_in=8'hFF; @(negedge clk) expect (8'hFF);
rst=1; load=1; data_in=8'hFF; @(negedge clk) expect (8'h00);
$display("TEST PASSED");
$finish;
end
endmodule
Memory
addr은 5 bit data는 8 bit의 크기를 가진다.
wr(write) 와 rd(read)는 1bit input을 가진다.
memory는 clk의 rising edge에서 동작한다.
memory.v
module memory #(parameter AWIDTH = 5, DWIDTH = 8) (
input [AWIDTH-1:0] addr,
input clk,
input wr,
input rd,
inout [DWIDTH-1:0] data
);
reg [DWIDTH-1:0] rdata;
reg [DWIDTH-1:0] memory_data [0:31];
always @(posedge clk) begin
if (wr) begin
memory_data[addr] <= data;
end
end
assign data = rd ? memory_data[addr] : 8'bz;
endmodule
memory_test.v
module memory_test;
localparam integer AWIDTH=5;
localparam integer DWIDTH=8;
reg clk ;
reg wr ;
reg rd ;
reg [AWIDTH-1:0] addr ;
wire [DWIDTH-1:0] data ;
reg [DWIDTH-1:0] rdata ;
assign data=rdata;
memory
#(
.AWIDTH ( AWIDTH ),
.DWIDTH ( DWIDTH )
)
memory_inst
(
.clk ( clk ),
.wr ( wr ),
.rd ( rd ),
.addr ( addr ),
.data ( data )
);
task expect;
input [DWIDTH-1:0] exp_data;
if (data !== exp_data) begin
$display("TEST FAILED");
$display("At time %0d addr=%b data=%b", $time, addr, data);
$display("data should be %b", exp_data);
$finish;
end
else begin
$display("At time %0d addr=%b data=%b", $time, addr, data);
end
endtask
initial repeat (67) begin #5 clk=1; #5 clk=0; end
initial @(negedge clk) begin : TEST
reg [AWIDTH-1:0] addr;
reg [DWIDTH-1:0] data;
addr=0; data=-1;
$display("Writing addr=%b data=%b",addr,data);
wr=1; rd=0; memory_test.addr=addr; rdata=data; @(negedge clk);
addr=-1; data=0;
$display("Writing addr=%b data=%b",addr,data);
wr=1; rd=0; memory_test.addr=addr; rdata=data; @(negedge clk);
addr=0; data=-1;
$display("Reading addr=%b data=%b",addr,data);
wr=0; rd=1; memory_test.addr=addr; rdata='bz; @(negedge clk) expect(data);
addr=-1; data=0;
$display("Reading addr=%b data=%b",addr,data);
wr=0; rd=1; memory_test.addr=addr; rdata='bz; @(negedge clk) expect(data);
$display("Writing ascending data to descending addresses");
addr=-1; data=0;
while ( addr ) begin
wr=1; rd=0; memory_test.addr=addr; rdata=data; @(negedge clk);
addr=addr-1;
data=data+1;
end
$display("Reading ascending data from descending addresses");
addr=-1; data=0;
while ( addr ) begin
wr=0; rd=1; memory_test.addr=addr; rdata='bz; @(negedge clk) expect(data);
addr=addr-1;
data=data+1;
end
$display("TEST PASSED");
$finish;
end
endmodule
* always의 sensitivity
1. always의 sensitivity list의 값이 *일 경우 입력값에 memory_data[addr], data 값이 변할 때 마다 always 문이 실행되게 된다.
위 그림을 보게 되면 clk에 상관없이 memory_data[addr], data 값이 변할 때 마다 데이터가 저장되는 것을 확인할 수 있다.
2.
Counter
Counter는 clk의 rising edge에 맞춰 동작한다.
rst는 active high에서 동작한다.
cnt_in과 cnt_out은 둘 다 5bit 신호를 가진다.
만약 rst가 high(1)일 경우 output은 0의 값을 가진다.
만약 load가 high(1)일 경우 cnt_in에 counter값이 가져와진다.
그렇지 않고, enab가 high(1)이면 cnt_out은 증가되고 cnt_out은 변하지 않는다.
counter.v
module counter #(parameter WIDTH = 5) (
input [WIDTH-1:0] cnt_in,
input enab,
input load,
input clk,
input rst,
output reg [WIDTH-1:0] cnt_out
);
always @(posedge clk or posedge rst) begin
if(rst) begin
cnt_out <= 0;
end
else begin
if(load) begin
cnt_out <= cnt_in;
end
else begin
if(enab) begin
cnt_out <= cnt_out + 1;
end
end
end
end
endmodule
counter_test.v
module counter_test;
localparam WIDTH=5;
reg clk ;
reg rst ;
reg load ;
reg enab ;
reg [WIDTH-1:0] cnt_in;
wire [WIDTH-1:0] cnt_out;
counter
#(
.WIDTH ( WIDTH )
)
counter_inst
(
.clk ( clk ),
.rst ( rst ),
.load ( load ),
.enab ( enab ),
.cnt_in ( cnt_in ),
.cnt_out ( cnt_out )
);
task expect;
input [WIDTH-1:0] exp_out;
if (cnt_out !== exp_out) begin
$display("TEST FAILED");
$display("At time %0d rst=%b load=%b enab=%b cnt_in=%b cnt_out=%b",
$time, rst, load, enab, cnt_in, cnt_out);
$display("cnt_out should be %b", exp_out);
$finish;
end
else begin
$display("At time %0d rst=%b load=%b enab=%b cnt_in=%b cnt_out=%b",
$time, rst, load, enab, cnt_in, cnt_out);
end
endtask
initial repeat (7) begin #5 clk=1; #5 clk=0; end
initial @(negedge clk) begin
rst=0; load=1; enab=1; cnt_in=5'h15; @(negedge clk) expect (5'h15);
rst=0; load=1; enab=1; cnt_in=5'h0A; @(negedge clk) expect (5'h0A);
rst=0; load=1; enab=1; cnt_in=5'h1F; @(negedge clk) expect (5'h1F);
rst=1; load=1; enab=1; cnt_in=5'h1F; @(negedge clk) expect (5'h00);
rst=0; load=1; enab=1; cnt_in=5'h1F; @(negedge clk) expect (5'h1F);
rst=0; load=0; enab=1; cnt_in=5'h1F; @(negedge clk) expect (5'h00);
$display("TEST PASSED");
$finish;
end
endmodule
RISC
위에서 생성한 Multiplexor~Counter까지 전부 RISC를 구성하는 요소에 해당한다.
risc.v를 top 모듈로 사용하고 위에서 생성한 모듈들을 인스턴스화 하여 RISC 구조가 동작하도록 되어있다.
Test Bench를 이용하여 Multiplexor~Counter까지 모듈들이 정상적으로 동작하는지 확인할 수 있다.
risc.v
module risc
(
input wire clk,
input wire rst,
output wire halt
);
localparam integer AWIDTH=5, DWIDTH=8 ;
/////////////////////
// CLOCK GENERATOR //
/////////////////////
wire [2:0] phase ;
counter
#(
.WIDTH ( 3 )
)
counter_clk
(
.clk ( clk ),
.rst ( rst ),
.load ( 1'b0 ),
.enab ( !halt ),
.cnt_in ( 3'b0 ),
.cnt_out ( phase )
) ;
////////////////
// CONTROLLER //
////////////////
wire [2:0] opcode ;
controller controller_inst
(
.opcode ( opcode ), // operation code
.phase ( phase ), // instruction phase
.zero ( zero ), // accumulator is zero
.sel ( sel ), // select instruction address to memory
.rd ( rd ), // enable memory output onto data bus
.ld_ir ( ld_ir ), // load instruction register
.inc_pc ( inc_pc ), // increment program counter
.halt ( halt ), // halt machine
.ld_pc ( ld_pc ), // load program counter
.data_e ( data_e ), // enable accumulator output onto data bus
.ld_ac ( ld_ac ), // load accumulator from data bus
.wr ( wr ) // write data bus to memory
) ;
/////////////////////
// PROGRAM COUNTER //
/////////////////////
wire [AWIDTH-1:0] ir_addr, pc_addr ;
counter
#(
.WIDTH ( AWIDTH )
)
counter_pc
(
.clk ( clk ),
.rst ( rst ),
.load ( ld_pc ),
.enab ( inc_pc ),
.cnt_in ( ir_addr ),
.cnt_out ( pc_addr )
) ;
/////////////////////
// ADDESS SELECTOR //
/////////////////////
wire [AWIDTH-1:0] addr ;
multiplexor
#(
.WIDTH ( AWIDTH )
)
address_mux
(
.sel ( sel ),
.in0 ( ir_addr ),
.in1 ( pc_addr ),
.mux_out ( addr )
) ;
/////////////////////////
// DATA/PROGRAM MEMORY //
/////////////////////////
wire [DWIDTH-1:0] data ;
memory
#(
.AWIDTH ( AWIDTH ),
.DWIDTH ( DWIDTH )
)
memory_inst
(
.clk ( clk ),
.wr ( wr ),
.rd ( rd ),
.addr ( addr ),
.data ( data )
) ;
//////////////////////////
// INSTRUCTION REGISTER //
//////////////////////////
register
#(
.WIDTH ( DWIDTH )
)
register_ir
(
.clk ( clk ),
.rst ( rst ),
.load ( ld_ir ),
.data_in ( data ),
.data_out ( {opcode,ir_addr} )
) ;
////////////////////////
// ARITHMETIC & LOGIC //
////////////////////////
wire [DWIDTH-1:0] acc_out, alu_out ;
alu
#(
.WIDTH ( DWIDTH )
)
alu_inst
(
.opcode ( opcode ),
.in_a ( acc_out ),
.in_b ( data ),
.a_is_zero ( zero ),
.alu_out ( alu_out )
) ;
//////////////////////////
// ACCUMULATOR REGISTER //
//////////////////////////
register
#(
.WIDTH ( DWIDTH )
)
register_ac
(
.clk ( clk ),
.rst ( rst ),
.load ( ld_ac ),
.data_in ( alu_out ),
.data_out ( acc_out )
) ;
//////////////////////////
// BUS DRIVER //
//////////////////////////
driver
#(
.WIDTH ( DWIDTH )
)
driver_inst
(
.data_en ( data_e ),
.data_in ( alu_out ),
.data_out ( data )
) ;
endmodule
risc_test.v
module risc_test;
reg clk ;
reg rst ;
wire halt ;
risc risc_inst (clk, rst, halt) ;
task clock (input integer number);
repeat (number) begin clk=0; #1; clk=1; #1; end
endtask
task reset;
begin
rst = 1; clock(1);
rst = 0; clock(1);
end
endtask
task expect (input exp_halt);
if (halt !== exp_halt) begin
$display("TEST FAILED");
$display("time=%0d: halt is %b and should be %b",
$time, halt, exp_halt);
$finish;
end
endtask
localparam [2:0] HLT=0, SKZ=1, ADD=2, AND=3, XOR=4, LDA=5, STO=6, JMP=7;
reg [7:0] test;
initial begin
$display("Testing reset");
risc_inst.memory_inst.array[0] = { HLT, 5'bx };
reset;
expect(0);
$display("Testing HLT instruction");
risc_inst.memory_inst.array[0] = { HLT, 5'bx };
reset;
clock(2); expect(0); clock(1); expect(1);
// TO DO: TEST THE "JMP" INSTRUCTION. MEMORY LOCATIONS 0 and 1 WILL BOTH
// CONTAIN AN INSTRUCTION TO JUMP TO LOCATION 2. MEMORY LOCATION 2
// WILL CONTAIN A "HALT" INSTRUCTION. IF THE "JMP" INSTRUCTION
// WORKS THEN THE HALT WILL OCCUR AFTER A TOTAL OF 11 CLOCKS.
$display("Testing JMP instruction");
risc_inst.memory_inst.array[0] = { JMP, 5'd2 };
risc_inst.memory_inst.array[1] = { JMP, 5'd2 };
risc_inst.memory_inst.array[2] = { HLT, 5'bx };
reset;
clock(10); expect(0); clock(1); expect(1);
// TO DO: TEST THE "SKZ" INSTRUCTION. MEMORY LOCATION 0 WILL CONTAIN AN
// INSTRUCTION TO SKIP THE NEXT INSTRUCTION IF THE ACCUMULATOR IS
// ZERO (IT IS). MEMORY LOCATION 1 WILL CONTAIN AN INSTRUCTION TO
// JUMP TO LOCATION 2. MEMORY LOCATION 2 WILL CONTAIN A "HALT"
// INSTRUCTION. IF THE "SKZ" INSTRUCTION WORKS THEN THE HALT WILL
// OCCUR AFTER A TOTAL OF 11 CLOCKS.
$display("Testing SKZ instruction");
risc_inst.memory_inst.array[0] = { SKZ, 5'bx };
risc_inst.memory_inst.array[1] = { JMP, 5'd2 };
risc_inst.memory_inst.array[2] = { HLT, 5'bx };
reset;
clock(10); expect(0); clock(1); expect(1);
$display("Testing LDA instruction");
risc_inst.memory_inst.array[0] = { LDA, 5'd5 };
risc_inst.memory_inst.array[1] = { SKZ, 5'bx };
risc_inst.memory_inst.array[2] = { HLT, 5'bx };
risc_inst.memory_inst.array[3] = { JMP, 5'd4 };
risc_inst.memory_inst.array[4] = { HLT, 5'bx };
risc_inst.memory_inst.array[5] = { 8'd1 };
reset;
clock(18); expect(0); clock(1); expect(1);
$display("Testing STO instruction");
risc_inst.memory_inst.array[0] = { LDA, 5'd7 };
risc_inst.memory_inst.array[1] = { STO, 5'd8 };
risc_inst.memory_inst.array[2] = { LDA, 5'd8 };
risc_inst.memory_inst.array[3] = { SKZ, 5'bx };
risc_inst.memory_inst.array[4] = { HLT, 5'bx };
risc_inst.memory_inst.array[5] = { JMP, 5'd6 };
risc_inst.memory_inst.array[6] = { HLT, 5'bx };
risc_inst.memory_inst.array[7] = { 8'd1 };
risc_inst.memory_inst.array[8] = { 8'd0 };
reset;
clock(34); expect(0); clock(1); expect(1);
$display("Testing AND instruction");
risc_inst.memory_inst.array[0] = { LDA, 5'd10};
risc_inst.memory_inst.array[1] = { AND, 5'd11};
risc_inst.memory_inst.array[2] = { SKZ, 5'bx };
risc_inst.memory_inst.array[3] = { JMP, 5'd5 };
risc_inst.memory_inst.array[4] = { HLT, 5'bx };
risc_inst.memory_inst.array[5] = { AND, 5'd12};
risc_inst.memory_inst.array[6] = { SKZ, 5'bx };
risc_inst.memory_inst.array[7] = { HLT, 5'bx };
risc_inst.memory_inst.array[8] = { JMP, 5'd9 };
risc_inst.memory_inst.array[9] = { HLT, 5'bx };
risc_inst.memory_inst.array[10]= { 8'hff};
risc_inst.memory_inst.array[11]= { 8'h01};
risc_inst.memory_inst.array[12]= { 8'hfe};
reset;
clock(58); expect(0); clock(1); expect(1);
$display("Testing XOR instruction");
risc_inst.memory_inst.array[0] = { LDA, 5'd10}; // 1
risc_inst.memory_inst.array[1] = { XOR, 5'd11}; // 2
risc_inst.memory_inst.array[2] = { SKZ, 5'bx }; // 3
risc_inst.memory_inst.array[3] = { JMP, 5'd5 }; // 4
risc_inst.memory_inst.array[4] = { HLT, 5'bx };
risc_inst.memory_inst.array[5] = { XOR, 5'd12}; // 5
risc_inst.memory_inst.array[6] = { SKZ, 5'bx }; // 6
risc_inst.memory_inst.array[7] = { HLT, 5'bx };
risc_inst.memory_inst.array[8] = { JMP, 5'd9 }; // 7
risc_inst.memory_inst.array[9] = { HLT, 5'bx }; // 8
risc_inst.memory_inst.array[10]= { 8'h55};
risc_inst.memory_inst.array[11]= { 8'h54};
risc_inst.memory_inst.array[12]= { 8'h01};
reset;
clock(58); expect(0); clock(1); expect(1);
$display("Testing ADD instruction");
risc_inst.memory_inst.array[0] = { LDA, 5'd9 }; // 1
risc_inst.memory_inst.array[1] = { ADD, 5'd11}; // 2
risc_inst.memory_inst.array[2] = { SKZ, 5'bx }; // 3
risc_inst.memory_inst.array[3] = { HLT, 5'bx };
risc_inst.memory_inst.array[4] = { ADD, 5'd11}; // 4
risc_inst.memory_inst.array[5] = { SKZ, 5'bx }; // 5
risc_inst.memory_inst.array[6] = { HLT, 5'bx }; // 6
risc_inst.memory_inst.array[7] = { JMP, 5'd9 };
risc_inst.memory_inst.array[8] = { HLT, 5'bx };
risc_inst.memory_inst.array[9] = { 8'hff};
risc_inst.memory_inst.array[11]= { 8'h01};
reset;
clock(42); expect(0); clock(1); expect(1);
for (test=1; test<=3; test=test+1) begin : TESTS
integer clocks;
reg [1:12*8] testfile ;
testfile = { "CPUtest", 8'h30+test, ".txt" } ;
$readmemb ( testfile, risc_inst.memory_inst.array ) ;
$display("Doing test %s", testfile);
case ( test )
1: clocks=138;
2: clocks=106;
3: clocks=938;
endcase
reset;
clock(clocks); expect(0); clock(1); expect(1);
end
$display("TEST PASSED");
$finish;
end
endmodule
Test Code를 보게 되면 입력에 대해 Memory Register의 각 비트가 의미하는 데이터를 해석하여 Opcode로 변환 시켜 상황에 맞게 동작하고 있다.
localparam [2:0] HLT=0, SKZ=1, ADD=2, AND=3, XOR=4, LDA=5, STO=6, JMP=7;
코드에서 선언한 Opcode에 맞게 위 코드들이 실행되며 각자 역할을 수행하게 된다.
'Semiconductor > RTL, Simulation' 카테고리의 다른 글
RTL - SRAM (`ifdef를 이용한 FPGA, ASIC 코드 분리) (0) | 2024.08.06 |
---|---|
RTL - SPI (Master, Slave, FSM) (2) FSM, MASTER, SLAVE & SLVAE 활용 (0) | 2024.06.02 |
RTL - SPI (Master, Slave, FSM) (1) FSM, SLAVE (0) | 2024.06.02 |
Xcelium - (Verilog 파일 전송 및 Xcelium 실행) (2) | 2024.04.03 |
RTL - n비트 양방향 시프트 레지스터 (0) | 2024.03.30 |