2024년 1월 17일 수요일

ETRI 0.5um CMOS Std-Cell DK 예제: CPU 6502 [3]/시스템 수준 테스트벤치 (2/3)

ETRI 0.5um CMOS Std-Cell DK 예제: CPU 6502 [3]

II. 시스템 수준 테스트 벤치 [2부]

    II-3. 베릴로그 RTL과 SystemC/C++의 결합

        simulation/sc_CPU_6502_Top.h

        simulation/obj_dir/Vcpu.h
        simulation/mti_sim/CPU_6502.h
        simulation/mti_sim/Vcpu.h
        베릴레이터의 언어 변환(Language Translation) 시뮬레이터
        QuestaSim의 동적 링크(Dynamic Linking) 시뮬레이터
    II-4. 동기식 메모리 모델
        simulation/sc_mem.h
        simulation/sc_mem.cpp
    II-5. Apple-1 컴퓨터
        simulation/sc_main.cpp

----------------------------------------------------------------


II. 시스템 수준 테스트 벤치 [2부]

II-3. 베릴로그 RTL과 SystemC/C++의 결합

RTL 베릴로그 소스를 자세히 다루지 않은 이유는 구문구조가 워낙 단순하기 때문이다. 디지털 논리회로를 안다면 구문은 반나절만 배우면 이해할 수 있다. 문제는 시스템 수준 검증 체계를 세우는 일이다. 하드웨어 언어로 테스트 벤치를 작성하느라 수많은 기교를 부리며 마치 HDL의 최고 수준인 양 뽐내는 것을 본다. 초보 소프트웨어 개발자가 보더라도 굳이 부질없는 짓을 한다고 할지 모른다. 그만큼 HDL은 검증용으로 쓰기에 추상성이 매우 낮다는 뜻이된다. 이제부터 6502 CPU를 검증하기 위한 Apple-1 컴퓨터를 구성하면서 C++로 얼마나 쉽게 시스템 검증 환경을 구축 할 수 있는지 보자. Apple-1 컴퓨터 모델에 사용된 소스 파일들은 아래와 같다.

simulation/sc_CPU_6502_Top.h

베릴로그 RTL을 SystemC의 환경에 통합하기 위한  감싸기(wapper)용 C++ 헤더 파일이다. 베릴로그를 이해하지 못하는 C++ 컴파일러에게 6502 CPU의 외형을 SystemC의 모듈 크래스에 싸서 노출 시킨다. C++컴파일러는 동작하는 객체(이를 흔히 API, Application Programing Interface,라고 한다)가 있다는 것 외에 감싸기 뒤에 뭐가 있는지 알필요도 없다.

    #include "Vcpu.h"

    SC_MODULE(sc_CPU_6502_Top)    // C++ Class
    {
        sc_clock            clk;
        sc_signal<bool>     reset;
        sc_signal<uint32_t> DI;     // Data-in
        sc_signal<uint32_t> DO;     // Data-out
        sc_signal<bool>     WE;     // Write Enable
        sc_signal<bool>     IRQ;    // Interrupt Request
        sc_signal<bool>     NMI;    // Non-Maskable Interrupt
        sc_signal<bool>     RDY;    // Ready
        sc_signal<uint32_t> AB;     // Address Bus

        // Verilated DUT
        Vcpu*   u_CPU_6502;    // 6502 CPU Verilog

        // Memory & I/O Model
        sc_mem* u_mem;

        SC_CTOR(sc_CPU_6502_Top) :   // Constructor
            clk("clk", 100, SC_NS, 0.5, 0.0, SC_NS, false),
            reset("reset"), DI("DI"), DO("DO"), WE("WE"), 
AB("AB"),
            IRQ("IRQ"), NMI("NMI"), RDY("RDY")
        {
            // DUT Instantiation
            u_CPU_6502 = new Vcpu("u_CPU_6502");
            // Binding
            u_CPU_6502->clk(clk);
            u_CPU_6502->reset(reset);
            u_CPU_6502->DI(DI);
            u_CPU_6502->DO(DO);
            u_CPU_6502->WE(WE);
            u_CPU_6502->IRQ(IRQ);
            u_CPU_6502->NMI(NMI);
            u_CPU_6502->RDY(RDY);
            u_CPU_6502->AB(AB);

            // Memory Instantiation
            u_mem = new sc_mem("u_mem");
            // Binding
            u_mem->clk(clk);
            u_mem->DI(DO);
            u_mem->DO(DI);
            u_mem->WE(WE);
            u_mem->AB(AB);

            // Environments: Reset/RDY/IRQ/NMI
            SC_THREAD(reset_generator);
            sensitive << clk;
        }

        // Environment: Reset/RDY/IRQ/MNI
        void reset_generator()
        {
            IRQ.write(0);
            NMI.write(0);
            RDY.write(0);
            reset.write(1);
            wait(clk.posedge_event());
            RDY.write(1);
            wait(clk.posedge_event());
            wait(clk.negedge_event());
            reset.write(0);
        }
    };

