[Verilator] make_hello_c 코드 분석 및 counter 구현

YumeIroVillain·2021년 12월 20일
0

6.5학기

목록 보기
7/20

앞서

코드의 의미를 분석해보고자 한다.
참고했던 네이버 블로그와 내용이 다를텐데,
이는 verilator 버전이 1년이 넘어 대대적으로 바뀌었기 때문으로 추정한다.

우선 make_hello_c를 ~/my_prj 로 복사한다.

cp -rf ./make_hello_c ~/my_prj

-f없으면 mkdir 따로 해야해서 귀찮을거다.

아무튼,
우리의 top.v 는 아래와 같은데

module top;
  initial begin
    $display("Hello World!");
    $finish;
  end
endmodule

테스트벤치 역할을 하는 sim_main.cpp 는 아래와 같다.

#include <verilated.h>
#include "Vtop.h"

int main(int argc, char** argv, char** env) {
    if (false && argc && argv && env) {}
    Vtop* top = new Vtop;

    // Simulate until $finish
    while (!Verilated::gotFinish()) {
        top->eval();
    }

    // Final model cleanup
    top->final();

    // Destroy model
    delete top;

    // Return good completion status
    return 0;
}

분석하려면 뭐부터 봐야할까? 당연히 코드 훑기->헤더->각각의 변수 및 class들의 원형 추적 순이다.
코드를 훑어보자.
Vtop* 가 선언되고, while 문을 finish 가 감지될 때까지 돌면서 eval 을 시전하고, 종료전에 final 호출 뒤 delete, return 한다.

근데 Vtop.h 가 같은 폴더에 없다?
Vtop.h 는 우리가 앞서 make 를 했을 때 obj_dir 에 생성된다.
즉, Vtop.h 는 make 를 통해 생성되며 make 는 .v 파일을 verilator 가 어찌어찌하는 그런 구조로 되어있다.

Vtop.h 는 아래와 같다.

#ifndef VERILATED_VTOP_H_
#define VERILATED_VTOP_H_  // guard

#include "verilated.h"

class Vtop__Syms;
class Vtop___024root;

// This class is the main interface to the Verilated model
class Vtop VL_NOT_FINAL {
  private:
    // Symbol table holding complete model state (owned by this class)
    Vtop__Syms* const vlSymsp;

  public:

    Vtop___024root* const rootp;

    explicit Vtop(VerilatedContext* contextp, const char* name = "TOP");
    explicit Vtop(const char* name = "TOP");
    virtual ~Vtop();
  private:
    VL_UNCOPYABLE(Vtop);  ///< Copying not allowed

  public:
    // API METHODS
    /// Evaluate the model.  Application must call when inputs change.
    void eval() { eval_step(); }
    /// Evaluate when calling multiple units/models per time step.
    void eval_step();
    /// Evaluate at end of a timestep for tracing, when using eval_step().
    /// Application must call after all eval() and before time changes.
    void eval_end_step() {}
    /// Simulation complete, run final blocks.  Application must call on completion.
    void final();
    /// Return current simulation context for this model.
    /// Used to get to e.g. simulation time via contextp()->time()
    VerilatedContext* contextp() const;
    /// Retrieve name of this model instance (as passed to constructor).
    const char* name() const;
} VL_ATTR_ALIGNED(VL_CACHE_LINE_BYTES);

#endif  // guard

메소드들이 선언되어있고,
이것이 포함된 VL_NOT_FINAL 매크로가 있다.
일반적으로, class a b {...} 는 invalid syntax이나,
이 경우 b가 사실 매크로로써, uniform initialization 의 기능을 한다고 한다. 이것은 좀 더 읽어봐야겠다.(C++은 파고들수록 언어자체가 난해하다.)


<의문>

class a b {...} 는 invalid syntax 아닌가 싶었는데
스택오버 검색해보니 위의 경우도 b가 사실은 매크로로써, valid 한 문법이라고 한다.
그래서 b의 매크로가 뭔지 추적해서 까보니 그냥 아무것도 선언되어있지 않다.
도대체 왜 이렇게 짠거지?

어쨌든, 부차적인 문제니 의문으로 일단 남겨두고 전진한다.
<의문 해결>
final 키워드를 못쓰던 시절 공백 define 문으로 저렇게 표시하고자 남겨뒀다는 의견을 들었다.
확실히 그 말이 맞는 것 같다.
덕분에 C++에서 final 키워드의 존재도 알게 되었다.
내가 모르는게 참 많다..


