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

[Harman 세미콘 아카데미] 69일차 - Verilog(4x4 keypad, 4bit CPU 개요, ALU, ACC)

by Graffitio 2023. 10. 7.
[Harman 세미콘 아카데미] 69일차 - Verilog(4x4 keypad, 4bit CPU 개요, ALU, ACC)
728x90
반응형
[4x4 Keypad]

 

📌 4x4 keyboard 구상

 

4x4 keypad를 만들어 보자.

 

 

키패드도 일종의 버튼들의 모음이므로, 풀다운 또는 풀업을 적용시켜줘야 한다.

우리는 버튼을 누른 순간 동작시키는 풀업을 시용하도록 하자.

 

row이든 col이든 둘 중 아무거나 하나는 input잡고 다른 하나는 output으로 잡으면 된다.

단, input에 풀업 저항을 셋팅해줄 것.

 

키패드의 어떤 버튼이 입력되었는지 스캔하는 기능은

FND에서 잔상효과 사용하듯이 설계할 것이다.

16개의 키 스캔하는데, 약 4ms가 걸리고,

사람이 버튼을 동작시키는데 최소한 4ms 이상은 걸리니까

다음 버튼의 입력까지의 시간은 충분하다.

포트를 절약하기 위해서 array를 만들어서 사용한다.

 


 

📌 Code

 

