Skip to content

Instantly share code, notes, and snippets.

@mizukmb
Last active June 14, 2020 13:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mizukmb/ddb641cdf04e7e9eb71c299c9ff44da3 to your computer and use it in GitHub Desktop.
Save mizukmb/ddb641cdf04e7e9eb71c299c9ff44da3 to your computer and use it in GitHub Desktop.
// CPU EMULATOR
// CPUの作り方10講3章
#![allow(unused)]
use std::num::Wrapping;
// 命令コードのインデックス
const MOV: u16 = 0;
const ADD: u16 = 1;
const SUB: u16 = 2;
const AND: u16 = 3;
const OR: u16 = 4;
const SL: u16 = 5;
const SR: u16 = 6;
const SRA: u16 = 7;
const LDL: u16 = 8;
const LDH: u16 = 9;
const CMP: u16 = 10;
const JE: u16 = 11;
const JMP: u16 = 12;
const LD: u16 = 13;
const ST: u16 = 14;
const HLT: u16 = 15;
// 汎用レジスタのインデックス
const REG0: u16 = 0;
const REG1: u16 = 1;
const REG2: u16 = 2;
const REG3: u16 = 3;
const REG4: u16 = 4;
const REG5: u16 = 5;
const REG6: u16 = 6;
const REG7: u16 = 7;
fn main() {
// 配列の宣言
let mut reg: [u16; 8] = [0; 8];
let mut rom: [u16; 256] = [0; 256];
let mut ram: [u16; 256] = [0; 256];
let mut pc = 0; // プログラムカウンタ
let mut ir = 0; // インストラクションレジスタ (命令レジスタ。現在実行中の命令を格納する)
let mut flag_eq = 0; // 比較用フラグ
rom = assembler(rom);
println!("START");
loop {
ir = rom[pc]; // romのPC番地にある命令ビット列をインストラクションレジスタに転送
println!("PC: {}, REG0: {}, REG1: {}, REG2: {}, REG3: {}", pc, reg[0], reg[1], reg[2], reg[3]);
pc += 1; // PCの更新
println!("ir: {}", ir);
println!("flag_eq: {}", flag_eq);
match op_code(ir) {
MOV => reg[op_reg_a(ir)] = reg[op_reg_b(ir)],
ADD => reg[op_reg_a(ir)] = reg[op_reg_a(ir)] + reg[op_reg_b(ir)],
SUB => reg[op_reg_a(ir)] = reg[op_reg_a(ir)] - reg[op_reg_b(ir)],
AND => reg[op_reg_a(ir)] = reg[op_reg_a(ir)] & reg[op_reg_b(ir)],
OR => reg[op_reg_a(ir)] = reg[op_reg_a(ir)] | reg[op_reg_b(ir)],
SL => reg[op_reg_a(ir)] = reg[op_reg_a(ir)] << 1,
SR => reg[op_reg_a(ir)] = reg[op_reg_a(ir)] >> 1,
SRA => reg[op_reg_a(ir)] = reg[op_reg_a(ir)] & 0b1000_0000_0000_0000 | reg[op_reg_a(ir)] >> 1,
LDL => reg[op_reg_a(ir)] = reg[op_reg_a(ir)] & 0b1111_1111_0000_0000 | op_data(ir) & 0b1111_1111,
LDH => reg[op_reg_a(ir)] = op_data(ir) << 8 | reg[op_reg_a(ir)] & 0b1111_1111,
CMP => flag_eq = if reg[op_reg_a(ir)] == reg[op_reg_b(ir)] {
1
} else {
0
},
JE => if flag_eq == 1 {
pc = op_addr(ir)
},
JMP=> pc = op_addr(ir),
LD => reg[op_reg_a(ir)] = ram[op_addr(ir)],
ST => ram[op_addr(ir)] = reg[op_reg_a(ir)],
HLT => break,
_ => println!("OP CODE '{}' IS NOT FOUND", op_code(ir)),
}
}
println!("ram[64] = {}", ram[64]);
println!("FINISHED!!");
}
// 1+2+3+...+9+10の計算例
fn assembler(mut rom: [u16; 256]) -> [u16; 256] {
// レジスタ0 に値 `0` を設定
rom[0] = ldh(REG0, 0);
rom[1] = ldl(REG0, 0);
// レジスタ1 に値 `1` を設定
rom[2] = ldh(REG1, 0);
rom[3] = ldl(REG1, 1);
// レジスタ2 に値 `0` を設定
// REG2 に 0, 1, 2, 3... と次に足す数を格納していく
rom[4] = ldh(REG2, 0);
rom[5] = ldl(REG2, 0);
// レジスタ3に値 `10` を設定
// 終了条件みたいなもの。REG2がREG3の数値になるまで足し続ける
rom[6] = ldh(REG3, 0);
rom[7] = ldl(REG3, 10);
// REG0に加算する数をREG2に設定する
rom[8] = add(REG2, REG1);
// 加算
rom[9] = add(REG0, REG2);
// メモリ番地64にREG0の内容を転送
rom[10] = st(REG0, 64);
// 10まで足したか?
rom[11] = cmp(REG2, REG3);
// Flagが1 (つまり10まで足した) ならば rom[14] にジャンプするようにPCを設定
rom[12] = je(14);
// Flagが0だったら rom[8] にジャンプするようにPCを設定
rom[13] = jmp(8);
rom[14] = hlt();
rom
}
// 各命令の命令コードの機械語(16bit)を作成し返す
// 上位4ビット (11~14)→命令コード
// 下位11ビット(0~10)→オペランド
// MOV: move命令
// 0~4: 不使用
// 5~7: 第2オペランド
// 8~10 第1オペランド
// 11~14: 命令コード
fn mov(ra: u16, rb: u16) -> u16 {
(Wrapping(MOV) << 11).0 | (Wrapping(ra) << 8).0 | rb << 5
}
// add: 加算命令
// 0~4: 不使用
// 5~7: 第2オペランド
// 8~10 第1オペランド
// 11~14: 命令コード
fn add(ra: u16, rb: u16) -> u16 {
(Wrapping(ADD) << 11).0 | (Wrapping(ra) << 8).0 | rb << 5
}
// sub: 減算命令
// 0~4: 不使用
// 5~7: 第2オペランド
// 8~10 第1オペランド
// 11~14: 命令コード
fn sub(ra: u16, rb: u16) -> u16 {
(Wrapping(SUB) << 11).0 | (Wrapping(ra) << 8).0 | rb << 5
}
// and: 論理積命令
// 0~4: 不使用
// 5~7: 第2オペランド
// 8~10 第1オペランド
// 11~14: 命令コード
fn and(ra: u16, rb: u16) -> u16 {
(Wrapping(AND) << 11).0 | (Wrapping(ra) << 8).0 | rb << 5
}
// or: 論理和命令
// 0~4: 不使用
// 5~7: 第2オペランド
// 8~10 第1オペランド
// 11~14: 命令コード
fn or(ra: u16, rb: u16) -> u16 {
(Wrapping(OR) << 11).0 | (Wrapping(ra) << 8).0 | rb << 5
}
// sl: 1bit左シフト命令
// 0~7: 不使用
// 8~10 第1オペランド
// 11~14: 命令コード
fn sl(ra: u16) -> u16 {
(Wrapping(SL) << 11).0 | (Wrapping(ra) << 8).0
}
// sr: 1bit右シフト命令
// 0~7: 不使用
// 8~10 第1オペランド
// 11~14: 命令コード
fn sr(ra: u16) -> u16 {
(Wrapping(SR) << 11).0 | (Wrapping(ra) << 8).0
}
// sra: shift right arithmetic
// 1bit右シフト命令。シフトする前の最上位ビットが0のときは0, 1のときは1が最上位に書き込まれる
// 0~7: 不使用
// 8~10 第1オペランド
// 11~14: 命令コード
fn sra(ra: u16) -> u16 {
(Wrapping(SRA) << 11).0 | (Wrapping(ra) << 8).0
}
// ldl: load immediate value low
// 第1オペランドで指定したレジスタのデータの **下位** 8ビットに第2オペランドの8ビットデータを直接書き込む
// 0~7: 第2オペランド
// 8~10: 第1オペランド
// 11~14: 命令コード
fn ldl(ra: u16, rb: u16) -> u16 {
(Wrapping(LDL) << 11).0 | (Wrapping(ra) << 8).0 | rb
}
// ldh: load immediate value high
// 第1オペランドで指定したレジスタのデータの **上位** 8ビットに第2オペランドの8ビットデータを直接書き込む
// 0~7: 第2オペランド
// 8~10: 第1オペランド
// 11~14: 命令コード
fn ldh(ra: u16, rb: u16) -> u16 {
(Wrapping(LDH) << 11).0 | (Wrapping(ra) << 8).0 | rb
}
// cmp: compare
// 第1オペランドと第2オペランドで指定したレジスタを比較し、完全一致の場合はFlagを1にして、そうでない場合はFlagを0にする
// 0~4: 不使用
// 5~7: 第2オペランド
// 8~10 第1オペランド
// 11~14: 命令コード
fn cmp(ra: u16, addr: u16) -> u16 {
(Wrapping(CMP) << 11).0 | (Wrapping(ra) << 8).0 | (Wrapping(addr) << 5).0
}
// je: jump equal
// Flagが1にセットされている場合にのみ、第2オペランドの8ビットをプログラムカウンタ(PC)に代入する。0の場合はなにもしない
// 0~7: 第2オペランド
// 8~10: 使わない
// 11~14: 命令コード
fn je(addr: u16) -> u16 {
(Wrapping(JE) << 11).0 | addr
}
// jmp: jump
// Flagの内容に影響されず、第2オペランドの8ビットをプログラムカウンタ(PC)に代入する。
// 0~7: 第2オペランド
// 8~10: 使わない
// 11~14: 命令コード
fn jmp(addr: u16) -> u16 {
(Wrapping(JMP) << 11).0 | addr
}
// ld: load memory
// 第2オペランドの8ビットをアドレスとするメインメモリの内容を第1オペランドで指定したレジスタに転送する
// 0~7: メインメモリアドレス
// 8~10: レジスタアドレス
// 11~14: 命令コード
fn ld(ra: u16, addr: u16) -> u16 {
(Wrapping(LD) << 11).0 | (Wrapping(ra) << 8).0 | addr
}
// st: store memory
// 第1オペランドで指定したレジスタの内容を第2オペランドの8ビットをアドレスとするメインメモリに転送する
// 0~7: メインメモリアドレス
// 8~10: レジスタアドレス
// 11~14: 命令コード
fn st(ra: u16, addr: u16) -> u16 {
(Wrapping(ST) << 11).0 | (Wrapping(ra) << 8).0 | addr
}
// hlt: halt
// プログラムカウンタの更新を停止する
// 0~10: 不使用
// 11~14: 命令コード
fn hlt() -> u16 {
(Wrapping(HLT) << 11).0
}
// 命令コード・オペランドの抽出
// 命令コードの抽出
// 命令コードのビット列は4ビット固定で必ず上位4ビットが命令となっている
fn op_code(ir: u16) -> u16 {
ir >> 11
}
// オペランド(REG_A)の抽出
fn op_reg_a(ir: u16) -> usize {
(ir >> 8 & 0b111) as usize
}
// オペランド(REG_B)の抽出
fn op_reg_b(ir: u16) -> usize {
(ir >> 5 & 0b111) as usize
}
// オペランド(data)の抽出
// 下位8ビットの抽出なのでビットシフトの必要はない
fn op_data(ir: u16) -> u16 {
ir & 0b1111_1111
}
// オペランド(addr)の抽出
// 下位8ビットの抽出なのでビットシフトの必要はない
fn op_addr(ir: u16) -> usize {
(ir & 0b1111_1111) as usize
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment