핵 컴퓨터
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
핵 컴퓨터는 16비트 아키텍처를 기반으로 하는 간단한 컴퓨터로, 두 개의 메모리 장치와 중앙 처리 장치(CPU)로 구성된다. 읽기 전용 메모리(ROM)에는 실행할 프로그램 코드가 저장되고, 임의 접근 메모리(RAM)는 데이터 저장 및 입출력에 사용된다. CPU는 산술 논리 장치(ALU), 레지스터, 프로그램 카운터(PC) 등으로 구성되어 데이터 처리 및 프로그램 제어를 담당한다. 핵 컴퓨터는 A-명령어와 C-명령어로 구성된 기계어를 사용하며, 어셈블리 언어를 통해 프로그래밍할 수 있다. 핵 어셈블러는 어셈블리 언어 프로그램을 기계어로 변환하며, 예제 프로그램을 통해 기본적인 작동 방식을 이해할 수 있다.
2. 하드웨어 구조
핵(Hack) 컴퓨터 하드웨어는 블록 다이어그램에 나타난 것처럼 세 가지 기본 요소로 구성된다. 이는 두 개의 별도 16비트 메모리 장치와 중앙 처리 장치(CPU)이다. 데이터는 16비트 워드 단위로 컴퓨터 내에서 이동하고 처리되므로, 핵 컴퓨터는 16비트 아키텍처로 분류된다.
핵심 구성 요소는 다음과 같다.
이 세 장치는 병렬 버스를 통해 서로 연결되어 있다. 특히, 명령어 메모리(ROM)와 데이터 메모리(RAM)에 접근하는 버스 시스템(주소 버스는 15비트, 데이터 및 명령어 버스는 16비트)은 서로 완전히 독립적이다. 이러한 구조는 메모리 장치와 CPU 간의 버스 통신 방식이 하버드 아키텍처 모델을 따른다는 것을 의미한다. 모든 메모리 접근은 워드 어드레싱 방식, 즉 16비트 워드 단위로만 가능하다.
2. 1. 읽기 전용 메모리 (ROM)
핵 컴퓨터의 읽기 전용 메모리(ROM)는 실행될 프로그램의 명령어를 저장하는 공간이다. ROM은 개별적으로 주소 지정이 가능한 16비트 메모리 레지스터들이 순차적으로 배열된 구조를 가진다. 메모리 주소는 0부터 시작한다. ROM은 순차적으로 작동하는 장치이기 때문에 시스템 클럭 신호가 필요하며, 이는 시뮬레이션이나 컴퓨터 에뮬레이터를 통해 제공될 수 있다.
ROM의 주소 버스는 15비트 폭을 가지므로, 최대 32,768개의 16비트 워드(명령어)를 저장할 수 있다. 현재 실행해야 할 명령어의 주소는 중앙 처리 장치(CPU) 내의 프로그램 카운터(PC) 레지스터에 의해 지정된다. 특정 클럭 신호 주기(사이클)에 프로그램 카운터가 가리키는 주소의 ROM 내용은 다음 클럭 주기 시작 시에 현재 실행할 명령어로 사용된다. 별도의 명령어 레지스터는 존재하지 않으며, 명령어는 매 클럭 주기마다 현재 활성화된 ROM 레지스터에서 직접 해독된다.
2. 2. 임의 접근 메모리 (RAM)
임의 접근 메모리(램, RAM)는 실행 중인 프로그램의 데이터를 저장하고 컴퓨터의 메모리 맵 I/O 메커니즘을 위한 저장 영역과 서비스를 제공한다.[1] RAM은 개별적으로 주소를 지정할 수 있는 순차적인 16비트 메모리 레지스터의 연속적인 선형 배열로 구성되지만, 기능적으로는 주소 범위를 기준으로 세 개의 세그먼트로 나뉜다. 모든 메모리는 워드 어드레싱만 가능하다.[1]
핵 컴퓨터의 RAM 주소 공간은 다음과 같이 구성된다.
선택된 RAM 메모리 레지스터의 상태 변경은 시스템 클럭 신호에 의해 동기화된다.[1]
2. 3. 중앙 처리 장치 (CPU)

