2P by GN⁺ | ★ favorite | 댓글 1개
  • 6o6은 보호 기능이 약한 NMOS 6502 위에서 다시 6502를 소프트웨어로 실행해, 오래된 8비트 시스템에 제어 가능한 가상 실행 계층을 더하는 프로젝트임
  • 게스트 코드의 명령 실행과 메모리 접근을 중간에서 통제해 주소 재매핑, 불법 읽기/쓰기 차단, jam opcode 트랩 같은 기능을 제공함
  • 핵심은 호스트 6502의 ALU를 그대로 빌려 쓰는 방식으로, 게스트 레지스터와 플래그를 호스트에 올려 같은 명령을 실행한 뒤 결과를 다시 저장함
  • 검증에는 Klaus Dormann의 6502 functional test suite와 lib6502 기반 테스트 환경이 쓰였고, 최적화된 구성은 1,602,516,769개 명령으로 비최적화 구성보다 36.5% 적은 명령을 실행함
  • 함께 공개된 The Incredible KIMplement 1.0과 여러 예제는 KIM-1 에뮬레이션, 중첩 가상화, 작업 전환, geoRAM 기반 외부 메모리 시스템까지 6o6의 활용 범위를 보여줌

6o6과 KIMplement가 공개한 것

  • The Incredible KIMplement 1.0은 1KB, 1MHz MOS/Commodore KIM-1 6502 단일 보드 컴퓨터를 에뮬레이션함
    • 확장 없는 Commodore 64에서 동작함
    • KIM의 내장 TTY를 지원하고, 실제 컴퓨터의 시리얼 포트를 통한 접근도 가능함
    • 주소 공간은 16K로 확장됨
  • 6o6은 “6502-on-6502”의 약자로, 6502 CPU 위에서 실행되는 완전한 소프트웨어 NMOS 6502 가상 CPU임
    • 게스트 코드 실행을 제어함
    • 문서화되지 않은 opcode와 jam opcode를 트랩함
    • 모든 메모리 접근을 추상화함
    • 주소 재매핑, 불법 읽기/쓰기 가로채기, 가상 메모리 기반 실행을 지원함
  • Commodore 64와 Apple IIe에서 게스트 hello world를 실행하고, 다시 6o6 안에서 6o6을 실행하는 중첩 가상화도 동작함
    • stage 1은 거의 즉시 실행됨
    • stage 2는 더 느림
    • stage 3은 매우 느리지만 동작함

6502에 가상화가 필요한 이유

  • 초기 개인용 컴퓨터에서는 보통 하나의 프로그램이 전체 기계를 제어했고, 프로그램이 잘못 동작하면 재부팅으로 해결할 수 있었음
  • 다중 사용자나 다중 작업 환경에서는 잘못된 코드가 다른 주소 공간을 훼손하거나, 위험한 명령을 실행하거나, 자원을 독점하는 문제가 생김
  • NMOS 6502는 약 4,000개 미만의 트랜지스터로 구성된 단순한 CPU라 보호 기능에 한계가 있음
    • 많은 역사적 NMOS 6502 시스템은 zero page나 프로세서 스택 위치를 자유롭게 옮기기 어려웠음
    • 임의 위치로 코드 주소를 재매핑해 fixup 없이 실행하는 기능이 없었음
    • 특정 메모리 위치 접근을 포괄적으로 금지할 수 없었음
    • 문서화되지 않은 jam 또는 KIL opcode가 실행되면 프로세서가 완전히 멈출 수 있었음
  • 일부 문제는 하드웨어로 완화할 수 있음
    • NMI를 주기적으로 발생시키면 인터럽트 플래그를 설정해 시스템을 독점하려는 프로세스를 외부에서 멈출 수 있음
    • 일부 6502 멀티태스킹 커널은 이런 방식으로 선점형 작업 전환을 구현함
    • Eastern House Software의 “Trap65” 같은 in-circuit emulator는 잘못된 opcode를 트랩 가능한 BRK로 바꿀 수 있었지만, 비싸고 복잡한 버스 조작에는 한계가 있었음

