IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
한국 developerWorks   >  Special Issue  > developerworks

컴퓨팅 기술의 원형 탐험 Part 2: 작은 아이디어가 만들어낸 큰 차이



안윤호안윤호 mindengine@freechal.com

필자는 아마추어 리눅스 커널 해커였으며 최근에 팹(Fab)이라는 책을 번역 출간했다. 컴퓨터의 여러 분야에 관심이 많고 컴퓨터와 문화의 인터페이스에도 관심이 많다.




2008년 4월 15일



연재순서
1회(2008년 3월): 간단한 레지스터 머신에서 시작해 보기
2회(2008년 4월): 작은 아이디어가 만들어낸 큰 차이

영화 터미네이터를 보면 사이버다인이라는 회사에서 만든 최초의 터미네이터는 미래에서 타임머신을 타고 온 터미네이터의 부서진 부품에서 수거된 스카이넷 칩을 사용해 만들었다. 영화의 2부는 이 칩을 파기하려고 미래에서 다시 터미네이터가 온다는 줄거리다. 필자는 메타서큘러 계산기를 볼 때마다 터미네이터를 떠올리곤 한다. 배우는 입장에서 리스프(Lisp)를 만들어 보기 위해 리스프가 필요하다. 다만 최초의 리스프는 어셈블러로 만들어졌다.

전혀 실감나지 않는 이야기지만 컴퓨터를 만드는 일에는 컴퓨터가 필요했다. 칩을 시뮬레이트하기 위해서도 컴퓨터가 필요하고, 칩에 코드를 적용하고 테스트하기 위한 개발 도구에도 필요하며, 프로그래밍 언어를 실행하는 데도 컴퓨터가 필요하다. 과거에는 컴퓨터가 없었거나 개발 환경이 없어서 손으로 컴파일했다(주관적인 의견이지만 컴퓨터를 발전시키는 일에는 발전된 모습의 컴퓨터가 필요하다. 상상력과 비전이 언제나 미래의 컴퓨터가 된다. 요즘은 앨런 케이가 말한 것 같은 상상력 증폭기가 시들한 모습이다).

과거 8비트 시절에도 희소한 자원이었던 컴퓨터가 필요했다. 8비트 컴퓨터를 만들기 위해 더 좋은 컴퓨터가 필요했다. 수행되는 컴퓨터의 모델이 필요했다. 이를테면 빌 게이츠의 베이직 인터프리터는 미니컴퓨터를 이용해서 8비트의 8080을 모의 실험해서 나왔다. 그 다음은 실제로 구현하기 위해 MITS라고 하는 기업이 있는 앨버커퀴라는 마을로 갔고 인터프리터와 컴파일러를 파는 MS라는 회사가 나왔다. MS-DOS의 기본 모형에 가까운 CP/M 역시 DEC(Digital Equipment Corporation)의 미니컴퓨터가 개발의 원형을 제공했다. 마이크로프로세서는 인텔의 테드호프 팀에서 만들었는데 이들의 머릿속에는 이미 PDP-8이라는 컴퓨터가 있었다.

그러니 필자가 말한 터미네이터 이야기가 아주 동떨어진 것은 아니다. 어디엔가 원형이 숨쉬고 있고 필자의 취미는 원형을 포함해 그 뒷이야기를 추적하는 것이다. 생물학 배경을 갖고 있는 필자의 적성은 비교해부학(Comparative Anatomy)이다. 일의 윤곽은 과거를 잘 헤집어보는 것으로 쉽게 파악된다. 큰 영양가는 없겠지만 어떤 사물을 생각하는 데에는 시절과 인연을 잘 살피는 일이 중요하다고 한 선사(禪師)의 말이 생각난다. 이 선사는 식어가는 재에서 불씨를 찾지 못하겠다고 하는 제자를 깨우친 후 이 말을 했다. 잘 살펴보니 불씨는 남아있었던 것이다.

아무튼 컴퓨터를 만들려면 컴퓨터가 있는 편이 편하다(위조지폐범이 돈을 만들려면 실제 화폐가 있어야 한다). 다행히 컴퓨터는 도처에 널려있다. 컴퓨터보다는 사람들의 시간이 희소한 자원이다. 이번 글은 32비트나 64비트 슈퍼컴퓨터(굴러다니는 PC)를 가지고 ‘원시적 컴퓨터’ 기계를 만드는 방법을 생각해보는 리버스 엔지니어링이다. 레트로라고 보아도 좋겠다. 그 의도는 예상보다 컴퓨터라는 것이 별것 아니며 누구나 만들 수 있는 것이라는 생각을 갖도록 해보자는 것이다. 그러면 SICP 5장이 조금 더 쉽게 보일 것이다.



