전공 과목/컴퓨터 구조

Lecture 04: Instructions - Language of the Computer - 1

arsenic-dev 2024. 9. 28. 15:53

경희대학교 김정욱 교수님의 컴퓨터 구조 수업을 기반으로 정리한 글입니다.

Instruction (Introduction)

▶ Compiler: high-level language -> assembly language, Assembler: assembly language -> binary machine language

 

※ assembler: 단순히 instruction에 대응하는 binary 조합을 dictionary를 보고 찾는 것이다.

 

Instruction (명령어)

Assembly Language Notation(표기법)으로, 컴퓨터한테 명령을 할 때 쓰는 명령어이다.

이때 instrucion이 모여 있는 집합을 instruction set이라고 한다.

 

이렇게 컴파일된 결과를 binary로 바꿨을 때(assembler) 컴퓨터가 이해를 할 수 있게 된다.

 

이러한 Instruction을 정의한 ISA 중, 여기선 MIPS를 사용할 예정이다.

MIPS

MIPS (Microprocessor without Interlocked Pipeline Stages)

ISA 중 하나로, 컴파일링 해주는 instruction이다.

 

※ 여기서 MIPS는 millions of Instructions per second인 MIPS와는 전혀 다른 의미이다.

 

※ ISA: Instruction Set Architecture, 여러 컴파일링 해주는 회사의 instruction이다.

Design Principles of MIPS

성능(속도)을 최대화하고, 비용을 최소화하면서, 하드웨어 및 컴파일러를 쉽게 구축할 수 있는 언어를 만들자

 

Design Principle 1

간담함을 위해 규칙적으로 만들자.

 

ex) 레지스터 비트 수 32비트로 통일되어 있다.

 

Design Principle 2

작은 것이 빠르다.

 

Design principle 2 ex) register

register는 개수가 작을 수록 읽어야 하는 비트 수가 작아지게 되어 속도가 빨라진다.

이러한 register는 개수가 32개로 고정되어 있어, 2^5, 즉 5비트로 표현 가능하다.

 

※ register 개수 = 저장할 수 있는 공간 개수

 

※ 1 byte = 8 bit

 

Design Principle 3

좋은 설계는 적당한 타협을 요구한다.

 
 

Design principle 3 ex) register

register는 임시로 저장할 수 있는 저장 공간으로, 이는 32 비트로 구성되되어 있고, 개수도 32개로 고정되어 있다.

 

레지스터에 접근하거나 명령을 내릴 때, 32비트의 제한된 공간 안에서 이를 표현해야 한다.

즉, 컴퓨터가 명령을 실행할 때 32비트로 그 모든 정보를 담아야 한다. 

 

32비트 중에서 레지스터 번호를 표현, 즉 호하기 위해 5비트 사용,

예를 들어, 00000은 레지스터 0, 00001은 레지스터 1, 11111은 레지스터 31을 나타낸다.

 

근데 만약 레지스터 개수가 더 많아지면 레지스터 번호를 표현하는데 더 많은 비트가 필요한데,

레지스터 번호를 표현하는 데 너무 많은 비트를 사용하면 실제로 연산을 표현할 수 있는 공간이 줄어든다.

 

그 결과, 명령을 복잡하게 표현할 수 없게 되고, 단순히 계산밖에 할 수 없는 한계가 생긴다.

 

즉, 레지스터 개수를 적게하면 명령어를 더 많이 할 수 있다.

 

+ Making the common case fast

자주 쓰이는 것들을 빠르게 만들자.

 

+ Making the common case fast ex) $zero register

$0 register에는 다른 값을 저장 할 수 없고, 0이라는 값이 이미 물리적으로 저장되어 있다.

 

이러한 $zero register는 register 간의 값의 옮길 때 사용하는데,

레지스터 간 데이터 교환이 자주 사용되기 때문에 $zero gegister을 만든 것이다.

 

예를들어, add 명령어로 1번 레지스터의 값과 $zero register의 값을 더한 값을 2번 레지스터에 저장하라고 하면,

사실상 1번 레지스터의 값을 2번 레지스터로 옮긴 것이다.

 

ex) 어떤 거는 꼭 저장을 해야 하고, 어떤 거는 저장을 할 필요가 없을 때 저장을 해야 하는 거는 saved register로 옮겨줘야 한다.

Arthmetic(연산) Instructions in MIPS

