아아 진짜 오늘 너무 바보짓했다 ㅠㅜ

 

기계어/어셈블리어 파트 이론 마무리하고 과제하는데

 

이번 장의 과제가 어셈블리어로 곱셈 연산과

 

키보드, 화면 입출력 어셈블리어로 구현하는 예제인데

 

곱샘 연산 구현하는데만 4~5시간은 낭비해버리고 말았다.

 

// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/04/mult/Mult.tst

load Mult.asm,
output-file Mult.out,
compare-to Mult.cmp,
output-list RAM[0]%D2.6.2 RAM[1]%D2.6.2 RAM[2]%D2.6.2;

set RAM[0] 0,   // Set test arguments
set RAM[1] 0,
set RAM[2] -1;  // Test that program initialized product to 0
repeat 20 {
  ticktock;
}
set RAM[0] 0,   // Restore arguments in case program used them as loop counter
set RAM[1] 0,
output;

set PC 0,
set RAM[0] 1,   // Set test arguments
set RAM[1] 0,
set RAM[2] -1;  // Ensure that program initialized product to 0
repeat 50 {
  ticktock;
}
set RAM[0] 1,   // Restore arguments in case program used them as loop counter
set RAM[1] 0,
output;

set PC 0,
set RAM[0] 0,   // Set test arguments
set RAM[1] 2,
set RAM[2] -1;  // Ensure that program initialized product to 0
repeat 80 {
  ticktock;
}
set RAM[0] 0,   // Restore arguments in case program used them as loop counter
set RAM[1] 2,
output;

set PC 0,
set RAM[0] 3,   // Set test arguments
set RAM[1] 1,
set RAM[2] -1;  // Ensure that program initialized product to 0
repeat 120 {
  ticktock;
}
set RAM[0] 3,   // Restore arguments in case program used them as loop counter
set RAM[1] 1,
output;

set PC 0,
set RAM[0] 2,   // Set test arguments
set RAM[1] 4,
set RAM[2] -1;  // Ensure that program initialized product to 0
repeat 150 {
  ticktock;
}
set RAM[0] 2,   // Restore arguments in case program used them as loop counter
set RAM[1] 4,
output;

set PC 0,
set RAM[0] 6,   // Set test arguments
set RAM[1] 7,
set RAM[2] -1;  // Ensure that program initialized product to 0
repeat 210 {
  ticktock;
}
set RAM[0] 6,   // Restore arguments in case program used them as loop counter
set RAM[1] 7,
output;

사용한 테스트 스크립트는 이건데

 

 

ALU로 M=M+D 연산이 가능한걸 잊어버리고,

+1연산으로 이중루프를 만들어 곱셈 연산을 구현하느라

 

클럭 루프를 너무 많이 돌아버렸고, 

앞에 작은 수를 다루는 경우는 문제 없었지만

 

마지막 6 * 7 예제에서 210회 클럭안에 수행해야하는데

M=M+D 연산으로 하면 금방할걸

+1연산으로 구현해 놓으니 210회 클럭안에 연산을 마치지 못해서 자꾸 에러뜨길래

이게 라인 수가 너무 많아서 그런갑다 싶어 라인 수를 줄이느라 시간낭비했다.

 

그러다가 갑자기 +1 안해도 되는게 생각나서 했더니 20분도 안걸리고 해결했다 ㅜㅜ

아아아아아ㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏ

이거에서 해매지만 않았어도 진작에 입출력 제어 끝내고 다음 장으로 넘어갈텐데

 

곱셈 연산 해결한게 밤 11시 30분이라

입출력 헨들링까지 하고 글 정리하고 가면 좀 더 지나야 갈수있을거같다.

