어제 겨우 겨우 HACK 구현을 마무리하고

SW로 넘어가기 전에 어셈블러에 대해서 짚고 넘어가야한다.

 

어셈블러야 4장에서 기계어 바이너리와 심볼릭한 기계어(어셈블리어)에 대해서

살펴보고 직접 구현했고, 어떻게 어셈블리어가 기계어가 매칭되는건지 알고 있다.

 

근데 4장에서 시뮬레이션을 돌릴때

작성한 어셈블리어 코드 한줄 한줄은

ROM에서 각자의 주소를 가지고 있었고 이들을 PC를 통해 넘어가거나 JUMP해서 다음 명령어를 수행할수 있었다.

 

라벨 심볼, 변수와 심볼 테이블

그런데 (LOOP), (END) 같은 라벨 심볼과 점프하기 전에 이 라벨심볼이 있었을때나 @LOOP, @END

@i, @sum과 같이 변수 준 경우까지 분명 어떤 주소를 가리킨게 아니었어도, 시뮬레이션을 실행시키면  @LOOP 이던데가 @23이라던가 @i가 @53로 바뀌어져 있었다.

 

 

 위는 4장에서 진행한 mult.asm 코드와 이를 cpu 에뮬레이터에 올렸을때 rom에 올라간 각 명령어들의 주소를 보여준다.

이 어셈블코드는 중간에 주석이 없음에도 총 26줄이었지만 ROM에 올라가면서 미리 정의된 심볼이 상수 주소로 바뀌었고, 라벨 심볼 (LOOP)는 사라졌었다. 그러면 이 라벨 심볼을 가리키고 있던 @LOOP는 어떻게 되었는지를 보면 @10으로 바뀌어 있다. 

 

 뒤에나오지만 미리 정의된 심볼이야 그 심볼들의 고유의 주소를 갖고, 라벨 심볼의 경우 그 라벨 심볼의 다음 줄에 있는 명령어를 가리키게 된다. Mult.asm의 경우 @LOOP는 기존의 (LOOP) 다음에 @R1(그리고 그 뒤에는 D=M)이 있었으므로 @LOOP는 @1(원래는 R1이므로 미리 할당된 주소 1으로 대체됨) 명령어의 주소인 @10으로 바뀌게 되었다.

 

 이 때 라벨, 변수 심볼릭의 주소를 정리하기 위한 심볼 테이블이 사용되는데 어떻게 심볼릭 라벨과 변수가 각자의 주소를 가지게 되었는지를 그리고 심볼 테이블과 어셈블러의 동작 원리를 알아보고, 직접 구현한다.

 

 

HACK 어셈블리어 복습

 hack의 심볼릭과 바이너리 명령어를 정리한 테이블인데 또 복습을 할 필요가 있나 싶지만 잠깐 짚고 넘어가면 심볼릭에 따라 위 테이블의 바이너리가 나온다.

 

 심볼릭에는 선정의된 심볼릭으로 가상 레지스터(R0, R1 ..., R15)가 있었고, 주소를 나타내는 SP, LCL, ARG, THIS, THAT, SCREEN, KBD도 선정의된 심볼릭이다(SP, LCL, ARG 등은 가상 머신 파트에서 다룬다). 라벨 심볼은 미리 정의되지 않았지만 어셈블리어를 작성하는 사람이 임의로 정할수 있는 점프 주소를 나타내는 심볼릭이 된다. 그리고 변수 심볼이 있지만 대충 넘어가고, 어쨋든 어셈블리의 과정에서 심볼릭으로 작성한 A 명령어들은 이 심볼들의 실제 주소를 나타내는 심볼 테이블의 값을 참조하여 대체된다. 

 

 심볼릭 규칙이야 심볼은 _, ., $,  : 같은 특수기호로 시작되면 안되고, A 명령어 상수(주소)의 경우 0-32767(접근 가능 주소 공간 크기 : 2^15-1)의 범위 내여야 하며, 공백이나 빈줄은 넘어가니 ok, 위 동작을 나타내는 심볼릭<->기계어 테이블에도 나오지만 변수나 상수를 제외한 대부분의 경우 대문자로 써야한다.

 

 

어셈블러의 동작

 일단 어셈블러의 동작을 크게 두가지로 나누면 명령어 처리와 심볼 처리가 있다.

 

1. 명령어 처리

 어셈블러는 입력된 어셈블리어를 위 표처럼 기계어로 변환시켜야하고, 심볼릭 참조/참조 심볼릭이라고나와 있기는 한데 그냥 숫자 주소로 나타내는, 그러니까 실제로는 숫자 주소인 곳을 참조(의미)하는 라벨 심볼, 변수 심볼을 문자가 아닌 숫자(실제 주소)로 바꿀수 있어야한다. 

 

