OCaml로 Game Boy 에뮬레이터 만들기 (2022)

19 hours ago 2

  • CAMLBOY는 OCaml로 개발되어 브라우저에서 동작하는 Game Boy 에뮬레이터
  • OCaml의 중상규모 프로젝트 개발 및 고급 기능 사용법을 실제로 익히기 위해 선택된 프로젝트임
  • 기본 구조, 추상화, GADT, 펑터, 런타임 모듈 교체 등 다양한 OCaml 언어 특성을 실용적으로 활용함
  • 브라우저에서 60FPS로 동작하며, 성능 개선 과정과 병목 분석, 최적화 경험을 공유함
  • OCaml 생태계, 테스트 자동화, 그리고 에뮬레이터 개발이 실무 능력 향상에 미치는 영향을 정리함

프로젝트 개요

  • 몇 달간 CAMLBOY 프로젝트를 진행하며, OCaml로 Game Boy 에뮬레이터를 제작함
  • 데모 페이지에서 실행 가능하며, 다양한 homebrew ROM을 포함함
  • 저장소는 GitHub에 공개됨

OCaml 학습 동기와 프로젝트 선정 배경

  • 새로운 언어 학습 시, 중/대규모 코드 작성 방법고급 기능의 실제 활용법에 한계를 느낌
  • 이러한 문제 해결을 위해 실질적인 프로젝트 경험의 필요성을 느껴 Game Boy 에뮬레이터 개발을 선택함
  • 이유
    • 사양이 명확해 구현 범위가 정해져 있음
    • 충분히 복잡하지만 수개월 내 완료 가능한 크기임
    • 개인적 동기가 큼

에뮬레이터 목표

  • 가독성 및 유지보수성을 중시한 코드 작성
  • js_of_ocamlJavaScript로 컴파일해 브라우저에서 실행
  • 모바일 브라우저에서도 플레이 가능한 FPS 달성
  • 다양한 컴파일러 백엔드 성능 벤치마크 구현

본문 목표 및 주요 내용

이 글의 목적은 OCaml로 Game Boy 에뮬레이터를 제작하는 여정 공유임
다루는 내용:

  • Game Boy 아키텍처 개요
  • 테스트 가능하고 재사용성 높은 코드 구조화 방법
  • functor, GADT, 일급 모듈 등 고급 OCaml 기능 실전 활용
  • 성능 병목 찾기, 최적화 및 개선 경험
  • OCaml 전반에 대한 생각

전체 구조와 주요 인터페이스

  • CPU, Timer, GPU 등 주요 하드웨어가 동기화된 클럭에 따라 동작
  • 버스는 주소에 따라 각 하드웨어 모듈에 데이터 접근/전달 기능 담당
  • 각 하드웨어 모듈은 Addressable_intf.S 인터페이스를 구현함
  • 버스 전체는 Word_addressable_intf.S 인터페이스를 따름

메인 루프 동작 방식

  • 하드웨어의 동기화를 위해 메인 루프에서 아래 순환 단계 실행
    1. CPU 명령 1개 실행 및 소비된 사이클수 기록
    2. 같은 사이클수만큼 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 ROMppx_expect 활용
    • 기능별 테스트 ROM: 산술연산, MBC 지원 등 구체적 영역 검증
    • 실패시 화면 출력 등 명확한 진단 가능
  • 통합 테스트로 대규모 리팩토링, 새로운 기능 추가 시 신뢰도 확보
  • 탐색적 개발 방식 적용: 테스트 ROM으로 반복적으로 구현 및 검증

브라우저 UI 및 성능 최적화

  • js_of_ocaml쉽게 JS 빌드
  • Brr 라이브러리로 Javascript DOM API에 OCaml 방식으로 안전하게 접근
  • 초기 성능(20FPS)은 낮았으나, 크롬 프로파일러로 GPU, 타이머, Bigstringaf 등 병목 분석
  • 각 모듈별로 최적화 커밋 진행, JS 빌드에서 비효율적인 인라이닝 비활성화로 최종 60FPS(PC/모바일) 달성
  • 네이티브 빌드에서는 1000FPS까지 성능 발휘

벤치마크 및 하드웨어 비교

  • 헤드리스 벤치마크 모드 구현해 각 환경별 FPS 측정 가능

에뮬레이터 개발과 실무 능력

  • 경쟁프로그래밍과 비슷하게, 명확한 사양 해석 → 구현 → 검증 루프 반복
  • 사양 기반 개발/테스트 진행에 실질적 도움이 되는 경험

최신 OCaml 생태계 및 도구 발전

  • dune간편한 빌드 시스템 경험 제공
  • Merlin, OCamlformat 등으로 자동완성, 코드 네비게이션, 포매팅 용이
  • setup-ocaml로 Github Actions에도 손쉽게 적용 가능

함수형 언어에 대한 소고

  • 함수형 언어란 사이드 이펙트 최소화라는 설명에 의문을 가짐
  • 추상화하에 숨은 mutable 상태는 성능을 위해 적극적으로 사용
  • 필자는 정적 타입, 패턴매칭, 모듈 시스템, 타입 추론 등을 선호함

불편사항 및 추상화 의존 비용

  • 종속성 관리 표준화가 아직 복잡하고 설명 부족 (opam 등)
  • 모듈-펑터 구조로 추상화를 가미하면, 의존성 계층 전체 구조까지 수정 필요
  • OOP와 달리 추상화 도입 시 상위 의존 모듈 작성법까지 변경 필요

추천 학습 자료

결론

  • CAMLBOY 프로젝트를 통해 OCaml의 고급 기능과 테스트, 추상화, 브라우저 호환성 등을 실용적으로 경험
  • 생태계 발전과 현실적인 개발 경험에서 얻은 장점과 한계 명확히 인식
  • 에뮬레이터 개발이 중급 이상의 개발자 실력 향상에 실질적인 도움이 됨

Read Entire Article