공사중

Qtspim 이해하기 본문

카테고리 없음

Qtspim 이해하기

행운개발자 LuckyDeveloper 2019. 3. 5. 21:35

Spim? Qtstpim?

Spim은 assembly 코드를 읽고 실행하는 simulator입니다. spim은 binary 코드를 읽지 않는다는 점이 중요합니다. 그리고 가장 최신 버전의 spim을 Qtspim이라고 부릅니다. 다른 버전과 다르게 window, Mac os, linux에서 같은 코드, 같은 interface로 실행합니다. 다른 버전은 여전히 사용가능하지만 maintain되거나 update 되지 않습니다.

Getting started with Qtspim

[00400000] 8fa40000  lw $4, 0($29)            ; 183: lw $a0 0($sp) # argc 

위 사진에서 표시된 각 줄은 각 명령어에 대한 설명입니다.

  1. square brackets[]안에 들어있는 숫자는 16진수의 명령어 주소를 나타냅니다
  2. 두 번째 숫자는 16진수로 표현된 instruction의 numeral encoing입니다.
  3. 세 번째는 명령어의 연상기호mnemonic 표기입니다.
  4. ; 다음에 오는 코드들은 assembly file안에 적혀있는 actual line입니다. 이 line에 의해서 명령어가 실행된 것입니다.
  5. 만약 ; 뒤에 아무것도 없다면 pseudoinstruction을 MIPS instruction으로 변환시킬 때 두 개 이상의 명령어로 변환된 것을 의미합니다.
파일을 업로드하고 버그가 났을 때는 두 가지 방법으로 debugging을 할 수 있습니다. Step by Step으로 실행을 해보거나, break point을 걸어서 실행을 해보는 것입니다. break point를 걸고 실행을 하면 해당 지점까지 실행하고 이후의 명령어를 실행할 것인지 물어보는 pop up 창이 나타납니다.

Qtspim은 내장 edito가 없어서 문제점을 발견한 뒤에는 sublime text나 notepad++같은 editor를 켜서 코드를 수정해야합니다.

그리고 수정된 코드를 다시 올려서 돌려보기 전에 반드시 아래와 같이 reinitialize를 해야합니다.


Tutorial

1. 아주 잘 설명된 pdf 파일은 여기에 있습니다.
2. tutorial의 코드를 보다가 잘 이해가 안되는 연상기호mnemonic들은 여기에서 ctrl+F를 해서 검색해보면 설명이 나옵니다. 

3. 위의 pdf 파일에 대한 한글 설명은  여기에서 들을 수 있습니다.

4. 영상의 내용 중 이해하고 memory layout에 대한 부분만 그림으로 가져왔습니다.


Modes of Addressing Data

우리는 memory에 있는 데이터를 가져와서 cpu의 register에 저장해서 연산을 해야합니다. 여기서는 어떻게 memory에 있는 데이터에 접근해서 register에 저장하는지에 대해서 알아봅니다. 먼저 아래와 같은 명령어가 있습니다. (x는 register에, y는 memory에 있다고 가정합니다. )

ADD x, y
  1. Register mode : Register holds the data.  register에 저장되어있는 x를 refer하는 mode입니다.
  2. Direct Addressing : register는 데이터가 저장되어있는 주소를 바로/직접적으로 holds합니다.
  3. Indirect Addressing: data는 memory에 저장되어있습니다. 이 주소를 메모리의 다른 곳이 가지고 있습니다. 다시 이 곳의 주소를 register가 가지고 있습니다.
  4. Index mode : R + x(index) = EA(Effective address)
  5. Index Deffering mode : R + x(index) = Address of Address of data
  6. Auto increament mode : register contains address of data. But after use, the contains will be incremented. To point next instruction.
  7. Auto decreament mode : 6.의 반대
  8. Immediate mode : register constains instruction. (register는 memory주소 1000을 가리키고 memory의 1000에는 opcode, 1001과 1002에는 operand 각각 하나씩 들어있는 경우)
  9. Relative mode : PC(program counter) holds the address of instruction to be fetched
  10. (각 모드에 대한 설명은 위 링크 ~29:35초)