6o6의 실행 방식

  • 단순 인터프리터 방식도 6502에서는 실용적일 수 있음
    • 6502는 레지스터 수가 적음
    • 명령은 56개이고 주소 지정 모드도 많지 않음
    • 프로세서 상태 추적이 비교적 쉬움
  • 6o6의 “가상화”는 호스트 6502의 ALU를 게스트 내부 연산에 사용한다는 점에 있음
    • 게스트 accumulator와 플래그를 호스트 CPU에 로드함
    • 게스트가 실행할 동일한 명령을 호스트에서 실행함
    • 결과와 플래그를 저장하고, 호스트 상태를 정리함
  • 직접 산술과 플래그 처리를 재구현하지 않아도 되는 점이 이 방식의 장점임
    • decimal mode, 즉 BCD 산술도 자연스럽게 동작함
    • 실제 6502가 계산하므로 결과가 6502와 같음
  • 메모리 값을 읽거나 레지스터를 전송할 때도 음수 플래그와 zero 플래그 처리를 위해 같은 방식을 사용함
  • 구현에는 자기 수정 코드가 사용되므로 ROM에 넣으려면 별도 주의가 필요함

VM, 하네스, 커널 구조

  • 6o6 VM은 전체 시스템이 아니라 엔진 역할을 맡음
  • 실행 환경은 세 부분으로 나뉨
    • VM: 실제 6502 위에서 동작하는 하드웨어 비의존적 가상 CPU
    • 하네스: 게스트 메모리와 관리 대상 하드웨어에 대한 인터페이스
    • 커널: VM을 호출하고 예외와 게스트 상태를 처리하는 제어 루프
  • 하네스는 표준화된 jump table로 바이너리 인터페이스를 제공함
    • 특정 주소의 load/store를 구현함
    • instruction fetch를 처리함
    • 하드웨어 스택과 스택 포인터를 유지함
    • VM은 페이지 크기나 메모리 페이징 존재 여부를 가정하지 않음
  • 하네스는 단순 덧셈·비트 시프트 기반 주소 변환부터 페이지드 가상 메모리까지 구현할 수 있음
    • page fault를 외부로 넘기지 않고 하네스가 load/store 과정에서 paging in/out을 수행할 수 있음
    • 보호 예외도 하네스가 발생시킬 수 있음
  • 커널은 VM 실행을 시작하고, VM이 반환한 상태 코드를 해석함
    • 하네스나 6o6 자체에서 발생한 예외를 처리함
    • 게스트 레지스터와 PC를 검사하거나 수정함
    • 특정 서비스 루틴을 네이티브로 처리할 수 있음
    • VM 호출 사이에는 게스트 CPU가 “정지” 상태이므로 상태 캡처나 컨텍스트 스위칭이 가능함
  • VM은 가상 IRQ, NMI, reset을 직접 발생시키지 않음
    • 이런 이벤트 발생 시점은 커널이 결정함
    • BRK는 지원되지만, VM은 스택을 설정한 뒤 새 PC로 이동하지 않고 예외를 반환함

KIMplement에서의 6o6 활용

  • KIMplement의 하네스는 표준 6502 스택과 KIM-4 확장 장치를 가상화함
    • $0000-$17ff: 읽기/쓰기 메모리
    • $1800-$1fff: ROM
    • $2000-$3fff: RAM
    • $4000-$fff7: 매핑되지 않은 쓰기 불가 공간
    • $1ff8-$1fff는 벡터용으로 $fff8-$ffff에 mirror됨
  • 호스트 Commodore 64는 하위 16K를 $4000-$7fff에 저장하고, 나머지는 하네스가 합성함
  • KIMplement 커널은 RRIOT 에뮬레이션, LED 표시, TTY 서비스, stop과 Single-Step Switch용 NMI 삽입, KIM-1 ROM monitor 일부 트랩을 처리함
  • VM 실행 후 KIMplement 커널은 6o6의 PC를 검사해 현재 루틴을 가로챌지 결정함
    • TTY와 일부 기능이 이 방식으로 구현됨