Mult_v1.asm Mult_v2.asm Mult_v3.asm
//210 클럭안에 6 * 7 곱연산을 마무리하지 못해 실패
@0 // init R2 as 0
D=A
@R2
M=D
@R0                  // if R0 or R1 == 0 -> end
D=M
@BIG_STOP
D;JEQ
@R1
D=M
@BIG_STOP 
D;JEQ
@j                    // init j(=R0)     -  j * i = result
M=1
(BIG_LOOP)        //--start of big loop--------------------------------
@j                    // make big loop end condition value
D=M
@R0
D=M-D
@BIG_STOP       // if (R0 - j < 0 )  goto BIG_STOP  r0 = 3, d = 4 => 3-4 < 0
D;JLT
@i
M=1
(SMALL_LOOP)   // -----------start of small loop--------------
@i                   //make small loop end condition value
D=M
@R1
D=M-D
@SMALL_STOP  // if (R1 - i < 0 )  goto SMALL_STOP r1 =5, i = 6    -> 5 - 6 < 0
D;JLT
@R2                 //small loop start
M=M+1
@i
M=M+1
@SMALL_LOOP // goto small loop
0;JMP
(SMALL_STOP)   // -----------end of small loop--------------
@j                   //start big loop
M=M+1
@BIG_LOOP
0;JMP               //goto big loop
(BIG_STOP)        //--end of big loop--------------------------------
@BIG_STOP
0;JMP
@R2 // init R2 as 0
M=0
@R1
D=M
@y // x * y = resuilt    <- R0 * R1 = R2, y = R1
M=D
@R0
D=M
@BIG_LOOP_END
D;JEQ
(BIG_LOOP_START)
@y
D=M
@R1
M=D
@R0
D=M
@BIG_LOOP_END
D;JEQ
(SMALL_LOOP_START)
@R1
M=M-1
@R1
D=M
@SMALL_LOOP_END
D;JLT
@R2
M=M+1
@SMALL_LOOP_START
0;JMP
(SMALL_LOOP_END)
@R0
M=M-1
@BIG_LOOP_START
0;JMP
(BIG_LOOP_END)
@BIG_LOOP_END
0;JMP
@R2
M=0
@R0
D=M
@END
D;JEQ
@R1
D=M
@END
D;JEQ
  (LOOP)
@R1
D=M
@R2
M=M+D
@R0
M=M-1
@R0
D=M
@END
D;JEQ
@LOOP
0;JMP
  (END)
@END
0;JMP

 

삽질하면서  HACK 어셈블리어 구현 연습한건 좋긴한데

이렇게 간단하게 할수있는걸 하루/4 시간을 낭비한게 너무 아깝긴하다 ㅜㅜ

마지막 Mult.asm이 마지막 구현 결과

 

 

gif 용량이 너무 커져서 0 곱셈 부분은 좀 자르고

활용한 CPU 에뮬레이터로 앞서 구현한 ALU와 메모리, 프로그램 카운터가 어떻게 동작하는지 알수 있다.

 

 

위에껀 너무 빠르니까

천천히 볼수있게 6 * 7부분만 캡처

 

 

두번째 과제로는

입출력 처리 어셈블리어 작성

1. 무한 루프를 돌며 키보드 입력을 받고, 입력되면 화면 전체를 꺼멓캐 한다.

2. 키를 놓으면 허옇게 하면된다.

 

 

이걸 어떻게 하냐

지난 글에 키보드, 스크린에 대해서 제대로 설명안했는데

각 입출력장치 메모리맵에다가 값을 읽고 써주면 될거같다.

 

키보드와 스크린은 어디있나 싶었는데

CPU 에뮬레이터에 상에 같이 있었다.

 

 

키보드 버튼을 누르면 키보드가 사용가능해져서

실제 키입력을 누르면 가상 머신에 반영되는거같다.

 

 

HACK 스크린

아무튼 HACK 컴퓨터의 스크린은

1. 256 x 512 = 2^8 * 2^9 = 2^17 = 131,072개의 픽셀을 가지고 있고

-> 레지스터가 16비트이고, 한비트가 한픽셀 값을 저장하니까 전체 픽셀을 16으로 나누면 8K의 RAM 공간이 사용된다.

ex) 131,072 / 16 = 2^17/2^4 = 2 ^ 13 = 8192 = 8K

 -> 전체 메모리 32K 중에 RAM이 16K니까, RAM 절반을 얘가 사용하네?

2. RAM의 16384번지에 있고, 이 베이스 주소는 SCREEN 이란 이름으로 미리 정의되어있다.

3. 보통 이미지 다룰때 처럼 베이스 번지가 0, 0을 나타낸다.