R1은 register에 있는 데이터를 직접적으로 가지고 있고, R2는 memory에 있는 데이터의 주소를 가지고 있다고 해보겠습니다. 이때 각각의 addressing mode를 0과 1이라고 표시합니다. 그리고 ADD를 하는 opcode를 000이라고 합니다. R1과 R2가 가리키는 데이터의 합을 하는 명령어는 아래와 같이 쓸 수 있으며, 최종적으로 데이터가 저장되는 곳은 R2가 가리키는 주소의 메모리 위치 입니다.

[operator][Source data ] [Destination data ] [op code] [mode] [source Register] [mode][destination Register] 000 0 R1 1 R2

문제를 풀어보겠습니다.

문제에서 나온 n-operand instruction에서 n은 0,1,2를 의미할 수 있습니다. 순서대로 피연산자가 0,1,2개인 명령어 구성을 나태냅니다.

opcode 4bits로 나타낼 수 있는 명령어의 수는 2^4= 16개입니다. 첫 줄에서 2개의 피연산자를 가지는 명령어를 사용한다고 했으니 opcode로 나타낼 수 있는 16개의 명령어 중 12개를 사용했습니다.


이제 남은 4개로 피연산자 1개를 가지는 명령어를 30개를 구성해야합니다. 그런데 피연산자가 1개만 필요하기 때문에 Destination data 4bits만 사용하면 되고 source data의 4bits opcode의 확장으로 사용할 수 있습니다. opcode로 나타낼 수 있는 남은 16-12=4개 중에서 2개만 사용하면 30개의 피연산자 1개를 가지는 명령어를 구성할 수 있습니다. (4개 중 2개) * (source data 4bit로만든 2^4 =16개) = 32개로 30개 이상의 피연산자 1개를 가지는 명령어를 만들 수 있기 때문입니다.


이제 남은 2개의 opcode 영역 * source data 영역 2^4개 * destination data 영역 2^4개 = 총 2^9개의 no-operatnd instruction을 실행할 수 있습니다. 

Function Call : parameter, return value

.data:
.text:
	main:
		addi $a1, $0, 50   #by convention $a* is argument
		addi $a2, $0, 100

		jal addNumbers

	li $v0, 10
	syscall

addNumbers:
	add $v1, $a1, $a2  #by convention $v1 is return value
	jr $ra
관습적으로 register a는 param으로, $v0과 $v1는 return value로 사용됩니다. j나 jal를 한다고 register의 값이 변하는 것이 아니기 때문에 레지스터들을 구분해서 사용함으로서 param와 return value를 사용할 수 있는 것입니다.
j는 그저 jump로서 다른 instruction으로 이동하는 것이고, jal는 jump and link입니다. link는 $ra라 불리는 return address를 남기고 j를 하는 것으로 함수의 호출 이후에 돌아올 주소를 미리 저장해두는 것을 의미합니다. 일반적으로 $ra는 함수가 호출된 바로 다음 address를 가리킵니다.


아래 내용은 정말 좋은 tutorial을 찾아서 해당 키워드만 제시하는 형태로 저장해놓습니다. 여기의 part 7 부분입니다. 


 

위 문서의 내용을 참고할 때기억해야할 점은 다음과 같습니다.  As always (for these notes), each item on the stack is four bytes long.

$sp란? http://chortle.ccsu.edu/assemblytutorial/Chapter-25/ass25_3.html

"The picture shows a stack of MIPS full words. The stack pointer register $sp by convention points at the top item of the stack. The stack pointer is register $29. The mnemonic register name $sp is used by the extended assembler."

push란? http://chortle.ccsu.edu/assemblytutorial/Chapter-25/ass25_4.html

By software convention, $sp always points to the top of the stack. Also by convention, the stack grows downward (in terms of memory addresses). So, for our stack of 4-byte (full word) data, adding an item means subtracting four from $sp and storing the item in that address. This operation is called a push operation.

pop이란? http://chortle.ccsu.edu/assemblytutorial/Chapter-25/ass25_5.html

