PLI에서 TCP/IP를 통해서 통신하고, perl server에서 처리하기

예전에 PLI에서 윈도우 제어 하려고 별짓을 다했었는데, 그 중에 PLI에서 TK 윈도우를 바로 부른 것도 있었습니다. PLI에서 TK를 부르는(C-TK interwork을 이용한) 방법은 TK 스크립을 거의 직접 쓸 수 있다는 점에서 편리하긴 한데, NCVerilog에서 너무 버전을 심하게 탄다는 단점(TK의 버전도 맞춰 줘야 합니다. -_-;)이 있어서 환경이 바뀌면서 잘 안쓰게 되더군요.

게다가 시뮬레이션 돌리면서 이런 저런것을 실시간 출력할때 UNIX/Linux/Windows 안가리는 인터페이스를 고민하다보니, TCP/IP와 Perl 만한게 없더군요. Simulator에서 이런 저런 GUI 부분이 귀찮아서 socket 기반으로 만들었던 기억을 되살려 하나 만들어봤습니다. PLI에서 TCP/IP 패킷을 전송하는 부분을 하나 만들어봤습니다.

#include 
#include <"arpa/inet.h">
#include <"net/netinet.h">
#include <"sys/socket.h">
#include <"sys/types.h"> #include "vpi_user.h"

#define DEST_IP "127.0.0.1"
#define DEST_PORT 7890

static struct sockaddr_in dest_addr;

static int count = 0;
static int sockfd;
int counta;

PLI_INT32 cosim_hello_calltf(PLI_BYTE8 *userdata) {
        char buf[1024];
        s_vpi_time ctime;
        ctime.type = vpiScaledRealTime;
        vpi_get_time(NULL, &ctime);
        vpi_printf("[%2.2f] Hello VPI: %d, %d\n", ctime.real, count, counta);
        sprintf(buf, "\n##[%2.2f] Hello VPI: %d, %d\n", ctime.real, count, counta);
        send(sockfd, &buf, sizeof(buf), 0);

        count++;
        counta++;
        return(0);
}

PLI_INT32 cosim_hello_init() {
        int len, result;
        counta = 100;
        sockfd = socket(AF_INET, SOCK_STREAM, 0);

        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(DEST_PORT);
        dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);

        len = sizeof(dest_addr);
        result = connect(sockfd, (struct sockaddr *)&dest_addr, len);

        if (result == -1) {
                fprintf(stderr, "socket open error\n");
                exit(1);
        }

        return(0);
}

void cosim_register_hello() {
        s_vpi_systf_data tf_data;
        tf_data.type = vpiSysTask; // make as task
        tf_data.sysfunctype = 0;
        tf_data.tfname = "$cosim_hello";
        tf_data.calltf = cosim_hello_calltf;
        tf_data.compiletf = cosim_hello_init;
        tf_data.sizetf = NULL;
        tf_data.user_data = NULL;
        vpi_register_systf(&tf_data);
}

void (*vlog_startup_routines[])() = {cosim_register_hello, 0}


별 내용은 없고, 그냥 verilog code에서 cosim_hello()를 호출하면 loop돌면서 값을 출력하는 예제입니다.

 

이 코드 틀은 다이나릿의 기안도 박사님 IDEC 강좌 자료에 있는 “HW/SW 동시 협조 시뮬레이션”이란 강좌의 첫번째 PLI 예제에서 따왔으며, 저는 이 함수에 TCP/IP 전송이 가능하도록 수정하였습니다.

컴파일은 다음과 같이 일반적인 NCVerilog 컴파일과 다르지 않지요. (Windows에서 Modelsim 사용하시는 분은 gcc보다는 visual c++의 cl 을 사용하시는 것이 속 편합니다. MingW 버전의 gcc가 되기는 하는데, Modelsim에서 버전을 상당히 심하게 탔던 것으로 기억됩니다. 요즘 버전은 어떨지 모르겠습니다만. )

 

$gcc -I$CDS_HOME/tools/inca/include -c cosim_hello.c
$ld -shared -o cosim.so cosim_hello.o

NCVerilog Compile & Elaboration

 

$ ncvlog -work worklib test_hello.v
$ ncelab worklib.test_hello -loadvpi ./cosim:cosim_register_hello
$ ncsim worklib.test_hello


이런 식으로 사용하면 되는데, 위의 패킷을 받아줄 서버는 간단히 perl로 짜주면 됩니다. Perl-TK로 GUI를 작성하는 것도 가능하구요.

