《计算机系统要素》Day07 组装计算机

·
瀚星空 计算机系统要素 Nand2tetris February 27, 2018

前几天,我们实现了构成一个计算机的所有必须部件:RAM,ROM,CPU。

今天,我们把这些部件集成起来,构成我们完整的计算机。

组装的过程就很简单了,只需要把对应的接口(Interface)接好就行:

CHIP Computer {

    IN reset;

    PARTS:
    CPU(inM=inM, instruction=instruction, reset=reset, outM=MemoryIn, writeM=MemoryLoad, addressM=MemoryAddress, pc=ROMAddress);
    ROM32K(address=ROMAddress, out=instruction);
    Memory(in=MemoryIn, load=MemoryLoad, address=MemoryAddress, out=inM);
}

上硬件模拟器(Emulator)看看:

mark

看起来不错,我们看看能不能运行:

mark

完美,这证明我们前几天设计的 RAM,CPU 都没有问题

DAY 9 编译(Compiler)器

编译器,即把汇编语言翻译成二进制代码,编译器的大部分工作很简单,就是全局替换而已。我们先复习一下 HACK 的汇编指令以及他对弈的二进制代码

A 指令 :

mark

C 指令:

mark

真正比较复杂的是汇编中出现的 Symbol 、 Label 以及 变量。

Symbol 是 HACK 汇编预定义好的,只要做简单的替换即可,汇编表如下:

mark

而 Label 就比较复杂,因为 Label 是下一行跳转指令要跳转的位置,由于需要实现向下跳转,如果我们不把整个汇编文件读取完毕的话,是无法知道所有的 Label 所代表的内存地址的,因此,在逐行翻译之前,我们必须对整个汇编文件进行一次读取,并给每一条汇编指令编写行号。

mark

其中,A 指令和 C 指令翻译时需要占用一行,Label 指令在二进制代码中不翻译,因此不需要额外的行数,全部读取完后,Label 的下一行 A或C指令所对应的二进制代码行号就是这个 Label 翻译成二进制时所指代的地址。

mark

除了预定义 Symbol 和 Label 外,汇编中还有 自定义 Symbol 也就是 变量 无法被简单替换,在第一次预读取时,我们给所有的 Label 分配了地址,第一次中未被分配的其他单词,就是变量了,变量从第16位开始,依次分配,我们需要进行第二次预读取,才能给变量分配地址。

由于需要进行多次的读取操作,因此我们最好需要封装一个对象替我们把源代码进行处理,处理时,我们需要去掉所有的空格,去掉所有的注释,同时,如果这个对象能替我们判断这一行的指令类型,并提取出关键词,编译工作会顺利很多。

由于编译工作涉及到大量的字符串处理,为了加快开发,我这里使用 JAVA 语言作为编译器的开发语言,同时,JAVA 具有较好的面向对象特性,也能较好的按照教材的思路进行编译器的开发。

高级语言的编写不在本章的讨论范围内,因此在这里直接给出源代码:

Main.java

import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    static private String SRC = "Pong.asm"; //编译文件路径
    public static void main(String[] args) throws IOException {
        System.out.println(System.getProperty("user.dir"));
        FileWriter fo = new FileWriter("out.hack");
        BufferedWriter bfo = new BufferedWriter(fo);
        FileInputStream fs = new FileInputStream(SRC);
        HackParser hackParser = new HackParser(fs);
        SymbolTable symbolTable = new SymbolTable(SRC);
        while (hackParser.hasMoreCommands()) {
            hackParser.advance();
            System.out.println("当前行指令:" + hackParser.nowLine);
            if (hackParser.commandType().equals("A_COMMAND")) {
                System.out.println("Symbol: " + hackParser.symbol());
                System.out.println(symbolTable.getAddress(hackParser.symbol()));
                bfo.write(symbolTable.getAddress(hackParser.symbol()) + "\n");
            } else if (hackParser.commandType().equals("C_COMMAND")) {
                bfo.write("111" + Code.comp(hackParser.comp()) + Code.dest(hackParser.dest()) + Code.jump(hackParser.jump()) + "\n");
                System.out.println("111" + Code.comp(hackParser.comp()) + Code.dest(hackParser.dest()) + Code.jump(hackParser.jump()));
            }
        }
        bfo.close();
        fo.close();
    }
}

HackParser.java

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner;