General 3-operand format of MIPS assembly language notation

operation destination, source 1, source 2

▶ source 1과 source 2 레지스터에 있는 값들을 operation 하고, 그 결과 값을 destination register에 저장하라는 의미이다.

 

※ 인풋 2개(source1, source 2), 아웃풋 1개(destination)로 구성되어 있다.

 

add a, b, c

▶ b + c 값을 a에 저장하라는 의미이다. 이때 a + b를 c에 저장하는 게 아니니 주의해야 한다.

 

※ 위처럼 일반적인 형태는 R format이다.

 

※ MIPS 명령어 형식

  • R format (Register format): 레지스터 간의 연산 수행할 때 사용 ex) add
  • I format (Immediate format): 레지스터와 상수(즉시값)을 함께 사용할 때 사용 ex) addi
  • J format (Jump format): 프로그램 실행 프름을 변경할 때 사용 ex) jr

Register

CPU(Central Processing Unit, 중앙 처리 장치)가 요청을 처리하는 데 필요한 데이터를 일시적으로 저장하는 기억 장치이다.

 

캐시, 메모리, 디스크는 CPU 외부에 있는 반면,

Register는 CPU 내부에 있어 접근이 빠르다.

 

※ CPU 내부

  • Register: 값을 임시적으로 저장한다.
  • ALU: CPU 내부에서 사칙 연산을 수행한다.
  • Control Unit: 어디로 갈지 컨트롤 한다.

▶ CPU와 캐시 간의 데이터 교환

 

CPU와 캐시가 간의 데이터 교환은  전기적인 신호, 0 or 1로 이루어진다.

 

CPU는 주소 버스 통해 캐시의 주소 값을 요청하는데, 이때 주소 값을 요청하면 해당 주소에 접근할 수 있게 된다.

 

주소 버스를 통해 레지스터엔 처음에 저장이 안 되어 있던 것을 가져 오기 위해 캐시로 가서,

캐시에서 값을 꺼낸 것을 데이터 버스를 통해 CPU에 전달한다.

 

즉, CPU가 주소 버스를 통해 캐시에 접근을 하고 접근을 해서 나온 데이터 값은 데이터 버스를 통해,

Register는 32비트이니, 32비트 짜리 선을 가지고 32비트 값을 전달해서 그 값을 레지스터에 저장을 하게 되는 것이다.

 

※ 제어 버스는 컨트롤 해주는 것이다.

▶ 저장 장치

 

  • Disk: 대용량의 데이터를 영구적으로 저장하는 장치로, 모든 데이터가 존재하기에 지워지면 안 된다. ex) HDD
  • Memory: 프로그램을 실행시켰을 때 임시로 데이터를 저장하는 장치로, 주로 RAM(메인 메모리)를 말한다. 이때, RAM은 전원이 꺼지면 데이터가 사라진다.
  • Cache: 메인 메모리보단 크기가 훨씬 작지만 레지스터(CPU)에서 가까워 빠르게 접근 가능하다.
  • Registers: CPU 안에 있는 소형 저장 공간으로, 32(numbered 0-31)개의 데이터만 저장할 수 있다.

※ register는 임시적으로 저장하는 공간이기에 그 값이 처음부터 register에 존재할 수는 없다. 실제로는 하드디스크에 존재하게 됙, 여기서 자주 쓰는 것들은 메인 메모리에 올리고, 여기서도 자주 쓰는 것들을 또 캐시에 올리고, 거기서 또 자주 쓰는 것을 레지스터에 올리는 것이다.

 

 
 

캐시, RAM 보충 설명

CPU는 먼저 캐시 메모리에서 데이터를 찾고,

 

캐시에 데이터가 존재하면 이를 캐시 히트(cache hit)라고 하여 매우 빠르게 처리하지만,

캐시에 데이터가 없으면 캐시 미스(cache miss)가 발생하여 RAM에서 데이터를 가져오게 된다.

 

RAM 접근은 캐시 접근보다 느리지만 디스크에 비해 여전히 빠른 속도로 처리된다.

 

※ register 용도

  • s: saved, 잃어버리지 않고 꼭 저장을 해야할  저장하는 용도 ex) 나중에 활용
  • t: temporary,  잠깐 스쳤다 갈 때 저장하는 용도 ex) 연산 과정에서 잠깐 사용, 중간 결과 보관
  • zero: 0 값만 존재, 레지스터 간 데이터를 교환하는 용도
  • a: argument, 함수로 전달되는 매개변수(인자)의 용도
  • v: return value, 함수를 반환하는 용도

 

