非専門的シンギュラリティー研究所

無限に動き続けるシステムを表す方法を AI なども使って考えていきます。

昔ながらの BASIC(5)

今回は仮想命令のコードを 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