위의 감싸기 헤더에 선언된 객체 Vcpu는 베릴로그 언어로 기술된 CPU 하드웨어를 C++ 영역으로 들여옴을 뜻한다. 이종의 언어로 기술된 객체를 들여오는 방법은 언어 변환기(language translatoe)를 쓰거나 병행 시뮬레이션(co-simulation)을 수행할 수도 있다.

먼저, 언어 변환기를 사용하는 방법이다. 베릴레이터(Verilator)는 베릴로그(Verilog) 언어로 기술된 구문을 SystemC/C++로 변환해 주는 오픈-소스 도구다[바로가기]. 사용법도 매우 간단하다.

$ verilator --sc -Wall --top-module cpu \
                ../source/cpu.v ../source/ALU.v

하위 폴더 obj_dir에 SystemC 로 변환된 베릴로그 모듈이 생성된다. 최상위 모듈의 C++ 크래스 명이 Vcpu 다.

simulation/obj_dir/Vcpu.h

하위 폴더 obj_dir은 베릴레이터가 변환한 C++의 파일들이 저장되는 곳이다. 구문은 표준 C++ 이긴 하지만 기계 번역의 산물 이다. 인간이 읽기엔 내용이 매우 산만하다. 베릴로그 상위 모듈 cpu에서 번역되어 C++로 노출시킨 크래스 이름이 Vcpu이며 입출력 포트의 선언(방향과 자료형) 정도만 유의하여 살펴봐 두자.

    class alignas(VL_CACHE_LINE_BYTES) Vcpu VL_NOT_FINAL :
     public ::sc_core::sc_module, public VerilatedModel {
         ...........
        public:
        // PORTS
        sc_in<bool> &clk;
        sc_in<bool> &reset;
        sc_in<uint32_t> &DI;
        sc_out<uint32_t> &DO;
        sc_out<bool> &WE;
        sc_in<bool> &IRQ;
        sc_in<bool> &NMI;
        sc_in<bool> &RDY;
        sc_out<uint32_t> &AB;
         ............
        // CONSTRUCTORS
        SC_CTOR(Vcpu);
         ............
    };

