지난 글에서 컴퓨터 HACK을 만들기 위해서

 

1. CPU

2. ROM 32K

3. Screen

4. Keyboard

5. RAM

6. Computer

 

이렇게 6가지로 나누어서 설명했다.

 

 

1. CPU 구현하기

CPU는 ALU와 세 레지스터 - 어드레스 레지스터, 데이터 레지스터, 프로그램 카운터로 만들면 된다. 각각이 뭐하는건지는 직접 만들기도 하고 수차례 시뮬레이션도 돌렸으니 넘어가고 고맙게도 아래의 그림처럼 연결하면 된다고 알려준다. 동작 과정은 다음과 같다.

 

 1) 명령어 해독 : CPU 입력 - 명령어 instruction은 A 명령어(MSB가 0, op-code라고도 했던거같은데), C 명령어(MSB가 1일때) 올수 있고, C 명령인 경우 111accccccccdddjjj 6개의 제어 비트 ccccccc와 a로 선택된 연산을 수행하고, ddd로 지정한 곳에 연산 결과를 저장한다. jjj가 000이 아닌 경우 연산 결과에 따라서 jjj의 조건(0과같거나, 크거나, 작거나 등)에 따라 어드레스 레지스터에 입력된 명령어 주소로 점프한다.

 

 2) 명령어 실행 : 들어온 명령어가 A 명령어인 경우 A 레지스터에 담는다. C 명령어의 경우 해독된 명령어의 연산을 수행한다.

 

 3) 명령어 가져오기 : 명령어가 실행되면 프로그램 카운터는 다음에 실행할 명령어의 주소를 준비하고, 현재 명령어가 끝나면 PC에 지정된 주소의 명령어가 실행된다. 하지만 앞서 보았듯이 "@LOOP\n 0;JMP" 처럼 라벨 심볼로 점프할때는  어드레스 레지스터 A에 담겨진 라벨 심볼 (LOOP)의 주소로 점프하여 명령어를 수행하게 되고, PC도 이를 따라가 다음 실행할 주소를 담아서 수행한다. 

 

이제 CPU 구현에 필요한 내용은 대강 정리했는데

어디부터 시작하는게 좋을지가 고민이다.

프로그램 카운터나 각 먹스, 레지스터에는 제어 비트를 어떻게 넣어야 하는건지

 

 

 위 그림을 보고 사용해야할 회로들의 인터페이스를 정리해보자

CPU = ALU + D/A registor + PC +(Mux 16 2개)

1. ALU

- 입력 : 16비트 x(D), y(M/A),   제어 비트 6개

- 출력 : 상태 출력 비트 2개 zr ng, alu 연산 출력 16비트 outM

2. PC

- 입력 : 16비트 입력(다음 실행 주소 A-점프시),  제어 비트 3개(reset, load, inc-이 순서대로 조건에따라 수행) 

- 출력 : 제어 비트에 따라 리셋시 0, 로드시 점프할 주소, 증산시 다음 명령어, 이도저도 아닐땐 상태 유지

3. Regsitor

- 입력 : 16비트 입력 in, 로드 제어비트 load

- 출력 : 현재 값 out, 입력 로드시 out[t+1] = in[t]가 된다.

4. MUX 16

- 입력 ; 16비트 a, b

- 출력 : sel이 0인경우 a, sel == 1일때 b

 

CPU

- 입력 : inM(데이터 메모리에서 읽어온 값), instruction(현재 실행할 명령어)

- 출력

 outM : RAM[addressM]에 쓰여진 결과

 addressM : 값이 쓰여질 주소, A 레지스터의 출력

 writeM : 메모리에 쓸지 읽을지 여부

 pc : 다음 명령의 주소

 

 

 

1.1 PC 구현하기

1. reset 입력 : PC의 제어비트 같은 경우에는 reset을 바로 연결시켜주면 될거같기는 한데 ..  

2: inc 입력 : inc는 reset이 0이 아니면 1이니까 Not(in=reset, out=resetNotOut) 해서 넣어주면 될거같다.

