[베릴로그 RTL 예제] 탁구게임기
0. 개요
베릴로그 HDL로 그리 화려하지는 않은 비디오 게임기를 만들어본다. 1인 탁구 게임기다. 마이크로 컨트롤러보드에서 C++언어로 이정도 게임기를 만들기는 그리 어려운 일은 아닐지도 모른다. 이런 동작을 하는 디지털 회로를 설계하기는 좀더 난해할 수 있다.
디지털 회로 설계에도 컴퓨팅 언어가 사용된다. 베릴로그 HDL(Hardware Description)을 사용할 것이다. 제대로 설계 되었는지 확인도 하지 않고 무턱대고 회로를 꾸밀 수는 없다. 하드웨어는 매우 단단해서 한번 잘못 만들어 놓으면 고치기는 매우 어렵다. 디지털 회로를 실제 하드웨어로 꾸며보기 전에 먼저 시뮬레이션을 해볼 텐데 프로그래밍 언어 C++와 하드웨어 묘사용 크래스 라이브러리 SystemC 를 사용한다. 동작이 확인 되면 디지털 논리 소자들은 FPGA 에 넣어 작동 시켜볼 것이다. 이에 만족하면 한걸음 더 나가 "내 칩"으로 만들어 보기로 하자. 세상에서 유일한 내가 만든 게임기 IC를 내놓을 꿈을 꾸면서 말이다. 나만의 반도체 IC 부품을 제작하려면 큰 비용이 들지만 우리나라 정부에서 학생들에게 무료로 제공하는 "내 칩 제작 서비스"를 통해 "내 칩"을 만들어 볼 수 있다. 왼쪽에 노란 딱지가 붙여진 칩이 탁구 게임기 "내 칩"이다. SOP 28 무료다.
1. 탁구대
비디오 게임기 설계를 목표하는 만큼 그래픽 시현에 대해 알아보자. 2파원 평면에 그림을 그리는 방식은 다양하지만 그중 가장 단순하면서 직관적인 것이 래스터 스캔(Raster Scan)이다. 가로와 세로로 훓어가며 그림이 될 좌표에 점을 찍는다. 그래픽 평면은 가로 128, 세로 64개의 점으로 정했다. 한 화면은 총 8192개의 점으로 구성된다. 공과 패들이 움직이는 모습을 보여 주려면 초당 10개 화면이 만들어져야 한다. 간단해 보이지만 무려 8만 1천여개의 점을 쏟아내는 하드웨어를 설계하는 것이 목표다.
움직이는 화면을 보여주려면 끊임 없이 가로 축 좌표와 세로축 좌표를 생성하고 그림의 위치에 점을 찍어 주어야 한다. 2차원 그래픽의 좌표점을 생성하고 탁구대의 벽을 그리는 하드웨어의 베릴로그 묘사는 다음과 같다. 디지털 회로의 카운터는 클럭을 받아 숫자를 센다. 가로축 좌표 x_pos 를 생성하기 위해 0부터 127까지 센다. 세로축 카운터의 숫자 범위는 0부터 63까지다. 각각 7비트와 6비트로 선언되었다. 가로 축 좌표 9에서 15 사이의 좌표점에 점을 찍어 탁구대 벽을 그리는 베릴로그 묘사는 다음과 같다.
// Filename: pong_SbS.v
// Purpose: Draw Table
//
module pong_SbS(clk, reset, x_pos, y_pos, pixel);
input clk;
input reset;
output [6:0] x_pos;
output [5:0] y_pos;
output pixel;
reg [5:0] y_pos;
reg pixel;
always @(posedge clk or posedge reset)
begin
if (reset)
begin
x_pos <= 0;
y_pos <= 0;
end
else
begin
x_pos += 1;
if (x_pos==0) y_pos += 1;
end
end
가로 축 x_pos 는 7비트로 선언되었으므로 127(=1111111b)까지 센 후 다음은 0이다. 세로 축 y_pos는 가로 선을 모두 찍고난 x_pos가 0일 때 1씩 증가한다. 6비트 이므로 63(=111111b)다음은 0이다. 세로 축 카운트가 63까지 모두 세면 한 화면의 모두 그려진 셈이다.
탁구대 평면 그림을 그리는 베릴로그를 시뮬레이션 해보자. 테스트 벤치는 SystemC 로 작성 하였다.
// Filename: sc_pong_SbS_TB.h
//
#ifndef _SC_PONG_SBS_TB_H_
#define _SC_PONG_SBS_TB_H_
#include <verilated_vcd_sc.h>
#endif
{
sc_clock clk;
sc_signal<bool> reset;
sc_signal<bool> pixel;
sc_signal<sc_uint<7> > x_pos;
sc_signal<sc_uint<6> > y_pos;
sc_trace_file* fp; // VCD file
#endif
VerilatedVcdSc* tfp; // Verilator VCD
#endif
clk("clk", 100, SC_NS, 0.5, 0.0, SC_NS, false)
{
SC_THREAD(Test_Gen);
sensitive << clk;
// Instantiate DUT ----------
u_pong_SbS = new Vpong_SbS("u_pong_SbS");
u_pong_SbS->clk(clk);
u_pong_SbS->reset(reset);
u_pong_SbS->x_pos(x_pos);
u_pong_SbS->y_pos(y_pos);
u_pong_SbS->pixel(pixel);
// VCD Trace
fp = sc_create_vcd_trace_file("sc_pong_SbS_TB");
fp->set_time_unit(100, SC_PS);
sc_trace(fp, clk, "clk");
sc_trace(fp, reset, "reset");
sc_trace(fp, x_pos, "x_pos");
sc_trace(fp, y_pos, "y_pos");
sc_trace(fp, pixel, "pixel");
#endif
// Trace Verilated Verilog internals
Verilated::traceEverOn(true);
tfp = new VerilatedVcdSc;
sc_start(SC_ZERO_TIME);
u_pong_SbS->trace(tfp, 99); // Trace levels of hierarchy
tfp->open("Vpong_SbS.vcd");
#endif
}
};
#endif
SystemC는 C++ 로 하드웨어를 묘사하기 위해 만들어진 크래스 라이브러리다. 만일 SystemC가 생소하다면 유튜브 동영상 "Learn SystemC"[링크]를 보도록 한다. 10여분짜리 6편으로 구성된 이 동영상은 SystemC를 간략하게 핵심만 짚어 설명한다. 처음 세편 정도만 들어보는 것 만으로 시작하기에 충분하다.
탁구대 그리기를 기술한 베릴로그 pong_SbS.v의 테스트벤치를 살펴보자. C++의 크래스를 사용하기 위해 먼저 SystemC를 인클루드 한다.
#include <systemc.h>
베릴로그 HDL로 묘사된 하드웨어를 SystemC의 테스트벤치로 불러오려면 언어변환 되어야 한다. 베릴레이터(Verilator)는 오픈-소스 언어변환기로서 베릴로그를 SystemC의 C++ 크래스로 변환해준다. C++의 SystemC 크래스로 변환된 베릴로그의 파일들은 현재 작업 디렉토리의 obj_drv에 저장된다. 언어 변환한 C++ 헤더 파일 명은 베릴로그 파일 앞에 대문자 V 가 붙는다. SystemC 테스트벤치에서 C++로 변환된 베릴로그의 헤더 파일을 인클루드 한다.
#include "Vpong_SbS.h"
테스트벤치 모듈은 SystemC의 크래스 sc_pong_SbS_TB 다. 크래스와 동일한 이름의 구성자(constructor)가 존재한다.
SC_MODULE(sc_pong_SbS_TB)
{
SC_CTOR(sc_pong_SbS_TB):
clk("clk", 100, SC_NS, 0.5, 0.0, SC_NS, false)
{
......
}
};
구성자가 수행할 주요 임무 중 하나는 사건(event)과 이에 반응할 콜백(call-back) 함수의 지정이다. 클럭 신호 clk에 발생하는 사건에 감응되어 반응할 콜백 함수 Test_Gen() 를 아래와 같이 지정하였다.
SC_THREAD(Test_Gen);
sensitive << clk;
시험의 대상 DUT(Design Under Test)는 포인터 크래스로 선언되었다.
Vpong_SbS* u_pong_SbS;
DUT를 사례화(instantiate)한 후 지역 하드웨어 객체에 연결(mapping)도 구성자 내에서 이뤄진다.
// Instantiate DUT ----------
u_pong_SbS = new Vpong_SbS("u_pong_SbS");
u_pong_SbS->clk(clk);
u_pong_SbS->reset(reset);
u_pong_SbS->x_pos(x_pos);
u_pong_SbS->y_pos(y_pos);
u_pong_SbS->pixel(pixel);
모듈 내의 지역 하드웨어 객체(베릴로그의 wire 또는 reg에 해당)들은 미리 선언 되었다.
sc_clock clk;
sc_signal<bool> reset;
sc_signal<bool> pixel;
sc_signal<sc_uint<7> > x_pos;
sc_signal<sc_uint<6> > y_pos;
주기적인 클럭을 선언하기 위한 객체는 sc_clock 이다. 주기(period)와 비율(duty ratio)등 클럭의 규격은 구성자와 함께 선언되었다. 다음은 테스트벤치의 구성자에서 주기 100 나노 초, 듀티 비 50% 인 클럭 발생기의 초기화 선언이다.
clk("clk", 100, SC_NS, 0.5, 0.0, SC_NS, false)
하드웨어 신호 clk에 발생하는 사건에 감응된 콜백 함수 Test_Gen()는 별도의 sc_pong_SbS_TB.cpp에 기술하였다.
// Filename: sc_pong_SbS_TB.cpp
//
#include "sc_pong_SbS_TB.h"
{
reset.write(true);
wait(clk.posedge_event());
wait(clk.posedge_event());
wait(clk.posedge_event());
reset.write(false);
{
wait(clk.posedge_event());
if (x_pos.read()==127 && y_pos.read()==63)
{
wait(5000, SC_NS);
sc_stop();
}
}
}




