이번에 정리할 내용은 RISC를 기반으로한 개방형 표준 ISA인 RISC-V에 대한 내용입니다.
개방형 표준 ISA라는 것은 학계와 산업에 라이센스나 로열티가 없이 자유롭게 제공됨을 뜻합니다.
2010년 UC 버클리 연구자들에 의해 시작되었고, 80x86 ISA보다 더 간단하고 우아하다고 설명합니다.
최근 많은 공급업체들이 빠르게 채택하고 있으며, 마이크로컨트롤러부터 슈퍼컴퓨터까지 모든 수준의 컴퓨팅 시스템에 적합하다는 장점이 있습니다.

RISC-V에서 operand(피연산자)는 반드시 레지스터에 담겨있어야만 합니다. 총 32개의 64bit 범용 레지스터가 존재합니다. 여담으로 Intel architecture에서는 메모리에서 직접 연산이 가능한 경우도 있습니다.
명시적인 피연산자는 일반적으로 3개이며, 대부분의 명령어는 아래와 같습니다.
명령어 destination, source 1, source 2
또한 연산 결과를 연산에 사용했던 레지스터가 아닌 다른 레지스터에 저장하는데, 그 이유는 단순히 RISC 방식이 레지스터가 더 많기 때문입니다.
피연산자의 위치 지정, 즉 메모리에 접근하는 방법은 메모리 주소를 지정하는 방식입니다.
64bit에서는 0~264-1 개의 주소를 표현할 수 있지만, 이것은 현재 컴퓨터 사양을 고려했을 때 너무 큰 수치이므로 실제로는 0~248-1개 정도만을 사용하고 있습니다.
또한 메모리와 레지스터 간에는 64bit 단위로 데이터가 전송됩니다
하지만 조금 벗어나게도 명령어의 크기는 32bit입니다.
단순성: 32비트 고정 길이 명령어는 디코딩이 단순하고 효율적이며, 하드웨어 설계가 간결해집니다.
효율적인 메모리 사용: 고정된 명령어 크기는 메모리와 대역폭을 효율적으로 사용할 수 있게 해줍니다.
명령어 기능성: 32비트 명령어로도 충분히 복잡한 연산을 표현할 수 있고, 필요한 경우 확장 명령어를 사용하여 처리할 수 있습니다.
64비트는 데이터 처리 단위: 64비트 아키텍처는 레지스터와 데이터 처리에 해당하며, 명령어의 크기는 별개의 문제입니다.
명령어도 64bit architecture에 맞게 전면적인 수정을 거쳤을 경우의 문제점은 인텔의 IA-64가 가장 잘 보여줍니다. 기존에 사용하던 IA-32와의 호환이 전혀되지않았기 때문에 결국 실패를 인정하고 명령어는 32bit로 그대로 유지한 CPU를 새로 만들어 냈습니다.
RISC-V는 모듈형으로 설계 되었으며, 설계의 유연성을 높이고 다양한 응용 분야에 맞게 필요한 기능만 선택적으로 사용할 수 있게 합니다.
또한 기본 베이스 ISA와 다양한 확장 ISA들로 구성됩니다.
기본 베이스
확장

선택적 사용이 가능하다는 뜻은 만약 정수 연산이 필요한 계산기를 만들 것이라면 I와 M만 가져와서 사용할 수 있다는 것을 뜻합니다.
RISC-V는 총 33개의 user-visible registers를 갖고 있다. 그 중 32개는 범용 레지스터, 나머지 하나는 programm counter(PC)이다.
64-bit wide
레지스터 x0는 하드웨어적으로 상수 "0"으로 고정: 자주 사용되기 때문에 메모리에 접근해서 가져오는 것보다 미리 지정해놓는 것이 더 효율적
x1은 return address: 함수가 호출된 후 다시 돌아올 명령어의 주소를 저장
부호는 어디에 어떻게 나타내야할까?
간단하게는 MSB를 부호로 사용할 수 있습니다. 이해하기 쉽다는 장점이 있지만, 뺄셈을 지원하기 위해서는 추가적인 회로 설계가 필요합니다.
그래서 고안된 것이 2의 보수로 음수를 표현하는 방법입니다.
n-bit의 이진수를 m-bit의 이진수로 변환(m>n)
-> MSB(부호bit)를 가져와서 모든 비트를 채우도록 복제
Signed load에서 16bit 숫자를 32bit 레지스터에 저장할 때 부호 확장 방식을 사용
남은 bit를 0으로 차우도록 복제
unsigned load에서 사용

opcode에 할당된 bit는 6bits이고, A와 B 타입 모두 6bits만큼을 사용하고 있기 때문에 26개의 명령어를 가질 수 있습니다.

Q1) 타입이 2개 존재하므로 MSB의 비트를 구분자로 사용하거나, opcode의 길이만으로 어떤 Type인지 구분하는 방법
Q2) MSB에 0 또는 1을 할당하는 방법이라면 25 + 210개의 명령어를 가질 수 있을 것이고,
길이만으로 구분을 할 수 있다면 26 + 211개의 명령어를 가질 수 있을 것이다.
32bit Instruction은 여러 개의 field로 나누어집니다.