3. load 입력 : load 의 경우에는 명령어의 j 비트가 000인 경우에만 점프하는게 아니니까 3bit or한 결과가 0이면 load는 0, 1이면 1로해야겠다. 일단 instruction의 LSB 3비트만 받아서 or 연산 후 jmp123or을 출력하여 load 자리에 넣는다고 생각하고 이 이름을 입력받도록 하자.

4. in 입력 : A 레지스터의 출력을 써야하니 일단 A 레지스터를 어떻게 할지 생각하지는 않았지만 aRegistorOut이란 이름으로 해놓자.

5. 출력 : PC의 출력은 pc란 이름으로 출력되어야한다.

 

일단 프로그램 카운터 쪽 파트는 이렇게 정리했다.

    //PC PARTS
    Or(a=instruction[0], b=instruction[1], out=jmp12or);
    Or(a=jmp12or, b=instruction[2], out=jmp123or)
    Not(in=reset, out=resetNotOut)
    PC(in=aRegistorOut, load=jmp123or, inc=resetNotOut, reset=reset, out=pc);

 

 

 

 

 

1.2 A registor 구현하기

가장 먼저 PC를 만들면서 A의 입력을 받도록 했었으니까 이번에는 A 레지스터를 한번 보자

A Registor는 일단 좌측의 Mux16의 결과를 받아 우측의 Mux16과 PC로 보내고 있으며, 제어비트 c가 0인지 1인지에 따라 값을 읽기만 하거나 저장을 한다.

1. in 입력 : in의 경우 좌측 먹스의 출력을 받으니 일단 leftMuxOut 정도로 해놓자

2. 출력 : A 레지스터의 출력은 우측 먹스의 입력으로, PC의 입력으로도 사용하는데 일단 aRegistorOut 정도로 해놓자.

3. c 입력 : A 레지스터에 값을 쓸지 말지 여부는 명령어가 A 명령어인지 C 명령어인지에 따라 결정되었었다. 라벨 심볼이든, 변수든, 상수가 오던간에 A 명령어 이므로 instruction의 MSB를 보고 0이면 c에는 1, MSB가 1이면 c에는 0을 놓도록 Not 게이트(출력명은 opcodeOut)를 추가하여 연결해보자.

    //A registor PARRTS
    Not(in=instruction[15], out=opcodeOut);
    //if opcode is 0 (== A instruction) -> opcodeOut = 1 -> load = 1
    Register(in=leftMuxOut, load=opcodeOut, out=aRegistorOut);

+ 오류 발견1 ) D registor 구현 중 A에 로드해야하는 경우 추가 발견

