슈퍼 패미컴
3장 CPU와 65c816
CPU는 명령을 한 줄씩 읽어 실행하는 일꾼입니다. 슈퍼 패미컴의 65c816이 가진 레지스터와 8/16비트 전환을 봅니다.
CPU: 명령을 한 줄씩 실행하는 장치 쉽게
롬 안의 프로그램은 "A에 5를 넣어라", "그 값을 어디에 적어라" 같은 아주 작은 명령이 줄지어 있는 것입니다. CPU는 이 명령을 한 줄 읽고, 무슨 뜻인지 알아내고, 실행한 다음, 다음 줄로 넘어가는 일을 1초에 수십만에서 백만 번 가까이 되풀이하는 일꾼입니다.
지금 몇 번째 줄을 읽는지는 PC(프로그램 카운터)라는 책갈피가 기억합니다. 보통은 한 줄을 실행하면 책갈피가 다음 줄로 넘어가지만, "어디로 가라"는 명령은 책갈피를 다른 곳에 꽂습니다. 반복과 갈림길은 모두 이렇게 만들어집니다.
레지스터: CPU 안의 작은 저장 칸 쉽게
메모리는 크지만 CPU에서 멀리 있습니다. 그래서 CPU 안에는 지금 다루는 값을 잠깐 쥐고 있는 아주 작은 칸이 몇 개 있습니다. 이것이 레지스터입니다. 일꾼의 손이나 작업대 위 메모지라고 생각하면 됩니다. 슈퍼 패미컴의 CPU인 65c816은 손이 이렇게 생겼습니다.
- A (16비트): 계산하는 손. 읽고, 더하고, 비교하고, 적는 일 대부분이 A를 거칩니다.
- X (16비트): 세는 손. "표의 몇 번째"처럼 위치를 셀 때 씁니다(인덱스).
- Y (16비트): X와 같은 일을 하는 두 번째 세는 손.
- PC (16비트): 프로그램 카운터. 지금 읽을 줄을 가리키는 책갈피.
- P (8비트): 상태 플래그 8개. "방금 결과가 0이었나" 같은 표시와 8/16비트 설정.
그림의 나머지 S·DB·PB·D는 이 장의 「더 깊이」와 5장에서 다룹니다.
레지스터 이름은 외울 필요가 없습니다. A로 계산하고, X·Y로 세고, PC가 다음 줄을 가리키고, P에 "방금 결과" 표시가 남는다는 것만 기억하면 5장의 어셈블리를 읽을 수 있습니다.
8비트와 16비트를 오가는 CPU 쉽게
65c816은 한 번에 1바이트(8비트)씩 다룰 수도, 2바이트(16비트)씩 다룰 수도 있습니다. 어느 쪽인지는 P 레지스터의 두 칸이 정합니다.
- M 칸이 1이면 A가 8비트, 0이면 16비트입니다.
- X 칸이 1이면 X·Y가 8비트, 0이면 16비트입니다.
이 칸을 끄는 명령이 REP, 켜는 명령이 SEP입니다. REP #$20은 "M 칸(값 $20)을 꺼라", 곧 "A를 16비트로"라는 뜻입니다. 중요한 점은 같은 명령 바이트라도 이 설정에 따라 뒤에 붙는 값의 길이가 달라진다는 것입니다. 아래 프로그램을 한 줄씩 실행해 보세요.
LDA(A에 값 넣기)의 명령 바이트는 두 번 다 A9이지만, 처음에는 뒤에 2바이트(34 12), 두 번째에는 1바이트(56)가 붙습니다. 그래서 롬을 읽을 때는 "지금 M·X가 어떤 상태인지" 알아야 바이트를 제대로 끊을 수 있습니다. 엉뚱한 상태로 읽으면 한 바이트씩 어긋나 뒤의 명령이 모두 엉터리로 보입니다. 마지막에 A가 $1256인 것도 눈여겨보세요. 8비트 모드에서도 위 바이트(B)는 지워지지 않고 숨어 있습니다.
더 깊이65c816의 계보, 두 가지 모드, P 레지스터 전부
6502에서 65c816까지
65c816은 1970년대의 8비트 CPU 6502를 16비트로 넓힌 것입니다. Western Design Center(WDC)의 빌 멘시(Bill Mensch)가 설계했고 1985년에 나왔습니다. Apple IIGS와 슈퍼 패미컴이 이 CPU를 썼는데, 슈퍼 패미컴의 CPU 칩(Ricoh 5A22)은 65c816에 DMA 같은 장치를 더한 것입니다. 주소는 24비트라 16MB까지 가리킬 수 있습니다.
슈퍼 패미컴에서 CPU 속도는 어느 주소를 읽느냐에 따라 3.58MHz, 2.68MHz, 1.78MHz로 달라집니다. 롬은 보통 2.68MHz로 읽지만, 게임이 레지스터 $420D의 비트 0을 켜면 $80–$BF의 $8000–$FFFF와 $C0–$FF 뱅크를 3.58MHz로 읽습니다(FastROM). 롬 헤더의 "빠른 롬" 비트는 "이 카트리지는 빠르게 읽어도 된다"는 표시일 뿐, 속도를 바꾸지는 않습니다(4장).
에뮬레이션 모드와 네이티브 모드
65c816은 전원을 켜면 6502처럼 동작하는 에뮬레이션 모드로 시작합니다. 이 모드에서는 A·X·Y가 모두 8비트로 묶이고 스택은 $0100–$01FF에만 쌓입니다. 옛 6502 프로그램이 그대로 돌게 하려는 장치입니다. 16비트 기능을 쓰려면 CLC 다음 XCE를 실행해 네이티브 모드로 바꿉니다. 슈퍼 패미컴 게임은 거의 모두 시작하자마자 이렇게 합니다. 이 장과 5장의 「명령 한 줄씩 실행하기」는 네이티브 모드만 다룹니다.
P 레지스터의 여덟 칸
- N: 결과의 맨 위 비트(음수 표시). Z: 결과가 0이면 1.
- C: 덧셈에서 올림이 났거나, 비교에서 "크거나 같음"이면 1. V: 부호 있는 덧셈이 넘쳤으면 1.
- M·X: 위에서 본 8/16비트 설정. X를 1로 바꾸는 순간 X·Y의 위 바이트는 0으로 지워집니다. A의 위 바이트(B)는 M을 바꿔도 남아 있고,
XBA로 A와 맞바꿀 수 있습니다. - D: 10진(BCD) 계산 모드. I: 켜면 IRQ 인터럽트를 받지 않습니다.
- 에뮬레이션 모드에서는 비트 5가 쓰이지 않고 비트 4가 B(BRK 표시)입니다.
뱅크 레지스터와 다이렉트 페이지
명령에 적힌 주소는 대부분 16비트라서, 나머지 8비트(뱅크)는 레지스터가 채웁니다. 코드는 PB, 데이터는 DB를 씁니다. LDA $8012는 실제로 DB:$8012를 읽습니다. 짧은 주소 LDA $10은 다이렉트 페이지라고 하며 D + $10, 늘 뱅크 $00을 읽습니다. 1바이트 짧아 자주 쓰는 변수에 씁니다. 리셋 때 DB·PB·D는 모두 0입니다.
실제 패치에서 실제 패치에서
전원을 켜면 어디부터 실행하나
돌격편 롬의 파일 0x7FFC에는 00 80, 곧 $8000이 있습니다. 이 자리를 리셋 벡터라고 하며, CPU는 전원을 켜면 에뮬레이션 모드로 $00:8000부터 읽기 시작합니다. 같은 헤더의 0x7FEA에 있는 $818D는 NMI 루틴의 주소입니다. NMI는 화면을 한 번 다 그린 직후마다 불리는데, 게임이 레지스터 $4200의 비트 7을 켜 두었을 때만 옵니다.
CPU가 둘인 카트리지
슈퍼 마리오 RPG 카트리지에는 SA-1이라는 CPU가 하나 더 있습니다. 본체 CPU보다 세 배쯤 빠른(10.74MHz) 65c816이라 같은 어셈블리로 짭니다. 두 CPU는 동시에 돌고, 이 게임은 필드 메뉴를 본체 CPU에, 전투 메뉴를 SA-1에 맡기도록 만들어졌습니다. 그래서 아이템 이름 하나를 한글로 바꾸려 해도 두 길을 따로 고쳐야 했습니다.
직접 해 보기 직접 해 보기
툴킷의 역어셈블러는 롬 바이트를 위 「명령 한 줄씩 실행하기」의 목록처럼 명령으로 풀어 줍니다. REP·SEP를 만나면 폭을 바꿔 가며 읽고, 중간부터 읽을 때는 시작 폭을 직접 알려 줍니다.
krpatch snes dis game.sfc 808000 40 # $80:8000부터 명령 40개
krpatch snes dis game.sfc 808000 --m16 --x16 # A·X/Y를 16비트로 놓고 시작같은 자리를 --m16 없이 한 번, 붙여서 한 번 읽어 보세요. 어느 쪽이 말이 되는지 비교하는 것이 실제 분석에서 폭을 알아내는 방법입니다.
실제 패치에서 이 개념이 쓰인 롬 분석 노트
노트의 자세한 절은 「자세히 보기」를 펼친 안쪽에 있습니다.
직접 해 보기 kr-patch-toolkit 명령 설명
참고 자료
- WDC, W65C816S 데이터시트 — 레지스터 구성, 에뮬레이션·네이티브 모드, M·X 플래그, 리셋 때의 초기값
- fullsnes, CPU Registers and Flags — P 레지스터 비트 배치
- fullsnes, SNES Memory Map — 영역별 CPU 속도 3.58·2.68·1.78MHz
- fullsnes, SNES Cart SA-1 — SA-1이 10.74MHz 65C816이라는 것
- SNESdev Wiki, 65C816 — 명령과 주소 지정 방식 요약
- Wikipedia, WDC 65C816 — 1985년 출시, Apple IIGS·SNES(Ricoh 5A22) 채용
댓글