위로


C나 파이썬으로 만드는 프로세서(오토마타 만들기)

레지스터 머신이라는 이름이 기억에 필요한 장치인 레지스터의 존재를 떠올리게 한다. 단순한 AND OR NOT으로 만들어진 장치와 절차적인 작업을 실행할 수 있는 장치의 차이는 기억장치의 존재다. 기억장치는 상태(state)를 기억한다. 디지털 기계의 상태라는 것은 별다른 것이 아니다. 1010111… 같은 값을 저장하고 기억하는 것이다. AND OR NOT의 단순조합으로으로 만들 수 있는 장치는 입력이 들어오면 바로 출력으로 변한다. 이를테면 다음과 같은 유사코드가 되겠다.



volatile int i,j ;
  int k ; // i와 j는  변수라기보다는 I/O 포트값으로 생각하는 편이 현실적일 것이다.
  {
  k= i&&k;
  }
  

이런 정도로도 많은 일을 할 수 있겠지만(2진수의 가감산이나 논리연산 같은 것) 아주 복잡한 일은 시킬 수 없다. 입력을 하면 곧바로 출력을 내지만 연속적인 동작이나 몇 가지 정해진 일을 하는 것 같은 일은 할 수 없다. 일종의 오토마타가 필요했다.

논리회로나 전자장비가 발전하자 시퀀셜 머신이 중요한 주제로 등장했다. 예전에는 자동인형이나 오토마타로 불리던 것을 전기적으로 구현한 것이다. 수학적 이론은 그 이전부터 발전했다. 오토마타는 무엇으로 만들건 할 수 있는 일이 많았다. 기계식 계산기를 컴퓨터의 조상(오토마타)으로 보고 교과서에 예제로 올리는 역사적인 이유는 이들이 알고리즘을 구현하고 있었기 때문이다. 기계보다 전기가 오토마타를 만들기에 적합했다.  

전기식 장치인 릴레이가 발전하자 기계식보다 할 수 있는 일이 훨씬 많아졌다. 어떤 릴레이는 상태를 기억할 수 있고 외부 신호에 따라 일정한 반복 작업을 수행할 수 있도록 조합할 수 있었다. 기계의 상태는 무한하지 않고 유한하며(FSM: finite sate machine) 따라서 고장이 나지 않는 한 간단한 조작들을 실행할 수 있었다. 시간이 지나자 릴레이들이 숫자를 더하거나 빼거나 곱셈, 나눗셈을 할 수도 있었다. 숫자를 세는 카운터를 만들 수도 있었고 펀치카드와 비슷한 것을 읽을 수도 있게 되었다(릴레이가 나오기 훨씬 전에 배비지의 기계는 기계적으로 비슷한 아이디어를 구현했다). 계산기를 만들 수도 있었다. 릴레이로 만든 시퀀스 제어기는 1970년대까지 중요한 산업용 제어기였다. 

유한 상태 기계의 핵심은 몇 개의 상태라는 값을 가지고 이들을 오가는 방법이 정해져 있는 것이다. 상태는 특정한 값을 갖는 (반드시 이진수가 아니더라도) 보관장소가 있어야 한다. 기계의 핀조합이건 펀치카드건 릴레이건 일종의 기억장치와 같다. 릴레이 다음에는 진공관이 나와 더 편해졌지만 릴레이는 1 또는 0에 해당하는 전기적인 값을 갖는다. 진공관도 마찬가지였다. 

상태를 기억시키는 방법만 확립되면 어떤 상태에서 다른 상태로 이동시키는 것은 비교적 간단하다. 2차대전이 시작될 무렵이 되자 알고리즘을 오토마타에 적용하는 방법이 여러 가지가 나오게 되었다. 간단한 시퀀스 조작을 적용하면 암호도 풀어 낼 수 있었다. 암호를 푸는 방법은 수학적인 것이지만 절차는 기계적인 적용이다.

튜링머신의 고안