이때 한가지 주의해야 할 점은 perl의 IO::INET 의 accept() 함수가 blocking type이기 때문에 이것을 non-blocking type으로 해 주시고 loop을 돌려야지만 DoOneEvent() 함수가 정상적으로 수행된다는 점이지요.
(설마 MainLoop()로 돌리실 분은 없을 테니 ^^;) 저도 처음엔 DoOneEvent함수가 좀처럼 동작을 안하는 것처럼 느껴져서 헤맸습니다. 결론은 accept()함수 문제더군요.

perl 함수의 주요 부분만간단히 정리하자면(예제를 위해서 perl script를 하나 더 만들기가 귀찮고, 전체 내용은 회사 작업이라 공개할 수 없고.. 라는 어려움 때문에 별로 문제 안되는 부분만 올립니다. )



use strict;
use IO::Socket;
use Tk;
use encoding 'utf-8';    # 한글 쓰시려면..
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);

my $portnum = 7890;

# make a socket

my $mw = MainWindow->new(-title=>"Terminal");

.....

my $sock = IO::Socket::INET->new(
                LocalHost => 'localhost',
                LocalPort => $portnum,
                Proto => 'tcp',
                Listen => 10, # SOMAXCONN 으로 해도 됩니다.
                Reuse => 1,
                TimeOut => 1,
                );

....
my($new_sock, $c_addr, $buf, $flag);
...

# NonBlocking으로 만듭시다.
$flag = fcntl($sock, F_GETFL, 0) or die "Can not get flag: $!\n";
$flag = fcntl($sock, F_SETFL, $flag | O_NONBLOCK) or die "Can not set flag: $!\n";

while(1) {
        while (($new_sock, $c_addr) = $sock->accept()) {
                ... 소켓에서 읽어서 일하세요....
                        DoOneEvent(0);
        }

 

요런 식이라는 것이지요. 중간 중간에 엄청 생략되어 있음은 유의하세요..

집중이 안되는 여름

연일 30도를 넘나드는 더위가 계속되고 있습니다.


이럴때 항상 문제가 되는 것이 집중력이 떨어진다는 건데요.. 저도 마찬가지 입니다.
(실은 개인적으로 좋은 일이 생겨서 그럴지도 모르겠습니다만 ^^;)


오늘만해도 gcc-MinGW에서 mti vpi 연결시키는 거 때문에 잠깐 modelsim userguide를 보다가, 딴짓을 하기 시작해서 대략한 5시간동안 딴짓을 했습니다.


뭐 딴짓이라고 해도 모레쯤이나 하려던 processor market forecasting자료 정리였는데, 어짜다보니 하고 있더군요. 이건 뭐, 원래 하려던 일은 까맣게 잊고 나중에 하려던 일을 한참하다가 “아.. 내가 오늘 하려던 일은 이게 아닌데.. ” 뭐 이런 시나리오라고나 할까요.


VPI 이야기가 나와서 그러는데요.. (또또.. 옆길로 빠집니다요) 회사 툴이 System Verilog-verification feature를 지원해주지 않아서, C/PLI나 만들어야 겠다는 이야기를 했었죠? 요즘 teal library를 분석하고 있는 중인데, 이거야 원.. 보면서 몇 번을 감탄한 것인지 모르겠습니다.


사실 뭐 PLI 연결하는 것이야 별 차이있겠습니까? 뭐 thread를 사용하는 것도 그렇다치구요.. 근데, PLI 연결 부분을 teal_synch.cpp라는 몰아두고, 이걸 class로 덮어씌우고 연산자 오버로딩을 해버리니 예술적인 경지에 이르르더군요. 사용하는 사람 입장에서는 이 부분이 verilog랑 붙기 때문에 어쩌구 저쩌구 생각할 필요가 거의 없도록 만들어졌더군요.


아쉽게도 저는 유연성 보다는 빠른 동작을 즐기기 때문에 teal을 그대로 이용하지는 않겠지만, (처음에는) 재미삼아 분석해 본 라이브러리인데 아주 많은 걸 배웠습니다. 천재성이 담긴 코드에요.. 저에겐 ‘이 정도쯤은 해야 verification engineer가 되는 건가.. 에효..’ 라는 상념에 빠지게 하더군요..


C++에 익숙하시고, PLI를 공부중인 분들은 상당히 도움을 받으실 수 있을 것 같습니다. 혹시라도 관심있으신분은 http://www.trusster.com/ 에서 다운 받아보실 수 있습니다. 이거 관련된 책도 있는데, 아직은 저도 본 상태가 아니라 뭐라 말씀드리기는 어렵습니다. (TRUSS도 아직 사용해보지 못했습니만, Teal만으로도 괜찮습니다 ^^;)


아.. 그러고보니 관련 기사도 있었군요.. EETimes Korea의 기사입니다.


위의 부분까지는 MS Writer에서 썼는데, 평소처럼 publish하려니 좀 허전하네요.. ^^;

혹시라도 PC환경에서 cygwin/modelsim하에서 사용하시는 분들을 위하여 몇 가지만

1) Makefile.Windows를 만듭니다. 이 파일은 Makefile.linux를 복사하시는 것이 편합니다.
2) 아래와 같이 고칩니다. (필요에 따라 더 고치셔도 됩니다.)