성능 최적화

  • 6o6과 하네스 사이의 호출은 큰 병목이 될 수 있음
    • 단순한 명령도 최소 한 번의 fetch가 필요함
    • 간접 주소 지정은 더 많은 메모리 접근을 만들 수 있음
  • KIMplement 0.2에서는 메모리 load 일부를 전처리기 매크로로 인라인화
    • 임의 가상 주소용 load 루틴과 zero page 최적화 load 루틴을 VM에 직접 연결함
    • 속도는 크게 개선됐지만 VM 크기는 커짐
    • store는 빈도가 낮고 더 복잡해 서브루틴 호출로 유지함
  • 최신 반복에서는 인라인 매크로가 program counter에 접근하는 방식의 비효율도 정리해 instruction fetch를 더 개선함
  • KIMplement 0.3에서는 “extra helpings”라는 원시적인 instruction fusion을 추가함
    • 메모리를 건드리지 않는 명령은 커널로 즉시 돌아갈 필요가 없음
    • 즉시값 명령, accumulator 중심 명령, 대부분의 implied 명령, 분기하지 않은 branch가 대상임
    • load/store, PC 비순차 변경, 예외가 발생하면 VM은 명령 묶기 시도를 멈춤
  • extra helpings는 VM 자체를 빠르게 만들지는 않음
    • KIMplement처럼 PC 위치에 따라 기능을 제한하면 약간 느릴 수도 있음
    • 대신 커널이 관찰할 변화가 없는 명령마다 불필요하게 실행되지 않아 전체 시스템의 다른 부분이 빨라짐
    • PC를 정밀하게 제어해야 하는 애플리케이션에는 방해가 될 수 있어 단계적 또는 완전 비활성화 옵션이 있음

검증과 테스트 결과

  • 검증에는 Klaus Dormann의 functional test suite가 사용됨
    • 제공된 바이너리는 하드웨어에 대한 가정을 하지 않음
    • 성공 종료는 특정 위치의 무한 루프로 신호를 보냄
  • 테스트는 Ian Piumarta의 lib6502 CPU emulator를 사용해 셸에서 직접 실행되도록 구성됨
  • lib6502는 decimal mode의 edge case 때문에 처음에는 실패했고, 패치 후 통과함
  • Klaus가 제공하는 바이너리는 전체 64K라서 기본 6502 주소 공간 안에 6o6과 함께 둘 수 없었음
    • lib6502에 32K bank-switched 최소 시스템 패치를 추가함
    • $7000-$efff 영역을 사용하고, 테스트 바이너리의 앞 32K와 뒤 32K를 서로 다른 bank에 둠
  • 세 가지 구성으로 테스트함
    • extra helpings 없음, inline fetch macro 없음
    • inline fetch macro 있음, extra helpings 없음
    • inline fetch macro와 extra helpings 모두 있음
  • 세 구성 모두 Klaus suite를 통과함
  • 명령 수 결과는 다음과 같음
    • 6o6 없는 lib6502: 30,646,178개 명령
    • 최적화 없는 6o6: 2,188,322,914개 명령
    • inline fetch macro 적용: 1,713,350,225개 명령
    • inline fetch macro와 extra helpings 적용: 1,602,516,769개 명령
  • 가장 빠른 6o6 구성은 가장 덜 최적화된 구성보다 36.5% 적은 명령을 실행함
  • 가장 빠른 구성은 게스트 명령 하나당 평균 52.3개 명령을 실행함
    • 이 수치에는 하네스, 커널, 6o6 실행이 모두 포함됨
    • 명령마다 cycle count가 다르므로 속도 배율로 해석하면 안 됨

