Skip to content

Instantly share code, notes, and snippets.

@youzysu
Last active May 13, 2023 12:24
Show Gist options
  • Save youzysu/1968bb637c15d0e78f08556597edfee4 to your computer and use it in GitHub Desktop.
Save youzysu/1968bb637c15d0e78f08556597edfee4 to your computer and use it in GitHub Desktop.
CS03. CPU

CS03. CPU

목표

  • 컴퓨터 구조(Computer Architecture) 이해하기
  • 컴퓨터 구성 요소 중 CPU의 동작 방식을 이해하고 구현하기

내용

  • CPU Simulator 만들기
    • CPU 구성 요소에 대해 이해하고, CPU 명령어를 처리하도록 구현하기
    • CPU가 프로그램 명령을 하나씩 가져와서 실행하는 동작을 시뮬레이션한다.

🚀 기능 구현 사항

CPU는 메모리에 저장된 프로그램을 차례대로 가져가서(fetch) 해당 명령어를 해석해서(decode) 실행(execute)한다.

Computer

  • 컴퓨터는 메모리에 저장된 프로그램을 실행한다.
  • 컴퓨터는 프로그램 실행을 위해 필요한 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

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;

📌 CPU Method

fetch(program)

  • 전달받은 프로그램에서 현재 programCount에 해당하는 명령어를 가져와서 리턴한다.
  • programCount를 1 증가시킨다.

execute(word, memory)

  • 전달받은 명령어 word를 분석한다.
  • 분석한 명령 command의 고유한 key(Instruction Bitcode)에 따라 계산하고 로직을 처리한다.

load(memory, address)

  • 메모리 주소의 값을 반환한다.

store(memory, address, src)

  • 메모리 주소에 src 값을 저장한다.

dump()

  • programCount, R1, R2, ..., R7의 값 순서로 레지스터 값들을 배열에 담아 반환한다.

reset()

  • 프로그램 카운트와 레지스터를 초기값으로 설정한다.

📌 CPU Command

LOAD_R(0001)

  • 메모리의 주소 reg2 + reg3에 있는 값을 reg1로 로드한다.

LOAD_V(0010)

  • 메모리의 주소 reg2 + value에 있는 값을 reg1로 로드한다.

STORE_R(0011)

  • reg1의 값을 메모리의 주소 reg2 + reg3에 저장한다.

STORE_V(0100)

  • reg1의 값을 메모리의 주소 reg2 + value에 저장한다.

AND(0101)

  • reg2reg3의 AND 연산 결과값을 reg1에 저장한다.

OR(0110)

  • reg2reg3의 OR 연산 결과값을 reg1에 저장한다.

ADD_R(0111)

  • reg2reg3의 ADD 연산 결과값을 reg1에 저장한다.

ADD_V(1000)

  • reg2value의 ADD 연산 결과값을 reg1에 저장한다.

SUB_R(1001)

  • reg2reg3의 SUB 연산 결과값을 reg1에 저장한다.

SUB_V(1010)

  • reg2value의 SUB 연산 결과값을 reg1에 저장한다.

MOV(1011)

  • valuereg1에 저장한다.

📌 Decoder

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;

Properties

  • key, reg1, reg2, reg3, value

parse(word)

  • 전달받은 명령어 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)

Utils

  • 주어진 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;

📌 ALU

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;

Memory

  • 데이터 메모리 주소 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;

MOV R4, 0x00A0

  • 0x00A0R4에 저장한다.
  • 비트코드: 1011 100 010100000

MOV R5, 0x0002

  • 0x0002R5에 저장한다.
  • 비트코드: 1011 101 000000010

LOAD R1, R4, R5

  • 0x00A2(R4 + R5) 주소의 메모리 값을 읽어서 R1로 로드한다.
  • 비트코드: 0001 001 100 000101

ADD R2, R1, #4

  • R14를 덧셈(+) 연산해서 R2에 저장한다.
  • 비트코드: 1000 010 001 1 00100

SUB R3, R1, R2

  • R2R1 값을 뺄셈(-) 연산해서 R3에 저장한다.
  • 비트코드: 1001 011 001 000 010

STORE R3, R4, #4

  • 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) 되어있다.
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;
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;
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;
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;
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();
computer.printData();
const INSTRUCTION = {
LOAD_R: '0001',
LOAD_V: '0010',
STORE_R: '0011',
STORE_V: '0100',
AND: '0101',
OR: '0110',
ADD_R: '0111',
ADD_V: '1000',
SUB_R: '1001',
SUB_V: '1010',
MOV: '1011',
};
module.exports = INSTRUCTION;
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 = 0b10000000;
this.data.set(0x00a2, temp);
}
}
module.exports = Memory;
const Utils = {
calRegIndex(word, indexStart, indexEnd) {
const bin = word.substring(indexStart, indexEnd + 1);
return parseInt(bin, 2);
},
calValue(word, indexStart) {
const bin = word.substring(indexStart);
return parseInt(bin, 2);
},
};
module.exports = Utils;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment