Link

6502의 주소지정 방식(addressing mode)

기호 설명:

  • imm: 8비트 즉시값(상수)
  • addr: 16비트 메모리 주소
  • zp: 8비트 제로 페이지 주소
  • rel: 8비트 PC 상대 주소

서론

프로그램 명령어는 크게 연산코드(operation code)와 피연산자(operand)로 구성되어 있다. 연산코드(opcode)는 CPU가 수행할 연산의 종류를 나타내는 숫자 코드이고, 피연산자는 연산의 대상을 가리킨다. 연산의 대상이란 주로 레지스터나 메모리상의 데이터를 가리키는데, CPU 설계자들은 이러한 연산 대상들을 접근하기 위한 방법으로 다양한 종류의 주소지정 방식(addressing mode)을 제공한다.

같은 대상에 접근하더라도 주소지정 방식에 따라 명령어를 메모리에 저장하기 위해 필요한 공간부터 명령어를 실행하기 위해 필요로 하는 CPU 사이클의 개수가 달라진다. CPU가 제공하는 주소지정 방식을 잘 익혀두면 코드의 효율성을 높일 수 있다. 이 글에서는 6502에서 제공하는 주소지정 방식에 대해 다루도록 한다.

1. 묵시 주소지정 방식(implied addressing mode)

명령어 자체에 고정된 피연산자가 있기 때문에 별도의 명시적 피연산자 지정이 필요 없다. 이 방식을 사용하는 명령어의 예로는 INX, INY, CLC, SEC, SEI 등이 있다.
implied: op


2. 즉시 주소지정 방식(immediate addressing mode)

프로그램 코드상에 직접 피연산자를 명시하는 방법을 가리킨다. 8비트 정수값을 사용한다.

어셈블리어 코드 상에서 즉시값을 나타낼 때는 접두사로 #기호를 사용한다.
immediate: op #imm

상수를 나타낼 때 사용하는 기호들:

  • 이진수: %
  • 16진수: $
  • 아스키 문자: ‘’
LDA #10     ; A <- 10(십진수)
LDA #$AF    ; A <- $AF(16진수)
LDA #'T'    ; A <- 'T'(아스키 문자)
LDA #%0110  ; A <- %0110(이진수)
CMP #25     ; A와 십진수 상수 25를 비교한다


3. 누산기 주소지정 방식(accumulator addressing mode)

누산기 레지스터 A를 피연산자로 사용한다. ROL, ROR, ASL, LSR 명령어에서 이 방식을 사용한다.
accumulator: op A


4. 직접 주소지정 방식(direct addressing mode)

6502의 주소 버스 길이는 16비트다. 그래서 6502의 절대 주소(absolute) 주소는 16비트 정수로 나타낸다. 이러한 16비트 정수값을 그대로 주소 지정에 사용하는 방식을 절대 주소지정방식(absolute addressing mode) 또는 직접 주소지정방식(direct addressing mode)라고 하는데, 6502에서는 이러한 절대주소에 인덱스 레지스터 X, Y의 값을 더해서 피연산자가 있는 유효주소를 구하는 인덱스 주소지정 방식(indexed addressing mode)을 제공한다.

4-1. 직접 주소지정 방식(direct addressing mode) 또는 절대 주소지정 방식(absolute addressing mode)

16비트 절대 주소를 피연산자의 주소로 사용한다. 피연산자는 코드에 직접 지정한 메모리 번지에 존재한다.
direct: op $addr

LDA $02FF
JMP $72FF

실제 코드상에서는 16진수 주소를 직접 명시하는 대신 레이블을 통해 기호 상수를 사용하는 경우가 더 흔하다.

	.ORG $0200
	LDA num1
	LDX num2
	JSR foo
	JMP bar
	...
	
foo:
	...
	RTS
bar:
	...
	
num1 .DB $AB
num2 .DB $CD


4-2. 인덱스 주소지정 방식(indexed direct addressing mode)

