Harman 세미콘 아카데미/SoC를 위한 Peripheral 설계

2024.12.3 [SoC를 위한 Peripheral 설계]11 - cpu의 구조와 동작원리5(ROM, Processor simulation)

U_Pong 2024. 12. 3. 23:28

2024.12.3 수업날


 

이번에는 저번시간에 이어서 ROM을 만들어보고, Processor에 합쳐서 시뮬레이션으로 결과를 확인해본다.

 

복습을 해보자면
ALU: 연산기
ACC: 누산기(누적해서 계산한다)
PC: 1씩 증가하거나 점프하는 카운터
MAR: ROM으로부터 데이터를 Adress
MDR: ROM으로부터 해당되는 데이터를 받는다
IR: 명령 레지스터

 

 

 

< ROM 만들기 순서 >

 

 

넉넉하게 256byte로 준 것이다.

 

 

CE(Chip Enable)을 체크표시한다.

 

 

다음으로 아래의 .coe 파일을 다운받는다.

calculator.coe
0.00MB

 

.coe 파일 내용 중

첫 번째 줄은 16진수라는 의미이다.

두 번째 줄은 'cpu 4bit 계산기 프로그램'의 hex code를 차례대로 나열한 것이다.

 

 

그리고 아래의 Load COE File에서 다운받은 .coe 파일을 입력하고 OK를 누른다.

 

 

그러면 아래와 같이 VHDL 식 코드가 생성된다.

 

