본문 바로가기
CS/컴퓨터 구조

[Chapter 2.1 컴퓨터 구조 및 설계] 레지스터의 역할과 메모리의 역할 / MIPS 어셈블리어 (산술 연산, 메모리 연산, 상수 연산)

by 베어 그릴스 2022. 7. 7.
본 정리는 CS422-컴퓨터 구조 및 설계 : 하드웨어/소프트웨어 인터페이스. David A. Patterson,존 헤네시 책을 바탕으로 하고 있음을 미리 알립니다.

 

명령어 집합


  • 컴퓨터의 명령어 집합이다
  • 다른 컴퓨터는 다른 명령어 집합을 갖는다
    • 그러나 많은 측면에서 공통점이 있다 
  • 초기 컴퓨터는 매우 단순한 명령어 집합을 갖고 있었다
  • 발전하여 복잡한 명령어 집합(Complex instruction sets - CISC)을 갖게 된다.
    • 한 명령어가 한 번에 많은 명령을 할 수 있다
  • 현대의 컴퓨터들은 다시 단순한 명령어 집합을 갖게 된다.
    • Reduced instruction sets(RISC)

 

MIPS 명령어 집합


이 책에서 예제로 사용할 명령어 집합으로, 현대의 많은 ISA(Instruction set Architecture - 명령어 집합 구조)의 공통점들을 갖고 있다.

 

Reduced instruction sets(RISC)이다.

 

하드웨어 연산


기본적으로 모든 컴퓨터는 산술 연산(Arithmetic)을 할 수 있어야 한다.

 

다음 MIPS 어셈블리 언어는 두 변수 B와 C를 더해서 그 합을 a에 넣으라고 컴퓨터에 지시하는 것이다.

 

add a, b, c # a gets b + c

 

MIPS산술 명령어는  (RISC이기 때문에) 반드시 한 종류의 연산만 지시하며, 항상 변수 3개를 갖는 형식을 엄격히 지킨다.

 

이러한 규칙에서 우리는 하나의 설계 규칙을 도출해낼 수 있다.

 

설계 원칙 1 : 간단하게 하기 위해서는 규칙적인 것이 좋다.

 

예시로 다음 C언어를 MIPS 어셈블리어로 바꿔보자.

f = (g + h) - (i + j);

다음과 같이 될 것이다.

add t0, g, h # temp t0 = g + h
add t1, i, j # temp t1 = i + j
sub f, t0, t1 # f = t0 - t1

 

레지스터


우리가 C, JAVA 등의 High level Language로 코딩했을 때는 그냥 변수를 만들어서 그 변수에 값을 저장하면 끝이었지만, 산술 명령어의 피연산자에는 제약이 있다. 레지스터(register)라고 하는 하드 웨어로 직접 구현된 특수 위치 몇 곳에 있는 것만을 사용할 수 있다. 

 

레지스터는 하드웨어 설계의 기본 요소인 동시에 프로그래머에게도 보이는 부분이므로, 컴퓨터를 구성하는 벽돌과 같다고 할 수 있다.

 

MIPS 구조에서 레지스터는 32비트의 크기를 갖는다.

 

MIPS에서 32비트가 한 덩어리로 처리되는 일이 매우 빈번하므로 이것을 워드(word)라고 부른다.

 

프로그래밍 언어에서 사용하는 변수와 하드웨어 레지스터의 큰 차이점 하나는 레지스터는 개수가 한정되어 있다는 점이다.

 

MIPS는 32개의 레지스터를 갖는다. 즉, MIPS는 32비트의 레지스터를 32개 갖고 있다.

 

각 레지스터는 임시 값을 갖는 레지스터 0의 값을 저장하는 레지스터 등 역할을 갖고 있다. 

ex) C나 java의 변수에 해당하는 레지스터를 위해선 $s0,$s1 등, MIPS 명령어로 컴파일하기 위해 필요한 임시 레지스터를 위해서는 $t0,$t1 등등을 이용한다.

 

왜 레지스터를 32개로 제한해서 갖고 있을까? 그 이유는 설계 원칙 2번째에 있다.

 

설계 원칙 2: 작은 것이 더 빠르다

 

레지스터의 개수는 제한되어있기 때문에 컴파일러는 자주 쓰이는 변수를 시간이 오래 걸리는 메모리가 아니라 레지스터에 저장하려는 레지스터 최적화를 해야만 한다.

 

위에서 썼던 예시를 레지스터를 사용해서 바꿔보자

f = (g + h) - (i + j);

f, …, j in $s0, …, $s4

 

다음과 같이 될 것이다.

add $t0, $s1, $s2
add $t1, $s3, $s4
sub $s0, $t0, $t1

 

 

메모리와 메모리 연산자


프로그래밍 언어를 쓸 때 위 예제와 같이 단순한 변수 외에도 배열이나 구조체 같은 자료구조들이 있다.

이러한 복잡한 자료구조에는 레지스터 개수보다 훨씬 많은 데이터가 있을 수 있는데, 이러한 구조들은 컴퓨터에서 어떻게 표현되고 사용될까?

 

우린 이러한 큰 데이터는 메모리에 저장한다.

 