/**
 * Created by SincereXIA on 2018/1/29.
 *
 * @author SincereXIA.
 * @Time 2018/1/29 11:27.
 */
public class HackParser {
    public String nowLine = "";
    private Scanner asmSrc;
    private Boolean hasNext = true;

    HackParser(FileInputStream asmSrc) throws IOException {
        this.asmSrc = new Scanner(asmSrc);
        nowLine = "";
        hasNext = true;
    }

    /**
     * 输入流是否还有更多命令
     *
     * @return
     */
    public boolean hasMoreCommands() {
        return hasNext;
    }

    /**
     * 从输入中读取下一条命令
     */
    public void advance() {
        nowLine = "";
        while (nowLine.equals("") && asmSrc.hasNextLine()) {
            String s;
            s = asmSrc.nextLine();
            s = s.replace(" ", "");
            if (s.isEmpty() || s.charAt(0) == '/') {
                continue;
            }
            if (s.contains("//")) {
                nowLine = s.substring(0, s.indexOf("//"));
            } else nowLine = s;
            hasNext = asmSrc.hasNextLine();
        }
    }

    /**
     * 返回当前命令类型: A_COMMAND, C_COMMAND, L_COMMAND
     *
     * @return
     */
    public String commandType() {

        char typeChar = nowLine.charAt(0);
        if (typeChar == '@') return "A_COMMAND";
        if (typeChar == '(') return "L_COMMAND";
        return "C_COMMAND";
    }

    /**
     * 返回当前A指令符号
     *
     * @return
     */
    public String symbol() {
        return nowLine.substring(1);
    }

    public String Label() {
        return nowLine.substring(nowLine.indexOf('(') + 1, nowLine.indexOf(')'));
    }

    /**
     * 当前指令的 dest 助记符
     *
     * @return
     */
    public String dest() {
        if (!nowLine.contains("=")) {
            return "";
        } else {
            return nowLine.substring(0, nowLine.indexOf("="));
        }
    }

    public String comp() {
        int start;
        if (!nowLine.contains("=")) {
            start = 0;
        } else {
            start = nowLine.indexOf("=") + 1;
        }
        int end;
        if (!nowLine.contains(";")) {
            end = nowLine.length();
        } else {
            end = nowLine.indexOf(";");
        }
        return nowLine.substring(start, end);
    }

    public String jump() {
        if (!nowLine.contains(";")) {
            return "";
        } else {
            return nowLine.substring(nowLine.indexOf(";") + 1);
        }
    }
}

Code.java

/**
 * Created by SincereXIA on 2018/1/29.
 *
 * @author SincereXIA.
 * @Time 2018/1/29 13:47.
 */
public class Code {
    static public String dest(String code) {
        StringBuilder stringBuilder = new StringBuilder("000");
        if (code.contains("A")) {
            stringBuilder.replace(0, 1, "1");
        }
        if (code.contains("D")) {
            stringBuilder.replace(1, 2, "1");
        }
        if (code.contains("M")) {
            stringBuilder.replace(2, 3, "1");
        }
        return stringBuilder.toString();
    }

    static public String comp(String code) {
        if (code.equals("0")) return "0101010";
        else if (code.equals("1")) return "0111111";
        else if (code.equals("-1")) return "0111010";
        else if (code.equals("D")) return "0001100";
        else if (code.equals("A")) return "0110000";
        else if (code.equals("M")) return "1110000";
        else if (code.equals("!D")) return "0001101";
        else if (code.equals("!A")) return "0110001";
        else if (code.equals("!M")) return "1110001";
        else if (code.equals("-D")) return "0001111";
        else if (code.equals("-A")) return "0110011";
        else if (code.equals("-M")) return "1110011";
        else if (code.equals("D+1")) return "0011111";
        else if (code.equals("A+1")) return "0110111";
        else if (code.equals("M+1")) return "1110111";
        else if (code.equals("D-1")) return "0001110";
        else if (code.equals("A-1")) return "0110010";
        else if (code.equals("M-1")) return "1110010";
        else if (code.equals("D+A")) return "0000010";
        else if (code.equals("D+M")) return "1000010";
        else if (code.equals("D-A")) return "0010011";
        else if (code.equals("D-M")) return "1010011";
        else if (code.equals("A-D")) return "0000111";
        else if (code.equals("M-D")) return "1000111";
        else if (code.equals("D&A")) return "0000000";
        else if (code.equals("D&M")) return "1000000";
        else if (code.equals("D|A")) return "0010101";
        else if (code.equals("D|M")) return "1010101";

        return "error";
    }

