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

[Harman 세미콘 아카데미] 93일차 - SoC Design(CPU를 활용한 Calculator 구현, SoC / MicroBlaze)

by Graffitio 2023. 11. 16.
[Harman 세미콘 아카데미] 93일차 - SoC Design(CPU를 활용한 Calculator 구현, SoC / MicroBlaze)
728x90
반응형
[Calculator]

 

CPU와 4x4 KeyPad, FND를 활용하여 계산기를 구현해보자.

키패드 구성은 다음과 같다.

 

7 8 9 ÷ (D)
4 5 6 × (E)
1 2 3 - (B)
C 0 = (F) + (A)

 


 

📌 Top module

 

module Calculator_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));
    
    wire [7:0] outreg_data;
    wire [3:0] kout;
    
    processor cpu(.clk(clk), .reset_p(reset_p), .key_value(key_value), .key_valid(key_valid), .outreg_data(outreg_data), .kout(kout));
    // kout : key로 입력된 값을 출력(프로세스가 키 값을 제대로 받는 지 확인하기 위한 것)
    
    wire [15:0] bcd;
    // 12bit binary를 받아서 16bit decimal로 변환
    bin_to_dec bin2dec(.bin({4'b0000, outreg_data}), .bcd(bcd));
    // 올림수가 하나만 나와도 4자리 다 읽어야 해서 bcd vector : 16
    
    wire [15:0] value;
    assign value = {kout, 4'h1, bcd[7:0]}; // bcd 중 위의 8자리는 안 쓴다(최대인 9x9 = 81이므로 7bit면 충분하ㅏ다.)
    FND_4digit_cntr_cal fnd(.clk(clk), .rst(reset_p), .value(value), .com(com), .seg_7(seg_7));
    
endmodule

 


 

📌 Result

 

1. 덧셈

2. 뺄셈

3. 곱셈

4. 나눗셈

 


 

📌 Improve

 

좀 더 연산 결과를 편하게 보기 위해 수정을 좀 해주자.

 

module FND_4digit_cntr_cal( //// ring counter top module 생성 ---- 16bit의 값을 받아서 1ms 간격으로 한 자리씩 켜준다.
    input clk, rst,
    input [15:0] value, //// 4자리니까 16bit
    output [3:0] com,
    output [7:0] seg_7
);
    reg [16:0] clk_1ms;//// 8ns * 2^17 = 1,048,576ns = 1048㎲ = 1.048ms
    always @(posedge clk) clk_1ms = clk_1ms + 1 ;
    
    ring_count_fnd ring1(.clk(clk_1ms[16]), .com(com)); //// 1ms posedge마다 좌시프트
                                                                   //// 1ms 마다 CT 순대로 LED가 점멸한다.
                                                                   
    wire [7:0] seg_7_font;
    reg [3:0] hex_value;
    decoder_7seg_cal ring_seg_7(.hex_value(hex_value), .seg_7(seg_7_font));
    assign seg_7 = ~seg_7_font;
    
    always @(negedge clk) begin //// 카운팅은 posedge에 했으니, 겹치지 않게 negedge
        case(com)
            4'b1110 : hex_value = value[15:12]; //// CT1에 해당하는 hex_value는 value[15:12]
            4'b1101 : hex_value = value[11:8];
            4'b1011 : hex_value = value[7:4];
            4'b0111 : hex_value = value[3:0];
        endcase  //// edge triggering은 default에서 현재 값을 유지해도 FF가 만들어지니 상관없다.           
    end
endmodule

 

module decoder_7seg_cal( ////7_seg용 폰트변환기
    input [3:0] hex_value,
    output reg [7:0] seg_7
);    
    always @(hex_value) begin
        case(hex_value)
            4'b0000 : seg_7 = 8'b0000_0011;   //// a를 최상위 비트로 쓰자.
            4'b0001 : seg_7 = 8'b1001_1111;   
            4'b0010 : seg_7 = 8'b0010_0101;
            4'b0011 : seg_7 = 8'b0000_1101;
            4'b0100 : seg_7 = 8'b1001_1001;
            4'b0101 : seg_7 = 8'b0100_1001;
            4'b0110 : seg_7 = 8'b0100_0001;
            4'b0111 : seg_7 = 8'b0001_1111;
            4'b1000 : seg_7 = 8'b0000_0001;
            4'b1001 : seg_7 = 8'b0000_1001;   /// 0~9
            
            4'b1010 : seg_7 = 8'b0001_0001;   //// A~F
            4'b1011 : seg_7 = 8'b1111_1101; // B는 (-) 이므로 -가 출력되도록 변경
            4'b1100 : seg_7 = 8'b0110_0011;
            4'b1101 : seg_7 = 8'b1000_0101;
            4'b1110 : seg_7 = 8'b0110_0001;
            4'b1111 : seg_7 = 8'b1110_1101; // F : (=) 이므로 다음과 같이 벼경
        endcase //// 0~F까지 출력 코드
    end /// end 먼저 써주는 습관을 들이자.
endmodule

 

 


 

[SoC]

 

📌 SoC 개요

[SoC가 출범하게 된 계기]

 

우리가 썼던 ARM을 예시로 들어 보자.

하나의 시스템을 구현하는데, 굳이 필요없는 기능들이 너무 많다.

최근 구현한 자율주행 프로젝트만 봐도, 핵심 시스템으로 구현하지 않아도 될 ADC 등의 기능들까지

이미 Chip 안에 내장되어 있다.

 

결론은 굳이 ARM를 쓸 필요가 없다는 것이다.

최적화를 위해 내가 필요한 기능만 넣어서 설계할 수 있고,

그렇게 내가 구현하고자 하는 기능들만 모아 최적화해 하나의 Chip으로 만든 것이 SoC이다.

 

그렇게 만들면, CPU 안에 회로가 적으므로 전력소모가 적다는 장점을 가지고(전원 관리에 용이)

굳이 다른 것들을 넣어서 에러가 발생할 소지 또한 감소하게 된다.

추가로 그만큼 여유 공간이 생기므로 배터리 등의 추가 기능 구현이 가능하다.

 

그 예시로 미사일을 들 수 있는데,

미사일은 신뢰성이 가장 중요하다.

날라가다 전력부족으로 시스템이 죽는다면?

아니면 굳이 필요하지 않았던 기능때문에 에러가 발생한다면?

사소한 오류로 인해 엄청난 재앙을 불러올 수 있다.

 

cf)

실제로 따지고 보면, SoaC라 쓰는 것이 맞다.

System on a Chip이기 떄문

근데 다들 SoC라 쓰니까 그렇게 하자.

 


 

📌 New Project

 

새로 프로젝트를 만들어 주고, 이제부터는 Basys3 보드를 사용할 것이다.

 

 

 

Cora보다 사용하기 편하다.

FND와 버튼이 달려있을 뿐더러, 16개의 LED도 장착되어 있으므로 LED bar를 활용해서 디버그했던 것을

더 수월하게 진행할 수 있다. 또한 플래시 메모리가 있어, 전원이 차단돼도 입력된 프로그램이 삭제되지 않는다.

 

cf) 3번 포트는 ADC 포트이므로 가능하면 쓰지 말 것.

 

 

위 시퀀스처럼 다양한 점퍼들이 

가운데 두 핀(JTAG)에 연결할 경우, USB를 통해서만 데이터 전송받음

위 두 핀(SPI)은 플래시메모리에서 비트스트림 등을 불러와서 쓰는 모드

 

USB 포트에 저장장치를 연결하면, 그 저장장치에 있는 비트스트림을 불러와서 쓸 수도 있다.

 

 

필요한 모듈 소스들을 가져오자.

 


 

📌 Basys3 보드에서 FND 셋팅 및 테스트

 

테스트용 탑모듈

module fnd_test_top(  /// 보드의 입출력과 연결해주는 모듈을 top module
    input clk,
    output [7:0] seg_7,
    output [3:0] com    ///4bit 짜리라 com 단자가 4개 있다.
);
    assign com = 4'b1000;  //// 7_seg에서 실제로 출력되는 위치가 com(ct1~ct4)
    
    reg [27:0] clk_div;    
    always @(posedge clk) //// posedge : rising edge
        clk_div = clk_div +1; //// block(begin-end) 안에 문장이 하나밖에 없다면, block 생략 가능하다.
    
    reg [3:0] count;
    always @(negedge clk_div[25]) begin //// 약 0.5초마다 count가 증가한다.(보드의 주기 : 8ns) → 8ns x 2^26 = 0.56 sec
                                        //// 2진수에서 다음 bit의 주기는 이전 bit 주기의 2배
        count = count+1; 
    end
    
//    wire [7:0] seg_7_font;
//    decoder_7seg seg7( .hex_value(count), .seg_7(seg_7_font));
//    assign seg_7 = ~seg_7_font;     /// 만약에 font가 반대로 출력될 경우에 이 구문 사용해서 seg_7을 반전시켜주면 된다.
    
    decoder_7seg seg7( .hex_value(count), .seg_7(seg_7));  /// 디코더의 seg_7을 탑모듈의 seg_7에 연결
endmodule

 

 

xdc 파일 셋팅

## Clock signal
set_property -dict { PACKAGE_PIN W5   IOSTANDARD LVCMOS33 } [get_ports clk]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports clk]

##7 Segment Display
set_property -dict { PACKAGE_PIN W7   IOSTANDARD LVCMOS33 } [get_ports {seg_7[0]}]
set_property -dict { PACKAGE_PIN W6   IOSTANDARD LVCMOS33 } [get_ports {seg_7[1]}]
set_property -dict { PACKAGE_PIN U8   IOSTANDARD LVCMOS33 } [get_ports {seg_7[2]}]
set_property -dict { PACKAGE_PIN V8   IOSTANDARD LVCMOS33 } [get_ports {seg_7[3]}]
set_property -dict { PACKAGE_PIN U5   IOSTANDARD LVCMOS33 } [get_ports {seg_7[4]}]
set_property -dict { PACKAGE_PIN V5   IOSTANDARD LVCMOS33 } [get_ports {seg_7[5]}]
set_property -dict { PACKAGE_PIN U7   IOSTANDARD LVCMOS33 } [get_ports {seg_7[6]}]

#set_property -dict { PACKAGE_PIN V7   IOSTANDARD LVCMOS33 } [get_ports dp]

set_property -dict { PACKAGE_PIN U2   IOSTANDARD LVCMOS33 } [get_ports {com[0]}]
set_property -dict { PACKAGE_PIN U4   IOSTANDARD LVCMOS33 } [get_ports {com[1]}]
set_property -dict { PACKAGE_PIN V4   IOSTANDARD LVCMOS33 } [get_ports {com[2]}]
set_property -dict { PACKAGE_PIN W4   IOSTANDARD LVCMOS33 } [get_ports {com[3]}]

 

 


 

📌 Slide Switch를 활용하여 FND 제어

 

module FND_4digit_cntr( //// ring counter top module 생성 ---- 16bit의 값을 받아서 1ms 간격으로 한 자리씩 켜준다.
    input clk, rst, lap,
    input [15:0] value, //// 4자리니까 16bit
    output [3:0] com,
    output [7:0] seg_7
);
    reg [16:0] clk_1ms;//// 8ns * 2^17 = 1,048,576ns = 1048㎲ = 1.048ms
    always @(posedge clk) clk_1ms = clk_1ms + 1 ;
    
    ring_count_fnd ring1(.clk(clk_1ms[16]), .com(com)); //// 1ms posedge마다 좌시프트
                                                                   //// 1ms 마다 CT 순대로 LED가 점멸한다.
                                                                   
//    wire [7:0] seg_7_font;
    reg [3:0] hex_value;
//    decoder_7seg ring_seg_7(.hex_value(hex_value), .seg_7(seg_7_font));
    decoder_7seg ring_seg_7(.hex_value(hex_value), .seg_7(seg_7));
//    assign seg_7 = ~seg_7_font;
    
    always @(negedge clk) begin //// 카운팅은 posedge에 했으니, 겹치지 않게 negedge
        case(com)
            4'b1110 : hex_value = value[15:12]; //// CT1에 해당하는 hex_value는 value[15:12]
            4'b1101 : hex_value = value[11:8];
            4'b1011 : hex_value = value[7:4];
            4'b0111 : hex_value = value[3:0];
        endcase  //// edge triggering은 default에서 현재 값을 유지해도 FF가 만들어지니 상관없다.           
    end
endmodule

 

## Switches
set_property -dict { PACKAGE_PIN V17   IOSTANDARD LVCMOS33 } [get_ports {value[0]}]
set_property -dict { PACKAGE_PIN V16   IOSTANDARD LVCMOS33 } [get_ports {value[1]}]
set_property -dict { PACKAGE_PIN W16   IOSTANDARD LVCMOS33 } [get_ports {value[2]}]
set_property -dict { PACKAGE_PIN W17   IOSTANDARD LVCMOS33 } [get_ports {value[3]}]
set_property -dict { PACKAGE_PIN W15   IOSTANDARD LVCMOS33 } [get_ports {value[4]}]
set_property -dict { PACKAGE_PIN V15   IOSTANDARD LVCMOS33 } [get_ports {value[5]}]
set_property -dict { PACKAGE_PIN W14   IOSTANDARD LVCMOS33 } [get_ports {value[6]}]
set_property -dict { PACKAGE_PIN W13   IOSTANDARD LVCMOS33 } [get_ports {value[7]}]
set_property -dict { PACKAGE_PIN V2    IOSTANDARD LVCMOS33 } [get_ports {value[8]}]
set_property -dict { PACKAGE_PIN T3    IOSTANDARD LVCMOS33 } [get_ports {value[9]}]
set_property -dict { PACKAGE_PIN T2    IOSTANDARD LVCMOS33 } [get_ports {value[10]}]
set_property -dict { PACKAGE_PIN R3    IOSTANDARD LVCMOS33 } [get_ports {value[11]}]
set_property -dict { PACKAGE_PIN W2    IOSTANDARD LVCMOS33 } [get_ports {value[12]}]
set_property -dict { PACKAGE_PIN U1    IOSTANDARD LVCMOS33 } [get_ports {value[13]}]
set_property -dict { PACKAGE_PIN T1    IOSTANDARD LVCMOS33 } [get_ports {value[14]}]
set_property -dict { PACKAGE_PIN R2    IOSTANDARD LVCMOS33 } [get_ports {value[15]}]

 

 


 

[MicroBlaze]

 

Vivado의 기능 중, 블록으로 디자인한 것들을 HDL 코드로 변환시켜주는 기능이 존재한다.

이 것들을 활용하면, 기본 기능들은 굳이 힘들게 코드로 쳐내지 않아도 손쉽게 구현할 수 있다.

그래도 직접 만드는 것이 더 좋겠지?

 

 

위와 같은 방법으로 원하는 기능의 블록을 드래그하면 된다.

 

 

MicroBlaze 얘가 CPU임

 

 

 

F6 : 블록에 이상이 있는지 검증하는 단계

 

 

그렇게 클락과 통신 등 기본 옵션들이 추가된 모듈 완성

 

 

Validate Design 버튼 혹은 F6키를 눌러 이상없는지 확인

 

 

 

Create HDL Wrapper 기능을 실행시켜,

해당 블록디자인들을 HDL 코드로 변환시켜준다.

 

 

HDL 코드로 변환 완료.

 


 

728x90
반응형