이제는 counter 를 구현해보도록 한다.
여태 우리는 input도 output도 없는 정말 허수아비 모듈만 구현했는데, input 과 output의 선언이 이루어진 cnt.v 파일이 어떻게 Vcnt.h 형태로 영향이 나타날지
그리고 Makefile 은 어떤 식으로 수정을 해야 할지,
그리고 sim_main.cpp 는 어떤 식으로 수정을 해야할지를 조사할 것이다.

cp -rf make_hello_c/ 1_counter 로 새 폴더에 전부 붙여넣고,
vi cnt.v 로 아래와 같이 선언한다.

module cnt
    (
        input clk,
        input rstn,
        output reg[7:0] out
    );

    always @(posedge clk, negedge rstn) begin
        if(!rstn) begin
            out<=0;
        end
        else begin
            if(out<250)
                out<=out+1;
            else
                out<=0;
        end
    end

endmodule

터무니없이 간단한 코드다.

Makefile 에서
top.v -> cnt.v
obj_dir/Vtop -> obj_dir/Vcnt 로 바꿔준다.

sim_main.cpp 에서
#include "Vtop.h" -> #include "Vcnt.h" 로 바꿔준다.
Vtop top = new Vtop; -> Vcnt top = new Vcnt; 로 바꿔준다.

make clean 해서 기존 obj_dir 있으면 날려주고
이제 make 를 갈겨준다.

무한루프를 도는 것을 확인할 수 있는데,
이는 우리가 cnt.v 에 top.v때와 달리 인위적인 $finish 를 넣지 않아줘서 그런 것에 불과하다.
터트리고 나오자.

obj_dir 가 생성되었는데, 까보자.

// Verilated -*- C++ -*-
// DESCRIPTION: Verilator output: Primary model header
//
// This header should be included by all source files instantiating the design.
// The class here is then constructed to instantiate the design.
// See the Verilator manual for examples.

#ifndef VERILATED_VCNT_H_
#define VERILATED_VCNT_H_  // guard

#include "verilated.h"

class Vcnt__Syms;
class Vcnt___024root;

// This class is the main interface to the Verilated model
class Vcnt VL_NOT_FINAL {
  private:
    // Symbol table holding complete model state (owned by this class)
    Vcnt__Syms* const vlSymsp;

  public:

    // PORTS
    // The application code writes and reads these signals to
    // propagate new values into/out from the Verilated model.
    VL_IN8(&clk,0,0);
    VL_IN8(&rstn,0,0);
    VL_OUT8(&out,7,0);

    // CELLS
    // Public to allow access to /* verilator public */ items.
    // Otherwise the application code can consider these internals.

    // Root instance pointer to allow access to model internals,
    // including inlined /* verilator public_flat_* */ items.
    Vcnt___024root* const rootp;

    // CONSTRUCTORS
    /// Construct the model; called by application code
    /// If contextp is null, then the model will use the default global context
    /// If name is "", then makes a wrapper with a
    /// single model invisible with respect to DPI scope names.
    explicit Vcnt(VerilatedContext* contextp, const char* name = "TOP");
    explicit Vcnt(const char* name = "TOP");
    /// Destroy the model; called (often implicitly) by application code
    virtual ~Vcnt();
  private:
    VL_UNCOPYABLE(Vcnt);  ///< Copying not allowed

  public:
    // API METHODS
    /// Evaluate the model.  Application must call when inputs change.
    void eval() { eval_step(); }
    /// Evaluate when calling multiple units/models per time step.
    void eval_step();
    /// Evaluate at end of a timestep for tracing, when using eval_step().
    /// Application must call after all eval() and before time changes.
    void eval_end_step() {}
    /// Simulation complete, run final blocks.  Application must call on completion.
    void final();
    /// Return current simulation context for this model.
    /// Used to get to e.g. simulation time via contextp()->time()
    VerilatedContext* contextp() const;
    /// Retrieve name of this model instance (as passed to constructor).
    const char* name() const;
} VL_ATTR_ALIGNED(VL_CACHE_LINE_BYTES);

#endif  // guard

달라진 점이 보일텐데,

    VL_IN8(&clk,0,0);
    VL_IN8(&rstn,0,0);
    VL_OUT8(&out,7,0);