이종의 언어로 기술된 객체를 들여오는 또다른 방법은 병행 시뮬레이션(Co-Simulation)이다. 실행중인 프로세스가 다른 실행 파일(쉐어드 오브젝트, shared object 혹은 다이나믹 링킹 라이브러리, DLL)를 함수처럼 부르는 방법이다. 베릴로그 시뮬레이터도 어짜피 컴퓨팅 언어의 컴파일러와 다를바 없다. 소스 코드를 컴파일하여 실행 코드(바이너리)를 만들고 이를 컴퓨터에서 실행 시킨다. 컴퓨터의 운영체제는 실행 중인 소프트웨어(이를 프로세스라고 한다)들 사이에 데이터를 주고받도록 허용하는데 이를 IPC 라고 한다. 병행 시뮬레이션도 IPC를 활용하여 각각 실행중인 베릴로그 시뮬레이터와 C++ 코드 사이에 실행중 데이터를 주고 받는다. C++에서 베릴로그 시뮬레이터를 마치 하위 함수(API)처럼 취급하는 셈이다. C++ 컴파일러는 실행 중 호출할 다른 프로세스의 함수를 알고 있어야 한다. 이 함수를 정의하고 노출 시키는 방법은 하드웨어 언어마다 다르다. 하드웨어의 검증에 C 언어 함수를 불러 쓸 필요성이 절실했기에 베릴로그는 진즉부터 PLI/VPI라는 표준을 제정해 두었다. 하지만 매우 불편했다. 시스템베릴로그 SystemVerilog에서 이를 개선하여 DPI-C 를 표준으로 세웠다. 이 표준에 따르면 "직접 연결(direct programing interface)[SystemVerilog DPI]"이라고 하지만 중간 절차가 복잡하고 보기 사납다[바로가기]. 상용 HDL 시뮬레이터 QuestaSim은 대표적인 하드웨어 언어(Verilog, SystemVerilog, VHDL)를 단일 실행체계에서 취급한다. SystemC 가 표준으로 제정됨에 따라 이에 추가되었다. 최상위 모듈이 어떤 언어로 되어 있던지 상관하지 않는다.

simulation/mti_sim/CPU_6502.h

시뮬레이션을 위한 최상위 모듈(테스트 벤치)이 SystemC일 경우 하위 모듈이 HDL로 작성 되었다면 C++ 컴파일러[주]에게 하드웨어 언어로 작성된 모듈이 있다는 표시(sc_foreign_module)를 해주고 그 모듈의 이름을 알려주기만 하면 된다. C++의 가상 크래스를 활용하면 하위 모듈이 다른 언어로 작성 되었으므로 컴파일 시 내용은 무시하고 외곽만 유지하게 만들 수 있다. 아래의 예에서 CPU_6502 크래스는 SystemC/C++로 노출될 입출력 신호의 소속 데이터만 선언되어 있고 소속 함수가 없는 껍데기다. 크래스의 구성자(constructor)에 연결할 베릴로그의 모듈명 "cpu" 을 적시하고 있다. 이런 크래스를 베릴로그 감싸개(Verilog wrapper)라고 한다.

    /***************************************************
    Filename: CPU_6502.h
    Purpose: Verilog wrapper for Arlet's Verilog-6502
    ****************************************************/
    class CPU_6502 : public sc_foreign_module
    {
    public:
        sc_in<bool> clk;
        sc_in<bool> reset;
        sc_out<sc_bv<16> > AB;
        sc_in<sc_bv<8> > DI;
        sc_out<sc_bv<8> > DO;
        sc_out<bool> WE;
        sc_in<bool> IRQ;
        sc_in<bool> NMI;
        sc_in<bool> RDY;
        CPU_6502(sc_module_name nm) :
            sc_foreign_module(nm, "cpu"), // Verilog module name
            clk("clk"), reset("reset"),
            AB("AB"), DI("DI"), DO("DO"), WE("WE"),
            IRQ("IRQ"),
            NMI("NMI"),
            RDY("RDY")
        {
        }
    };

[주] QuestaSim 에 SystemC를 컴파일 하기 위해 C++ 컴파일러를 내장하고 있다. 이 컴파일러는 리눅스 운영체제의 GNU의 GCC 툴 체인과 동일 하다. 이는 하드웨어 시뮬레이터에서 리눅스 운영체제의 모든 기능을 그대로 활용할 수 있다는 뜻이기도 하다.

simulation/mti_sim/Vcpu.h

