저번에 이어서 다른 명령어들의 필드를 알아보도록 하겠다. 더하고 빼고 불러왔으면 이제 논리를 이용해야지 않겠는가? if문 써야지!!! 바로 알아보자
논리 연산 명령어는 비트별로 계산을 위해서 존재한다. Word내에서 비트를 추출하거나 삽입하는(and, or) 방식으로 용이하다.
자리이동 명령어라고 한다. 우리가 흔히 쓰는 비트 이동 연산자이다. 종류는 두가지가 있다.
R포맷을 따르므로
와 같이 명령어 필드를 갖는다.
왼쪽으로 자리이동 시키고, 빈칸은 0으로 채움
sll을 i비트만큼 하는것은, 2^i 로 곱하는 것
ex)
0000 0110(6)
을 왼쪽으로 2비트 옮겨보자 - >0001 1000(24)
가 되었다. 즉 6에서 2비트를 움직였더니 24가 되었다. 2^2배가 된것!
오른쪽으로 자리이동 시키고, 빈칸은 0으로 채움
srl을 i비트만큼 하는것은, 2^i 로 나누는 것 (부호 없는 경우에만)
ex)
0000 1000(8)
을 오른쪽으로 2비트 옮겨보자 - >0000 0010(2)
이 되었다. 즉 8에서 2비트를 움직였더니 1가 되었다. 2^2만큼 나눠지게 된것!
and $t0, $t1, $t2
와 같이 쓴다. and를 사용하게 되면 내가 원하는 위치의 비트만 수정하고 다른 비트는 유지할 수 있다. 주로 비트를 삭제(0으로 바꿈)를 할수있다.
ex)
$t20000 0110
에서 1번째 비트만 0으로 바꾸고 싶다면?
$t10000 0100
을 and 연산시켜준다.
or $t0, $t1, $t2
와 같이 작성하며, and 연산과 같이 원하는 부분에 비트를 세트(1로 바꿈) 시킬 수 있다.
ex)
$t20000 0110
에서 4,5,6,7비트를 1로 바꾸고 싶다면?
$t11111 0000
을 or 연산시켜준다.
MIPS는 not
이 없고, nor
이 있다.
어 그러면 not 연산 어케함??
nor
게이트만으로 not
연산을 구현할 수 있다. nor
게이트는 입력값들에 대한 OR 연산 후 그 결과를 반전시키는 연산을 수행한다. 이는 not
연산과 동일한 결과가 나온다! 따라서 not
연산을 별도로 구현할 필요 없이 nor
연산만으로 'not
의 역할을 대신하여 단순성
을 높인거다.
ex)
1.0000 0011
을not
연산 시키고싶다.
2.0000 0000
과or
연산하면0000 0011
이 나온다. 이를 반전시키면
3.1111 1100
이 최종적으로 나오게 된다.
ex2)
1.0000 0011
에서 0,1,2번째 비트만 반전하고 싶다.
2.1111 1000
과or
연산하면1111 1011
이 나온다. 반전시키면?
3.0000 0100
으로 0,1,2 번째 비트만 반전된 결과가 나온다.
nor $t0, $t1, $zero #zero 레지스터는 항상 0값을 가진다.
분기 명령어는 특정한 조건을 만족하는 순간 lable 된 위치로 분기 하도록 작동한다. branch 된다고 얘기한다. 만약에 a==1을 만족하면 명령어를 실행해야 하지 않겠는가? 그 명령어의 위치로 이동하도록 하는 MIPS 명령어다.
위와 같이 두가지 종류가 있다.
beq rs, rt, L1
If (rs == rt), L1레이블이 있는 명령어로 분기;
bne rs, rt, L1
If (rs != rt), L1 레이블이 있는 명령어로 분기;
조건부 분기 명령어는 두가지로 '같거나' '같지 않거나' 를 참으로 삼는 명령어다.
beq
= branch if equal 의 약자로 조건이 참일경우 labled된 명령어로 분기한다.
bne
= = branch if not equal 의 약자로 조건이 참일경우 labled된 명령어로 분기한다. 여기서의 조건의 참은 !=
이다. 주의하도록!
j L1
무조건 L1 레이블이 있는 명령어로 분기
JUMP TO L1 이라는 뜻이다. 정말 간단하게 L1으로 이동하라는 뜻...
레이블은 컴파일러가 생성한다.
그다음 어셈블러에서 2진수로 바뀐다.
C code:
if (i==j) f = g+h;
else f = g-h;
//f, g, … in $s0, $s1, …
Compiled MIPS code:
bne $s3, $s4, Else
add $s0, $s1, $s2
j Exit
Else: sub $s0, $s1, $s2
Exit: …
C언어로 작성된 코드는 i==j
라면 참 명령어, 아니면 else문을 실행하도록 짜여있다.
그렇다면 우리는 조건이 참이 아닐때, 즉 i not equal j
일때 else로 jump하도록 명령어를 작성할수있다.
bne $s3, $s4, Else
-> if($s3!=$s4) jump to Else
로 해석할 수 있다.
만약에 i==j
를 만족해서 bne
를 건너뛴다면, $s0 = $s1 + $s2
를 실행한뒤 j Exit
를 실행한다. j Exit
가 없다면 Else 레이블을 또 실행하게 되기 때문에 탈출을 꼭 해주어야 한다.
C code:
while (save[i] == k) i += 1;
// i in $s3, k in $s5, save의 베이스 레지스터는 $s6
Compiled MIPS code:
Loop: sll $t1, $s3, 2 (수치 곱셈 명령어는 없음)
add $t1, $t1, $s6
lw $t0, 0($t1)
bne $t0, $s5, Exit
addi $s3, $s3, 1
j Loop
Exit: …
sll $t1, $s3, 2
이 명령어를 통해서 i의 값을 시프트 시켜 2^2 곱을 해준다. 즉 $s3 * 4 연산과 같다.
왜 이런 연산을 함???
MIPS에서는 간접 주소 지정 시, 워드 단위로 주소를 지정하기 때문에, 배열 요소에 접근하기 위해 이러한 곱셈이 필요하다. 만약에
i=2
라고 가정해보자. 그럼 인덱스는 2이기 때문에0,1
두 배열을 건너뛰어야 한다. 그렇다면 실제 기본주소에서 2배열을 건너뛰기 위해서는2Word
를 건너뛰어야 한다. 왜냐하면 메모리 저장 단위는1Word
이기 때문이다. 하지만 메모리 주소의 단위는바이트
기 때문에 기본주소에2word
를 더하는게 아닌, 즉1word = 4byte
이기 때문에8바이트
를 더해야 하며, 이는i값
(2로 가정중)의 4배이다. 따라서2bit left shift
를 통해서 바이트 단위 변환을 해준다고 생각하면 된다.
add $t1, $t1, $s6
이 명령어는 $t1
레지스터의 값에 $s6
레지스터의 값을 더한다. 즉 인덱스 만큼 이동한 값을 베이스 레지스터 주소만큼 이동하여 상대 주소를 나타낸다. 따라서 $t1
은 우리가 원하는 주소로 완성된다.
lw $t0, 0($t1)
$t1
레지스터에 저장된 주소에서 워드(4바이트)를 로드하여 $t0
레지스터에 저장한다.
0($t1)
는 $t1
이 가리키는 주소에서 0바이트 떨어진 위치, 즉 $t1
의 주소 그 자체를 의미한다. 오프셋은 꼭 작성해야 하기에 이전 포스팅에서 말한것처럼 0 offset
을 더해주면 된다.
bne $t0, $s5, Exit
$t0
와 $s5
레지스터의 값을 비교한다. 만약 두 값이 다르면 조건이 참이 되어, Exit 레이블로 분기(점프)한다. While
탈출을 위해, 특정 조건($t0 != $s5) 하에서 루프를 빠져나와 프로그램의 다음 부분을 실행하게 된다.
이 명령어는 $s3
레지스터의 값에 즉시값(immediate value) 1을 더하여 결과를 다시 $s3
에 저장한다. i+=1
을 수행하기 위해 작성한다.
이 명령어는 Loop 레이블로 무조건적으로 점프한다. 이는 루프의 시작 부분으로 돌아가 다시 조건을 검사하고 코드를 실행하게 한다.
이 부분은 실제 MIPS 명령어가 아니라 루프를 빠져나와 실행을 계속할 위치를 표시하는 레이블이다.
간단한 루프 하나 돌리는데 알아야 할게 이렇게 많았다니...
하지만 loop문을 통해서 나온 명령어들의 필드를 스스로 확인해보고 적용해보고, 또한 어떤 로직이 사용되었는지 이해한다면 뒤에서 학습할 더 복잡한 연산을 이해하는데 도움이 될 것이다. 숙제는 명령어들의 포맷과 명령어 필드에 대응되는 레지스터 알아오기.