CPUエミュレーター

pp.48ff.

テキストからはかなり書き換えた。andor&&||の代替表現として予約されているようなので、下のコード例ではひとまず_and_orにしてエスケープしておく。

#include <iostream>
#include "CPU_emulator_0.h"

/*
ニモニック(のようなもの)一覧
mov(ra, rb)   : RegA ← RegB

add(ra, rb)   : RegA ← RegA + RegB
sub(ra, rb)   : RegA ← RegA - RegB

_and(ra, rb)  : RegA ← RegA & RegB
_or(ra, rb)   : RegA ← RegA | RegB

sl(ra)        : 左シフト
sr(ra)        : 右シフト
sra(ra)       : 算術右シフト

ldl(ra, ival) : 下位バイトを即値で上書き
ldh(ra, ival) : 上位バイトを即値で上書き

cmp(ra, rb)   : 等しければフラグを立てる

je(addr)      : イコールフラグが立ったらジャンプ
jmp(addr)     : 強制ジャンプ

ld(ra, addr)  : RegA  ← *addr
st(ra, addr)  : *addr ← RegA

hlt           : 状態を保持して停止 
*/

int main(void) {
    // アセンブル(のようなこと)をして、ROM (に見立てた排列)へ格納する。
    // 1+2+ ... +10=55 を計算する。
    rom[0] = ldh(REG0, 0);     // レジスタ0 (総和の初期値)をクリアする。
    rom[1] = ldl(REG0, 0);

    rom[2] = ldh(REG1, 0);     // レジスタ1に1 (増分)をセットする。
    rom[3] = ldl(REG1, 1);

    rom[4] = ldh(REG2, 0);     // レジスタ2 (足す数の初期値)をクリアする。
    rom[5] = ldl(REG2, 0);

    rom[6] = ldh(REG3, 0);     // レジスタ3に10 (ループ回数)をセットする。
    rom[7] = ldl(REG3, 10);

    rom[8] = add(REG2, REG1);  // 足す数をインクリメントして、
    rom[9] = add(REG0, REG2);  // それをここまでの総和に加算して、
    rom[10] = st(REG0, 64);    // ここまでの総和をRAMの64番地へ格納して、
    rom[11] = cmp(REG2, REG3); // 足す数==ループ回数であるなら、
    rom[12] = je(14);          // ループを抜けるが、
    rom[13] = jmp(8);          // そうでなければ加算を繰り返す。

    rom[14] = hlt;

    // 実行直前の各レジスタの状態を表示して、実行して、
    execute();

    // 全ステップを実行し終えたら、最後に計算結果を表示する。
    printf("\nram[64] = %d\n", ram[64]);

    return 0;
}

 
CPU_emulator_0.h

#ifndef _CPU_EMULATOR_0_H_
#define _CPU_EMULATOR_0_H_

enum { MOV, ADD, SUB, AND, OR, SL, SR, SRA, LDL, LDH, CMP, JE, JMP, LD, ST, HLT };
enum { REG0, REG1, REG2, REG3, REG4, REG5, REG6, REG7 };

#define mov(ra, rb)   (MOV << 11 | (ra) << 8 | (rb) << 5)       // RegA ← RegB

#define add(ra, rb)   (ADD << 11 | (ra) << 8 | (rb) << 5)       // RegA ← RegA + RegB
#define sub(ra, rb)   (SUB << 11 | (ra) << 8 | (rb) << 5)       // RegA ← RegA - RegB

#define _and(ra, rb)  (AND << 11 | (ra) << 8 | (rb) << 5)       // RegA ← RegA & RegB
#define _or(ra, rb)   (OR  << 11 | (ra) << 8 | (rb) << 5)       // RegA ← RegA | RegB

#define sl(ra)        (SL  << 11 | (ra) << 8)                   // RegA ← RegA << 1
#define sr(ra)        (SR  << 11 | (ra) << 8)                   // RegA ← RegA >> 1
#define sra(ra)       (SRA << 11 | (ra) << 8)                   // RegA ← (RegA & 0x8000) | RegA >> 1

#define ldl(ra, ival) (LDL << 11 | (ra) << 8 | ((ival) & 0xFF)) // RegA ← (RegA & 0xFF00) | ival
#define ldh(ra, ival) (LDH << 11 | (ra) << 8 | ((ival) & 0xFF)) // RegA ← (RegA & 0x00FF) | ival << 8

