本文最后更新于 990 天前,其中的信息可能已经有所发展或是发生改变。
上一篇记录了一次CPU的制作过程:如何制作一个简易16位CPU – LovelyCat的小站
于是我又试着写了一个编译器来实现真正的可编程,下面只展示一些核心代码。
由于编程语言语法简单,编译器的逻辑也很简单,无数if for套上去就是了。
此编译器逐行读取代码,因此一行只能有一条指令。
语法
结构:指令 参数1, 参数2, 参数3…
例如:MOV R0, 1
ALU ADD, R1 = R0 + R1
核心代码
枚举
指令代码枚举类
public enum Code {
LOAD("0001",2, 8,ParamType.Register, ParamType.ADDR),
STORE("0010",2, 8,ParamType.Register, ParamType.ADDR),
MOV("0011",2, 8,ParamType.Register, ParamType.VALUE),
ALU("0100",4, 4,ParamType.VALUE, ParamType.Register, ParamType.Register, ParamType.Register),
HALT("0101",1, 1,ParamType.VALUE),
JUMP_A("0110",1, 12,ParamType.VALUE),
JUMP_S("0111",1, 12,ParamType.VALUE),
JUMP_GT("1000",5, 6,ParamType.VALUE, ParamType.Register, ParamType.Register, ParamType.BOOLEAN, ParamType.BOOLEAN),
JUMP_LT("1001",5, 6,ParamType.VALUE, ParamType.Register, ParamType.Register, ParamType.BOOLEAN, ParamType.BOOLEAN),
JUMP_EQ("1010",5, 6,ParamType.VALUE, ParamType.Register, ParamType.Register, ParamType.BOOLEAN, ParamType.BOOLEAN);
public String code;
public int paramsLength;
public ParamType[] paramTypes;
public int vLength;
Code(String code, int paramsLength, int vLength, ParamType... paramTypes) {
this.code = code;
this.paramsLength = paramsLength;
this.vLength = vLength;
this.paramTypes = paramTypes;
}
}
paramsLength是参数的个数,vLength是某参数的最大长度,code为4位指令。
同样可以为ALUCode创建一个枚举
ALU操作枚举
public enum ALUMode {
ADD("0001"),
SUB("0010");
public String code;
ALUMode(String code) {
this.code = code;
}
}
参数类型枚举
public enum ParamType {
Register,
ADDR,
VALUE,
BOOLEAN
}
数据检查与操作
初步检查参数是否合法
// 检查参数是否合法 合法返回空 不合法返回信息
public static String checkParamType(ParamType paramType, String value) {
switch (paramType) {
case Register:
if (regNameToADDR(value).equals("-2")) return String.format("未知寄存器 %s {%s}", value, value);
break;
case BOOLEAN:
if (value.length() != 1 || !isOnly2B(value)) return String.format("布尔值只能为0或1");
break;
}
return "";
}
检查参数长度
public static String GreatThanMaxLength(Code code, String v) {
return code.vLength < v.length() ? String.format("参数长度(%s)过长 最长应为 %s {%s}", v.length(), code.vLength, v) : "";
}
位拓展器
// 位拓展
public static String extend(String origin, int to, int mode) {
String ss = "";
String add = "";
switch (mode) {
case 0:
add = "0";
break;
case 1:
add = "1";
break;
}
for (int i=0;i<to - origin.length();i++) {
ss += add;
}
return ss + origin;
}
// 十进制整数转二进制并拓展位
public static String intTo2BAndExtend(String num, int extendTo, int extendMode) {
return extend(Integer.toBinaryString(Integer.parseInt(num)),extendTo, extendMode);
}
比较整数与指定位宽的二进制的大小
若isADDR为真则将vB转换成二进制再与a比较
// 比较 十进制数与指定位数的二进制数的大小 例如 a=9 bits=4 isADDR = true 则9 < 1111
public static String to2BAndGreatThan(String a, int bits, boolean isADDR) {
String extendedvB = extend("1", bits, 1);
long vB = Long.parseLong(extendedvB);
long vB10 = Long.parseLong(String.valueOf(vB), 2);
return Long.parseLong(a) > (isADDR ? vB : vB10) ? String.format("%s 数值太大 最大应为 %s(%s) [%s > %s] {%s}", a, extendedvB, vB10, a, vB10, a) : "";
}
构造ROM HEX
在Logisim里面我用的是地址位宽和数据位宽都为16的ROM/RAM。
因此从Logisim导出的ROM HEX文件中有16列4096行,刚好是65536个数据。
// 构造可以被Logisim ROM读取的文本
StringBuilder resultHEXROM = new StringBuilder();
resultHEXROM.append("v3.0 hex words addressed\n");
int codeIndex = 0;
int maxLines = 4096;
int maxColumns = 16;
for (int lineIndex=0;lineIndex<maxLines;lineIndex++) {
StringBuilder lineStr = new StringBuilder(" ");
for (int columnIndex=0;columnIndex<maxColumns;columnIndex++,codeIndex++)
lineStr.append(codeIndex > result6.size() - 1 ? "0000" : result6.get(codeIndex)).append(" ");
String lineHex = Integer.toHexString(lineIndex * 16);
String resultLine = extend(lineHex, 4, 0) + ":" + lineStr;
resultHEXROM.append(resultLine).append("\n");
}
主要判断逻辑
String preCode = code.code;
switch (code) {
case ALU:
preCode += ALUMode.valueOf(params[0]).code + regNameToADDR(params[1]) + regNameToADDR(params[2]) +regNameToADDR(params[3]) + "00";
break;
case MOV:
errorMsg = GreatThanMaxLength(code, params[1]);
if (!errorMsg.equals("")) break;
String movValue = intTo2BAndExtend(params[1], 8, 0);
errorMsg = to2BAndGreatThan(params[1], 8, paramTypes[1] == ParamType.ADDR);
preCode += regNameToADDR(params[0]) + "00" + movValue;
break;
case HALT:
String haltValue = params[0];
errorMsg = GreatThanMaxLength(code, haltValue);
if (!errorMsg.equals("")) break;
errorMsg = to2BAndGreatThan(haltValue, 12,paramTypes[0] == ParamType.ADDR);
preCode += extend(params[0], 12, 0);
break;
case LOAD:
case STORE:
String loadValue = params[1];
errorMsg = GreatThanMaxLength(code, loadValue);
if (!errorMsg.equals("")) break;
errorMsg = to2BAndGreatThan(loadValue, 8,paramTypes[1] == ParamType.ADDR);
preCode += regNameToADDR(params[0]) + "00" + loadValue;
break;
case JUMP_A:
case JUMP_S:
String jump_aValue = params[0];
errorMsg = GreatThanMaxLength(code, jump_aValue);
if (!errorMsg.equals("")) break;
errorMsg = to2BAndGreatThan(jump_aValue, 12,paramTypes[0] == ParamType.ADDR);
preCode += intTo2BAndExtend(jump_aValue,12,0);
break;
case JUMP_EQ:
case JUMP_GT:
case JUMP_LT:
String jump_eqValue = params[0];
errorMsg = GreatThanMaxLength(code, jump_eqValue);
if (!errorMsg.equals("")) break;
errorMsg = to2BAndGreatThan(jump_eqValue, 6,paramTypes[0] == ParamType.ADDR);
preCode += intTo2BAndExtend(jump_eqValue,6,0) + regNameToADDR(params[1]) + regNameToADDR(params[2]) + params[3] + params[4];
break;
}
result2.add(preCode);
编程测试
这次的代码用的是上一期演示的指令。
最后
到目前为止,整个项目就已经很完整了,有可以执行指令的CPU和编译器,现在可以轻松地编写出自制CPU可以运行的程序了。