오토마타를 잘 조합하면 되는 것인데 이 과정에 튜링의 고안인 튜링머신이 적용되었다. 당시에 상태를 기억하는 방법은 기다란 강철 테이프에 자기적으로 기록하는 장치를 사용하는 것이었다. 튜링의 기계는 독일의 에니그마 암호를 풀었다. 고속으로 돌아가는 강철 테이프가 메모리였으며 메모리에 기록과 판독을 할 수만 있으면 이론적으로는 어떤 계산이든지 못할 것이 없다. 튜링머신은 이런 식으로 생각할 수 있었다. 기계는 절차적인 연산을 되풀이하여 해를 구한다.  

전자식 오토마타와 시퀀셜 제어기의 구조는 종이 한 장 차이였다. 그리고 그 다음에 나오는 컴퓨터와도 미세한 차이 밖에 없었다. 그러나 이 작은 차이들이 아주 큰 차이를 만들어냈다고 볼 수 있다. 컴퓨터 문명이라는 것이 모두 기계의 상태 변화를 이용한 계산에 의존하고 있다.   

그러면 첫 단계로 상태를 갖는 기계를 생각하기 위해 다음과 같은 유사코드 프로그램을 생각해 보자. 먼저 상태 테이블을 만들고 다음과 같이 정의되어 있다고 가정하자. 논리회로 시간에는 모두 2진수로 배우지만 그것은 기계의 관점이고 프로그램에서는 제한이 없다.

state[0]=1;
  state[1]=2;
  state[2]=99;
  ..
  ...
  .....
  state[99]=100;
  state[100]=0;

그리고 다음과 같은 코드가 돌아간다고 생각하자.


int address = 0 ;
for (;;)
  {
  //  getchar() ;
  // for loop가 한번 순환할 때마다 논리회로의 클럭이 1번 적용된 것과  같은 효과다. getcghar()는 키보드 입력으로 스텝을 흉내 낸다.  
  address=  state[address];
  }

기계는 state[0]에서 그 다음에 갈 번지수인 1을, stae[1]에서 번지수 2를 얻어낸다. 그 다음에는 state[2]에서 99를, state[99]에서는 100을 얻는다. 그리고 state[100]에서는 0을 얻는다. 그러면 수많은 배열 중에서 몇 가지 상태만을 확실하게 오간다. 이런 오토마타는 배열을 채워 넣기에 따라 카운터처럼 움직이고 몇 가지 상태를 오가면서 일을 할 수 있다.

초기의 컴퓨터나 시퀀스 제어기가 이런 식으로 계산한다고 하면 오토마타의 상태를 직접 코딩하는 일이 필요했고 하드웨어를 직접 만지는 것으로 코딩이 변하는 것이었다. 프로그래밍이란 하드웨어 프로그래밍을 의미한다. 상태변수를 정하기에 따라 아주 복잡한 계산도 가능하다. 컴퓨터에서 글자 하나를 바꾸는 것이 실제 기계에서는 회로를 바꾸는 번거로운 일로 변한다. 그래도 획기적인 변화였다.



위로


잇따르는 진화

여기서 몇 가지 진화 내지는 변형의 가능성이 더 있다. 하나는 상태에 더해 출력신호를 덧붙이는 것이다. 조금 더 기계적인 느낌을 주기 위해 16진수를 사용해 출력을 표시해보자.


stae[..]..   // 앞서와 마찬가지로 상태를 정의한다.
out[0]=0xFF; // 1111 1111
out[1]=0x12; // 0001 0010
out[2]=0x11; //...
  ..
  ...
  .....
out[99]=0xF0;
out[100]=0x0;

그리고 다음과 같은 코드가 돌아간다고 생각하자.


int address = 0;
for (;;)
  {
  address=  state[address];
  outp[xx] = out[address]
  }

그러면 상태가 바뀔 때마다 outp[xx]로 신호와 비슷한 것을 출력할 수 있다. outp[xx]에 신호선이 물려있다면 변하는 신호를 볼 수 있을 것이다. 다른 방법도 생각해 볼 수 있다. 앞의 코드를 조금 바꾼다. 



volatile inp =0;
int address = 0;
for (;;)
  {
  if  (inp=0) {
               address=  state[address];
  outp[xx] = out[address]
  }
             if  (inp=1) {
             address=  state1[address];
  outp[xx] = out1[address]
  }
  ...
}