16비트 절대 주소와 함께 인덱스 레지스터 X 또는 Y를 사용한다. 피연산자가 있는 유효주소(effective address)는 절대주소에 인덱스 레지스터 값을 더한 값이다. 예를 들어 X=$30일 때, LDA $0123, X 명령어가 지정한 피연산자의 주소는 $0123+$30=$0153이다.

indexed direct: op $addr, X / op $addr, Y

	LDA #0
	LDX #0
clrMem:
	STA arr1, X	; arr1 + X 번지에 A 레지스터의 값을 저장
	INX			; X의 값을 1 증가
	CPX #10		; X를 배열의 길이인 10과 비교
	BNE clrMem	; 10이 아닌 경우 clrMem으로(STA 명령어) 점프

; 10바이트 길이 배열: 메모리 공간 10바이트를 예약한다
arr1 .DSB 10


5. 제로 페이지(zero-page)

6502는 메모리 절약과 빠른 접근을 위해 제로 페이지 영역, 즉 메모리 $0000-$00FF 영역만을 가리키는 제로 페이지 주소 지정을 제공한다.
제로페이지 주소지정 역시 직접 주소지정방식과 마찬가지로 제로 페이지 주소를 그대로 사용하는 제로 페이지 직접주소지정 방식과 인덱스 레지스터 값을 더하는 제로 페이지 인덱스 주소지정 방식이 있다.

​### 5-1. 제로 페이지 직접주소지정 방식(zero-page direct addressing) 8비트 제로 페이지 주소를 피연산자의 주소로 사용한다. 제로 페이지이므로 지정된 주소의 상위 바이트 주소는 자동으로 $00이 된다.
예를 들어 제로 페이지 주소가 $AB인 경우 유효 주소는 $00AB가 된다.

zero-page direct: op $zp

LDA #$FF
STA $10
STA $20
STA $30


5-2. 제로 페이지 직접주소지정 방식(zero-page direct addressing)

인덱스 주소지정 방식과 마찬가지로 제로 페이지 주소에 인덱스 레지스터 X, Y의 값을 더하여 피연산자의 유효주소를 지정한다.

zero-page direct: op $zp, X / op $zp, Y

LDA #0
	TAX		; X <- A
clrZeroPage:
	STA $0, X
	INX
	BNE clrZeroPage ; 제로 페이지를 0으로 채운다.


6. 간접 주소(indirect address)

16비트 절대주소와 8비트 제로 페이지 주소는 간접 주소로 이용될 수 있다. 간접 주소(indirect address)란 지정한 메모리 주소에 피연산자 대신에 피연산자가 있는 메모리 번지가 있는 경우를 말한다. 간접 주소를 이용하면 실행시간에 피연산자의 주소를 동적으로 변경할 수 있기 때문에 프로그램의 유연성을 높일 수 있지만 그만큼 메모리 공간과 CPU 사이클을 더 많이 필요로 한다. 6502는 간접 주소와 관련하여 다음 세 가지 주소지정 방식을 제공한다.

​### 6-1. 간접 주소지정 방식(indirect addressing mode) 어셈블리 코드에서 간접 주소를 표현할 때는 16비트 절대 주소를 괄호로 둘러싼다. 명령어에 지정된 주소에는 명령어가 요구하는 피연산자가 있는 것이 아니라 피연산자가 있는 메모리 번지가 저장되어 있다. 간접 주소는 마치 포인터 변수를 참조하는 것과 유사하다. 포인터 변수에는 일반적인 값 대신 값이 들어있는 메모리 주소가 저장된다. 원하는 값에 접근하기 위해서는 포인터 변수를 역참조 해야 하는데, 간접 주소 역시 지정된 메모리 주소에 접근하여 다시 그곳에 있는 주소 값을 읽어들이는 과정이 필요하다. 이렇게 읽어들인 주소가 피연산자의 유효 주소가 되는 것이다.

