메모리 개요
메모리는 프로그램의 명령어와, 그 명령어가 사용할 데이터가 들어있는 공간이다.
(폰 노이만 구조에서는 프로그램의 명령어도 데이터이다.)
저장 단위는 byte 단위이며, 레지스터와 달리 4byte로 고정되지 않고 1, 2, 4, 8 byte 등으로 유동적인 사이즈로 저장할 수 있다.
하지만 레지스터가 한번에 4byte 씩 파악하기 때문에, 명령어의 사이즈는 4byte 로 고정되어있다.
메모리 접근
메모리에 접근하여 데이터를 읽고 쓰는 명령어는 기본적으로 아래와 같은 형태를 띈다.
Load, 메모리의 데이터를 불러와(loading) 레지스터에서 읽는 것
ld [메모리주소], 레지스터주소
메모리 -> 레지스터 순서로 읽으면 되니 이해가 간단하다.
메모리주소는 R + A 형태로 표현되고, [ ] 는 C언어의 * 처럼 해당 주소 메모리에서 값을 읽어오는 명령어이다.
R 은 메모리의 주소를 저장하고 있는 '레지스터'
A 는 (-2^12) ~ (2^12-1) 범위의 상수 또는 레지스터값이다.
단, 주의할 점은 반드시 R + A 형태로 표현이 되어야 한다.
ld [R+A], S
R = register
A = constant or register / 생략 가능
ex)
ld [%l0 + %o0], %l2 (o)
ld [%l0 + 100], %l2 (o)
ld [%l0 - 4], %l2 (o, %l0 + (-4) 와 같다.)
ld [%l0], %l2 (o, %l0 + 0 과 같다.)
ld [%l0 - %l1], %l2 (x, 메모리 주소에 2의 보수를 취해 더하는 것은 지원하지 않는다. 음수 주소는 말이 안되기 때문)
** 이건 되네, R + A 의 위치가 고정될 필요는 없는지 질문해보자.
ld [10 + %l0], %l2 (x, R 위치에는 상수가 올 수 없다.)
Store, 레지스터의 데이터를 메모리에 저장(Store) 하는 것
st 레지스터, [메모리주소]
레지스터 -> 메모리 순서로 읽으면 되니 역시 이해하기 쉽다.
여기서도 메모리 주소는 R + A 형태로 나타난다.
st S, [R+A]
R = register
A = constant or register / 생략 가능
아주 간단한 예제
메모리의 %fp - 4 주소에 10을 저장하고, 그 값을 다시 레지스터에 불러오는 아주 간단한 코드이다.
하나 눈여겨 볼 점은 -4 + %fp 의 형태로 사용하는 것이 가능하다는 것이다.
즉, R + A 에서 A + R 순서로 써도 상관없다. 어차피 더하기만 하면 되기 때문이다.
하지만 이렇게는 안된다.
레지스터의 값을 바로 - 할 수는 없다.
메모리 크기와 메모리 주소 사이의 관계
메모리의 주소값은 할당할 메모리 크기와 관련이 있다.
만약 4 byte ( 1 word ) 사이즈의 메모리에서 데이터를 읽어온다고 해보자.
그렇다면 컴퓨터는 메모리 주소에서부터 4byte 만큼 총 32개 비트를 읽어 하나의 데이터로 본다.
즉, 메모리 주소는 데이터를 읽을 시작점이 되는 것이다.
따라서 4 byte 크기의 데이터를 저장하는 메모리의 주소는 항상 4의 배수여야 한다.
1002 위치는 4의 배수가 아닌 2의 배수다.
따라서 이 위치로부터 4byte 사이즈의 데이터를 읽으려고 할 때는 에러가 발생한다.
컴퓨터는 데이터의 크기 배수만큼의 시작점만 이용한다.
아까 서두에서 메모리에 담을 수 있는 데이터의 크기는 1, 2, 4, 8 로 다양하다고 하였다.
이 경우에도 마찬가지이다.
1 byte 의 경우에는 아무데나 저장할 수 있고
2 byte (half word) 의 경우에는 2의 배수인 메모리 주소에 저장할 수 있다.
8 byte (2 word) 의 경우에도 8의 배수인 메모리 주소에 저장할 수 있다.
데이터 사이즈 / 타입별 Load, Store 명령어들
먼저 Load 경우부터 정리해보자.
Load 할 때는 1/2/4/8 바이트 사이즈의 데이터를 고정된 4 바이트 사이즈 레지스터에 넣어야 한다.
4바이트는 레지스터에 그대로 넣으면 되고, 8바이트는 레지스터 2개에 저장하면 되니 문제가 없다.
하지만 1, 2 바이트 사이즈는 레지스터에 저장해도 공간이 남는다.
이 때 남은 공간을 어떻게 처리하는 지는 signed, unsigned 데이터 여부에 따라 달라진다.
1, 2 바이트 데이터는 레지스터의 낮은 비트부터 채워서 저장하고,
나머지 높은 비트는 signed 이면 데이터의 MSB 비트로 채우고, unsigned 이면 0으로 채운다.
이를 Sign Extension 이라고 한다.
ldsb | load signed byte, (load 1 byte signed data) |
ldub | load unsigned byte, (load 1 byte unsigned data) |
ldsh | load signed half word, (load 2 bytes signed data) |
lduh | load unsigned half word, (load 2 bytes unsigned data) |
ld | load word, (load 4 bytes data) |
ldd | load double words, (load 8 bytes data) |
이렇게 메모리의 데이터를 레지스터의 LSB 쪽부터 채워나간다.
이번엔 load 를 한번 살펴보자.
load 는 4바이트 레지스터의 값을 메모리에 옮기는 과정이다.
만약 1byte 만 저장하는거면 레지스터의 LSB 쪽 1바이트만 메모리에 저장하는 거고
만약 2byte 만 저장하는거면 레지스터의 LSB 쪽부터 2바이트만 메모리에 저장하고
만약 4byte 만 저장하는거면 레지스터 값을 그대로 메모리에 저장하고
만약 8byte 만 저장하는거면 레지스터 2개의 값을 그대로 메모리에 저장하면 된다.
이때 주의할 점은 레지스터의 4byte 사이즈가 그대로 메모리에 옮겨가기 때문에,
1byte만 저장하더라도 메모리에는 4byte의 값을 쓰는 것과 같지만, 실제로 사용하는 의미있는 값은 낮은 비트의 값만 유효하다고 볼 수 있다.
stb | store 1 byte (store low 1 byte of register) |
sth | store half word (store low 2 bytes of register) |
st | store word (store 4 bytes of register) |
std | store double word (store 8 bytes from 2 registers) |
이때 std 명령어의 경우, 2개 레지스터를 읽기 때문에 반드시 짝수번째 래지스터를 할당해야 한다.
그리고 메모리에서 8바이트 사이즈 데이터를 읽으므로 아까 말한대로 메모리 주소는 8의 배수여야만 한다.
이 코드의 19번째 줄을 보면 홀수번째 레지스터에 메모리 값을 load 하고 있다.
이 경우 어셈블은 문제없이 되지만
이렇게 실행중 오류가 발생한다.
이렇게 짝수 레지스터로 바꾼 뒤 다시 실행해보면
이렇게 레지스터 2개에 동시에 데이터가 잘 들어갔음을 알 수 있다.
이것으로 메모리와 레지스터 사이 데이터를 주고받는 명령어들을 정리해보았다.
다음 글에서는 SPARC 메모리 맵에서 Static 영역, 즉 어셈블 하는 시점에서 미리 값을 할당해두는 정적 메모리 영역을 사용하는 방법을 정리해보겠다.
'CS > 어셈블리' 카테고리의 다른 글
[SPARC] 23. label로 data 영역의 메모리주소 가져오기 (set, sethi) (0) | 2023.11.09 |
---|---|
[SPARC] 22. 정적 메모리와 경계정렬 (0) | 2023.10.20 |
[SPARC] 20. switch - case 구현하기 (0) | 2023.10.18 |
[SPARC] 19. Delay Slot Optimization (0) | 2023.10.17 |
[SPARC] 18. Branch Delay Slot 의 발생 이유 (0) | 2023.10.17 |