이전 글에서 CPU의 구조와 작동원리에 대해 알아보았습니다.
이전글: https://metastable.tistory.com/28
이번에는 이론을 바탕으로 CPU를 Verilog로 설계하였는데요,
처음부터 깊게 들어가면 너무 어렵기 때문에 간단한 16 bit CPU를 설계하도록 하겠습니다.
시작 전에 말씀드리자면 2년 전 베릴로그가 익숙하지 않을 때 작성하여서
오류랑 잘못된 코딩스타일이 많을 수 있습니다..
(ex. reset, latch 생기는 코딩 등)
추후 시간이 나면 한 번 정리해서 올리도록 하겠습니다.
참고로 여기서 사용한 메모리는 앞에서 설계한 sram을 instance하여 이용하였습니다.
RISC-V Instruction set이나 GitHub에 있는 코드를 한 번 읽어보는 것도 좋을 것 같습니다.
전체 코드를 보실 분들은 맨 밑을 참고해주세요!
※ 2023.05.16 : Diagram 추가
조금 더 예쁘고 자세하게 수정해 보았습니다.
Verilog Code
DUT
`timescale 1ns / 1ps
module cpu(clk, reset, DR, AC, IR, AR, PC);
input clk, reset;
output reg [15:0] DR, AC, IR;
output reg [11:0] AR, PC;
reg [3:0] sc; //Sequence Counter
reg [15:0] T; //output of decoder for Sequence Counter
reg [7:0] D; //output of decoder for OPCODE
reg I; //flipflop for IR[15]
reg E; //register
Input은 CLK와 RST만 사용하였습니다.
특수 목적 레지스터는 DR, AC, IR, AR, PC 5가지를 사용하였습니다.
always @ (negedge reset) begin //reset
if(!reset) begin
DR <= 0; //register reset
AC <= 0;
IR <= 0;
AR <= 0;
PC <= 0;
for(j = 0; j < 4096; j = j + 1) begin //SRAM reset
sync_sram.mem[j] <= 4'h0000;
end
end
end
코드를 짠 지 좀 되었는데 reset을 따로 빼놓는 식으로 짰더라구요
지금이랑 스타일이 다른데 이런 식으로 짜도 되는지는 모르겠네요..
always @ (posedge clk or negedge reset) begin //Sequence counter
if(!reset) begin
sc <= 0;
end
else begin
sc <= sc + 4'b0001;
end
end
Sequence Counter는 클락에 맞추어 항상 돌아가도록 설정하였습니다.
always @ (sc) begin //4 to 16 decoder for SC
case(sc)
4'b0000: T <= 16'b0000_0000_0000_0001; //t0
4'b0001: T <= 16'b0000_0000_0000_0010; //t1
4'b0010: T <= 16'b0000_0000_0000_0100; //t2
4'b0011: T <= 16'b0000_0000_0000_1000; //t3
4'b0100: T <= 16'b0000_0000_0001_0000; //t4
4'b0101: T <= 16'b0000_0000_0010_0000; //t5
4'b0110: T <= 16'b0000_0000_0100_0000; //t6
default: T <= 16'b0000_0000_0000_0000;
endcase
end
CPU는 T0부터 Tn까지 동작하는데 제가 설계한 cpu는 T6까지만 동작합니다.
따라서 T6까지 one hot code 스타일로 표현해주었습니다.
always @ (IR) begin //3 to 8 decoder for opcode
case(IR[14:12])
3'b000: D <= 8'b0000_0001; //D0
3'b001: D <= 8'b0000_0010;
3'b010: D <= 8'b0000_0100;
3'b011: D <= 8'b0000_1000;
3'b100: D <= 8'b0001_0000;
3'b101: D <= 8'b0010_0000;
3'b110: D <= 8'b0100_0000;
3'b111: D <= 8'b1000_0000; //D7
default: D <= 8'b0000_0000;
endcase
end
명령어의 opcode 부분은 디코더를 거쳐 갑니다.
else if(T[3] == 1) begin //t3
if(D[7] == 1 && I == 0) begin //I'D7: register-reference instruction
if(AR == 12'b1000_0000_0000) begin //CLA
AC <= 0;
end
else if(AR == 12'b0100_0000_0000) begin //CLE
E <= 0;
end
else if(AR == 12'b0010_0000_0000) begin //CMA
AC <= ~AC;
end
else if(AR == 12'b0001_0000_0000) begin //CME
E <= ~E;
end
else if(AR == 12'b0000_1000_0000) begin //CIR
AC <= AC >> 1;
AC[15] <= E;
E <= AC[0];
end
else if(AR == 12'b0000_0100_0000) begin //CIL
AC <= AC << 1;
AC[0] <= E;
E <= AC[15];
end
else if(AR == 12'b0000_0010_0000) begin //INC
AC <= AC + 16'b0000_0000_0000_0001;
end
else if(AR == 12'b0000_0001_0000) begin //SPA
if(AC[15] == 0)
PC <= PC + 1;
end
else if(AR == 12'b0000_0000_1000) begin //SNA
if(AC[15] == 1)
PC <= PC + 1;
end
else if(AR == 12'b0000_0000_0100) begin //SZA
if(AC == 0)
PC <= PC + 1;
end
else if(AR == 12'b0000_0000_0010) begin //SZE
if(E == 0)
PC <= PC + 1;
end
else if(AR == 12'b0000_0000_0001) begin //MOV
DR <= AC;
end
sc <= 0; //reset sc at end of register-reference instruction
end
else if(D[7] == 0 && I == 1) begin //D7'IT3: indirect address
AR <= sync_sram.mem[AR][11:0];
end
register-reference 부분입니다.
else if(T[4] == 1) begin //T4, memory-reference instruction
if(D[0] == 1) begin //AND
DR <= sync_sram.mem[AR];
end
else if(D[1] == 1) begin //ADD
DR <= sync_sram.mem[AR];
end
else if(D[2] == 1) begin //LDA
DR <= sync_sram.mem[AR];
end
else if(D[3] == 1) begin //STA
sync_sram.mem[AR][11:0] <= AC;
sc <= 0;
end
else if(D[4] == 1) begin //BUN
PC <= AR;
sc <= 0;
end
else if(D[5] == 1) begin //BSA
sync_sram.mem[AR][11:0] <= PC;
AR <= AR + 1;
end
else if(D[6] == 1) begin //ISZ
DR <= sync_sram.mem[AR];
end
end
else if(T[5] == 1) begin //T5
if(D[0] == 1) begin //AND
AC <= AC ^ DR;
sc <= 0;
end
else if(D[1] == 1) begin //ADD
{E, AC} <= AC + DR;
sc <= 0;
end
else if(D[2] == 1) begin //LDA
AC <= DR;
sc <= 0;
end
else if(D[5] == 1) begin //BSA
PC <= AR;
sc <= 0;
end
else if(D[6] == 1) begin //ISZ
DR <= DR + 1;
end
end
else if(T[6] == 1) begin //T6
if(D[6] == 1) begin //ISZ
sync_sram.mem[AR] <= DR;
if(DR == 0) begin
PC <= PC + 1;
end
sc <= 0;
end
end
end
memory-reference 부분입니다.
제가 설정한 Instruction에 따르면 최대 T6까지 동작합니다.
Testbench
`timescale 1ns / 1ps
module tb_cpu();
parameter word_width = 16, word_depth = 4096, addr_width = 12;
reg clk, we, reset;
reg [addr_width-1:0] addr;
reg [word_width-1:0] sram_in;
wire [word_width-1:0] sram_out;
wire [15:0] DR, AC, IR;
wire [11:0] AR, PC;
integer file_cpu_out;
sync_sram sram01(addr, sram_in, sram_out, clk, we);
cpu cpu01(clk, reset, DR, AC, IR, AR, PC);
always #5 clk = ~clk;
initial begin
clk = 1'b0; reset = 1'b1; we = 1'b0;
file_cpu_out = $fopen("result.txt", "w");
#1
reset = 1'b0; #1
reset = 1'b1; #1
$readmemb("instruction.txt", sram01.mem);
sram01.mem[100] = 16'b1100_0100_1010_0100;
#230
$fdisplay(file_cpu_out, "%b", AC);
$fclose(file_cpu_out);
$finish;
end
endmodule
우선 sram에 실행할 명령어를 0번지부터 넣어주었습니다.
넣어준 명령어는 다음과 같습니다.
그리고 100번지에는 1100_0100_1010_0100 (C4A4) 을 넣어주었습니다.
먼저 손으로 계산해 볼까요?
AC | E | |
초기값 | 0000 | 0 |
LDA 이후 | C4A4 | 0 |
CMA 이후 | 3B5B | 0 |
CME 이후 | 3B5B | 1 |
CIR 이후 | 9DAD | 1 |
INC 이후 | 9DAE | 1 |
따라서 출력은 9DAE가 나와야 합니다.
이제 시뮬레이션을 통해 값이 잘 나왔는지 확인해보겠습니다.
Simulation
사진이 잘 보이지 않으면 클릭 부탁드려요!
Waveform을 보면 동작 사이클에 맞춰서 잘 돌아가는 것을 볼 수 있습니다.
각각의 명령어를 잘 수행한 후 9DAE가 AC에 저장되었습니다.
$fopen으로 만든 파일에도 값이 출력된 것을 확인할 수 있습니다.
전체 Verilog code
`timescale 1ns / 1ps
module cpu(clk, reset, DR, AC, IR, AR, PC);
input clk, reset;
output reg [15:0] DR, AC, IR;
output reg [11:0] AR, PC;
reg [3:0] sc; //Sequence Counter
reg [15:0] T; //output of decoder for Sequence Counter
reg [7:0] D; //output of decoder for OPCODE
reg I; //flipflop for IR[15]
reg E; //register
integer j;
always @ (negedge reset) begin //reset
if(!reset) begin
DR <= 0; //register reset
AC <= 0;
IR <= 0;
AR <= 0;
PC <= 0;
E <= 0;
for(j = 0; j < 4096; j = j + 1) begin //SRAM reset
sync_sram.mem[j] <= 4'h0000;
end
end
end
always @ (posedge clk or negedge reset) begin //Sequence counter
if(!reset) begin
sc <= 0;
end
else begin
sc <= sc + 4'b0001;
end
end
always @ (sc) begin //4 to 16 decoder for SC
case(sc)
4'b0000: T <= 16'b0000_0000_0000_0001; //t0
4'b0001: T <= 16'b0000_0000_0000_0010; //t1
4'b0010: T <= 16'b0000_0000_0000_0100; //t2
4'b0011: T <= 16'b0000_0000_0000_1000; //t3
4'b0100: T <= 16'b0000_0000_0001_0000; //t4
4'b0101: T <= 16'b0000_0000_0010_0000; //t5
4'b0110: T <= 16'b0000_0000_0100_0000; //t6
default: T <= 16'b0000_0000_0000_0000;
endcase
end
always @ (IR) begin //3 to 8 decoder for opcode
case(IR[14:12])
3'b000: D <= 8'b0000_0001; //D0
3'b001: D <= 8'b0000_0010;
3'b010: D <= 8'b0000_0100;
3'b011: D <= 8'b0000_1000;
3'b100: D <= 8'b0001_0000;
3'b101: D <= 8'b0010_0000;
3'b110: D <= 8'b0100_0000;
3'b111: D <= 8'b1000_0000; //D7
default: D <= 8'b0000_0000;
endcase
end
always @ (posedge clk) begin
if(T[0] == 1) begin //t0
AR <= PC;
end
else if(T[1] == 1) begin //t1
IR <= sync_sram.mem[AR];
PC <= PC + 3'h001;
end
else if(T[2] == 1) begin //t2
AR <= IR[11:0];
I <= IR[15];
end
else if(T[3] == 1) begin //t3
if(D[7] == 1 && I == 0) begin //I'D7: register-reference instruction
if(AR == 12'b1000_0000_0000) begin //CLA
AC <= 0;
end
else if(AR == 12'b0100_0000_0000) begin //CLE
E <= 0;
end
else if(AR == 12'b0010_0000_0000) begin //CMA
AC <= ~AC;
end
else if(AR == 12'b0001_0000_0000) begin //CME
E <= ~E;
end
else if(AR == 12'b0000_1000_0000) begin //CIR
AC <= AC >> 1;
AC[15] <= E;
E <= AC[0];
end
else if(AR == 12'b0000_0100_0000) begin //CIL
AC <= AC << 1;
AC[0] <= E;
E <= AC[15];
end
else if(AR == 12'b0000_0010_0000) begin //INC
AC <= AC + 16'b0000_0000_0000_0001;
end
else if(AR == 12'b0000_0001_0000) begin //SPA
if(AC[15] == 0)
PC <= PC + 1;
end
else if(AR == 12'b0000_0000_1000) begin //SNA
if(AC[15] == 1)
PC <= PC + 1;
end
else if(AR == 12'b0000_0000_0100) begin //SZA
if(AC == 0)
PC <= PC + 1;
end
else if(AR == 12'b0000_0000_0010) begin //SZE
if(E == 0)
PC <= PC + 1;
end
else if(AR == 12'b0000_0000_0001) begin //MOV
DR <= AC;
end
sc <= 0; //reset sc at end of register-reference instruction
end
else if(D[7] == 0 && I == 1) begin //D7'IT3: indirect address
AR <= sync_sram.mem[AR];
end
end
else if(T[4] == 1) begin //T4, memory-reference instruction
if(D[0] == 1) begin //AND
DR <= sync_sram.mem[AR];
end
else if(D[1] == 1) begin //ADD
DR <= sync_sram.mem[AR];
end
else if(D[2] == 1) begin //LDA
DR <= sync_sram.mem[AR];
end
else if(D[3] == 1) begin //STA
sync_sram.mem[AR] <= AC;
sc <= 0;
end
else if(D[4] == 1) begin //BUN
PC <= AR;
sc <= 0;
end
else if(D[5] == 1) begin //BSA
sync_sram.mem[AR] <= {0, PC};
AR <= AR + 1;
end
else if(D[6] == 1) begin //ISZ
DR <= sync_sram.mem[AR];
end
end
else if(T[5] == 1) begin //T5
if(D[0] == 1) begin //AND
AC <= AC ^ DR;
sc <= 0;
end
else if(D[1] == 1) begin //ADD
{E, AC} <= AC + DR;
sc <= 0;
end
else if(D[2] == 1) begin //LDA
AC <= DR;
sc <= 0;
end
else if(D[5] == 1) begin //BSA
PC <= AR;
sc <= 0;
end
else if(D[6] == 1) begin //ISZ
DR <= DR + 1;
end
end
else if(T[6] == 1) begin //T6
if(D[6] == 1) begin //ISZ
sync_sram.mem[AR] <= DR;
if(DR == 0) begin
PC <= PC + 1;
end
sc <= 0;
end
end
end
endmodule
'Digital Design > 컴퓨터구조' 카테고리의 다른 글
[컴퓨터구조] 파이프라이닝 (Pipelining) (4) | 2023.04.10 |
---|---|
[컴퓨터구조] ASIC Design Flow (2) | 2023.03.16 |
[컴퓨터구조] CPU 작동 원리 (0) | 2023.01.09 |
[메모리] SRAM에 데이터 쓰고 읽기 (Design with Verilog) (0) | 2023.01.08 |
[메모리] SRAM Full Custom Design (Layout) (0) | 2023.01.08 |
댓글