6502에서 간접 주소지정 방식을 사용하는 명령어는 점프 명령어인 JMP 뿐이다. JMP ($1234)라는 명령어가 있다면 이는 $1234번지로 점프하라는 의미가 아니라, $1234 번지에 저장돼 있는 16비트 주소를 읽어와서 그 주소로 점프하라는 뜻이다. 다시 말해, 우리가 JMP ($1234) 명령어를 사용하기 위해서는 $1234 번지에 점프 하고자 하는 명령어 주소를 저장해둬야 한다.

indirect: op ($addr)


연습문제

메모리상의 데이터가 그림과 같을 때, 다음 질문에 대해 답하시오.

LocationValue
$1530$03
$1531$13
$1532$FA
$1533$02

(a). JMP ($1530) 명령어의 수행 이후 실행될 명령어의 주소는? -> $1303
(b). 그럼 JMP ($1532) 명령어는? -> $02FA
(c). 직접 주소를 이용하여 $FA13으로 점프하는 코드를 작성하시오. -> JMP $FA13
(d). 간접 주소를 이용하여 다시 코드를 작성하시오. -> JMP ($1531)


6-2. 사전 인덱스된 간접주소지정 방식(pre-indexed indirect addressing mode)

제로 페이지 주소와 인덱스 레지스터 X의 값을 합한 주소를 간접 주소로 사용한다. 즉, 명령어에 지정한 8비트 제로 페이지 주소를 zp라고 했을 때, 연산자의 유효 주소는 zp + X 번지에 저장되어 있는 메모리 주소 값이 된다. 예를 들어 X=$10이고, LDA ($a0, X)라는 명령어가 있을 때, 이 명령어의 피연산자는 $a0 + $10 = $b0 번지가 아니라, $b0 번지에 있는 메모리 주소가 된다.

이 방식의 명령어에서는 X 레지스터만을 인덱스 레지스터로 사용할 수 있다. Y 레지스터는 사용할 수 없다. 또한 간접 주소로 계산되는 메모리 주소는 어떤 값이 되는 상관이 없지만, 반드시 제로 페이지($0000-$00FF)를 가리키게 된다. 예를 들어 X=$15일 때, ($FF, X)는 간접 주소로 $FF+$15=$0114가 아니라, $0014를 가리키게 된다.

pre-indexed indirect: op ($zp, X)

주소지정의 올바른 예:

($26, X)

($30, X)
($FF, X) ; 물론 이 둘의 합이 제로 페이지 영역을 벗어나도 제로 페이지를 가리킨다. X=$15인 경우 $FF+$15=$0114이지만, 참조할 주소는 $0014로 지정된다.


주소지정의 잘못된 예:

($0116, X) ; 제로 페이지만을 참조해야 한다. 
($16, Y) ; Y 레지스터를 사용했다.


​연습문제

다음이 가리키는 주소는? (X=$10이라고 가정한다.)

(a). ($03, X) -> $0013
(b). ($75, Y) -> 사용할 수 없음. Y 레지스터를 사용했기 때문.
(c). ($1013, X) -> 사용할 수 없음. 제로 페이지 주소만을 사용할 수 있다.
(d). ($17, X) -> $0027
(e). ($F1, X) -> $0001

X=$10이라고 가정했을 때 사전 인덱싱된 간접 주소지정방식을 이용하여 다음의 위치를 가리키도록 코드를 작성하시오.

(f). 제로 페이지의 $25 바이트 -> ($15, X)
(g). 제로 페이지의 $07 바이트 -> ($F7, X) ; 10진수 음수 -9의 2의 보수 표기는 $F7
(h). 페이지 1의 $15 바이트 -> 지정할 수 없음.
(i). 메모리의 $F2 바이트 -> ($E2, X)
(j). 메모리의 $0101 바이트 -> 지정할 수 없음.


6-3. 사후 인덱스된 간접주소지정 방식(post-indexed indirect addressing)

앞서 다룬 주소지정 방식은 간접 주소가 지정된 제로 페이지 주소와 X 레지스터의 값의 합이었다. 유효 주소는 이 간접 주소를 역참조 하여 얻어낼 수 있다. 그러나 이 방식에서 간접 주소는 이미 명령어에 주어진다. 하지만 유효 주소는 간접 주소를 역참조 하여 얻어낸 주소 값에 다시 Y 레지스터를 더해야 얻을 수 있다.