만큼 달라졌다. Vcnt로 변수명 싸그리 바뀐 것 제외하고는 위가 유일하다.
그럼 VL_IN8 은 딱봐도 8비트짜리 input line 일텐데 이것의 C++ 원형은 무엇인가?
아래에 기술한다.


verilator/include/verilated.h 를 까보면 자료형의 원형을 알 수 있다.

1~8비트면 CData
9~16비트면 S
I, Q, E, 그 이상이면 포인터형태로써 넘겨지며 W
이런식으로 Prefix 가 붙은 자료형으로 vluint8_t 와 같은 꼴로 내부적으로 유지가 되는 것을 확인할 수 있고, 이는 이따 체크해볼 수 있다.

vluint8_t 같은 자료형의 원형은 verilator/include/verilatedos.h 에서 확인할 수 있으며
아키텍쳐에 따라 다른 모양이지만

처럼 결국 char, short int, long long 등등으로 그 크기에 맞춰 선언되어있음을 확인할 수 있다.


eval의 역할이 무엇인지 알고싶다면, 위와 같이 sim_main.cpp를 변형해보자.

// DESCRIPTION: Verilator: Verilog example module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2017 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
//======================================================================

// Include common routines
#include <verilated.h>

// Include model header, generated from Verilating "top.v"
#include "Vcnt.h"

int main(int argc, char** argv, char** env) {
    // See a similar example walkthrough in the verilator manpage.

    // This is intended to be a minimal example.  Before copying this to start a
    // real project, it is better to start with a more complete example,
    // e.g. examples/c_tracing.

    // Prevent unused variable warnings
    if (false && argc && argv && env) {}

    // Construct the Verilated model, from Vtop.h generated from Verilating "top.v"
    Vcnt* top = new Vcnt;

    printf("cnt value = %d\n", top->out);
    top->rstn=1;
    top->eval();
    top->rstn=0;
    top->eval();
    top->rstn=1;
    top->eval();
    printf("cnt value = %d\n", top->out);

    // Final model cleanup
    top->final();

    // Destroy model
    delete top;

    // Return good completion status
    return 0;
}

위를 보면 정말 modelsim보다 편리한 점을 바로 확인할 수 있다.

    printf("cnt value = %d\n", top->out);
    top->rstn=1;
    top->eval();
    top->rstn=0;
    top->eval();
    top->rstn=1;
    top->eval();
    printf("cnt value = %d\n", top->out);

로 변수의 로그를 바로 찍을 수 있고, 변수를 바로 튕겨줄 수 있고, eval 때리는 시점을 내가 직접 정할 수 있다.
리셋을 1로 해주고 0으로 해주고 다시 1로 해주는 것에 불과하다.

카운터니까 클럭이 튕길때마다 1씩 올라간다.
이를 확인하기 위해 아예 for문을 적어보자.

// DESCRIPTION: Verilator: Verilog example module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2017 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
//======================================================================

// Include common routines
#include <verilated.h>

// Include model header, generated from Verilating "top.v"
#include "Vcnt.h"

int main(int argc, char** argv, char** env) {
    // See a similar example walkthrough in the verilator manpage.

    // This is intended to be a minimal example.  Before copying this to start a
    // real project, it is better to start with a more complete example,
    // e.g. examples/c_tracing.

    // Prevent unused variable warnings
    if (false && argc && argv && env) {}

    // Construct the Verilated model, from Vtop.h generated from Verilating "top.v"
    Vcnt* top = new Vcnt;

    printf("cnt value = %d\n", top->out);
    top->rstn=1;
    top->eval();
    top->rstn=0;
    top->eval();
    top->rstn=1;
    top->eval();
    printf("cnt value = %d\n", top->out);

    for(int i=0;i<252;i++){
        top->clk = 0;
        top->eval();
        top->clk = 1;
        top->eval();
        printf("cnt value = %d\n", top->out);
    }

    // Final model cleanup
    top->final();

    // Destroy model
    delete top;

    // Return good completion status
    return 0;
}

252번 조낸 clk을 튕긴다.
make로 확인해보자.

의도한 바와 같이 잘 작동함을 확인할 수 있다.
이로써 eval() 메소드에 대한 감을 잡을 수 있다.
다음에는 verilator의 makefile 에 --trace 옵션을 줘서 .vcd 파일을 만든 뒤, gtkwave를 통한 wave 분석을 확인할 것이다.

profile
HW SW 둘다 공부하는 혼종의 넋두리 블로그 / SKKU SSE 17 / SWM 11th

0개의 댓글