※ register는 앞에 $(달러)로 표시가 있다.

Why the number of the register is 32?

 

레지스터 개수가 많아지면 레지스터를 호출, 즉 부르는 데에 사용하는 비트가 더 많이 필요,

즉, 레지스터를 부르기 위한 Clock cycle time이 증가하기 때문이다.

 

※ 위 책갈피 중 'Design principle 3 ex) register' 부분 참고

▶ register 개수 32 vs 128

 

register 개수가 32개일 때는 2^5으로, 32비트 중 5비트씩 총 3개(destination, source1 , source2)로, 총 15개,

즉 32 - 15 = 17, 명령할 때 17만큼의 자유도가 있는 것이다.

 

하지만 register 개수가 128개일 때는 2^7으로, 32비트 중 7비트씩 총 3개로, 총 21개,

즉 32 -21 = 11, 명령할 때  11만큼의 자유도로 줄어든다.

 

그러면 register 개수를 16개로 더 줄이면 자유도가 더 많아져 좋은 거 아닌가?

 

물론 32 - 4 × 3 = 20으로 자유도는 더 높다.

 

하지만 레지스터 자체가 16개밖에 없으니 (공간 제한) 레지스터 안에 필요한 값이, 즉 미리 저장된 값이 없어,

캐시, 혹은 메모리에서 불러와야 해서 시간이 더 오래 걸릴 수 있다.

 

때문에, 가장 optimal한 개수가 32개이다.

Operands of the Computer Hardware

Example

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

▶ C code

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)

▶ compiled MIPS code

 

※ f, g, h, i, j는 각각 $s0, $s1, $s2, $s3, $s4에 저장되어 있다고 가정한다.

Memory Operands

array는 어떻게 처리할 것인가?

 

사칙 연산은 레지스터를 통해 이루어지는데,

array 물리적인 데이터 값은 메모리 (HDD or RAM or 캐시)에 있기에,

가장 먼저 해야 할 것은 메모리로부터 레지스터로 가지고 오는 작업이 필요하다.

▶ Memory -> Processor(CPU)

 

※ 데이터는 밑에서부터 위로 저장되어 있다. (stack)

 

Processor가 CPU라고 하면 우리가 원하는 데이터는 메모리에 있고,

메모리에 있는 값들을 CPU로 먼저 가져와야 한다.

 

이때 메모리에 있는 각각의 32비트짜리 데이터를 word라고 하는데,

이 메모리에 접근하려면  메모리로부터 값을 부르는 instruction을 수행해야 한다.

 

메모리로 접근하는 주소인 word address도 32 bit, 즉 4 byte이다.

이때 메모리 입장에선 데이터가 매우 크기 때문에, bit가 아닌 byte로 연산, 즉 ⭐메모리는 byte 단위로 접근한다.

 

※ Byte Address가 n이라는 것 시작 주소로부터 n byte만큼 떨어진 곳을 의미한다. ex) A[n/4]

 

※ base address(starting address): array의 시작 address이다. ex) A[0]

Example 1

 g = h + A[8]

▶ C code

lw $t0,32($s3)  # Temporary reg $t0 gets A[8]
add $s1,$s2,$t0  # g = h + A[8]

▶ compiled MIPS code

 

1. Transfer A[8] to a register

메모리에서 register로 값을 옮기는 과정이다.

 

이때 32(4 byte × 8: byte addressing)는 base address로부터 얼만큼 떨어져 있는지를 표현한 것이다.

실제 물리적인 의미는 32 + $s3이다.

 

※ lw(load word): 메모리로부터 값 불러와 레지스터에 저장하는 명령어이다. 이때 lw는 R format이 아니라 형식이 조금 다르다.

 

2. Add h to A[8] and put sum in the register corresponding(일치) to g

register끼리 연산하는 과정이다.

 

※ g, h는 각각 $s1, $s2에 저장되어 있으며, array A의 base address는 $s3에 저장되어 있다고 가정한다.

Example 2

A[12] = h + A[8]

▶ C code

lw $t0,32($s3) # Temporary reg $t0 gets A[8]
add $t0,$s2,$t0 # Temporary reg $t0 gets h + A[8]
sw $t0,48($s3) # Stores h + A[8] back into A[12]