포함된 예제

  • hello world 예제는 같은 프로그램을 먼저 네이티브 CPU에서 실행하고, 이후 6o6을 통해 실행함
    • Commodore 64에서는 $ffd2, Apple II에서는 $fded의 문자 출력 루틴으로 매핑됨
    • 커널은 PC가 문자 출력 루틴을 가리키는 것을 감지하면 게스트 accumulator를 가져와 네이티브 ROM 루틴을 호출하고, 스택에서 return address를 꺼내 루프로 돌아감
  • inception 예제는 같은 하네스와 커널로 6o6이 자기 자신을 payload로 실행함
    • 각 stage는 고유한 zero page와 stack을 가짐
    • 현재 6o6은 자기 수정 코드를 사용하므로 stage마다 VM 복사본이 필요함
    • stage 3에서는 메모리 대부분이 VM 3개 사본에 쓰이며, inline fetch macro 포함 VM은 개당 10KB 이상임
  • 중첩 실행에서 CHROUT 호출은 stage 3에서 stage 2, stage 1을 거쳐 마지막에 네이티브 루틴으로 전달됨
  • payload 종료는 RTS 명령을 “kick”처럼 사용함
    • 시작 시 스택에 return address가 없기 때문에 RTS는 stack underflow를 유발함
    • 하네스가 이를 예외로 보고하면 커널이 정상 종료로 처리함
    • 깊은 stage에서도 같은 방식으로 상위 커널까지 전파됨
  • Apple II에서는 실행 후 CALL 2051, Commodore 64에서는 RUN으로 다시 실행할 수 있음
    • Apple II 버전은 $9000 위의 resident DOS 영역까지 사용하므로 실행 후 재부팅을 권장함

작업 전환 예제

  • tasks 예제는 두 독립 작업 사이를 오가는 작은 task-switching 커널임
  • 각 작업은 자기 zero page, stack, 작은 코드 주소 영역을 가지며, 서로와 VM의 존재를 알지 못함
  • 하나의 작업은 알파벳을 표시하고, 다른 작업은 숫자를 표시함
    • 숫자는 시각적으로 구분하기 위해 reverse video로 표시됨
    • 키를 누를 때마다 작업이 전환됨
  • 두 작업은 zero page의 같은 위치를 상태 저장에 쓰지만, zero page가 독립되어 있어 각자 마지막 위치에서 이어서 실행됨
  • 컨텍스트 스위칭에 필요한 것은 현재 작업 정보와 각 작업의 A, X, Y, P, S, PC 상태 저장 영역임
    • 하네스는 “on CPU” 작업을 보고 zero page, stack, 실행 코드의 물리 주소를 선택함
    • 커널은 전환 시 다른 상태를 저장·로드하고 다른 작업을 “on processor”로 표시함

