본문 바로가기

RISC-V CPU 설계/개념 정리

교과블록 3주차 공부

명령어 구조

 

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입니다.

이 이진수의 각 비트의 가중치는 다음과 같습니다.

Copy code
1 1 0 1 0 1 1 0
 
128 64 32 16 8 4 2 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 명령어 >
 

 

 

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 레지스터에 저장하는 연산을 수행합니다.