본문 바로가기
# Semiconductor/- Semicon Academy

[Harman 세미콘 아카데미] 74일차 - Verilog(Program Counter, Register, Control Block, ROM, CPU Simulation)

by Graffitio 2023. 10. 16.
[Harman 세미콘 아카데미] 74일차 - Verilog(Program Counter, Register, Control Block, ROM, CPU Simulation)
728x90
반응형
[Program Counter]

 

이전 시간에 만든 N bit Half adder를 활용하여 Program counter를 만들어보자.

 

 

프로그램 카운터는 위 그림처럼 조합 회로를 구현한 LUT과 Register를 구현한 DFF로 구성되어 있다.

(조합회로에 clk 신호를 받는 DFF를 연결하여 순차적 기능 부여)

 

📌 Code

 

module register_Nbit_p_alltime #(parameter N = 8)(
        input [N-1:0] d,
        input clk, reset_p, wr_en, rd_en,
        output [N-1:0] register_data, // 상시 출력
        output [N-1:0] q
    );

    reg [N-1:0] register_temp;
    
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)register_temp = 0;
        else if(wr_en) register_temp = d;
    end

    assign q = rd_en ? register_temp : 'bz;
    assign register_data = register_temp; // enable 신호와 관계없이 무조건 출력됨.
    
endmodule

 

module program_address_counter(
    input clk, reset_p,
    input pc_inc, load_pc,
    input pc_rd_en, // pc값이 BUS로 출력될 것인지 말 것인지
    input [7:0] pc_in, // 이동할 데이터의 주소
    output [7:0] pc_out
    );
    
    // upcounter는 adder와 DFF를 합쳐서 만든다.(조합회로와 순차회로의 결합)
    wire [7:0] sum, next_addr, cur_addr;
    half_adder_N_bit #(.N(8)) ha8bit(.inc(pc_inc), .load_data(cur_addr), .sum(sum));
    
    // 다음 주소로 갈 지, 아니면 아예 다른 주소로 점프할 지 결정
    assign next_addr = load_pc ? pc_in : sum;
    
    register_Nbit_p_alltime #(.N(8)) pc_reg(.d(next_addr), .clk(clk), .reset_p(reset_p), .wr_en(1'b1), .rd_en(pc_rd_en), .register_data(cur_addr), .q(pc_out));

endmodule

 

 


 

[Register 배치]

 

📌 Code

 

CPU 모듈에 일단 레지스터들은 먼저 배치하도록 하자.

이전에 만들었던 PC와 ALU & ACC도 함께 배치하자.

 

 