    static public String jump(String code) {
        switch (code) {
            case "JGT":
                return "001";
            case "JEQ":
                return "010";
            case "JGE":
                return "011";
            case "JLT":
                return "100";
            case "JNE":
                return "101";
            case "JLE":
                return "110";
            case "JMP":
                return "111";
            default:
                return "000";
        }
    }
}

SymbolTable.java

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.regex.Pattern;

/**
 * Created by SincereXIA on 2018/1/29.
 *
 * @author SincereXIA.
 * @Time 2018/1/29 14:40.
 */
public class SymbolTable {
    private HashMap<String, Integer> symbolTable;
    private HackParser hackParser;
    private int addressNow;
    private String fileName;

    SymbolTable(String fileName) throws IOException {
        FileInputStream fs = new FileInputStream(fileName);
        hackParser = new HackParser(fs);
        symbolTable = new HashMap();
        symbolTable.put("SP", 0);
        symbolTable.put("LCL", 1);
        symbolTable.put("ARG", 2);
        symbolTable.put("THIS", 3);
        symbolTable.put("THAT", 4);
        symbolTable.put("R0", 0);
        symbolTable.put("R1", 1);
        symbolTable.put("R2", 2);
        symbolTable.put("R3", 3);
        symbolTable.put("R4", 4);
        symbolTable.put("R5", 5);
        symbolTable.put("R6", 6);
        symbolTable.put("R7", 7);
        symbolTable.put("R8", 8);
        symbolTable.put("R9", 9);
        symbolTable.put("R10", 10);
        symbolTable.put("R11", 11);
        symbolTable.put("R12", 12);
        symbolTable.put("R13", 13);
        symbolTable.put("R14", 14);
        symbolTable.put("R15", 15);
        symbolTable.put("SCREEN", 16384);
        symbolTable.put("KBD", 24576);

        addressNow = 0;

        while (hackParser.hasMoreCommands()) {
            hackParser.advance();
            if (hackParser.commandType().equals("A_COMMAND")) {
                addressNow++;
            } else if (hackParser.commandType().equals("C_COMMAND")) {
                addressNow++;
            } else if (hackParser.commandType().equals("L_COMMAND")) {
                symbolTable.put(hackParser.Label(), addressNow);
            }
        }

        addressNow = 16;
        fs.close();
        fs = new FileInputStream(fileName);
        hackParser = new HackParser(fs);
        while (hackParser.hasMoreCommands()) {
            hackParser.advance();
            if (hackParser.commandType().equals("A_COMMAND")) {
                if (!symbolTable.containsKey(hackParser.symbol()) && !isInteger(hackParser.symbol())) {
                    symbolTable.put(hackParser.symbol(), addressNow);
                    addressNow++;
                }
            }
        }
    }

    public static boolean isInteger(String str) {
        Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
        return pattern.matcher(str).matches();
    }

    public String getAddress(String label) {
        Integer address;
        String string;
        if (symbolTable.containsKey(label)) {
            address = symbolTable.get(label);
        } else {
            address = Integer.valueOf(label);
        }
        string = Integer.toBinaryString(address);
        StringBuilder sb = new StringBuilder(string);
        for (int i = sb.length(); i <= 15; i++) {
            sb.insert(0, '0');
        }
        return sb.toString();
    }
}

我编写的编译器共有四个类,执行 Main.java 输出 output.hack,为编译好的二进制代码文件。

用这个编译器编译的 Pong.hack 如下图所示,可以看到,和官方编译器的编译结果完全相同:

mark

整个汇编代码共 27483 行,是一个用方向键控制挡板,反弹小球的游戏,CPU 模拟器执行代码效果如下:

mark

至此,我们就完成了整个 hack 平台硬件体系的设计与制作,至此,我们的 HACK 计算机有了整套的 RAM、ROM、CPU、输入输出设备,有了一整套汇编指令和汇编编译器,能运行汇编语言编写的小程序,接下来,我们会进行 HACK 平台软件体系的建设,HAPPY CODEING!!

  • 《计算机系统要素》Day06 构建一个CPU
  • 配置 windows UWP 应用,使用代理服务器

_(:3 」∠)_
(抱歉)
这里不能评论哦


© 2023 夏末阁. Using Typecho & Moricolor.

陕ICP备16018536号-1