R-type을 기준으로 나누어보면
1. opcode: 어떠한 타입의 명령어인지 나타냅니다.
2. rd: definition register를 나타냅니다.
3. funct3: 특정한 타입의 명령어 중에서 어떠한 기능을 하는 명령어인지 나타냅니다.
4. rs1, rs2: 명령어에 사용될 소스 레지스터입니다.
5. funct7: 연산의 세부사항을 결정합니다.
+) imm: 연산에 즉시 추가될 값을 지정합니다.

두 개의 source 레지스터와 하나의 dstination 레지스터를 사용
오직 register operands만을 사용

Assembly(어셈블리)
ADD rd, rs1, rs2
Semantics(의미론)
GPR[rd] <- GPR[rs1] + GPR[rs2]
PC <- PC + 4(명령어의 길이가 32-bit이기 때문에 4개 늘어남)
Variations
세부 동작

R-type의 opcode는 0110011로 동일
ADD와 SUB를 하나로, shift 연산 두 개를 하나로 묶으면 총 9개의 세부 명령어가 존재하므로 funct3는 3bits만 할당
RISC-V에서 범용레지스터는 32개이므로 레지스터마다 5bits를 할당
funct7은 비트할당의 일관성을 이유로 7bits가 할당되어있어 128개의 세부 명령어를 만들 수 있지만, 미래확장성을 고려하여 전부 다 사용하고 있지는 않음
명령어 리스트

SLL: rs1의 값을 rs2에 저장된 시프트 양만큼 왼쪽으로 이동시키고, 그 결과를 rd에 저장. 이후 LSB는 0비트로 대체, MSB는 버려짐 (= 2n을 곱하는 것과 같은 효과)
SRL: rs1의 값을 rs2에 저장된 시프트 양만큼 오른쪽으로 이동시키고, 그 결과를 rd에 저장. 이후 MSB는 0비트로 대체, LSB는 버려짐 (= 2n을 나누는 것과 같은 효과)
rs2의 값은 0~31: rs2에 있는 값의 하위 6비트만 사용
SRA: 똑같이 오른쪽으로 시프트 후 부호 비트로 채워줌
SLT(Set Less Than)는 signed comparision을 수행
SLTU(Set on Less Than Unsigned)는 unsigned comparision을 수행

```
ADDI rd, rs1, imm
``` ```
GPR[rd] <- GPR[sr1] + sign-extend (imm)
PC <- PC +4
```Arithmetic: {ADDI}
Compare: {signed, unsigned} x {SLTI(Set if Less Than)}
Logic: {ANDI, ORI, XORI}
Shift: {SLLI, SRLI, SRAI (Left, Right-Logical, Right-Arithmetic)}
여기서 R-type과 다른 점은 SUBI가 없다는 것인데 그 이유는 imm에서 음수 표현까지 지원하기 때문.
imm[11:0] = [-204810, 204710]




| 필드 | 비트 수 | 비트 |
|---|---|---|
| imm = -50 | 12 | 111111001110 |
| rs1 = 1 | 5 | 00001 |
| funct3 = ADD | 3 | 000 |
| rd = 15 | 5 | 01111 |
| opcode | 7 | 0010011 |
레지스터는 제한적이고, 데이터는 보통 큽니다.
산술 연산은 레지스터에서 수행되어야 하므로 "데이터 전송 명령어(data transfer instruction)"가 필요합니다.
LW rd, offset(base register)byte_address = sign-extend(offset) + GPR[base]
GPR[rd] <- Mem[byte_adress]
PC <- PC +4LD (Double word)
LW (word)
LWU (Unsigned word)
LH (Half word)
LHU (Half unsigned word)
LB (byte)
LBU (Unsigned byte)
여기서 Unsigned는 zero-extend를 뜻하며, 8bit를 불러 오고 나머지 비트는 0으로 채우는 것을 의미하며 signed는 부호 비트로 채우는 것을 의미합니다.


| 필드 | 비트 수 | 비트 |
|---|---|---|
| imm = 8 | 12 | 000000001000 |
| rs1 = 2 | 5 | 00010 |
| funct3 = LW | 3 | 010 |
| rd = 14 | 5 | 01110 |
| opcode | 7 | 0000011 |


SW rs2, offset(base register)byte_address = sign-extend(offset) + GPR[base]
Mem[byte_adress] <- GPR[rs2]
PC <- PC +4

| 필드 | 비트 수 | 비트 |
|---|---|---|
| imm = 8 | 7 | 00000000 |
| rs2 = 14 | 5 | 01110 |
| rs1 = 2 | 5 | 00010 |
| funct3 = sw | 3 | 010 |
| imm = 8 | 5 | 01000 |
| opcode | 7 | 0100011 |