요즘 인공지능이 뭐든 다 해준다고 한다. 인공지능이 컴퓨터 이므로 컴퓨터로 할 수 있는 일은 아주 잘 해낸다. 개발(소프트웨어는 물론 하드웨어 까지)에 초급 개발자는 필요 없다는 말이 공공연하다. 기성세대에게는 인공지능이 제법 신통하게 보일 수 있겠지만 이제 막 공학도의 길에 들어선 입장에서 어떤 느낌이 들까? 설마 인공지능이 알아서 해주니 골치 아프게 공부할 필요 없다는 생각이 들지 않길 바란다. 과연 인공지능은 내 느낌(vibe) 만 듣고도 다 해줄까? 구글의 인공지능 제미니는 바이브 코딩(Vibe Coding)을 이렇게 설명한다.
"a software development approach where you build applications using plain natural language instead of writing manual syntax or code line by line."
"수동으로 구문을 작성하거나 코드를 한 줄씩 입력하는 대신, 평이한 '자연어'를 사용하여 애플리케이션을 구축하는 소프트웨어 개발 방식."
설계는 나의 생각을 컴퓨팅 언어로 기술(묘사)하는 것이다. 결함이 없다고 판단된 묘사를 컴파일(또는 합성과 배치배선)하고 칩 제작용 도면을 생성하는 과정은 자동화 되어 있다. 전자회로 설계 자동화(EDA, Electronics Design Automation) 도구가 충분히 발달한 덕분이다. 이제 그 묘사하기(코딩) 조차 인공지능이 다 해준다고 하니 굳이 이 강의를 더 진행 할 필요도 없을 것 만 같다. 이 강의는 베릴로그(Verilog)라는 하드웨어 언어로 '내 생각'을 기술하고 '내 칩 제작 서비스'를 통해 세상에서 유일한 나의 칩(My Chip)을 얻는 것이다. 앞으로 강의에서 다룰 '내 생각'은 탁구 비디오 게임기다. 설계는 비디오 장치에 게임 화면을 그리고 게임을 수행하는 '알고리즘(탁구공의 움직임과 탁구채의 이동 제어 그리고 게임 규칙)'을 컴퓨팅 언어로 묘사하는 것으로 시작한다. 하드웨어를 묘사하는 컴퓨팅 언어(베릴로그라고 하는)를 배워야 하고 비디오 장치에 그림을 그리는 원리를 알고 있어야 한다. 컴퓨팅 언어(베릴로그, C++), 비디오 장치 등 당장 익혀야 할 사항들이 생겼다. 어쩌면 현대 전자공학의 산물들을 접하지 못했더라면 몰랐을 것들이지만 다행히 오늘을 사는 우리는 전자공학이 일궈낸 도구들을 상식으로 알고 질문을 제기하지 않는다[
1주차 강의:'2. 상식에 기대어 보자']. 인공지능이 신통하다 하니 귀찮게(공부하기 싫다는 것을 우리는 귀찮다고 치부하고 있지는 않을까?) 한줄 한줄 코딩 할 것 없이 '느낌'을 전달하는 것으로 비디오 게임기 하드웨어 설계라는 목적 달성이 가능할 지 알아보기로 한다. 제니미(구글의 인공지능)에게 '탁구'보다는 조금 복잡한 '우주 침략자(Space Invader)'게임을 만들어 보라고 해봤다.
2. Q01. 인공지능은 Space Invader 게임을 알고 있을까?
첫 질문은 무턱대고 Space Invader 를 들이 대봤다.
Q1. Can you write Space Invader game in Verilog RTL? [문답 링크]
질문자는 게임의 규칙을 일일이 설명할 필요 없이 '느낌(의도)'를 전달 했을 뿐이지만 제미니는 Space Invader 이 두 단어에서 무슨 게임 인지 알아냈다. 급기야 Verilog RTL 에서 질문자의 수준을 간파한다. 하드웨어로 만들려면 수천 줄의 구문을 써야 하고 여러 그래픽 기법(스프라이트)과 제어기(스테이트 머신), 오디오 드라이버 등 많은 자원이 들어 간다며 일단 게임의 기본기만 만들어 주겠단다. 아울러 FPGA로 동기식 디지털 하드웨어를 구현 할 수 있다고 한다. 몇가지 전문용어를 구사한 것을 보고는 질문자가 디지털 하드웨어와 컴퓨팅 시스템을 알고 있다고 판단한 것이 분명하다. 인공지능의 답을 들어보자.
Writing a full, arcade-perfect [Space Invaders](https://www.google.com/search?q=space+invaders&kgmid=/m/0jvd8) game requires thousands of lines of code, covering sprite memory, complex state machines, rendering logic, and audio drivers.
However, we can build a fully functional hardware architecture and the core game logic engine in Verilog RTL. This design implements standard synchronous design principles suitable for synthesis on an FPGA (like a Xilinx or Altera development board).
그러면서 일단 간단한 게임 규칙과 비디오 인터페이스만 구현된 코드를 생성해 주었다.
Below is the implementation of the core Game Logic & Video Interface Engine.
------------------------------
## 💻 Verilog RTL Implementation
space_invaders_engine.v [베릴로그 소스 링크]
소스 코드 중 입출력에 대하여 간단히 설명을 다음과 같이 덧붙였다.
## 🛠️ Hardware Integration Details
To compile and execute this on physical hardware like an FPGA, wrap this core module with two external components:
* VGA Synchronization Driver: Generates the specific vga_x, vga_y, and video_on timing outputs using standard H-Sync and V-Sync timing specifications.
* Sprite RAM Blocks: Rather than using blocky programmatic squares like the baseline code (cannon_draw, invader_draw), map an on-chip ROM file to standard bit-arrays to display pixelated spaceships and retro aliens. [1, 2]
더 도와 줄 수 있다면서 참고 링크도 알려 주었다.
Would you like help with:
* Writing the accompanying VGA Sync Timing driver module?
* Adding an on-chip ROM/RAM Sprite block to render retro alien textures?
* Expanding the configuration for a fully functional score counter?
[1] [https://github.com](https://github.com/nikkatsa7/SpaceInvadersFpgaGame)
[2] [https://fpgaw0rld.wordpress.com](https://fpgaw0rld.wordpress.com/2016/05/20/space-invaders-fpga-game/)
제미니가 베릴로그 RTL을 제대로 만들었을까 싶어서 테스트 벤치를 요구 했다.
Q2. wrute testbench [문답 링크]
질문에 오타가 있었다. 영어에 서툴다고 판단 했는지 한글로 답변을 준다. 한국인 인줄은 어떻게 알았을까?
다음은 위에서 설계한 space_invaders_engine 모듈을 검증하기 위한 Verilog 테스트벤치(Testbench) 코드입니다. 실제 하드웨어 환경처럼 클럭(Clock)과 리셋(Reset)을 공급하고, 플레이어가 왼쪽/오른쪽으로 움직이거나 미사일을 발사하는 상황을 시뮬레이션합니다. 또한 VGA 컨트롤러가 화면을 스캔하듯 vga_x와 vga_y를 순차적으로 증가시켜 픽셀 출력이 정상적으로 나오는지 확인합니다.
## 💻 Space Invaders Testbench Code
자기가 만들어 놓은 테스트벤치 이면서 시뮬레이션으로 무엇을 확인해야 하는지 지적까지 한다. 이쯤되면 약간 건방져 보이는 것은 인간으로서 자격지심이 들었기 때문일지도 모른다.
## 🔍 시뮬레이션 확인 포인트
1. 상태 천이(State Transition): 리셋이 해제되면 rgb가 파란색(3'b001)을 출력하다가, btn_fire가 입력된 직후 다음 화면 스캔부터 검은색 배경(3'b000)과 오브젝트들이 렌더링되는지 확인합니다.
2. 미사일 이동: btn_fire를 누르면 bullet_active 신호(내부 레지스터)가 올라가고, 매 프레임마다 bullet_y 좌표가 감소하며 위로 올라가는지 파형(Waveform)으로 검증합니다.
3. 픽셀 멀티플렉서: vga_x, vga_y가 외계인 좌표(invader_x, invader_y)나 플레이어 좌표(cannon_x)와 일치하는 구간에서 rgb 신호가 각각 빨간색(3'b100) 또는 초록색(3'b010)으로 정확히 변하는지 봅니다.
이 테스트벤치를 구동하기 위해 ModelSim, Vivado Simulator, 또는 EDA Playground 중 어떤 툴을 사용하고 계신가요? 툴에 맞는 시뮬레이션 실행 방법이나 파형(Waveform) 분석에서 확인하고 싶은 특정 신호가 있다면 말씀해 주세요.
제미니가 만들어준 베릴로그 RTL과 테스트벤치를 사용하여 시뮬레이션을 해봤다. 소스 코드는 전혀 손대지 않았다.
$ cd ~/ETRI050_DesignKit/Project/MyChip_Games/space_invader_by_Gemini/Q.02_Space_Invaders_in_Verilog_TB
$ make run
제미니는 상용 시뮬레이터를 언급 했지만 오픈-소스 베릴로그 시뮬레이터 Icarus 를 사용 했다. 다음과 같은 출력을 보여준다.
./tb_space_invaders_engine
VCD info: dumpfile tb_space_invaders_engine.vcd opened for output.
[STATUS] 시스템 리셋 해제 - START 상태 진입
[STATUS] Fire 버튼 입력 - PLAY 상태로 전환 완료
[ACTION] 오른쪽 이동 버튼(btn_right) 활성화
[ACTION] 미사일 발사(btn_fire) 활성화
[MONITOR] Time: 1602140000 | Coord: (50, 50) | RGB Output: 100 (Invader)
[MONITOR] Time: 1602180000 | Coord: (52, 50) | RGB Output: 100 (Invader)
[MONITOR] Time: 1602220000 | Coord: (52, 50) | RGB Output: 100 (Invader)
[MONITOR] Time: 1602260000 | Coord: (54, 50) | RGB Output: 100 (Invader)
.....
[MONITOR] Time: 14349220000 | Coord: (328, 448) | RGB Output: 010 (Cannon)
[MONITOR] Time: 14349260000 | Coord: (328, 448) | RGB Output: 010 (Cannon)
[MONITOR] Time: 14349300000 | Coord: (330, 448) | RGB Output: 010 (Cannon)
....
[MONITOR] Time: 18889300000 | Coord: (230, 65) | RGB Output: 100 (Invader)
[MONITOR] Time: 18889340000 | Coord: (230, 65) | RGB Output: 100 (Invader)
[STATUS] 테스트벤치 시뮬레이션 완료
tb_space_invaders_engine.v:111: $finish called at 24006960000 (1ps)
게임에 등장하는 객체들(외계 침략자, 대포, 총알)이 그려질 화면의 좌표가 문자 메시지로 출력 되었다. 디지털 회로의 시뮬레이션 이므로 파형(waveform)을 볼 수 있다.
화면의 좌표에 침략자들(alien)과 방어 포대(cannon)가 점으로 찍힐 위치가 디지털 파형과 값으로 나타났다. 한 화면이 다 그려지면 그 표시로 frame_tick 가 한번 출력 된다. 위의 시뮬레이션은 겨우 한 화면 분량의 시뮬레이션이다. 발사 버튼이 눌리지 않아서 대포알(bullet)은 나타나지 않고있다.
제미니가 만들어 준
게임 엔진에 대하여
테스트벤치가 작동하는 방식을 간략히 살펴보자. 먼저 게임 엔진의 입출력은 다음과 같다.
module space_invaders_engine (
input wire clk, // System clock (e.g., 25MHz or 50MHz)
input wire rst_n, // Active-low reset
input wire btn_left, // Player move left
input wire btn_right, // Player move right
input wire btn_fire, // Player shoot
input wire [9:0] vga_x, // Current VGA horizontal pixel coordinate (0-639)
input wire [9:0] vga_y, // Current VGA vertical pixel coordinate (0-479)
input wire video_on, // VGA video active region indicator
output reg [2:0] rgb // Output Color to DAC: [2]=Red, [1]=Green, [0]=Blue
);
2차원 화면을 훓는 (vga_x, vga_y)가 입력인 점에 주목하자. 외부에서 생성된 화면 주사신호를 받아 게임에 등장할 외계인과 포대 그리고 총알을 표시한다. 출력은 화면에 찍힐 화소점 rgb 이 있을 뿐이다. 화면 주사신호는 테스트벤치에서 생성하여 게임 엔진에 공급한다.
다음은 테스트 벤치의 일부분이다. 시뮬레이션 시나리오를 볼 수 있다.
module tb_space_invaders_engine();
......
// 3. UUT (Unit Under Test) 인스턴스화
space_invaders_engine uut (
......
.vga_x(vga_x),
.vga_y(vga_y),
.video_on(video_on),
.rgb(rgb)
);
......
// 5. VGA 가상 스캔 루프 (640x480 화면 스캔 시뮬레이션)
integer h, v;
initial begin
......
forever begin
for (v = 0; v < 525; v = v + 1) begin // 480활성+45블랭킹
for (h = 0; h < 800; h = h + 1) begin // 640활성+160 블랭킹
vga_x = h;
vga_y = v;
// 640x480 활성 영역 내부일 때만 video_on 활성화
if (h < 640 && v < 480) video_on = 1;
else video_on = 0;
@(posedge clk);
end
end
end
end
시뮬레이션 수행은 미리 준비된 시나리오를 따른다. 대화형이 아니다. 게임기라는 동적 의외성을 반영하지 못한다.
// 6. 테스트 시나리오 자극(Stimulus) 입력
initial begin
......
// --- 시나리오 1: START 화면에서 게임 시작 버튼(Fire) 누르기 ---
#200;
btn_fire = 1;
#80; // 클럭 몇 주기 동안 누름 유지
btn_fire = 0;
$display("[STATUS] Fire 버튼 입력 - PLAY 상태로 전환 완료");
// --- 시나리오 2: 플레이어 우측 이동 테스트 ---
#500;
btn_right = 1;
$display("[ACTION] 오른쪽 이동 버튼(btn_right) 활성화");
#5000; // 플레이어가 오른쪽으로 이동하도록 충분히 대기
btn_right = 0;
end
......
// 7. 모니터링 및 디버깅 로그 출력
// 특정 좌표를 지나갈 때 RGB 색상 변화 확인
always @(posedge clk) begin
if (video_on && (rgb==3'b100)) // Invader
$display("[MONITOR] Time: %0t | Coord: (%0d, %0d) | RGB Output: %b (Invader)",
$time, vga_x, vga_y, rgb);
......
end
initial begin
$dumpfile("tb_space_invaders_engine.vcd");
$dumpvars(2, uut);
end
endmodule
게임 엔진은 그래픽 장치를 직접 구동하지 않고 있다. 외부의 스캔 주소를 받아 화면 위에 표시될 화소점을 출력한다.
제미니가 알려준 시뮬레이션 확인 포인트에서는 '검은 바탕에 오브젝트 들이 렌더링..' 이라며 마치 그림을 보듯이 설명 했지만 정작 시뮬레이션 출력은 문자와 디지털 파형이다. 이런 고약한 인공지능 같으니라니! 명색이 비디오 게임인데 좌표나 디지털 파형으로는 무려 640x480 화소점 크기의 화면이 제대로 작동하는지 확인하기는 불가능하다. 점 한개를 찍는데 1클럭이 사용되고 있으므로 한 VGA 화면을 위해 30만개 이상의 클럭을 사용하고 있다.
한 클럭마다 동작을 보려는 것이 아니므로 그래픽 화면을 표시하는 테스트 벤치를 작성 하라고 주문 했다.
Q4. Space Invader game is graphic arcade game. So the testbench must display graphic output. How do you recomend ? [문답 링크]
제미니는 이에 동의 한다면서 눈으로 확인 가능하려면 그래픽 출력을 추천한다.
하드웨어(Verilog RTL)나 시스템 모델(SystemC)로 게임을 설계할 때, 파형(Waveform)의 0과 1 신호만 보고 그래픽 게임이 정상 동작하는지 검증하는 것은 매우 어렵습니다.
[Space Invaders](https://www.google.com/search?q=space+invaders&kgmid=/m/0jvd8) 같은 그래픽 아케이드 게임을 검증할 때 시뮬레이션 도중 또는 종료 후 눈으로 그래픽 출력을 직접 확인할 수 있는 3가지 실무적 방법을 추천합니다.
시뮬레이션을 실시하는 컴퓨터는 이미 훌륭한 그래픽 장치와 여러 라이브러리(소프트웨어)를 갖추고 있으니 이를 활용하라면서 추천한 방법들은 아래와 같다.
## 1. OpenCV 라이브러리 연동 (가장 추천 🌟)
SystemC는 C++ 기반이므로, 강력한 이미지 처리 라이브러리인 OpenCV를 테스트벤치 코드에 직접 링크할 수 있습니다.
* 동작 원리: SystemC 테스트벤치 내부에 화면 해상도(예: 128x64 또는 640x480) 크기의 cv::Mat 이미지 버퍼를 생성합니다. RTL이나 모델이 픽셀 데이터를 보낼 때마다 해당 버퍼의 픽셀 색상 값을 업데이트합니다.
* 시각화 방식:
* 실시간 확인: cv::imshow() 함수를 사용하면 시뮬레이션이 돌면서 실시간으로 외계인과 우주선이 움직이는 윈도우 창이 띄워집니다.
* 녹화 확인: cv::VideoWriter를 사용하면 시뮬레이션 전체 과정을 .mp4나 .avi 동영상 파일로 저장하여 검증 보고서에 첨부할 수 있습니다.
## 2. 가상 프레임 버퍼를 PPM/BMP 이미지 파일로 덤프 (단순함 🖼️)
외부 라이브러리(OpenCV) 설치가 까다롭거나 제한된 환경(서버/터미널 환경)이라면, C++ 표준 파일 입출력(std::ofstream)만을 이용해 PPM(Portable Pixmap) 또는 BMP 파일로 저장하는 방법입니다.
* 동작 원리: 테스트벤치 내부에 2차원 배열 배열(char frame_buffer[Y][X])을 잡고 프레임 데이터를 모읍니다.
* 시각화 방식: VGA의 V-Sync(수직 동기화) 신호가 오거나 한 프레임 렌더링이 끝나는 시점에 해당 배열을 이미지 파일 포맷 헤더 규격에 맞게 파일로 출력합니다. (frame_001.ppm, frame_002.ppm 형태)
* 시뮬레이션 종료 후 생성된 이미지들을 연결하면 애니메이션처럼 게임 진행 상황을 컷 단위로 확인할 수 있습니다.
## 3. ModelSim / QuestaSim의 "Virtual Memory Window" 기능 활용 (EDA 전용 🛠️)
깃허브 예제처럼 게임 내부에 **화면 데이터를 담는 비디오 RAM(VRAM)**을 별도의 레지스터 배열이나 메모리로 설계한 경우에 쓸 수 있는 방법입니다.
* 동작 원리: EDA 시뮬레이터(ModelSim 등)는 시뮬레이션 내부의 2차원 메모리 배열을 이미지로 해석해 주는 자체 뷰어 기능을 제공합니다.
* 시각화 방식: 시뮬레이터를 멈추어 둔 상태(Freeze)나 실행 중에 VRAM 배열을 우클릭한 뒤 "View as Image" 혹은 **"Memory Grid View"**를 선택하면, 현재 VRAM에 저장된 흑백/컬러 도트 맵이 시뮬레이터 내부 UI 창에 그대로 시각화됩니다.
세가지 방법과 동작원리 설명을 붙여 놓긴 했는데 낮설기 그지없다. 구체적인 예시를 보여주긴 했는데 OpenCV 라는 그래픽 라이브러리를 사용하는 경우다. OpenCV 라고? 인공지능이 내준 여러 선택지 중 하나를 고를려면 '가장 추천'하는 방법과 '단순한' 방법의 차이를 직관적으로 이해하는 능력이 필요하다.
첫번째 방법은 '테스트벤치 코드에 그래픽 라이브러리를 직접 링크' 하여 '실시간 확인'이 가능하다. 두번째 방법은 'PPM(Portable Pixmap) 또는 BMP 파일로 저장'한 후 '생성된 이미지들을 연결하여 애니메이션 처럼'보라고 한다. 동영상 처럼 보이려면 초당 10 프레임 이상의 이미지를 붙여야 하고 버튼 입력에 실시간으로 게임이 진행 되는 그림을 봐야 한다. 두번째 방법은 대화형 시뮬레이션이 필요한 그래픽 게임 개발에 전혀 합당하지 않다고 판단할 수 있다. 당연한 상식이다. 목표에 접근하는 방법의 선택하는 것 역시 설계자의 중요 덕목이다. 많은 경우 상식에 의지할 것이다. 상식은 경험(훈련)을 통해 강화 된다.
## 💡 구체적인 구현 예시 (OpenCV 연동 예제 소스코드)
만약 1번(OpenCV) 방식을 사용한다면 SystemC 모니터 프로세스를 아래와 같이 직관적으로 확장할 수 있습니다.
#include <opencv2/opencv.hpp>
#include <systemc.h>
// SystemC 모니터 모듈 내부의 일부 예시
void tb_space_invaders::vga_display_vulkan() {
// 128x64 해상도의 흑백(기본 8비트 1채널) 가상 스크린 생성
cv::Mat virtual_screen = cv::Mat::zeros(64, 128, CV_8UC1);
while(true) {
wait(clk.pos());
// 가상 GLCD 혹은 VGA 인터페이스 신호를 모니터링하여 픽셀 매핑
if (lcd_write_en.read() == true) {
int x = lcd_x_addr.read().to_int();
int y = lcd_y_addr.read().to_int();
// 픽셀이 켜지면 흰색(255), 꺼지면 검은색(0)
virtual_screen.at<uchar>(y, x) = (lcd_data.read() == 1) ? 255 : 0;
}
// V-Sync 혹은 주기적 타이밍에 화면 리프레시 (예: 60Hz 마다)
if (frame_tick.read() == true) {
// 화면이 너무 작으면 눈에 안 보이므로 4배 확대하여 출력
cv::Mat resized_screen;
cv::resize(virtual_screen, resized_screen, cv::Size(), 4.0, 4.0, cv::INTER_NEAREST);
cv::imshow("Space Invaders 하드웨어 시뮬레이션 창", resized_screen);
cv::waitKey(1); // 1ms 대기하며 윈도우 이벤트 처리
}
}
}
세가지 방법에 이어서 인공지능은 어떤 그래픽 라이브러리를 선호하는지 묻는다. 질문자가 여러 그래픽 방식 중 구체적으로 제시하지 않은 까닭이다. 그러면서 어느 뭔가 해줄 수 있다고 한다.
가장 시도해보고 싶으신 시각화 방식은 몇 번인가요?
* 만약 1번이나 2번 C++ 그래픽 파일 출력 방식에 관심이 있으시다면, 참조하신 GoodKook 저장소 환경에 곧바로 붙여서 빌드할 수 있도록 Makefile 수정 팁과 구체적인 파일 저장 C++ 함수를 작성해 드릴 수 있습니다.
답변에 GoodKook의 깃허브 저장소에 있는 방식을 해주겠단다. 벌써부터 제미니는 질문자(GoodKook)를 파악하고 있었다.
5. Q05. SDL2를 써서 비디오 게임 시뮬레이션의 시각화
여러 그래픽 처리 방법 중에서 SDL을 특정해서 요구했다. SDL을 검색하면 아래와 같은 설명을 볼 수 있다.
SDL(Simple DirectMedia Layer)은 게임 및 멀티미디어 애플리케이션 개발을 위한 크로스 플랫폼 오픈소스 개발 라이브러리입니다. 운영체제(OS)마다 다른 로우 레벨 하드웨어 제어 방식을 하나로 추상화하여 API 형태로 제공합니다. 개발자가 코드를 한 번만 작성해도 Windows, macOS, Linux, iOS, Android 등 다양한 플랫폼에서 구동되는 프로그램을 쉽게 만들 수 있도록 돕습니다.
이제부터 한글로 질문을 해보겠다.
Q5. space_invaders_engine의 베릴로그 테스트벤치에 SDL을 사용하여 시각화 할 수 있도록 할 것 [문답 링크]
실시간 그래픽 시현을 주문 해서 만든 게임 엔진은 앞선 것(
Q.01)과 차이가 있다. 미리 갖춰진 VGA 그래픽 제어장치에서 2D 화면을 훓는 좌표값이 주어지는 방식에서 벗어나 게임 엔진 내에서 외부의 그래픽 장치(graphic device) 메모리를 능동적으로 제어한다. 게임 엔진의 외형에서 확연한 차이를 볼 수 있다.
//-----------------------------------------------------------
// 128x64 Graphic LCD Bus Interface를 탑재한 아케이드 게임 엔진
//-----------------------------------------------------------
module space_invaders_engine_glcd (
input wire clk, // 시스템 클럭 (예: 50MHz)
input wire rst_n, // Active-low 리셋
input wire btn_left, // 플레이어 왼쪽 이동
input wire btn_right, // 플레이어 오른쪽 이동
input wire btn_fire, // 미사일 발사
// SDL2 가상 LCD 모듈 및 DPI-C 테스트벤치로 직접 데이터를 밀어넣는 그래픽 버스 인터페이스
output reg lcd_write_en, // LCD 메모리 쓰기 신호
output reg [6:0] lcd_x, // 0 ~ 127 수평 주소
output reg [5:0] lcd_y, // 0 ~ 63 수직 주소
output reg lcd_data // 1-bit 픽셀 데이터
);
입출력 포트에서 이 게임 엔진이 외부의 그래픽 장치를 제어하는 방식을 유추해 볼 수 있다. 가로 128 세로 64 화소점을 갖는 소형 LCD 그래픽 장치의 디스플레이 메모리(GRAM)에 픽셀 값을 써넣는 방식이다.
게임 엔진 내부에 화면을 훓는 카운터가 그래픽 장치의 메모리 주소다.
// --- 3. LCD 스트리밍 주소 카운터 및 데이터 브로드캐스트(줄번호: 148)
// 하드웨어 자체적으로 128x64를 무한 반복 스캔하며 가상 LCD에 그리기 신호를 보냅니다.
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
scan_x <= 7'b0;
scan_y <= 6'b0;
end else begin
if (scan_x == SCREEN_WIDTH - 1'b1) begin
scan_x <= 7'b0;
if (scan_y == SCREEN_HEIGHT - 1'b1)
scan_y <= 6'b0;
else
scan_y <= scan_y + 1'b1;
end else begin
scan_x <= scan_x + 1'b1;
end
end
end
end
화면 스캔 좌표 (scan_x, scan_y)가 포대(cannon_x, cannon_y) 와 총알 (bullet_x, bullet_y)의 위치에 오면 화소값을 준다.
// 그리기 판정 레이어 계산 (현재 스캔 좌표 기준)
wire draw_cannon = (scan_x >= cannon_x) &&
(scan_x < cannon_x + CANNON_WIDTH) &&
(scan_y >= CANNON_Y) &&
(scan_y < CANNON_Y + CANNON_HEIGHT);
wire draw_bullet = bullet_active &&
(scan_x >= bullet_x) &&
(scan_x < bullet_x + BULLET_WIDTH) &&
(scan_y >= bullet_y) &&
(scan_y < bullet_y + BULLET_HEIGHT);
이어서 다수의 외계인을 그린다.
reg draw_invader;
always @(*) begin
draw_invader = 1'b0;
for(i=0; i<3; i=i+1) begin
if (invader_alive[i]) begin
if ((scan_x >= invader_x[i]) &&
(scan_x < invader_x[i] + INVADER_WIDTH) &&
(scan_y >= invader_y) &&
(scan_y < invader_y + INVADER_HEIGHT)) begin
draw_invader = 1'b1;
end
end
end
end
아래 그림은 위의 구문에 의해 i-번째 외계인(invader)이 사각형으로 화면에 찍히는 모습이다.
외계인, 포대 그리고 총알의 화소를 논리합 하여 세 객체가 합쳐진 최종 화면을 그린다.
// 버스 출력 핀 드라이브 처리 (줄번호:188)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
lcd_write_en <= 1'b0;
lcd_x <= 7'b0;
lcd_y <= 6'b0;
lcd_data <= 1'b0;
end else begin
// 매 사이클마다 지속적으로 데이터 유효를 알림
lcd_write_en <= 1'b1;
lcd_x <= scan_x;
lcd_y <= scan_y;
if (current_state == STATE_PLAY) begin
// 오브젝트 중 하나라도 걸리면 픽셀을 켬(1), 빈 공간이면 끔(0)
lcd_data <= (draw_cannon||draw_bullet||draw_invader);
end else if (current_state == STATE_START) begin
// 시작 대기 화면: 바둑판무늬 대기 스크린 예시
lcd_data <= (scan_x[2] ^ scan_y[2]);
end
end
end
새롭게 만들어진 게임 엔진을 검증하는 테스트벤치를 살펴보자. 먼저 인공지능이 설명을 들어보기로 한다. 베릴로그 테스트벤치에서 SDL의 그래픽 라이브러리(API)를 연결 시키기 위해 DPI-C를 사용했다.
순수 Verilog 테스트벤치(RTL) 환경에서 실시간 그래픽을 시현하려면, Verilog의 DPI-C (Direct Programming Interface)를 사용하여 C/C++ 표준 라이브러리 및 SDL2 기능을 Verilog 함수처럼 직접 호출해야 합니다. 이 방식을 사용하면 SystemC를 거치지 않고도 ModelSim, QuestaSim, Vivado Simulator, VCS 또는 Verilator(C++ wrapper 형태) 등에서 Verilog 테스트벤치가 실행되는 동안 실시간으로 창이 뜨며 게임 화면이 시현됩니다. 이를 구현하기 위한 DPI-C 연동 C++ 모듈, 수정된 Verilog 테스트벤치, 그리고 Verilator 및 일반 시뮬레이터용 빌드 가이드입니다.
문법과 문장 실행 방식이 분명한 차이가 있으므로 이종 컴퓨팅 언어 사이에 소스 코드 수준에서 결합될 수 없다. 컴파일된 라이브러리의 함수(API)를 호출하도록 한다. 이미 컴파일되어 실행가능한 바이너리 형식이므로 컴퓨팅 언어와 무관하게 호출 할 수 있다. 공유 라이브러리(또는 실행 라이브러리)로서 윈도우즈 시스템에서는 이를 DLL(Dynamic Linking Library), 리눅스는 so(shared object) 라고 한다. 하드웨어 언어 역시 외부의 C,C++등으로 제작된 공용 라이브러리를 불러오기 위한 규정을 두고 있는데 베릴로그는 PLI/VPI, 시스템베릴로그(SystemVerilog)는 DPI-C라고 한다. 설계 복잡도가 증가하면서 이미 구축된 라이브러리를 활용한 시스템 수준 테스트벤치 기법은 필수다. 제미니는 DPI-C를 추천하고 있다. C++로 작성한 그래픽 라이브러리 구동용 소스 코드다.
Verilog 테스트벤치에서 픽셀 정보를 넘겨받아 SDL2 창에 실시간으로 매핑해 주는 C++ 인터페이스 파일입니다.
C++ 의 함수 호출이 포함된 베릴로그 테스트벤치다.
테스트벤치 최상단에서 C++ 함수들을 임포트(Import)하여 사용합니다. space_invaders_engine의 lcd_x, lcd_y 주소와 lcd_write_en 신호 타이밍에 맞추어 C++ 픽셀 버퍼에 데이터를 기록하고, 전체 화면 스캔이 끝날 때마다 화면을 리프레시합니다.
다음 그림은 베릴로그 DUT를 사례화(instantiation)하고 SDL의 API를 불러 화면을 그리는 테스트벤치의 구성이다.
이종 언어로 구성된 테스트벤치를 컴파일 하여 시뮬레이터를 빌드 하려면 다소 까다로운 절차가 필요하다. 베릴로그를 C++ 로 변환해 주는 오픈-소스 도구 Verilator를 사용하여 단일의 실행 파일을 만들 수 있다. 또는 상용 시뮬레이터(ModelSim / QuestaSim)는 단일 환경이지만 각각 별도 오브젝트(바이너리) 수준에서 상호 호출 하는 방식이다. 이종 언어를 결합하려면 컴파일러의 명령줄 옵션이 꽤나 복잡하고 길다. 제미니는 친절 하게도 두 도구를 사용한 명령줄 옵션을 알려 주었다.
## 🛠️ 3. 빌드 및 컴파일 명령어 가이드
Verilator를 사용하여 이 DPI-C 융합 Verilog 테스트벤치를 가동하고 단독 실행 바이너리로 뽑아내는 최적화 컴파일 명령어 세트입니다.
# 1. Verilator를 사용하여 Verilog와 C++ DPI 소스를 결합하고 컴파일 환경 빌드# --binary 플래그는 Verilator가 스스로 main 소스를 생성하여 자체 바이너리를 뽑아내도록 유도합니다.
verilator --binary -Wall -Wno-fatal \
space_invaders_engine_glcd.v \
tb_space_invaders.v \
sim_display.cpp \
-LDFLAGS "$(sdl2-config --libs)" \
-CFLAGS "$(sdl2-config --cflags) -std=c++17" \
--top-module tb_space_invaders \
--Mdir obj_dir
# 2. 빌드가 완료되면 생성된 독자 실행 파일을 실행합니다.
./obj_dir/Vtb_space_invaders
* ModelSim / QuestaSim 유저인 경우 실행 방법:
# C++ 코드를 공유 라이브러리(.so)로 빌드한 뒤 vlog/vsim에 링크해야 합니다.
g++ -shared -fPIC -o sim_display.so sim_display.cpp $(sdl2-config --cflags --libs) -I$MODELSIM_HOME/include
vlog space_invaders_engine_glcd.v tb_space_invaders.v
vsim -sv_lib sim_display work.tb_space_invaders
그리고 시뮬레이션에는 별 도움도 않될 말을 덧붙인다. 인공지능은 수다스럽다.
## 🎨 실행 결과 아케이드 시현 검증 구조
이 아키텍처를 가동하면 별도의 SystemC 래퍼 스케일 작업 없이 RTL 코어 내부의 무한 스캔 루프 주소(scan_x, scan_y)가 실시간으로 DPI 인터페이스에 수 밀리초 단위로 파이프라이닝되면서, 화면이 찢어지는 현상(Tearing) 없이 완벽히 동기화된 복고풍 하드웨어 도트 그래픽이 윈도우 스크린 상에 60Hz급 프레임으로 가볍고 부드럽게 연동 및 표출됩니다.
일방적인 시현에 더하여 키보드 입력을 추가한 본격적인 대화형 시뮬레이터를 제안한다.
이 DPI-C 통합 구조를 사용하면서 혹시 특정 에뮬레이션 상황을 연출하기 위해 키보드 인터럽트 입력 핸들러(SDL_PollEvent에서의 키 입력)를 역방향으로 RTL 버튼 입력(btn_left, btn_right)에 실시간 피드백 맵핑하는 고도화 과정도 추가해 드릴까요?
제미니가 작성해준 소스 코드와 명령으로 시뮬레이터를 빌드해보자. Verilator를 사용하여 시뮬레이터를 빌드하고 실행하는 쉘 스크립트는
run.sh 다. 명령줄에서 쉘 스크립트를 실행한다. 게임 화면을 볼 수 있다! 그런데 외계 우주인과 대포가 밋밋한 사각형이다. 첫 질문에서 제미니가 생성한 베릴로그 소스 코드가 스프라이트(sprite ROM) 등 하드웨어 자원이 많이 들어가는 부분은 제외한 단순 게임 규칙을 기술한 RTL이었다는 점을 상기한다.
앞선 테스트벤치는 DPI-C를 사용하여 게임 화면을 그래픽으로 보여줄 뿐 입력은 여전히 시나리오에 의한 진행이었다. 실제와 가깝도록 대화형 테스트벤치를 만들어보자. 키보드를 사용하여 포대의 위치를 이동하고 포탄을 발사하는 코드를 요구했다.
Q6a. 외계인의 공격을과 키보드를 사용하여 총알 발사를 구현하라. 베릴로그 테스트벤치를 보여달라 [문답a 링크]
베릴로그 RTL은 제대로 만들었지만 테스트벤치와 DPI-C 코드가 제대로 연결되지 않았다는 변명을 한다.
DPI-C 소스 코드(sim_display.cpp)에서 SDL2 키보드 이벤트 이웃풋을 Verilog 테스트벤치로 전달(Export)받아, 하드웨어 입력 핀(btn_left, btn_right, btn_fire)을 실시간 제어할 수 있도록 완전히 확장한 Verilog 테스트벤치(tb_space_invaders.v) 코드입니다. 추가로 외계인이 플레이어를 향해 무작위로 미사일을 투하하는 로직(외계인 공격)도 테스트벤치 레벨에서 모니터링하기 좋게 결합했습니다.
------------------------------
새 테스트벤치와 DPI-C를 연동 시키기 위해 C++ 함수를 수정 하라고 한다.
------------------------------
## 💻 4. 연동을 위해 업데이트가 필요한 DPI-C 파일 (sim_display.cpp)
위 테스트벤치에서 호출하는 dpi_poll_keyboard 함수가 실제 호스트 PC의 키보드 입력을 캐치할 수 있도록 C++ 소스에 아래의 키 이벤트 폴링 함수를 보완해 주어야 합니다.
// sim_display.cpp 내부에 추가/수정해야 할 키보드 감지 함수 예시
extern "C" void dpi_poll_keyboard(unsigned char* out_left, unsigned char* out_right, unsigned char* out_fire) {
SDL_Event event;
// 키 상태를 고정적으로 유지하기 위해 스태틱 변수 활용 또는 SDL_GetKeyboardState 사용 가능
const Uint8* state = SDL_GetKeyboardState(NULL);
// 기본 이벤트 큐 비우기 (창 닫기 이벤트 등의 안전장치)
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
SDL_Quit();
exit(0);
}
}
// SDL2 실제 키 매핑 상태를 Verilog 아웃풋 포인터에 다이렉트로 전달
// 1이면 버튼 눌림(High), 0이면 버튼 뗌(Low)
*out_left = state[SDL_SCANCODE_LEFT] ? 1 : 0;
*out_right = state[SDL_SCANCODE_RIGHT] ? 1 : 0;
*out_fire = state[SDL_SCANCODE_SPACE] ? 1 : 0;
}
인공지능이 이래라 저래라 한다. 감히! 내가 알면 너 한테 시켰겠니? DPI-C의 C++ 소스코드를 전부 보여달라고 주문했더니 그제서야 만들어준다.
Q6b. 이 테스트벤치와 연동되는 sim_display.cpp 전체 통합 소스 코드가 필요하다. [문답b 링크]
이쯤 되면 인공지능 의존도가 높아지기 시작한다.
Verilog 테스트벤치(tb_space_invaders.v)와 완벽하게 기계적으로 맞물려 돌아가는 sim_display.cpp 전체 통합 소스 코드입니다. 이 코드는 실시간으로 화면을 그려주는 그래픽 엔진 역할과 동시에, 호스트 PC의 키보드 입력(좌/우 방향키, 스페이스바) 상태를 낚채어 Verilog의 btn_left, btn_right, btn_fire 신호선으로 실시간 피드백해 주는 양방향 DPI-C 매커니즘이 구현되어 있습니다.
------------------------------
게다가 인공지능은 유혹까지 서슴지 않는다. 대형 아케이드 판 게임으로 확장해 주겠단다.
------------------------------
## 🚀 컴파일 및 구동 최종 점검 명령어
이제 프로젝트 디렉터리에 space_invaders_engine_glcd.v, tb_space_invaders.v, sim_display.cpp 파일 3개가 한 곳에 완비되었습니다. 리눅스 터미널(Ubuntu/WSL)에서 아래 명령어 묶음을 실행하면, C++ 컴파일러 플래그와 SDL2 동적 라이브러리가 Verilator 실행 파일 빌드 아티팩트에 하나로 묶여 컴파일되며 아케이드 에뮬레이터 바이너리가 완성됩니다.
모든 설계 인프라가 구현되었습니다. 구동 중 환경 상의 문제로 빌드 에러가 나거나, 외계인 레이아웃의 마리 수 및 대형을 아케이드판 규격 수준인 5행 11열 격자로 대대적으로 확장하고 싶다면 어떤 방향으로 개선을 도와드릴까요?
시뮬레이터를 빌드하고 포탄 발사를 확인해본다. 위의 답변 중 space_invaders_engine_glcd.v는 이전(Q.05)에 생성했던 베릴로그 RTL이다.
$ cd ~/ETRI050_DesignKit/Project/MyChip_Games/\
space_invader_by_Gemini/Q.06_Bullet_Fire
$ source run.sh
verilator --binary -Wall -Wno-fatal \
../Q.05_Visulization_with_SDL2/space_invaders_engine_glcd.v \
tb_space_invaders.v \
sim_display.cpp \
-LDFLAGS "$(sdl2-config --libs)" \
-CFLAGS "$(sdl2-config --cflags) -std=c++17" \
--top-module tb_space_invaders \
--Mdir obj_dir
./obj_dir/Vtb_space_invaders
7. Q07. 대형 아케이드 게임으로 대대적 확장
대형 아케이드 게임으로 확장을 해주겠다는 제미니의 유혹에 넘어가 봤다.
Q7. 3마리로 배치된 외계인의 개수를 늘려 실제 오락실 게임처럼 외계인 부대 대형(예: 5행 5열 등)으로 Verilog 로직을 리팩토링하라 [문답 링크]
외계인 부대의 대형이 늘어 하드웨어 자원이 증가하는 것을 방지하는 대책까지 고려해 준다고 한다.
[Space Invaders](https://www.google.com/search?q=space+invaders&kgmid=/m/0jvd8)의 상징적인 요소인 외계인 부대 대형(5행 × 5열 = 총 25마리)을 하드웨어로 구현하기 위해 space_invaders_engine_glcd.v 코드를 리팩토링했습니다.
하드웨어(Verilog)에서 25마리의 독립된 이동 좌표를 개별 레지스터로 만들면 FPGA 리소스를 엄청나게 낭비하게 됩니다. 따라서 아케이드 기기의 정석적인 설계 기법을 따라 외계인 무리 전체의 기준 좌표(group_x, group_y) 하나만 움직이고, 각 외계인의 생사 여부는 25비트 레지스터(invader_alive)의 비트 맵으로 효율적으로 관리하도록 구조를 대폭 개선했습니다.
------------------------------
변경된 내용을 설명하고 그로부터 얻은 효과(하드웨어 자원 절약)까지 비교 설명 하고 있다. 25개의 외계인은 집단으로 묶여 이동한다. 외계인 위치를 각각 표시하지 않고 집단 좌표에서 상대위치를 계산하여 외계인을 표시한다. 이때 살아있는 외계인과 죽은 외계인을 표시하기 위해 25비트 레지스터를 사용 하므로서 하드웨어 사용량을 줄였다.
------------------------------
## 🔍 설계 변경 핵심 요약
1. 25비트 레지스터 압축 기법: 외계인 25마리 각각의 X, Y 좌표 레지스터를 만들면 총 25 × 13비트 = 325비트의 플립플롭이 낭비되지만, 부대 기준점(group_x, group_y)과 상태 맵(invader_alive 25비트) 구조로 단 38비트만 사용하여 25마리를 완벽히 추적 및 제어합니다.
2. 동적 격자 스캔 연단: 화면을 스캔하는 단계(always @(*))에서 현재 스캔 픽셀 좌표(scan_x, scan_y)가 5행 5열의 바운더리 내부인지 역산하여 데이터가 매핑되므로 하드웨어가 매우 가볍게 동작합니다.
외계인을 확장한 게임기를 시뮬레이션 해본다. 게임 하드웨어 베릴로그 RTL이 변경되었을 뿐 테스트벤치는 이전(Q.06) 과 동일하다.
$ cd ~/ETRI050_DesignKit/Project/MyChip_Games/\
space_invader_by_Gemini/Q.07_Alien_5x5
$ source run.sh
verilator --binary -Wall -Wno-fatal \
./space_invaders_engine_glcd.v \
../Q.06_Bullet_Fire/tb_space_invaders.v \
../Q.06_Bullet_Fire/sim_display.cpp \
-LDFLAGS "$(sdl2-config --libs)" \
-CFLAGS "$(sdl2-config --cflags) -std=c++17" \
--top-module tb_space_invaders \
--Mdir obj_dir
./obj_dir/Vtb_space_invaders
대형 아케이드 게임이라고 했지만 외계인 개수의 증가에 불과 했다. 아래로 내려올 뿐 외계인의 공격은 없다. 게임의 재미를 위해 확장 해줄 수 있단다.
이제 게임 엔진의 레이아웃이 완성되었습니다. 여기서 더 확장하고 싶으신 부분이 있으신가요?
* 외계인들이 무작위로 아래로 미사일을 하강 투하하는 적 공격 로직 추가
* 아군 패들이 맞았을 때 차감되는 목숨(Life) 시스템과 점수 스코어 보드 연동
* 단순 네모 도트가 아닌 실제 클래식 도트 그래픽을 입히는 Sprite ROM 매핑 설계
8. Q08. 게임 재미를 위해 외계인 반격
외계인 반격으로 게임하는 재미를 높여 달라고 주문해 봤다.
외계인의 공격을 표현하기 위해 난수 발생기를 넣었다고 한다.
외계인 부대(5행 5열) 중 최하단에 있는 외계인들이 무작위로 아래 방향으로 적 미사일(Invader Bullet)을 투하하는 공격 로직을 추가했습니다. 하드웨어 내부에 외계인 전용 미사일 좌표 레지스터(inv_bullet_x, inv_bullet_y)와 활성화 플래그(inv_bullet_active)를 추가하고, 의사 난수 유도용 카운터를 결합하여 일정 주기마다 살아있는 외계인 위치에서 미사일이 떨어지도록 구현했습니다.
------------------------------
기존 코드의
2. 게임 메인 로직과
3. 그리기 레이어 판정 부분에 외계인 공격 및 플레이어 피격 판정이 추가된 최종 버전입니다.
베릴로그 RTL로 의사 난수 발생기가 궁금하다. 카운터에 모듈로 연산(%)으로 '무작위'라니 어색하다.
// 💡 하드웨어 의사 난수용 자유 가동 카운터 (추가)
always @(posedge clk)
rand_counter <= rand_counter + 1'b1;
......
always @(posedge clk or negedge rst_n) begin
// [D] 💡 외계인 공격 발사 생성 로직 (추가)
if (!inv_bullet_active && (rand_counter[4:0] == 5'h1F))
shoot_col = rand_counter[7:5] % 5; // 0~4열 무작위 선택
...
가장 아래에 위치한 외계인 만 공격할 수 있도록 했다. 중간에 놓인 외계인이 미사일을 발사하면 팀 킬이 될 수 있다. 인공지능이 제법 똑똑한 척 한다.
### 🎮 추가된 연동 메커니즘
1. **지능형 최하단 발사체 선정:** 5개의 열(`shoot_col`) 중 하나가 선택되면, 위에서부터가 아닌 아래 행(`r=4`)에서부터 역순으로 탐색하여 **그 시점에 살아있는 가장 최하단의 외계인** 손끝에서 미사일이 발사되도록 설계했습니다. 죽은 외계인의 공중에서 미사일이 생겨나는 버그를 완벽히 차단합니다.
2. **피격 판정 연결:** 적 미사일이 플레이어 좌표(`cannon_x`) 영역 내에 침범하면, 상태 머신이 즉시 `STATE_OVER`로 변환되며 화면 전체가 테두리 모드로 변경(Game Over 스크린)됩니다.
새로 생성된 베릴로그 RTL로 시뮬레이션 해봤더니 외계인 공격을 볼 수 없었다. 이유를 따져 물었다.
그 이유를 대면서 수정된 RTL를 만들어 준다.
외계인이 미사일을 공격(투하)하지 않는 이유는 rand_counter를 이용한 확률 조건문이 하드웨어(RTL) 루프 상에서 너무 엄격했거나, 25마리 외계인의 생사 조건을 판별하는 로직의 인덱싱 규칙이 맞지 않아 발사 기회를 계속 박탈당했기 때문입니다. Verilator linter 검증을 다시 한번 철저하게 수행하고, 외계인의 미사일 투하 빈도를 체감할 수 있도록 조건 확률을 완화(rand_counter[3:0] == 4'hF)하는 동시에, 5x5 외계인 배열 중 실제 하단에 배치된 외계인의 생존 플래그를 정교하게 트래킹하여 높은 빈도로 미사일을 투하하도록 수정한 완벽한 Verilog RTL 코드입니다.
------------------------------
앞선 코드에서 외계인 공격이 실패한 이유를 설명한다. 변명하는 인공지능의 혀가 길다. 처음부터 그리 해주지!
### 🛠️ 공격 실패 요인 수정 포인트 명시
* **배열 인덱스 하드웨어 매핑 고정:** `invader_alive[r*5 + shoot_col]` 구문에서 정수형 수식 연산 시 Verilator가 내부 비트 크기를 오인해 생존 비트를 0으로 오판하던 연산 결함을 `shoot_row`와 `shoot_col` 레지스터의 명시적 비트 유도 배정(`(shoot_row * 3'd5) + shoot_col`)으로 원천 교정했습니다.
* **난수 모듈로(Modulo) 에러 수정:** `rand_counter` 상위 비트를 `5`로 나눌 때 범위가 튀던 현상을 차단하기 위해 삼항 연산 바운더리 마스킹을 삽입했습니다.
여전히 모듈로 연산이 쓰였다. '의사(pseudo)' 난수라지만 시원찮다. 차라리 LFSR(Linear Feedback Shift Register)는 어땠을까? 외계인 공격을 확인해 보자. 게임 RTL 만 변경되었을 뿐이므로 테스트벤치는 이전 Q.06의 것을 사용한다.
$ cd ~/ETRI050_DesignKit/Project/MyChip_Games/\
space_invader_by_Gemini/Q.08d_Alien_Attack_Fix
$ source run.sh
verilator --binary -Wall -Wno-fatal \
./space_invaders_engine_glcd.v \
../Q.06_Bullet_Fire/tb_space_invaders.v \
../Q.06_Bullet_Fire/sim_display.cpp \
-LDFLAGS "$(sdl2-config --libs)" \
-CFLAGS "$(sdl2-config --cflags) -std=c++17" \
--top-module tb_space_invaders \
--Mdir obj_dir
./obj_dir/Vtb_space_invaders

9. Q09. 테스트벤치를 SystemC 로 바꾸자
앞선 테스트벤치는 베릴로그 였다. 외부의 그래픽 라이브러리를 사용하기 위해 DPI-C 를 적용하였다. 이번에는 테스트벤치 환경 모두를 SystemC 와 C++ 체계로 옮겨와 본다.
Q9a. import space_invaders_engine.v into SystemC testbench [문답a 링크]
제미니는 베릴로그를 SystemC로 옮겨오는 방법을 알려줬다. Verilator를 사용하라고 한다.
Verilator 환경에서 SystemC 3.0(또는 이전 규격) 테스트벤치를 사용하여 Verilog 모듈인 space_invaders_engine을 인클루드하고 최상위에서 연결(Top-level Binding)하는 방법입니다. Verilator는 Verilog 파일을 컴파일할 때 --sc 옵션을 주면 해당 모듈을 SystemC sc_module을 상속받는 C++ 클래스로 자동 변환해 줍니다. 따라서 SystemC 소스 코드 안에서 외래 모듈(sc_foreign_module) 선언을 할 필요 없이, Verilator가 생성한 헤더 파일을 직접 인클루드(Import)하여 일반 SystemC 모듈처럼 선언하고 신호선을 바인딩하면 됩니다.
------------------------------
Verilator가 변환해 줄 헤더 파일 Vspace_invaders_engine.h를 인클루드하여 가상 디스플레이 모듈(sc_glcd128x64_TLM)과 입력 자극 테스트벤치(sc_invaders_TB)를 유기적으로 연결합니다.
DPI-C에 비하여 테스트벤치 구성의 차이를 설명해준다. SystemC를 사용하면 '강력한' ESL 검증 환경을 구축할 수 있다고 하는데 그 근거가 궁금하다.
------------------------------
## 💡 아키텍처 상의 이점
이렇게 SystemC 환경으로 space_invaders_engine.v를 가져오면(Import), 전반적인 하드웨어 설계는 검증된 Verilog RTL 내부 논리 구조를 100% 그대로 유지하면서, 디스플레이 연동이나 스코어 보드 연산, 호스트 인터페이스 같은 복잡한 소프트웨어 성향의 주변 장치들은 SystemC(C++ 기반 SDL2 함수 덩어리)를 통해 매우 자유롭고 가볍게 설계·확장할 수 있는 강력한 ESL(Electronic System-Level) 검증 환경 체계가 구축됩니다.
시뮬레이터 빌드에 필요한 모든 파일들을 만들라고 지시했다.