본문 바로가기
컴퓨터

[컴퓨터 구조] Assembly language for MIPS instructions

by skyjwoo 2020. 4. 5.
728x90
반응형

instruction 이란?

간단히 말해서, cpu를 직접적으로 동작시킬 수 있는 명령어이다. 가장 아래 단계(lowest level)의 언어라 할 수 있으며, 우리가 말하는 '기계어'가 바로 이것이다. binary형태로 이뤄진다.(이진 수, 0100...) 우리가 작성한 python, C, JAVA 코드는 compiler, assembler를 거쳐 

 

cpu마다 instruction set이 존재하는데, 이게 다 다르다. 물론 유사한 형태를 갖는 instruction들도 있고 유사한 특징들을 공유한다. 그러나 완전히 같지는 않다. 이 처럼 특정 CPU 동작에 쓰이는 instruction들의 집합(set)을 ISA(Instruction Set Architecture)라 한다.  

 

그럼 이 instruction이 존재한다면 결국 이걸 코딩해야 하는데, 인간은 0100100...과 같은 숫자들을 다 기억해서 효율적으로 사용하지 못한다. 따라서 코딩의 편의성과 효율성을 위해 instruction들을 기억하기 쉬운 상징 형태로 만들었다. 이게 바로 assembly language(어셈블리어)이다. 어셈블리어(assembly language)는 어셈블러(assembler)를 통해 instructions(machine language, 기계어)로 번역된다. (본 글에서 결국 다루고 싶은 것은 instructions이다 그러나 이는 assembly 언어에 바로 대응될 수 있고,  또 숫자로 설명하는 것보단 단어로 설명하는 게 더 가독성이 높기에 instruction과 assembly language를 혼용해서 사용하도록 하겠다.)

 

오늘은 MIPS instructions를 다루기 위한 어셈블리어에 대해 알아보도록 할 것이다. 이에 앞서 한 가지 더 설명을 덧붙이자면, instructions는 일을 하기 위한 지침서 같은 거라면, 일을 행하는 대상이 있어야 할 텐데, 이를 데이터라 하자. 모든 코드가 그렇지만, 이 데이터를 다루기 위해서는 데이터가 저장될 공간이 있어야 한다. 그 공간 중 가상의 공간이 변수(variables)이고, 물리적인 실제 공간이 메모리나 하드 디스크이다. 프로세서 내부에 있는 저장 공간은 레지스터라 한다.[각주:1] 메모리에서 불러온 코드와 데이터를 이 레지스터에 잠시 저장하여 사용하는 것이다. 

 

MIPS는 32bit 레지스터를 쓰는데 다음과 같이 이뤄져 있고, 0~31번까지의 번호를 갖는다. 그러나 기계어와 마찬가지로 인간은 수를 기억하는 데 약하기에 기호로 바꿔서 주로 쓰인다.

 

 

 

자주 쓰이는 레지스터의 기호들 몇 개만 살펴보겠다.

  1. (8~15, 24, 25번 레지스터)\$t0 ~ \$t9: temporary values, 임시적인 값들을 저장, instruction 실행 도중에 쓰기 위해 잠시 저장해 두기 위한 장소
  2. (16~23)\s0~ \$s7: 메모리의 주소값 또는 값들이 저장됨. 여길 참조해 메모리에서 값을 가져오고 메모리의 해당 주소에 값을 저장한다. temporary값들보다 더 오래 저장되고 쓰인다.
  3. (31)\$ra: 함수를 call했을 때 return할 주소를 담고있다.
  4. (0)\$zero: 상수값 0

 

 

 

그럼 이제 본격적으로 MIPS에서 쓰이는 assembly language의 연산자들을 살펴보도록 하겠다. 연산자 뒤에 붙는 논항의 수와 역할들을 잘 살펴보면 좋겠다.

 

 

  • 수리 연산자(기본 사칙 연산들)

 

add \$t0, \$s1, \$s2 //\$t0 = \$s1+\$s2

sub \$t1, \$s3, \$s4 //\$t1 = \$s3 - \$s4

 

 

  • 메모리 연산자

 

A[3] = {1,3,5};

A -> \$s0 (A의 주소값이 s0에 저장되어 있다고 가정)

라고 할 때,

 

lw \$t0, 4(\$s0) //A[1]에서 값을 가져와 t0에 저장한다. 

sw \$t0, 8(\$s0) //t0의 값을 A[2]에 저장한다.

 

 

  • Immediate 연산자(즉치 연산자)

 

addi \$s0, \$s1, -2 (i는 immediate value, 즉치, 바로 쓸 수 있는 값, 뒤에 주로 실제 값이 온다.)// 

 

 

