학습 주제
- Processor 설계를 학습해야 하는 이유와 더불어서 살펴볼 Y86-64 ISA에 대해 살펴볼 예정이다.
정리한 내용
개요
지금까지는 machine-level program 수준으로만 computer-system을 살펴봤었다. 지금 chapter에서는 processor 하드웨어의 설계를 간략히 봐볼 것이다.
왜 하드웨어 설계를 학습해야 할까?
- 지적으로 흥미롭고(intellectually interesting) 중요한 것이기 때문. 동작이 어떻게 돌아가는지 배우는 것에는 본질적인 가치가 있다.
- 전반적인 Computer System이 어떻게 동작하는지를 이해 가능. Chapter6에서 나올 memory system을 위해, processor-memory간의 interface를 중점으로 봐도 좋음
- Embedded system 설계자들은 프로세서가 어떻게 동작하는지를 무조건 이해해야 하는데, 이들은 더욱이 낮은 추상화 레벨에서 설계되고 프로그래밍 되기 때문임.
- 현재, 혹은 목표가 프로세서 설계자라면 말할 것도 없다.
현재 Chapter에서 사용할 Architecture Y86-64의 특징
- X86-64 Instruction set으로부터 영감을 얻은 것
- 더 적은 data types
- 더 적은 instructions
- 더 적은 addressing mode (addressing mode란 주소를 지정하는 방식으로, instruction들의 인자로 오는 것들을 생각해보면 좋을 것이다.)
- 예) D(Base, Index, Scale) → 이런 방식이 addressing mode 중 하나
- 여기 Y86-64에선 Index와 Scale을 지원하지 않기에 addressing mode가 적다고 하는 것.
- 단순한 byte-level encoding
- byte 수준의 encoding → machine code가 더 커지는 단점이 잇음
- 단, CPU의 decoding logic 설계가 훨씬 쉽다.
- 매우 단순한 instruction set이지만, 정수 데이터에 대한 연산은 모두 가능
- 이후에 이런 프로세서를 설계하면서 맞닥뜨리는 도전들을 봐볼 것이다.
여러가지 도전들
- digital 하드웨어 설계의 배경지식을 제공해줄 것이다.
- 기본 building block들
- 어떻게 서로 연결되고 동작하는지를 이해하기 위해
- HCL Language를 소개할 것이다.
- logic 설계 개념을 알고 있어도 사용하는 표기를 이해하기 위해 읽어야 한다.
프로세서 설계의 단계
- 처음에는 sequential한 연산을 기반으로 하는 Y86-64를 볼 것이다.
- 하나의 instruction은 하나의 clock cycle마다 실행되는 경우를 봐볼 것.
- 즉, cycle은 충분히 느려야 한다.
- 이후 sequential한 설계를 기반으로 pipelined 프로세서를 만들기 위해 ‘변형’을 가해볼 것이다.
- 각 instruction들을 다섯 단계(stage)로 나눌 것이다.
- 한 cycle에는 하나의 stage를 실행하고, 또한 새로운 하나의 instruction이 pipeline에 들어올 것이다.
- 즉, 5개의 instruction이 동시에 실행됨을 생각할 수 있다.
- 이런 Y86-64 ISA의 순차적인 동작을 유지하기 위해 여러 “Hazard Condition”(각 instruction들 사이의 의존 관계 등)을 다뤄야 한다.
4.1장에서 다룰 내용
ISA를 정의하려 함 → ISA를 정의하는 것은 다음을 정의하는 것과 같다.
- 상태의 다양한 요소들을 정의
- instruction set과 이들의 encoding을 정의
- convention들을 정의
- 예외 사항을 다룸
4.1.1 Programmer-Visible State
Programmer-visible state란, 각 Instruction들은 processor state의 여러 부분들을 읽거나 수정할 수 있는데, 이렇게 읽거나 수정할 수 있는 processor state를 Programmer-visible state라고 한다.
- 여기서 Programmer는 assembly code로 프로그램을 작성하는 프로그래머 또는 machine-level code를 생성하는 compiler 를 지칭한다.
이 Y86-64의 state
- 15개의 program register (x86-64에서 %r15만 생략한 것)
- → 각각 64-bit word를 저장한다.
- three 1-bit Condition code들(ZF, SF, OF)
- → 가장 최근의 산술,논리 연산의 영향에 대한 정보를 저장한다.
- PC
- → 현재 실행할 instruction의 주소를 담고 있다.
- Memory→ program과 data를 담고 있다.
- → 개념적으론 큰 byte 배열이다.
- Stat(status code)→ normal operation인지 exception이 발생했는지를 나타낸다.
- → 프로그램의 실행 상태를 나타낸다.
4.1.2 Y86-64 Instructions
위 그림을 보면 여러 instruction들이 있는 것을 볼 수 있다. 포함하는 요소들을 나열해보자면 다음과 같다.
- 오직 8-byte integer operation
- 오직 8-byte 정수형 데이터만을 다루기 때문에, 아무런 모호성 없이 이를 words 라고 지칭할 수 있다.
- 더 적은 addressing mode
- memory의 addressing mode로는 오로지 Displacement밖에 없다(index register 또는 scale factor는 y86-64에서는 지원하지 않는다).
- 더 적은 operation
Y86-64 Instruction의 세부 사항
- Move instruction : irmovq, rrmovq, mrmovq, rmmovq
- 8-byte 데이터만 다루므로 뒤 접미사로는 q밖에 오지 않는다.
- source : immediate, register, memory
- destination : register, memory
- 이들의 조합으로 더 많은 instruction이 있을 것 같지만, 4개밖에 없다. 그 이유는 다음과 같다.
- Y86-64 instruction은 immediate value(상수)를 memory로 옮기는 것은 허용하지 않고 있다.
- memory에서 memory로 직접 옮기는 것은 하나의 instruction으로 처리할 수 없다.(x86-64도 마찬가지임)
- memory는 addressing mode로 Displacement만 지원한다.
- Integer operation instruction : addq, subq, andq, xorq
- 오직 register data에만 동작을 한다 (x86-64는 memory data에도 동작을 했었다).
- 이 명령어들은 전부 3개의 condition code들(ZF, SF, OF)을 설정한다.
- ZF : zero code
- SF : sign code
- OF : overflow code
- Jump instruction : jmp, jle, jl, je, jne, jge, jg
- 여기 책에서는 Jmp를 Branch라고 한다.
- 각 Branch들은 설정되어 있는 Condition code와 각 branch type에 따라 선택된다.
- 각 branch들의 조건은 x86-64와 동일하다.
- Conditional move instruction : cmovle, cmovl, cmove, cmovne, cmovge, cmovg
- 이들 전부 rrmovq instruction과 동일한 format을 가진다.
- 단, destination register는 조건이 만족할 때만 update된다는 차이가 있다.
- Call and ret instruction : call, ret
- call : stack에 return address를 push하고 destination 주소로 jump한다.
- ret : 해당 call로부터 반환시킨다.
- push and pop instruction : pushq, popq
- x86-64와 동일
- Halt instruction : halt
- x86-64에선 hlt 라고 하는 동일한 기능을 하는 instruction이 있다. → 단, x86-64에선 사용을 허용하지 않는데, 이는 전체 시스템에 대한 동작을 중지시키기 때문이다.
- Y86-64에선 processor를 중지시키기 위해 사용한다.
- 이 때 HLT 라는 status code를 설정한다.
4.1.3 Instruction Encoding
이제 4.1.3장에선 byte-level encoding에 대해 살펴볼 것이다.
Y86-64 Instruction의 Byte-level Encoding
- 각 instruction들은 어떤 field를 가지느냐에 따라 1 ~ 10byte의 크기를 가진다.
Field
- Instruction Type
- 모든 instruction들이 이 정보를 가지고 있음
- 1-byte 크기를 가진다(2개의 4bit 정보)
- 상위 4-bit : code 라고 한다.
- 0 ~ 0xB의 범위안의 값을 가진다.
- 하위 4-bit : function value 라고 한다.
- instruction group은 같은 code 값을 가지는데, 이 때 이 function value로 instruction을 구분한다.
- Operation들은 6이라는 code값을 공통으로 가지고 있고, Branch와 Move instruction들도 이와 같은 모습을 하고 있다. 따라서 각각 function value로 각각의 instruction들을 구분한다.Figure 4.3 Function codes for Y86-64 instruction set.
-
-
- 특이한 모습으로는, rrmovq 나 jmp instruction같은 경우, conditional이 아닌데도 같은 code 값을 갖는다.
- 이는 instruction의 byte format이 동일해서 code 값을 공통으로 하는 것이다.
- 즉, 다시 말하자면 code 값으로 byte format을 구분할 수 있다는 것.
-
- register identifier(ID) - rA, rB
-
- Instruction에서 4-bit의 크기를 차지한다.
- Y86-64 ISA에선 15개의 register를 갖는데, 이 때 각 register는 0 ~ 0xE 범위의 번호를 주소로 갖는다.
- 이 주소가 바로 register identifier 이다.
- CPU의 register file에 저장된다.
- 0xF의 값을 갖는 instruction들은 이를 register를 필요로 하지 않는 다는 의미로 사용한다.
- 몇몇 instruction들은 하나 또는 두개의 register를 operand로 갖는다. → rA와 rB로 부른다.
- 이들은 source 또는 destination으로 사용된다.
- 또한 base register(in an address computation)로도 사용된다.
- register를 인자로 갖지 않는 instruction들은 이러한 register specifier 공간이 없다(call 또는 ret instruction이 그 예시이다).
- register를 하나만 인자로 갖는 instruction들은 다른 하나를 0xF로 지정한다(popq 또는 irmovq 와 pushq 가 그 예시이다).
- immediate value
- 사용처
- immediate data (예: irmovq 의 operand)
- displacement for address specifier (예: rmmovq 와 mrmovq 의 displacement)
- jmp 또는 call의 목적지 주소
- 이 때의 목적지 주소는 절대 주소로 주어져야 한다.
- 이전에는 PC-relative addressing을 x86-64에서 사용하고 있음을 봤었다. 이는 더 compact한 encoding을 위해, 그리고 다른 memory part로 이동될 때(linker에 의해 주소를 할당받을 때 등) branch의 target address를 수정할 필요 없게끔 하기 위해서이다.
- 여기선, 더욱이 간단함을 더 중요시하고 있기 때문에 absolute addressing 방식을 사용한다.
- 또한 x86-64와 마찬가지로 모든 정수는 little endian encoding을 사용한다. → 즉, imm 값은 모두 8byte이고, 전부 little endian으로 표기해야 한다.
- 사용처
예시
rmmovq %rsp, 0x123456789abcd(%rdx)
- rmmovq 는 40이라는 instruction type을 가지고 있음
- source register로 %rsp 를 갖는다. → rA field로 들어가고 이는 4의 값을 가진다.
- base register로 %rdx 를 갖는다. → rB field에 들어가고 이는 2의 값을 가진다.
- Displacement로 사용되는 Imm value인 0x123456789abcd 값은 8-byte little endian에 따라 instruction에 들어가게 된다.
- 따라서 Instruction의 구성은 다음과 같게 된다.
4042cdab896745230100 -> [40][42][cdab896745230100]
💡 Comparing x86-64 to Y86-64 instruction encodings x86-64 : compact하고, register-field는 다양한 위치에 들어갈 수 있고, 또한 1, 2, 4, 8byte 등의 상수 값을 처리할 수 있다. Y86-64 : 더 간단하지만 덜 compact하다. 또한 고정된 위치에서만 register field가 등장하고, 오직 8-byte상수만 처리 가능하다.
연습문제 4.1
다음 assembly code를 보고 instruction의 byte-encoding을 결정해라
- 정답
- 0x100 : 30f30f00000000000000
- 0x10a : 2031
- 0x10c : 4013fdffffffffffffff
- 0x116 : 6031
- 0x118 : 700c0100000000000000
연습문제 4.2
아래의 byte sequence를 보고 Y86-64 instruction으로 encoding해라
(위의 문제를 풀어보면, 이들을 encoding 하는 것은 어렵지 않다)
- 정답
- 책 p.517에 나옴
4.1.4 Y86-64 Exceptions
Programmable-visible state는 도한 status code, Stat을 포함한다고 했다. 이제 이 Stat에 대해 다뤄보자.
- Code value 1 : AOK
- 정상적인 실행을 하고 있을 때 설정됨
- Code value 2 : HLT
- halt instruction을 맞닥뜨렸을 때 설정됨
- Code value 3 : ADR
- invalid memory address에 읽기 또는 쓰기를 시도 시 설정됨
- instruction을 memory에서 CPU로 fetch할 때
- data에 read 또는 write 할 때
- 주소의 maximum을 정해두는데, 이를 넘어갈 때 ADR이 설정된다.
- invalid memory address에 읽기 또는 쓰기를 시도 시 설정됨
- Code value 4 : INS
- invalid instruction을 맞닥뜨렸을 때
Y86-64의 Exception에 대해
Y86-64는 exception들 중 하나를 만나면 프로세서의 실행을 멈춰버린다. x86-64같은 복잡한 프로세서의 경우 exception에 대한 복잡한 처리를 수행한다.
- exception handler라는 것이 있음
- 각 exception당 수행할 것들이 미리 설계되어 있음
- 이 수행에 대해 설정도 따로 할 수 있음 → signal handler를 정의함으로(Chapter 9에서 자세히 볼 예정)
4.1.5 Y86-64 Programs
long sum(long *start, long count)
{
long sum = 0;
while (count) {
sum += *start;
start+=;
count--;
}
return sum;
}
여기에서는 위의 코드를 gcc로부터 생성된 x86-64 code와 Y86-64의 code를 비교하며 살펴볼 예정이다.
x86-64 V.S. Y86-64
x86-64 code.
Y86-64 code.
Complete assembly code written in Y86-64
Figure 4.7. Simple program writeen in Y86-64 assembly code.
코드를 보기 전 알아야 할 구성요소들
이 프로그램은 data와 instruction 모두를 포함하고 있다. 여러가지 지시자들도 있는데, 이를 살펴보자.
. 으로 시작하는 단어 : assembler directives라고 한다. 이는 assembler에게 word data들 또는 생성된 code들이 어디 주소에 생성되어야 할지를 설명해준다.
- .pos
- (line 2) : 생성된 code는 주소값 0에서부터 시작해야 함을 지시하고 있다. (Y86-64 프로그램은 항상 0에서부터 시작한다고 한다)
- (line 39) : line 40에 stack이라는 label을 볼 수 있는데, 즉 이 stack은 주소 0x200부터 아래로 자라난다고 정의를 한 것이다. 이것으로 line 3에서 stack pointer %rsp를 초기화 하는 모습을 볼 수 있다.
- .align
- (line 8 ~ 13) : 4개의 words를 가지는 array를 선언한 모습을 볼 수 있다. line 9의 array label은 array의 시작을 명시하고 있다. 또한 .align 지시자를 통해 8-byte boundary로 align되도록 하고 있다. main에서 이 four-words array를 sum의 인자로 넘기는 모습도 확인할 수 있다.
YAS, assembler for creating Y86-64 code
위의 예시를 봤듯이, Y86-64 code를 만드는 도구는 assembler가 유일하다. 즉, 프로그래머가 compiler, linker, runtime-system가 해야 할 일을 도맡아서 해야한다는 것이다. 다행히도, 이 Y86-64 code에 대해선 작은 프로그램들만 볼 것이라고 한다.
위의 code를 YAS에 의해 assembling한 결과
- YAS의 결과
- ASCII 형식으로 되어 있어, 우리가 읽기 쉽다.
- 하나의 line에 instruction 또는 data를 포함하고, 또한 address와 그 뒤로는 1 ~ 10byte의 값들(instruction들)이 나온다.
-
- 위쪽 주소를 보면 0x000부터 해서 0x090까지 instruction들에 대한 byte sequence가 적혀있다.추가적으로 볼 것
- 위의 0x000부터 0x090까지는 아래의 text영역의 범위라고 할 수 있다.
- 또한 stack의 영역은 0x200부터 시작이니 옆의 그림에서의 stack의 시작 주소(맨 위 주소)는 0x200라는 것을 알 수 있다.
- 즉, 흔히 볼 수 있는 process의 virtual address space의 그림을 보며 이해를 해보자면 다음과 같다.
YIS???
- instruction set simulator라고 하는데, 이의 목적은 Y86-64 machine-code 프로그램의 실행을 프로세서의 세부적인 행동을 신경쓰지 않고 모델링하기 위해서이다.
- 실제로 이 code를 실행하기 전에 program이 동작을 잘 하는지를 살펴보기 위한 debugging 용도로 있다고 한다.
- register와 memory 값들 중 왼쪽 값은 초기 값을 나타낸다. (전부 0임)
- 오른쪽 값은 프로그램이 끝날 때의 값을 보여주고 있다.
- %rax register에 담긴 값은 four words array를 sum한 결과값이 담겨 있는 것이다.
- stack에 대한 register인 %rsp는 0x200의 값을 가지고 있는데, 이는 start address라고 위에 언급했었다.
- 또한 memory를 보면 0x1f0 ~ 0x1f8까지 변경된 것을 알 수 있는데, 이는 메모리 stack이 0x200에서 아래로 자라나기 때문에 0x200보다 작은 범위에서 사용이 되고 있음을 보여주고 있다.
- 그리고 실행 코드의 주소의 상한이 0x090인 것을 미루어 보아, 메모리 영역이 실행 코드를 침범하지 않았다는 것을 볼 수 있다.
4.1.6 Some Y86-64 Instruction Details
간략하게 눈여겨 볼만한, 또는 주의해야할 점들을 살펴볼 예정이다.
대부분의 Y86-64 instruction 들은 program state를 확실하게 변경한다. 하지만 그렇지 않은 것들이 있는데, 이를 좀 살펴보자.
pushq instruction : stack pointer를 8만큼 감소시키고 또한 memory에 register 값을 쓰는 두 가지의 동작을 수행한다.
- 이는 무엇을 먼저 수행하는지 명확하지 않을 수가 있다.
- %rsp의 원래의 값을 push하는 걸 수도 있고,
- 감소된 %rsp의 값을 push하는 걸 수도 있다.
이런 두가지의 해석의 존재는 모호성을 유발하므로 이를 x86-64에서 동작하는 바와 같이 동작한다고 생각할 것이다. 이때, x86-64의 동작을 확인해볼 필요성을 느낄 것이다.
x86-64의 push 동작(Practice Problem 4.7)
- 기존 stack pointer의 주소를 %rax 에 옮겨놓았다.
- 이후 stack pointer 주소를 stack에 push한다. ← 이 동작을 보고 위의 두가지 해석 중 어떤 방식으로 동작하는지를 알 수가 있다.
- 만약 %rsp를 push한 결과,
- push된 값이 %rsp가 감소되기 전의 값이라면 1번과 같이 동작한 것이다.
- 만약 %rsp가 감소된 값이 들어갔다면 2번과 같이 동작한 것이다.
- 만약 %rsp를 push한 결과,
- 따라서 push된 %rsp의 값을 확인하기 위해 %rdx에 push된 값을 꺼내서 옮겨 넣은 것이다.
- 즉, push된 %rsp의 값과 미리 저장했던 원래의 %rsp값 끼리 subq 연산을 통해,
- 0이 나온다면 1번과 같은 동작으로
- 0이 나오지 않는다면 2번과 같은 동작으로 동작한 것이다.
→ 프로그램의 결과로 항상 0이 나온다고 하므로, x86-64의 pushq 연산의 동작은 원래의 주소를 push하고 stack pointer를 감소시키는 것이다.
💡 하지만 x86 processor에서도 push 연산은 일치하지 않는다? (Intel documentation의 PUSH instruction부분에 따르면) IA-32 processor의 경우, 감소되기 전의 값을 push한다. 하지만 8086 processor의 경우, 감소된 새 stack pointer 값을 push한다고 한다.
즉, x86 processor가 어떠한 모드 아래에서 동작하는지에 따라 동작이 일치하지 않을 수가 있다는 것이다.
이렇게 일치하지 않게 되면 무엇이 안좋은가?
- code의 portability(이식성)이 떨어지게 된다.
- 문서를 더 복잡하게 한다.
따라서 이러한 이유로 위의 4.1.6에서와 같이 세부적인 사항들까지 살펴봤던 것이다.
'Computer Science > 컴퓨터 구조' 카테고리의 다른 글
[CSAPP] 4.3 Sequential Y86-64 Implementations (0) | 2023.03.01 |
---|---|
[CSAPP] 4.2 Logic Design and the Hardware Control Language HCL (논리 설계와 하드웨어 제어 언어 HCL) (0) | 2023.03.01 |
[CSAPP] 9.7 Case Study: The Intel Core i7 / Linux Memory System (0) | 2023.03.01 |
[CSAPP] 9.8 Memory Mapping (메모리 맵핑) (0) | 2023.03.01 |
[CSAPP] 8.3 System Call Error Handling (시스템 콜 에러 처리) (0) | 2023.03.01 |