2.1 서론
컴퓨터 하드웨어에게 일을 시키려면 하드웨어가 알아들을 수 있는 언어로 말을 해야 한다. 컴퓨터 언어에서 단어를 명령어(instruction)라 하고 그 어휘를 명령어 집합(instruction set)이라고 한다.
The MIPS Instruction Set
MIPS 구조에서 레지스터의 크기는 32비트이다. MIPS에서는 32비트가 한 덩어리로 처리되는 일이 매우 빈번 -> 이것을 워드라고 부름
2.2 하드웨어 연산
add a, b, c
#두 변수 b와 c를 더해서 그 합을 a에 넣으라고 컴퓨터에 지시하는 것
MIPS 산술 명령어는 반드시 한 종류의 연산만 지시하며 항상 변수 세 개를 갖는 형식을 엄격히 지킨다.
다음은 네 변수의 합을 구하는 명령어들이다.
add a, b, c
add a, a, d
add a, a, e
#이처럼 네 변수의 합을 구하려면 명령어 세 개가 필요하다. #은 주석으로서, 고급 프로그래밍 언어와 달리 한 줄에 명령어 하나만을 쓸 수 있으며, 줄이 끝나면 주석도 끝난다는 점이 C와 다르다
이렇게 모든 명령어가 피연산자를 반드시 세 개씩 갖도록 제한하는 것은 하드웨어를 단순하게 하자는 원칙과 부합한다.
하드웨어 설계 3원칙
**설계원칙 1. 간단하게 하기 위해서는 규칙적인 것이 좋다. **
ex) f = (g + h) - (i + j);
add t0 ,g , h #temporary variable to contains g + h
add t1 , i , j #temporary variable t1 contains i + j
sub f , t0 , t1 #f gest t0 - t1, which is (g+h) - (i+j)
2.3 피연산자
산술명령어의 피연산자에는 제약 -> 레지스터라고 하는 하드웨어로 직접 구현된 특수 위치 몇 곳에 있는 것만을 사용할 수 있다.
즉 모든 연산은 레지스터에서 일어난다. (MIPS 구조에서 레지스터의 크기는 32비트. MIPS에서는 32비트가 한 덩어리로 처리 되는 일이 빈번. 이것을 워드라고 부름)
Q) 프로그래밍 언어에서 사용하는 변수와 하드웨어 레지스터의 가장 큰 차이점은 레지스터는 개수가 한정되어 있다는 점이다.
레지스터 개수를 32개로 제한하는 이유는 하드웨어 기술의 바탕이 되는 3가지 설계 원칙 중 두 번쨰 원칙에서 찾을 수 있다.
설계 원칙 2: 작은 것이 더 빠르다.
레지스터가 아주 많아지면 전기 신호가 더 멀리까지 전달되어야 하므로 클럭 사이클 시간이 길어진다.
“작은 것이 더 빠르다”가 절대적인 것은 아니다.레지스터 개수를 31개로 한다고 해서 32개보다 빨라지지는 않는다. (또 다른 이유는 명령어 형식에서 레지스터가 사용하는 비트 수와 관련이 있다)
레지스터를 사용하여 C 치환문을 번역
ex) f = (g + h) - (i + j);
에서 컴파일러가 변수 f, g, i, j를 레지스터 $s0, $s1, $s2, $s3, $s4에 각각 할당했다고 하자. 컴파일된 MIPS 코드를 보여라.
위 예제 어셈블리어와 매우 비슷. 임시 변수 대신에 $t0와 $t1을 사용하는 것이 다를 뿐이다.
add $t0, $s1, $s2 # register $t0 contains g + h
add $t1, $s3, $s4 # register #t1 contains i + j
sub $s0, $t0, $t1 # f gets $t0 - $t1, which is (g + h ) - (i + j)
그렇다면 단순 변수 외에도 배열이나 구조체 같은 복잡한 자료구조는 컴퓨터 내에서 어떻게 표현되고 사용되는가?
프로세서는 소량의 데이터만을 레지스터에 저장할 수 있지만, 컴퓨터의 메모리는 수십억 개의 데이터를 저장할 수 있다. 그러므로 배열이나 구조체 같은 자료구조는 메모리에 보관한다. (프로세서란 ? -> 처리장치 하드웨어 관점으로는 보통 중앙연산처리장치(CPU)를 말하고, 소프트웨어 관점으로는 BASIC 등의 프로그램 언어로 쓴 기계어로 변환하는 번역기 즉 인터프리터, 컴파일러, 어셈블러를 총칭해서 프로세서라고 한다. 즉 다시 말해서 하드웨어, 소프트웨어의 어떠한 처리를 하는 것을 프로세서라고 부른다.)
MIPS의 산술연산은 산술연산에서만 실행되므로 메모리와 레지스터 간에 데이터를 주고받는 명령어가 있어야 한다. 이런 명령어를 데이터 전송 명령어(data transfer instruction) 라고 한다.
메모리는 주소가 인덱스 역할을 하는 큰 일차원 배열이다.
메모리에서 레지스터로 데이터를 복사해 오는 데이터 전송 명령을 적재(load)라 한다. 적재 명령은 연산자 이름과 메모리에서 읽어 온 값을 저장할 레지스터, 메모리 접근에 사용할 상수와 레지스터로 구성된다. 메모리 주소는 명령어의 상수 부분과 두번째 레지스터의 값의 합으로 구해진다. MIPS에서 이 명령어의 실제 이름은 lw(load wrod)이다.
※ char 단위 데이터들-> 하나씩 포인팅 하기 byte가 좋음. (한 byte 단위로 움직이게 되어 있기 때문이다.) => 1바이트를 가지고 한 개의 문자, 캐릭터를 표현할 수 있기 때문에. 모든 컴퓨터는 다 byte 단위를 쓴다.
※ Big Endian : leftmost byte is word address ex)MIPS (우하)
※ Little Endian : rightmost byte is word address (좌상)
메모리 피연산자를 사용하는 치환문의 번역
A는 100워드 배열, 변수 g,h는 레지스터 $s1, $s2에 할당되었다고 가정한다. 또 배열 A의 시작주소가 $3에 기억되어 있다고 할 때, 다음 C문장을 컴파일 하라.
#C code
g = h + A[8]
g in $s1, h in $s2, base address of A in $s3
이 치환문에 연산은 하나밖에 없지만, 피연산자 중 하나가 메모리에 있으므로 먼저 A[8]을 레지스터로 옮긴 후 연산을 시작해야 한다. 이 배열 원소의 주소는 $s3에 있는 배열의 시작 주소에 인덱스 8을 더한 값이다. 이 데이터는 다음 명령어가 사용할 수 있도록 임시 레지스터에 넣어야 한다.
lw $t0,8($s3) #Temporary reg $to gests A[8]
(이 명령어 그대로는 약간의 문제가 있어 곧 수정될 것이다. 하지만 당장은 이대로 사용한다.)
이제 필요한값 (A[8])을 레지스터 $t0에 넣었으므로 덧셈을 수행할 수 있다. 아래의 덧셈 명령어는 h($s2에 있음)를 A[8] ($t0 값)에 더해서 g에 해당하는 레지스터 ($s1)에 넣는다
add $s1, $s2, $t0 # g = h + A[8]
데이터 전송 명령어의 상수 부분(8)을 변위(offset)라 하고, 주소 계산을 위해 여기에 더해지는 레지스터($s3)를 레지스터($s3)를 베이스 레지스터라고 한다.
※ offset이란
① 컴퓨터 기억 장치에서 임의 주소에서 간격을 두고 떨어진 주소와의 거리. 기억 장치가 페이지 혹은 세그먼트 단위로 나누어져 있을 때 하나의 시작 주소로부터 오프셋(offset)만큼 떨어진 위치를 나타내는 것이다.
[네이버 지식백과] 오프셋 [offset] (IT용어사전, 한국정보통신기술협회)
하드웨어 소프트웨어 인터페이스
프로그램에서 8비트로 구성된 바이트를 많이 사용하므로 대부분의 컴퓨터는 바이트 단위로 주소를 지정한다. 워드 주소는 워드를 구성하는 4바이트 주소 중 하나를 사용한다. 그러므로 연속된 워드의 주소는 4씩 차이가 난다.
위 그림은 실제 MIPS의 주소를 보여준다. 예를 들어 3번쨰 워드의 바이트 주소는 8이다.
MIPS에서 워드의 시작 주소는 항상 4의 배수이어야 한다. 이러한 요구 사항을 정렬 제약(alignment restriction)이라 하며, 많은 컴퓨터에서 이 방법을 사용한다. (정렬을 사용하면 데이터 전송이 빨라지는데 그 이유는 4장을 보면 알 수 있다.)
앞의 코드에서 바이트 주소를 제대로 구하려면 베이스 레지스터 $s3에 4x8, 즉 32를 더해야 한다. 그래야 A[8/4]가 아닌 A[8]주소가 구해진다.
적재와 반대로 레지스터에서 메모리로 데이터를 보내는 명령어를 저장이라 한다. 즉 연산자 이름, 저장할 데이터를 갖고 있는 레지스터, 배열 원소 선택에 사용할 변위, 베이스 레지스터로 구성된다. 앞에서와 마찬가지로 주소의 일부는 상수 형태를 명령어에 포함되어 있고 일부는 레지스터에 기억되어 있다. MIPS에서 이 명령어의 이름은 SW(store word)이다.
적재와 저장을 사용한 번역
변수 h가 레지스터 $s2에 할당되어 있으며 배열 A의 시작주소는 $s3에 들어있다고 가정하자. 다음 C 문장을 MIPS 어셈블리 프로그램으로 바꾸어라
A[12] = h + A[8]
연산자는 하나지만 피연산자 두 개가 메모리에 있기 때문에 MIPS 명령어가 더 필요하다.
lw $t0, 32($s3) #Temporary reg $t0 gets A[8]
add $t0, $s2, $t0 #Temporary reg $t0 gets h + A[8]
마지막 명령어는 48(4x12)변위로 $s3를 베이스 레지스터로 사용하여 합을 A[12]에 저장한다.
sw $t0, 48($s3) #Stores h + A[8] back into A[12]
MIPS에서 메모리와 레지스터 사이에 워드를 복사하는 명령어들이 lw 와 sw인데 다른 컴퓨터에서는 데이터 전송을 위해 적재 명령어와 저장 명령어 외에 다른 명령어들을 함께 사용하기도 한다.
하드웨어 소프트웨어 인터페이스
컴퓨터가 갖고 있는 레지스터보다 프로그램에서 사용되는 변수가 더 많은 경우가 자주 있다. 그러므로 컴파일러는 자주 사용된는 변수를 가능한 한 많이 레지스터에 넣고 나머지 변수는 메모리에 저장했다가 필요할 때 꺼내서 레지스터에 넣는다. 자주 사용하지 않는(또는 한참 후에 사용할) 변수를 메모리에 넣는 일을 레지스터 스펠링(spilling)이라고 말한다.
좋은 선응르 얻고 에너지를 절약하기 위해서는 컴파일러가 레지스터를 효과적으로 사용하여야 한다.
상수 또는 수치 피연산자
이제까지 배운 명령어만으로 상수를 사용하려면 메모리에서 상수를 읽어와야 한다. (상수는 프로그램이 적재될 때 메모리에 넣어진다.) 예를 들어 레지스터 $s3에 상수 4를 더하는 코드는 아래와 같다.
lw $t0, AddrConstant4($s1) # $t0 = constant 4
add $s3, $s3, $to # $s3 = $s3 + $t0 ($t0 == 4)
여기서 $s1 + AddConstant4는 상수 4가 저장되어 있는 메모리주소라고 가정한다.
적재 명령을 사용하지 않는 방법은 피연산자 중 하나가 상수인 산술연산 명령어를 제공하는 것이다. 이 수치를 수치(immediate) 피연산자라고 한다.
수치 피연산자를 갖는 덧셈 명령어는 addi인데 레지스터 $s3에 4를 더하려면 다음과 같이 쓰면 된다.
addi $s3, $s3, 4 // $s3 = $s3 + 4
상수 피연산자 -> 상수 필드를 갖는 산술 명령어를 사용하면 매번 메모리에서 상수를 가져오는 것보다 연산이 훨씬 빨라지고 에너리를 덜 소모하게 된다.
상수 0 -> 또 다른 역할 . 유용한 여러 변형을 제공함. 단순한 명령어 집합을 가능하게 함. ex) 복사 연산은 피연산자 중 하나가 0인 add 명령어이다. 따라서 MIPS에서는 레지스터 $zero를 값 0으로 묶어 두도록 회로가 구현되어 있다. 쓰이는 빈도가 높을 수록 상수를 명령어에 포함시키도록 하는 것이 Make the Common case fast 하라는 좋은 아이디어의 또 다른 예가 된다.
2.4 부호 있는 수와 부호 없는 수
컴퓨터 내에서 모든 정보는 이진 자리 수 , 즉 비트로 구성되므로 비트가 계산의 기본 단위가 된다.
- LSB MIPS 워드에서 가장 오른쪽 비트
- MSB MIPS 워드에서 가장 왼쪽 비트
- 오버플로 : 오른쪽 비트들만으로는 표현이 불가능할 때
- 부호와 크기 표현법 : 양수와 음수를 구별하는 표현방법
- 작은 수 에서 큰 수를 뺄 때 부호 없는 수의 경우에는 어떻게 될까? -> 2의 보수 표현법 등장
-
2의 보수 표현법에서 모든 음수는 MSB가 1이라는 장점이 있다. 따라서 하드웨어가 양수인지 음수인지 알아보려면 MSB만 검사하면 된다. 그러므로 MSB를 부호 비트(sign bit)라 부른다.
- 부호확장(sign extension) : 부호 있는 적재의 경우 레지스터의 남는 곳을 채우기 위해 부호를 반복하여 복사를 함. 이게 부호확장.
- 1b(load byte) 명령어는 바이트를 부호있는 수로 간주하고 남은 24비트를 부호확장하여 채운다.
- 1bu(load byte unsigned) 명령어는 부호없는 정수를 다룬다. C프로그램은 바이트를 부호있는 정수로 다루기 보다 대부분의 경우에 문자를 표시하기 위해 사용하므로, 1bu 명령어는 실제적으로 바이트 적재를 위해서만 사용된다.
- 부호없는 정수 -> unsigned integer
2.5 명령어의 컴퓨터 내부 표현
레지스터가 명령어에서 참조가 되기 때문에 레지스터 이름을 숫자로 매핑하는 규칙이 있어야 하는데, MIPS에서는
- 레지스터 $s0~$s7까지는 레지스터 번호 16에서 23번까지로
- $t0~$t7까지는 번호 8에서 15번까지로 매핑
MIPS 어셈블리 언어를 기계어로 변환
add $t0, $s1, $s2
십진수 표현은 아래와 같다.
-
명령어의 각 부분을 필드(field)라 부른다.
-
처음과 마지막 필드( 이 경우 0과 32에 해당하는 부분)가 컴퓨터에게 덧셈을 하라고 지시하는 부분이다.
-
두 번째 필드는 덧셈에 사용할 첫 번째 피연산자 레지스터의 번호(17=$s1)
-
세 번째 필드는 두 번쨰 피연산자 레지스터 번호(18 = $s2)를 나타낸다.
-
네 번째 필드는 계산 결과가 들어갈 레지스터의 번호(8=$t0)이다.
-
이 명령어에서 다섯 번째 필드는 사용되지 않으므로 0으로 표시했다.
-
종합하면 이 명령어는 레지스터 $s1을 레지스터 $s2에 더해서 그 합을 레지스터 $t0 에 넣으라는 것이다.
위 예제에서 보인 레이아웃을 명령어 형식이라고 한다. MIPS 명령어의 길이는 데이터 워드와 마찬가지로 32비트이다. “간단하게 하기 위해서는 규칙적인 것이 좋다”라는 설계 원칙에 따라 모든 MIPS 명령어는 예외 없이 32비트이다.
어셈블리 언어와 구별하기 위하여 명령어를 숫자로 표현한 것을 기계어라(machine language)라고 하고, 이런 명령어들의 시퀀스를 기계 코드(machine code)라 한다.
(명령어 형식 : 이진수의 필드로 구성된 명령어의 표현 형식) (기계어 : 컴퓨터 시스탬 내에서 사용하는 명령어의 이진수 표현)
거의 모든 컴퓨터의 데이터 길이는 4의 배수이므로 16진수(hexadecimal)가 많이 사용된다.
MIPS 명령어의 필드
- op : 6bits - 명령어가 실행할 연산의 종류로서 연산자(opcode)라고 부른다
- rs : 5-bits - 첫 번째 근원지(source) 피연산자 레지스터
- rt : 5-bits - 두 번째 근원지 피연산자 레지스터
- rd : 5-bits - 목적지(destination) 레지스터. 연산결과가 기억된다.
- shamt : 5-bits - 자리이동(shift)량(2.6절에서 사용, 그 때까진 0)
- funct : 기능(function) op 필드에서 연산의 종류를 표시하고 funct 필드에서는 그 중의 한 연산을 구체적으로 지정한다. 기능 코드(function code)라고 부르기도 한다.
이것보다 필드 길이가 더 길어야 하는 경우에는 문제가 생길 수 있다. 예를 들어 lw 명령어는 레지스터 필드 두 개와 상수 필드 하나가 필요하다. 만일 위의 5비트 중 하나를 주소로 쓴다면 2⁵=32보다 작은 값만을 사용할 수 있다. 이런 문제 떄문에 마지막 하드웨어 설계원칙이 도출된다.
설계 원칙 3: 좋은 설계에는 적당한 절충이 필요하다.
MIPS 설계자들이 택한 절충안은 모든 명령어의 길이를 같게 하되, 명령어의 종류에 따라 형식을 다르게 하는 것이었따.
위으 명령어의 형식은 R타입 또는 R형식이라 하는데, 이것만으로는 부족하기 때문에 I타입 또는 I형식이라는 두 번쨰 명령어 형식을 만들었다. I타입은 수치 연산과 데이터 전송 명령어에서 사용되며 그 모양은 다음과 같다.
16비트 주소를 사용하므로 lw명령은 베이스 레지스터 rs에 저장된 주소를 기준으로 +-2의 15승바이트를 지정할 수 있다. 마찬가지로 addi에서 사용할 수 있는 상수는 +-2의 15승보다 더 클 수 없다.
lw $t0, 32($s3) #temporary reg $t0 gets A[8]
여기서 rs 필드에는 19($s3의 번호), rt 필드에는 8($t0), 주소 필드에는 32가 들어간다. 이 명령어에서는 **rt 필드의 의미가 바뀌어 적재 결과가 들어갈 목적지 레지스터 번호르 표시하는 것을 바뀌었다. **
명령어 형식이 여러가지가 되면 하드웨어가 복잡해지지만 몯느 형식을 유사하게 함으로써 복잡도를 낮출 수 있었다. 예를 들어 R 타입과 I 타입의 처음 세 필드는 이름과 크기가 같고, I 타입의 네 번쨰 필드 길이는 R타입의 나머지 세 필드 길이를 더한 것과 같게 하였다.
MIPS 어셈블리 언어를 기계어로 번역
ex) $t1에 배열A의 시작 주소가 기억되어 있고 $s2는 변수 h에 대응된다고 할 때 다음 C 문장 A[300] = h + A[300]; 은 아래와 같이 컴파일 된다.
lw $t0, 1200($t1) # temporary reg $t0 gets A[300]
add $t0, $s2, $t0 # temporary reg $t0 gets h + A[300]
sw $t0, 1200($t1) # Stores h + A[300] back into A[300]
이 세 명령어에 해당하는 MIPS 기계어를 보여라
편의를 위해 기계어 명령어를 우선 십진수로 표현하기로 한다. lw 명령어의 op 필드 값은 35이다.(그림 2.5참조 - MIPS 명령어 인코딩 표) rs 필드에는 베이스 레지스터 번호 9($t1)가, rt 필드에는 목적지 레지스터 번호 8($t0)이 지정되어 있다. A[300]을 선택하기 위한 변위 값(1200=300x4)은 마지막 필드인 address 필드에 있다.
두 번째 명령어인 add 명령어는 op필드 값이 0, funct 필드 값이 32이다. 레지스터 피연산자 세개(18,8,8)가 각각 두 번째, 세 번째, 네 번쨰 필드에 있으며 각각 $s2, $t0, $t0에 대응된다. sw 명령어의 op 필드 값은 43이며 나머지 부분은 lw와 같다. 위 명령어를 이진수로 표현하면 다음과 같이 된다.(생략)
첫 번째와 세 번째 명령어의 이진수 표현이 매우 유사함에 주목하라. 유일한 차이점은 왼쪽에서 세 번째 비트의 값이다.
요점정리
오늘 날의 컴퓨터는 두 가지 중요한 원리에 바탕을 두고 있다.
- 명령어는 숫자로 표현된다.
- 프로그램은 메모리에 기억되어 있어서 숫자처럼 읽고 쓸 수 있다.
이것이 내장프로그램의 개념이다. 이 개념을 발명한 덕분에 컴퓨터가 눈부시게 발전. 명령어를 숫자처럼 취급하게 된 결과 프로그램이 이진수 파일 형태로 판매되게 되었다. (명령어와 데이터를 똑같이 취급함으로써 컴퓨터 시스템의 메모리 하드웨어와 소프트웨어가 모두 간단해진다. 특히 데이터 저장을 위해 개발된 메모리 기술이 프로그램 저장에도 그대로 사용되며, 컴파일러 같은 프로그램이 인간에게 편리한 형태로 작성된 코드를 컴퓨터가 이해할 수 있는 코드로 바꿀 수 있는 것도 이 특성 덕분이다.)
참고자료
컴퓨터 구조 및 설계 지음 DAVID A.PATTERSON, JOHN L>HENNESSY