计算机系统基础的课内作业:简易汇编器开发(C++)
一.作业要求
请开发一个RISC-V汇编器,支持自动将以下RISC-V汇编指令编译成RISC-V机器码。要求如下:
(1)该汇编器需支持的汇编命令包含:lui,sw,addi,add。
(2)汇编器运行方式:创建一个名为source.s的汇编源代码文本,从附件1中随意挑选4条lui,sw,addi,add汇编测试语句,将这3条汇编语句写入到source.s文本中。然后用开发的汇编器运行该文本,并将生成的机器码输出到另一个名为binary.txt的文本中。最后再比较生成的机器码与附件1中的机器码是否一致,从而验证汇编器的功能(生成的机器码在binary.txt中用16进制表示)。
(3)汇编器功能要求如下:
a)能进行语法纠错,例如:如果输入luik a3, 0x200,会进行报错,并指出语法错误的位置luik。
b)能检查参数的非法范围,例如,若输入lui a3, 0x2000000,则会提醒0x2000000超出合法界限。
c)不考虑压缩型指令,所以指令机器码均为4字节(32位)。
(4)汇编器使用的开发语言没有限制。
二.实现步骤
1.解析汇编代码:解析源代码文件 “source.s” 。读取文件的每一行,并将其分割成指令和参数。
2.指令识别:识别指令lui,sw,addi,add,并提取相关的参数:指令instruction,三个操作数operand(对于lui指令,实际上只有两个操作数,lui a5,0x20000,但是可以把“x”当初标点符号分隔出三个操作数a5 0 20000,其中第二个操作数0我们不需要,可以不管)。
3.参数处理:对于每个参数,确定它是寄存器还是立即数,并将其转换为适当的格式。例如,32个寄存器定义了相对应的map,可以根据不同寄存器名称获取寄存器的编号(如a0对应得寄存器的编号为10),立即数转换为相应的二进制。
4.错误检查:检查语法错误(例如,未知的指令或寄存器),以及参数的非法范围(例如,过大或过小的立即数)。
5.机器码生成:所有的指令和参数都被正确地识别和处理之后就可以开始生成机器码了。将指令和参数编码为32位二进制,然后再把二进制转为8位十六进制字符串。
6.输出结果:将生成的机器码写入到输出文件 “binary.txt” 中。
三.源代码
assembler.cpp
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <string>
using namespace std;
#define SOURCE "F:\\Assembler\\source.s"
#define BINARY "F:\\Assembler\\binary.txt"
bool isPunctuation(char c);
vector<string> splitOperands(const string& input);
string toBinary(int value, int width);
string assembleInstruction(string line);
string binaryToHex(string binary);
// 定义4条指令和对应的操作码映射
map<string, string> instructionMap = {
{"lui", "0110111"},
{"sw", "0100011"},
{"addi", "0010011"},
{"add", "0110011"}
};
// 定义功能码
map<string, string> functionMap = {
{"sw", "010"},
{"addi", "000"},
{"add", "000"}
};
// 定义32个不同寄存器的映射
map<string, int> registerMap = {
{"zero", 0},{"ra", 1},{"sp", 2},{"gp", 3},
{"tp", 4},{"t0", 5},{"t1", 6},{"t2", 7},
{"s0", 8},{"s1", 9},{"a0", 10},{"a1", 11},
{"a2", 12},{"a3", 13},{"a4", 14},{"a5", 15},
{"a6", 16},{"a7", 17},{"s2", 18},{"s3", 19},
{"s4", 20},{"s5", 21},{"s6", 22},{"s7", 23},
{"s8", 24},{"s9", 25},{"s10", 26},{"s11", 27},
{"t3", 28},{"t4", 29},{"t5", 30},{"t6", 31},
};
// 测试代码
void test()
{
string input = "addi gp,gp,-2048";
cout << binaryToHex(assembleInstruction(input)) << endl;;
}
int main() {
ifstream inputFile(SOURCE);
ofstream outputFile(BINARY);
if (!inputFile.is_open() || !outputFile.is_open()) {
cerr << "无法打开文件" << endl;
return 1;
}
string line;
while (getline(inputFile, line)) {
string machineCode = binaryToHex(assembleInstruction(line));
if (machineCode != "") {
outputFile << machineCode << endl;
cout << line << endl;
}
}
inputFile.close();
outputFile.close();
//test();
return 0;
}
// 判断每个字符是否为以下标点符号
bool isPunctuation(char c) {
return (c == ' ' || c == ',' || c == '.' || c == '!' || c == 'x' || c == ';' || c == ':' || c == '(' || c == ')');
}
// 根据以上标点分隔每个操作数
vector<string> splitOperands(const string& input) {
vector<string> operands;
size_t start = 0;
size_t end = 0;
while (end != string::npos) {
start = input.find_first_not_of(" ,.!?;:()x", end);
if (start == string::npos) break;
end = input.find_first_of(" ,.!?;:()x", start);
string operand = input.substr(start, end - start);
operands.push_back(operand);
}
return operands;
}
// 将寄存器编号转为5位二进制
string toBinary(int value, int width) {
string result;
for (int i = width - 1; i >= 0; i--) {
char bit = ((value >> i) & 1) + '0';
result.push_back(bit);
}
return result;
}
// 编译四条指令
string assembleInstruction(string line) {
string instruction, operand1, operand2, operand3;
// 使用自定义的 splitOperands 函数分割操作数
vector<string> operands = splitOperands(line);
instruction = operands[0];
operand1 = operands[1];
operand2 = operands[2];
operand3 = operands[3];
if (instructionMap.find(instruction) == instructionMap.end()) {
cerr << "错误:无效的指令: " << instruction << endl;
return "";
}
string opcode = instructionMap[instruction];
if (instruction == "lui") {
if (registerMap.find(operand1) == registerMap.end()) {
cerr << "错误:无效的寄存器 " << operand1 << endl;
return "";
}
int immediate = stoi(operand3, nullptr, 0);
//把十六进制的立即数转为十进制,因为toBinary只支持十进制转二进制
char buffer[6];
int i;
for (i = 0; i < operand3.length(); i++)
buffer[i] = operand3[i];
char* stop;
buffer[i] = '\0';
int ans = strtol(buffer, &stop, 16);
if (ans >= (1 << 20)) {
cerr << "错误:立即数超出合法范围" << endl;
return "";
}
return toBinary(ans, 20) + toBinary(registerMap[operand1], 5) + opcode;
}
else if (instruction == "sw") {
if (registerMap.find(operand1) == registerMap.end() || registerMap.find(operand3) == registerMap.end()) {
cerr << "错误:无效的寄存器 " << operand1 << " 或 " << operand3 << endl;
return "";
}
int offset = atoi(operand2.c_str());
string temp = toBinary(offset, 12);
return temp.substr(0, 7) + toBinary(registerMap[operand1], 5) + toBinary(registerMap[operand3], 5) + functionMap[instruction] + temp.substr(7, 5) + opcode;
}
else if (instruction == "addi") {
if (registerMap.find(operand1) == registerMap.end() || registerMap.find(operand2) == registerMap.end()) {
cerr << "错误:无效的寄存器 " << operand1 << " 或 " << operand2 << endl;
return "";
}
int immediate = stoi(operand3, nullptr, 0);
if (immediate > 2047 || immediate < -2048) {
cerr << "错误:立即数超出合法范围" << endl;
return "";
}
return toBinary(immediate, 12) + toBinary(registerMap[operand2], 5) + functionMap[instruction] + toBinary(registerMap[operand1], 5) + opcode;
}
else if (instruction == "add") {
if (registerMap.find(operand1) == registerMap.end() || registerMap.find(operand2) == registerMap.end() ||
registerMap.find(operand3) == registerMap.end()) {
cerr << "错误:无效的寄存器 " << operand1 << " 或 " << operand2 << " 或 " << operand3 << endl;
return "";
}
return "0000000" + toBinary(registerMap[operand3], 5) + toBinary(registerMap[operand2], 5) + functionMap[instruction] + toBinary(registerMap[operand1], 5) + opcode;
}
return "";
}
// 将32位二进制代码转为8位十六进制
string binaryToHex(string binary) {
if (binary.length() % 4 != 0) {
cerr << "错误:输入的二进制字符串长度不是4的倍数" << endl;
return "";
}
string result;
for (size_t i = 0; i < binary.length(); i += 4) {
string nibble = binary.substr(i, 4);
int decimalValue = stoi(nibble, nullptr, 2);
char hexDigit = (decimalValue < 10) ? (decimalValue + '0') : (decimalValue - 10 + 'A');
result.push_back(hexDigit);
}
return result;
}
四.运行截图
五.拓展指令数量
如果有拓展多条指令的需求,可以在instructionMap中添加需要的指令和指令操作码,然后在assembleInstruction中实现编译(可仿照原有的四条指令,分别对三个操作数编译成对应的二进制字符串,然后按指令格式拼接这些字符串)。