View
이 글은 insight 출판사의 [밑바닥부터 만드는 컴퓨팅 시스템 / The Elements of Computing System]이라는 책에 있는 프로젝트(과제) 를 수행하는 글입니다. 해외에서는 Nand2Tetris라는 이름의 프로젝트로 잘 알려져 있습니다. 내용은 동일하니, 공부할 때 참고하세요!
기계어 프로그램을 살펴보면 HW가 왜 그렇게 설계되었는지 알 수 있다. 기계어는 프로세서에서 산술/논리 연산을 하고, 메모리에서 값을 Save/Load하고, 레지스터끼리 데이터를 교환하고, 조건 검사를 하는 등의 여러가지 작업을 수행할 수 있다. 이는 HW에게 직접 명령을 내리는 것이므로, 기계어 역시 HW플랫폼의 구성요소라고 볼 수 있다.
배경
기계어 Machine Language
기계어는 프로세서와 레지스터를 이용해 메모리를 조작할 수 있도록 미리 정한 규칙을 말한다.
-
메모리 Memory
-
데이터와 명령어를 저장하는 HW장치를 통칭하는 말.
-
메모리는 각각 유일한 주소를 가진다. (Memory[Address] / RAM[Address])
-
-
프로세서 Processor
-
보통 우리는 이를 CPU라고 부른다.
-
특정한 기초연산(산술, 논리, 메모리접근, 제어, 분기)을 수행.
-
메모리와 레지스터의 값을 피연산자로 연산하고, 결과를 메모리나 레지스터에 저장.
-
-
레지스터 Register
- 프로세서가 빠르게 접근하여 연산을 수행할 수 있도록 보조하는 작은 메모리.
언어
기계어는 보통 1010001100011001 같은 코드로, 연상기호(mnemonic)를 이용해 ADD R3, R1, R9로도 표기가 가능하다.
기호를 이용해 프로그래밍(어셈블리어_Assembly Language)한 뒤, 기호 명령어를 2진 코드로 번역(어셈블러_Assembler를 통해 번역)할 수 있다.
기계어도 CPU, 레지스터, 어셈블리 문법등에 따라 달라질 수 있으나, 기본 방법 자체는 거의 비슷하다.
명령
산술 논리 연산
+-와 같은 기초 산술 연산과 비트반전_bit-wise negation, 비트이동_bit-shift 등의 불 연산을 함께 일컫는 말.
메모리 접근
레지스터와 메모리 사이 데이터를 저장/로드할 수 있다.
산술 논리명령도 필요한 경우 메모리 주소에 접근할 수 있다.
메모리 주소를 가리키는 방법인 주소 지정 모드 _Addressing Mode 에는 크게 3가지가 있다.
-
직접 주소 지정 방식 Direct Addressing : 특정 주소나 주소를 나타내는 기호를 활용
ex) LDR R1, 67 -> LDR R1, bar (R1 = Mem[67])
-
즉시 주소 지정 방법 Immediate Addressing : 레지스터에 주소에서 값을 가져오는 대신 상수를 바로 저장
ex) LDR R1, 67 (R1 = 67)
-
간접 주소 지정 방식 Indirect Addressing : 포인터의 개념으로, x = *(foo+j)로 표현되는 코드를 의미
ex) ADD R1, foo, j -> LDR* R2, R1 -> STR R2, x
제어흐름 Flow of Control
분기_branching : 다음 번 명령이 아닌, 다른 위치로 갑자기 이동해서 실행
-
반복_repetition : 루프의 시작으로 복귀
-
조건실행_conditional execution : If-Then 절에 해당
-
서브루틴 호출_subroutine calling : 다른 코드 세그먼트의 머리로 점프
이런 기능 구현을 위해 어셈블리 언어에는 특정한 주소로 점프하는 기능이 포함되어있다.
고수준의 언어에서는 아래와 같은 방식으로 표현되는 코드를
While (R1 >= 0) {
code segment1
}
code segment2
저수준에서는 아래와 같은 방식으로 표현한다.
beginWhile:
JNG R1, endWhile
code segment1
JMP beginWhile
endWhile:
code segment2
JMP beginWhile은 무조건점프_unconditional jump 명령어로, 가려는 위치의 주소만 작성한다. 그러나 JNG R1, endWhile은 조건점프_conditional jump 명령어로, 불 조건까지 명시해줘야 한다.
Hack의 기계어 명세
이 책을 통해 공부하면서 우리가 핵심적으로 하고싶은 일은 Hack이라는 컴퓨터 시스템을 하나 설계해보는 것이다.
Hack은 폰노이만 플랫폼이며 CPU, 명령 메모리 모듈, 데이터 메모리 모듈, 메모리 매핑 I/O장치 2개를 지닌 16Bit 컴퓨터이다. 이번 장과 앞으로의 장에서 Hack 시스템의 뼈대를 맞추고, 살을 붙이게 될 것이다.
개요
메모리 주소 공간
주소공간 = 명령어 메모리_instruction memory + 데이터 메모리 _data memory
width = 16Bit (주소를 담을 수 있는 공간은 15Bit) 이고 size = 32K 로, 최대 32K개의 16Bit word를 저장할 수 있다.
Hack CPU는 명령어 메모리에 저장된 프로그램만 수행할 수 있다. (명령어 메모리는 읽기 전용 ... 과거에 닌텐도 칩을 교체하여 게임을 바꾸듯, 외부에서 프로그램을 Load 해줘야 한다) -> txt파일의 기계어를 명령어로 번역해 메모리에 load하는 기능을 통해 실행해볼 수 있다.
레지스터
D, A라는 이름의 16Bit 레지스터 2개. 이는 산술, 논리 연산으로 조작할 수 있다.
D 레지스터는 데이터 값 저장용으로 사용
A 레지스터는 데이터 레지스터, 주소 레지스터 두 가지 역할을 수행한다. 이를 통해 메모리 직접 접근을 용이하게 한다. 명령어의 길이가 16Bit (명령어의 길이는 1 Word)이고 주소의 길이가 15Bit 이므로 명령어에 주소를 담을 수 없다. 명령어에서 A 레지스터에 담긴 주소를 이용하라고 지시하는 방법을 통해 메모리 주소에 직접 접근할 수 있다. 또한, 명령어 메모리에 직접 접근하는 것 또한 용이하게 한다. JUMP 명령어로 명령어 주소를 이동할 때, 명령어에 주소를 담지 않아도 (불가능하니깐) A에 담긴 주소를 이용해 바로 이동할 수 있다.
예제
1부터 100까지 정수를 더하는 프로그램을 Hack언어 코드로 표현해보자.
@S 또는 @3은 A레지스터에 S의 위치(주소)를 저장하거나 3을 저장하라는 의미이다.
@i // i 선언후 1로 초기화
M = 1 // M은 *A의 값
@SUM // Sum 선언 및 0으로 초기화
M=0
(Loop)
@i // i를 D에 저장하고
D=M
@100 // D는 i-100
D=D-A
@END // D가 0보다 크면 END로 JUMP
D;JGT
@i // i를 D에 저장
D=M
@SUM // SUM=SUM+D
M=D+M
@i // i++
M=M+1
@LOOP // LOOP로 이동
0;JMP
(END)
@END // END로 이동 (무한루프)
0;JMP
위의 코드에서 볼 수 있듯이, Hack코드에는 2가지 종류의 명령어가 있다.
A 명령어 Address Instruction
A 명령어는 A 레지스터에 15Bit 값 설정에 사용한다.
[ A = 0VVV VVVV VVVV VVVV ]
@Value / @Symbol의 형태로 사용한다.
-
@15 : A 레지스터에 0000 0000 0000 1111을 저장
-
@S : S 라는 기호가 참조하는 메모리 주소가 31이라면 A 레지스터에 0000 0000 0001 1111을 저장
A 명령어의 용도
-
상수 입력 (유일한 상수 입력 방법이다!) @3
-
C 명령어가 접근할 수 있도록 데이터주소를 미리 입력 @A
-
C 명령어로 점프할 수 있도록 명령어주소를 미리 입력 @LOOP
C 명령어 Compute Instruction
Hack에서 거의 모든 작업을 수행하는 명령어.
DEST = COMP ; JUMP 의 형태로 사용되며, 'DEST = '와 '; JUMP'는 필요없는 경우에 생략할 수 있다.
[ C = 1xxA CCCC CCDD DJJJ ]
A, C는 Comp필드로 ALU가 수행할 연산을, D는 Dest 필드로 연산결과를 저장할 공간을, J는 Jump 필드로 다음 실행할 명령어를 의미한다.
계산 필드 Computation Field
D, A, M 레지스터의 값들로 미리 세팅된 함수를 연산한다. (M 레지스터는 Mem[A]을 생각하면 된다!)
어떤 연산을 할 지는 7Bit의 Comp필드에서 정의된다. A Bit은 A 레지스터를 쓸지 M 레지스터를 쓸지를 의미하고, C Bit들은 연산의 종류를 의미한다. 이를 통해 최대 128개의 연산을 수행할 수 있다.
목적지 필드 Destination Field
Comp 부분에서의 연산결과는 3Bit의 Ddest가 가리키는 몇 개의 목적지에 저장된다. 3개의 Bit는 각각 순서대로 A레지스터, D레지스터, Memory[A]를 의미한다. 만약 Dest가 110이라면 A레지스터와 D레지스터에 결과를 저장한다는 의미.
점프 필드 Jump Field
Jump 부분에서는 다음에 수행할 명령어를 지시한다.
기본 설정은 다음 줄의 명령어를 실행(Jump하지 않음)하는 것이고 조건에 따라 Jump를 수행할지말지 결정하거나, 무조건 Jump를 수행할 수도 있다. 3개의 Bit은 각각 순서대로 Out < 0, Out = 0, Out > 0일때 Jump를 수행할 수 있음을 의미한다. 만약 Jump가 110이라면 Out <= 0일때 Jump가 수행된다.
C 명령어는 항상 어떤 연산이라도 해야한다.
따라서, 무조건점프에 대해서는 0;JMP (0인데 무조건 점프) 같은 형태의 명령어를 사용하야 한다. 0은 ALU가 0이라는 아무것도 하지 않는 연산을 하고 지나간다.
A 레지스터 사용시 충돌
A레지스터는 데이터 메모리 선택 (M) 과 명령어 메모리 선택 (Jump) 모두에 이용되기 때문에 주의 깊은 코딩이 필요하다. (데이터 메모리위치로 점프해버리면 프로그램이 고장난다!)
기호 Symbol
어셈블리는 상수나 기호를 이용해 메모리주소를 참조한다.
미리 정의된 기호 Predefined Symbol
RAM내의 특정 주소는 미리 정의되어 있어서, 해당 기호를 통해 참조 가능하다.
-
가상 레지스터 virtual register. R0-R15는 RAM[0-15]를 가리킨다.
-
미리 정의된 포인터 predefined pointer. SP LCL ARG THIS THAT기호는 각각 R0-R4를 가리킨다. (이름이 2개인 레지스터들이다) 이들은 가상머신에서 활용된다.
-
I/O 포인터. SCREEN, KBD는 RAM주소 0x4000, 0x6000을 참조하도록 정의되어있다. (이는 매핑되어있다고 표현한다)
레이블 기호 Label Symbol
주로 goto문 (JMP)의 목적지표시에 사용된다. 위 예제의 (LOOP), (END)등의 선언이 바로 레이블 기호이다.
실행할 명령어의 메모리주소를 (LableSymbol)로 선언하라는 의미이다. 선언문을 적으면 선언된 라인의 앞, 뒤 모두에서 사용이 가능하다.
변수 기호 Variable Symbol
위 두 경우를 제외하면 모든 사용자 정의 기호는 변수기호이다.
어셈블러는 RAM주소 0x0010부터 차례로 유일한 메모리주소를 할당해준다.
입력/출력 조작
스크린과 키보드는 Hack과 메모리 맵 memory map을 통해 통신한다.
[ 2진 값 -> 스크린으로 배정된 메모리 -> 화면 표시 ]
[ 키보드 입력 -> 키보드로 배정된 메모리 -> 2진 값 ]
스크린 Screen
Hack 컴퓨터의 스크린은 512x256의 흑백픽셀(각 픽셀을 1Bit로 표현)을 가진다. 이는 RAM주소 0x4000부터 8K짜리 메모리맵에 대응한다. 각 픽셀은 좌상단부터 행 단위로 메모리에 연결되며, 한 행은 16Bit Word 16개로 표현된다. ( 위에서 R번째 왼쪽에서 C번째 픽셀의 주소 = RAM[0x4000 + Rx32 + C/16]에 위치한 Word의 C%16번째 Bit )
RAM 내의 메모리맵에서 해당 픽셀이 대응하는 Bit을 읽고 스크린의 픽셀에 내용을 출력한다.
키보드 Keyboard
Hack 컴퓨터는 RAM주소 0x6000의 1 Word짜리 메모리맵을 통해 키보드와 통신한다. 키보드가 눌릴 때 마다 해당 ASCII CODE가 메모리에 기록되고, 그 외의 경우에 0이 기록된다. 추가로 화살표, esc 등의 키의 입력도 받아들인다. 0x6000의 메모리에 저장된 값을 통해 키보드에서 어떤 값이 들어왔는지 알 수 있다.
구문규칙과 파일 형식
2진 코드 파일 Binary Code File
16개의 0과 1로 구성된 한 줄이 명령어 하나. 이 명령어들이 모여 프로그램을 만든다.
파일의 n번째 line이 메모리의 n번째 명령어로 순서 그대로 저장된다.
어셈블리 언어 파일
각 line은 텍스트라인이며, 각 텍스트라인은 명령어나 기호선언_symbol declaration을 의미한다.
-
명령어 : A 명령어 / C 명령어
-
(symbol) : 레이블할당을 지시. 이에 대응하는 기계어는 따로 생성X.
상수 및 기호
상수는 음수가 아닌 10진법의 수.
맨 앞 기호가 숫자가 아니라면 특수문자를 이용한 문자열 선언도 가능하다.
주석
// 슬래시 2개를 이용하면 해당 line의 뒷 문자는 주석처리되어 어셈블러에 의해 번역되지 않는다.
공백
공백문자와 빈 line은 무시된다.
대소문자
모든 어셈블리의 연상기호_mnemonic은 대문자로 사용한다. 그 외의 사용자 정의 레이블, 변수는 대소문자를 구분하여 사용한다.
정리
현재 설계정인 Hack 시스템은 저급 시스템이기에 지원하지 않는 기능이 많다. 그러나 이는 비용을 감안하면 모두 SW를 통해 구현이 가능한 기능들이다.
기계어 분류에서 한 명령에 포함되는 메모리 주소의 수는 중요한 특성이다. 대부분의 기계어는 적어도 1개의 주소는 곧바로 사용이 가능하지만 ( 내가 배운 ARM에서도 ) Hack은 그렇지 않기 때문에 Half-기계어라고 할 수 있다.
어셈블러는 어셈블리 프로그램을 2진파일로 바꾸는 작업도 하지만 프로그램 내의 시스템 정의 기호, 사용자 정의 기호를 관리하고 이를 메모리 주소로 바꾸는 일도 한다. 이에 대해서는 6장에서 다시한번 배워보자.
'학부생 CS > Elements of Comp-Sys' 카테고리의 다른 글
6-1. Assembler - [밑바닥부터 만드는 컴퓨팅 시스템] (0) | 2019.11.27 |
---|---|
5. Computer Architecture - [밑바닥부터 만드는 컴퓨팅 시스템] (0) | 2019.11.24 |
3. Sequential Logic - [밑바닥부터 만드는 컴퓨팅 시스템] (0) | 2019.11.23 |
2. Boolean Arithmetic - [밑바닥부터 만드는 컴퓨팅 시스템] (0) | 2019.11.23 |
1. Boolean Logic - [밑바닥부터 만드는 컴퓨팅 시스템] (0) | 2019.11.23 |