ROM VHDL code
-- (c) Copyright 1995-2024 Xilinx, Inc. All rights reserved.
-- 
-- This file contains confidential and proprietary information
-- of Xilinx, Inc. and is protected under U.S. and
-- international copyright and other intellectual property
-- laws.
-- 
-- DISCLAIMER
-- This disclaimer is not a license and does not grant any
-- rights to the materials distributed herewith. Except as
-- otherwise provided in a valid license issued to you by
-- Xilinx, and to the maximum extent permitted by applicable
-- law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND
-- WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES
-- AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING
-- BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-
-- INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and
-- (2) Xilinx shall not be liable (whether in contract or tort,
-- including negligence, or under any other theory of
-- liability) for any loss or damage of any kind or nature
-- related to, arising under or in connection with these
-- materials, including for any direct, or any indirect,
-- special, incidental, or consequential loss or damage
-- (including loss of data, profits, goodwill, or any type of
-- loss or damage suffered as a result of any action brought
-- by a third party) even if such damage or loss was
-- reasonably foreseeable or Xilinx had been advised of the
-- possibility of the same.
-- 
-- CRITICAL APPLICATIONS
-- Xilinx products are not designed or intended to be fail-
-- safe, or for use in any application requiring fail-safe
-- performance, such as life-support or safety devices or
-- systems, Class III medical devices, nuclear facilities,
-- applications related to the deployment of airbags, or any
-- other applications that could lead to death, personal
-- injury, or severe property or environmental damage
-- (individually and collectively, "Critical
-- Applications"). Customer assumes the sole risk and
-- liability of any use of Xilinx products in Critical
-- Applications, subject only to applicable laws and
-- regulations governing limitations on product liability.
-- 
-- THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS
-- PART OF THIS FILE AT ALL TIMES.
-- 
-- DO NOT MODIFY THIS FILE.

-- IP VLNV: xilinx.com:ip:dist_mem_gen:8.0
-- IP Revision: 13

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;

LIBRARY dist_mem_gen_v8_0_13;
USE dist_mem_gen_v8_0_13.dist_mem_gen_v8_0_13;

ENTITY dist_mem_gen_0 IS
  PORT (
    a : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
    qspo_ce : IN STD_LOGIC;
    spo : OUT STD_LOGIC_VECTOR(7 DOWNTO 0)
  );
END dist_mem_gen_0;

ARCHITECTURE dist_mem_gen_0_arch OF dist_mem_gen_0 IS
  ATTRIBUTE DowngradeIPIdentifiedWarnings : STRING;
  ATTRIBUTE DowngradeIPIdentifiedWarnings OF dist_mem_gen_0_arch: ARCHITECTURE IS "yes";
  COMPONENT dist_mem_gen_v8_0_13 IS
    GENERIC (
      C_FAMILY : STRING;
      C_ADDR_WIDTH : INTEGER;
      C_DEFAULT_DATA : STRING;
      C_DEPTH : INTEGER;
      C_HAS_CLK : INTEGER;
      C_HAS_D : INTEGER;
      C_HAS_DPO : INTEGER;
      C_HAS_DPRA : INTEGER;
      C_HAS_I_CE : INTEGER;
      C_HAS_QDPO : INTEGER;
      C_HAS_QDPO_CE : INTEGER;
      C_HAS_QDPO_CLK : INTEGER;
      C_HAS_QDPO_RST : INTEGER;
      C_HAS_QDPO_SRST : INTEGER;
      C_HAS_QSPO : INTEGER;
      C_HAS_QSPO_CE : INTEGER;
      C_HAS_QSPO_RST : INTEGER;
      C_HAS_QSPO_SRST : INTEGER;
      C_HAS_SPO : INTEGER;
      C_HAS_WE : INTEGER;
      C_MEM_INIT_FILE : STRING;
      C_ELABORATION_DIR : STRING;
      C_MEM_TYPE : INTEGER;
      C_PIPELINE_STAGES : INTEGER;
      C_QCE_JOINED : INTEGER;
      C_QUALIFY_WE : INTEGER;
      C_READ_MIF : INTEGER;
      C_REG_A_D_INPUTS : INTEGER;
      C_REG_DPRA_INPUT : INTEGER;
      C_SYNC_ENABLE : INTEGER;
      C_WIDTH : INTEGER;
      C_PARSER_TYPE : INTEGER
    );
    PORT (
      a : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
      d : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
      dpra : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
      clk : IN STD_LOGIC;
      we : IN STD_LOGIC;
      i_ce : IN STD_LOGIC;
      qspo_ce : IN STD_LOGIC;
      qdpo_ce : IN STD_LOGIC;
      qdpo_clk : IN STD_LOGIC;
      qspo_rst : IN STD_LOGIC;
      qdpo_rst : IN STD_LOGIC;
      qspo_srst : IN STD_LOGIC;
      qdpo_srst : IN STD_LOGIC;
      spo : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
      dpo : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
      qspo : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
      qdpo : OUT STD_LOGIC_VECTOR(7 DOWNTO 0)
    );
  END COMPONENT dist_mem_gen_v8_0_13;
  ATTRIBUTE X_CORE_INFO : STRING;
  ATTRIBUTE X_CORE_INFO OF dist_mem_gen_0_arch: ARCHITECTURE IS "dist_mem_gen_v8_0_13,Vivado 2019.2";
  ATTRIBUTE CHECK_LICENSE_TYPE : STRING;
  ATTRIBUTE CHECK_LICENSE_TYPE OF dist_mem_gen_0_arch : ARCHITECTURE IS "dist_mem_gen_0,dist_mem_gen_v8_0_13,{}";
  ATTRIBUTE CORE_GENERATION_INFO : STRING;
  ATTRIBUTE CORE_GENERATION_INFO OF dist_mem_gen_0_arch: ARCHITECTURE IS "dist_mem_gen_0,dist_mem_gen_v8_0_13,{x_ipProduct=Vivado 2019.2,x_ipVendor=xilinx.com,x_ipLibrary=ip,x_ipName=dist_mem_gen,x_ipVersion=8.0,x_ipCoreRevision=13,x_ipLanguage=VERILOG,x_ipSimLanguage=MIXED,C_FAMILY=artix7,C_ADDR_WIDTH=8,C_DEFAULT_DATA=0,C_DEPTH=256,C_HAS_CLK=0,C_HAS_D=0,C_HAS_DPO=0,C_HAS_DPRA=0,C_HAS_I_CE=0,C_HAS_QDPO=0,C_HAS_QDPO_CE=0,C_HAS_QDPO_CLK=0,C_HAS_QDPO_RST=0,C_HAS_QDPO_SRST=0,C_HAS_QSPO=0,C_HAS_QSPO_CE=1,C_HAS_QSPO_RST=0,C_HAS_QSPO_SRST=0,C_HAS_SPO=1,C_HAS_WE=0,C_MEM_INIT_" & 
"FILE=dist_mem_gen_0.mif,C_ELABORATION_DIR=./,C_MEM_TYPE=0,C_PIPELINE_STAGES=0,C_QCE_JOINED=0,C_QUALIFY_WE=0,C_READ_MIF=1,C_REG_A_D_INPUTS=0,C_REG_DPRA_INPUT=0,C_SYNC_ENABLE=1,C_WIDTH=8,C_PARSER_TYPE=1}";
BEGIN
  U0 : dist_mem_gen_v8_0_13
    GENERIC MAP (
      C_FAMILY => "artix7",
      C_ADDR_WIDTH => 8,
      C_DEFAULT_DATA => "0",
      C_DEPTH => 256,
      C_HAS_CLK => 0,
      C_HAS_D => 0,
      C_HAS_DPO => 0,
      C_HAS_DPRA => 0,
      C_HAS_I_CE => 0,
      C_HAS_QDPO => 0,
      C_HAS_QDPO_CE => 0,
      C_HAS_QDPO_CLK => 0,
      C_HAS_QDPO_RST => 0,
      C_HAS_QDPO_SRST => 0,
      C_HAS_QSPO => 0,
      C_HAS_QSPO_CE => 1,
      C_HAS_QSPO_RST => 0,
      C_HAS_QSPO_SRST => 0,
      C_HAS_SPO => 1,
      C_HAS_WE => 0,
      C_MEM_INIT_FILE => "dist_mem_gen_0.mif",
      C_ELABORATION_DIR => "./",
      C_MEM_TYPE => 0,
      C_PIPELINE_STAGES => 0,
      C_QCE_JOINED => 0,
      C_QUALIFY_WE => 0,
      C_READ_MIF => 1,
      C_REG_A_D_INPUTS => 0,
      C_REG_DPRA_INPUT => 0,
      C_SYNC_ENABLE => 1,
      C_WIDTH => 8,
      C_PARSER_TYPE => 1
    )
    PORT MAP (
      a => a,
      d => STD_LOGIC_VECTOR(TO_UNSIGNED(0, 8)),
      dpra => STD_LOGIC_VECTOR(TO_UNSIGNED(0, 8)),
      clk => '0',
      we => '0',
      i_ce => '1',
      qspo_ce => qspo_ce,
      qdpo_ce => '1',
      qdpo_clk => '0',
      qspo_rst => '0',
      qdpo_rst => '0',
      qspo_srst => '0',
      qdpo_srst => '0',
      spo => spo
    );
END dist_mem_gen_0_arch;

 

 

이제 ROM VHDL 코드를 저번에 만들었던 Processor의 코드에 인스턴스 한다.

Processor 최종 코드
module Processor(
      input clk, reset_p,
      input [3:0] key_value,
      input key_valid,
      output [3:0] kout,                        // cpu가 key 입력을 받았는지 확인함
      output [7:0] outreg_data);
      
      wire [7:0] int_bus, mar_out, rom_out;
      wire mar_inen, mdr_inen;                      // in enable
      wire mdr_oen;                                         // out enable
      wire pc_inc, load_pc, pc_oen;
      wire [7:0] ir_data;
      wire ir_inen;
      wire [3:0] bus_reg_data;
      wire breg_inen;
      wire acc_high_reset_p, acc_oen, acc_in_select; 
      wire [1:0] acc_high_select_in, acc_low_select;
      wire op_add, op_sub, op_mul, op_div, op_and;
      wire zero_flag, sign_flag;
      wire tmpreg_inen, tmpreg_oen;
      wire creg_inen, creg_oen;
      wire dreg_inen, dreg_oen;
      wire rreg_inen, rreg_oen;
      wire outreg_inen;
      wire keychreg_oen;
      wire inreg_oen;
      wire keyout_inen;
      
      
      // Control_Block 모듈: CPU 제어 논리
      // 입력: 클럭, 리셋, IR 데이터, 플래그
      // 출력: 여러 컨트롤 신호 (MAR, MDR, PC, ALU 등) 활성화 및 설정 신호
      Control_Block CB(
            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,
            acc_high_reset_p, acc_oen, acc_in_select,
            acc_high_select_in, acc_low_select,
            op_add, op_sub, op_mul, op_div, op_and,
            tmpreg_inen, tmpreg_oen, creg_inen, creg_oen, 
            dreg_inen, dreg_oen, rreg_inen, rreg_oen, 
            outreg_inen, keychreg_oen, inreg_oen, keyout_inen, rom_en);
      
      
      // Program_addr_counter 모듈: Program Counter 제어
      // PC 증가 또는 로드 수행, 내부 버스를 통해 값을 전달
      Program_addr_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),
            .pc_out(int_bus));
      
      
      // MAR (Memory Address Register): 메모리 주소 저장
      register_Nbit_p # (.N(8)) MAR(         
            .clk(clk), .reset_p(reset_p),
            .d(int_bus),                                          // 내부 버스에서 입력 받음
            .wr_en(mar_inen), .rd_en(1),     
            .register_data(mar_out));                 // 출력 데이터 (MAR의 주소 값)
      
      
      // MDR (Memory Data Register): 메모리 데이터 저장
      register_Nbit_p # (.N(8)) MDR(         
            .clk(clk), .reset_p(reset_p),
            .d(rom_out),                                                      // ROM의 출력 데이터를 입력
            .wr_en(mdr_inen), .rd_en(mdr_oen),     
            .q(int_bus));                                                     // 내부 버스에 출력 연결
      
      
      // IR (Instruction Register): 명령어 저장
      register_Nbit_p # (.N(8)) IR(         
            .clk(clk), .reset_p(reset_p),
            .d(int_bus),                                                      // 내부 버스에서 입력 받음
            .wr_en(ir_inen), .rd_en(1),     
            .register_data(ir_data));                                 // 출력 데이터 (명령어)
      
      
      // BREG: ALU 연산을 위해 데이터 보관 (4비트)
      register_Nbit_p # (.N(4)) BREG(                 // ALU를 4 bit로 사용했기 때문
            .clk(clk), .reset_p(reset_p),
            .d(int_bus[7:4]),                                            // 상위 4비트만 입력으로 사용
            .wr_en(breg_inen), .rd_en(1),     
            .register_data(bus_reg_data));                  // ALU로 전달할 데이터
      
      
      // ALU 및 ACC: 산술 및 논리 연산 수행
      Block_ALU_ACC b_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_in(acc_high_select_in), .acc_low_select(acc_low_select),
            .bus_data(int_bus[7:4]),                                    // ALU 입력 데이터
            
            .op_add(op_add), .op_sub(op_sub), .op_mul(op_mul), .op_div(op_div), .op_and(op_and),      // 연산 제어
            .bus_reg_data(bus_reg_data),                          // 레지스터 데이터
            .zero_flag(zero_flag), .sign_flag(sign_flag),    // 상태 플래그
            
            .acc_data(int_bus));                                        // 내부 버스로 결과 출력
      
      
      // TMPREG: 임시 레지스터
      register_Nbit_p # (.N(4)) TMPREG(                 
            .clk(clk), .reset_p(reset_p),
            .d(int_bus[7:4]),
            .wr_en(tmpreg_inen), .rd_en(tmpreg_oen),     
            .q(int_bus[7:4]));
      
      
      // CREG, DREG, RREG: 추가 레지스터 (4비트)
      register_Nbit_p # (.N(4)) CREG(                 
            .clk(clk), .reset_p(reset_p),
            .d(int_bus[7:4]),
            .wr_en(creg_inen), .rd_en(creg_oen),     
            .q(int_bus[7:4]));
      
      
      register_Nbit_p # (.N(4)) DREG(                 
            .clk(clk), .reset_p(reset_p),
            .d(int_bus[7:4]),
            .wr_en(dreg_inen), .rd_en(dreg_oen),     
            .q(int_bus[7:4]));
      
      
      register_Nbit_p # (.N(4)) RREG(                 
            .clk(clk), .reset_p(reset_p),
            .d(int_bus[7:4]),
            .wr_en(rreg_inen), .rd_en(rreg_oen),     
            .q(int_bus[7:4]));
      
      
      // OUTREG: CPU 출력용 레지스터
      register_Nbit_p # (.N(8)) OUTREG(                 
            .clk(clk), .reset_p(reset_p),
            .d(int_bus),
            .wr_en(outreg_inen),     
            .register_data(outreg_data));
      
      
      // KEYCHREG: 키 입력 유효성을 확인하기 위한 레지스터
      register_Nbit_p # (.N(4)) KEYCHREG(                 
            .clk(clk), .reset_p(reset_p),
            .d({key_valid, key_valid, key_valid, key_valid}),
            .wr_en(1), .rd_en(keychreg_oen),     
            .q(int_bus[7:4]));
      
      // INREG: 키 값을 저장하는 레지스터
      register_Nbit_p # (.N(4)) INREG(                 
            .clk(clk), .reset_p(reset_p),
            .d(key_value),
            .wr_en(1), .rd_en(inreg_oen),     
            .q(int_bus[7:4]));
      
      // KEYOUTREG: 키 출력 확인 레지스터
      register_Nbit_p # (.N(4)) KEYOUTREG(                 
            .clk(clk), .reset_p(reset_p),
            .d(int_bus[7:4]),
            .wr_en(keyout_inen), .rd_en(1),     
            .register_data(kout));
      
      /////////// 2024.12.03 --> ROM 추가
      dist_mem_gen_0 rom(  
            .a(mar_out), 
            .qspo_ce(rom_en),
            .spo(rom_out));
      
