PythonでRISC-Vの簡単な仮想マシン

このPythonコードは、簡単なRISC-V仮想マシン（VM）を実装したものです。この仮想マシンは、メモリ、レジスタ、プログラムカウンタを持ち、いくつかの基本的なRISC-V命令（ADD, ADDI, BEQ, JAL, HALT）を実行できます。

riscv_vm.py
~~~
class RISCV_VM:
    def __init__(self, memory_size=256):
        self.registers = [0] * 32  # 32個の整数レジスタ
        self.memory = [0] * memory_size  # メモリ領域
        self.pc = 0  # プログラムカウンタ
        self.running = True
  
    def load_program(self, program):
       self.memory[:len(program)] = program
    
    def fetch(self):
        instr = self.memory[self.pc]
        self.pc += 1
        return instr
    
    def decode_execute(self, instr):
        opcode = instr & 0x7F  # 7ビットのオペコード
        rd = (instr >> 7) & 0x1F
        rs1 = (instr >> 15) & 0x1F
        rs2 = (instr >> 20) & 0x1F
        imm = (instr >> 20)  # 即値
        
        if opcode == 0x33:  # ADD命令 (rd = rs1 + rs2)
            self.registers[rd] = self.registers[rs1] + self.registers[rs2]
        elif opcode == 0x13:  # ADDI命令 (rd = rs1 + imm)
            self.registers[rd] = self.registers[rs1] + imm
        elif opcode == 0x63:  # BEQ命令 (if rs1 == rs2 then PC += imm)
            if self.registers[rs1] == self.registers[rs2]:
                self.pc += imm
        elif opcode == 0x6F:  # JAL命令 (jump and link)
            self.registers[rd] = self.pc
            self.pc += imm
        elif opcode == 0x67:  # HALT (仮の命令として設定)
            self.running = False
        else:
            print(f"Unknown instruction: {instr}")
    
    def run(self):
        while self.running:
            instr = self.fetch()
            self.decode_execute(instr)
            print(f"PC: {self.pc}, Registers: {self.registers}")

# サンプルプログラム (ADDI x1, x0, 5; ADDI x2, x0, 10; ADD x3, x1, x2; HALT)
program = [
    (0x13 | (1 << 7) | (0 << 15) | (5 << 20)),  # ADDI x1, x0, 5
    (0x13 | (2 << 7) | (0 << 15) | (10 << 20)), # ADDI x2, x0, 10
    (0x33 | (3 << 7) | (1 << 15) | (2 << 20)),  # ADD x3, x1, x2
    0x67  # HALT
]

vm = RISCV_VM()
vm.load_program(program)
vm.run()
~~~

コードの構成

RISCV_VM クラス: 仮想マシンの主要なコンポーネントをカプセル化します。
__init__: 仮想マシンの初期化を行います。32個のレジスタ、メモリ、プログラムカウンタ（pc）、実行フラグ（running）を準備します。

load_program: メモリにプログラムをロードします。
fetch: 現在のpcが指すメモリ位置から命令をフェッチ（読み込み）します。その後、pcをインクリメントします。

decode_execute: フェッチされた命令をデコード（解釈）し、実行します。命令のオペコードとオペランド（レジスタ番号、即値）を抽出し、それに応じてレジスタの値を変更したり、pcを更新したりします。
run: 仮想マシンの実行ループです。runningフラグがTrueの間、命令をフェッチ、デコード、実行し続けます。命令の実行後、pcとレジスタの内容を出力します。

サンプルプログラム:
program: 仮想マシンが実行する命令をリストで定義しています。各命令は整数で表され、RISC-Vの命令フォーマットに従ってエンコードされています。
ADDI x1, x0, 5 : x1レジスタにx0レジスタの値+5を代入(x1=5)
ADDI x2, x0, 10 : x2レジスタにx0レジスタの値+10を代入(x2=10)
ADD x3, x1, x2 : x3レジスタにx1レジスタの値+x2レジスタの値を代入(x3=15)
HALT: プログラムを停止します。

実行部分:
vm = RISCV_VM(): RISCV_VMクラスのインスタンスを作成します。
vm.load_program(program): サンプルプログラムを仮想マシンのメモリにロードします。
vm.run(): 仮想マシンの実行を開始します。

プログラムコードの説明：
(0x13 | (1 << 7) | (0 << 15) | (5 << 20)) # ADDI x1, x0, 5

0x13：これはADDI（即値追加）命令のオペコードである。RISC-V ISAでは、オペコードは7ビット・フィールドである。16進数の0x13は2進数では0b0010011であり、これがADDIの正しいオペコードである。
(1 << 7): この部分はデスティネーション・レジスタ（rd）をエンコードする。
1: レジスタ番号。この場合はx1（レジスタ番号1）。
<< 7: 左シフト演算子。RISC-V命令形式のrdフィールドがビット7から11（合計5ビット）を占めるため、1を7ビット左にシフトする。
1 << 7の結果は、0b10000000（10進数128）となる。これにより、レジスタ番号1がrdの正しいビット位置に配置される。

(0 << 15): この部分は最初のソース・レジスタ(rs1)をエンコードする。
0: レジスタ番号。x0は常に値0を保持する特別なレジスタである。
<< 15: rs1フィールドが15ビットから19ビットを占めるので、0を15ビット左にシフトする。
0 << 15の結果は0b0（10進数の0）である。

(5 << 20): この部分は即値(imm)を符号化する。
5：即値。rs1の値（x0）に5を加えたい。


実行
コマンドラインまたはターミナルで、保存したファイルがあるディレクトリに移動します。
python riscv_vm.py と入力して実行します。
結果確認:
実行結果として、各命令実行後のPCの値とレジスタの内容が出力されます。
最終的に、x3レジスタに15（5 + 10）が格納されていることが確認できるはずです。

$ python riscv_vm.py

結果
PC: 1, Registers: [0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
PC: 2, Registers: [0, 5, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
PC: 3, Registers: [0, 5, 10, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
PC: 4, Registers: [0, 5, 10, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]