프로세서 안에서 값이 지나다니는 길이 선로라면, 제어 장치는 선로 전환기를 조작하는 신호소다. 열차(값)는 스스로 길을 고르지 못해서, 신호소가 명령어의 앞머리 몇 비트를 보고 갈림길마다 어느 쪽으로 보낼지, 어느 역에 짐을 내릴지를 정해 준다. 명령어 종류마다 스위치 묶음을 표 한 줄로 적어 두면 그만이라 만들기는 쉽다. 다만 스위치 하나만 잘못 놓아도 엉뚱한 레지스터나 메모리가 덮어써진다.
예시로 보기
제어가 없으면 단일 사이클 데이터패스는 무엇을 할지 모른다. sw $t0, 12($s3)을 실행한다고 하자. $s3 = 100, $t0 = 12다.
- 맞는 설정: ALU 둘째 입력은 상수 12, 메모리 쓰기는 켜고, 레지스터 쓰기는 끈다. 메모리 112번지에 12가 들어가고 레지스터는 그대로다.
- 레지스터 쓰기만 잘못 켜면: 메모리에는 똑같이 쓰는데, 덤으로 ALU 결과 112가 rt 자리의
$t0에 들어간다.$t0가 12에서 112로 바뀐다.
sw는 레지스터를 고치면 안 되는 명령어다. 신호 1비트가 프로그램을 망가뜨린다. 그래서 명령어 종류마다 모든 신호의 값을 빠짐없이 정해 둔다.
sw의 RegWrite, R형의 RegDst·ALUSrc, lw의 MemtoReg, beq의 Branch)에서 결과가 달라짐, X 칸에 0과 1을 넣은 두 경우가 무작위 프로그램 3,000개에서 같은 결과 — 24_single-cycle-datapath_impl.py (실제 하드웨어가 아니라 파이썬 흉내)제어 신호 여덟 가지
| 신호 | 0일 때 | 1일 때 | 닿는 부품 |
|---|---|---|---|
| RegDst | 쓸 레지스터 번호 = rt (20~16번 비트) | 쓸 레지스터 번호 = rd (15~11번 비트) | 멀티플렉서 |
| ALUSrc | ALU 둘째 입력 = 레지스터에서 읽은 값 2 | ALU 둘째 입력 = 부호 확장한 16비트 | 멀티플렉서 |
| MemtoReg | 레지스터에 쓸 값 = ALU 결과 | 레지스터에 쓸 값 = 메모리에서 읽은 값 | 멀티플렉서 |
| RegWrite | 레지스터에 쓰지 않는다 | 클록이 올라갈 때 쓴다 | 레지스터 파일 |
| MemRead | 메모리를 읽지 않는다 | 주소의 값을 읽어 내보낸다 | 데이터 메모리 |
| MemWrite | 메모리에 쓰지 않는다 | 클록이 올라갈 때 쓴다 | 데이터 메모리 |
| Branch | 분기 명령이 아니다 | 분기 명령이다 | 다음 PC 고르기 |
| ALUOp (2비트) | ALU가 할 일의 종류를 ALU 제어에 알린다 (아래) | ALU 제어 |
표의 뜻은 공개 강의 자료에서 확인했다1.
다음 PC를 고르는 신호 PCSrc는 제어 장치가 혼자 정하지 못한다. 분기 명령이어도 두 값이 같을 때만 가야 하기 때문이다. 그래서 Branch와 ALU의 Zero 출력을 AND 게이트에 넣는다1.
\[\text{PCSrc} = \text{Branch} \cdot \text{Zero}\]가운뎃점은 AND다. 둘 다 1일 때만 1이다.
주 제어의 표
주 제어는 명령어의 맨 앞 6비트(opcode)만 보고 신호를 낸다. opcode는 R형 0, lw 35, sw 43, beq 4다2.
| 신호 | R형 | lw |
sw |
beq |
|---|---|---|---|---|
| RegDst | 1 | 0 | X | X |
| ALUSrc | 0 | 1 | 1 | 0 |
| MemtoReg | 0 | 1 | X | X |
| RegWrite | 1 | 1 | 0 | 0 |
| MemRead | 0 | 1 | 0 | 0 |
| MemWrite | 0 | 0 | 1 | 0 |
| Branch | 0 | 0 | 0 | 1 |
| ALUOp | 10 | 00 | 00 | 01 |
값은 공개 강의 자료 두 곳에서 확인했다3. X는 “0이든 1이든 결과가 같다”는 표시다.
표를 외우지 않고 한 줄씩 만들 수 있다. lw를 예로 든다.
- rt에 쓴다. → RegDst = 0, RegWrite = 1
- 주소는 레지스터 + 상수다. → ALUSrc = 1, ALU는 더한다 → ALUOp = 00
- 메모리를 읽는다. → MemRead = 1, MemWrite = 0
- 읽은 값을 레지스터에 쓴다. → MemtoReg = 1
- 분기가 아니다. → Branch = 0
X가 붙는 자리. sw와 beq는 레지스터에 쓰지 않는다(RegWrite = 0). 그러면 “어느 레지스터에 쓸까”(RegDst)와 “무슨 값을 쓸까”(MemtoReg)는 어느 쪽을 골라도 버려진다. X로 두면 회로를 만들 때 더 간단한 쪽을 고를 수 있다. 반대로 RegWrite와 MemWrite에는 X가 올 수 없다. 이 둘은 값을 실제로 고치는 신호라서, 아무렇게나 두면 예시처럼 엉뚱한 곳이 덮인다.
j를 넣으면. 신호 Jump를 하나 더 만들고, 다음 PC 앞에 멀티플렉서를 하나 더 둔다. j(opcode 2)에서는 Jump = 1, RegWrite = 0, MemWrite = 0, Branch = 0이고 나머지는 X다. 다른 명령어에서는 Jump = 0이다4.
게이트로 만들기
표를 회로로 옮기면 두 층이 된다. 앞층의 AND 게이트 넷이 opcode 6비트를 보고 “R형이다”, “lw다”, “sw다”, “beq다”를 하나씩 알아본다. 뒤층의 OR 게이트가 그 넷을 모아 신호를 만든다.
$+$는 OR다. 예를 들어 “lw다”는 opcode가 $100011_2$일 때만 1이 되는 AND 게이트다. 이 식은 X 칸을 0으로 채운 것과 같다5.
ALU 제어
| ALU가 할 일은 두 단계로 정한다. 주 제어가 “더해라(00)”, “빼라(01)”, “명령어의 funct 칸을 봐라(10)” 가운데 하나를 ALUOp로 내면, 작은 회로인 ALU 제어가 그것과 funct 6비트를 보고 [[2-1학기/컴퓨터 구조/3.개념집/08.ALU | ALU]]에 넣을 4비트를 만든다6. |
| 명령어 | ALUOp | funct | ALU가 하는 일 | ALU 제어 4비트 |
|---|---|---|---|---|
lw, sw |
00 | 보지 않는다 | 더하기 (주소) | 0010 |
beq |
01 | 보지 않는다 | 빼기 (견주기) | 0110 |
add |
10 | 100000 | 더하기 | 0010 |
sub |
10 | 100010 | 빼기 | 0110 |
and |
10 | 100100 | AND | 0000 |
or |
10 | 100101 | OR | 0001 |
slt |
10 | 101010 | 작으면 1 | 0111 |
두 단계로 나누는 까닭은 주 제어를 작게 두기 위해서다. R형 다섯 명령어는 opcode가 모두 0이라 주 제어에게는 한 가지로 보인다. 다섯을 가르는 일은 funct만 보는 작은 회로에 맡긴다.
ALU 제어를 게이트 식으로
ALU 제어 4비트를 위에서부터 $op_3 op_2 op_1 op_0$, funct를 $F_5 \dots F_0$이라 쓴다. 표에서 funct의 위 두 비트는 늘 10이라 볼 필요가 없다6.
\[op_3 = 0, \qquad op_2 = \text{ALUOp}_0 + \text{ALUOp}_1 \cdot F_1, \qquad op_1 = \overline{\text{ALUOp}_1} + \overline{F_2}, \qquad op_0 = \text{ALUOp}_1 \cdot (F_3 + F_0)\]윗줄은 “아니다”(NOT)이다. $op_2$는 “빼기 쪽인가”를 뜻한다. beq이거나(ALUOp₀ = 1), R형이면서 funct의 1번 비트가 1일 때(sub, slt)다. add에 넣어 보면 ALUOp = 10, funct = 100000이라 $op_2 = 0$, $op_1 = 0 + 1 = 1$, $op_0 = 1 \cdot 0 = 0$으로 0010이 나온다5.
오래된 판의 교재와 일부 강의 자료는 ALU 제어를 3비트(AND 000, OR 001, 더하기 010, 빼기 110, 작으면 1은 111)로 적는다. 위 4비트에서 맨 앞 0을 뗀 것과 같다.
스스로 설명해 보기
1. beq의 ALUSrc가 0이어야 하는 이유는? 16비트 상수는 어디에 쓰이나?
ALU는 두 레지스터를 견줘야 하므로 둘째 입력이 레지스터 값이다. 16비트 상수는 ALU로 가지 않고, 부호 확장과 2칸 밀기를 거쳐 분기용 가산기로 간다.
2. R형의 MemtoReg는 X가 아니라 0이다. 왜인가?
R형은 RegWrite = 1이라 쓸 값이 실제로 레지스터에 들어간다. ALU 결과를 써야 하므로 0으로 정해져야 한다. X는 쓰기 신호가 0이라 선택이 버려질 때만 붙는다.
3. sw의 MemRead는 왜 X가 아니라 0인가?
결과만 보면 읽은 값이 버려지므로 X여도 될 것 같다. 하지만 메모리를 읽는 일은 실제 장치에서 시간과 전력을 쓰고, 없는 주소를 읽으면 오류가 날 수 있다. 그래서 필요 없을 때는 꺼 둔다4.
이 설계의 핵심 아이디어는?
명령어의 뜻을 “갈림길마다의 선택 + 쓰기 스위치”의 묶음으로 바꾼다. 데이터패스는 그대로 두고 표의 한 줄만 바꾸면 다른 명령어가 된다.
이 방법을 쓸 수 있는 다른 상황은?
| [[2-1학기/컴퓨터 구조/3.개념집/28.파이프라인 레지스터와 제어 | 파이프라인]]에서도 같은 표를 쓴다. 신호를 한꺼번에 쓰지 않고 명령어와 함께 단계마다 실어 보낼 뿐이다. |
활용
- 새 명령어 넣기: 데이터패스에 길이 있으면 표에 줄 하나만 더한다. 길이 없으면 멀티플렉서를 넓히고 신호를 더한다(
j의 Jump가 그 예). - 흔한 실수:
lw의 RegDst를 1로 쓰는 것.lw의 목적지는 rd가 아니라 rt다. I형에는 rd 칸이 없다. - 흔한 실수:
beq의 ALUOp를 00으로 쓰는 것. 견주려면 빼야 하므로 01이다. -
연습: [[2-1학기/컴퓨터 구조/4.연습문제/25.데이터패스 추적 예제 사다리 데이터패스 추적 예제 사다리]]
연결
-
선수: [[2-1학기/컴퓨터 구조/3.개념집/24.단일 사이클 데이터패스 단일 사이클 데이터패스]], [[2-1학기/컴퓨터 구조/3.개념집/16.MIPS 명령어 형식 MIPS 명령어 형식]], [[2-1학기/컴퓨터 구조/3.개념집/08.ALU ALU]] -
진리표에서 게이트 식 만들기: [[공학수학/이산수학/3.개념집/05.불 대수와 논리 회로 불 대수와 논리 회로]] -
신호가 고르는 부품: [[2-1학기/컴퓨터 구조/3.개념집/06.멀티플렉서와 디코더 멀티플렉서와 디코더]] -
파이프라인에서 신호를 실어 나르기: [[2-1학기/컴퓨터 구조/3.개념집/28.파이프라인 레지스터와 제어 파이프라인 레지스터와 제어]]
자주 하는 오해
틀렸다. X는 “0이어도 1이어도 결과가 같다”는 뜻이다. 표에서 숫자가 아닌 칸이라 “꺼짐”으로 읽기 쉽고, 실제 회로가 X 칸에 0을 내는 일이 많아서 그럴듯해 보인다. 하지만 sw의 RegDst를 1로 두어도 프로세서는 똑같이 동작한다. RegWrite가 0이라 고른 번호를 쓰지 않기 때문이다. 확인하는 법: 흉내기에서 X 칸을 전부 0으로 채운 것과 전부 1로 채운 것을 같은 프로그램으로 돌리면 레지스터와 메모리가 늘 같다. 거꾸로 X가 아닌 칸은 하나만 바꿔도 결과가 달라진다.
확인 문제
C1 lw의 제어 신호 여덟 가지를 쓰라.
답: RegDst 0, ALUSrc 1, MemtoReg 1, RegWrite 1, MemRead 1, MemWrite 0, Branch 0, ALUOp 00.
이유: rt에 쓰고(RegDst 0), 주소는 레지스터 + 상수이며(ALUSrc 1, 더하기), 메모리에서 읽은 값을 쓴다(MemRead 1, MemtoReg 1).
C2 sw와 beq에서 RegDst와 MemtoReg는 X인데, RegWrite와 MemWrite는 어떤 명령어에서도 X가 되지 못한다. 왜인가?
답: RegDst와 MemtoReg는 “레지스터에 쓸 때 무엇을 어디에”를 고르는 신호라서, 쓰지 않는 명령어(RegWrite = 0)에서는 무엇을 골라도 버려진다. RegWrite와 MemWrite는 레지스터나 메모리를 실제로 고치는 스위치라서 값에 따라 늘 결과가 달라진다.
C3 addi $t5, $t0, -7(rt ← rs + 상수, opcode 8)을 지금 데이터패스에 넣으려 한다. 부품을 더하지 않고 제어 표에 줄 하나만 더해 된다. 그 줄을 쓰라.
답: RegDst 0, ALUSrc 1, MemtoReg 0, RegWrite 1, MemRead 0, MemWrite 0, Branch 0, ALUOp 00.
이유: lw처럼 rt에 쓰고 ALU가 레지스터 + 상수를 더한다. 다른 점은 메모리를 거치지 않고 ALU 결과를 바로 쓴다는 것이다(MemtoReg 0, MemRead 0). $t0 = 12이면 $t5에 5가 들어간다7.
C4 sub $t0, $s1, $s2와 beq $s1, $s2, L은 둘 다 ALU가 뺄셈을 한다. ALUOp는 각각 무엇이고, ALU 제어 4비트는 같은가?
답: sub는 ALUOp 10(funct를 보라), beq는 ALUOp 01(빼라). ALU 제어 4비트는 둘 다 0110으로 같다.
이유: sub는 opcode가 0이라 주 제어가 무슨 연산인지 모르고 funct 100010을 봐야 안다. beq는 opcode만으로 뺄셈임을 안다.
C5 “R형의 RegDst를 0으로 바꿔도 결과는 같다”가 틀렸음을 보이는 예를 들라. $t0 = 12, $t1 = 5에서 add $t2, $t0, $t1을 쓴다.
답: 맞는 설정이면 $t2에 17이 들어간다. RegDst를 0으로 하면 쓸 번호가 rd($t2)가 아니라 rt($t1)가 되어, $t2는 그대로 0이고 $t1이 5에서 17로 바뀐다.
-
Princeton COS/ELE 375 (Fall 2015, David August), Lecture 8 “Control”, 슬라이드 6~15 (제어 신호의 이름, 참·거짓일 때의 뜻, PCSrc = Branch & Zero), https://www.cs.princeton.edu/courses/archive/fall15/cos375/lectures/08-Control.pdf ↩ ↩2
-
같은 강의, 슬라이드 17 (Control Design: Inputs. opcode 비트와 10진 값 0, 35, 43, 4). MIPS Reference Data (교재의 참조표, Univ. of Patras 게시본)의 lw 23hex, sw 2bhex, beq 4hex, j 2hex, addi 8hex, https://eclass.upatras.gr/modules/document/file.php/EE805/MIPS_Instruction_Set/MIPSReferenceData.pdf ↩
-
NYU, A. Gottlieb, Computer Architecture 2007-08 Fall, Lecture 17 (주 제어의 진리표: 입력 Op5~Op0과 출력 아홉 줄), https://cs.nyu.edu/~gottlieb/courses/2000s/2007-08-fall/arch/lectures/lecture-17.html. Edinboro Univ. CSCI 312 (Fall 2017) 강의 노트 “Building the ALU Control”의 ALUOp 값(lw·sw 00, beq 01, R형 10), https://mirkwood.cs.edinboro.edu/~bennett/class/csci312/fall2017/notes/control/one.html. Patterson & Hennessy, Computer Organization and Design (MIPS Edition), 4장 “The Processor” ↩
-
j의 제어 줄과 “MemRead를 꺼 두는 이유”는 연 자료에서 글로 확인하지 못했다.j의 줄은 “레지스터도 메모리도 고치지 않는다”에서 정했고 흉내기로 확인했다. MemRead의 이유는 [확인필요]. ↩ ↩2 -
주 제어의 식은 표에서 1인 칸을 모은 것이고, ALU 제어의 식은 표에서 직접 구했다. 두 식 모두 표와 같은지 [[2-1학기/컴퓨터 구조/3.개념집/25_control-unit_verify.py 25_control-unit_verify.py]]에서 전수 검사했다. -
Edinboro Univ. 같은 노트 (ALU 제어 4비트 0000 AND, 0001 OR, 0010 add, 0110 subtract, 0111 slt, 1100 nor와 ALUOp·funct 표, funct의 위 두 비트는 늘 10) ↩ ↩2
-
addi의 제어 줄은 이 문서의 명령어 범위 밖이다. 흉내기에 이 줄을 넣어$t5= 5가 나오는 것을 확인했다. ↩