명령어 구조
opcode, funct7, funct3 field
> 명령어가 어떤 동작을 하는지 명시하는 부분 // 통칭 opcode
rs1,rs2,rd field
>레지스터 번호를 명시 ... rs1 > 첫 번째 피연산자 레지스터 , rs2 > 두 번째 피연산자 레지스터 , rd > 목적지 레지스터
레지스터 파일에 있는 레지스터이 개수가 32개 이기 때문에 rs1, rs2, rd 모두 5-bit로 이루어져 있다.
(32 = 2^5)
(32 = 2^5)개 레지스터 중 1개를 명시하려면 5-bit가 필요하기 때문이다.
imm(immediate)은 명령어에 포함된 상수로 = 즉치값
- R-format instruction의 경우 레지스터에서 3개의 피연산자를 모두 가져오지만,
I-format instruction의 경우, 한 개의 피연산자를 instruction 자기 자신에게 저장할 수 있다. - 그 값을 immediates라고 부른다. 왜? 바로 instructions에서 바로! 접근 가능하기 때문이다.
register나 memory access를 필요로 하지 않는다. - 12 bit의 immediate field는 2의 보수를 활용할 경우 -2^11 ~ +2^11-1 의 범위까지 저장 가능하다.
- 컴퓨터 산술의 하드웨어 설계에서 2의 보수를 활용함으로써, unsigned number와 signed number의 덧셈 뺄셈을 편리하고 간단하게 할 수 있다.
소프트웨어 프로그램
>> 조건에따라 (조건없이) 데이터 연산 혹은 처리를 하는것
CPU는 최소한 3가지 종류의 명령어를 제공
(1) 데이터처리 명령어(data processing instruction)
>> 산술연산, 논리연산 etc
(2)메모리접근 명령어(memory access instruction)
>> CPU가 데이터를 메모리에서 레지스터로 가져오는 명령어, 그리고 레지스터 값을 메모리로 쓰기 위한 명령어
(3)분기 명령어(branch instruction)
>>if for while 조건문 , 함수 호출
RV32I 데이터 처리 명령어
< 산술명령어 >
(1) R-type
add rd,rs1,rs2 // opcode 51 0 0
sub rd,rs1,rs2 // opcode 51 32 0
(2) I-type
addi rd,rs1,imm
ex) addi a5, a5, -12 // 'a5 + (- 12)(=x15 + (-12))' 덧셈을 하여 a5(=x15) 레지스터에 저장하는 명령어
// opcode 19 funct3 0
cf) -12는 imm12에 어떤 형태로 들어갈까?
> 음의 정수를 2의 보수체계를 사용해 표현한다.
'-12'를 2의 보수로 표현한 값이 imm[11:0]에 들어간다.
자세히 설명하면 '+12'fmf 12-bit로 표현하면 이진수로 0000_0000_1100이며
이 값을 bitwise inverting ( 0 > 1 , 1> 0)하고 1을 더하면 1111_1111_0011 +1 을 하면 이진수로
1111_1111_0100 이다 즉 imm[11:0]에는 '-12'를 2의 보수로 표현한 값인 16진수로 '0xFF4가 들어간다.
따라서 addi a5, a5, -12 명령어는 16진수로 0xFF47_8793이 된다.
cf) 즉치값의 범위 >> +2^11 -1 ~ -2^11 why? > imm[11:0] 11은 MSB
명령어 실행과정
>> sub 에서 13 -27 = -14 인데 -27을 2의 보수로 바꿔 덧셈 해준다.
<addi 명령어의 실행과정>
addi a5, a5, -12
(1) a5 에서 x15 (그냥 rs1 x15부터 시작한걸로 정해짐) x15에 27이 들어있네
(2) -12 >> 0xFF4 12-bit 형태 >> 이대로는 ALU에서 연산 불가
왜냐하면 32-bit CPU에서는 연산의 기본 단위가 32-bit이기 때문 , >> ALU 입출력 크기가 모두 32-bit
>>12-bit immediate을 32-bit로 만들어 줘야한다.
방법 (1) sing-extension (2) zero-extension
(1) sing-extension : 12-bit에서 sign 역할을 하는 MSB를 확장되는 위쪽 20-bit에 모두 채워 넣는다.
cf) MSB (most significant bit)
> MSB, 즉 Most Significant Bit는 이진(binary) 표현에서 가장 중요한 비트를 나타냅니다. 이진수는 0과 1로 구성되어 있으며, 각 비트는 특정 가중치를 가집니다. MSB는 이 가중치 중에서 가장 큰 값을 가지는 비트입니다.
예를 들어, 8비트 이진수를 생각해보겠습니다.
이 이진수에서 MSB는 가장 왼쪽에 있는 비트인 1입니다.
이 이진수의 각 비트의 가중치는 다음과 같습니다.
MSB인 1은 가장 왼쪽에 위치하고, 가장 큰 가중치인 128을 가지고 있습니다.
MSB의 위치와 가중치는 이진수의 값에 큰 영향을 미칩니다. 예를 들어, MSB가 1이면 해당 이진수는 128이상의 값이라는 것을 의미하며, MSB가 0이면 128보다 작은 값임을 나타냅니다.
(2) zero-extension
>확장되는 위쪽 20-bit을 무조건 0으로 채워넣는다.
어떤 경우에 방법이 달라지는가??
>> 확장하려는 수의 데이터 타임에 따라 결정한다.
확장하려는 수를
signed 정수로 생각하면 sign-extension
ex) 0xFF4 > singed 정수로 생각하면 -12 >> 0xFFFF_FFF4
unsigned 정수로 생각하면 zero-extension
0xFF4를 > zero로 생각하면 0x0000_0FF4가 되며 이 값은 +4084이다.
addi 명령어에 포함된 imm[11:0]에는 signed 정수가 들어간다.
< 논리 명령어 >
>> 논리 명령어는 source operand를 bit 별로 연산한다.
(1) AND 연산 source aperand(rs1,rs2)의 특정 위치에 있는 bit들을 빼내고 싶을 때 사용할 수 있다.
ex) andi x3,x2, 0x0FF명령어는 첫번째 rs인 x2에 있는 마지막 byte를 빼내 x3레지스터에 저장한다.
AND연산은 I/O 장치에 있는 레지스터를 읽은 후 확인하고 싶은 bit만을 빼낼 때 유용하게 사용될 수 있다.
OR 연산은 특정 bit만 1로 만들고 나머지 bit들은 그대로 유지하고 싶을 때 사용할 수 있다.
ex) ori x3, x2,0xFF 명령어는 x2 레지스터에 있는 맨 아래 byte만 모두 1로 만들고 나머지 상위 3-byte는 그대로 유지한 값을 x3 레지스터에 저장한다.
XOR 연산은 (1) source operand의 값을 반전 시킬 때 유용하다
> 두 번째 입력 값이 1이면 Y는 첫 번째 입력을 반전 한 값이 된다.
ex) xori x3,x2, 0xFF명령어는 첫 번째 source operand에 있는 마지막 바이트를 반전시킨다.
그 결과가 x3레지스터에 저장된다.
둘 째, 레지스터를 0으로 초기화할 때 유용하다.
xor x3 ,x2 ,x2 명령어를 실행하면 그 결과 x3은 0이 된다.
>> 두 입력이 같을 때 출력은 0이 되기 때문이다.
논리연산 , 산술연산 구분을 어떻게 해?
따라서, 명령어 이름과 해당 명령어가 수행하는 연산을 보고 산술 연산인지 논리 연산인지를 판단할 수 있습니다. 그럼에도 불구하고, 이 두 연산 유형은 모두 I-type 명령어 형식을 사용하기 때문에 구분하기 위해 명령어의 이름과 해당 연산의 의미를 함께 고려해야 합니다.
shift 명령어의 동작
> 첫 번째 source operand를 두 번째 source operand에 명시되어 있는 shift amount 만큼 shift 하면 된다.
Shift amount는 R-type 명령어에서는 두 번째 source operand인 rs2 레지스터의 마지막 5-bit에 명시되며
I-type 명령어에서는 명령어에 포함된 5-bit에 명시된다.
5-bit를 사용하는 이유는 shift amount로 0 ~ 31까지 명시 할 수 있기 때문이다.
32-bit 레지스터에 있는 값을 31-bit로 초과해 shift 하는 것은 별 의미 없는 연산이다.
32-bit 레지스터에 있는 모든 값이 shift되어 남아 있지 않게 되지 때문이다.
(1) logical shift (2) arithmetic shift
그리고 각각에 대해 왼쪽(left) , 오른쪽( right) 방향으로 shift 할 수 있다. 총 4가지 shift가 있다.
1. Logical Shift
- 왼쪽 로직 쉬프트 (Left Logical Shift): 각 비트를 왼쪽으로 이동시키고, 오른쪽에는 0을 채웁니다.
- 오른쪽 로직 쉬프트 (Right Logical Shift): 각 비트를 오른쪽으로 이동시키고, 왼쪽에는 0을 채웁니다.
2. Arithmetic Shift
- 왼쪽 산술 쉬프트 (Left Arithmetic Shift): 각 비트를 왼쪽으로 이동시키고, 오른쪽에는 0을 채웁니다.
- 오른쪽 산술 쉬프트 (Right Arithmetic Shift): 각 비트를 오른쪽으로 이동시키고, 왼쪽에는 최상위 비트(Most Significant Bit, MSB) 값을 유지합니다.
이렇게 총 4가지 방향으로 쉬프트가 가능합니다.
왼쪽 로직 쉬프트와 산술 쉬프트의 차이
왼쪽 로직 쉬프트의 경우, 두 연산 모두 같은 결과를 생성합니다. 왜냐하면 왼쪽으로 쉬프트할 때, 오른쪽에 채워지는 값이 항상 0이기 때문입니다. 따라서, 산술 쉬프트의 경우도 왼쪽으로 쉬프트할 때 동일한 결과를 생성합니다.
하지만 오른쪽으로 쉬프트할 때, 두 연산 사이에는 차이가 있습니다. 산술 쉬프트는 최상위 비트(MSB) 값을 유지하며 쉬프트하는 반면, 로직 쉬프트는 항상 0을 채웁니다.
예를 들어, 8비트 숫자 11010010를 오른쪽으로 쉬프트한다고 가정해보겠습니다.
- 산술 쉬프트: 11101001
- 로직 쉬프트: 01101001
산술 쉬프트에서는 MSB(최상위 비트) 값인 1을 유지하고, 로직 쉬프트에서는 오른쪽에 0을 채웁니다. 이렇게 오른쪽 쉬프트에서 두 연산 사이에 차이가 발생합니다.
따라서 SLL,SRL,SRA 세 종류의 명령어를 제공한다.
sra s3, s1 ,s4
s3 = s1 >>> s4[4:0]
(x19 = x6 >>> x20[4:0])
rs1 (Source Register 1) - 6
- 레지스터 번호: 6
- 레지스터 이름: s1
rs1은 산술 쉬프트를 수행할 값이 저장된 레지스터를 가리킵니다. 이 경우 s1 레지스터에 저장된 값이 산술 쉬프트의 대상 값이 됩니다.
rs2 (Source Register 2) - 20
- 레지스터 번호: 20
- 레지스터 이름: s4
rs2는 산술 쉬프트할 비트 수가 저장된 레지스터를 가리킵니다. 여기서 s4 레지스터에 저장된 값은 산술 쉬프트할 비트 수를 나타냅니다.
rd (Destination Register) - 19
- 레지스터 번호: 19
- 레지스터 이름: s3
rd는 산술 쉬프트 연산의 결과를 저장할 레지스터를 가리킵니다. s3 레지스터에 산술 쉬프트 연산의 결과 값이 저장됩니다.
결과적으로, s1 레지스터에 저장된 값을 s4 레지스터의 하위 5비트 값만큼 오른쪽으로 산술 쉬프트하여 그 결과를 s3 레지스터에 저장하는 연산을 수행한다는 것을 의미합니다.
srai s3, s1, 4
- s1: 산술 쉬프트를 수행할 원본 값이 저장된 레지스터입니다.
- 4: 이 값은 산술 쉬프트할 비트 수를 나타냅니다. SRAI 명령어의 즉시 값으로 사용되므로, 이 값은 0부터 31까지의 산술 쉬프트할 비트 수를 나타냅니다.
- s3: 산술 쉬프트 연산의 결과를 저장할 레지스터입니다.
결과적으로, s1 레지스터에 저장된 값을 오른쪽으로 4비트 산술 쉬프트하여 그 결과를 s3 레지스터에 저장하는 연산을 수행한다는 것을 의미합니다.
이 SRAI s3, s1, 4 명령어는 s1 레지스터의 값을 오른쪽으로 4비트 산술 쉬프트하여 그 결과를 s3 레지스터에 저장하는 연산을 수행합니다.
'RISC-V CPU 설계 > 개념 정리' 카테고리의 다른 글
[RISC-V] 4.1 조합회로 (1) | 2024.05.11 |
---|---|
교과블록 4주차 개념정리(3장-4~ 3.5) (0) | 2024.04.21 |
교과블록 2주차 발표 (3장) (0) | 2024.04.10 |
교과블록 2주차 발표준비 (1장) (0) | 2024.04.10 |