geoRAM 기반 64K 외부 메모리 예제

  • vmgr 예제는 Commodore 64 전용이며, 시스템 자체 RAM을 쓰지 않는 64K 주소 공간을 외부 메모리로 제공함
  • geoRAM은 Commodore 공식 REU와 다른 paged RAM 장치임
    • REU는 DMA 중심이며, MOS 8726 REC를 사용해 main memory와 읽기·쓰기·교환 작업을 수행함
    • geoRAM은 I/O 범위 $de00의 256바이트 window page로 메모리를 매핑함
    • 제어 레지스터는 $dffe, $dfff에 있음
    • 현대 호환 clone은 최대 4MB 용량도 있음
    • VICE는 geoRAM 에뮬레이션을 지원함
  • 예제는 RC2014 Z80 kit computer용으로 제공됐던 6502 processor module의 ROM을 사용함
    • ROM에는 monitor와 Lee Davison의 EhBASIC이 들어 있음
    • 사용된 ROM은 GitHub에 있는 pre-built ROM이며, 6551 버전을 사용함
  • 하네스는 guest ROM 영역인 $c100부터의 write를 무시함
    • 16K 미만 쓰기는 빠른 경로를 사용함
    • 그 이상은 mask와 shift로 geoRAM bank를 조정함
    • 현재 geoRAM page를 캐시해 같은 page 접근에서는 설정 작업을 건너뜀
  • 커널과 main program은 이 예제에서 하나로 합쳐져 있음
    • geoRAM 존재와 동작을 확인함
    • ROM 이미지를 geoRAM에 복사함
    • BRK는 monitor로 되돌림
    • illegal instruction, user-defined instruction trap 등은 BRK처럼 처리함
  • RC2014 ROM의 serial vector를 가로채 간단한 터미널을 에뮬레이션함
    • PETSCII와 터미널 문자 변환을 수행함
    • 작은 커서를 유지함
    • 게스트 레지스터와 플래그를 결과에 맞게 조정함
    • CTRL-SHIFT-Commodore로 에뮬레이트된 시스템을 reset할 수 있고 메모리는 유지됨
  • EhBASIC cold start에서 메모리 크기를 직접 입력하지 않으면 C64와 geoRAM 조합에서 32768바이트 free를 찾는 데 약 1분이 걸림
    • ROM은 $8000 hard cap으로 빌드되어 실제로는 더 있어도 32768바이트로 제한됨
    • $8000-$c0ff에는 다른 것을 둘 수 있음
    • EhBASIC은 소문자 command나 keyword를 받지 않아 모두 대문자로 입력해야 함
  • 실제 Commodore 128DCR과 512K geoRAM cartridge에서도 동작함
    • 부동소수점 연산도 정상 동작함
    • bad instruction은 제어된 방식으로 즉시 가로채짐
    • 256바이트 window를 제외하면 화면의 시스템은 6502 자체 주소 공간에서 실행되지 않음
    • 512K geoRAM에서도 64K 6502 시스템 task 8개를 별도로 둘 수 있음

향후 개선과 활용처

  • 6o6을 ROM에서 실행할 수 있게 만드는 개선은 가능하지만, 리팩터링이 필요하고 더 느려질 수 있어 선택지에 가까움
  • 65816 에뮬레이션은 범위 밖으로 보지만, NMOS 시스템에서 CMOS 명령을 에뮬레이트하는 것은 가능할 수 있음
    • ALU를 사용하므로 NMOS 6502가 CMOS 65C02를 에뮬레이트해도 플래그는 NMOS 방식으로 설정됨
    • 반대 방향도 마찬가지임
    • 주소 지정은 현재 NMOS CPU 방식으로 작성되어 있음
  • inline memory macro 방식에는 peephole optimization 기회가 있음
    • 실제 assembly 전에 “post-preprocessor” pass를 둘 수 있음
    • 도구 체인이 더 복잡해지므로 일반적인 이득을 확인해야 함
  • 다운로드한 코드를 현재 작업을 망치지 않고 실행하는 용도가 6o6의 명시적 활용처 중 하나임
    • Gopher client 일부로 사용해 다운로드한 것을 동적으로 실행하는 아이디어가 있음
  • 새 6502 시스템을 직접 설계한다면 필요한 기능을 하드웨어로 구현하는 쪽이 더 빠를 수 있음
  • NMOS CPU에서 보호 장치가 적거나 추가 실리콘을 최소화하려는 경우, 6o6은 유연하고 적응 가능한 대안이 됨

