nand2tetris-rs

I try doing [Nand to Tetris](https://www.nand2tetris.org/) in Rust.


Project maintained by KBone12 Hosted on GitHub Pages — Theme by mattgraham

トップページ

Computer Architecture

Project 4 では仮想マシン上でアセンブリを動かしているので、先に Project 5 を行います。この Project 5 では CPU を実装する以外はほとんど組み立てるだけです。

Memory

これはそのまま、 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

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);
    }
}

Computer

いよいよまとめ上げです。本家のページにあげられている図の通りに結んでいけば実装できます。

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 に進みます。