반도체, 임베디드 Study/디지털 설계

Xcelium, Verilog를 이용한 CPU RISC 구조 설계

잇(IT) 2024. 6. 2. 12:24
728x90

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 등이 있다. 


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에 맞게 위 코드들이 실행되며 각자 역할을 수행하게 된다.

728x90