본문 바로가기
Digital Design/컴퓨터구조

[컴퓨터구조] 16bit CPU 설계 (Design with Verilog)

by 스테고사우르스 2023. 1. 12.

이전 글에서 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은 CLKRST만 사용하였습니다.

 

특수 목적 레지스터는 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

댓글