I try doing [Nand to Tetris](https://www.nand2tetris.org/) in Rust.
Project 4 では仮想マシン上でアセンブリを動かしているので、先に Project 5 を行います。この Project 5 では CPU を実装する以外はほとんど組み立てるだけです。
これはそのまま、 Ram16k, Screen, Keyboard を組み立てるだけです。特に、まだ外部の入出力は考慮していないので Screen, Keyboard は Ram や Register そのままです。
pub struct Memory {
address: [bool; 15],
load: bool,
input: [bool; 16],
ram: Ram16k,
screen: Ram8k,
keyboard: Register,
}
impl Memory {
pub fn new() -> Self {
Self {
address: [false; 15],
load: false,
input: [false; 16],
ram: Ram16k::new(),
screen: Ram8k::new(),
keyboard: Register::new(),
}
}
pub fn get_output(&self) -> [bool; 16] {
mux16(
&self.ram.get_output(),
&mux16(
&self.screen.get_output(),
&self.keyboard.get_output(),
self.address[1],
),
self.address[0],
)
}
pub fn tick(&mut self, address: &[bool; 15], load: bool, input: &[bool; 16]) {
// 長いので省略
}
}
Cpu も基本的には組み立てるだけですが、受け取った instruction を分解する必要があります。 A-instruction については簡単で、最上位ビットが 0 であることを確認すれば残りを A レジスタに入れるだけです。 C-instruction は instruction の上から 3 ビットは固定であり、残りを ALU と 各レジスタや multiplexor の切り替え用、そして JMP 命令用に振り分ける必要があります。 JMP 命令用の振り分けは、 JLE, JEQ, JGT それぞれを実装して PC の load にその OR を入力すれば実装できます。
pub struct Cpu {
address: [bool; 15],
write_to_memory: bool,
result: [bool; 16],
a: Register,
d: Register,
pc: Pc,
}
impl Cpu {
pub fn new() -> Self {
Self {
address: [false; 15],
write_to_memory: false,
result: [false; 16],
a: Register::new(),
d: Register::new(),
pc: Pc::new(),
}
}
pub fn get_output(&self) -> (([bool; 15], bool, [bool; 16]), [bool; 16]) {
(
(self.address, self.write_to_memory, self.result),
self.pc.get_output(),
)
}
pub fn tick(&mut self, reset: bool, m: &[bool; 16], instruction: &[bool; 16]) {
let write_to_a = or(not(instruction[0]), instruction[10]);
let use_m = and(instruction[0], instruction[3]);
let zero_d = and(instruction[0], instruction[4]);
let negate_d = and(instruction[0], instruction[5]);
let zero_a = and(instruction[0], instruction[6]);
let negate_a = and(instruction[0], instruction[7]);
let f = and(instruction[0], instruction[8]);
let negate_alu = and(instruction[0], instruction[9]);
let write_to_d = and(instruction[0], instruction[11]);
self.write_to_memory = and(instruction[0], instruction[12]);
let (result, zero, negate) = alu(
&self.d.get_output(),
&mux16(&self.a.get_output(), m, use_m),
zero_d,
negate_d,
zero_a,
negate_a,
f,
negate_alu,
);
self.result = result;
let a = self.a.get_output();
self.address = [
a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13],
a[14], a[15],
];
self.pc.tick(
reset,
or(
and(instruction[13], negate),
or(
and(instruction[14], zero),
and(instruction[15], and(not(zero), not(negate))),
),
),
true,
&a,
);
let mut a_data = *instruction;
a_data[0] = false;
self.a
.tick(write_to_a, &mux16(&a_data, &self.result, instruction[0]));
self.d.tick(write_to_d, &self.result);
}
}
いよいよまとめ上げです。本家のページにあげられている図の通りに結んでいけば実装できます。
pub struct Computer {
rom: Rom,
cpu: Cpu,
memory: Memory,
}
impl Computer {
pub fn new() -> Self {
Self {
rom: Rom::new(),
cpu: Cpu::new(),
memory: Memory::new(),
}
}
pub fn tick(&mut self, reset: bool) {
let ((address, write_to_memory, cpu_output), pc) = self.cpu.get_output();
self.memory.tick(&address, write_to_memory, &cpu_output);
let memory_data = self.memory.get_output();
let pc = [
pc[1], pc[2], pc[3], pc[4], pc[5], pc[6], pc[7], pc[8], pc[9], pc[10], pc[11], pc[12],
pc[13], pc[14], pc[15],
];
self.rom.set_address(&pc);
let instruction = self.rom.get_output();
self.cpu.tick(reset, &memory_data, &instruction);
}
}
ここまでで一応ハードウェア部分全体を実装したことにはなりましたが、現状だと画面出力やキーボード入力等ができません。それらを実装した上で、 Project 4 や Project 6 に進みます。