▶ compiled MIPS code

 

1, 2

Example 1 과정과 동일하다.

 

이때, 1, 2에서 둘 다 $t0를 중복 사용하는 이유는 최소한의 레지스터를 사용하기 위해서이다.

 

또한, A[8]이 아니라 ⭐A[0]이면 0($s3)으로 표기를 동일하게 한다.

 

3. Store the sum into A[12]

register에서 메모리로 값을 옮기는 과정이다.

 

※ array: 메모리에 있는 물리적인 값

 

※ sw(store word): 메모리로부터 값 불러와 레지스터에 저장하는 명령어이다. 이때, 명령어 작성할 때 lw와 레지스터의 순서가 반대니 주의해야 한다. save word라고도 해도 되나, 정식 명칭을 store word이다.

 

※ h는  $s2에 저장되어 있으며, array A의 base address는 $s3에 저장되어 있다고 가정한다.

Constant or Immediate Operands

register 간의 연산이 아니라, 상수와 register 연산은 어떻게 처리할까?

 

1번째 방법

lw $t0,AddrConstant4($s1) # t0 = constant 4
add $s3,$s3,$t0 # $s3 = $s3 + $t0 ($t0 == 4)

▶ 1번째 방법 예시 코드

 

메모리로부터 4라는 값을 CPU로 불러와 레지스터에 저장한 후, 그 후에 add 연산을 한다.

 

하지만 이때, 예를 들어 for 문 혹은 while문을 수할 때 i++ (incremental), 즉 1을 더하는 걸 많이 하는데

이러한 방식을 사용하면 메모리로 가는 불필요한 과정이 많아져서 속도가 느리다는 단점이 있다.

 

※ 여기서 AddConstant4는 실제로 있는 게 아니라, 4가 $s1에 저장된 address를 개념적으로 나타낸 것이다. ex) 32, 64

 

2번째 방법

addi $s3,$s3,4 # $s3 = $s3 + 4

▶ 2번째 방법

 

add는 레저스터간 연산이기에 메모리로 부터 상수값을 불러오는 선작업이 필요했는데,

새로운 명령어 addi는 immediate 연산, 즉 constant 연산시 사용되는 방식으로,

이 방식은 메모리로 접근하지 않고 레지스터와 constant 간의 사칙 연산을 수행한다.

 

이때, add는 input이 register source 가 2개, addi는 register source 1개, constant 1개로 한 줄로 바로 상수와 연산하기에,

instruction도 2개에서 1개로 줄고, 메모리로 접근할 필요도 없어져 속도가 빠르다.

 

Make common case fast에 해당한다.

 

MIPS register 0 ($zero) is the constant 0

$zero는 0 값이 저장되어 있기 때문에 덮어쓸 수 없어,

destination 역할은 할 수 없고 source 역할로만 존재한다. (레지스터 간 값 교환)

add $t2, $s1, $0 # $s1 -> $t2

▶ $0 예시 코드

 

Example 1

f = g + (h - 5);

▶ C code

addi $t0,$s2,-5
add $s0,$s1,$t0

▶ compiled MIPS code 1

subi $t0,$s2,5
add $s0,$s1,$t0

▶ compiled MIPS code 2

 

※ f, g, h는 각각 $s0, $s1, $s2에 저장되어 있다고 가정한다.

 

⭐ Example 2

f = - g - A[4]

▶ C code

lw $t0,16($s6)
sub $t0,$0,$t0
sub $s0,$t0,$s1

▶ compiled MIPS code 1

lw $t0,16($s6)
sub $t0,$0,$t0
sub $t1,$0,$s1
add $s0,$t0,$t1

▶ compiled MIPS code 2

 

이 방식은 instrucion 개수가 4개가 되고, add라는 명령어도 한 개 더 쓰게 되어 속도가 느려질 수 있다.

즉, ' compiled MIPS code 1'이 현명한 컴파일링이라고 볼 수 있다.

 

※ f, g는 각각 $s0, $s1에 저장되어 있으며, array A의 base address는 $s6에 저장되어 있다고 가정한다.

 

※ MIPS assembly language

▶ MIPS assembly language

 

  • Data transfer: 메모리로부터 불러오는 것을 말한다.
  • siftt left/right: 곱하기/나누기를 할 때 사용한다.
  • beq/bne: 같냐/다르냐를 비할 때 사용한다.