핵 컴퓨터의 중앙 처리 장치(CPU)는 그림과 같이 내부 구조를 가진 통합 논리 장치이다. 상업적으로 사용 가능한 CPU에서 발견되는 여러 기능을 간략화하여 제공한다.
CPU의 가장 핵심적인 부분은 컴퓨터의 계산 기능을 수행하는 산술 논리 장치(ALU)이다. ALU는 두 개의 16비트 입력값을 받아 하나의 16비트 결과값을 출력하는 조합 논리 회로이다. 어떤 계산을 수행할지는 6개의 제어 비트 입력으로 결정된다. ALU는 계산 결과가 0인지(zr 플래그), 음수인지(ng 플래그)를 나타내는 두 개의 단일 비트 상태 플래그도 출력한다.
CPU 내부에는 D와 A라는 두 개의 16비트 레지스터(메모리 레지스터)가 있다.
- D 레지스터 (데이터 레지스터): 범용 레지스터로 사용되며, 그 값은 항상 ALU의 한쪽 입력(x 피연산자)으로 사용된다. (일부 명령어에서는 이 값이 무시될 수 있다.)
- A 레지스터 (주소 레지스터): 명령어에 따라 그 값을 ALU의 다른 쪽 입력(y 피연산자)으로 제공할 수 있다. 또한, 데이터 메모리(RAM)의 주소를 지정하거나 분기 명령어의 목적지 주소를 저장하는 데 사용된다.
- M 레지스터: A 레지스터와 밀접하게 연관된 "의사 레지스터"이다. M은 실제로 하드웨어로 구현된 레지스터가 아니라, A 레지스터에 저장된 주소가 가리키는 RAM의 값을 의미한다. 즉, CPU는 A 레지스터를 통해 M 레지스터(RAM의 특정 위치)에 접근한다.
CPU의 또 다른 중요한 요소는 프로그램 카운터(PC) 레지스터이다. PC는 16비트 이진 카운터로, 하위 15비트는 다음에 실행할 명령어의 명령 메모리 주소를 가리킨다. 일반적으로 PC는 매 클럭 사이클마다 1씩 증가하여 다음 명령어를 순차적으로 실행하도록 한다. 하지만 분기(jump) 명령어가 실행되면 PC에 비순차적인 값이 설정되어 프로그램 실행 흐름을 변경할 수 있다. PC에는 리셋(reset) 입력 단자가 있어, 이 신호가 활성화되면 PC 값이 0으로 초기화된다.
많은 실제 CPU 설계와 달리, 핵 컴퓨터 CPU는 외부 또는 내부 인터럽트를 처리하거나 함수 호출을 지원하기 위한 별도의 하드웨어 메커니즘을 가지고 있지 않다.
2. 4. 외부 입출력 (I/O)
핵 컴퓨터는 메모리 맵 방식의 입출력(I/O)을 사용한다.화면 출력가상 화면은 256 x 512 픽셀의 해상도를 가지며, 비트맵 방식의 흑백 출력을 지원한다. 화면에 원하는 내용을 표시하려면, 데이터 메모리의 특정 영역, 즉 주소 16384 (0x4000)부터 24575 (0x5FFF)까지에 해당하는 화면 RAM 영역에 비트맵 데이터를 직접 기록해야 한다.
이 메모리 영역의 데이터 워드는 비트의 선형 배열로 취급되며, 각 비트의 값(0 또는 1)은 화면 상의 개별 픽셀의 상태(흰색 또는 검은색)를 결정한다. 구체적으로, 화면 RAM 세그먼트의 첫 번째 메모리 주소(16384)에 저장된 워드의 최하위 비트(LSB)는 화면의 가장 왼쪽 위 모서리 픽셀에 해당한다. 비트 값이 0이면 해당 픽셀은 흰색으로, 1이면 검은색으로 설정된다. 첫 번째 워드의 다음으로 중요한 비트는 바로 오른쪽 옆 픽셀을 제어하며, 이러한 방식으로 하나의 워드는 16개의 픽셀 상태를 나타낸다.
화면의 첫 번째 가로줄(512 픽셀)은 화면 메모리의 처음 32개 워드(32 워드 * 16 비트/워드 = 512 비트)에 의해 표현된다. 첫 번째 줄의 매핑이 완료되면, 그 다음 32개 워드가 두 번째 가로줄의 픽셀들을 동일한 방식으로 매핑하며, 이 과정이 화면 전체(256줄)에 걸쳐 반복된다. 컴퓨터 외부의 로직(하드웨어나 에뮬레이터)은 이 화면 RAM 영역을 주기적으로 읽어 가상 화면의 내용을 업데이트한다.
키보드 입력키보드 입력은 CPU 에뮬레이터 프로그램을 실행하는 호스트 컴퓨터에 연결된 키보드를 통해 이루어진다. 사용자가 키를 누르면, 해당 키에 대응하는 16비트 스캔 코드가 RAM 주소 24576 (0x6000)에 위치한 키보드 메모리 맵 레지스터에 저장된다. 만약 아무 키도 눌리지 않은 상태라면, 이 레지스터의 값은 0이 된다.
에뮬레이터는 사용자가 키보드 입력을 활성화하거나 비활성화할 수 있는 토글 기능을 제공한다. 키보드 스캔 코드의 인코딩 방식은 인쇄 가능한 문자에 대해서는 ASCII 코드를 따른다. Shift 키의 조합 효과도 일반적으로 적용되며, 표준 PC 키보드에서 흔히 볼 수 있는 방향 제어 키(←, ↑, ↓, →)나 Fn 키 등에 대한 코드도 정의되어 있다.
2. 5. 작동 주기
중앙 처리 장치(CPU)와 메모리 유닛의 단계별 작동은 클럭 신호에 의해 제어된다. 클럭 사이클이 시작되면, 프로그램 카운터(PC)의 현재 값이 가리키는 ROM 주소에 저장된 명령어를 가져와 해독한다. 필요한 경우, 명령어에 명시된 산술 논리 장치(ALU)의 피연산자를 정렬한다.이후 ALU는 지정된 연산을 수행하고, 결과에 따라 상태 플래그를 설정한다. 계산 결과는 명령어가 지정하는 위치에 저장된다. 마지막으로, 프로그램 카운터는 다음에 실행해야 할 명령어의 주소로 업데이트된다. 현재 명령어가 분기 명령이 아니라면 PC 값은 1 증가한다. 분기 명령이라면 PC에는 A 레지스터에 저장된 다음 명령어의 주소가 로드된다. 이 과정은 업데이트된 PC 값을 사용하여 다음 클럭 사이클에서 반복된다.
핵 컴퓨터는 명령어 메모리와 데이터 메모리 버스가 분리된 하버드 아키텍처를 사용한다. 이 구조 덕분에 현재 명령어를 실행하는 동시에 다음 명령어를 메모리에서 가져오는 작업(fetch)을 하나의 클럭 사이클 내에서 동시에 처리할 수 있다. 즉, 각 명령어는 한 클럭 사이클 안에 완전히 실행된다. 클럭 속도는 하드웨어 시뮬레이터나 CPU 에뮬레이터에서 조절할 수 있으며, 사용자는 프로그램을 한 단계씩 실행(single-step)할 수도 있다.
ROM에 로드된 프로그램의 실행은 CPU의 리셋(reset) 비트로 제어된다. 리셋 비트 값이 0이면 앞서 설명한 작동 사이클에 따라 프로그램이 실행된다. 리셋 비트를 1로 설정하면 PC가 0으로 초기화된다. 이후 리셋 비트를 다시 0으로 설정하면 프로그램의 첫 번째 명령어부터 실행이 시작된다. 단, 리셋 시 RAM에는 이전 작업의 값들이 그대로 남아있을 수 있다.
핵 컴퓨터는 하드웨어나 기계어 수준에서 인터럽트를 지원하지 않는다.
2. 6. 데이터 타입
RAM에는 16비트 값을 저장할 수 있다. RAM에 저장된 값의 데이터 유형은 값이 저장된 위치나 프로그램 내 사용 방식에 따라 추론된다. 기본적으로 하드웨어에서 지원하는 데이터 타입은 2의 보수 형식을 사용하는 16비트 부호 있는 정수이다. 따라서 부호 있는 정수는 -32768부터 32767까지의 범위를 가진다. RAM 값의 하위 15비트는 ROM 또는 RAM의 주소를 나타내는 포인터로 사용될 수도 있다.화면 입출력(I/O)용으로 할당된 RAM 메모리 레지스터 값은 화면이 켜져 있을 때 컴퓨터의 독립적인 I/O 서브시스템에 의해 256행 x 512열 가상 화면의 16 픽셀 맵으로 해석된다. 키보드 메모리의 코드 값은 프로그래밍 방식으로 읽어 프로그램에서 사용할 수 있도록 해석될 수 있다.
부동 소수점 형식에 대한 하드웨어 지원은 없다.
3. 명령어 집합 구조 (ISA) 및 기계어
핵 컴퓨터의 명령어 집합 구조(ISA)와 여기서 파생된 기계어는 다른 많은 아키텍처에 비해 간소한 편이다. ALU가 연산을 지정하는 데 사용하는 6비트는 이론적으로 64개의 서로 다른 명령어를 표현할 수 있지만, 핵 컴퓨터의 ISA에는 공식적으로 18개의 명령어만 구현되어 있다. 또한 핵 컴퓨터 하드웨어는 정수 곱셈이나 나눗셈, 함수 호출 기능을 직접 지원하지 않기 때문에, 이러한 연산에 해당하는 기계어 명령어는 ISA에 포함되지 않는다.
핵 기계어 명령어는 16비트 이진수로 인코딩되며, A-명령어와 C-명령어의 두 가지 기본 유형으로 나뉜다.
3. 1. A-명령어
핵 기계어는 16개의 이진 숫자로 각각 인코딩된 두 종류의 명령어만 있다.[1] 이 중 가장 중요한 비트(MSB)가 "0"인 명령어를 A-명령어 또는 주소 명령어라고 부른다.[1] A-명령어는 다음과 같이 비트 필드로 인코딩된다.'''0'''b14b13b12b11b10b9b8b7b6b5b4b3b2b1b0
- '''0''': A-명령어의 가장 중요한 비트는 항상 "0"이다.
- b14 - b0: 나머지 15개 비트는 0에서 32767까지의 십진수 범위 내에서 음수가 아닌 정수의 이진 표현을 제공한다.
이 명령어가 실행되면, 나머지 15비트 값은 0으로 확장되어 CPU의 A-레지스터에 로드된다.[1] 부작용으로, A-레지스터에 로드된 값으로 표시되는 주소를 가진 RAM 레지스터가 다음 클럭 사이클에서 읽기 또는 쓰기 작업을 위해 활성화된다.[1] 따라서 A-명령어는 특정 메모리 주소를 선택하거나, 상수 값을 A-레지스터에 직접 로드하는 데 사용된다.
3. 2. C-명령어
핵 컴퓨터 기계어의 두 종류 명령어 중 하나인 C-명령어(계산 명령어)는 프로그래밍의 핵심적인 역할을 수행한다. 이 명령어는 ALU 연산, 연산 결과의 레지스터 또는 메모리 저장, 그리고 계산 결과에 따른 조건부 프로그램 분기(점프) 기능을 담당한다.C-명령어는 16비트 이진수로 표현되며, 항상 최상위 비트(MSB)가 '1'이다. 나머지 15비트는 여러 필드로 나뉘어 명령어의 구체적인 동작을 정의한다. C-명령어의 일반적인 어셈블리어 형식은 다음과 같다.
`''dest''=''comp'';''jump''`
여기서 `dest`는 계산 결과를 저장할 목적지를, `comp`는 수행할 계산을, `jump`는 분기 조건을 의미한다. `dest`나 `jump` 부분은 필요에 따라 생략될 수 있어, `dest=comp` 또는 `comp;jump` 형태도 가능하다.
C-명령어의 16비트는 다음과 같은 구조로 인코딩된다.
111a c1c2c3c4c5c6d1d2d3j1j2j3
각 비트 필드의 의미는 다음과 같다.
- 111: 첫 번째 비트 '1'은 이 명령어가 C-명령어임을 나타낸다. 두 번째와 세 번째 비트 '1'은 CPU에서 무시되지만 관례적으로 '1'로 설정된다.
- a: ALU 연산에 사용될 두 번째 피연산자(y)의 출처를 지정한다. `a=0`이면 A 레지스터 값을, `a=1`이면 메모리 M(RAM[A]) 값을 사용한다.
- c1~c6: ALU가 수행할 구체적인 계산 내용을 지정하는 6개의 제어 비트이다. 이 비트들은 어떤 연산(예: 덧셈, 논리곱, 값 증가 등)을 수행할지 결정한다. (자세한 내용은 계산 (comp) 필드 섹션 참조)
- d1~d3: ALU의 연산 결과를 어디에 저장할지 지정하는 3개의 비트이다. A 레지스터, D 레지스터, 메모리 M 중 하나 또는 여러 곳에 동시에 저장할 수 있다. (자세한 내용은 저장 (dest) 필드 섹션 참조)
- j1~j3: 명령어 실행 후 다음으로 실행할 명령어의 주소를 결정하는 3개의 비트이다. ALU 연산 결과(예: 결과가 0보다 큰지, 0과 같은지 등)에 따라 조건부로 분기하거나, 무조건 분기하거나, 분기하지 않을 수 있다. (자세한 내용은 분기 (jump) 필드 섹션 참조)
3. 2. 1. 계산 (comp) 필드
C-명령어에서 '''comp''' 필드는 ALU가 수행할 연산을 지정하는 7개의 비트(`a`, c1~c6)로 구성된다. C-명령어의 기본 형식은 `dest=comp;jump` 이며, `dest` (결과 저장 위치)나 `jump` (분기 조건) 필드는 생략될 수 있다.C-명령어의 비트 필드 인코딩은 다음과 같다.
'''111'''a c1c2c3c4 c5c6d1d2 d3j1j2j3
- `a` 비트: ALU 연산에 사용될 두 번째 피연산자(y)를 선택한다.
- `a=0` 이면 A 레지스터의 값을 사용한다.
- `a=1` 이면 메모리(RAM)의 `A` 레지스터 주소에 해당하는 값, 즉 M (RAM[A])을 사용한다.
- c1 ~ c6 비트: ALU가 수행할 구체적인 연산 종류를 지정한다. 이 비트들은 ALU 내부의 제어 신호로 사용되어, 피연산자를 선택하고 어떤 연산(덧셈, 비트 AND, 2의 보수 등)을 수행할지 결정한다.
아래 표는 `a` 비트와 `c` 비트 조합에 따른 ALU 출력 함수 `f(x,y)`와 해당 연산을 나타내는 어셈블리어 니모닉을 보여준다. 표에서 '''D'''는 D 레지스터의 현재 값, '''A'''는 A 레지스터의 현재 값, '''M'''은 A 레지스터가 가리키는 메모리 주소(RAM[A])의 현재 값을 의미한다.
a | c1 | c2 | c3 | c4 | c5 | c6 | ALU 출력: f(x,y) | 니모닉 |
---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 (상수 0) | 0 |
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 (상수 1) | 1 |
0 | 1 | 1 | 1 | 0 | 1 | 0 | ||
-1 | ||||||||
0 | 0 | 0 | 1 | 1 | 0 | 0 | D 레지스터 값 | D |
0 | 1 | 1 | 0 | 0 | 0 | 0 | A 레지스터 값 | A |
1 | 1 | 1 | 0 | 0 | 0 | 0 | M (RAM[A]) 값 | M |
0 | 0 | 0 | 1 | 1 | 0 | 1 | D의 비트별 NOT 연산 | !D |
0 | 1 | 1 | 0 | 0 | 0 | 1 | A의 비트별 NOT 연산 | !A |
1 | 1 | 1 | 0 | 0 | 0 | 1 | M의 비트별 NOT 연산 | !M |
0 | 0 | 0 | 1 | 1 | 1 | 1 | D의 2의 보수 음수 | -D |
0 | 1 | 1 | 0 | 0 | 1 | 1 | A의 2의 보수 음수 | -A |
1 | 1 | 1 | 0 | 0 | 1 | 1 | M의 2의 보수 음수 | -M |
0 | 0 | 1 | 1 | 1 | 1 | 1 | D + 1 (D 증가) | D+1 |
0 | 1 | 1 | 0 | 1 | 1 | 1 | A + 1 (A 증가) | A+1 |
1 | 1 | 1 | 0 | 1 | 1 | 1 | M + 1 (M 증가) | M+1 |
0 | 0 | 0 | 1 | 1 | 1 | 0 | D - 1 (D 감소) | D-1 |
0 | 1 | 1 | 0 | 0 | 1 | 0 | A - 1 (A 감소) | A-1 |
1 | 1 | 1 | 0 | 0 | 1 | 0 | M - 1 (M 감소) | M-1 |
0 | 0 | 0 | 0 | 0 | 1 | 0 | D + A | D+A |
1 | 0 | 0 | 0 | 0 | 1 | 0 | D + M | D+M |
0 | 0 | 1 | 0 | 0 | 1 | 1 | D - A | D-A |
1 | 0 | 1 | 0 | 0 | 1 | 1 | D - M | D-M |
0 | 0 | 0 | 0 | 1 | 1 | 1 | A - D | A-D |
1 | 0 | 0 | 0 | 1 | 1 | 1 | M - D | M-D |
0 | 0 | 0 | 0 | 0 | 0 | 0 | D와 A의 비트별 AND 연산 | D&A |
1 | 0 | 0 | 0 | 0 | 0 | 0 | D와 M의 비트별 AND 연산 | D&M |
0 | 0 | 1 | 0 | 1 | 0 | 1 | D와 A의 비트별 OR 연산 | D>A |
1 | 0 | 1 | 0 | 1 | 0 | 1 | D와 M의 비트별 OR 연산 | D>M |
3. 2. 2. 저장 (dest) 필드
C-명령어에서 '''dest 필드'''는 ALU의 연산 결과를 어느 레지스터에 저장할지 결정하는 역할을 한다. C-명령어 형식 `dest=comp;jump`에서 `dest` 부분에 해당하며, 생략될 수도 있다.[1]이 필드는 명령어의 3개 비트(`d1`, `d2`, `d3`)로 구성된다. 각 비트는 특정 저장 위치를 제어하며, 이 비트들의 조합에 따라 A 레지스터, D 레지스터, 또는 메모리 M (RAM[A] 위치) 중 하나 또는 여러 곳에 동시에 연산 결과를 저장할 수 있다.[1]
아래 표는 `d1`, `d2`, `d3` 비트 값에 따른 저장 위치와 해당 어셈블리 언어 니모닉을 보여준다.[1]
d1 | d2 | d3 | ALU 출력 저장 위치 | 니모닉 |
---|---|---|---|---|
0 | 0 | 0 | 출력 저장 안 함 | none |
0 | 0 | 1 | M (RAM[A]) | M |
0 | 1 | 0 | D 레지스터 | D |
0 | 1 | 1 | M과 D | MD |
1 | 0 | 0 | A 레지스터 | A |
1 | 0 | 1 | A와 M | AM |
1 | 1 | 0 | A와 D | AD |
1 | 1 | 1 | A와 M과 D | AMD |
예를 들어, `d1=1`, `d2=1`, `d3=0` 이면 ALU의 출력 값은 A 레지스터와 D 레지스터에 동시에 저장되며, 어셈블리 니모닉으로는 `AD`로 표현된다.[1] 만약 세 비트가 모두 0이면 (`d1=0`, `d2=0`, `d3=0`), ALU의 연산 결과는 어디에도 저장되지 않는다.[1]
3. 2. 3. 분기 (jump) 필드
C-명령어 형식 `dest=comp;jump`에서 `jump` 부분은 프로그램의 흐름을 제어하는 분기(jump)를 담당한다. 이 분기는 ALU의 가장 최근 계산 결과에 따라 조건부로 실행되거나, 무조건 실행될 수 있다.C-명령어의 16비트 중 마지막 3개 비트인 `j1`, `j2`, `j3`가 어떤 조건에서 분기할지를 결정한다. 각 비트 조합에 따른 분기 조건과 해당 어셈블리어 니모닉은 다음 표와 같다.
j1 | j2 | j3 | 분기 조건 | 니모닉 |
---|---|---|---|---|
0 | 0 | 0 | 분기 없음 | none |
0 | 0 | 1 | 출력이 0보다 큼 | JGT |
0 | 1 | 0 | 출력이 0과 같음 | JEQ |
0 | 1 | 1 | 출력이 0보다 크거나 같음 | JGE |
1 | 0 | 0 | 출력이 0보다 작음 | JLT |
1 | 0 | 1 | 출력이 0과 같지 않음 | JNE |
1 | 1 | 0 | 출력이 0보다 작거나 같음 | JLE |
1 | 1 | 1 | 무조건 분기 | JMP |
4. 어셈블리 언어
핵 컴퓨터는 핵 컴퓨터 ISA를 구현하는 하드웨어 플랫폼용 프로그램을 만들기 위한 텍스트 기반 어셈블리 언어를 제공한다. 핵 어셈블리 언어 프로그램은 일반적으로 `.asm` 파일 확장자를 가진 텍스트 파일에 저장되며, 소스 코드는 대소문자를 구분한다.
각 텍스트 줄은 다음 요소 중 하나를 포함할 수 있다.
어셈블러는 빈 줄과 주석을 처리 과정에서 무시한다. 레이블 선언, A-명령어, C-명령어는 구문 내부에 공백 문자를 포함할 수 없으나, 줄의 시작이나 끝에 오는 공백은 허용되며 무시된다. 각 요소의 구체적인 구문과 기능은 하위 섹션에서 자세히 설명한다.
4. 1. 주석
핵 어셈블리 언어에서 `//` 기호로 시작하는 텍스트는 주석으로 취급된다. 주석은 소스 코드 한 줄 전체를 차지할 수도 있고, A-명령어, C-명령어 또는 레이블 선언과 같은 다른 프로그램 소스 줄의 끝에 추가될 수도 있다. 어셈블러는 주석 시작 기호(`//`) 뒤에 오는 모든 내용을 완전히 무시한다. 따라서 주석은 최종적으로 생성되는 기계 코드에는 아무런 영향을 미치지 않는다. 주로 코드의 이해를 돕기 위한 설명을 추가하거나 특정 코드 부분을 일시적으로 비활성화하는 데 사용된다.4. 2. 기호 및 숫자 상수
핵 어셈블리 언어에서는 다양한 목적으로 영숫자 기호를 사용할 수 있다. 기호는 영문자(대소문자 구분), 숫자, 그리고 밑줄(`_`), 마침표(`.`), 달러 기호(`$`), 콜론(`:`) 문자를 포함할 수 있다. 단, 기호는 숫자로 시작할 수 없다. 이러한 사용자 정의 기호는 주로 변수 이름이나 레이블을 만드는 데 사용된다.핵 어셈블러는 미리 정의된 몇 가지 기호도 인식한다.
기호 | 값/용도 | 설명 |
---|---|---|
R0 ~ R15 | 0 ~ 15 | 범용 레지스터를 나타내는 데이터 메모리 주소 |
SCREEN | 16384 | 메모리 맵 방식의 가상 화면 출력 시작 주소 |
KBD | 24756 | 키보드 입력 시작 주소 |
SP | 운영 체제 스택용 | 스택 포인터 |
LCL | 운영 체제 스택용 | 지역 변수 세그먼트의 베이스 주소 |
ARG | 운영 체제 스택용 | 인수 세그먼트의 베이스 주소 |
THIS | 운영 체제 스택용 | 현재 객체의 this 세그먼트 베이스 주소 |
THAT | 운영 체제 스택용 | 현재 객체의 that 세그먼트 베이스 주소 |
또한, 0부터 32,767까지의 범위를 가지는 음수가 아닌 십진수 상수를 사용할 수 있다. 숫자 상수는 십진수(0-9) 문자열로 표현해야 한다. 음수 표현을 위한 마이너스 기호(-) 사용이나 이진, 8진 표현은 지원되지 않는다.
4. 3. 변수
핵 컴퓨터의 어셈블리 언어 프로그램에서는 사용자가 직접 정의한 기호(symbol)를 변수로 사용할 수 있다. 이 변수는 실제로는 이름이 붙은 RAM 레지스터, 즉 메모리 주소를 가리킨다. 어셈블러는 프로그램을 어셈블하는 시점에 이 기호를 특정 RAM 주소와 연결(바인딩)하며, 따라서 소스 코드에서 변수 기호는 해당 메모리 주소 자체로 취급된다.변수는 별도의 선언 과정 없이, A-명령어에서 처음 참조될 때 암시적으로 정의된다. 어셈블러는 소스 코드를 처리하면서 변수 기호에 RAM 주소 16번부터 시작하는 고유한 양의 정수 값을 할당한다. 주소는 변수가 소스 코드에 처음 등장하는 순서대로 차례차례 할당된다. 관례적으로 변수를 나타내는 사용자 정의 기호는 모두 소문자로 작성하는 것이 일반적이다.
4. 4. 레이블
레이블은 왼쪽 괄호 '(' 와 오른쪽 괄호 ')' 로 둘러싸인 기호이다. 소스 프로그램에서 레이블은 별도의 줄에 '(레이블_이름)' 과 같은 형식으로 정의된다. 어셈블러는 이렇게 정의된 레이블을 소스 코드상 바로 다음에 오는 명령어가 위치할 메모리 주소와 연결시킨다.레이블은 프로그램 전체에서 단 한 번만 정의해야 하지만, 정의된 위치와 관계없이 프로그램 내 어디에서든 여러 번 참조하여 사용할 수 있다. 심지어 레이블이 정의된 줄보다 앞서는 코드에서도 해당 레이블을 참조하는 것이 가능하다. 관례적으로 레이블 이름은 모두 대문자로 표기한다.
레이블의 주된 용도는 분기 명령어(C-명령어의 한 종류)가 실행 흐름을 이동시킬 목적지 주소를 식별하는 것이다.
4. 5. A-명령어 (어셈블리)
A-명령어는 핵 컴퓨터 어셈블리어의 한 종류로, `@value` 형식으로 작성된다. 여기서 `value`는 0부터 32767 사이의 10진수 상수, 레이블, 또는 변수(미리 정의되거나 사용자가 정의한 것)가 될 수 있다. A-명령어가 실행되면, `value`에 해당하는 값을 15비트 이진수로 변환하여 A 레지스터와 M 의사 레지스터에 저장한다. 이때 A 레지스터에는 이 15비트 값이 왼쪽 비트에 0이 추가되어 16비트로 확장되어 저장된다.A-명령어는 주로 세 가지 목적으로 사용된다.
- 첫째, 프로그램 내에서 음수가 아닌 숫자 값, 즉 프로그램 상수를 정의하는 유일한 방법이다.
- 둘째, RAM 메모리 주소를 지정하는 데 사용된다. 이는 M 의사 레지스터 메커니즘을 통해 이루어지며, 이후 C-명령어에서 해당 메모리 위치를 참조할 때 사용된다.
- 셋째, 분기를 위한 주소를 설정하는 데 사용된다. 분기를 수행하는 C-명령어는 분기할 목적지 주소로 A 레지스터의 현재 값을 사용하는데, A-명령어는 주로 레이블을 참조하여 분기 명령 전에 해당 목적지 주소를 A 레지스터에 설정하는 역할을 한다.
4. 6. C-명령어 (어셈블리)
C-명령어는 핵 컴퓨터의 ALU(Arithmetic Logic Unit) 계산 엔진과 프로그램 흐름 제어 기능을 담당한다. 명령어 구문은 `dest=comp;jump` 형식으로 정의되며, `comp`, `dest`, `jump` 세 필드로 구성된다. 여기서 '='와 ';' 문자는 필드를 구분하는 역할을 한다.- `comp` 필드는 모든 C-명령어에서 필수이다. 이 필드는 수행할 계산을 지정하며, 문서화된 28개의 니모닉 코드 중 하나를 사용해야 한다. 이 코드들은 대문자로 표기하며 내부 공백은 허용되지 않는다. ALU 제어 비트는 이론상 64개의 계산 기능을 지정할 수 있지만, 공식적으로 인식되고 문서화된 것은 18개이다.
- `dest` 필드는 `comp` 필드에서 계산된 결과 값을 저장할 위치(레지스터 또는 메모리)를 지정한다. '=' 기호와 함께 사용되며, 여러 위치를 동시에 지정할 수도 있다. 만약 계산 결과를 저장할 필요가 없다면 `dest` 필드와 '=' 기호는 생략한다. 허용되는 저장 위치 조합은 정해진 니모닉 코드로 지정된다.
- `jump` 필드는 다음에 실행할 명령어의 ROM 주소를 지정하여 프로그램의 흐름을 제어한다. ';' 기호와 함께 사용된다. 만약 `jump` 필드를 생략하면 현재 명령어 다음의 명령어를 순차적으로 실행한다. 분기 조건이 충족되면 A 레지스터의 현재 값이 다음 명령어의 주소가 된다. 분기 조건이 충족되지 않으면 다음 명령어를 순서대로 실행한다. 계산 결과 값에 따라 6가지 비교 조건을 사용한 조건부 분기가 가능하며, 무조건 분기(`0;JMP`)도 지원된다. 무조건 분기 시에는 `comp` 필드 값이 필요 없더라도 반드시 `0`과 같은 값을 지정해야 한다. 지원되는 분기 조건들은 정해져 있다.
C-명령어 작성 시에는 내부 공백을 사용하지 않아야 한다. `dest` 필드가 없으면 `comp;jump` 형식으로, `jump` 필드가 없으면 `dest=comp` 형식으로 작성한다. 두 필드 모두 없으면 `comp`만 작성한다.
5. 어셈블러
핵 컴퓨터용 어셈블러는 어셈블리 언어로 작성된 프로그램(`.asm` 파일)을 기계어 코드(`.hack` 파일)로 변환하는 도구이다. 자유롭게 이용 가능한 소프트웨어 중 하나로 제공된다.
어셈블러는 Hack 어셈블리 언어 소스 파일(.asm)을 입력으로 받아 Hack 기계어 출력 파일(.hack)을 생성한다. 이 출력 파일 역시 텍스트 파일 형태이며, 각 줄은 소스 파일의 실행 가능한 한 줄에 해당하는 16자리 이진 숫자로 이루어진 문자열이다. 이는 핵 컴퓨터의 명령어 집합 구조(ISA) 및 기계어 사양에 따라 인코딩된 결과이다.
이렇게 생성된 .hack 파일은 에뮬레이터 사용자 인터페이스를 통해 핵 컴퓨터 에뮬레이터에 로드하여 실행할 수 있다.
6. 예제 어셈블리 언어 프로그램
다음은 핵 컴퓨터의 어셈블리 언어로 작성된 예제 프로그램으로, 1부터 100까지의 정수를 더하여 그 결과를 사용자 정의 변수 'sum'에 저장하는 과정을 보여준다.[1] 이 프로그램은 1부터 100까지 반복하며 각 정수를 'sum' 변수에 더하는 'while' 루프 구조를 구현한다. 사용자 정의 변수 'cnt'는 현재 정수 값을 저장하는 데 사용된다. 이 예제는 메모리 맵 I/O를 제외한 핵 컴퓨터 어셈블리 언어의 주요 기능을 보여주며, 아래 C 언어 코드를 핵 어셈블리로 변환한 것이다.[1]
```c
// 1부터 100까지 더하기
int cnt = 1;
int sum = 0;
while (cnt <= 100) {
sum += cnt;
cnt++;
}
```
아래 표는 핵 어셈블리 언어 소스 파일의 내용(두 번째 열, 굵은 글씨)과 각 줄에 대한 설명, 명령어 유형, 할당된 ROM 주소, 그리고 어셈블러가 생성한 기계어 코드(마지막 열)를 보여준다. 줄 번호는 설명을 위한 것이며 실제 코드에는 포함되지 않는다. 전체 줄 주석, 빈 줄, 레이블 정의는 기계어 코드를 생성하지 않으며, 각 명령어 줄 끝의 주석도 어셈블러에 의해 무시된다.[1] 마지막 열의 기계 코드는 16비트 이진 정수가 아닌 16개의 이진 문자 텍스트 문자열로 표시된다.[1]
줄 번호 | 핵 어셈블리 언어 프로그램 | 동작 설명 | 명령어 유형 | ROM 주소 | 핵 기계 코드 |
---|---|---|---|---|---|
01 | // 1부터 100까지의 연속 정수 더하기 | 프로그램 동작 설명 주석 | 전체 줄 주석 | ---- | 코드 생성 안 함 |
02 | // sum = 1 + 2 + 3 + ... + 99 + 100 | 주석은 어셈블러에서 무시됨 | 전체 줄 주석 | ---- | 코드 생성 안 함 |
03 | 빈 소스 줄은 어셈블러에서 무시됨 | 빈 줄 | ---- | 코드 생성 안 함 | |
04 | @cnt // 루프 카운터 선언 | 변수 기호 "cnt"가 RAM 주소 16에 바인딩됨 | A-명령어 | 00 | 0000000000010000 |
05 | M=1 // 루프 카운터를 1로 초기화 | RAM[16] ← 1 | C-명령어 | 01 | 1110111111001000 |
06 | @sum // 합계 누산기 선언 | 변수 기호 "sum"이 RAM 주소 17에 바인딩됨 | A-명령어 | 02 | 0000000000010001 |
07 | M=0 // 합계를 0으로 초기화 | RAM[17] ← 0 | C-명령어 | 03 | 1110101010001000 |
08 | (LOOP) // while 루프 시작 | 레이블 기호 "LOOP"가 ROM 주소 04에 바인딩됨 | 레이블 선언 | ---- | 코드 생성 안 함 |
09 | @cnt // cnt의 주소 참조 | A 레지스터 ← 16 | A-명령어 | 04 | 0000000000010000 |
10 | D=M // 현재 cnt 값을 D 레지스터로 이동 | D 레지스터 ← RAM[16] | C-명령어 | 05 | 1111110000010000 |
11 | @100 // 루프 제한(100)을 A 레지스터에 로드 | A 레지스터 ← 100 | A-명령어 | 06 | 0000000001100100 |
12 | D=D-A // 루프 테스트 계산 수행 (cnt - 100) | D 레지스터 ← D - A | C-명령어 | 07 | 1110010011010000 |
13 | @END // 분기 대상 목적지(END 레이블 주소) 로드 | A 레지스터 ← 18 (END 레이블의 ROM 주소) | A-명령어 | 08 | 0000000000010010 |
14 | D;JGT // D > 0 (cnt > 100)이면 루프 종료 (END로 점프) | 조건부 분기 | C-명령어 | 09 | 1110001100000001 |
15 | @cnt // cnt의 주소 참조 | A 레지스터 ← 16 | A-명령어 | 10 | 0000000000010000 |
16 | D=M // 현재 cnt 값을 D 레지스터로 이동 | D 레지스터 ← RAM[16] | C-명령어 | 11 | 1111110000010000 |
17 | @sum // sum의 주소 참조 | A 레지스터 ← 17 | A-명령어 | 12 | 0000000000010001 |
18 | M=D+M // cnt를 sum에 더하기 (sum = sum + cnt) | RAM[17] ← D + RAM[17] | C-명령어 | 13 | 1111000010001000 |
19 | @cnt // cnt의 주소 참조 | A 레지스터 ← 16 | A-명령어 | 14 | 0000000000010000 |
20 | M=M+1 // 카운터 증가 (cnt = cnt + 1) | RAM[16] ← RAM[16] + 1 | C-명령어 | 15 | 1111110111001000 |
21 | @LOOP // 분기 대상 목적지(LOOP 레이블 주소) 로드 | A 레지스터 ← 4 (LOOP 레이블의 ROM 주소) | A-명령어 | 16 | 0000000000000100 |
22 | 0;JMP // LOOP 레이블로 무조건 점프 | 무조건 분기 | C-명령어 | 17 | 1110101010000111 |
23 | (END) // 종료 지점 레이블 | 레이블 기호 "END"가 ROM 주소 18에 바인딩됨 | 레이블 선언 | ---- | 코드 생성 안 함 |
24 | @END // 분기 대상 목적지(END 레이블 주소) 로드 | A 레지스터 ← 18 | A-명령어 | 18 | 0000000000010010 |
25 | 0;JMP // END 레이블로 무조건 점프 (무한 루프 생성) | 무조건 분기 | C-명령어 | 19 | 1110101010000111 |
이 프로그램의 명령어 순서는 A-명령어, C-명령어, A-명령어, C-명령어... 패턴을 따르는데, 이는 핵 어셈블리 프로그램의 전형적인 모습이다. A-명령어는 다음에 오는 C-명령어에서 사용할 상수 값이나 메모리 주소를 지정하는 역할을 한다.[1]
A-명령어의 세 가지 사용 방식이 모두 나타난다.
- 상수 값 로드: 11번째 줄(`@100`)에서는 상수 값 100을 A 레지스터에 직접 로드한다. 이 값은 다음 줄(12번째 줄, `D=D-A`)에서 루프 종료 조건을 검사하기 위한 계산(cnt - 100)에 사용된다.[1]
- 변수 주소 로드: 4번째 줄(`@cnt`)에서 사용자 정의 변수 'cnt'가 처음 등장하므로, 어셈블러는 이 기호('cnt')를 사용 가능한 다음 RAM 주소(이 경우 16번지)에 할당하고, 이 주소 값을 A 레지스터에 로드한다. 동시에 M 의사 레지스터(선택된 메모리 위치를 가리킴)도 이 주소를 참조하게 되어 RAM[16]이 현재 작업 대상 메모리 위치가 된다.[1] 'sum' 변수도 마찬가지로 6번째 줄에서 RAM 17번지에 할당된다.
- 레이블 주소 로드: 21번째 줄(`@LOOP`)에서는 레이블 'LOOP'에 바인딩된 ROM 메모리 주소(이 경우 4번지)를 A 레지스터에 로드한다. 다음 줄(22번째 줄, `0;JMP`)의 무조건 분기 명령어는 A 레지스터의 값(즉, 'LOOP' 레이블의 주소)을 CPU의 프로그램 카운터 레지스터에 로드하여 프로그램 실행 흐름을 루프의 시작 지점(ROM 4번지)으로 이동시킨다.[1]
핵 컴퓨터는 프로그램 실행을 명시적으로 중단시키는 기계어 명령어를 제공하지 않는다. 따라서 프로그램의 마지막 두 줄(24번째 `@END`, 25번째 `0;JMP`)은 실행 흐름을 END 레이블 위치(ROM 18번지)로 계속 점프시키는 무한 루프를 만들어 프로그램 실행을 사실상 멈추게 한다. 이는 CPU 에뮬레이터 환경에서 핵 어셈블리 프로그램을 실행할 때 흔히 사용되는 방식이다.[1]
참조
[1]
서적
The Elements of Computing Systems: Building a Modern Computer from First Principles, 2nd Edition
The MIT Press
[2]
문서
Nand to Tetris Syllabus
https://drive.google[...]
[3]
웹사이트
Build a Modern Computer from First Principals: From Nand to Tetris
https://www.coursera[...]
2021-08-21
[4]
웹사이트
From Nand to Tetris
https://www.nand2tet[...]
2021-08-23
[5]
서적
The Elements of Computing Systems: Building a Modern Computer from First Principles, 2nd Edition
The MIT Press
[6]
웹인용
Build a Modern Computer from First Principals: From Nand to Tetris
https://www.coursera[...]
2021-08-21
[7]
웹인용
From Nand to Tetris
https://www.nand2tet[...]
2021-08-23
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com