이 방식의 명령어에서는 Y 레지스터만을 인덱스 레지스터로 사용할 수 있다. Y 레지스터의 값은 간접 주소에 더하는 값이 아니라, 간접 주소로 얻어낸 절대 주소의 값에 더해지는 것이므로 괄호로 묶지 않는다.

post-indexed indirect: op ($zp), Y


예)

($13), Y
($02), Y

연습문제

메모리상의 데이터가 다음과 같다고 가정하자.

LocationValue
$0011$01
$0012$BC
$0013$88
$0014$E3
$0015$00
$00FF$E0
$0100$FE
$0101$02
$0102$0A
$0103$0F
$FF00$08


X=$12, Y=$20, A=15 일 때, 다음 예제의 빈칸을 채우시오.

Addressing modeInstructionResult
pre-indexed indirectSTA ($00, X)Byte $88BC=15
indirectJMP ($0014)Next instruction at $00E3
post-indexed indirectADC ($14), Y$00E3 + $20 = $0103
A += byte at $0103
A = 30
indirectJMP ($0100)Next instruction at $02FE


바이트 순서(byte order)와 리틀 엔디안(little-endian) 방식

바이트 순서란 메모리에 연속된 바이트, 쉽게 말해 2바이트 이상의 데이터를 저장할 때 바이트를 어떤 순서로 저장할 것인지를 정해둔 규칙이다. 바이트 순서에는 크게 빅 엔디안(big-endian)과 리틀 엔디안(little-endian) 방식이 있는데, 프로세서마다 사용하는 바이트 순서가 다르다. 경우에 따라 두 방식 모두를 지원하기도 한다.

6502는 리틀 엔디안 방식을 사용한다. 리틀 엔디안 방식의 바이트 순서에 따르면, 연속된 n바이트 데이터를 메모리에 저장할 때 하위 바이트부터 상위 바이트 순서로 저장해야 한다. 가령 32비트 정수 $1234abcd 는 메모리에 저장될 때 $cd, $ab, $34, $12 순서로 저장된다. 연습문제에서 $0100 번지에 있는 주소를 $FE02가 아니라, $02FE로 읽는 것도 같은 맥락이다.


7. 상대주소지정 방식(relative addressing mode)

현재 프로그램 카운터의 값(다음에 실행할 명령어의 주소)으로부터 상대적인 변위(displacement)를 사용하는 주소지정 방식이다. 이 방식은 BEQ, BNE, BPL과 같은 분기 명령어(branch instruction)에서만 사용된다. 6502의 상대 주소는 부호 있는 1바이트 정수(-128~+127)를 사용한다. 따라서 분기 명령어의 점프 가능한 범위는 현재 PC 값에서 1바이트 범위로 제한된다.

프로그래머가 직접 명령어의 길이를 계산해서 점프할 주소의 상대적 위치를 계산하는 것은 어려운 일이다. 하지만 그렇게 할 필요는 없다. 상대 주소를 계산하는 것은 어셈블러의 몫이기 때문이다. 어셈블리 코드상에서는 분기 명령어의 피연산자로 16비트 절대 주소(주로 점프할 명령어의 레이블)를 사용할 수 있다. 하지만 어셈블러가 생성한 바이너리 코드에서 이 값은 8비트 상대 주소로 변환된다.

relative: op rel

예제)

0100 		.ORG $0100
0100 		LDA #0
0102 loop1:	ADC #$10
0104 		CMP #$f0
0106 		BNE loop1
0108 		BRK

이 프로그램의 기계어 코드:

A9 00 69 10 C9 F0 D0 FA 00
BNE loop1 ; D0 FA

-> D0은 BNE의 opcode이며, 레이블 LOOP1은 이로부터 $fa만큼 떨어진, 즉 -6만큼 떨어진 명령어의 주소를 의미한다.


참고 문헌