논리 연산자

or \$t0, \$t1, \$t2 //t1, t2를 or한 결과를 t0에 저장

xor \$t0, \$t1, \$t2 //t1, t2를 xor한 결과를 t0에 저장, xor는 exclusive or의 준말로, 서로 다른 숫자이면 1, 같은 숫자이면 0을 반환한다.

and \$t0, \$t1, \$t2 // t1과 t2를 and한 결과를 t0에 저장한다.

nor \$t0, \$t1, \$t2 //\$t1과 0을 or한 결과(결국 \$t1)를 뒤집는다(0->1, 1->0) == not \$t1과 같아진다. 즉, 이런 식으로 not을 표현할 수 있다. 

% not은 없다. nor로 대신 연산

 

 

  • control flow(흐름 제어)를 위한 instruction들-branch 류, jump 류

 

-조건 분기[각주:2](branch): 조건에 맞으면 분기, 즉 갈라진다.

beq \$t0, \$t1, L1 // t0과 t1이 같으면 L1으로 간다. branch on equal (L1은 코드에서 명시된 label을 말함, else같은 것이나 혹은 직접 명시된 것들..)

bne \$t0, \$t1, L1 // t0과 t1이 다르면 L1으로 간다. branch on not equal

bltz \$t0, L1 // t0가 0보다 작으면 L1으로 간다. branch less than zero

 

-비조건 분기(branch) 연산자

L1 // L1으로 점프

jr rs // 특정 register값으로 jump

 

 

  • Shift 연산자

 

bit를 shift(이동)시키는 것이다. 2진수 체계이기에 이를 통해 곱셈, 나눗셈의 효과를 얻는다. 마치 10진수에서 자릿수를 이동시키는 것과 같다. 10 -> shift left 1 -> 100 (10을 곱한 효과)

 

sll \$s1, \$s2, 2 //\$s1에 \$s2를 shift left 2 왼쪽으로 2칸 옮긴 값을 저장한다.  (shift left logical)      

srl \$s1,\$s2, 2 //\$s1에 \$s2를 shift right 2 오른쪽으로 2칸 옮긴 값을 저장한다. (unsigned 값에서만 쓰임, shift right logical)

sra \$s1, \$s2, 2 //\$s1에 \$s2를 shift right 2 오른쪽으로 2칸 옮긴 값을 저장한다. (shift right arithmetic)

 

shift right 연산이 2개가 구분되어져 있는 이유는 shift 연산에 따라 원하는 값이 나오지 않을 수 있기 때문이다.

특히 unsigned value이면 문제가 없지만, signed value 즉, 부호가 있는 값의 경우 문제가 생긴다. 예를 들어 1100$_{2}$(signed 표기에서 10진수로 -4)를 shift right 2한다고 하면, 0011$_{2}$(10진수로 3)이 될 것이다. 하지만 우리가 원하는 값은 -4/$2^{2}$인 -1(1111$_{2}$)이다. 이를 해결하기 위한 방법으로 shifting한 후 처음 부호 값을 채워준다. 즉, 0011이 아닌 처음 부호값인 1을 채워주어 1111$_{2}$(-1)이 되는 것이다. 이와 같은 방법이 적용된 연산자가 sra 연산자 이다.

 

 

  • Inequality 연산자

 

부등식 연산자.

set less than: 적으면 set한다(1로 만든다)

 

slt rd, rs, rt //rs<rt이면, rd=1, 아니면 rd=0

 

slti rt, rs, constant //rs<constant이면, rd=1, 아니면 rd=0, (constant는 상수)

 

sltu rd, rs, rt // sign부호도 하나의 값으로 처리하여 계산한다. 즉, unsigned라 가정하고 계산한다. 1111>0000, 나머지는 slt와 동일.

 

 

  1. 우리가 작성한 코드는 하드웨어에 저장되고, 실행하기 위해 메모리에 올라온 후 프로세서에서 필요한 코드들을 가져와 실행하게 된다. 그런데 이 과정에서 매번 메모리에서 불러오는 작업을 할 수 없으니, 프로세서 내부에도 저장을 위한 공간을 만들어 놓았다. 그게 바로 레지스터이다. [본문으로]
  2. 분기라는 말은 프로그램의 진행을 강이 흐른다고 보면 강이 갈라지는 것을 생각해 보면 된다. 예를 들어 if문의 경우 특정 조건에 따라 if문 내의 절이 실행되거나 else문의 절이 실행된다. 즉 코드의 흐름이 나뉘어지게 되는 것이다. [본문으로]
728x90
반응형

댓글