#define cmp(ra, rb)   (CMP << 11 | (ra) << 8 | (rb) << 5)       // Flag ← true if(RegA==RegB)

#define je(addr)      (JE  << 11 | ((addr) & 0xFF))             // PC ← addr if(Flag)
#define jmp(addr)     (JMP << 11 | ((addr) & 0xFF))             // PC ← addr

#define ld(ra, addr)  (LD  << 11 | (ra) << 8 | ((addr) & 0xFF)) // RegA  ← *addr
#define st(ra, addr)  (ST  << 11 | (ra) << 8 | ((addr) & 0xFF)) // *addr ← RegA

#define hlt           (HLT << 11)


#define op_code(ir) ((ir) >> 11 & 0xF)   // オペコード      (bit14:11)を抽出する。
#define op_regA(ir) ((ir) >>  8 & 0b111) // オペランド(RegA)(bit10: 8)を抽出する。
#define op_regB(ir) ((ir) >>  5 & 0b111) // オペランド(RegB)(bit 7: 5)を抽出する。
#define op_data(ir) ((ir)       & 0xFF)  // オペランド(data)(bit 7: 0)を抽出する。
#define op_addr(ir) ((ir)       & 0xFF)  // オペランド(addr)(bit 7: 0)を抽出する。


// 16ビットレジスタ×8本、16ビット×256ステップのROM、16ビット×256ステップのRAM (に見立てた排列)を用意する。
uint16_t reg[8], rom[256], ram[256];

// 命令レジスタ(に見立てた16ビット変数)を用意する。
uint16_t ir;

// プログラムカウンター(に見立てた8ビット変数)を用意する。
uint8_t  pc = 0;

// 比較フラグ(に見立てたブール変数)を用意する。
bool flag_eq = false;


void execute(void) {
    printf("         PC     IR  REG0  REG1  REG2  REG3  REG4  REG5  REG6  REG7\n");

    uint8_t inst;
    do {
        // 命令をROMから命令レジスタへフェッチして、
        ir = rom[pc];

        // 確認のため各レジスタの内容を表示して、
        printf("実行前: %3d 0x%04X %5d %5d %5d %5d %5d %5d %5d %5d\n", pc, ir, reg[0], reg[1], reg[2], reg[3], reg[4], reg[5], reg[6], reg[7]);

        // プログラムカウンターをインクリメントして、
        pc++;

        // デコードして、実行して、ライトバックする。
        switch (inst = op_code(ir)) {
        case MOV: reg[op_regA(ir)] = reg[op_regB(ir)]; break;

        case ADD: reg[op_regA(ir)] += reg[op_regB(ir)]; break;
        case SUB: reg[op_regA(ir)] -= reg[op_regB(ir)]; break;

        case AND: reg[op_regA(ir)] &= reg[op_regB(ir)]; break;
        case OR: reg[op_regA(ir)]  |= reg[op_regB(ir)]; break;

        case SL: reg[op_regA(ir)] <<= 1; break;
        case SR: reg[op_regA(ir)] >>= 1; break;
        case SRA: reg[op_regA(ir)] = (reg[op_regA(ir)] & 0x8000) | reg[op_regA(ir)] >> 1; break;

        case LDL: reg[op_regA(ir)] = (reg[op_regA(ir)] & 0xFF00) | (op_data(ir) & 0xFF); break;
        case LDH: reg[op_regA(ir)] = (reg[op_regA(ir)] & 0x00FF) | op_data(ir) << 8; break;

        case CMP: reg[op_regA(ir)] == reg[op_regB(ir)] ? flag_eq = true : flag_eq = false; break;

        case JE: if (flag_eq) { pc = op_addr(ir); }; break;
        case JMP:               pc = op_addr(ir); break;

        case LD: reg[op_regA(ir)] = ram[op_addr(ir)]; break;
        case ST: ram[op_addr(ir)] = reg[op_regA(ir)]; break;

        case HLT: break;
        default: break;
        }
        //printf("実行後: %3d 0x%04X %5d %5d %5d %5d %5d %5d %5d %5d\n", pc, ir, reg[0], reg[1], reg[2], reg[3], reg[4], reg[5], reg[6], reg[7]);
    } while (inst != HLT);

    printf("         PC     IR  REG0  REG1  REG2  REG3  REG4  REG5  REG6  REG7\n");
}

#endif

実行結果