베릴로그는 비트 크기를 상세히 지정한 RTL이지만 베릴로그 언어 변환기 베릴레이터는 이보다 높은 수준의 32-비트 정수 자료형  uint32_t 를 사용한다. 하지만 HDL 시뮬레이터 QuestaSim의 베릴로그 감싸개는 RTL 이다. 입출력 포트의 자료형을 맞춰주는 포장기를 한꺼풀 더 씌워 줬다. Apple-1 컴퓨터 시스템을 구성하는 메모리 모델이 베릴레이터의 자료형을 따랐기 때문이다.

    #include "systemc.h"
    #include "CPU_6502.h"
    /********************************************************
    Filename: Vcpu.h
    Purpose: SystemC interface wrapper compatible with
                Verilated SC module
    *********************************************************/
    SC_MODULE(Vcpu)
    {
        sc_in<bool>         clk;
        sc_in<bool>         reset;
        sc_in<uint32_t>     DI;
        sc_out<uint32_t>    DO;
        sc_out<bool>        WE;
        sc_in<bool>         IRQ;
        sc_in<bool>         NMI;
        sc_in<bool>         RDY;
        sc_out<uint32_t>    AB;

        sc_signal<bool>         _clk;
        sc_signal<bool>         _reset;
        sc_signal<sc_bv<16> >   _AB;
        sc_signal<sc_bv<8> >    _DI;
        sc_signal<sc_bv<8> >    _DO;
        sc_signal<bool>         _WE;
        sc_signal<bool>         _IRQ;
        sc_signal<bool>         _NMI;
        sc_signal<bool>         _RDY;

        CPU_6502*   u_CPU_6502;    // foreign module

        void eval()
        {
            _clk = clk.read();
            _reset = reset.read();
        
            _DI = DI.read();
            DO.write(sc_uint<32>(_DO));
            WE.write(_WE);
            AB.write(sc_uint<32>(_AB));
            _IRQ = IRQ.read();
            _NMI = NMI.read();
            _RDY = RDY.read();
        }
    
        SC_CTOR(Vcpu) : // constructor
            clk("clk"), reset("reset"),
            AB("AB"), DI("DI"), DO("DO"), WE("WE"),
            IRQ("IRQ"), NMI("NMI"), RDY("RDY")
        {
            SC_METHOD(eval);
            sensitive << clk << reset;
            sensitive << DI << IRQ << NMI << RDY;

            // Verilog model instantiation
            u_CPU_6502 = new CPU_6502("u_CPU_6502");
            // Binding
            u_CPU_6502->clk(_clk);
            u_CPU_6502->reset(_reset);
            u_CPU_6502->DI(_DI);
            u_CPU_6502->DO(_DO);
            u_CPU_6502->WE(_WE);
            u_CPU_6502->IRQ(_IRQ);
            u_CPU_6502->NMI(_NMI);
            u_CPU_6502->RDY(_RDY);
            u_CPU_6502->AB(_AB);
        }
    };

SystemC/C++ 환경에서 베릴로그 RTL을 불러 결합하는 두가지 방법에 대해 다뤘다. 하위 모듈을 결합하는 방식의 차이를 비교하면 아래와 같다.

베릴레이터의 언어 변환(Language Translation)

 +-------------------+
 | sc_CPU_6502_Top   |
 +------+------------+
        |  +-----------------+
        +--| obj_dir/Vcpu.h  |
           +--+--------------+--+     Language
              | Behavior in C++ |<--- Translation --- Verilog
              +-----------------+     (Verilator)

                                 +-------------------------------+
                                 |   sc_CPU_6502_Top             |
                                 |  +-------------------------+  |
                                 |  | Vcpu                    |  |
 +------------------+            |  | (SystemC wrapper)       |  |
 | cpu              |            |  |            :            |  |
 |(Verilog)     clk <            |  |            :   clk[bool]<---
 |             reset<            |  |    C++     : reset[bool]<---
 |           DI[7:0]<            |  | Translated :DI[uint32_t]<---
 |           DO[7:0]> --Verilator-->|  Behavior  :DO[uint32_t]>---
 |          AB[15:0]>            |  |            :AB[uint32_t]>---
 |                WE>            |  |            :    WE[bool]>---
 |               IRQ<            |  |            :   IRQ[bool]<---
 |               NMI<            |  |            :   NMI[bool]<---
 |               RDY<            |  |            :   RDY[bool]<---
 |                  |            |  |            :            |  |
 +------------------+            |  |            :            |  |
                                 |  |            :            |  |
                                 |  +-------------------------+  |
                                 |  (SystemC/C++)                |
                                 +-------------------------------+