4. 이 화면은 그레이스케일이나 컬러가아닌 단순 흑백 화면으로 해당 픽셀에 1을 넣으면 검정, 0을 넣으면 흰색이된다.

5. RAM[SCREEN + row * 32 + col/16] : 1에서 말했듯이 레지스터에서 16개의 픽셀을 보관하고 있다보니, 픽셀 하나하나에 접근할수 없고, 접근하고자 하는 레지스터의 해당 픽셀 부분을 바꿔주면 될거같다.

 

 

 

스크린 예시1 ) 스크린 좌표 0,0 픽셀에다가 검정 표시하기

대강 스크린 베이스 레지스터에다가 1000 0000 0000 0000을 넣으면 될거같은데

A 명령어는 0부터 시작하고 값은 15비트밖에 못넣지 않았던가? 

 

아 다시 생각해보니까

A 명령어는 @SCREEN이니까 상관이 없네

 

그럼 1000 0000 0000 0000은 어떻게 만들어야 할지가 막막하다.

C명령어 테이블을 보면 C비트에다가 111010을 넣으면 -1이 나온다고 한다.

10진수 -1은   0000 0000 0000 0001에다가 2의 보수를 취해 구하면

-1 (10) = 1111 1111 1111 1111(2)이 된다.

 

우리가 구하고자 하는 값은 1000 0000 0000 0000이니

1. 어드레스 레지스터의 값은 2^15승이 최대이므로, A 명령어의 값에 2^16을 넣지 않고

2. 0111 1111 1111 1111를 컨트롤 비트로 -A든 -D든 해서

3. 목적지를 데이터 레지스터로 설정하여 담은 뒤

4. 스크린의 베이스 레지스터에다가 데이터 레지스터 값을 넣어보자

 

진짜 될진 모르겟네

0111 1111 1111 1111은

 

   1000 0000 0000 0000

-  0000 0000 0000 0001

2^16 - 1이므로 65,535가 된다.

 

@65535

D=-A

이걸 하려고 했지만 위 표를 보니 A 레지스터 최대값이 32767이라고 안돌아간다..

 

@32767
D=-A

으로 에뮬레이터에 돌려봤는데

 

데이터레지스터의 값을 2진수로 보니 내가 원했던 값이랑 거의 비슷하다?

그리고 왜 32767이 2^15 = 32768(10) = 0100 0000 0000 0000 이니까

32767의 1의보수 = 1011 1111 1111 1111이고

-32767 = 1100 0000 0000 0000 라 생각했지만

 

2진법 변환기에서 32767은 

0111 1111 1111 1111

32768이

1000 0000 0000 0000 이라고 한다.

 

 

오늘 곱샘연산에서 1/4나절 헛짓하고, 시간이 시간이라 그런가 잘 이해가 안된다

 

 

 

1 = 2^0

0000 0000 0000 0001

2 = 2^1

0000 0000 0000 0010

4 = 2^2

0000 0000 0000 0100

8 = 2^3

0000 0000 0000 1000

...

 

2^15

1000 0000 0000 000

 

하 이렇게 2진법을 쓰니까 이해가간다

2^0 = 1이 LSM이니 0에서 부터 시작해서 16비트의 MSB가 2^15 위치인데

 

2^15=32,768이고

A 명령어의 맨 앞비트만 사용 불가하니

최대 가능한 값은 0111 1111 1111 1111 = 32,768 -1 = 32,767이니까

위의 코드가 동작해서

 

-A 연산한 결과가

-32,767이 1000 0000 0000 0001이 나왔다!

 

내가 원하는 건 0, 0픽셀만 출력하면 되니

1000 0000 0000 0000으로 만들어주기 위해

-A 한 결과에다가 -1을 해주면 된다.

 

@32767
D=-A
@SCREEN
M=D-1

 

픽셀 하나만 찍다보니 뭐가 바뀌었나 싶긴한데 잘보면

화면 좌측 상단 쪽에 M=D-1 연산한 뒤에 아주 작은 검은점 하나 생겼다.

 

 

 

 

스크린 예시2 ) 첫째 행만 검은줄로 채우기