endmodule

 

 

 

< Processor simulation >

다음으로, Test bench를 통해 Processor에서 사칙연산이 잘 되는지 확인한다.

 

 

1. 덧셈

Test bench Processor code
module tb_Processor();
      
      reg clk, reset_p;
      reg [3:0] key_value;
      reg key_valid;
      
      wire [3:0] kout;
      wire [7:0] outreg_data;
      
      Processor DUT(
            clk, reset_p,
            key_value,
            key_valid,
            kout,                        // cpu가 key 입력을 받았는지 확인함
            outreg_data);
      
      initial begin
            clk = 0; reset_p = 1;
            key_value = 0;
            key_valid = 0;
      end
      
      always #5 clk = ~clk;
      
      initial begin       
            #10; reset_p = 0; #10;                                             // 초기화 끝
            key_value = 3; key_valid = 1; #10_000;                // 10us
            key_value = 0; key_valid = 0; #10_000;
            
            key_value = 4'ha; key_valid = 1; #10_000;            // a --> '+'    
            key_value = 0; key_valid = 0; #10_000;
            
            key_value = 5; key_valid = 1; #10_000;                
            key_value = 0; key_valid = 0; #10_000;
            
            key_value = 4'hf; key_valid = 1; #10_000;             // f --> '='        
            key_value = 0; key_valid = 0; #10_000;
            $stop;
            
      end
      