Removing an item from a stack is called a pop operation. In the real-world analogy an item is actually removed: a dish is physically moved from the stack. In a software stack, "removal" of an item means it is copied to another location and the stack pointer is adjusted. When a software stack is popped, does the popped item remain in memory? Yes. The data is copied to a new location, but the old location is not changed. However, since the stack pointer is moved, "logically" the data is no longer on the stack.

j란?

At right is a sketch of what you can do with the j instruction. (The same could be done with the b instruction.) If the main routine needs to start up ("to call") a subroutine sub it can jump to it with a j instruction. At the end of the subroutine, control can be returned with another j instruction. http://chortle.ccsu.edu/assemblytutorial/Chapter-26/ass26_2.html

$ra란?

The register that is used for linkage is register $31, which is called $ra by the extended assembler. It holds the return address for a subroutine. The instruction that puts the return address into $ra is (usually) the jal instruction.

Register 별 역할

$t0 - $t9 — The subroutine is free to change these registers. $s0 - $s7 — The subroutine must not change these registers. $a0 - $a3 — These registers contain arguments for the subroutine. The subroutine can change them. $v0 - $v1 — These registers contain values returned from the subroutine.

branch delay?

http://chortle.ccsu.edu/assemblytutorial/Chapter-26/ass26_4.html

How continual subroutine works? : http://chortle.ccsu.edu/assemblytutorial/Chapter-27/ass27_4.html

In the Simple Linkage convention of the previous chapter, registers $s0―$s7 must not be altered by a subroutine. But this restriction creates a problem when subroutines call other subroutines. Say that main calls subA and that subA calls subB. subA can't save any values in $s0―$s7 (because it is not allowed to alter them). But any values it saves in $t0―$t9 might be clobbered by subB (because subB is allowed to alter them). In effect, subA can't use any registers! Not good. The solution is to allow subA to use $s0―$s7. However, before using one of these registers, subA must save its value on the stack. Later, when subA returns to its caller, it must restore the register to its initial state.

Stack-based Linkage Convention

$fp란? http://chortle.ccsu.edu/assemblytutorial/Chapter-28/ass28_1.html

A stack frame is the section of the run-time stack that holds the data of a subroutine. In a high-level language a local variable is implemented as a location on the run-time stack. Each time a subroutine is activated, new locations for variables are pushed onto the stack. The section of the stack for each activation is called a stack frame or an activation record. A frame pointer holds the address of the stack frame for a subroutine.
A variable is a location in the run-time stack that is used to store data. The values stored in a variable may change as the program executes. The variable is the space in the stack, not a register.
Register $30 is reserved, by software convention, for use as a frame pointer. In the extended assembler it has the mnemonic name $fp. When a subroutine starts running, the frame pointer and the stack pointer contain the same address. But the stack (and the stack pointer) may be involved in arithmetic expression evaluation. This often involves pushing and popping values onto and off of the stack. If $sp keeps changing, it would be hard to access a variable at a fixed location on the stack. To make things easy for compilers (and for human assembly language programmers) it is convenient to have a frame pointer that does not change its value while a subroutine is active. The variables will always be the same distance from the unchanging frame pointer.

추가적인 convention 내용은 http://chortle.ccsu.edu/assemblytutorial/Chapter-28/ass28_6.html를 참고. 

$sp와 $fp 차이점

$sp : stack pointer
$fp : frame pointer 


우리는 subroutine call할 때마다 sp = sp - ((push할 지역변수의 수 * 4byte) 해서 stack에 공간을 만든다. 그런데 sp는 데이터를 pop/push하면서 sp의 위치가 변경될 수 있다. 따라서 메모리의 특정 위치에 있는 데이터를 가져올 때 계속해서 변하는(변할 수 있는) sp를 기준으로 할 때는 원하는 데이터와 $sp의 거리가 일정하지 않다.($sp가 이동하면 4byte 떨어져있던 데이터가 8byte떨어진 상태로 변할 수 있다.) 그래서 처음 subrountine이 call 되었을 때 이동시키는 $sp의 값을 $fp도 갖게하여 4($fp) 또는 8($fp)와 같이 참조할 때 항상 데이터가 $fp로부터 같은 거리에 있게할 수 있다.