minkylee

[컴퓨터구조] 하드웨어 연산 본문

CSE/컴퓨터구조

[컴퓨터구조] 하드웨어 연산

minkylee 2024. 4. 16. 16:17

instruction

  • 컴퓨터의 하드웨어에게 일을 시키기 위해서는, 하드웨어가 알아들을 수 있는 언어를 해야한다.
  • 컴퓨터 언어에서의 단어를 명령어(instruction) 이라고 한다.

instruction은 Opcode, Operand 로 이루어져 있다.

  • Opcode : what the instruction dose, 하드웨어가 어떤 작업을 할지에 대한 명령어
  • Operands : the object of an operation, 하드웨어가 어떤 대상에 대해 작업을 할 지에 대한 명령어

예를 들어 add $2 $4 $2 가 있을 때, add가 opcode가 되고, $2, $4, $2가 operands가 된다.

만약 add = 000000001111111, $2, $4, $2 = 010100010110000000 이라면 이진수 또한 마찬가지로 opcode와 operand로 구성된다.

어떤 방식으로 어셈블리어, 기계어는 하드웨어와 소통을 할 수 있을까?

ISA(Instruction Set Architecture)

  • CPU가 이해할 수 있는 instruction의 집합
  • ISA는 CPU마다 모두 다르지만 비슷한 이론에 근본을 둔 비슷한 하드웨어 기술이며, 몇몇 흔한 명령어와 기본적인 명령어(add...)는 모든 컴퓨터가 제공해야한다는 이유 때문에 매우 비슷하다.
  • CPU 제조사마다 본인 회사가 만드는 CPU가 다르고, 같은 회사라도 CPU마다 다르기 때문에 ISA는 모두 다르다.
  • 이 때 동일한 프로그램이 다른 CPU에서 돌아가게 해주는 것이 ISA

instruction set

  • 하드웨어와 소프트웨어의 경계선에 있다.
  • 이 컴퓨터가 어떤 Instruction을 가지고 있는가는 컴퓨터마다 다르다. (버전이 업데이트 된다고 해서 확 바뀌지 않는다.)
  • 초창기에는 굉장히 간단한 instruction set을 가지고 있었다.

RISC-V (Reduced Instruction Set Computer)

  • 2010년부터 미국의 UC버클리에서 개발중인 무료 오픈소스 RISC 명령어셋 아키텍처
  • CISC 내부에 갖추어진 모든 명령어들 중 불과 20%에 해당하는 명령어들 만이 전체 80% 이상의 일을 처리한다. 따라서 CISC와 같이 모든 명령어 셋을 갖고 있는 것은 비효율 적이기 때문에 이를 극복하기 위해 만들어진 것이 RISC 이다.
  • 장점
    • CISC보다 더 빠른 속도로 동작할 수 있으며, 단순하고, 전력 소모가 적고, 가격도 저렴하다
    • CPU의 구현이 간단하다.
  • 단점
    • 하드웨어가 간단한 대신 소프트웨어가 크고 복잡해졌으며, 하위 호환성이 부족하다는 단점이 있다. 즉, 간단한 작업도 하드웨어는 단순한 명령만 처리하기 때문에, 소프트웨어가 이를 처리해야 한다.
    • 어셈블리어 언어가 대체적으로 길다. 따라서 instruction이 많아지기 때문에 메모리 사용량이 증가할 수 있다.
  • 용도
    • RISC 구조는 파이프라인 중첩이 가능해서 같은 수의 명령어에 대해 적은 clock으로 처리가 가능하며 발열과 전력 소모도 줄일 수 있다. 따라서 임베디드 프로세서에서는 RISC 구조를 많이 사용한다. (MIPS, ARM)

RISC-V 명령어의 형태

각각의 명령어는 Opcode와 Operands로 구성되는데, 각각의 피연산자는 64비트 길이를 가진다. (이는 8 byte, 1(double)word)

Operands에는 레지스터의 주소, 메모리의 주소가 올 수 있으며, 현대 컴퓨터의 레지스터는 보통 32개의 레지스터를 가진다. (따라서 각각의 레지스터는 x0~x31 의 주소를 가진다.)

add a, b, c // 모든 산술 연산에는 세 개의 변수를 가지고 있다.

세 개의 변수는 두 개의 source, 한 개의 목적지로 나누어진다.

  • ex) 변수 b, c, d, e 를 더하여 a에 넣고 싶다면 총 세 개의 명령어가 필요하다.
