- 컴퓨터 구조(Computer Architecture) 이해하기
- 컴퓨터 구성 요소 중 CPU의 동작 방식을 이해하고 구현하기
- CPU Simulator 만들기
- CPU 구성 요소에 대해 이해하고, CPU 명령어를 처리하도록 구현하기
- CPU가 프로그램 명령을 하나씩 가져와서 실행하는 동작을 시뮬레이션한다.
CPU는 메모리에 저장된 프로그램을 차례대로 가져가서(fetch) 해당 명령어를 해석해서(decode) 실행(execute)한다.
- 컴퓨터는 메모리에 저장된 프로그램을 실행한다.
- 컴퓨터는 프로그램 실행을 위해 필요한 CPU와 메모리로 구성된다.
- CPU는 메모리에 저장된 프로그램 명령을 하나씩 가져와서 실행한다.
- 메모리는 프로그램 로직과 데이터 값을 가진다.
class Computer {
constructor(memory, cpu) {
this.memory = memory;
this.cpu = cpu;
}
run() {
while (this.cpu.programCount < this.memory.program.size) {
this.playProgram(this.memory.program);
}
}
playProgram(program) {
const word = this.cpu.fetch(program);
const data = this.memory.data;
this.cpu.execute(word, data);
}
// 테스트용
printRegValues() {
console.log(this.cpu.dump());
}
printData() {
console.log(this.memory.data);
}
}
module.exports = Computer;
CPU는 ALU와 Registers로 구성된다.
const ALU = require('./ALU');
const Decoder = require('./Decoder');
const INSTRUCTION = require('./INSTRUCTION');
class CPU {
constructor() {
this.ALU = ALU;
this.programCount = 0;
this.regs = [null, null, null, null, null, null, null, null];
}
fetch(program) {
const word = program.get(this.programCount);
this.programCount += 1;
return word;
}
execute(word, memory) {
const command = new Decoder(word);
switch (command.key) {
case INSTRUCTION.LOAD_R: {
this.regs[command.reg1] = this.load(
memory,
this.regs[command.reg2] + this.regs[command.reg3]
);
break;
}
case INSTRUCTION.LOAD_V: {
this.regs[command.reg1] = this.load(
memory,
this.regs[command.reg2] + command.value
);
break;
}
case INSTRUCTION.STORE_R: {
const src = this.regs[command.reg1];
const address = this.regs[command.reg2] + this.regs[command.reg3];
this.store(memory, address, src);
break;
}
case INSTRUCTION.STORE_V: {
const src = this.regs[command.reg1];
const address = this.regs[command.reg2] + command.value;
this.store(memory, address, src);
break;
}
case INSTRUCTION.AND: {
const operand1 = this.regs[command.reg2];
const operand2 = this.regs[command.reg3];
this.regs[command.reg1] = this.ALU.and(operand1, operand2);
break;
}
case INSTRUCTION.OR: {
const operand1 = this.regs[command.reg2];
const operand2 = this.regs[command.reg3];
this.regs[command.reg1] = this.ALU.or(operand1, operand2);
break;
}
case INSTRUCTION.ADD_R: {
const operand1 = this.regs[command.reg2];
const operand2 = this.regs[command.reg3];
this.regs[command.reg1] = this.ALU.add(operand1, operand2);
break;
}
case INSTRUCTION.ADD_V: {
const operand1 = this.regs[command.reg2];
const operand2 = command.value;
this.regs[command.reg1] = this.ALU.add(operand1, operand2);
break;
}
case INSTRUCTION.SUB_R: {
const operand1 = this.regs[command.reg2];
const operand2 = this.regs[command.reg3];
this.regs[command.reg1] = this.ALU.sub(operand1, operand2);
break;
}
case INSTRUCTION.SUB_V: {
const operand1 = this.regs[command.reg2];
const operand2 = command.value;
this.regs[command.reg1] = this.ALU.sub(operand1, operand2);
break;
}
case INSTRUCTION.MOV:
this.regs[command.reg1] = command.value;
break;
}
}
load(memory, address) {
return memory.get(address);
}
store(memory, address, src) {
memory.set(address, src);
}
dump() {
this.regs[0] = this.programCount;
return this.regs;
}
reset() {
this.programCount = 0;
this.regs = [null, null, null, null, null, null, null, null];
}
}
module.exports = CPU;
- 전달받은 프로그램에서 현재
programCount
에 해당하는 명령어를 가져와서 리턴한다. programCount
를 1 증가시킨다.
- 전달받은 명령어
word
를 분석한다. - 분석한 명령
command
의 고유한 key(Instruction Bitcode)에 따라 계산하고 로직을 처리한다.
- 메모리 주소의 값을 반환한다.
- 메모리 주소에 src 값을 저장한다.
- programCount, R1, R2, ..., R7의 값 순서로 레지스터 값들을 배열에 담아 반환한다.
- 프로그램 카운트와 레지스터를 초기값으로 설정한다.
- 메모리의 주소
reg2 + reg3
에 있는 값을reg1
로 로드한다.
- 메모리의 주소
reg2 + value
에 있는 값을reg1
로 로드한다.
reg1
의 값을 메모리의 주소reg2 + reg3
에 저장한다.
reg1
의 값을 메모리의 주소reg2 + value
에 저장한다.
reg2
와reg3
의 AND 연산 결과값을reg1
에 저장한다.
reg2
와reg3
의 OR 연산 결과값을reg1
에 저장한다.
reg2
와reg3
의 ADD 연산 결과값을reg1
에 저장한다.
reg2
와value
의 ADD 연산 결과값을reg1
에 저장한다.
reg2
와reg3
의 SUB 연산 결과값을reg1
에 저장한다.
reg2
와value
의 SUB 연산 결과값을reg1
에 저장한다.
value
를reg1
에 저장한다.
const INSTRUCTION = require('./INSTRUCTION');
const Utils = require('./Utils');
class Decoder {
constructor(word) {
this.key;
this.reg1;
this.reg2;
this.reg3;
this.value;
this.parse(word);
}
parse(word) {
this.key = word.substring(0, 4);
this.reg1 = Utils.calRegIndex(word, 4, 6);
switch (this.key) {
case INSTRUCTION.MOV:
this.setMov(word);
break;
case INSTRUCTION.LOAD_R:
case INSTRUCTION.STORE_R:
case INSTRUCTION.AND:
case INSTRUCTION.OR:
case INSTRUCTION.ADD_R:
case INSTRUCTION.SUB_R:
this.setThreeReg(word);
break;
case INSTRUCTION.LOAD_V:
case INSTRUCTION.ADD_V:
case INSTRUCTION.SUB_V:
case INSTRUCTION.STORE_V:
this.setLastValue(word);
break;
}
}
setThreeReg(word) {
this.reg2 = Utils.calRegIndex(word, 7, 9);
this.reg3 = Utils.calRegIndex(word, 13, 15);
}
setLastValue(word) {
this.reg2 = Utils.calRegIndex(word, 7, 9);
this.value = Utils.calValue(word, 11);
}
setMov(word) {
this.value = Utils.calValue(word, 7);
}
}
module.exports = Decoder;
- key, reg1, reg2, reg3, value
- 전달받은 명령어
word
에 따라 IR(16비트코드)를 해석하여 Register Index 또는 Value를 계산한다.- 공통:
Instruction(4bit) R(3bit)
- 유형1:
R(3bit) 000 R(3bit)
- 유형2:
R(3bit) 1 Value(5bit)
- 유형3:
Value(9bit)
- 공통:
- 주어진 word의 indexStart부터 indexEnd 또는 끝까지의 비트코드를 십진수로 바꾼 결과를 반환한다.
const Utils = {
calRegIndex(word, indexStart, indexEnd) {
return parseInt(word.substring(indexStart, indexEnd + 1), 2);
},
calValue(word, indexStart) {
return parseInt(word.substring(indexStart), 2);
},
};
module.exports = Utils;
const ALU = {
or(operand1, operand2) {
return operand1 | operand2;
},
and(operand1, operand2) {
return operand1 & operand2;
},
add(operand1, operand2) {
return operand1 + operand2;
},
sub(operand1, operand2) {
return operand1 - operand2;
},
};
module.exports = ALU;
- 데이터 메모리 주소 0x00a2에 임의의 값
256
저장
0x0000b MOV R4, 0x00A0
0x0010b MOV R5, 0x0002
0x0020b LOAD R1, R4, R5
0x0030b ADD R2, R1, #4
0x0040b SUB R3, R1, R2
0x0050b STORE R3, R4, #4
class Memory {
constructor() {
this.program = new Map();
this.data = new Map();
this.setProgram();
this.setData();
}
setProgram() {
this.program
.set(0, '1011100010100000')
.set(1, '1011101000000010')
.set(2, '0001001100000101')
.set(3, '1000010001100100')
.set(4, '1001011001000010')
.set(5, '0100011100100100');
}
setData() {
const temp = 256;
this.data.set(0x00a2, temp);
}
}
module.exports = Memory;
0x00A0
을R4
에 저장한다.- 비트코드: 1011 100 010100000
0x0002
를R5
에 저장한다.- 비트코드: 1011 101 000000010
0x00A2
(R4 + R5) 주소의 메모리 값을 읽어서 R1로 로드한다.- 비트코드: 0001 001 100 000101
R1
과4
를 덧셈(+) 연산해서R2
에 저장한다.- 비트코드: 1000 010 001 1 00100
R2
와R1
값을 뺄셈(-) 연산해서R3
에 저장한다.- 비트코드: 1001 011 001 000 010
R3
값을 (R4
+#4
) 주소 메모리에 저장한다.- 비트코드: 0100 011 100 1 00100
const CPU = require('./CPU');
const Memory = require('./Memory');
const Computer = require('./Computer');
const memory = new Memory();
const cpu = new CPU();
const computer = new Computer(memory, cpu);
computer.run();
computer.printRegValues();
[6, 256, 260, -4, 160, 2, null, null]
- R0: 현재 programCount인 6
- R1: 메모리 주소 162(160 + 2)에 저장된 256이 R1에 LOAD 되어있다.
- R2: R1 값인 256에 4를 더한 260
- R3: R1 값인 256에서 R2 값인 260을 뺀 값인 -4
- R4: 160(0x00A0)
- R5: 2(0x0002)
computer.printData();
Map(2) { 162 => 256, 164 => -4 }
- 메모리 주소 164에 산술 결과
4
가 저장(store) 되어있다.