QuestaSim의 동적 링크(Dynamic Linking)

 +-------------------+
 | sc_CPU_6502_Top   |
 +------+------------+
        |  +----------------+
        +--| mti_sim/Vcpu.h |
           +----+-----------+
                |  +-----------------+
                +--| CPU_6502.h      |                   +----------+
                   | SystemC wrapper <...Dynamic Linking |Verilog   |
                   +-----------------+    at Run-Time ...>  Binary  |
                                                         +----------+

                    +---------------------------------------+
                    |                        sc_CPU_6502_Top|
                    |                +----------------+     |
                    | +--------------+---+  Vcpu      |     |
                    | |        CPU_6502  |(SC wrapper)|     |
                    | +---+    (Verilog  |            |     |
                    +----+|      wrapper)|            |     |
                         ||              |            |     |
 +------------------+    ||              |            |     |
 |cpu               |  D ||              |            |     |
 |(Verilog)     clk <~~Y~~<_clk[bool]    |   clk[bool]<------
 |             reset<~~N~~<_reset[bool]  | reset[bool]<------
 |           DI[7:0]<~~A~~<_DI[sc_bv<8>] |DI[uint32_t]<------
 |           DO[7:0]>~~M~~>_DO[sc_bv<8>] |DO[uint32_t]>------
 |          AB[15:0]>~~I~~>_AB[sc_bv<16>]|AB[uint32_t]>------
 |                WE>~~C~~>WE[bool]      |    WE[bool]>------
 |               IRQ<~~~~<IRQ[bool]      |   IRQ[bool]<------
 |               NMI<~~L~~<NMI[bool]     |   NMI[bool]<------
 |               RDY<~~I~~<RDY[bool]     |   RDY[bool]<------
 |                  |  N ||              |            |     |
 +------------------+  K ||              |            |     |
                         ||              |            |     |
                    +----+|              |            |     |
                    | +---+              |            |     |
                    | |                  |            |     |
                    | +--------------+---+            |     |
                    |                +----------------+     |
                    | (SystemC/C++)                         |
                    +---------------------------------------+

위의 두가지 방법중 어떤 경우에도 SystemC/C++로 노출되는 모듈의 크래스는 Vcpu 이며 입출력 자료형도 동일하다. SystemC로 작성된 상위 시스템 수준 테스트 벤치에 어떤 방법을 사용하던 동일하게 적용할 수 있다.

II-4. 동기식 메모리 모델

베릴로그 RTL로 기술된 CPU 6502의 메모리 인터페이스는 동기형 방식을 사용하고 있다는 점에 유의한다. 동기식 메모리(synchronous memory) 인터페이스 타이밍은 아래와 같다. 클럭의 엣지에 맞춰 입출력이 이뤄진다. 주소는 클럭 엣지 이전에 준비되어야 하며 해당 주소의 데이터는 다음 클럭에서 사용 할 수 있다. 디지털 회로가 모두 클럭에 맞춰 동기되는 만큼(그래서 RTL 이다) 칩에 내장된 메모리의 입출력 타이밍도 이를 따른다. 입출력 타이밍을 칩 내의 플립플롭과 동기화 시켜  놓음으로써 지연이 심한 메모리의 입출력 데이타가 회로 전체에 주는 영향을 배제할 수 있다.