첫째 행만 검은줄을 채우기 위해 네 가지를 기억하면 될거같다.

1.  메모리 레지스터 하나당 16비트를 가지고 있어 16개의 픽셀 값을 표현한다.

2. 1111 1111 1111 1111은 -1이다.

3. 스크린의 열 길이가 512픽셀이니 512/16 = 32개의 픽셀에다가 -1을 채우면 된다.

4. @SCREEN 그러니까 스크린 베이스 어드레스는 16384이므로 16416번지까지 -1을 넣어주자

 

 

금방 할줄 알았는데 생각보다 머릴 좀 써야된다.

앞에서 어셈블리어 연습 많이 했다고 생각했는데 값을 넣었다 뺏다만 했지

값 + 1은 몰라도 주소 + 1은 안해서 어떻게 하나 고민했는데

 

 

ㅋㅋㅋ 진짜 이것도 결국에 검색해서 알았는데

꼭 A명령어 1번, C 명령어 1번 같은 식으로 할 필요가 없고,

A명령어 1번, C명령어 2번이든 여러번 하는게 가능했다.

 

A명령어

C명령어 각각 한번씩만 하면

 

도저히 RAM[A+1]에 접근할 방법이 생각이 안났는데

A=100일때, A=A+1을 시켜놔도

 

@3

M=D를 하려고하면 A의 값이 3으로 바뀌어져

아무리 생각해도 101번 번지에 담을수가 없었기 때문이다.

 

잠깐 찾아봤더니 이게 가능한거더라 .

@x
A=M+D
M=-1

 

이러면 나머지 문제도 금방 풀수 있을거같다.

 

 

 

@16416
D=A
@dest
M=D
@SCREEN
D=A
@cur_addr
M=D
    (LOOP)
@cur_addr
A=M
M=-1
@cur_addr
M=M+1
D=M
@dest
D=D-M
@LOOP
D;JLT
     (END)
@END

 

 

 

 

 

 

벌써 시간이 2시 넘었네 ;;

 

나머지는 얼른 대충 구현하고 가야겠다.

 

 

하도 앞에서 삽질을 많이 하는 바람에 

실제 과제는 금방 끝냈다.

 

 

두 번째 과제 : 어셈블리어로 키보드 스크린 입출력 제어 

1. 키보드 입력 받으면 화면 꺼멓게

2. 키보드 입력이 없으면 화면 허옇게

 

@INIT
0;JMP
     (INIT)
@SCREEN  //start screen addr
D=A
@cur_addr //cur screen addr
M=D
@8192      //total screen register num
D=A
@SCREEN
D=M+D
@end_addr // end screen addr
M=D
     (LOOP)
@KBD
D=M
@BLACK     // when key is pressed, val is greater than 0 
D;JGT
@WHITE
0;JMP
     (WHITE)
@cur_addr
D=M
@SCREEN
D=D-M
@LOOP
D;JEQ        // cur_addr ==screen  dont ram[cur_addr]=1 and  cur_addr -= -1
@cur_addr
A=M
M=0
@cur_addr
M=M-1
@LOOP
0;JMP
     (BLACK)
@cur_addr
D=M
@end_addr
D=D-M
@LOOP
D;JEQ         // cur_addr == end__addr dont ran[cur_addr]=-1 and cur_addr+1
@cur_addr
A=M
M=-1         // ram[cur_addr] = -1
@cur_addr
M=M+1      // cur_addr += 1
@LOOP
0;JMP

 

캡처한 gif는 너무 느려서 커지는 바람에 용량 줄인다고 반토막 냈다

동작은 잘되는데

하드웨어 시뮬레이션이라 그런가 클럭 속도 제한때문에 그런건지

아니면 내가 한번에 모든 픽셀을 바꿀줄 몰라서 그런걸까 

생각보다 반영되는게 좀 많이 느리다

 

 

 

 

새벽까지 하긴 해버렸지만 거의 이틀만에

난드투 테트리스 기계/어셈블리어 파트를 마무리했다 

대충 책보는거랑 정리하는거랑 걸리는 시간 차이가..

 

내일 5장 얼마나 진행할수 있을까 ㅋㅋㅋㅋㅋㅋ

+ Recent posts