ARCH_LIB_OPT = -L/cygdrive/c/Modeltech_6.0/win32 -lmtipli
ARCH_SHARED_SUFFIX = dll
SYS_ARCH_CC = g++
              

3) Makefile을 수정합니다.


SIMULATOR_HOME = /cygdrive/c/Modeltech_6.0
ARCH    = Windows
SIM     = mti_2_0



대략적인 힌트만 드렸습니다만, 이 정도면 필요한 부분을 추가적으로 수정하셔서 verification_top()함수를 작성하신 다음에 linking하시는데 문제 없을듯 합니다.

PLI와 Simulator의 연결(I)

Automated Functional Verification 방법에는 여러 가지가 있지만, testvector 발생 유닛(보통 Directed Random방식을 사용하지요?)과 golden model을 이용한 checker model을 만들어서 DUV(Design Under Verification)의 결과와 비교하는 것이 가장 편한 방법 중에 하나임은 부정할 수 없습니다. (여담입니다만, 국내에서는 많은 경우 golden model없이 설계하는 경우가 많아서 검증을 위하여 작성한 golden model이 실제로 RTL보다도 정확성이 떨어지는 경우가 있다는 것이 문제가 종종 발생합니다. 여기서는 golden모델의 확보에 대한 이야기는 나중으로 미루죠.)보통 golden model은 C model을 이용하게 되는데, C 모델을 Verilog와 동시에 simulation하는 것은 그리 녹녹한 일이 아닙니다.


저는 프로세서를 대부분 다루기 때문에 C모델이라 함이 대부분 ISS simulator가 됩니다. 이후에 Simulator와 C모델은 그냥 섞어 쓸 가능성이 높은데, 보시는 분께서 편하신 대로 생각하시면 되겠습니다.


우선 Simulator를 만들 때 그 목적을 정확히 할 필요가 있습니다. 초기에 Simulator의 목적은 executable spec.의 의미가 가장 중요한 의미였을 것입니다. 그래서, 대부분 function level의 정확성을 가지지요. 프로세서의 경우 보통 이야기하는 ISS(Instruction Set Simulator)정도의 수준일 것입니다.


이때 고려하는 사항은 동작의 정확성, 빠른 동작 속도, 유연한 변경 가능성(design space exploration을 해야 하니까요)과 같은 것을 고려하게 됩니다.


그런데, 아시다시피 Verilog와 Simulation을 한다던지, Verilog Model대신 사용하려고 할 경우에는 ISS level뿐만 아니라, BFM 수준, 간혹은 Pin-level accuracy를 필요하게 됩니다. 통신이나 영상쪽의 모델은 뭐 Functional model이나 BFM이나 큰 어려움이 없습니다. Latency가 거의 정의되어 있기 때문이지요.


프로세서의 경우 약간 복잡해지는데, hazard의 발생, instruction issue rate의 변화, exception의 발생을 고려해야 하는데, 이 경우 bus function이 발생하는 Instruction Fetch와 Data Access stage의 동작을 모사하기 위해서는 대부분의 pipeline을 표현해내야 합니다. 예전에 pipeline 수준의 accuracy를 가지는 simulator를 만들고, pin-level interface를 붙여서 나름대로 쓸만한 PLI model을 만든 적이 있지요. 단지 문제는 pipeline수준의 accuracy를 가지다 보니, 너무 너무 느려져 버린거지요.


