[베릴로그 RTL 예제] 탁구 게임기 -3편: 탁구대의 그래픽 테스트 벤치-
목차:
1. RTL 베릴로그로 "탁구대" 그리기
2. 그래픽 LCD 구동 칩의 시뮬레이션 모델
3. "탁구대"의 그래픽 테스트 벤치
3-1. 핸드 쉐이크
3-2. RTL 베릴로그 "탁구대"
3-3. 그래픽 LCD 인터페이스 모델
3-4. 실습 및 과제
-------------------------------------------------------
3. "탁구대"의 그래픽 테스트 벤치
베릴로그로 기술한 설계의 시뮬레이션의 출력은 디지털 파형으로 기록되곤 한다. 멀티 미디어, 디지털 신호처리 같은 대량의 입출력을 취급하는 설계에서 클럭마다 일일이 확인하여 그 결과를 판단하기는 어렵다. "탁구 게임기"는 작은 규모지만 비디오 신호를 다룬다. 클럭 마다 생성되는 RTL 설계의 화소를 2차원 그래픽 화면에 시현하므로써 효과적으로 비디오 게임기의 동작을 검증 할 수 있다. RTL 베릴로그로 기술한 "탁구대"를 그래픽 LCD를 갖춘 테스트벤치로 시뮬레이션 해보자.
3-1. 핸드 쉐이크
앞서 기술한 RTL "탁구 게임기"는 매 클럭 마다 그래픽 신호(좌표 및 화소값)를 출력한다. 이와는 달리 그래픽 LCD는 좌표를 받아 한 화소를 표시하기 위해 여러 클럭에 걸쳐 순차적으로 동작한다. 핸드쉐이크(hand-shake)는 서로다른 입출력 처리율(throughput)을 가진 두 장치 사이의 정보 전달을 위한 규약이다. 처리율이 낮은 외부 장치가 준비되었을 때 비로서 출력을 낼 수 있는 핸드쉐이크 동작을 디지털 회로로 구현하는 유용한 기법은 유한상태 머신(FSM, Finite State Machine)이다.
핸드 쉐이크 FSM은 그래픽 LCD에서 표시 절차가 진행 중 임을 표시하는 busy 가 0일 때 이 작동하여 p_tick으로 유효한 픽셀 값의 출력을 알린다. FSM의 상태도는 다음과 같다.
3-2. RTL 베릴로그 "탁구대"
그래픽 신호를 출력하는 "탁구 게임기"에 그래픽 LCD의 낮은 처리율을 맞추기 위한 인터페이스 기구 FSM을 추가한 RTL 베릴로그는 다음과 같다. 화소의 좌표는 매 클럭 마다 카운트 하여 얻는 대신 LCD가 준비 되었을 때(busy=0) x_pos가 증가한다. 표시할 화소가 준비되었음을 알리기 위해 p_tick을 내보낸다. 그래픽 신호를 생성하는 "탁구대" 베릴로그 모듈과 그래픽 LCD 인터페이스 모듈 사이에 양방향 핸드쉐이크가 작동한다.
//
// Filename: pong_SbS.v
//
module pong_SbS(clk, reset, x_pos, y_pos, pixel, p_tick, busy);
input clk;
input reset;
output [6:0] x_pos;
output [5:0] y_pos;
output pixel;
output p_tick;
input busy;
reg [6:0] x_pos;
reg [5:0] y_pos;
reg pixel;
reg p_tick;
// FSM ////////////////////////
reg [2:0] State;
parameter sWait = 3'b001;
parameter sPixel = 3'b010;
always @(posedge clk or posedge reset)
begin
if (reset)
begin
x_pos <= 127;
y_pos <= 63;
p_tick <= 0;
State <= sWait;
end
else
case(State)
sWait:
begin
if (!busy)
begin
x_pos += 1;
if (x_pos==0) y_pos += 1;
p_tick <= 1'b1;
State <= sPixel;
end
end
sPixel:
begin
if (busy)
begin
p_tick <= 1'b0;
State <= sWait;
end
end
default:
State <= sWait;
endcase
end
assign pixel = (x_pos>9 && x_pos<15)? 1'b1:1'b0;
endmodule
3-3. 그래픽 LCD 인터페이스 모델
레지스터 트랜스퍼 수준(RTL)으로 기술한 "탁구대"의 베릴로그 출력을 그림으로 표시해야 하므로 그래픽 LCD의 입출력 역시 이와 동일한 추상화 수준으로 변경 되어야 한다. 그래픽 LCD 시뮬레이션 모델 sc_glcd128x64 을 하위 모듈로 둔 RTL 인터페이스용 SystemC 모듈은 다음과 같다.
//
// Filename: sc_glcd128x64_TB.h
//
#include <systemc.h>
#include "sc_glcd128x64.h" // GLCD
#include "glcd128x64_defs.h"
SC_MODULE(sc_glcd128x64_TB)
{
sc_in<bool> reset;
sc_in<sc_uint<7> > x_pos;
sc_in<sc_uint<6> > y_pos;
sc_in<bool> pixel;
sc_in<bool> p_tick;
sc_out<bool> busy;
sc_signal<bool> RS; // Register Mode: Inst(L), Data(H)
sc_signal<bool> RW; // Read(H), Write(L)
sc_signal<bool> E; // Enable @ Posedge
sc_signal<sc_uint<8> > DBi; // Data Bus (Input)
sc_signal<sc_uint<8> > DBo; // Data Bus (Output)
sc_signal<bool> CS1; // Chip-Select #1
sc_signal<bool> CS2; // Chip-Select #2
sc_signal<bool> RST; // Reset(L)
sc_glcd128x64* u_sc_glcd128x64;
void Test_Gen(void);
SC_CTOR(sc_glcd128x64_TB):
clk("clk", 305, SC_NS, 0.5, 0.0, SC_NS, false)
{
SC_THREAD(Test_Gen);
sensitive << clk;
// DUT
u_sc_glcd128x64 = new sc_glcd128x64("u_sc_glcd128x64");
u_sc_glcd128x64->RS(RS); // Register Mode Select
u_sc_glcd128x64->RW(RW); // Read(H), Write(L)
u_sc_glcd128x64->E(E); // Enable @ Posedge
u_sc_glcd128x64->DBi(DBi); // Data Bus
u_sc_glcd128x64->DBo(DBo); // Data Bus
u_sc_glcd128x64->CS1(CS1); // Chip-Select #1
u_sc_glcd128x64->CS2(CS2); // Chip-Select #2
u_sc_glcd128x64->RST(RST); // Reset(L)
}
};
콜백 함수 Test_Gen()은 sc_glcd128x64_TB.cpp 에 작성되었다. 앞서 그래픽 LCD 모델의 테스트 벤치[링크]는 콜백 함수에 사건 감응 신호 지정이 없었다. SC_THREAD 로 지정한 콜백 함수[링크]는 시간 대기 wait(100, SC_NS) 함수를 사용하여 LCD 구동 수순을 진행 했었다. 이와 달리, 추상화 수준을 "탁구대"의 RTL에 맞추기 위해 클럭의 상승 엣지 사건에 반응하여 그래픽 LCD 시뮬레이션 모델을 구동하는 신호 생성 수순을 진행한다.
//
// Filename: sc_glcd128x64_TB.cpp
//
#include <unistd.h>
#include "sc_glcd128x64_TB.h"
#define SET_INST(_CS1_,_CS2_,_INST_) \
{ \
RS.write(false); \
RW.write(false); \
......
DBi.write(_INST_); \
wait(clk.posedge_event()); \
E.write(true); \
wait(clk.posedge_event()); \
E.write(false); \
wait(clk.posedge_event()); \
}
#define SET_DATA(_CS1_,_CS2_,_DATA_) \
{ \
RS.write(true); \
RW.write(false); \
......
DBi.write(_DATA_); \
wait(clk.posedge_event()); \
E.write(true); \
wait(clk.posedge_event()); \
E.write(false); \
wait(clk.posedge_event()); \
}
#define GET_DATA(_CS1_,_CS2_,_DATA_) \
{ \
RS.write(true); \
RW.write(true); \
CS1.write(_CS1_? false:true); \
CS2.write(_CS2_? false:true); \
wait(clk.posedge_event()); \
E.write(true); \
wait(clk.posedge_event()); \
E.write(false); \
wait(clk.posedge_event()); \
_DATA_ = DBo.read(); \
}
#define SET_PIXEL(_X_,_Y_,_01_) \
{ \
SET_INST(true, true, INST_SET_Z_ADDRESS|0x00) \
SET_INST((_Y_<64? true:false), (_Y_>63? true:false), INST_SET_Y_ADDRESS|(_Y_%64)) \
SET_INST((_Y_<64? true:false), (_Y_>63? true:false), INST_SET_X_ADDRESS|(_X_/8)) \
sc_uint<8> _GD_DATA_; \
GET_DATA((_Y_<64? true:false), (_Y_>63? true:false), _GD_DATA_) \
if (_01_) _GD_DATA_ |= (0x01<<(x%8)); \
else _GD_DATA_ &= ~(0x01<<(x%8)); \
SET_INST((_Y_<64? true:false), (_Y_>63? true:false), INST_SET_Y_ADDRESS|(_Y_%64)) \
SET_DATA((_Y_<64? true:false), (_Y_>63? true:false), _GD_DATA_) \
}
void sc_glcd128x64_TB::Test_Gen(void)
{
int x, y;
while(true)
{
wait(clk.posedge_event());
if (reset.read())
{
// Reset Sequence
busy.write(true);
......
busy.write(false);
continue;
}
if (p_tick.read())
......
}
}
RTL "탁구대"에서 좌표와 화소 값이 준비되었다는 p_tick 를 받게되면 busy를 올려 LCD 구동 수순이 진행 되고 있음을 표시한다. 화소 표시 절차를 마치면 busy를 내려 핸드쉐이크를 마친다.
void sc_glcd128x64_TB::Test_Gen(void)
{
int x, y;
while(true)
{
wait(clk.posedge_event());
if (reset.read())
{
......
continue;
}
if (p_tick.read())
{
busy.write(true);
y = x_pos.read();
x = y_pos.read();
if (pixel.read())
SET_PIXEL( x, y, true)
else
SET_PIXEL( x, y, false)
busy.write(false);
}
}
}
3-4. 실습
베릴로그로 작성한 "탁구대"의 출력을 그래픽 LCD 시뮬레이션 모델 테스트벤치에 물려 RTL 시뮬레이션한다. 시뮬레이션 테스트벤치의 파일 구성은 다음과 같다.
$ cd ~/ETRI050_DesignKit/Projects/RTL/pong_SbS/03_TableDraw
$ tree
.
├── pong_SbS
│ └── pong_SbS.v
└── simulation
├── Makefile
├── glcd128x64_defs.h
├── sc_glcd128x64.cpp
├── sc_glcd128x64.h
├── sc_glcd128x64_TB.cpp
├── sc_glcd128x64_TB.h
├── sc_pong_SbS_TB.cpp
├── sc_pong_SbS_TB.h
└── sc_main.cpp
a. 따라하기
실습을 위한 테스트 벤치와 Makefile이 준비되어있는 예제 시뮬레이션 디렉토리로 이동한다.
$ pwd
~/ETRI050_DesignKit/Projects/RTL/pong_SbS/03_TableDraw/simulation
$ make
Makefile for Co-Simulation of Verilog-RTL example, pong_SbS
TOP_MODULE=pong_SbS make lint
TOP_MODULE=pong_SbS VCD_TRACE=[YES]|NO ROTATE_SCREEN=YES|[NO] make build
make run
make wave
make clean
모델을 시험해 볼 수 있는 테스트벤치와 함께 시뮬레이터를 빌드한다.
$ ROTATE_SCREEN=YES make build
verilator --sc -Wno-WIDTHTRUNC -Wno-WIDTHEXPAND \
--trace --timing --pins-sc-uint \
--top-module pong_SbS --exe --build \
-CFLAGS -g \
-CFLAGS -I../../c_untimed \
-CFLAGS -I/opt/systemc/include \
-CFLAGS -DVCD_TRACE_TEST_TB \
-CFLAGS -DVCD_TRACE_DUT_VERILOG \
-CFLAGS -DVCD_TRACE_GLCD \
-CFLAGS -DROTATE_SCREEN \
-LDFLAGS -lm \
-LDFLAGS -lSDL2 \
../pong_SbS/pong_SbS.v \
./sc_main.cpp \
./sc_glcd128x64.cpp \
./sc_glcd128x64_TB.cpp \
./sc_pong_SbS_TB.cpp
시뮬레이션 모델 테스트벤치를 실행 한다.
$ make run
시뮬레이션을 그림으로 확인 할 수 있다. 디지털 파형을 보면 아래와 같다.
$ make wave
b. 과제
예제 디렉토리에 시뮬레이션 모델과 테스트벤치 그리고 Makefile 까지 모두 준비되어 있다. 단지 따라해보기를 넘어 소스 코드를 읽고 토론을 통해 모델링과 테스트에 활용된 기법들을 이해하기 바란다. 클럭 주기와 처리율이 상이한 두 디지털 장치 사이의 핸드쉐이크의 필요성에 대하여 토론해 보자. 아울러 디지털 회로의 FSM을 베릴로그 RTL로 기술한 기법을 이해한다. 베릴로그로 RTL과 추상화 수준을 맞추기 위해 SystemC로 작성한 시뮬레이션 모델이 변경된 사항을 비교 설명해 보자.






















