명령어의 종류는 무척 다양하다. 연산을 하는 명령어, 데이터를 불러오거나 저장하는 명령어, 분기하는 명령어 등 수행해야하는 명령어의 종류는 다양하다. 하나하나 알아보자.
R 포멧에서 R은 레지스터의 약자이며 산술 연산을 하는 명령어이다. 우리가 어셈블리로 명령어를 적을 때는
ADD X9 X10 X11
이렇게 저장할 레지스터, 피연산 레지스터 2개를 순서대로 적었지만 포멧에서는 그 순서가 아니다.
차례차례 알아보자면, opcode는 명령어 코드를 의마혹 Rm과 Rn은 각각 두번째, 첫번째 레지스터를 의미한다. Rd는 연산 결과를 저장하는 도착지 레지스터이다.
shamt는 나중에 shift연산을 할 때 사용되는 레지스터이다. shift연산도 R포멧이므로 여기에 들어가 있다.
Rm, Rn, Rd 각각 5비트씩인 이유는 레지스터가 총 32개이기 때문이다.
데이터를 저장하거나 불러올 때는 D포멧의 명령어를 사용한다. 각 필드에 대해서 알아보자.
Rt는 데이터를 저장하는 곳이고 Rn은 불러오거나 저장하고자하는 데이터의 시작 주소를 의미한다. address는 그 주소로부터 얼마나 떨어져있는 지를 의미하여 단위는 doubleword이다.
연산 중에서도 Immediate를 사용하는 연산들은 I포멧이다. Rd는 연산 결과를 저장하는 필드, Rn은 피연산자 필드, immediate는 연산을 위해 사용되는 상수 필드이다.
지금까지 알아본 3가지 타입에 해당되는 명령어들이다.
논리 연산자도 있다.
LSL과 LSR에서 LS는 Logical shift의 약자이다. shift 연산을 잘 생각해보면 1비트씩 옮기면은 2를 곱하거나 나누는 것이 된다. 그래서 shift연산을 이용해서 곱셈, 나눗셈을 할 수도 있다.
다음은 분기를 하는 연산자이다. 아주 간단한 예가 if-else문이다. 특정 조건에 맞으면은 if문을 수행하고 그렇지 않으면은 else문을 수행하는 경우에는 중간에 명령어들을 뛰어넘어야하는 경우가 있다. 이런 경우에 명령어를 뛰어넘는 것을 분기라고 한다. 그 분기에 조건이 있으면 조건부 분기(Conditional Operation)인 것이다.
위 그림에서는 3가지 종류의 분기 명령어가 나오는데 CBZ는 Compare Branch if zero의 약자로, 만약 0이면은 분기하라는 뜻이고 CBNZ는 0이 아니라면 분기하라는 뜻이다. 어디로 분기할 것인지는 뒤에 Label이 붙어서 구분할 수 있다.
분기의 예제이다. i와 j가 같으면은 덧셈을 다르면 뺄셈 연산을 하는 코드를 컴파일링해보자. 먼저 i가 저장된 x22와 j가 저장된 x23을 뺀다. 그리고 그 값을 x9에 저장하고 그 값이 0인지 아닌지를 CBNZ를 이용해서 판단하고 분기한다. 분기를 하면 else문으로 이동하고 아니라면 B 명령어를 이용해서 종료하는 Exit로 분기한다.
다음은 loop문을 컴파일링한 예제이다.
먼저 i값은 index이기 때문에 save
에서 얼마나 떨어져있는지 실제 주소를 계산하기 위해 LSL연산을 한다. 이렇게해서 i에 8을 곱한 다음에 x25에 save
의 주소가 저장되어 있으니 x10에 저장된 offset과 더한다. 그리고 그 데이터를 LDUR연산자로 가져온다. 이러면 이제 save[i]
에 접근했다.
그 다음 save[i]와 k를 빼고 그 값을 CBNZ 명령어를 통해서 0인지 아닌지 판단하고 분기할지 말지를 결정한다. 조건을 만족하지 못하면 i에 1을 더하고 Loop로 분기한다.
basic block의 정의는, 명령어를 entry에 들어오면 무조건 해당 명령어를 다 수행하기 전에는 빠져나올 수 없는 명령어의 단위를 basic block이라고 한다.
그래서 밑에 모든 블럭은 각각 실행을 하다가 중간에 빠져나갈 구멍이 없고 무조건 실행을 하면 실행을 마쳐야만 다음 명령을 실행할 수 있기 떄문에 basic block이지만
좀 더 크게 basic 블럭을 정의한다면(스코프를 기준으로) branch가 2개이므로 2개라고 볼 수도 있따.
++
basic block은 하드웨어를 설계하는데에 최적화를 고려하는 최소의 단위이다.
조건문 연산에는 등호만 있는 것이 아니라 부등호도 있다. <와 > 등 부등호 연산도 있고 또 그 과정에서 signed인지 unsigned인지도 연산의 과정에 영향을 준다. 어떻게하면 부등호 연산을 수행할 수 있을까?
overflow와 carry의 큰 차이점은 signed이나 unsigend이냐 또 연산의 종류이다.
이 4가지의 Condition Codes를 통해서 우리는 부등호 연산을 수행할 수 있다.
이런 4가지의 flag(condition codes)를 생성하는 산술 연산자는 기존의 산술 연산자 뒤에 S-suffix가 붙은 경우이다.
예를 하나 들어서 a와 b의 크기를 비교한다고 해보자. a가 1이고 b가 -4이고 3비트 체계라면은 a는 (001)이고 b는 (100)이다. 그래서 a-b를 하면은 a + (-b) 이므로 (001) + (100) 이므로 결과는 (101)이다. 실제 연산값은 5인데 3비트 체계이므로 5를 표현할 수 없다. 이 경우에는 overflow가 발생해서 V가 1이 된다. 또한 연산 결과에서 MSB가 1이므로 N도 1이다 그리고 연산결과가 0이 아니기 때문에 Z는 0이다. 연산 결과는 > 임을 표를 통해 알 수 있다.