MIPS의 산술 연산은 레지스터에서만 실행된다.

즉, 우리는 메모리와 레지스터 사 간에 데이터를 주고받는 명령어가 필요하다.

이때 사용되는 게 바로 메모리 연산자다.

 

메모리 연산자를 알아보기에 앞서 메모리부터 알아보자.

 

  • 메모리는 각 주소에 8bit씩 데이터를 저장한다. 이를 byte address라고 한다.
    • 8bit 씩 데이터를 저장하는 이유는 8bit의 데이터가 매우 유용하게 쓰이기 때문에 메모리에 한 byte씩 저장하여 데이터를 구분한다
    • 워드 주소를 사용할 경우 워드는 32bit이므로 4바이트의 크기를 차지한다. 즉, 각 주소는 4씩 차이가 나게 된다. 이러한 요구사항을 정렬 제약(Alignment restriction)이라고 한다.
  • word가 메모리에 저장될 때는 4byte씩 저장되므로 1byte씩 저장해주어야 하는데, 이때 4byte를 앞에서부터 즉, msb(most significant byte)부터 메모리에 저장할 것인지(Big Endian) 혹은 뒤에서부터 즉, lsb(least significant byte)부터 저장할 것인지 정해져 있다.(Little Endian)

 

 

Big Endian VS Little Endian

다음과 같이 1 word의 데이터가 있다고 하자. 즉 한 박스는 1byte를 나타낸다.

msb부터 차례로 4b 3b 2b 1b의 데이터가 들어가 있다고 하자.

 

그렇다면 메모리 주소가 0부터 시작이라고 하면, big endian의 경우 msb부터 저장하고 Little endian의 경우 lsb부터 저장하므로,

메모리 주소 big endian Little endian
0 4b 1b
1 3b 2b
2 2b 3b
3 1b 4b

 이러한 형식으로 다르게 데이터가 저장되게 된다.

 

 

메모리 연산자 lw

 

메모리에서 레지스터로 데이터를 복사해오는 데이터 전송 명령을 load라고 하고 MIPS에서 이 명령어의 실제 이름은 lw(load word)라고 한다.

 

예를 들어,

g = h + A[8];

의 C 코드가 있고, g in $s1, h in $s2, base address of A in $s3라 가정하자. 또 A배열은 4byte 크기의 word를 저장하고 있다고 하자.

 

이를 MIPS 어셈블리어로 바꿔보면

lw $t0, 32($s3) # load word
add $s1, $s2, $t0

이 된다. 앞에 32가 offset으로 곱해지는 이유는 인덱스가 8번째 인덱스인데, 4byte 크기의 word를 저장하고 있으므로 메모리 주소 상으로는 A의 주소에 32(4*8)가 더해져야 하기 때문이다.

 

즉, 32+$s3에 저장되어 있는 주소 값을 통해 그곳에 저장되어 있는 값을 $t0로 받아올 수 있게 된 것이다.

 

 

메모리 연산자 sw

 

적재와 반대로 레지스터에서 메모리로 데이터를 보내는 명령을 저장(store)이라 하고 MIPS에서 이 명령어의 실제 이름은 sw(store word)라고 한다.

 

예를 들어,

A[12] = h + A[8];

의 C 코드가 있고, h in $s2, base address of A in $s3 라 가정하자. 또 A배열은 4byte 크기의 word를 저장하고 있다고 하자.

 

이를 MIPS 어셈블리어로 바꿔보면

lw $t0, 32($s3) # load word
add $t0, $s2, $t0
sw $t0, 48($s3) # store word

즉 A배열의 시작 주소인 $s3에 12*4인 48을 더해서 해당 주소에 $t0값을 store 해준 것이다. 위의 예시와 비슷하다.

 

상수 연산자 (Immediate Operands)


프로그램에서 상수를 사용하는 경우는 많다.

 

그럼 이러한 상수는 어디에 저장하게 될까?

상수는 따로 저장하지 않고 연산자가 따로 있다.

 

ex) addi $s3, $s3, 4

 

상수 연산자는 빼기 연산은 따로 없고 음수 상수를 사용한다

 

ex) addi $s2, $s1, -1

 

설계 원칙 3: 자주 생기는 일을 빠르게 하라.

상수는 쓰이는 빈도가 높으므로 아예 상수를 명령어에 포함시키도록 하는 것이다.

 

상수 0의 경우는 어떠할까?

 

상수 0은 여러 변형을 제공함으로써 단순한 명령어 집합을 가능케하기 때문에 $zero라는 레지스터에 따로 저장해 두고 사용한다.

ex) add $t2, $s1, $zero 등의 값 복사

 

요점 정리


레지스터의 역할 은 변수를 저장하는 것이고 개수가 제한적이다. 

 

메모리의 역할 또한 데이터를 저장하는 것이지만, 크기가 레지스터에 비해 훨씬 크고 느리다. 그래서 자료구조 같은 대량의 데이터를 저장하는데 쓰인다.

 

변수의 개수가 레지스터의 제한을 넘을 경우 컴파일러의 레지스터 최적화는 필수적이다.

 

MIPS 어셈블리어의 산술 연산자, 상수 연산, 메모리 연산 등에 대해 알 수 있다.