[이미지출처] https://docs.xilinx.com/r/en-US/am007-versal-memory/NO_CHANGE-Mode-DEFAULT

simulation/sc_mem.h

동기형 메모리의 SystemC 모델은 아래와 같다. SystemC는 매크로 정의를 적극 활용하고 있어서 마치 다른 언어로 보이지만 C++의 크래스다. 헤더 파일 sc_mem.h 에 메모리의 외형인 입출력 신호를 선언하고 소속 함수 mem_Thread()는 쓰레드로서 클럭 신호 clk 에 감응시켰다. 입력 clk에 사건이 발생하면 이에 반응하는 사건구동(event-driven)방식 시뮬레이션 모델이다.

//-------------------------------------------------------------
// Filename: sc_mem.h
SC_MODULE(sc_mem)
{
    sc_in<bool>         clk;    // Clock for Synchronous Memory model
    sc_in<uint32_t>     AB;     // Address Bus
    sc_in<uint32_t>     DI;     // Data In
    sc_out<uint32_t>    DO;     // Data Out
    sc_in<bool>         WE;     // Write Enable

    void mem_Thread();
    uint32_t* mem;

    // Util
    int ReadHEX(char* Hex_Filename, int nOffset);
    int ReadBIN(char* BIN_Filename, int nOffset);

    SC_CTOR(sc_mem) :   // Constructor
        clk("clk"),
        AB("AB"),
        DI("DI"),
        DO("DO"),
        WE("WE")
    {
        SC_THREAD(mem_Thread);
        sensitive << clk;

        mem = new uint32_t[65536];

        ReadBIN((char*)"./Apple-1/wosmon.bin", 0xFF00);
    }
};

simulation/sc_mem.cpp

SystemC의 모듈은 C++의 크래스다. 내부 구성은 하드웨어 언어의 순차처리 블럭과 동일한 방식[주3]으로 하드웨어를 기술 한다. 단, C++언어이기 때문에 근본적으로 병렬 할당문(concurrent assign)은 허용되지 않는다. 사건에 반응하여 호출될 동기식 메모리 모델의 쓰레드 함수 mem_Thread() 는 다음과 같다.

주3] 베릴로그의 always, VHDL의 process()에 주어지는 감응 리스트(sensitivity list)

// -------------------------------------------------------------
// Filename: sc_mem.cpp

unsigned char KBD_Buff[MAX_BUF] = "";

// Keyboard Thread
void* thread_keyboard(void *arg) {
    int readfd = open(A1_KEYBOARD, O_RDONLY);

    char fifoBuf[MAX_BUF];
    int n;
    while(true) {
        n = read(readfd,(char*)fifoBuf,MAX_BUF-1)); // Read from FIFO
 
        for (int i=0; i<n; i++) {
            KBD_Buff[i] = fifoBuf[i];
            KBD_Buff[i+1] = '\0';
        }
    }
}

// -----------------------------------------------
// Synchronous Memory Model & Memory mapped I/O
// -----------------------------------------------
void sc_mem::mem_Thread()
{
    uint32_t    Address;
    int         nIdx = 0;

    // FIFO(named pipe) for display terminal
    int writefd = open(A1_DISPLAY, O_WRONLY);

    // Keyboard input from another terminal
    pthread_t thread_kbd;
    pthread_create(&thread_kbd, NULL, thread_keyboard, NULL);

    while (true)    // Thread: wait forever
    {
        wait(clk.posedge_event()); wait for event on 'clk'

        Address = AB.read(); // Read Address-Bus

        if (WE.read()) // Memory Write Enable?
        {
            mem[Address] = DI.read(); // WRITE MEMORY

            // Memory Mapped I/O (Screen & Keyboard)
            if (Address == PIA_DSP_REG)    // PIA.B display register
            {
                if(write(writefd, (char*)&c, 1) < 0) // Write to FIFO
                    perror("PIA_DSP_REG: write fail");
                mem[Address] = (mem[Address] & 0x07F); // Clear B7 
            }
        }
        else // Memory Read
        {
            if (Address == PIA_KBD_REG)    // PIA.A keyboard input
            {
                switch(KBD_Buff[nIdx])
                {
                case '\0':
                    mem[PIA_KBD_CTL] = (mem[PIA_KBD_CTL]&0x3F);
                    nIdx = 0;
                    break;
                ............
                default:
                    mem[Address] = (KBD_Buff[nIdx] | 0x80);
                    mem[PIA_KBD_CTL] = (mem[PIA_KBD_CTL]|0x80);
                    KBD_Buff[nIdx++] = '\0';
                    break;
                }
            }
            
            DO.write(mem[Address]); // READ MEMORY
        }
    }
}

6502 CPU의 입출력 처리 방식은 메모리 매핑(memory mapped I/O)이다. 할당된 주소에 접근 하면 입출력 장치가 작동된다. 따라서 메모리 모델 내에 입출력 처리 부분이 포함 되어있다. Apple I 시스템에 부착된 입출력 장치는 키보드와 스크린이다. 스크린의 경우 CPU에서 출력의 시점을 결정하므로 메모리 쓰기로 단순 하지만 키보드 입력은 임의 시점에 일어나므로 단순히 메모리 읽기로 처리하기 어렵다. 임의 시점 입력을 처리하는 방법으로 인터럽트를 쓰거나 주기적으로 검사하는 폴링 방식이 있다. Apple I 컴퓨터는 키보드 입력을 입력주변장치 칩(PIA, Parallel Interface Adapter)를 부착하고 폴링 방식을 적용하고 있다. 입력이 존재 유무를 위해 PIA의 컨트롤 레지스터에 핸드 쉐이크(Hand-Shake) 비트를 두고있다.

6502 CPU의 입출력 장치는 메모리에 매핑되어 있다. Apple I 컴퓨터 시스템에는 기본 입력장치로 키보드(KBD)와 출력 장치로 디스플레이(DSP)가 각각 0xD010 와 0xD012에 할당되어 있다.

[그림출처] Apple-1 Operation Manual [바로가기]  


II-5. Apple-1 컴퓨터

CPU에 동기식 메모리 모델이 연결된 Apple-1 컴퓨터 시스템을의 구성은 아래와 같다. 6502의 입출력 장치는 메모리 매핑 이므로 메모리 모델 내에 키보드와 디스플레이 주변장치를 구현했다. 입출력 장치의 메모리 매핑은 외부 입출력 장치 편에서 다룬다.

+--Apple-1 Computer System------------------------------------------+
|                                                  sc_CPU_6502_Top  |
|                                                                   |
|    +----------------------------------------------+               |
|    |                               (SystemC)      |               |
|    +------------------+    CPU_6502  :  Vcpu      |               |
|+------------------+   |              :            |    +---------+|
||CPU_6502          |   |              :            |    |         ||
||(Verilog)     clk <---<_clk[bool]    :   clk[bool]<    |         ||
||             reset<---<_reset[bool]  : reset[bool]<    | sc_mem  ||
||           DI[7:0]<---<_DI[sc_bv<8>] :DI[uint32_t]< <==>         ||
||           DO[7:0]>--->_DO[sc_bv<8>] :DO[uint32_t]>    |         ||
||          AB[15:0]>--->_AB[sc_bv<16>]:AB[uint32_t]>  Apple-1 I/O ||
||                WE>--->WE[bool]      :    WE[bool]>  PIA_KBD: D010|
||               IRQ<---<IRQ[bool]     :   IRQ[bool]<  PIA_DSP: D012|
||               NMI<---<NMI[bool]     :   NMI[bool]<    |         ||
||               RDY<---<RDY[bool]     :   RDY[bool]<    |         ||
||                  |   |              :            |    |         ||
|+------------------+   |              :            |    |         ||
|    +------------------+              :            |    +---------+|
|    |                                 :            |               |
|    +----------------------------------------------+               |
|                                                                   |
+-------------------------------------------------------------------+

