8087 코프로세서의 우아하고도 지저분한 비밀: 버스 스니핑과 마이크로코드
80년대나 90년대 초반에 PC를 조립해 본 사람이라면 ‘Math Co-processor(수치 연산 보조 프로세서)‘라는 단어만 들어도 가슴이 뛸 것입니다. 8087 칩 하나만 꽂으면 CAD나 1-2-3 로터스 스프레드시트가 날아다녔으니까요. 하지만 엔지니어의 관점에서 이 칩을 다시 들여다보면, 이 칩은 단순한 가속기가 아니라 극도의 제약 사항 속에서 탄생한 기괴하고도 천재적인 해킹의 집합체 라는 것을 알게 됩니다.
최근 Ken Shirriff가 8087 칩의 다이(die)를 역설계하여 명령어 디코딩 방식을 분석한 글이 올라왔는데, 그 내용이 꽤나 충격적이고 흥미롭습니다. 오늘은 이 내용을 바탕으로 인텔이 40년 전, 트랜지스터 하나가 아쉬웠던 시절에 어떻게 부동소수점 연산을 구현했는지 깊이 파헤쳐 보겠습니다.
CPU의 마음을 훔쳐보는 ‘버스 스니핑’
현대적인 관점에서 코프로세서라고 하면, 메인 CPU가 명시적으로 명령을 내리고 데이터를 전달하는 구조를 떠올립니다. 하지만 8086과 8087의 관계는 훨씬 더 복잡하고, 솔직히 말해 ‘스토커’ 에 가깝습니다.
8087은 8086의 레지스터에 접근할 권한이 없습니다. 그런데 8086의 메모리 주소 지정 방식은 세그먼트 레지스터와 오프셋을 조합하는 복잡한 방식입니다. 레지스터를 볼 수 없는 8087이 어떻게 메모리에서 데이터를 가져올까요?
인텔 엔지니어들의 해법은 실로 기발했습니다.
- 8087은 8086과 동일한 버스에 연결되어 모든 신호를 엿듣습니다(Sniffing).
- 8086이
ESCAPE(11011)로 시작하는 명령어를 만나면, 8087은 “아, 내 차례구나”라고 인식합니다. - (이 부분이 핵심) 8086은 8087을 위해 해당 메모리 주소를 계산하고 ‘더미 읽기(Dummy Read)’ 를 수행합니다. 읽은 데이터는 무시해버리죠.
- 8087은 8086이 계산해서 버스에 띄운 그 주소를 낚아채서 내부 레지스터에 저장하고, 실제 연산에 사용합니다.
즉, 8087은 8086을 ‘주소 계산기’로 써먹는 셈입니다. 이 방식은 우아하지만, 동시에 서로의 내부 큐(Queue) 상태를 완벽하게 동기화해야 한다는 엄청난 엔지니어링 난이도를 수반합니다. Ken의 분석에 따르면 8087은 8086의 명령어 큐 상태를 추적하기 위해 별도의 하드웨어를 내장하고 있습니다.
2K ROM이 너무 커서 만든 PLA 디코더
명령어를 해석해서 마이크로코드(Microcode)를 실행해야 하는데, 여기서 또 다른 문제가 발생합니다. 11비트나 되는 명령어를 바로 ROM 주소로 매핑하려면 2048 워드 크기의 ROM이 필요합니다. 지금이야 웃어넘길 크기지만, 당시 8087의 마이크로코드 ROM 전체가 1648 워드였습니다. 배보다 배꼽이 더 큰 상황이죠.
그래서 인텔은 PLA(Programmable Logic Array) 를 사용했습니다. PLA는 입력값에 대해 AND 평면과 OR 평면을 거쳐 출력을 내는 구조로, ROM보다 훨씬 적은 공간으로 복잡한 로직을 구현할 수 있습니다. 8087은 이 PLA를 사용해 11비트 명령어를 단 22개의 마이크로코드 진입점(Entry Point)으로 압축했습니다.
특히 인상적인 점은 ‘최적화’의 수준입니다.
MOD비트가 0, 1, 2인 경우(메모리 접근)를 하나의 패턴으로 묶어서 처리.- 특정 비트 패턴을 미리 전처리(Pre-processing)하여 PLA 크기를 줄임.
이것은 마치 정규표현식 엔진을 하드웨어로 구현한 것과 비슷합니다. 공간 효율성을 위해 로직의 복잡도를 기꺼이 감수한 것이죠.
마이크로코드의 조건부 점프와 ‘하드웨어 개입’
8087의 마이크로코드 엔진은 단순히 순차 실행만 하는 것이 아닙니다. 명령어의 비트 패턴을 직접 검사해서 조건부 점프를 할 수 있습니다. 예를 들어, 덧셈과 뺄셈은 대부분의 로직을 공유하다가 마지막에만 분기하면 되므로, 코드를 공유하여 ROM 용량을 아낍니다.
가장 흥미로운 부분은 ‘상수 로딩’ 입니다. $\pi$나 $\log_2(10)$ 같은 상수를 불러오는 명령어들이 있는데, 이 상수들의 지수(Exponent) 부분은 ROM 용량을 아끼기 위해 압축되어 있습니다. 마이크로코드가 실행되면서 필요에 따라 지수 값을 +1 하기도 합니다. 하드웨어와 소프트웨어(마이크로코드)의 경계가 모호하게 섞여 있는, 그야말로 ‘임베디드 최적화’의 끝판왕입니다.
Hacker News와 커뮤니티의 반응
이 기사에 대한 Hacker News의 반응도 흥미롭습니다. 특히 8087의 ROM이 트랜지스터 하나에 2비트를 저장하는 4-level 로직 을 사용했다는 점이 재조명되었습니다. 아날로그 방식의 전압 레벨을 구분해서 디지털 데이터를 저장한 것인데, 80년대에 이미 이런 MLC(Multi-Level Cell) 개념을 칩 내부에 적용했다는 것이 놀랍습니다.
한 유저는 “ARM은 처음부터 코프로세서 인터페이스를 명시적으로 설계했는데, 인텔은 왜 이렇게 복잡하게 했나?”라는 의문을 제기했습니다. 이에 대해 Ken은 당시 AMD Am9511 같은 칩들은 I/O 포트 방식을 썼지만, 인텔은 더 높은 성능과 통합을 위해 버스 스니핑 방식을 택했다고 설명합니다. 물론 이후 287, 387에서는 다시 I/O 포트 방식으로 회귀했지만요.
엔지니어로서의 단상
8087의 설계를 보며 느끼는 점은 “Clean Architecture는 풍요의 산물” 이라는 것입니다. 8087은 구조적으로 결코 깨끗하지 않습니다. 하드웨어와 마이크로코드가 얽혀 있고, 버스 인터페이스 유닛(BIU)이 마이크로코드의 흐름에 강제로 개입하기도 합니다.
하지만 이 칩은 초기 수율이 웨이퍼당 단 2개(!!)에 불과할 정도로 제조가 어려운 최첨단 기술이었습니다. 설계자들은 트랜지스터 하나, 마이크로코드 한 줄을 줄이기 위해 가능한 모든 ‘꼼수’와 ‘최적화’를 동원했습니다. 그 결과물인 8087은 오늘날 거의 모든 CPU에 들어가는 IEEE 754 부동소수점 표준의 기반이 되었습니다.
우리는 지금 풍요로운 컴퓨팅 자원 속에서 살고 있습니다. 하지만 가끔은 이렇게 바이트 하나, 사이클 하나를 아끼기 위해 치열하게 고민했던 선배 엔지니어들의 ‘Ad-hoc’한 설계에서 깊은 영감을 받곤 합니다. 완벽한 구조는 아니지만, 작동하게 만드는 집요함 이 거기에 있으니까요.
References:
- Original Article: Instruction decoding in the Intel 8087 floating-point chip
- Hacker News Thread: Hacker News Comments