배포와 라이선스

  • The Incredible KIMplement는 홈페이지GitHub에서 제공됨
  • 6o6은 GitHub에서 제공되며, 글에서 다룬 네 가지 예제도 포함됨
  • KIMplement 1.0 업데이트는 공개용 정리와 작은 버그 수정 중심임
  • KIMplement에는 Dave Hassler가 제공한 Tiny PILOT도 포함됨
    • Tiny PILOT은 1979년 MICRO magazine에 Nicholas Vrtis가 작성한 구현에 Bob Applegate와 Dave Hassler의 패치가 더해진 것임
    • Dave Hassler는 Carol Shaw와 Harry Stewart의 1980 Atari PILOT 구현에서 ELIZA도 포팅함
  • KIMplement와 6o6은 모두 Floodgap Free Software License로 배포됨

댓글과 토론

Hacker News 의견들
  • 단순하고 제한적인 6502인데도, 거의 50년 된 아키텍처가 계속 새로운 한계까지 밀려나는 걸 보는 건 늘 흥미로움
    초저가·대량 생산 시장을 겨냥한 일부 SoC에는 아직도 6502 코어가 들어감

    • 그 이유가 뭘까? 이미 잘 동작하는 설계를 유지해서 연구개발 비용을 아끼는 쪽일 듯함
      10센트짜리 RISC-V 코어를 이기기는 쉽지 않아 보임
      처음엔 6502 코어가 6502개 들어간 멀티코어 SoC를 떠올리고 웃었는데, FPGA로 해보면 재미있는 프로젝트가 될 것 같음
    • 원하는 거의 모든 작업에 쓸 수 있는 코드가 엄청나게 많이 있음
      Apple 2에 16비트 후속인 65816을 가변 속도 확장 카드로 꽂아 쓰고 있는데, 대부분은 8비트 모드로 돌림. 잘 동작하고 라이브러리 코드도 대부분 8비트라서임
      이 속도에서는 칩이 빠름. 특히 램과 CPU가 1:1로 클록되는 단순한 모델을 생각하면 더 그렇다. 내 경우 1MHz 버스를 통해 코드를 실행할 수 있어서, 대부분의 다중 사이클 연산이 메모리 fetch당 단일 버스 사이클처럼 됨
      또는 카드에 1MB RAM이 있고 이 RAM은 CPU 속도(0.15~16MHz)로 동작함. 이렇게 하면 고수준 언어로 작성된 큰 프로그램도 쓸 만한 속도로 돌릴 만큼 빠름. 물론 어셈블리는 말도 안 되게 빠름
      이것저것 해킹해 보기 꽤 재미있는 환경임
  • GEOS 창 안에서 GEOS를 실행할 수 있나?

  • 이 글은 한참 동안 댓글 없이 떠 있어서 나중에 훑어봐야겠다고 생각했는데, 핵심은 Commodore 64가 완전히 다른 6502 기반 시스템을 에뮬레이션하는 방식에 있음
    “6o6”, 즉 “6502-on-6502”는 6502 CPU 위에서 돌아가는 완전한 가상화 소프트웨어 NMOS 6502 CPU이고, 문서화되지 않은 opcode와 jam opcode 트랩까지 포함해 게스트 코드 실행을 완전히 제어하며, 메모리 접근도 전부 추상화함
    그래서 주소 재매핑, 불법 읽기·쓰기 가로채기, 심지어 완전한 가상 메모리 실행까지 가능함. 전체 기능 테스트를 통과할 뿐 아니라, 자기 자신을 가상화하는 자기 자신까지 가상화할 정도라니 6502 관점을 넘어 모든 관점에서 대단한 작업임
    Zilog Z80에 보호 모드가 있다는 영상도 떠올랐음: https://www.youtube.com/watch?v=DLSUAVPKeYk

  • 처음 6502 어셈블리를 독학하던 때가 떠오름. “The Visual Computer”라는 책이 있었고 플로피 디스크에 에뮬레이터가 같이 들어 있었는데, 정말 눈이 트이는 경험이었음
    책 PDF는 찾았지만 [1], 플로피에 있던 소프트웨어가 어딘가에 남아 있는지는 모르겠음
    [1] https://files.commodore.software/reference-material/books/c6...

  • https://archive.fo/2u3Y8