endmodule

결과를 보기 위해서는 시뮬레이션 창에서 상단의 1ms를 실행해야한다.

그리고 key가 눌렸을 때만 key_valid가 1이 되는 것이다.

 

위의 코드는 덧셈일때를 나타낸 것인데,

3 a 5 f 08 이렇게 된 경우는 --> 3 + 5 = 8이라는 의미이다.

 

 

다음으로 십의 자리가 증가하는 덧셈의 경우를 알아보자.

 

 

 

7 a 8 f 0f 이렇게 된 경우는 --> 7 + 8 = 15 라는 의미이다.

원래는 16진수로 결과값이 나타나있는데, Radix를 Unsigned Decimal로 변경하면 10진수로 값을 확인할 수 있다.

 

 

위의 코드에서 사용하였듯이 a~f는 아래와 같은 의미의 연산이다.

a(10): +
b(11): -
c(12): and연산
d(13): /
e(14): *

f(15): =

 

 

2. 뺄셈

다음으로 뺄셈일 때를 살펴보자.

 

 

9 b 5 f 04 이렇게 된 경우는 --> 9 - 5 = 4 라는 의미이다.

 

 

3. 곱셈

다음으로 곱셈일 때를 살펴보자.

 

 

 

7 e 8 f 38 이렇게 된 경우는 --> 7 x 8 = 56 라는 의미이다.

원래는 16진수로 결과값이 나타나있는데, Radix를 Unsigned Decimal로 변경하면 10진수로 값을 확인할 수 있다.

 

 

4. 나눗셈

다음으로 나눗셈일 때를 살펴보자.

 

 

8 d 5 f 01 03 이렇게 된 경우는 --> 8 / 5 = 1(몫)  3(나머지) 라는 의미이다.


cpu의 구조와 동작원리5(ROM, Processor simulation) 끝!