1. 서론
1) 명령어 : 컴퓨터 언어에서 단어 =>명령어 집합
2) 종류 : ARMv7 , ARMv8, Intel x86 등
3) 특징
- 컴퓨터 마다 다른 명령어집합을 가지고 있음
- 초기 컴퓨터들은 간단한 명령어 집합을 가지고 있었음
- 많은 현대 컴퓨터 또한 간단한 명령어 집합을 가지고 있음
2. 하드웨어 연산
1) MIPS 명령어 집합
- 반드시 한 종류의 연산만 지시하며, 항상 변수 3개를 갖는 형식을 가짐
add a b c // b+c 값을 a에 넣는다.
2) 산술 연산
- 더하기(ADD)와 빼기(Subtract), 그리고 3개의 피연산자로 구성된다.
- 모든 산술연산은 아래 구성을 따른다.
* 디자인 원칙 1 : 단순성(적은 비용에서 큰 성능)과 규칙성(구현을 간단하게)
f = (g + h) - (i + j);
위 코드를 MIPS code로 바꾸면
add t0, g, h
add t1, i, j
sub f, t0, t1
3. 피연산자
1) 산술 연산에서의 피연산자
- 레지스터라고 하는 하드웨어로 몇 곳에 있는 곳만 사용 가능
- MIPS구조에서 레지스터의 크기는 32bit
- 32비트가 한 덩이로 많이 사용돼서 32bit 데이터를 워드(word)라고 부른다
👉 Assembler(어셈블러) 이름
- $to, $t1, ..., $t9 는 임시적인 값으로 사용된다. => 총 10개
- $so, $s1, ..., $s7 은 변수를 저장한다. => 총 8개
* 디자인 원칙 2 : 작을수록 빠르다.
f = (g + h) - (i + j);
위 코드를 MIPS code로 바꾸면
add $t0, $s1, $s2
add $t1, $s3, $s4
sub $s0, $t0, $t1
2) Memory Operands(메모리 피연산자)
① 메모리와 레지스터
- 배열이나 구조체 같은 자료구조(크기가 큰 데이터)는 레지스터가 아닌 메모리에 저장됨
- 따라서 레지스터와 메모리간의 데이터를 주고받는 명령어가 필요함!
- 메모리는 주소가 인덱스 역할을 하는 큰 1차원 배열이며, 주소는 0부터 시작함
👉 MIPS는 바이트 주소 방식을 사용하고 각 워드는 4Byte(32bit) 이다.
- 적재 : 메모리에서 레지스터로 데이터를 복사 (lw)
- 저장 : 레지스터에서 메모리로 데이터를 보냄(sw)
- MIPS는 Big Endian(최상위 주소를 사용)계열에 속함
ex) 메모리와 레지스터 연산 예제
C code:
g = h + A[8]; # g=$s1, h=$s2, A시작주소 $s3
MIPS code :
# 4 byte * 8 = 32byte
lw $t0, 32($s3) # load word
add $s1, $s2, $t0
C code:
A[12] = h + A[8]; # h = $s2, A = $s3
MIPS code:
lw $t0, 32($s2) # load word
add $t0, $s2, $t0
sw $t0, 48($s3) # store word
② Immediate Operands(상수/수치 피연산자)
- 상수 피연산자는 자주 사용돼서 메모리에서 가져오는 것보다 레지스터 내에서 사용하는게 효과적
- 상수연산을 할때는 addi를 이용하면 됨.
addi $s3, $s3, 4
- MIPS register 0 ($zero) is the constant 0
add $t2, $s1, $zero
* 디자인 원칙 3: 자주 생기는 일을 빠르게 만들어라.
✅ 레지스터 vs 메모리
- 레지스터는 메모리보다 접근이 빠르다.
- 메모리 데이터에 대한 작업을 수행하려면 로드 및 저장 필요
=> 컴파일러는 자주 사용되는 변수를 가능한 많이 register에 저장해야 한다. (자주 사용하지 않는 변수는 메모리로)
4. 부호있는 수와 부호 없는 수
- MIPS 워드의 길이는 32bit 이므로 2^32가지의 패턴을 표현할 수 있음
- 그러면 32bit 숫자는 (0~2^32-1) 0 to +4,294,967,295가능
1) 2의 보수 표기방법
- 맨 왼쪽bit(MSB) 가 1이면 음수, 0이면 양수인 표기방법
- 범위 : –2^(n – 1) to +2^(n – 1) – 1
- 32bit이면 ? => –2,147,483,648 to +2,147,483,647
ⓐ 역부호화 방법 :
- 보수화 하고 1을 더한다.
ex) negate +2
–2 = 1111 1111 ... 1101 + 1
= 1111 1111 ... 1110
ⓑ 부호 확장
- 더 많은 비트를 사용하여 숫자 표시(16->32)
- 확장할 때 숫자를 유지하려면 sign bit를 왼쪽으로 채운다.
- 양수면 0으로 채우고, 음수면 1로 채운다.
Ex) 8-bit to 16-bit
+2: 0000 0010 => 0000 0000 0000 0010
–2: 1111 1110 => 1111 1111 1111 1110
- addi: extend immediate value
- lb, lh: extend loaded byte/halfword
- beq, bne: extend the displacement
5. 명령어의 컴퓨터 내부 표현
1) 레지스터 번호
- $t0 – $t7 are reg’s 8 – 15
- $t8 – $t9 are reg’s 24 – 25
- $s0 – $s7 are reg’s 16 – 23
ex) add $t0, $s1, $s2 이라는 연산이 있을 때,
=> 0000 0010 0011 0010 0100 0000 0010 0000 = 02324020(16진수)
2) MIPS 명령어의 필드
ⓐ MIPS R-format Instructions
- op: operation code(연산코드) (opcode)
- rs: first source register number(첫번째 근원지 피연산자 레지스터)
- rt: second source register number(두번째 근원지 피연산자 레지스터)
- rd: destination register number(목적지레지스터)
- shamt: shift amount (00000 for now) (얼마나 shift할건지)
- funct: function code (extends opcode) (연산코드의 확장)
- R 포맷은 add, addu, and, sll, srl 연산 가능
하지만 이 필드는 2^5보다 작은 값만을 사용 할 수있다.
그래서 5필드로는 부족하기 때문에 I 명령어 형식을 만듬.
ⓑ MIPS I-format Instructions
- op : 연산자
- rs : base register
- rt : destination register
- constant : 2^15 ~ 2^15 -1
- address : offset(rs의 주소값으로부터 얼마나 떨어져 있나를 표시)
=> I format은 16bits를 이용해 상수와 주소표현이 가능함
=> 주소값을 가져오는 bne, beq, lw, sw , 상수연산을 하는 addi, addiu, andi 등이 가능
* 16진수
Example: eca8 6420
=> 1110 1100 1010 1000 0110 0100 0010 0000
6. 논리 연산자
ex) shift 연산 (sll)
- shamt: 얼마큼 shift하는 지
- Shift left logical
- Shift left and fill with 0 bits
- sll by i bits multiplies by 2i (i비트 왼쪽으로 shift시 2i곱하는거랑 같음)
- Shift right logical
- Shift right and fill with 0 bits
- srl by i bits divides by 2i (unsigned only)
shift 연산시 rt가 기존 비트가 저장된 장소이고, rd는 shift됐을때 저장되는 곳이다. rs는 사용아함
7. 판단을 위한 명령어
1) 조건 분기
beq rs, rt, L1
//if(rs==rt) L1으로 이동
bne rs, rt, L1
//if(rs!=rt) L1으로 이동
j L1
//무조건 L1으로 이동
ex) if-then-else를 조건부 분기로 번역
if (i==j) f = g + h;
else f = g - h;
//f($s0), g($s1), h($s2), i($s3), j($s4)
bne $s3, $s4, ELSE
add $s1, $s2, $s0
j Exit
Else : sub $s1, $s2, $s0
Exit: ...
ex) while문
while (save[i] == k) i += 1;
//i($s3), k($s5), save시작주소($s6)
Loop: sll $t1, $s3, 2 //배열은 4byte씩쓰므로 2만큼 shift시 2배하는 거랑 같음 = 4
add $t1, $t1, $s6 //시작주소와 t1을 더함
lw $t0, 0($t1) //t1에 저장된 값을 t0에 로드함
bne $t0, $s5, Exit
addi $s3, $s3, 1
j Loop
Exit:
2) 기본 블록 : 시작과 끝을 제외한 내부에 branch target과 branch를 포함하지 않는 명령어들의 시퀀스
* 시작과 끝을 제외하고는 순차적으로 명령어가 수행되도록 하는 것 => 빠르게 실행됨
3) 더 많은 비교 연산자
slt rd, rs, rt
if (rs < rt) rd = 1; else rd = 0;
slti rt, rs, 상수
if (rs < constant) rt = 1; else rt = 0;
* 왜 blt와 bge를 사용하지 않는가?
하드웨어는 간단해야한다는 경고를 준수하여 blt는 너무 복잡하여 클럭속도가 느려져서 사용안함
ex) 부호있는 수와 부호없는 수 비교
- 부호 있는 수 비교 : slt, slti
- 부호 없는 수 비교 : sltu, sltiu(다 양수라고 가정
$s0 = 1111 1111 1111 1111 1111 1111 1111 1111
$s1 = 0000 0000 0000 0000 0000 0000 0000 0001
slt $t0, $s0, $s1 # signed
–1 < +1 $t0 = 1
sltu $t0, $s0, $s1 # unsigned
+4,294,967,295 > +1 $t0 = 0
8. 하드웨어의 프로시저 지원
*프로시저 : 함수
1) 단계
① 프로시져가 access할 수 있는 레지스터들($a0~$a3)에 인수(parameter)를 넣는다.
② 프로시져에게 제어를 넘긴다. (jal)
③ 프로시져용 storage를 획득한다.
④ 프로시져 operation을 수행한다.
⑤ Caller용register(예, $v0, $v1)에 결과를 넣는다.
⑥ Call이 발생했던 곳으로 돌아간다.(jr)
2) 레지스터 할당
- $a0 – $a3: 인수 레지스터 (reg’s 4 – 7)
- $v0, $v1: 반환 레지스터 (reg’s 2 and 3)
- $t0 – $t9: 변수 레지스터(8 ~15, 24&25)
- $s0 – $s7: 저장된 레지스터 (16~23) *사용하기 전/후에 저장/복구 해놔야됨
- $gp: global pointer for static data (reg 28)
- $sp: 스택 포인터 (reg 29)
- $fp: frame pointer (reg 30)
- $ra: 복귀 주소 (reg 31) (PC+4)
* 스택은 높은주소에서 낮은 주소쪽으로 성장함 => 스택에 푸쉬하면 스택포인터가 감소, 팝하면 증가함
3) 프로시저 콜 명령어
Procedure call: jump and link
jal ProcedureLabel
// 다음 명령어의 주소(PC+4)를 ra레지스터에 저장하고 타겟주소로 점프
Procedure return: jump register
jr $ra
pc값을 ra로 복사한다.
ex) 다른 프로시저를 호출하지 않는 프로시저(Leaf Procedure)
int leaf_example (int g, h, i, j){
int f;
f = (g + h) - (i + j);
return f;
}
g,h,i,j = $a0,$a1,$a2,$a3
f = $s0(기존 s0값을 stack에 저장할 필요 있음)
result in $v0
leaf_example:
addi $sp, $sp, -4 //push
sw $s0, 0($sp)
add $t0, $a0, $a1
add $t1, $a2, $a3
sub $s0, $t0, $t1
add $v0, $s0, $zero
lw $s0, 0($sp)
addi $sp, $sp, 4 //pop
jr $ra
ex) 다른 프로시저를 콜하는 프로시저(non-leaf procedures)
- nested call의 경우, caller는 stack에 return address와 call 이후에 필요한 인수, 임시변수들을 저장할 필요가 있다.
- Call 이후에 stack으로부터 원상 복구한다.
int fact (int n){
if (n < 1) return 1;
else return n * fact(n - 1);
}
Argument n in $a0
Result in $v0
fact:
addi $sp, $sp, -8 # n과 n-1을 저장해야함 4*2=8 push
sw $ra, 4($sp) # save return address
sw $a0, 0($sp) # save n
slti $t0, $a0, 1 # n < 1 이면, t0=1
beq $t0, $zero, L1 # n>=1(t0=0) 이면 L1으로 이동
addi $v0, $zero, 1 # n=0이면 v0=1
addi $sp, $sp, 8 # pop 2 items from stack
jr $ra # and return
L1: addi $a0, $a0, -1 # else n-1
jal fact # recursive call with (n-1)
lw $a0, 0($sp) # restore original n
lw $ra, 4($sp) # and return address
addi $sp, $sp, 8 # pop 2 items from stack
mul $v0, $a0, $v0 # multiply to get result i.e. n*fact(n-1)
jr $ra # and return
4) 프로시저 콜 후의 값 보존관계
5) 스택에서 지역변수
* 프로시저 프레임(액티베이션 레코드) : 프로시저의 저장된 레지스터들과 지역변수를 저장하는 스택영역
6) 메모리 구조
- text : 프로그램 코드
- static data : 전역 변수(상수/정적변수)
- dynamic data : 힙(malloc, new)
- stack : 자동적인 저장
'Major > Architecture' 카테고리의 다른 글
[컴퓨터 구조] Chapter 2 명령어: 컴퓨터 언어 (2) (0) | 2021.12.02 |
---|---|
[컴퓨터 구조] Chapter 1_컴퓨터 추상화 및 관련 기술 * (0) | 2021.09.28 |