simulation/sc_main.cpp

CPU와 메모리 그리고 입출력장치로 구성된 Apple-1 컴퓨터 시스템 모델을 SystemC의 시뮬레이션 커널에 올려놓도록 한다. 여느 C/C++의 소프트웨어 처럼 main() 함수에 선언해 주는 것으로 Apple-1 컴퓨터가 사례화(instantiation[주])된다.

주] 사례화(instantiation)는 실행형 객체를 존재케 한다는 뜻이다. C++의 크래스는 데이타와 코드를 함께 묶어놓은 매우 추상적인 객체다. 사례화된 C++의 크래스는 처음 내부 조성을 상세화(elaboration) 한다. 이때 자동적으로 호출되는 소속 함수가 구성자(constructor)다. 감응될 사건(sensivity list)과 이에 반응하여 호출될 쓰레드 함수의 지정이 필수다.


    /***********************************************************
    Associated Filename: sc_main.cpp
    Purpose: sc_main()
    ************************************************************/
    #include "sc_CPU_6502_Top.h"    // Apple-1 Computer Model

    int sc_main(int argc, char** argv)
    {
        // Instantiate Apple-1 Computer
        sc_CPU_6502_Top u_sc_CPU_6502_Top("u_sc_CPU_6502_Top");

        sc_start();    
// Run Simulator
        return 0;
    }

사례화된 객체가 구성되면 sc_start()를 호출하여 시뮬레이터를 진행 시킨다. 사건구동(event-driven) 시뮬레이터는 사건의 발생과 이에 반응하는 쓰레드가 존재하는한 수행이 이어진다. 앞서 살펴본 Apple-1 컴퓨터를 묘사한 크래스 sc_CPU_6502_Top에 클럭 발생 함수가 있다.

    clk("clk", 100, SC_NS, 0.5, 0.0, SC_NS, false)

그리고 Vcpu 크래스에 이 클럭에 감응된 쓰레드가 있다. 베릴로그에서 변환된 모델의 감응지정은 아래와 같다.

    // obj_dir/Vcpu.cpp
    Vcpu::Vcpu(sc_module_name /* unused */)
        : VerilatedModel{*Verilated::threadContextp()}
        , vlSymsp{new Vcpu__Syms(contextp(), name(), this)}
        , clk{vlSymsp->TOP.clk}
            ......
    {
        ......
        // Sensitivities on all clocks and combinational inputs
        SC_METHOD(eval);
        sensitive << clk;
        ......
    }

베릴로그 시뮬레이터 QuestaSim 에 동적 링크시키는 경우 Vcpu 크래스는 자료형 변환을 위한 메쏘드 함수에 모든 입력 신호를 감응 지정했다.

    // Filename: mti_sim/Vcpu.h
    SC_MODULE(Vcpu)
    {
        SC_CTOR(Vcpu) : // constructor
            clk("clk"), reset("reset"), ......
        {
            SC_METHOD(eval);
            sensitive << clk << reset;
            sensitive << DI << IRQ << NMI << RDY;
            ......
        }
    };

베릴로그 포장기(Verilog wrapper) 크래스 CPU_6502 에는 입출력 신호 지정외에 다른 조치는 필요 없다. 신호를 넘겨주면 베릴로그 시뮬레이터가 구동될 것이기 때문이다. 결국 두개의 사건구동 시뮬레이터가 서로 맞물려 작동하는 셈이다. 시뮬레이션 실행 속도가 느려지는 이유다.

--------------------------------

[목차][이전][다음]


댓글 없음:

댓글 쓰기