module processor(
    input clk, reset_p,
    input [3:0] key_value,
    input key_valid,
    output [7:0] outreg_data,
    output [3:0] kout // key로 입력된 값을 출력(프로세스가 키 값을 제대로 받는 지 확인하기 위한 것)
    );
    
    // MAR 
    // -> PC로 BUS로 주소값을 내보내고, BUS로부터 주소값을 받는 메모리 주소 레지스터
    wire [7:0] int_bus_data, mar_data; // internal bus data
    wire mar_inen; // BUS로부터 데이터를 받는 것을 허용
    register_Nbit_p_alltime #(.N(8)) mar(
       .d(int_bus_data), .clk(clk), .reset_p(reset_p), .wr_en(mar_inen), .rd_en(1'b1), .register_data(mar_data));
    // MAR은 상시 출력만 내보내므로, 조건부 출력부는 지워도 됨.
    
    // MDR
    wire [7:0] rom_data;
    wire mdr_oen; // BUS로의 데이터 출력 enable
    register_Nbit_p_alltime #(.N(8)) mdr(
       .d(rom_data), .clk(clk), .reset_p(reset_p), .wr_en(mdr_inen), .rd_en(mdr_oen), .q(int_bus_data));
    // 얘는 버스로 데이터를 출력하는 레지스터이므로, 조건부 출력 사용
    
    // IR
    // 입력된 명령어를 저장하기 위한 레지스터
    wire [7:0] ir_data;
    wire ir_inen;
    register_Nbit_p_alltime #(.N(8)) ir(
        .d(int_bus_data), .clk(clk), .reset_p(reset_p), .wr_en(ir_inen), .rd_en(1'b1), .register_data(ir_data));
    
    // PC
    wire pc_inc, load_pc, pc_rd_en;
    program_address_counter pc(
        .clk(clk), .reset_p(reset_p), .pc_inc(pc_inc), .load_pc(load_pc), .pc_rd_en(pc_oen), .pc_in(int_bus_data), .pc_out(int_bus_data));
    
    // BREG
    wire breg_inen;
    wire [3:0] bus_reg_data;
    register_Nbit_p_alltime #(.N(8)) breg(
        .d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(breg_inen), .rd_en(1'b1), .register_data(bus_reg_data));
    
    // TMPREG
    wire tmpreg_inen, tmpreg_oen;
    register_Nbit_p_alltime #(.N(8)) tmpreg(
        .d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(tmpreg_inen), .rd_en(tmpreg_oen), .q(int_bus_data[7:4]));
    
    // CREG
    wire creg_inen, creg_oen;
    register_Nbit_p_alltime #(.N(8)) creg(
        .d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(creg_inen), .rd_en(creg_oen), .q(int_bus_data[7:4]));
    
    // DREG
    wire dreg_inen, dreg_oen;
    register_Nbit_p_alltime #(.N(8)) dreg(
        .d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(dreg_inen), .rd_en(dreg_oen), .q(int_bus_data[7:4]));    
    
    // RREG
    wire rreg_inen, rreg_oen;
    register_Nbit_p_alltime #(.N(8)) rreg
        (.d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(rreg_inen), .rd_en(rreg_oen), .q(int_bus_data[7:4]));
       
    // ALU & ACC
    wire acc_oen, acc_high_reset_p, acc_in_select;
    wire [1:0] acc_high_select, acc_low_select;
    wire op_add, op_sub, op_mul, op_div, op_and;
    wire sign_flag, zero_flag;   
    block_alu_acc alu_acc(
         .clk(clk), .reset_p(reset_p), .acc_high_reset_p(acc_high_reset_p), .rd_en(acc_oen), .acc_in_select(acc_in_select),
         .acc_high_select(acc_high_select), .acc_low_select(acc_low_select), // acc의 mode를 선택
         .op_add(op_add), .op_sub(op_sub), .op_mul(op_mul), .op_div(op_div), .op_and(op_and),
         .bus_data(int_bus_data[7:4]), .bus_reg_data(bus_reg_data),
         .sign_flag(sign_flag), .zero_flag(zero_flag),
         .acc_data(int_bus_data) // 최종적인 연산의 결과 / 얘는 BUS로만 간다.(상위 4bit, 하위 4bit)
         );
    
    // key 입력받을 레지스터
    wire inreg_oen;
    register_Nbit_p_alltime #(.N(4)) inreg 
        (.d(key_value), .clk(clk), .reset_p(reset_p), .wr_en(1'b1), .rd_en(inreg_oen), .q(int_bus_data[7:4]));
    
    // 키 값이 입력되었는지 체크하는 레지스터
    wire keych_reg_oen;
    register_Nbit_p_alltime #(.N(4)) keych_reg // key change
        (.d({4{key_valid}}), .clk(clk), .reset_p(reset_p), .wr_en(1'b1), .rd_en(keych_reg_oen), .q(int_bus_data[7:4]));
    
    // 키 값이 제대로 CPU로 입력되었는지, FND 확인하기 위한 레지스터
    wire keyout_reg_inen;
    register_Nbit_p_alltime #(.N(4)) keyout_reg // key change
        (.d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(keyout_reg_inen), .rd_en(1'b1), .register_data(kout));              
    
    // 최종 출력을 위한 레지스터
    wire outreg_inen;
    register_Nbit_p_alltime #(.N(8)) outreg
        (.d(int_bus_data), .clk(clk), .reset_p(reset_p), .wr_en(outreg_inen), .rd_en(1'b1), .register_data(outreg_data));
        
        
    // ROM
    
    // Control_block
    
            
endmodule

 

아랫 쪽에는 ROM과 Control Block을 만들어서 추가로 배치해줄 것이다.

 


 

[Control Block]

 

📌 Control Block 설명

 

 

컨트롤블록은 총 3개의 모듈로 구성된다.

1. 링카운터(몇 번째 클락인지 나누는 링카운터) - 곱하기는 8 클락, 나누기는 9 클락

2. Instruction Decoder - 명령 디코더(주어진 명령을 해석하는 디코더)

3. Control Signal - 실행 모듈(해석된 명령을 실행)

총 3단계의 과정이 반복된다.

 

CPU에 대한 좀 더 자세한 정보들

 

CPU의 구조 및 원리 - CPU의 모든 것

[CPU란?] 📌 CPU란? 중앙처리장치(CPU)는 Centrol Processing Unit의 약자로, 컴퓨터에서 명령어를 해석하고 실행하는 역할읋 하는 마이크로 프로세서이며, 명령어의 해석과 자료의 연산, 비교등의 처리를

rangvest.tistory.com

 


 

📌 Ring Counter

 

패치하는데 3클락(PC - MAD - MDR)

디코딩은 클락 필요없음(디코더는 조합회로)

실행은 최대 9클락

따라서 최대 12클락이 필요하다.

리스크는 모든 명령을 똑같은 클락 수로 처리하기 때문에(최대한 단순하게 만들기 위해서)

최대 클락을 하나의 주기로 잡아놓고 그 안에서 다 해결할 것이다.

 

module ring_counter_clk12_n(
    input clk, reset_p,
    output [11:0] t
    );

    reg [11:0] temp;
    // 12 clk마다 명령 하나씩 처리하면 된다.
    always @(negedge clk or posedge reset_p) begin
        if(reset_p) temp = 12'b0000_0000_0000;
        else if(temp == 12'b0000_0000_0000) temp = temp + 1;
        else if(temp == 12'b1000_0000_0000) temp = 12'b0000_0000_0001;
        else temp = {temp[10:0], 1'b0}; // 좌시프트
    end
    
    assign t = temp;
    
endmodule

 

모듈 하나 만들었으면, 그때 그때 테스트해주자.

 


 

📌 명령어 디코더


다음과 같은 Instruction Set을 갖는 명령어 디코더를 만들어 보자

총 32가지 명령을 수행할 수 있다.

 

 

Instruction set.pdf
0.03MB

 

어셈블리 명령을 니모닉(Mnemonic)이라고 한다.

보통은 어셈블리에서는 move 명령어를 사용한다.

어셈블리에서는 다음과 같이 작성한다. add 1 3 (1 : op code, 3 : op rand)

 

NOP : 아무 것도 안 함.

OUTB : 키입력 출력

OUT : 연산 명령을 출력

LOAD : BUS의 값을 불러올 때

JZ : 연산의 결과가 0이면, 어디로 점프하겠다.( = 연산)

JMP : 그냥 무조건 점프(예시 : while 끝난 뒤 무조건 점프)

PSAH : acc하이를 로우로 푸시

 

IR이 8bit이므로 총 256개의 명령어를 저장할 수 있지만,

우리는 어짜피 32개의 명령밖에 안 가지고 있으므로 32개만 만들어 주자.

 

// 32가지 명령을 해석할 수 있는 명령어 디코더
module instr_decoder(
    input [7:0] ir_data,
    output reg nop, outb, outs, adds_s, sub_s, and_s, div_s,
               mul_s, shl, clr_s, psah, shr, load, jz, jmp, jge,
               mov_ah_cr, mov_ah_dr, mov_tmp_ah, mov_tmp_br,
               mov_tmp_cr, mov_tmp_dr, mov_tmp_rr, mov_cr_ah, mov_cr_br,
               mov_dr_ah, mov_dr_tmp, mov_dr_br, mov_rr_ah,
               mov_key_ah, mov_inr_tmp, mov_inr_rr   
    );
    
    always @(ir_data) begin
        {nop, outb, outs, adds_s, sub_s, and_s, div_s,
         mul_s, shl, clr_s, psah, shr, load, jz, jmp, jge,
         mov_ah_cr, mov_ah_dr, mov_tmp_ah, mov_tmp_br,
         mov_tmp_cr, mov_tmp_dr, mov_tmp_rr, mov_cr_ah, mov_cr_br,
         mov_dr_ah, mov_dr_tmp, mov_dr_br, mov_rr_ah,
         mov_key_ah, mov_inr_tmp, mov_inr_rr} = 0; // 모든 명령어 묶어서 0으로 초기화
      
         case(ir_data) // ROM에 저장되는 형태는 Hexa code로 저장
            // 다 0으로 초기화해줬으므로, 하나만 1 주면 된다.
            8'h00 : nop = 1;
            8'h0B : outb = 1;
            8'h07 : outs = 1;
            8'h50 : adds_s = 1;
            8'h52 : sub_s = 1;
            8'h54 : and_s = 1;
            8'h55 : div_s = 1;
            8'h51 : mul_s = 1;
            8'h15 : shl = 1;
            8'h10 : clr_s = 1;
            8'h14 : psah = 1;
            8'h16 : shr = 1;
            8'hD6 : load = 1;
            8'hD0 : jz = 1;
            8'hD4 : jmp = 1;
            8'hD2 : jge = 1;
            8'h83 : mov_ah_cr = 1;
            8'h84 : mov_ah_dr = 1;
            8'h88 : mov_tmp_ah = 1;
            8'h8A : mov_tmp_br = 1;
            8'h8B : mov_tmp_cr = 1;
            8'h8C : mov_tmp_dr = 1;
            8'h8D : mov_tmp_rr = 1;
            8'h98 : mov_cr_ah = 1;
            8'h9A : mov_cr_br = 1;
            8'hA0 : mov_dr_ah = 1;
            8'hA1 : mov_dr_tmp = 1;
            8'hA2 : mov_dr_br = 1;
            8'hA8 : mov_rr_ah = 1;
            8'hB0 : mov_key_ah = 1;
            8'hB9 : mov_inr_tmp = 1;
            8'hBD : mov_inr_rr = 1;
         endcase 
    end
    
endmodule

 


 

📌 Control Signal

 

t는 0~11까지 총 12 clk로 구성되고, 

해당 clk에 따라서 각 명령어들이 동작한다.

Control Signal은 BUS로, BUS로부터 누가 데이터를 전송 할 지를 결정하는 역할을 한다.

 

명령별 제어신호.pdf
0.04MB

 

module control_signal(
    input [11:0] t, // 링카운터의 출력을 입력 t로 받음
    input nop, outb, outs, add_s, sub_s, and_s, div_s,
          mul_s, shl, clr_s, psah, shr, load, jz, jmp, jge,
          mov_ah_cr, mov_ah_dr, mov_tmp_ah, mov_tmp_br,
          mov_tmp_cr, mov_tmp_dr, mov_tmp_rr, mov_cr_ah, mov_cr_br,
          mov_dr_ah, mov_dr_tmp, mov_dr_br, mov_rr_ah,
          mov_key_ah, mov_inr_tmp, mov_inr_rr, zero_flag, sign_flag,
    output mar_inen, mdr_inen, mdr_oen, ir_inen, pc_inc, load_pc, pc_oen,
           breg_inen, tmpreg_inen, tmpreg_oen, creg_inen, creg_oen,
           dreg_inen, dreg_oen, rreg_inen, rreg_oen,
           acc_high_reset_p, acc_oen, acc_in_select,
           op_add, op_sub, op_mul, op_div, op_and,
           inreg_oen, keych_reg_oen, keyout_reg_inen, outreg_inen, rom_en,
    output [1:0] acc_high_select, acc_low_select
    );
    
    assign pc_oen = t[0] | (t[3] & (load | jz | jmp | jge));
    assign mar_inen = t[0] | (( load | jz | jmp | jge ) & t[3]);
    assign pc_inc = t[1]|((load|jz|jmp|jge)&t[4]);
    assign mdr_oen = t[2]|((load|(zero_flag&jz)|(~sign_flag&jge)|jmp)&t[5]);
    assign ir_inen = t[2];
    assign tmpreg_inen = (t[3]&(mov_dr_tmp|mov_inr_tmp))|(t[5]&load);
    assign tmpreg_oen = t[3]&(outb|mov_tmp_ah|mov_tmp_br|mov_tmp_cr | mov_tmp_dr|mov_tmp_rr);
    assign creg_inen = t[3]&(mov_ah_cr|mov_tmp_cr);
    assign creg_oen = t[3]&(mov_cr_ah|mov_cr_br);
    assign dreg_inen = t[3]&(mov_ah_dr|mov_tmp_dr);
    assign dreg_oen = t[3]&(mov_dr_ah|mov_dr_br|mov_dr_tmp);
    assign rreg_inen = t[3]&(mov_tmp_rr|mov_inr_rr);
    assign rreg_oen = t[3]&mov_rr_ah;
    assign breg_inen = t[3]&(mov_tmp_br|mov_cr_br|mov_dr_br);
    assign load_pc = t[5]&((zero_flag&jz)|(~sign_flag&jge)|jmp);
    assign acc_oen = t[3]&(outs|mov_ah_cr|mov_ah_dr);
    assign acc_in_select = t[3]&(mov_tmp_ah|mov_cr_ah|mov_rr_ah|mov_key_ah|mov_dr_ah);
    assign acc_high_reset_p = t[3]&clr_s;
    assign acc_high_select[1] = (t[3]&(add_s|sub_s|and_s|div_s|mul_s|shl|mov_tmp_ah|mov_cr_ah|
        mov_rr_ah|mov_key_ah|mov_dr_ah))|(mul_s&(t[5]|t[7]|t[9]))|
        (div_s&(t[4]|t[5]|t[6]|t[7]|t[8]|t[9]|t[10]));
    assign acc_high_select[0] = (t[3]&(add_s|sub_s|and_s|mul_s|shr|mov_tmp_ah|mov_cr_ah|mov_rr_ah|
        mov_key_ah|mov_dr_ah))|(t[4]&(add_s|div_s|mul_s))|
        (mul_s&(t[5]|t[6]|t[7]|t[8]|t[9]|t[10]))| (div_s&(t[6]|t[8]|t[10]));
    assign acc_low_select[1] = (t[3]&(div_s|psah|shl))|(div_s&(t[5]|t[7]|t[9]|t[11]));
    assign acc_low_select[0] = (t[3]&(psah|shr))|(t[4]&(add_s|mul_s))|(mul_s&(t[6]|t[8]|t[10]));
    assign op_add = t[3]&add_s;
    assign op_sub = t[3]&sub_s;
    assign op_and = t[3]&and_s;
    assign op_div = div_s&(t[4]|t[6]|t[8]|t[10]);
    assign op_mul = mul_s&(t[3]|t[5]|t[7]|t[9]);
    assign rom_en = ~(t[1]|((load|jz|jmp|jge)&t[4]));
    assign mdr_inen = t[1]|((load|jz|jmp|jge)&t[4]);
    assign inreg_oen = t[3]&(mov_inr_tmp|mov_inr_rr);
    assign keych_reg_oen = t[3]&mov_key_ah;
    assign outreg_inen = t[3]&outs;
    assign keyout_reg_inen = t[3]&outb;

endmodule

 


 

📌 Control Block

 

위의 3가지 모듈이 합쳐진, Control Bolck 생성

module control_block(
    input clk, reset_p,
    input [7:0] ir_data,
    input zero_flag, sign_flag,
    output mar_inen, mdr_inen, mdr_oen, ir_inen, pc_inc, load_pc, pc_oen,
           breg_inen, tmpreg_inen, tmpreg_oen, creg_inen, creg_oen,
           dreg_inen, dreg_oen, rreg_inen, rreg_oen,
           acc_high_reset_p, acc_oen, acc_in_select,
           op_add, op_sub, op_mul, op_div, op_and,
           inreg_oen, keych_reg_oen, keyout_reg_inen, outreg_inen, rom_en,
    output [1:0] acc_high_select, acc_low_select
    );
    
    wire [11:0] t;
    ring_counter_clk12_n ring_12(.clk(clk), .reset_p(reset_p), .t(t));
    
    wire nop, outb, outs, add_s, sub_s, and_s, div_s,
          mul_s, shl, clr_s, psah, shr, load, jz, jmp, jge,
          mov_ah_cr, mov_ah_dr, mov_tmp_ah, mov_tmp_br,
          mov_tmp_cr, mov_tmp_dr, mov_tmp_rr, mov_cr_ah, mov_cr_br,
          mov_dr_ah, mov_dr_tmp, mov_dr_br, mov_rr_ah,
          mov_key_ah, mov_inr_tmp, mov_inr_rr;
          
    instr_decoder decode(
          .ir_data(ir_data), .nop(nop), .outb(outb), .outs(outs), .add_s(add_s), .sub_s(sub_s), .and_s(and_s), .div_s(div_s),
          .mul_s(mul_s), .shl(shl), .clr_s(clr_s), .psah(psah), .shr(shr), .load(load), .jz(jz), .jmp(jmp), .jge(jge),
          .mov_ah_cr(mov_ah_cr), .mov_ah_dr(mov_ah_dr), .mov_tmp_ah(mov_tmp_ah), .mov_tmp_br(mov_tmp_br),
          .mov_tmp_cr(mov_tmp_cr), .mov_tmp_dr(mov_tmp_dr), .mov_tmp_rr(mov_tmp_rr), .mov_cr_ah(mov_cr_ah), .mov_cr_br(mov_cr_br),
          .mov_dr_ah(mov_dr_ah), .mov_dr_tmp(mov_dr_tmp), .mov_dr_br(mov_dr_br), .mov_rr_ah(mov_rr_ah),
          .mov_key_ah(mov_key_ah), .mov_inr_tmp(mov_inr_tmp), .mov_inr_rr(mov_inr_rr)
          );
          
    control_signal c_signal(
          .t(t), .nop(nop), .outb(outb), .outs(outs), .add_s(add_s), .sub_s(sub_s), .and_s(and_s), .div_s(div_s),
          .mul_s(mul_s), .shl(shl), .clr_s(clr_s), .psah(psah), .shr(shr), .load(load), .jz(jz), .jmp(jmp), .jge(jge),
          .mov_ah_cr(mov_ah_cr), .mov_ah_dr(mov_ah_dr), .mov_tmp_ah(mov_tmp_ah), .mov_tmp_br(mov_tmp_br),
          .mov_tmp_cr(mov_tmp_cr), .mov_tmp_dr(mov_tmp_dr), .mov_tmp_rr(mov_tmp_rr), .mov_cr_ah(mov_cr_ah), .mov_cr_br(mov_cr_br),
          .mov_dr_ah(mov_dr_ah), .mov_dr_tmp(mov_dr_tmp), .mov_dr_br(mov_dr_br), .mov_rr_ah(mov_rr_ah),
          .mov_key_ah(mov_key_ah), .mov_inr_tmp(mov_inr_tmp), .mov_inr_rr(mov_inr_rr), .zero_flag(zero_flag), .sign_flag(sign_flag),
          .mar_inen(mar_inen), .mdr_inen(mdr_inen), .mdr_oen(mdr_oen), .ir_inen(ir_inen), .pc_inc(pc_inc), .load_pc(load_pc), .pc_oen(pc_oen),
          .breg_inen(breg_inen), .tmpreg_inen(tmpreg_inen), .tmpreg_oen(tmpreg_oen), .creg_inen(creg_inen), .creg_oen(creg_oen),
          .dreg_inen(dreg_inen), .dreg_oen(dreg_oen), .rreg_inen(rreg_inen), .rreg_oen(rreg_oen),
          .acc_high_reset_p(acc_high_reset_p), .acc_oen(acc_oen), .acc_in_select(acc_in_select),
          .op_add(op_add), .op_sub(op_sub), .op_mul(op_mul), .op_div(op_div), .op_and(op_and),
          .inreg_oen(inreg_oen), .keych_reg_oen(keych_reg_oen), .keyout_reg_inen(keyout_reg_inen), .outreg_inen(outreg_inen), .rom_en(rom_en),
          .acc_high_select(acc_high_select), .acc_low_select(acc_low_select)
          );        

endmodule

 


 

[ROM]

 

 

 

IP catalog에 ROM을 만들어 주는 기능도 포함되어 있다.

사용해보자.

 

 

Depth : 256 byte

Width : 8 bit

 

 

a[7:0] : 8 bit짜리 주소

spo[7:0] : 출력

ce : chip enable

 

 

ROM의 내부 구성을 수정해주자.

 

 

RADIX는 16 bit로 설정하고,

VECTOR에는 Hexa code를 동작 순서대로 넣어준다.

동작 별 Hexa code는 아래 pdf 파일을 참조

 

cpu_4bit_계산기_프로그램.pdf
0.08MB

 

Validate & save

 

Ok Click

 

Generate

 

 

우리가 작성한 code가 들어 있는 ROM이 만들어졌다.

얘 파일 명을 보면, vhd로 되어 있다.

표준 HDL은 Verilog, VHDL로 나뉜다. 

현장에서는 Verilog를 쓰고, 학계에서는 VHDL을 썼다. 

IP catalog의 기능들 중 몇몇은 옛날에 학계에서 만들었기 때문에 VHDL로 기술되어 있다.

 


 

[Processor]

 

생성한 Control block과 ROM도 Processor 모듈에 배치

 

module processor(
    input clk, reset_p,
    input [3:0] key_value,
    input key_valid,
    output [7:0] outreg_data,
    output [3:0] kout // key로 입력된 값을 출력(프로세스가 키 값을 제대로 받는 지 확인하기 위한 것)
    );
    
    // MAR 
    // -> PC로 BUS로 주소값을 내보내고, BUS로부터 주소값을 받는 메모리 주소 레지스터
    wire [7:0] int_bus_data, mar_data; // internal bus data
    wire mar_inen; // BUS로부터 데이터를 받는 것을 허용
    register_Nbit_p_alltime #(.N(8)) mar(
       .d(int_bus_data), .clk(clk), .reset_p(reset_p), .wr_en(mar_inen), .rd_en(1'b1), .register_data(mar_data));
    // MAR은 상시 출력만 내보내므로, 조건부 출력부는 지워도 됨.
    
    // MDR
    wire [7:0] rom_data;
    wire mdr_oen; // BUS로의 데이터 출력 enable
    register_Nbit_p_alltime #(.N(8)) mdr(
       .d(rom_data), .clk(clk), .reset_p(reset_p), .wr_en(mdr_inen), .rd_en(mdr_oen), .q(int_bus_data));
    // 얘는 버스로 데이터를 출력하는 레지스터이므로, 조건부 출력 사용
    
    // IR
    // 입력된 명령어를 저장하기 위한 레지스터
    wire [7:0] ir_data;
    wire ir_inen;
    register_Nbit_p_alltime #(.N(8)) ir(
        .d(int_bus_data), .clk(clk), .reset_p(reset_p), .wr_en(ir_inen), .rd_en(1'b1), .register_data(ir_data));
    
    // PC
    wire pc_inc, load_pc, pc_oen;
    program_address_counter pc(
        .clk(clk), .reset_p(reset_p), .pc_inc(pc_inc), .load_pc(load_pc), .pc_rd_en(pc_oen), .pc_in(int_bus_data), .pc_out(int_bus_data));
    
    // BREG
    wire breg_inen;
    wire [3:0] bus_reg_data;
    register_Nbit_p_alltime #(.N(8)) breg(
        .d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(breg_inen), .rd_en(1'b1), .register_data(bus_reg_data));
    
    // TMPREG
    wire tmpreg_inen, tmpreg_oen;
    register_Nbit_p_alltime #(.N(8)) tmpreg(
        .d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(tmpreg_inen), .rd_en(tmpreg_oen), .q(int_bus_data[7:4]));
    
    // CREG
    wire creg_inen, creg_oen;
    register_Nbit_p_alltime #(.N(8)) creg(
        .d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(creg_inen), .rd_en(creg_oen), .q(int_bus_data[7:4]));
    
    // DREG
    wire dreg_inen, dreg_oen;
    register_Nbit_p_alltime #(.N(8)) dreg(
        .d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(dreg_inen), .rd_en(dreg_oen), .q(int_bus_data[7:4]));    
    
    // RREG
    wire rreg_inen, rreg_oen;
    register_Nbit_p_alltime #(.N(8)) rreg
        (.d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(rreg_inen), .rd_en(rreg_oen), .q(int_bus_data[7:4]));
       
    // ALU & ACC
    wire acc_oen, acc_high_reset_p, acc_in_select;
    wire [1:0] acc_high_select, acc_low_select;
    wire op_add, op_sub, op_mul, op_div, op_and;
    wire sign_flag, zero_flag;   
    block_alu_acc alu_acc(
         .clk(clk), .reset_p(reset_p), .acc_high_reset_p(acc_high_reset_p), .rd_en(acc_oen), .acc_in_select(acc_in_select),
         .acc_high_select(acc_high_select), .acc_low_select(acc_low_select), // acc의 mode를 선택
         .op_add(op_add), .op_sub(op_sub), .op_mul(op_mul), .op_div(op_div), .op_and(op_and),
         .bus_data(int_bus_data[7:4]), .bus_reg_data(bus_reg_data),
         .sign_flag(sign_flag), .zero_flag(zero_flag),
         .acc_data(int_bus_data) // 최종적인 연산의 결과 / 얘는 BUS로만 간다.(상위 4bit, 하위 4bit)
         );
    
    // key 입력받을 레지스터
    wire inreg_oen;
    register_Nbit_p_alltime #(.N(4)) inreg(
        .d(key_value), .clk(clk), .reset_p(reset_p), .wr_en(1'b1), .rd_en(inreg_oen), .q(int_bus_data[7:4]));
    
    // 키 값이 입력되었는지 체크하는 레지스터
    wire keych_reg_oen;
    register_Nbit_p_alltime #(.N(4)) keych_reg( // key change
        .d({4{key_valid}}), .clk(clk), .reset_p(reset_p), .wr_en(1'b1), .rd_en(keych_reg_oen), .q(int_bus_data[7:4]));
        // {4{key_valid}} = {key_valid, key_valid, key_valid, key_valid}
    
    // 키 값이 제대로 CPU로 입력되었는지, FND 확인하기 위한 레지스터
    wire keyout_reg_inen;
    register_Nbit_p_alltime #(.N(4)) keyout_reg( // key change
        .d(int_bus_data[7:4]), .clk(clk), .reset_p(reset_p), .wr_en(keyout_reg_inen), .rd_en(1'b1), .register_data(kout));              
    
    // 최종 출력을 위한 레지스터
    wire outreg_inen;
    register_Nbit_p_alltime #(.N(8)) outreg(
        .d(int_bus_data), .clk(clk), .reset_p(reset_p), .wr_en(outreg_inen), .rd_en(1'b1), .register_data(outreg_data));
        
    // ROM
    wire rom_en;
    dist_mem_gen_0 rom(.a(mar_data), .qspo_ce(rom_en), .spo(rom_data));
    // MAR로부터 주소를 입력받고, 그 주소에 해당되는 명령어를 MDR로 보낸다.
    
    // Control_block
//    control_block c_block( // 변수명 그대로 썼으므로, 따로 지정해주지 않아도 된다.
//        clk, reset_p, ir_data, zero_flag, sign_flag,
//        mar_inen, mdr_inen, mdr_oen, ir_inen, pc_inc, load_pc, pc_oen,
//        breg_inen, tmpreg_inen, tmpreg_oen, creg_inen, creg_oen,
//        dreg_inen, dreg_oen, rreg_inen, rreg_oen,
//        acc_high_reset_p, acc_oen, acc_in_select,
//        op_add, op_sub, op_mul, op_div, op_and,
//        inreg_oen, keych_reg_oen, keyout_reg_inen, outreg_inen, rom_en,
//        acc_high_select, acc_low_select
//        );
        
    control_block c_block( // 변수명 그대로 썼으므로, 따로 지정해주지 않아도 된다.
        .clk(clk), .reset_p(reset_p), .ir_data(ir_data), .zero_flag(zero_flag), .sign_flag(sign_flag),
        .mar_inen(mar_inen), .mdr_inen(mdr_inen), .mdr_oen(mdr_oen), .ir_inen(ir_inen), .pc_inc(pc_inc), .load_pc(load_pc), .pc_oen(pc_oen),
        .breg_inen(breg_inen), .tmpreg_inen(tmpreg_inen), .tmpreg_oen(tmpreg_oen), .creg_inen(creg_inen), .creg_oen(creg_oen),
        .dreg_inen(dreg_inen), .dreg_oen(dreg_oen), .rreg_inen(rreg_inen), .rreg_oen(rreg_oen),
        .acc_high_reset_p(acc_high_reset_p), .acc_oen(acc_oen), .acc_in_select(acc_in_select),
        .op_add(op_add), .op_sub(op_sub), .op_mul(op_mul), .op_div(op_div), .op_and(op_and),
        .inreg_oen(inreg_oen), .keych_reg_oen(keych_reg_oen), .keyout_reg_inen(keyout_reg_inen), .outreg_inen(outreg_inen), .rom_en(rom_en),
        .acc_high_select(acc_high_select), .acc_low_select(acc_low_select)
        );        
            
endmodule

 

이렇게 4bit 계산기 기능을 가진 CPU가 완성되었다.

 


 

[Simulation]

 

module tb_processor();

    reg clk, reset_p;
    reg [3:0] key_value;
    reg key_valid;
    wire [7:0] outreg_data;
    wire [3:0] kout; // key로 입력된 값을 출력(프로세스가 키 값을 제대로 받는 지 확인하기 위한 것)
    
    processor DUT(clk, reset_p, key_value, key_valid, outreg_data, kout);
    
    // 초기화(입력만 초기화 해준다.)
    initial begin
        clk = 0;
        reset_p = 1;
        key_value = 0;
        key_valid = 0;
    end
    
    // clock
    always #4 clk = ~clk;
    
    // 3 + 2 = 5 를 시뮬레이션해보자.
    initial begin
        #8;
        reset_p = 0; #8; // 리셋 풀어주고
        
        key_value = 3; key_valid = 1; #10_000; // 키 입력은 오래 걸리니까 길게 주자.
        key_value = 0; key_valid = 0; #10_000; // 값 받았으면 ,리셋
        
        key_value = 10; key_valid = 1; #10_000; // 더하기를 a로 설정했으니, 10을 넣어준다.
        key_value = 0; key_valid = 0; #10_000; // 값 받았으면 ,리셋
        
        key_value = 2; key_valid = 1; #10_000; // 키 입력은 오래 걸리니까 길게 주자.
        key_value = 0; key_valid = 0; #10_000; // 값 받았으면 ,리셋        
        
        key_value = 4'b1111; key_valid = 1; #10_000; // 더하기를 a로 설정했으니, 10을 넣어준다.
        key_value = 0; key_valid = 0; #10_000; // 값 받았으면 ,리셋
        $stop;
    end
    
endmodule

 

 

코드 수정한 뒤, 새로고침하고 꼭 Run 다시 눌러줄 것.

 


 

 

728x90
반응형