add a, b, c
add a, a, d
add a, a, e

이러한 부분을 통하여 디자인의 원리를 알 수 있다.

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

  • 규칙성을 통해 구현이 더욱 간단해지고, 적은 비용으로 높은 성능을 내는 것이 가능하다.
f = (g + h) - (i - j)
add t0, g, h
add t1, i, j
sub f, t0, t1
  • 이렇게 규칙을 세움으로써 단순하게 명령어를 만들 수 있다.

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

  • 메모리에 직접 접근하지 않고 레지스터에 올려놓고 한다.

레지스터는 하드웨어 디자인의 가장 기초적인 저장소이고 memory hierachy의 가장 최상위에 위치한 계층이다.
RISC-V는 32 X 32-bit register를 가지고 있다.

왜 32개만 사용할까?

  • 레지스터가 많을수록 clock cycle이 길어지고, 이는 곧 속도의 저하를 나타낸다.

RISC-V의 레지스터

보존 레지스터와 비보존 레지스터

  • procedure 호출 후 값이 변하지 않는 것(보존)이 보장되는 레지스터와 보장되지 않은 레지스터
Preserved Not preserved
Saved Register: x8-x9 x18-x27 Temporary Register x5-x7, x28-x31
Stack Pointer(sp):x2 Argument/Result Register: x10-x17
Frame Pointer(fp): x8  
Return Address(ra): x1  
sp 위 stack item sp 아래 stack item

레지스터 주소 Convention

Registers Usage Remark
x0 the constant value 0  
x1(ra) return address  
x2(sp) stack pointer  
x3(gp) global pointer  
x4 (tp) thread pointer  
x5-x7 temporaries Can be overwritten by callee
x8-x9 saved(frame pointer)  
x10-x17 fuction arguments/result  
x18-x27 saved Must be saved/restored by callee
x28-x31 temporaries Can be overwritten by callee
  • temporaries와 saved 구분하는 이유 : 저장하지 않아도 되게하기 위해서

  • 여기서 x5, x6은 temporaries 이다 : 값을 저장하고 갈 필요가 없기 때문에

Memory

Memory Operand(메모리 피연산자)

프로그래밍 언어에서는 값을 하나만 기억하는 단순 변수 외에도 배열이나 구조체같은 복잡한 자료구조가 있다.
이런 복잡한 자료구조 하나에는 레지스터 개수보다 훨씬 많은 데이터 원소가 있을 수 있다.

이러한 자료구조들은 컴퓨터 내에서 어떻게 표현되고 사용될까?

배열이나 구조체같은 자료형은 메모리에 보관한다.

RISC-V의 산술 연산은 레지스터에서만 실행되므로, 메모리와 레지스터간의 데이터를 주고받은 명령어가 있어야 한다.

RISC-V에서는 Load 명령어를 통하여 Memory -> register 를 지원하고
Store 명령어를 통하여 Register -> Memory 를 지원한다.

Load와 Store 명령어를 Data Transfer Instruction 이라고 하며, 레지스터와 메모리 사이의 데이터 운반을 담당한다.

Memory Addressing

메모리에 접근하기 위해서는 instruction은 반드시 memory address를 지원해야 한다.

메모리는 크고, 1차원 배열 형태이며, 주소가 0부터 시작하여 각각의 배열에 주소가 있다.

데이터의 처리는 해당 architecture의 word 단위로 이루어진다. 우리는 32-bit가 word인 architecture를 다루고 있으므로, 실질적인 메모리의 형태는 다음과 같다.

따라서 word의 byte address는 word 안에 포함된 4byte의 하나의 주소와 일치한다.

이렇게 word 단위로 데이터를 처리하고 명령어를 수행한다는 것은 곧 특정 메모리의 주소에 접근할 때, 이진수의 끝자리가 00으로 끝난다는 것을 의미한다.

Endian

  • 하나의 word 안에서 32-bit를 어떤 순서로 놓느냐에 대한 정의.

RISC-V는 Little Endian 방식을 사용하며, 이는 LSB(Least Significant Byte)가 least address or a word에 위치한다.

  • LSB : 가장 하단에 있는 비트

  • 여기서 LSB는 33이 되고 MSB는 00이 된다.
  • RISC-V 에서는 word가 메모리에 정렬될 필요가 없으며, 4의 배수로 시작할 필요는 없다. 하지만 그래도 4의 배수로 접근하는게 좋다.
  • word boundary : 저장할 때는 바운더리를 지켜서 저장해야 한다. 걸쳐서 저장할 수 없다.
    • 메모리 핸들링이 쉬우나 메모리가 낭비되는 경향이 있음
  • RISC-V는 이런 word boundary가 없어 메모리 낭비를 최대한 줄이고 있다.