module key_pad_cntr(
    input clk, reset_p,
    input [3:0] row,
    output reg [3:0] col,
    output reg [3:0] key_value,
    output reg key_valid
    );
    
    reg [19:0] clk_div; // 1ms 분주기 생성
    always @(posedge clk) clk_div = clk_div + 1;
    
    wire clk_8msec;
    edge_detector_p(.clk(clk), .cp_in(clk_div[19]), .rst(reset_p), .n_edge(clk_8msec));
    // edge detector에서 나오는 펄스를 One cycle pulse라고 한다.
    
    parameter SCAN_0 = 5'b00001; // 0번 스캔
    parameter SCAN_1 = 5'b00010; // 1번 스캔
    parameter SCAN_2 = 5'b00100; // 2번 스캔
    parameter SCAN_3 = 5'b01000; // 3번 스캔
    parameter KEY_PROCESS = 5'b10000;
    
    ///////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////// FSM Begin ////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////
    reg [4:0] state, next_state;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) state = SCAN_0;
        else if(clk_8msec) state = next_state; 
    end
    
    // (*) : 아무 입력이 들어올 때 마다 동작하라는 의미
    // (*) 이런 식으로 always문 쓰면, MUX만으로 구성되기 떄문에 조합회로가 된다.
    always @(*) begin 
        case(state)
            SCAN_0 : begin
                // 풀업 저항을 쓸 것이므로 항상 1이 입력되고 있는 상태.
                // 따라서 4'b1111이 아니면, 어디선가 키 입력이 들어 왔다는 뜻
                if(row != 4'b1111) next_state = KEY_PROCESS;
                else next_state = SCAN_1;
            end
            SCAN_1 : begin
                if(row != 4'b1111) next_state = KEY_PROCESS;
                else next_state = SCAN_2;            
            end
            SCAN_2 : begin
                if(row != 4'b1111) next_state = KEY_PROCESS;
                else next_state = SCAN_3;            
            end
            SCAN_3 : begin
                if(row != 4'b1111) next_state = KEY_PROCESS;
                else next_state = SCAN_0;            
            end
            KEY_PROCESS : begin
                // clk는 매우 짧은 시간이라 바운싱이 무조건 있을 수 밖에 없다.
                // 하지만, 8msec 이후니까 바운싱은 이미 제거된 상태일 것이다.
                // 그래서 따로 디바운싱 필요없음.
                if(row != 4'b1111) next_state = KEY_PROCESS;
                else next_state = SCAN_0;                
            end                                    
        endcase
    end
    ///////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////// FSM End //////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            key_value = 0;
            key_valid = 0;
            col = 4'b0001;
        end
        else begin
            case(state)
                // 스캔할 때는 key_valid = 0으로 줘서 스캔모드
                SCAN_0 : begin col = 4'b1110; key_valid = 0; end
                SCAN_1 : begin col = 4'b1101; key_valid = 0; end
                SCAN_2 : begin col = 4'b1011; key_valid = 0; end
                SCAN_3 : begin col = 4'b0111; key_valid = 0; end
                KEY_PROCESS : begin
                    key_valid = 1;
                    case({col, row}) // 좌표라고 생각하면 된다.
                        8'b1110_1110 : key_value = 4'h0;
                        8'b1110_1101 : key_value = 4'h1;
                        8'b1110_1011 : key_value = 4'h2;
                        8'b1110_0111 : key_value = 4'h3;
                        
                        8'b1101_1110 : key_value = 4'h4;
                        8'b1101_1101 : key_value = 4'h5;
                        8'b1101_1011 : key_value = 4'h6;
                        8'b1101_0111 : key_value = 4'h7;
                        
                        8'b1011_1110 : key_value = 4'h8;
                        8'b1011_1101 : key_value = 4'h9;
                        8'b1011_1011 : key_value = 4'ha;
                        8'b1011_0111 : key_value = 4'hb;
                        
                        8'b0111_1110 : key_value = 4'hc;
                        8'b0111_1101 : key_value = 4'hd;
                        8'b0111_1011 : key_value = 4'he;
                        8'b0111_0111 : key_value = 4'hf;                                                
                    endcase              
                end                                                                
            endcase
        end
    end
    
endmodule

 

module key_pad_test_top(
    input clk, reset_p,
    input [3:0] row,
    output [3:0] col,
    output [3:0] com,
    output [7:0] seg_7
    );
    
    wire [3:0] key_value;
    wire key_valid;
    key_pad_cntr key_pad(.clk(clk), .reset_p(reset_p), .row(row), .col(col), .key_value(key_value), .key_valid(key_valid));
    
    reg [15:0] value;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            value = 0;
        end
        // 키패드 버튼 중 하나가 눌렸을 때, FND에 출력
        // 이거 안 하고 바로 넣어줘도 되기는 하는데, 확실하게 해주기 위해서 추가한다.
        else if(key_valid) begin
            value = key_value;
        end
    end
    
    FND_4digit_cntr(.clk(clk), .rst(reset_p), .value(value), .com(com), .seg_7(seg_7));
    
endmodule

 

## Pmod Header JA
set_property -dict { PACKAGE_PIN Y18   IOSTANDARD LVCMOS33 PULLUP TRUE} [get_ports { row[0] }]; #IO_L17P_T2_34 Sch=ja_p[1]
set_property -dict { PACKAGE_PIN Y19   IOSTANDARD LVCMOS33 PULLUP TRUE} [get_ports { row[1] }]; #IO_L17N_T2_34 Sch=ja_n[1]
set_property -dict { PACKAGE_PIN Y16   IOSTANDARD LVCMOS33 PULLUP TRUE} [get_ports { row[2] }]; #IO_L7P_T1_34 Sch=ja_p[2]
set_property -dict { PACKAGE_PIN Y17   IOSTANDARD LVCMOS33 PULLUP TRUE} [get_ports { row[3] }]; #IO_L7N_T1_34 Sch=ja_n[2]
set_property -dict { PACKAGE_PIN U18   IOSTANDARD LVCMOS33 } [get_ports { col[0] }]; #IO_L12P_T1_MRCC_34 Sch=ja_p[3]
set_property -dict { PACKAGE_PIN U19   IOSTANDARD LVCMOS33 } [get_ports { col[1] }]; #IO_L12N_T1_MRCC_34 Sch=ja_n[3]
set_property -dict { PACKAGE_PIN W18   IOSTANDARD LVCMOS33 } [get_ports { col[2] }]; #IO_L22P_T3_34 Sch=ja_p[4]
set_property -dict { PACKAGE_PIN W19   IOSTANDARD LVCMOS33 } [get_ports { col[3] }]; #IO_L22N_T3_34 Sch=ja_n[4]

PULLUP TRUE를 추가로 기입해주면, 내부적으로 PULL-UP 저항이 연결된다.

 

 

 Data sheet의 Pin Configuration를 참조하여 하드웨어적으로 배치해줘도 되는데,

하드웨어를 깔끔하게 셋팅하기 위해 아래와 같이 소프트웨어적으로 바꿔주자.

case({col, row}) // 좌표라고 생각하면 된다.
    8'b1110_1110 : key_value = 4'hD;
    8'b1110_1101 : key_value = 4'hE;
    8'b1110_1011 : key_value = 4'hB;
    8'b1110_0111 : key_value = 4'hA;

    8'b1101_1110 : key_value = 4'hF;
    8'b1101_1101 : key_value = 4'h3;
    8'b1101_1011 : key_value = 4'h6;
    8'b1101_0111 : key_value = 4'h9;

    8'b1011_1110 : key_value = 4'h0;
    8'b1011_1101 : key_value = 4'h2;
    8'b1011_1011 : key_value = 4'h5;
    8'b1011_0111 : key_value = 4'h8;

    8'b0111_1110 : key_value = 4'hC;
    8'b0111_1101 : key_value = 4'h1;
    8'b0111_1011 : key_value = 4'h4;
    8'b0111_0111 : key_value = 4'h7;                                                
endcase

 


 

📌 Result

 

 

계산기의 키패드와 같은 구조로 숫자를 배치하였다.

 


 

[4bit CPU 프로젝트 개요]

 

RTL 마지막 과제인 CPU 구현 프로젝트

간단하게 4bit짜리 CPU를 만들 것이고, 한 자리 수 사칙연산 기능까지 구현해볼 예정이다.

 

📌 용어 정리

 

<CPU>

: Central Processing Unit

  중앙 처리 장치를 의미하며, 사용자로부터 입력 받은 명령어를 해석/연산하여 그 결과를 출력하는 기능을 한다.

  스마트폰과 같은 소형 기기에 주로 쓰이는 통합형 프로세서는 CPU라는 명칭보다

  SoC(System on Chip) 또는 AP(Application Processor)라고 부른다.

  CPU의 속도는 clock(클럭)으로 나타내며, 1초에 CPU 내부에서 몇 단계의 작업이 처리되는 지 측정해 Hz로 나타낸다.

  ex) Cora z7 07s board의 system clock = 125MHz

  CPU는 일종의 두뇌 역할을 하는데, 이 역할의 핵심적인 기능을 담당하는 부분이 바로 Core(코어)이며

  코어의 갯수가 많아질수록 처리 속도는 코어의 수만큼 배로 빨라지게 된다.(다중코어)

  ex) i7 → 코어 수 8개(기본적으로 1개 + 추가 코어 7개라는 의미)

 

<Cache memory(캐시 메모리)>

: 캐시메모리는 CPU 내부의 임시 저장 공간으로서, CPU가 데이터를 처리할 때 자주 사용하는 데이터를 임시 보관하느 곳이다.

  캐시 메모리의 크기가 클수록 주/보조 기억장치로부터 데이터를 불러들이는 빈도가 줄어들어 처리 속도가 빨라지게 되고

  코어와의 거리에 따라 1차, 2차, 3차로 나뉘며 가까울수록 성능은 향상되지만, 제조가 어렵고 가격이 상승한다.

 

<Architecture(아키텍쳐)>

: 컴퓨터 시스템의 기본 구조 및 설계 방식, 제조 공정까지 포함하는 개념으로

  클럭의 속도나 코어의 수, 캐시의 용량이 모두 같더라도 아키텍쳐가 다르면, 전반적인 성능 차이가 발생한다.

  컴퓨터 아키텍쳐에 대한 추가 내용

 

Computer Architecture, RISC & CISC란?

[개요] RISC와 CISC는 컴퓨터 아키텍쳐 디자인에 사용되는 두 가지 주요한 접근 방식이다. 이 두 가지 방식은 명령어 집합 구조(ISA, Instruction Set Architecture)를 설계하는 방식에 관련된 것으로, 컴퓨터

rangvest.tistory.com

 


 

CPU의 구성도

 

전체적인 Flow

먼저 keypad로 데이터와 명령어를 입력 받아 레지스터에 저장한다.

명령어 데이터는 제어 Part를 통해서 어셈블리어로 변환된 뒤, Control signal로 전송된다.

숫자 데이터는 ACC에 저장되었다가 ALU로 넘어가서 연산을 한 뒤, 다시 ACC에 저장되고 BUS로 출력된다.

이후 연산을 마친 데이터는 FND를 통해서 그 값이 출력된다.

 

📌 연산 Part

 

<ALU>

ALU는 Alrithmetic Logic Unit의 약자로 덧셈, 뺼셈, 곱셈, 나눗셈 등의 산술연산과

AND, OR, NOT, XOR 등의 논리 연산을 수행하는 산술 논리 장치이다.

ALU는 CPU 내부에 위치하며,  연산 명령을 받아 데이터를 처리하고 그 결과를 다른 레지스터에 저장한다.

 

<ACC>

ACC는 Accumulator(누산기)로 CPU 내부에 위치한 레지스터 중 하나이이며,

주로 CPU의 명령어에 따라 데이터를 읽어오고 산솔 및 논리 연산의 중간 결과를 저장하는 데 사용된다

예를 들어, 연속적인 덧셈 연산을 수행할 때, ACC에 현재까지의 덧셈 결과에 더하는 방식으로 진행된다.

 

<BREG>

BREG는 버퍼 레지스터로, 데이터를 임시로 저장하고 전송하는 데 사용되는 레지스터이다.

예를 들어, 데이터를 메모리에서 CPU로 로드하거나, CPU에서 메모리로 데이터를 보낼 때,

중간 단계로 BREG를 사용할 수 있으며, 이는 데이터의 안정적인 전송과 처리를 돕는 역할을 한다.

 

<적용>

우리는 4bit 연산이 가능한 CPU를 만들 것이다.

처리 가능한 연산은 4bit이며 동시에 breg로 임시 저장도 해야 되기 때문에

ALU에는 총 8bit가 연결된다.

이때, 상위 4bit는 ACC - ALU / 하위 4bit는 BREG - ALU로 연결되고

따라서 Bus는 8bit로 구성되어야 한다.

 

위 연산기는 산술연산, 논리연산, 비교연산, 시프트 연산이 가능한데,

산술 연산은 가감산기를 활용,

논리연산은 삼항연산자(조건연산자)와 assign 문을 사용하여 작성해주면 된다.

시프트 연산 또한 가감산기로 연산 가능하고

비교 연산은 flag를 활용하여 연산하게 된다.

- 서로 같은 지 비교 : A에서 B를 빼고 그 값이 0이라면, zero_flag = 1 이 되고 이 것으로 같은 지 비교

- 크기 비교 : A에서 B를 빼고 그 값이 양수라면 sign_flag = 1이 되고 이를 통해 크기를 비교할 수 있다.

 

<산술 연산 예시>

4 + 7 계산

ACC에 4 저장, BREG에 7 저장

ALU에서 각각 값을 받아서 연산한 뒤에 ACC에 전달하고

연산이 종료되었다면, 그 값을 Data Bus로 전송

 


 

📌 레지스터 Part

 

위 레지스터 파트에 해당하는 레지스터들은 주로 컴퓨터 시스템에서 사용되는 레지스터들이다.

 

<TMPREG>

Temporary Register로 일시적으로 데이터를 저장하고 처리하는 데 사용되며,

주로 중간 계산의 결과를 저장하거나 데이터를 임시로 보관하는 데 활용된다.

TMPREG는 CPU의 연산 동안 임시 데이터 스토리지로 사용될 수 있다.

 

<CREG>

Control Register는 시스템의 제어 및 설정을 관리하기 위해 사용되는 레지스터이다.

시스템 설정, 모드 전환, 예외 처리 및 다른 중요한 제어 동작을 수행하기 위해 사용되며,

예를 들어 프로세서 모드 변경, 인터럽트 활성화/비활성화 및 보안 성정을 관리하는 데 사용될 수 있다.

 

<RREG>

General-Purpose Register는 일반 목적 레지스터로, 다양한 연산 및 데이터 저장에 사용되고

주로 프로그램 카운터, 스택 포인터, 범용 데이터 저장 등 다양한 목적으로 활용된다.

프로세서 아키텍쳐에 따라 여러 개의 RREG가 존재할 수 있으며, 주로 프로그래머가 직접 접근하여 데이터를 조작한다.

 

<OUTREG>

Output Register는 데이터 출력과 관련된 작업을 수행하는 데 사용되는 레지스터이고

주로 입출력 연산, 데이터 전송, 디스플레이 제어 및 통신과 관련된 작업에서 활용되며,

외부 장치와 데이터를 주고받는 데에 OUTREG를 사용할 수 있다.

 


 

📌 제어 Part

 

<PC>

PC는 Program Counter로 현재 실행 중인 프로그램의 다음 명령어 위치를 가리키는 레지스터이다.

CPU는 PC가 가리키는 명령어를 실행하고, 그 후에 PC를 증가시켜 다음 명령어의 위치를 가리키며

이러한 PC로 인해, CPU는 명령어를 순차적으로 실행할 수 있다.

이전 값을 기반으로 다음 값이 카운트되므로, Loadable Counter로 설계되어야 한다.

 

<MAR>

MAR은 메모리 주소 레지스터로 CPU가 메모리에서 데이터를 읽거나 쓸 때 사용된다.

CPU가 메모리를 액세스할 주소를 MAR에 저장하고,

이 주소를 기반으로 메모리와 상호작용한다.

 

<MDR>

MDR은 메모리 데이터 레지스터로, 메모리에서 읽어온 데이터나 메모리에 쓸 데이터를 임시로 저장하며,

CPU와 메모리 간의 데이터 교환을 수행하는 중요한 역할을 한다.

 

<IR>

IR은 Instruction Register의 약자로 명령어 레지스터를 의미하고 현재 실행 중인 명령어를 저장하며,

CPU는 IR에 저장된 명령어를 디코딩하고 실행하게 된다.

 

<Control Block>

제어 블록은 CPU의 동작을 제어하는 데 사용되는 논리 회로나 레지스터의 집합이다.

이는 명령어 실행, 레지스터 간의 데이터 이동, 연산 수행 등 CPU의 기능을 조절하고 동기화하하며

명령어의 디코딩과 실행을 가능하도록 하는 CPU의 핵심 구성 요소이다.

 

여러 모듈에서 동시에 버스로 데이터를 전송하면, 충돌이 발생하게 된다.

이러한 문제를 방지하기 위해 출력은 한 번에 한 모듈만나 버스로 내보내야 하고

이때, 버스로 입력되는 데이터들의 전송 Enable 신호는 Control block에서 관리한다.

(Enable된 것만 출력하고 나머지는 다 임피던스 출력)

 

<Fetch(패치, =읽기) : 메모리에서 명령어를 가져오는 과정>

Counter는 ROM에서 명령어를 불러올 때 마다 1씩 증가하고

우리는 명령어를 통해 ROM에 저장된 어셈블리어를하나씩 꺼내서 사용하는데,

(ROM에 저장되는 어셈블리어도 CPU를 만드는 사람이 다 작성해줘야 한다.)

이때 PC를 통해 명령어에 해당하는 어셈블리어의 위치를 가리킨다.

PC가 가리킨 위치의 주소가 MAR에 저장되고 저장된 주소에 맞는 데이터를

MDR에 임시저장한 뒤 내보내게 된다.

MDR에서 내보내진 명령어는 IR에서 저장된 뒤, Control Block을 통해 디코딩되어

명령을  수행한다.

 


 

📌 기타 관련 내용

 

<ROM>

ROM은 사실 조합회로이다.

값이 변하지 않고 이미 정해진 값을 꺼내서 쓰기만 하기 때문이다.(Read Only Memory)

이 것은 미리 코딩해줘야 하고 어셈블리어를 활용하여 코딩해줄 것이다.

 

<용어>

어셈블 : 프로그램 이름

어셈블링 : 어셈블리 언어를 헥사코드(컴퓨터가 알아들을 수 있는 기계어)로 변환하는 과정

어셈블러 : 변환해주는 주체

컴파일링 : C언어를 헥사코드(컴퓨터가 알아들을 수 있는 기계어)로 변환하는 과정

 

<어셈블러와 기계어>

어셈블러는 고급 프로그래밍 언어로 작성된 프로그램을 컴퓨터가 알아들을 수 있는 기계어로 변환하는 도구이다.

기계어는 컴퓨터 하드웨어가 직접 실행할 수 있는 이진 형태의 명령어로 이루어진 프로그래밍 언어이다.

기계어 명령어의 비트 배치는 해당 CPU 아키텍쳐에 따라서 다르며,

CPU 제조사 또는 설계자가 CPU 아키텍쳐를 결정하고 이 아키텍쳐에 따라 명령어 포맷과 비트 배치가 정의돤다.

예를 들어, add 라는 명령어를 x86 아키텍쳐는 01주소의 1번 bit에 배치하고

ARM 아키텍쳐는 72주소의 4번 bit에 배치한다고 하자.

이때, add 명령어가 활성화 된다면,

x86 아키텍쳐   : 01   0000_0010

ARM 아키텍쳐 : 72   0001_0000

이런 식으로 어셈블리어가 코딩될 것이다.

위와 같이 각 아키텍쳐마다 서로 다른 포맷과 비트배치를 가지므로

위 Rule을 아는 CPU 제작자가 어셈블러도 같이 만들어 줘야 하는데, 보통은 CPU 프로그래밍을 담당하는

프로그래머가 CPU 아키텍쳐에 맞게 프로그램을 작성하는 방식으로 진행된다.

 

<명령어 처리 방식>

우리가 만들 것은 RISC 구조의 CPU이다.

RISC와 CISC를 간단하게 설명하자면,

RISC는 하나의 사이클로 명령어 처리 / CISC는 여러 사이클로 명령어 처리

RISC 구조의 CPU는 모든 명령의 소요 clk 수가 동일하다.

그 덕분에 추가 기능들을 만들기가 쉬워진다는 장점을 가지고 있다.

RISC와 CISC에 대한 자세한 내용이 알고 싶다면?

 

Computer Architecture, RISC & CISC란?

[개요] RISC와 CISC는 컴퓨터 아키텍쳐 디자인에 사용되는 두 가지 주요한 접근 방식이다. 이 두 가지 방식은 명령어 집합 구조(ISA, Instruction Set Architecture)를 설계하는 방식에 관련된 것으로, 컴퓨터

rangvest.tistory.com

 

<RAM과 ROM>

 

RAM과 ROM에 대한 설명

 

[Harman 세미콘 아카데미] 41일차 - Verilog(Review, Memory, Clock Library, Stop watch)

[Review] 이전 내용 참조 더보기 https://rangvest.tistory.com/entry/Harman-1%EC%9D%BC%EC%B0%A8-VIVADO-setting [Harman 세미콘 아카데미] 1일차 - VIVADO setting [개요] 시스템 반도체를 설계하는 언어는 대표적으로 2가지가

rangvest.tistory.com

 


 

[새 프로젝트 생성]

 

프로젝트를 새로 만들어 주자.

 

 

RTL Project 선택

 

 

 

기본 모듈들을 또 만들기는 너무 귀찮으니,

이전 프로젝트에서 만들었던 기본 모듈의 소스들을 추가해서 프로젝트를 만들도록 하자.

 

Copy sources into project

이거 체크 안 하면 원본가지고 작업하게 되니까

반드시 체크하자!

 

 

이전 프로젝트에서 썼던 xdc 파일도 불러오고 

여기도 마찬가지로 Copy Constraints files into project 체크!

 

그 다음 단계에서는 보드를 선택해야 되는데,

업데이트 오류 등으로 사용할 보드가 검색되지 않을 수가 있다.

그럴 때는 그냥 넘어가고 따로 설정해주면 된다.

 

 

 

 

 

우측의ㆍㆍㆍ 을 클릭하고 사용할 보드를 선택하여 Ok누르면 보드 적용 완료

 

위와 같이 Project Manager에서 우리가 사용할 보드를 설정해줄 수 있다.

 

그래도 안 된다?

그러면 board file을 vivado 폴더에 직접 넣어주고 사용할 보드를  선택하는 방법이 있다.

 

vivado-boards-master.zip
0.50MB

 

위 파일을 다운로드받고, board_files 폴더를 찾아서 아래 위치에 덮어씌워주면 된다.

 

 


 

[ALU]

 

 

module alu(
    input clk, reset_p,
    input op_add, op_sub, op_mul, op_div, op_and, // 연산에 사용할 명령어
    input alu_lsb, // 최하위 비트 입력 하나 있어야 됨
    input [3:0] acc_high_data, // BUS를 통해 입력된 첫 번쨰 Data 4bit는 ACC에 저장 -> ALU의 상위 4bit에 저장
    input [3:0] bus_reg_data, // BUS를 통해 입력된 다음 Data 4bit는 BREG에 저장 -> ALU의 하위 4bit에 저장
    output [3:0] alu_out, // 연산 결과  -> ACC로 전송
    // 비교 연산을 위한 flag
    output zero_flag, // 연산의 결과가 0이면, zero flag = 1
    output sign_flag, // 계산 결과, 음수가 발생하면 sign_flag = 0, 부호(음수/양수)에 따라 sign flag값이 정해진다. 
    output carry_flag, // carry가 발생하면, carry flag = 1
    output cout // carry_out
    );
    // 8bit가 ALU와 연결된다.
    // ACC와 연결되는 것은 상위 4bit -> 연산은 4bit로만 이루어진다.
    // BREG와 연결되는 것은 하위 4bit
    // 예를 들어 만약 8bit짜리 CPU라면, BUS는 16bit로 구성되어야 한다.
    
    // 가감산기
    wire [3:0] sum;
    fadd_sub_4bit_d fadd_sub(.a(acc_high_data), .b(bus_reg_data),
                             .s(op_sub | op_div), // sub 또는 div 명령어가 발생했을 때를 의미한다.
                             .sum(sum), .carry(cout)); // add : s = 0, sub : s = 1 (나눗셈은 뺴기의 연속)
                             // 덧셈 뺄셈 연산은 alu_out으로 나가면 된다.
    
    // AND 연산
    assign alu_out = op_and ? (acc_high_data & bus_reg_data) : sum;
    
    // 연산 속도 : 곱하기는 2 clk, 나누기는 4 clk 이상
    // 이렇게 만든 alu로 산술연산, 논리연산, 시프트 연산, 비교 연산까지 가능하다.
    
    // Zero Flag
    wire zero_sum = ~(|sum); // sum의 모든 비트가 0이되면, zero_sum = 1
    register_Nbit_p #(.N(1)) zero_f(.d(zero_sum), .clk(clk), .reset_p(reset_p), .wr_en(op_sub), .rd_en(1'd1), .q(zero_flag));
    
    // Sign Flag
    // 뺸 결과가 양수거나 0이면, cout = 1 -> sign_flag = 0
    // 뺸 결과가 음수가 나오면 cout = 0 -> sign_flag = 1
    register_Nbit_p #(.N(1)) sign_f(.d(~cout), .clk(clk), .reset_p(reset_p), .wr_en(op_sub), .rd_en(1'd1), .q(sign_flag));
//    register_Nbit_p #(.N(1)) sign_f(.d(!(cout) & op_sub), .clk(clk), .reset_p(reset_p), .wr_en(op_sub), .rd_en(1'b1), .q(sign_flag));
    
    // Carry Flag
    // 곱하기, 나누기, 더하기할 때 봐야 하므로
    // wr_en은 1 줘서 계속 enable되게 하고 cout에 &로 묶어주자.
    register_Nbit_p #(.N(1)) carry_f(.d(cout & (op_add | op_sub | (op_mul & alu_lsb))), .clk(clk), .reset_p(reset_p), .wr_en(1'b1), .rd_en(1'd1), .q(carry_flag)); //

endmodule

 


 

[ACC]

 

ACC는 총 8bit의 크기가 필요하고

상위 4bit와 하위 4bit의 사용 용도가 다르기 때문에 반반 나눠서 구현해줄 것이다.

 

 

// 4bit짜리 ACC만들어서 두 개 이어붙일것이다.
module half_acc(
    input clk, reset_p,
    input load_msb, load_lsb, // 시프트하여 사용하기 위한 변수
    input rd_en, // Bus를 공통으로 쓰기 떄문에 Control Block으로부터 받는 Enable 신호
    input [1:0] s, // mode select
    input [3:0] data_in, // 데이터 입력받을 변수
    output [3:0] data2bus, // BUS의 신호를 받아서 데이터 출력을 내보내는 변수, 신호가 오지 않으면 임피던스 상태
    output [3:0] register_data // 언제든 바로바로 데이터 출력을 내보내는 변수
    );
    
    
    reg [3:0] d;
    always @(*) begin
        case(s)
            2'b00 : d = register_data; // 데이터 유지
            2'b01 : d = {load_msb, register_data[3:1]}; // 불러온 msb와 register_data의 상위 3bit를 결합한다 -> 우시프트(>>), 나눗셈
            2'b10 : d = {register_data[2:0], load_lsb}; // 좌시프트(<<), 곱셈 
            2'b11 : d = data_in; // ALU나 BUS로부터 전송받은 데이터
        endcase // 조합논리회로에서는 case를 전부 채우거나 default를 만들어줘야 한다. 그렇지 않으면 Latch가 만들어질 수도 있다
    end
    
    // Half ACC
    register_Nbit_p_alltime #(.N(4)) h_cc(.d(d), .clk(clk), .reset_p(reset_p), .wr_en(1'b1), .rd_en(rd_en), .register_data(register_data), .q(data2bus));
    // 상시 출력은 register_data, 조건 출력(Enable 신호)은 data2bus
    
endmodule

 

ACC blockdiagram

module acc(
    input clk, reset_p, acc_high_reset_p,
    input fill_value, // 안 쓰는 비트를 0으로 채우거나 1로 채움
    input rd_en, acc_in_select,
    input [1:0] acc_high_select, acc_low_select, // Half_ACC가 두 개이므로 구분해줘야 한다.
    input [3:0] bus_data, alu_data, // BUS와 ALU로부터 받는 데이터
    output [3:0] acc_high_data2bus, acc_high_register_data,
    output [3:0] acc_low_data2bus, acc_low_register_data     
    );
    
    // ACC는 Bus로부터도 데이터를 받고, ALU로부터도 데이터를 받으므로,
    // 어디서 받을 것인지 선택하는 기능이 필요하다.
    wire [3:0] acc_high_data;
    assign acc_high_data = acc_in_select ? bus_data : alu_data;
    
    half_acc acc_high(.clk(clk), .reset_p(reset_p | acc_high_reset_p), .load_msb(fill_value), .load_lsb(acc_low_register_data[3]), .rd_en(rd_en), .s(acc_high_select), // 좌시프트할 때는 하위 bit에 최상위 bit를 준다
                      .data_in(acc_high_data), // BUS나 ALU로부터 전송된 데이터를 acc_high로 받는다.
                      .data2bus(acc_high_data2bus), .register_data(acc_high_register_data));
    half_acc acc_low(.clk(clk), .reset_p(reset_p), .load_msb(acc_high_register_data[0]), .load_lsb(fill_value), .rd_en(rd_en), .s(acc_low_select),
                      .data_in(acc_high_register_data), .data2bus(acc_low_data2bus), .register_data(acc_low_register_data));
                      // BUS나 ALU로부터 Data를 load할 때는 4bit씩 load하는데,
                      // 이 때, acc_high만 load한 Data 4bit를 받고 acc_low는 안 받아(이전에 있던 값 유지)
                      // 다음 연산을 위해서 또는 acc_high에 다음 Data를 받기 위해서 현재 Data를 acc_low로 넘겨줄 필요가 있다.
                      // 그래서 acc_low의 data_in에 acc_high 4bit를 그냥 그대로 줘
    // acc_low는 acc_high를 불러와서 쓴다.
    // 불러온 뒤 불러온 상위 4bit는 리셋을 시켜줘야 해.
    // 근데 그냥 리셋을 쓰면 하위까지 다 지워짐.
    // 따라서 상위 4bit만 따로 리셋해주는 기능이 필요하다. -> acc_high에 acc_high_reset_p 추가                                     
    
endmodule

 


 

 

728x90
반응형