2. 심볼 처리

 일단 어셈블러로 어셈블리어를 변환시킬때 라벨 심볼이나 변수같은거 없이 상수만 쓰라고 할수도 있겠지만 어셈블리어로 코드를 짜야하는 사람들을 위해서 심볼릭을 쓸수 있게 해두었는데, 이를 위해서 어셈블러는 어셈블리어 코드 전체를 두번 봐야한다. 어셈블리어를 두번 읽어(한번은 심볼테이블만 만들고, 다음에는 심볼테이블을 참고하여 기계어로 변환) 변환하는 어셈블러를 투 패스 어셈블러라고 부른다.

 

 

 투 어셈블러의 동작 과정은 두번 통과하기 전에 초기화 과정에서 우선 심볼 테이블을 만들고, 거기다가 선 정의된 심볼들과 그의 주소들을 넣어둔다.

 

 첫 번째 통과 과정에서는 한줄 한줄 읽어가면서 라벨 심볼을 선언하거나 주석인 경우를 제외하여 A나 C 명령어를 만날때마다 0에서 시작하여 +1씩 카운팅을 하는데, 그러던 중에 (LOOP) 같은 라벨 심볼 선언문을 만났을때 심볼 테이블에다가 추가하고 그 라벨 심볼을 만났을때 카운팅 한 값 + 1 한 것을(라벨 심볼 다음 명령어 ROM 상의 주소)을 넣어주는 식으로 (LOOP)를 만났을때 카운팅된 값이 25이면, 라벨 테이블 상에서는 26이 등록된다.

 

 위 심볼 테이블의 LOOP 같은 경우에는 주석문을 제외하고 보면 @i가 ROM의 0번을, M=1가 ROM의 1번을, @sum이 ROM의 2번을, M=0가 ROM의 3번을 받고, 그 다음에 (LOOP)를 만났으므로 심볼 테이블에 LOOP : 3 + 1=4가 심볼 테이블에 추가된다 하지만 라벨 심볼이 선언되었으므로 아직은 +1하지 않고, 그 다음의 @i는 ROM의 4번 주소를 받게 된다. 이 부분이 조금 했갈리지만 (LOOP)에서도 +1 카운팅을 했다면 @i는 ROM의 5번 주소를 받게 될것이다. 아무튼 이 과정에서는 변수는 아니지만 라벨 심볼의 주소들을 심볼 테이블에 등록해는것 까지만 하였다.

 

 두 번째 통과 과정에서는 기계어로 번역하면서 변수도 처리하는데, 변수를 만난날 때 어떻게 되는지 예를들어 생각해보자. 만약 번역 중에 @R0을 만났을 때 하자 R0은 이미 심볼 테이블에 ROM의 0번 주소라고 정의되어있으므로 0으로 대치하여 기계어로 번역하게 된다. 하지만 심볼 테이블에 등록 되지 않은 경우에는 이 변수를 심볼 테이블에다가 추가하고, RAM 상의 주소를 할당받게 된다.

 

 잠깐 앞에서 햇갈렸던거 같은데 (LOOP), @LOOP 라벨 심볼의 경우 @LOOP는 점프할 명령어 주소, 그러니까 ROM 상의 주소로 대치되고, @i, @sum과 같이 라벨 심볼이 아닌 변수의 경우는 명령어의 주소가 아닌 데이터의 주소 RAM 상의 주소로 대치된다. 

 

 아무튼 다시 돌아와서 심볼 테이블에 없는 새 변수를 처음 만나게 된다면 RAM의 16번지를 할당 받게 되는데 0 ~ 15까지가 가상 레지스터 선정의 된 심볼들의 저장 공간으로 먼저 할당 받았기 때문이다. 그래서 그 다음 또 새 변수를 만나면 17, 18, ... 씩 올라가며 주소를 값으로 가지게 된다.

 

 다시 어셈블리어와 심볼 테이블을 보면, 첫번째 패스로 LOOP와 STOP의 ROM 상의 주소가 선정의된 심볼들(KBD 뒤에 LOOP) 다음에 추가 되어 있고, 두 번째 패스때 가장 먼저 변수 @i를 만나 RAM 16번지를, 그 다음에 @sum을 만나면서 RAM 17번지가 할당되어있다.

 

 

 이번 장은 간단한 어셈블러를 만드는거다 보니까 투패스와 심볼 테이블 말고는 크게 새로운 내용도 없고 분량도 적었다. 이제 어셈블러 구현으로 넘어가자.

+ Recent posts