A 레지스터에는 A 명령어가 들어올때 외에도 C 명령어일 때, 첫번째 d비트의 값이 1인 경우 instruction[5]==1, 연산 결과를 A 레지스터에 담는다. 명령어가 C 명령어고 첫번째 d비트가 1인지 여부 ((instruction[15] && instruction[5]) 한 결과를 loadA라 하면, opcodeOut과 loadA 둘중 하나가 1이면 1이되도록 or 연산을 한 후 aRegistorLoad 라는 이름으로 전달해주자.

    //A registor PARRTS
    Not(in=instruction[15], out=opcodeOut); // A instruction == store to A registor
    And(a=instruction[15], b=instruction[5], out=loadA); //if instruction is C, store outM to A registor
    Or(a=opcodeOut, b=loadA, out=aRegistorLoad);
    //if opcode is 0 (== A instruction) -> opcodeOut = 1 -> load = 1
    Register(in=leftMuxOut, load=aRegistorLoad, out=aRegistorOut);

 

 

 

1.3 D registor

이번에는 D registor를 구현해보자. D 레지스터는 A명령어로 입력하지는 못하고, C 명령어의 ddd 값에 따라서 대입하도록 되어있었다. 그러면 D 레지스터에 입력하는 경우는 어떤 경우가 있었나?

 위 표를 보면 2번째 d비트가 1일때만 D 레지스터에 입력하도록 되어있었다. 그러니까 A D M 순서니까 instruction[5], instruction[4], instruction[3] 순이 되겠다. 근데 지금보니까 d의 첫번째 비트가 1인 경우에도 A 레지스터에 값을 저장하도록 하고 있다. 그러니까 A 명령어인 경우 외에도 instruction[5] == 1일때도 A 레지스터의 load = 1이 되도록 해줘야 하는거같다. 일단 처음에 쓴 부분 뒤에다가 추가로 표기해놔야겠다.

 

1. in 입력 : D Registor는 ALU의 연산 결과를 담을수 있으므로 outM을 연결해주면 될거같은데, outM은 외부로 나가므로 루프할수 없으니 outMLoop 라는 루프 출력을 만들어서 in에다가 넣자.

2. c 입력 : 앞서 설명한것 처럼 명령어가 C 명령어이고 5번째 비트가 1일때 c에 넣도록 구현하자.

3. 출력 : A 레지스터때 처럼 dRegistorOut 정도로만 하자.

    //D registor PARTS
    And(a=instruction[15], b=instruction[4], out=loadD); //if instruction is C and dest is d, store outM to A registor
    Register(in=outMLoop, load=loadD, out=dRegistorOut);

 

이제 PC, A, D, 레지스터는 전부 구현했고, ALU 하기전에 먹스부터 정리해보자.

 

1.4 좌측 MUX16(한참 삽질하여 뒤에 다시 정리함)

 일단 좌측 먹스부터 생해보자. 왼쪽 먹스의 경우에는 a 자리에 ALU의 출력 outM, 이건 루프가 안되니 D 레지스터의 입력으로한 outMLoop를 사용하고, b 자리에는 instruction을 그대로 넣어주면 될거같다. MUX16은 sel이 0일때 a, 1 일때 b를 내보냈었는데 평소에는 A 레지스터에 A 명령어를 넣고, 아니다 C instruction[5]가 1일때만 outM을 A 레지스터에 넣었으니 이걸 sel 기준으로 잡으면 될거같다.

 

 먹스는 sel이 0일때 a를 출력으로 하지만, C instruction[5] == 1일때 A레지스터에 저장해야하니 Not(C instruction[5]) 한것을 sel에 넣어야 alu의 출력이 A 레지스터로 넣어지고, C instruction[5]가 0이라면 not 연산으로 sel에 들어가는 값이 1이되고 instruction이 A 레지스터로 전달 되겠다.

 

 잠깐 A 레지스터는 값과 주소를 저장한다 했는데, 값은 지금까지 한걸보면 A 명령어와 C 명령어의 목적지에 따라 ALU의 결과를 값으로 넣어주도록 했다. 하지만 C 명령어인데 instruction[5]가 아닌 경우는 A registor에 넣어도 되는건지 햇갈리기 시작했다. 

 

 지난번에 ALU, PC 구현할때는 완벽하게 동작을 이해하지 않더라도 표만 보고, 동작 조건만 따라서 연결만 해줬어도 어떤 흐름인지 따라가지는 못하지만 원하는 동작을 하기는 했었다. 지금도 A 레지스터에 먹스 a를 넣는 조건을 찾았으니 전처럼  해야할까? 그냥 그랬다가는 나중에 놓친 부분이 있으면 엄청 해맬거같은데 고민된다.

 

 C 명령어가 (A=)가 아닐때 잠깐 곱셈 어셈블리어로 CPU 에뮬레이터를 보니

 

@1     //이때 A에는 1이 들어가고,    현재 1번지의 값은 20이다.

D=M   //그러면 M에는 20이 들어갈 것이고, A 레지스터는 명령어를 저장하는게 아니라 기존의 1을 유지해야한다.

D=M 연산한 결과 A 레지스터는 1로 그대로 유지되고 있고,

D의 값이 RAM[1]의 값으로 덮어씌워졌다.

 

아 지금 보니까 생각난게 이래서 A 레지스터의 load를 조절해줘야 되구나.

아까 수정한 A 레지스터에는 C 명령어이고, instruction[5]가 1인 경우에만 load=1이 되도록 했으니

어셈블리어에서 A=이 아닐때는 load에 0이들어가 기존 값을 유지하는게 된다.

 

그리고 위에 사진에는 @22에 노란줄이 되어있는데 이 명령어는 A 명령어이고, A 레지스터는 순차 논리 회로이므로 다음 클럭때 A는 22로 반영이 될거같다.

실제로 A 레지스터의 값이 22로 변했다. 

그런데 아직 ALU아 하단 입력 M/A가 20 그대로 유지되고 있다.

현재 22이니까 22이여야 하는데 안바뀐건 시간이 안지나서 그런걸까?

아니면 우측 먹스에서 inM이 들어와 아직까지는 아래에 20이 들어가는거같다.

8번 명령어는 값을 저장하는 A 명령어라 M이 반영이 안된건지

 

 

아 다시 처음부터 보니까 이해가 되는게

내가 그동안 노란줄을 현재 실행한 명령어로 오해하고 있었다. 실제로는 프로그램 카운터가 가리키는 주소라

다음에 실행하는 명령어인데도

다시 이 코드를 한번씩 실행하면

 

 

PC가 0일때 모두 0이지만, 0번 명령어를 실행하는 즉시 PC는 1, A에는 A 명령어를 통해 2가 저장된다. 

registor(load=1) out(t+1) = in(t) 이었으므로 아직 ALU의 M/A 입력이 0이고,

다음 클럭때(PC가 2를 가리킬때) 2를 출력하게 된다.

 

 

일단 오케이 아까보다는 PC니 A 레지스터 값 저장이니 더 이해된거같네

아까 햇갈렷던 A 레지스터의 값이 22인데 ALU에 반영안된 이유는

현재 실행한 명령어는 8번 명령어라 A에는 즉시 저장되었지만 출력이 아직 안되었기 때문이고

8번 시점에 M/A 입력이 20인건, 7번 시점에서는 A가 1이지만 우측 먹스에 의해서 inM이 전달되서 그런거같다.

 

좌측 먹스를 정리하면서 좀 많이 해매버렸는데, A에 A 명령어든 C 명령어 연산 결과를 저장하든 말든간에 우측 먹스에 의해서 alu 아래 단자 값이 정해지니 이게 중요한거같다.

 

먹스 하나가 내용 정리하는데 어쩌다 보니 다른 회로 합친거보다 길어졌다.

 

 

잠깐만 다시 정리하자

 

outMLoop와 instruction을 A 레지스터로 저장 조건과 전달 조건

 

A 레지스터 저장 조건

1. 명령어가 A레지스터인 경우 - instruction을 저장한다.

2. 명령어가 C 명령어이고, instruction[5] = 1인 경우 - outMLoop를 저장한다.

 

 

A 레지스터 전달 조건

1. 명령어가 A레지스터인 경우 - instruction을 저장한다.

 -> 좌측 먹스로 명령어를 전달한다.

2. 명령어가 C 명령어이고, instruction[5] = 1인 경우 - outMLoop를 저장한다.

 -> 좌측 먹스로 outMLoop를 전달한다.

3. 그외 경우 : C 명령어이나 instruction[5] = 0인 경우

 -> 저장 하지 않아 기존의 저장된 값을 출력하므로 무관하다.

 

 그러므로 ((instruction[5] == 1) && (instruction[15] == 1)) ==1일때만 outMLoop를 A 레지스터로 전달하도록 하고

그외는 명령어를 전달하자.

 

그러면 이제 좌측 먹스 구현 조건을 다 정리된거같다.

    //left Mux16
    And(a=instruction[5], b=instruction[15], out=isOutMLoop)
    Mux16(a=instruction, b=outMLoop, sel=isOutMLoop);

 

 

 

하.. 오늘 안에 다 구현할수 있을줄 알았는데

밤에 좀 자고 너무 놀아서 결국에 5장도 마무리 못했다.

매 챕터가 다 금방 끝낼거같아보이긴 한데 막상 하면 막히는 부분에서 한참 해매니까 하루에는 못끝내고 못해도 이틀은 걸린다 ㅜㅜ 내일은 마무리해야지

+ Recent posts