LSB와 MSB 더 이해하기

unsigned char 데이터 형에 대해서 예를 들어보자

LSB의 위치는 가장 값이 작은 비트인 2^0에 위치한다. 프로그래밍 시 주로 난수발생 함수, 해시 함수, 검사합 함수 등에서 많이 사용된다. LSB는 값이 조금이라도 변경된다면, 데이터 형의 최하위 비트이므로 거의 100% 변화가 발생하기 때문에

MSB는 2^7이라는 값을 지니고 있는 위치의 비트이다. 데이터형의 가장 값이 큰 위치의 최상위 비트, signed char 에서의 부호비트와 같다.

Memory operand Example

// C code
A[12] = h + A[8];

ld x9, 64(x22) // 시작 주소는 x22에 있고, 8번째 주소는 word byte * 8 이므로 64, 따라서 x22 + 64를 하여 반입
add x9, x21, x9
sd x9(src), 96(x22)(dest) // 저장
  • h는 register x21에 있고, A의 base address는 x22에 있다고 가정해보자
  • A[8]을 register에 반입, h와 A[8]을 더하여 register에 저장, 해당 값을 A[12]에 저장

Register vs Memory

레지스터는 메모리보다 access 속도가 매우 빠르다.

또 arithmetic operation은 오로지 레지스터에 있는 값들로만 연산한다.

많이 사용하는 값을 레지스터에 넣어놔야 한다. 하지만 레지스터가 부족하여 산술연산을 할 때, 해당 데이터가 메모리에 있으면 lw를 통하여 값을 반입해야 한다.

  • 만약 연산을 해야하는데 레지스터에 값이 없으면 L1 cache로, L1에도 없으면 L2, 또 없으면 L3로 갔다가 최종적으로 Memory로 가게 된다.
  • 따라서 Memory로 가는데 총 124ns가 필요하다. -> 얼마 안되는 것 같지만 레지스터와 메모리는 124배나 차이나게 된다!
  • 반복되면 자연스럽게 성능은 하락하게 된다.
  • 따라서 똑똑한 컴파일러는 가능한 레지스터를 많이 사용할 수 있게 설계를 하여, 레지스터를 효율적으로 쓰게 만들어야 한다.
  • 덜 자주 사용되는 변수는 memory로 spill을 해야 하며, 이 과정을 Spilling Register 이라고 한다.

immediate Operands

많은 프로그램이 상수를 자주 사용한다. (ex for문, while문 등등...)

하지만 1이라는 상수를 메모리에 저장해서 사용할 때마다 레지스터에 넣는다면 매우 비효율적이다. -> 따라서 한 번에 더하는 연산을 개발했다 (I-type)

addi x22, x22, 4 // x22 = x22 + 4
  • add가 아니라 addi라는 특별한 타입을 만들어서 상수를 바로 더할 수 있게 만들었다. 해당 instruction을 사용함으로써 memeory에서 상수를 load하는 것 보다 immediate operand 는 매우 빨라졌고, 적은 에너지를 사용한다.
  • 상수가 operand인 경우 addi 를 사용한다.
  • 하지만 이런 Immediate operand의 경우 음수를 addi하는 것은 가능하지만, subi같은 것은 없다.
  • load operation을 하나 이득볼 수 있다.

이러한 개념에서 나온 것이 Hardwired Register
양수를 음수로 만들 때 단순히 앞에 -1을 곱하면 되지만, 컴퓨터는 다른 연산이 있다. 따라서 상수 0을 Register x0에 할당함으로써, 이를 보완한다.
x0은 0으로 고정되어 있기 때문에, lw나 비슷한 명령어를 사용하더라도 무시된다. x1에 있는 값을 음수로 바꾸고 싶다면

sub x1, x0, x1

이것을 사용하면 된다.

참고

https://hi-guten-tag.tistory.com/224
https://ttl-blog.tistory.com/968?category=967479
https://velog.io/@mysprtlty/RISC-V%EC%9D%98-Register
https://blog.naver.com/ansdbtls4067/220886567257
https://gusdnd852.tistory.com/185