쉽게 쉽게 만들려면 functional model로 simulator를 만들고, Verilog Model(DUV)상에 하나의 명령이 retire되는 순간에 register들의 값을 비교하는 방법도 가능합니다. 하지만, 해당 model이 불필요한 hazard 발생은 없는지, Instruction Fetching에 불필요한 사항이 추가되지는 않았는지 확인 할 수는 없습니다. (당연하죠.. reference model이 functional model이니 timing spec.을 만족시켰는지는 알수 없는것이지요)


흠.. 많이 옆으로 샜는데요..


C Model과 Verilog와 붙이는 방법이 Verilog-PLI (Programming Language Interface)를 이용하는 겁니다. Simulator는 clock단위로 동작하므로, 느낌상 아래와 같이 동작시키면 될 것 같습니다. 이런 경우에 verilog에서 C function을 호출할 때 가장 많이 사용되는 건 calltf()를 이용하는 방법입니다.



always @(posedge clk or negedge rst_x) begin


  $run_sim_calltf(xxxx);


end


즉, run_sim()을 calltf()의 callback function으로 등록하는 겁니다. 그리고, 매 클럭 calltf()를 불러주는 것이지요.


근데, simualtor의 이전 상태를 계속적으로 보존해야 하는 경우에는 매 클럭 새롭게 호출되는 calltf()를 이용할 때 문제가 있을 수 있습니다. (사실 그리 어려운 일은 아닙니다만, 예를 들기 위하여 ^^ ) 그래서, 내용을 보존하고 싶을 때는 misctf()를 사용하는 것도 괜찮습니다. misctf()는 원래 verilog simulation의 이런 저런 정리 작업을 하는데 사용하는 목적으로 만드는 건데요. 아래와 같이도 사용할 수 있습니다.



initial begin


  $run_sim_misctf(data, reason, paramvc);


end


뭐 이런 느낌입니다. misctf()의 경우 시뮬레이터의 초기 시에 simulator에 연결된 이후에 simulator의 종료 시까지 계속 머무르면서 파라미터의 값이 변경 될 때 마다 제어권을 가집니다. 이 파라미터의 변화시마다 클럭이 변화되었는지 확인하고, 클럭이 변화하였을 때 값을 호출하면 되겠습니다.


아래는 한 4년 전에 만든 PLI 모델중의 misctf부분인데요.. 실제 구현은 없으니 공개해도 별 문제 없을 것입니다. ^^; 대략 이런 느낌으로 만드시면 됩니다. 🙂
다음 번에는 좀 더 재미난 PLI 함수를 다루어보죠.. (아.. VPI는 초반에 좀 다루지 않네요. ^^; Blog의 예제를 위해서라도 예전에 acc_와 tf_를 이용해서 만든 PLI 모델을 업그레이드해야겠군요. )



int misctf_proc(int data, int reason,int paramvc) {
 
 static int  reset_called = FALSE;
 int   POInt;
 
 
 
 switch (reason) {
  case reason_paramvc : { // 파라미터의 값 변화로 인한 호출의 경우
   if (tf_getp(PIN_RST_X) == 0) { // reset state
    // 아래의 형태는 초기 조건에서의 리셋 호출을 위하여 사용된다.
    if (reset_called == FALSE || paramvc == PIN_RST_X) {
     io_printf(“$AE32KB_RUN : CORE RESET CONDITION\n”);
     // 실제적인 reset을 수행한다.
     
     reset_called = TRUE;
     
     POInt = 0;
     
     if (tf_getp(PIN_OSIEN) == 1) {
      POInt = POInt | OSIEN;
      if (tf_getp(PIN_OSIROM) == 1) {
       POInt = POInt | OSIROM;
      }
     }
     ResetCore(POInt);
    }
   }
   else if ( paramvc == PIN_CLK ) {  
    if (is_posedge(PIN_CLK,paramvc) == TRUE) {
     // 변경된 모든 입력 값을 받아서 반영한다. 
     apply_input();
     io_printf(“%s : CLOCK POSEDGE\n”, tf_strgettime());
     
     EndClock();
     // end_clock함수
     
     StartClock();
     // start clock 함수
     
     // 변경된 데이터를 기반으로 핀과 레지스터를 변경시킨다.
    }
   }
   break;
  }
//  case
  default :
  break;
 }
 
 // apply outputs
 apply_output();
    return 0;
}


이제 EISC processor도 어느 정도 정비를 마치고, 빠른 미래에  좀 더 공격적인 마케팅을 시작할 예정입니다. ^^; 많이 기대를 해 주세요.. 특히 학생분들께 좋은 기회가 많이 돌아가도록 노력 중입니다.