-
CAMLBOY는 OCaml로 개발되어 브라우저에서 동작하는 Game Boy 에뮬레이터임
- OCaml의 중상규모 프로젝트 개발 및 고급 기능 사용법을 실제로 익히기 위해 선택된 프로젝트임
-
기본 구조, 추상화, GADT, 펑터, 런타임 모듈 교체 등 다양한 OCaml 언어 특성을 실용적으로 활용함
- 브라우저에서 60FPS로 동작하며, 성능 개선 과정과 병목 분석, 최적화 경험을 공유함
- OCaml 생태계, 테스트 자동화, 그리고 에뮬레이터 개발이 실무 능력 향상에 미치는 영향을 정리함
프로젝트 개요
- 몇 달간 CAMLBOY 프로젝트를 진행하며, OCaml로 Game Boy 에뮬레이터를 제작함
-
데모 페이지에서 실행 가능하며, 다양한 homebrew ROM을 포함함
- 저장소는 GitHub에 공개됨
OCaml 학습 동기와 프로젝트 선정 배경
- 새로운 언어 학습 시, 중/대규모 코드 작성 방법과 고급 기능의 실제 활용법에 한계를 느낌
- 이러한 문제 해결을 위해 실질적인 프로젝트 경험의 필요성을 느껴 Game Boy 에뮬레이터 개발을 선택함
- 이유
- 사양이 명확해 구현 범위가 정해져 있음
- 충분히 복잡하지만 수개월 내 완료 가능한 크기임
- 개인적 동기가 큼
에뮬레이터 목표
-
가독성 및 유지보수성을 중시한 코드 작성
-
js_of_ocaml로 JavaScript로 컴파일해 브라우저에서 실행
- 모바일 브라우저에서도 플레이 가능한 FPS 달성
- 다양한 컴파일러 백엔드 성능 벤치마크 구현
본문 목표 및 주요 내용
이 글의 목적은 OCaml로 Game Boy 에뮬레이터를 제작하는 여정 공유임
다루는 내용:
- Game Boy 아키텍처 개요
-
테스트 가능하고 재사용성 높은 코드 구조화 방법
-
functor, GADT, 일급 모듈 등 고급 OCaml 기능 실전 활용
- 성능 병목 찾기, 최적화 및 개선 경험
- OCaml 전반에 대한 생각
전체 구조와 주요 인터페이스
-
CPU, Timer, GPU 등 주요 하드웨어가 동기화된 클럭에 따라 동작
-
버스는 주소에 따라 각 하드웨어 모듈에 데이터 접근/전달 기능 담당
- 각 하드웨어 모듈은 Addressable_intf.S 인터페이스를 구현함
- 버스 전체는 Word_addressable_intf.S 인터페이스를 따름
메인 루프 동작 방식
- 하드웨어의 동기화를 위해 메인 루프에서 아래 순환 단계 실행
- CPU 명령 1개 실행 및 소비된 사이클수 기록
- 같은 사이클수만큼 Timer, GPU 진행
- 이 방법으로 실제 하드웨어의 동기화 상태 모사
- 구현 코드 예시와 함께 설명 제공
8비트, 16비트 데이터 읽기/쓰기 추상화
- 다수 모듈이 8비트 데이터 입출력 인터페이스 (Addressable_intf.S) 구현
-
16비트 읽기/쓰기 확장은 Word_addressable_intf.S를 통해 상속 및 추가 기능 확장
- OCaml의 서명(signature) , 모듈 타입 포함(include) 방식으로 추상화 계층 구성
버스, 레지스터, CPU 구현
- 버스: 각 하드웨어 모듈에 대한 주소 기반 라우팅 기능 담당, 메모리맵 기준 분기처리
- 레지스터: 8비트, 16비트 레지스터 읽기/쓰기 인터페이스 제공
- CPU: 초기에는 버스 의존성이 강해 테스트가 어려움
- 펑터(functor) 적용으로 의존성 추상화 및 목(mock) 주입 가능
- 이를 통해 단위 테스트 작성이 훨씬 쉬워짐
인스트럭션 세트 표현 (GADT 활용)
- Game Boy는 8/16비트 명령 모두 존재, 인스트럭션 정의의 타입 안정성 필요
-
단순 variant 방식은 복잡한 패턴 매칭 반환값 타입 충돌 문제 발생
-
GADT(Generalized Algebraic Data Type) 를 적용해 입력 및 출력 타입 모두 안전하게 매칭 가능
- GADT 적용 시 각 인스트럭션의 인자 타입, 반환값 타입 모두 정확하게 타입 추론 가능
- 복잡한 명령어 패턴 및 파라미터에 안전하게 대응
카트리지 및 런타임 모듈 선택
- Game Boy 카트리지는 단순 ROM 외 추가 하드웨어(MBC, 타이머 등) 포함 가능
- 각 타입별로 모듈 별도 구현 및 런타임에 맞는 모듈 선택 필요
-
일급 모듈로 런타임 모듈 전환 및 확장성 실현
테스트와 탐색적 개발
-
test ROM 및 ppx_expect 활용
- 기능별 테스트 ROM: 산술연산, MBC 지원 등 구체적 영역 검증
- 실패시 화면 출력 등 명확한 진단 가능
- 통합 테스트로 대규모 리팩토링, 새로운 기능 추가 시 신뢰도 확보
-
탐색적 개발 방식 적용: 테스트 ROM으로 반복적으로 구현 및 검증
브라우저 UI 및 성능 최적화
-
js_of_ocaml로 쉽게 JS 빌드
-
Brr 라이브러리로 Javascript DOM API에 OCaml 방식으로 안전하게 접근
- 초기 성능(20FPS)은 낮았으나, 크롬 프로파일러로 GPU, 타이머, Bigstringaf 등 병목 분석
- 각 모듈별로 최적화 커밋 진행, JS 빌드에서 비효율적인 인라이닝 비활성화로 최종 60FPS(PC/모바일) 달성
- 네이티브 빌드에서는 1000FPS까지 성능 발휘
벤치마크 및 하드웨어 비교
-
헤드리스 벤치마크 모드 구현해 각 환경별 FPS 측정 가능
에뮬레이터 개발과 실무 능력
-
경쟁프로그래밍과 비슷하게, 명확한 사양 해석 → 구현 → 검증 루프 반복
- 사양 기반 개발/테스트 진행에 실질적 도움이 되는 경험
최신 OCaml 생태계 및 도구 발전
함수형 언어에 대한 소고
- 함수형 언어란 사이드 이펙트 최소화라는 설명에 의문을 가짐
-
추상화하에 숨은 mutable 상태는 성능을 위해 적극적으로 사용
- 필자는 정적 타입, 패턴매칭, 모듈 시스템, 타입 추론 등을 선호함
불편사항 및 추상화 의존 비용
- 종속성 관리 표준화가 아직 복잡하고 설명 부족 (opam 등)
-
모듈-펑터 구조로 추상화를 가미하면, 의존성 계층 전체 구조까지 수정 필요
- OOP와 달리 추상화 도입 시 상위 의존 모듈 작성법까지 변경 필요
추천 학습 자료
결론
- CAMLBOY 프로젝트를 통해 OCaml의 고급 기능과 테스트, 추상화, 브라우저 호환성 등을 실용적으로 경험
- 생태계 발전과 현실적인 개발 경험에서 얻은 장점과 한계 명확히 인식
-
에뮬레이터 개발이 중급 이상의 개발자 실력 향상에 실질적인 도움이 됨