今回は仮想命令のコードを Z80 のアセンブリ言語に変換する処理を追加しました。これも ChatGPT で作ってもらったものを改造しました。「CALL OUTPUT」で数値を出力できるものとします。Z80 は以前使ったことがあったので少しわかります。MZ-80K・MZ-80C のマニュアルが公開されていて、そこに Z80 命令表が記載されています。本もあったはずなのですが、今はあるかどうかわかりません。
class VirtualToZ80 { unordered_map<string, string> variables; public: void translate(map<int, string> program) { int labelCounter = 0; vector<int> destnums = getdestnums(); for (auto& [ln, code] : program) { stringstream ss(code); string instr; ss >> instr; if (std::find(destnums.begin(), destnums.end(), ln) != destnums.end()) { cout << "LABEL_" << ln << ":\n"; } if (instr == "PUSH") { int num; ss >> num; cout << " LD HL, " << num << "\n"; cout << " PUSH HL\n"; } else if (instr == "LOAD") { string var_src; ss >> var_src; string var = getVariable(var_src); cout << " LD HL, (" << var << ")\n"; cout << " PUSH HL\n"; } else if (instr == "STORE") { string var_src; ss >> var_src; string var = getVariable(var_src); cout << " POP HL\n"; cout << " LD (" << var << "), HL\n"; } else if (instr == "ADD") { binaryOp("ADD HL, DE"); } else if (instr == "SUB") { binaryOp("OR A\n SBC HL, DE"); } else if (instr == "MUL") { binaryMul(); } else if (instr == "DIV") { binaryDiv(); } else if (instr == "NEG") { cout << " POP HL\n"; cout << " LD DE, 0\n"; cout << " OR A\n"; cout << " SBC HL, DE\n"; cout << " PUSH HL\n"; } else if (isComparison(instr)) { compareOp(instr, labelCounter++); } else if (instr == "JMP") { string label; ss >> label; cout << " JP LABEL_" << label << "\n"; } else if (instr == "JNZ") { string label; ss >> label; cout << " POP HL\n"; cout << " LD A, H\n"; cout << " OR L\n"; cout << " JP NZ, LABEL_" << label << "\n"; } else if (instr == "CALL") { string label; ss >> label; cout << " CALL LABEL_" << label << "\n"; } else if (instr == "RET") { cout << " RET\n"; } else if (instr == "OUT") { cout << " CALL OUTPUT" << "\n"; } else if (instr == "IN") { string var_src; ss >> var_src; string var = getVariable(var_src); cout << " CALL INPUT" << "\n"; cout << " POP HL\n"; cout << " LD (" << var << "), HL\n"; } else if (instr == "REM") { // コメント string cmt; getline(ss, cmt); cout << ";" << cmt << "\n"; } else if (instr.back() == ':') { cout << code << "\n"; // ラベル表示 } else { cout << " ; Unknown instruction\n"; } } // データセグメントの出力 if (!variables.empty()) { cout << "\n; --- Variable Declarations ---\n"; for (auto& [name, label] : variables) { cout << label << ": DW 0\n"; } } } private: vector<string> tokenize(const string& line) { vector<string> tokens; size_t start = 0, end; while ((end = line.find(' ', start)) != string::npos) { if (end > start) tokens.push_back(line.substr(start, end - start)); start = end + 1; } if (start < line.size()) tokens.push_back(line.substr(start)); return tokens; } string getVariable(const string& varName) { if (!variables.count(varName)) { variables[varName] = "var_" + varName; } return variables[varName]; } bool isComparison(const string& op) { return op == "EQ" || op == "NE" || op == "LT" || op == "LE" || op == "GT" || op == "GE"; } void binaryOp(const string& opCode) { cout << " POP DE\n"; cout << " POP HL\n"; cout << " " << opCode << "\n"; cout << " PUSH HL\n"; } void binaryMul() { // 簡易な16ビット乗算(遅いがZ80にはMUL命令がない) cout << " POP DE\n"; cout << " POP HL\n"; cout << " CALL MUL16\n"; // ユーザ定義のサブルーチン cout << " PUSH HL\n"; } void binaryDiv() { // 簡易な16ビット除算 cout << " POP DE\n"; cout << " POP HL\n"; cout << " CALL DIV16\n"; // ユーザ定義のサブルーチン cout << " PUSH HL\n"; } void compareOp(const string& op, int labelId) { string labelTrue = "CMP_TRUE_" + to_string(labelId); string labelTrue1 = "CMP_TRUE1_" + to_string(labelId); string labelFalse = "CMP_FALSE_" + to_string(labelId); string labelEnd = "CMP_END_" + to_string(labelId); cout << " POP DE\n"; cout << " POP HL\n"; cout << " OR A\n"; cout << " SBC HL, DE\n"; string jmp; string jmp2; string lop; if (op == "EQ") jmp = "JP Z"; else if (op == "NE") jmp = "JP NZ"; else if (op == "LT") jmp = "JP M"; // 負 else if (op == "LE") { jmp = "JP Z"; jmp2 = "JP M"; lop = "or"; } else if (op == "GT") { jmp = "JP P"; jmp2 = "JP NZ"; lop = "and"; } else if (op == "GE") jmp = "JP P"; if (lop == "and") { cout << " " << jmp << " " << labelTrue1 << "\n"; cout << " JP " << labelFalse << "\n"; cout << labelTrue1 << ":\n"; cout << " " << jmp2 << " " << labelTrue << "\n"; cout << labelFalse << ":\n"; } else { cout << " " << jmp << " " << labelTrue << "\n"; } if (lop == "or") { cout << " " << jmp2 << " " << labelTrue << "\n"; } cout << " LD HL, 0\n"; cout << " PUSH HL\n"; cout << " JP " << labelEnd << "\n"; cout << labelTrue << ":\n"; cout << " LD HL, 1\n"; cout << " PUSH HL\n"; cout << labelEnd << ":\n"; } // 仮想命令コードの行き先になっている行番号を返す int getdestnum(const string& line) { stringstream ss(line); string cmd; ss >> cmd; cmd = to_upper(cmd); if (cmd == "JMP" || cmd == "JNZ" || cmd == "CALL") { int target; ss >> target; return target; } return -1; } // 仮想命令コードの行き先になっているすべての行番号を返す vector<int> getdestnums() { vector<int> result; auto pit = program.begin(); while (pit != program.end()) { int destnum = getdestnum(pit->second); if (destnum >= 0) { result.push_back(destnum); } pit++; } return result; } }; void compileVirtualToZ80() { VirtualToZ80 translator; translator.translate(program); }
フィボナッチ数列の例を変換した結果は以下のようになります。
; フィボナッチ数列 LD HL, 10 PUSH HL POP HL LD (var_N), HL LD HL, 1 PUSH HL POP HL LD (var_I), HL LD HL, 1 PUSH HL POP HL LD (var_A), HL LD HL, 1 PUSH HL POP HL LD (var_B), HL LABEL_100: LD HL, (var_A) PUSH HL CALL OUTPUT LD HL, (var_B) PUSH HL POP HL LD (var_T), HL LD HL, (var_A) PUSH HL LD HL, (var_B) PUSH HL POP DE POP HL ADD HL, DE PUSH HL POP HL LD (var_B), HL LD HL, (var_T) PUSH HL POP HL LD (var_A), HL LD HL, (var_I) PUSH HL LD HL, 1 PUSH HL POP DE POP HL ADD HL, DE PUSH HL POP HL LD (var_I), HL LD HL, (var_I) PUSH HL LD HL, (var_N) PUSH HL POP DE POP HL OR A SBC HL, DE JP Z CMP_TRUE_0 JP M CMP_TRUE_0 LD HL, 0 PUSH HL JP CMP_END_0 CMP_TRUE_0: LD HL, 1 PUSH HL CMP_END_0: POP HL LD A, H OR L JP NZ, LABEL_100 ; --- Variable Declarations --- var_N: DW 0 var_A: DW 0 var_I: DW 0 var_B: DW 0 var_T: DW 0
16ビット整数の乗算・除算
これも ChatGPT で作ってもらいました。
乗算
; HL = HL * DE ; 入力: HL, DE に符号なし16ビット整数 ; 出力: HL = HL * DE(下位16ビット) ; 使用レジスタ: HL, DE, BC, A MUL16: LD B, 16 ; 16ビット分ループ LD C, 0 LD A, 0 LD C, A ; C = 0 LD A, H OR L JR Z, MUL16_DONE ; HL = 0 なら終了 LD BC, 0 ; BC = 結果 (初期化) MUL16_LOOP: SLA L ; HL <<= 1(左シフト) RL H RL C ; シフトしながらキャリー保持 RL A JP NC, MUL16_SKIP_ADD ADD HL, DE ; キャリーが立っていたら DE を加える JP NC, MUL16_NO_OVERFLOW INC A ; オーバーフロー時、上位ビット記録 MUL16_NO_OVERFLOW: JP MUL16_SKIP_ADD MUL16_SKIP_ADD: DJNZ MUL16_LOOP LD HL, HL ; 結果は HL(下位16ビット) RET MUL16_DONE: LD HL, 0 RET
除算
; HL = 被除数, DE = 除数 ; 出力: HL = 商, DE = 余り ; 使用レジスタ: HL, DE, BC, A, IX DIV16: LD BC, 0 ; 商 = BC = 0 LD IX, 16 ; 16回繰り返す DIV16_LOOP: ; 左に 1 ビットシフト (HL = 余り, BC = 商) SLA L RL H RL C RL B ; 比較 HL vs DE PUSH BC PUSH HL OR A SBC HL, DE JP C, DIV16_SKIP_SUB ; HL >= DE の場合: ; HL = HL - DE, 商のビットを1にする LD A, C OR 1 LD C, A DIV16_SKIP_SUB: POP HL POP BC DEC IXH JP NZ, DIV16_LOOP ; 商 = BC, 余り = HL LD HL, BC RET