조금 더 확실한 코드가 필요하겠지만(이를테면 inp의 값을 인지하는 것이 address가 0이 되는 경우와 같은 조금 명확한 기준이 필요하며 잘못된 시퀀스에 있을 때에는 에러를 내거나 다시 0으로 돌아가는 것 같은 동작도 필요하지만 이해하는 데에는 이 정도로 충분하다) 위의 코드는 state1과 out1의 값을 적당히 채워 넣으면 inp의 조건에 따라 각기 다른 오토마타의 경로를 만들 수 있다. 출력도 바뀐다. state와 out의 배열을 추가하거나 더 정교한 루틴을 만들 수도 있다. 더 근본적인 해결책은 inp와 address에 대한 계산식을 만들어 그 필요한 state의 값을 구할 수 있다. out이나 outp의 값도 같이 계산할 수도 있지만 복잡하고 예측이 어려운 계산으로 흘러간다. 



for (;;)
  {
  // address와 inp를 가지고 다음 번의 address를 구하는 계산을 한다.
  // 그  다음은 앞의 코드와 똑같다.
}

지금까지 설명으로 상태를 정의하고 적당한 입력과 출력을 정의한다면 복잡한 오토마타를 만들 수 있다는 것이 확실하다. 프로그래밍은 분명히 state, out, inp를 정의하는 것이다(아직까지 기계어나 어셈블리어 같은 것은 나오지도 않았다). 실제로 물리적인 구현으로는 하드웨어를 프로그래밍하는 것이다. 상태를 변경하면 이 복잡한 프로그램은 에니그마의 암호를 풀지도 모른다(실제로 암호를 풀었다).     

그러면 추상적인 프로그램으로 숫자 바꾸기 게임을 하는 것은 그렇다고 치고 실제로 하드웨어로 만드는 방법을 생각해보자. 우선 간단한 기계라 상태가 256개 밖에 없다고 하자. 그러면 상태를 표시하는 데에는 8비트면(2^8=256) 가능하다. 그러면 inp와 address를 합친 데이터비트는 모두 8의 비트로 표시할 수 있어야 한다. 하드웨어를 만들어 본 적이 있으면 알겠지만 이 정도도 사실 쉽지 않은 과제다.

상태를 바꾸는 표를 하드웨어적으로 만들려면 논리회로 수업에 나온 카르노 테이블 같은 것을 만들어야 한다. 만약 가로축을 현재 상태(addrss와 inp의 상태), 세로축을 다음에 분기가 일어날 상태로 정한다면 8*8의 테이블을 만들어 이것을 AND OR NOT의 조합으로 만들어야 한다. 종이와 연필로 만든다면 대단한 도전이다. 하루 종일 걸리겠지만 8*8의 테이블을 채워야 한다(출력만을 만드는 outp를 별개로 친다고 해도 그렇다. 만약 out이 어떤 방법으로든 다시 입력에 반영된다면 outp의 비트만큼 테이블은 커진다). 4*4나 그 이상만 되어도 테이블에서 논리소자로 바꾸는 일은 노동이다. 이렇게 만든 논리회로의 값을 일종의 상태저장기인 플립플롭에 입력하는 것이 일반적인 제어기의 설계다. 예전의 디지털 전자 설계는 이런 회로들을 그리는 것으로 시작되고 끝났다.

그림 1은 무어머신의 구조다. 이 구조의 기계를 만든 무어(Alfred Moore)의 연구실은 나중에 에니악(ENIAC: Electronic Numerical Integrator and Calculator)을 만든 곳으로 디지털 로직 초창기에는 중요한 연구 장소였다. 그림에서 Compute Next state는 논리회로이며 Present State Memory는 단순한 몇 비트의 D-플립플롭으로 만든 것이다(그림 2). 앞의 프로그램의 Address 변수값은 Next State 신호선의 값이며 state[address]는 Present State라고 볼 수 있다. 아무튼 등가물을 만드는 것은 어려운 일이 아니다.

그림 1. 간단한 무어머신
간단한 무어머신


 

 

 

 

 

 

 

 

그림 2. D-플립플롭

D-플립플롭

우리가 컴퓨터 프로그램으로 만드는 것은 간단한 일이었지만 실제 구현은 까다로운 편이다. 아예 표준적인 부속품조차 없던 1940년대와 50년대에는 논리소자 하나하나를 조립하는 일이 큰 작업이었다. 지난번 기사(http://www.ibm.com/developerworks/kr/library/s_issue/20080318/)의 제어기 사진은 아마도 아주 간단한 제어밖에 할 수 없는 단순한 기계였을 것이다. 1970년대에 일반적인 TTL 소자의 부품이 쉽게 입수되던 시절에도 복잡한 논리회로를 만드는 것은 일종의 도전이었다. 같은 일을 릴레이로 만드는 일은 쉽게 실감이 나지 않을 정도다. 아주 단순한 회로도 어렵다.

이 일을 쉽게 만들게 된 것은 롬(ROM)을 사용하면서부터다. 복잡한 게이트를 일일이 만드는 것이 아니라 롬의 테이블에 값을 저장하면 되었던 것이다. 롬을 만드는 방법은 원시적인 방법으로는 배선을 직접 연결하거나 다이오드와 저항으로 매트릭스를 만들거나 전기적으로 퓨즈를 태우는 방식도 있고 바이오스(BIOS)를 만드는 플래시롬에 직접 저장할 수도 있다. 속도 차이는 있겠으나 실제로 독자들이 만들어볼 수도 있다. 요즘은 VHDL 같은 것으로 만들어낼 수도 있다. 아무튼 롬을 사용하면 설계는 그렇게 무서운 일이 아니다. 앞서 만든 프로그램의 상태를 롬에 집어 넣으면 롬은 그림 1의 Compute Next State에 해당하고 플립플롭은 롬의 출력에 그대로 물려진다. 그림 2의 D-플립플롭은 D의 입력이 clock에 들어오면 그대로 출력되는 소자로 클럭이 들어오면 Q는 그대로 롬의 번지수가 된다. 가장 간단한 회로다. 배열에 값을 지정하여 의미있는 동작을 만드는 것은 어려운 일이며 이 정도로도 충분히 프로그래밍이라고 할 수 있는 작업이다. 지금이나 그 당시나 대표적인 오토마타인 무어머신은 이런 식으로 움직였다.

지금까지 설명한 내용 가운데 특별히 어려운 것은 없다. 중요한 내용은 필요한 일이 있을 때마다 오토마타를 재구성해야 한다는 것을 강조하고 싶다. 일이 바뀌면 핸드컴파일과 핸드 와이어링을 반복해야 한다. 하드와이어링이 아무나 할 수 없는 일이라는 것을 실감하기 위해 PDP-10 회로판의 뒷면을 보여주고 싶다. 이런 작업은 우선 성격이 좋아야 하고 배선 실수도 없어야 한다(그림 3과 4의 출처는 위키백과다). 핸드와이어링은 롬이 나오면서 크게 줄어들었다.

그림 3. 1970년대까지도 플립플롭은 모듈인 경우가 많았다. 이런 모듈이 수천 개 필요한 경우도 많았다.

1970년대까지도 플립플롭은 모듈인 경우가 많았다. 이런 모듈이 수천 개 필요한 경우도 많았다.

그림 4. 래핑 도구를 이용해 배선을 하드와이어링한 회로의 백패널 사진 
래핑 도구를 이용해 배선을 하드와이어링한 회로의 백패널  사진

이런 일을 반복하다 보니 아주 머리가 좋은 사람들은 곧바로 일종의 메타프로그래밍이 가능하다는 것을 알게 되었다. 기계를 항상 재조직하는 것이 아니라 미리 정한 명령을 읽고 쓰는 기계를 만들면 되는 것이다.

다음 회에는 오늘날 컴퓨터의 원형인 폰 노이만 아키텍처와 프로세서와 관련된 고전에 등장하는 몇 가지 원시 형태의 프로세서에 대해 살펴보겠다.




위로


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us


[지난 Special Issue 보기]


사이트 여행

dW 커뮤니티
포럼 | 블로그 | Spaces
dW Student Community

로컬 컨텐츠

행사 및 세미나

기획 기사

개발자 입문

튜토리얼 및 교육

TOP 10 인기자료

SW 다운로드

RSS 피드

뉴스레터
  
자바스크립트가 작동이 중지되었습니다. 이 기능을 수행하시려면 브라우저에서 자바스크립스트를 작동시켜 주시거나 이곳을 클릭해주세요.
Special offers
IBM SOA Sandbox 시험판
dW Student Community
로보코드
코드 트레이닝


    IBM 소개 개인정보 보호정책 문의