[Python] 为 Vole 机器语言实现图形化界面

背景

计算机科学概论 一书中提到了 Vole\text{Vole} Vole 这种机器语言。由于这个这种语言的指令比较简单,我想到可以用图形化界面来展示 Vole\text{Vole} Vole 指令的执行效果。示例效果如下 ⬇️

本文会提供 Vole\text{Vole} Vole 的简介和相关代码

正文

基本信息

Vole\text{Vole} Vole 所对应的机器的体系结构如下

  • 1616 16 个通用寄存器( Register\text{Register} Register),编号为 0x0,0x1,⋯ ,0xE,0xF\text{0x0},\text{0x1},\cdots,\text{0xE},\text{0xF} 0x0,0x1,⋯,0xE,0xF。每个寄存器的长度为 11 1 字节( 88 8 位)
  • 机器内存( Memory\text{Memory} Memory)共有 256256 256 个 cell\text{cell} cell,每个 cell\text{cell} cell 可以存储 11 1 字节

Vole\text{Vole} Vole 语言的指令长度总是 22 2 字节(共 1616 16 位)。前 44 4 位是 op-code\text{op-code} op-code,后 1212 12 位用于表示 operand\text{operand} operand。 op-code\text{op-code} op-code 共有 1212 12 种 ⬇️

op-code\text{op-code} op-code operand\text{operand} operand 作用
0x1\text{0x1} 0x1 RXY\text{RXY} RXY 将地址为 XY\text{XY} XY 的 cell\text{cell} cell 中的内容复制到寄存器 R\text{R} R 中。 例子: 0x14A3\text{0x14A3} 0x14A3 指令会将地址为 A3\text{A3} A3 的 cell\text{cell} cell 中的内容复制到寄存器 0x4\text{0x4} 0x4 中
0x2\text{0x2} 0x2 RXY\text{RXY} RXY 将值 XY\text{XY} XY 保存到寄存器 R\text{R} R 中。 例子: 0x20A3\text{0x20A3} 0x20A3 指令会将 0xA3\text{0xA3} 0xA3 保存到寄存器 0x0\text{0x0} 0x0 里
0x3\text{0x3} 0x3 RXY\text{RXY} RXY 将寄存器 R\text{R} R 中的值复制到地址为 XY\text{XY} XY 的 cell\text{cell} cell 中。 例子: 0x35B1\text{0x35B1} 0x35B1 指令会将寄存器 0x5\text{0x5} 0x5 的值复制到地址为 B1\text{B1} B1 的 cell\text{cell} cell 中
0x4\text{0x4} 0x4 0RS\text{0RS} 0RS 将寄存器 R\text{R} R 的值复制到寄存器 S\text{S} S 中。 例子: 0x40A4\text{0x40A4} 0x40A4 指令会将寄存器 0xA\text{0xA} 0xA 的值复制到寄存器 0x4\text{0x4} 0x4 中
0x5\text{0x5} 0x5 RST\text{RST} RST 将寄存器 S\text{S} S 和寄存器 T\text{T} T 中的值相加(用 2's complement\text{2's complement} 2's complement 的方式)。 例子: 0x5726\text{0x5726} 0x5726 指令会将寄存器 0x2\text{0x2} 0x2 与寄存器 0x6\text{0x6} 0x6 的值的和保存到寄存器 0x7\text{0x7} 0x7 中
0x6\text{0x6} 0x6 RST\text{RST} RST 将寄存器 S\text{S} S 和寄存器 T\text{T} T 中的值相加(将两者的值视为浮点数)。 例子: 0x634E\text{0x634E} 0x634E 指令会将寄存器 0x4\text{0x4} 0x4 与寄存器 0xE\text{0xE} 0xE 的值(两个值都被视为浮点数)的和保存到寄存器 0x3\text{0x3} 0x3 中
0x7\text{0x7} 0x7 RST\text{RST} RST 对寄存器 S\text{S} S 和寄存器 T\text{T} T 中的值进行 OR\text{OR} OR 运算,将计算结果保存到寄存器 R\text{R} R 中。 例子: 0x7CB4\text{0x7CB4} 0x7CB4 指令会对寄存器 0xB\text{0xB} 0xB 与寄存器 0x4\text{0x4} 0x4 的值进行 OR\text{OR} OR 运算,并将计算结果保存到寄存器 0xC\text{0xC} 0xC 中
0x8\text{0x8} 0x8 RST\text{RST} RST 对寄存器 S\text{S} S 和寄存器 T\text{T} T 中的值进行 AND\text{AND} AND 运算,将计算结果保存到寄存器 R\text{R} R 中。 例子: 0x8045\text{0x8045} 0x8045 指令会对寄存器 0x4\text{0x4} 0x4 与寄存器 0x5\text{0x5} 0x5 的值进行 AND\text{AND} AND 运算,并将计算结果保存到寄存器 0x0\text{0x0} 0x0 中
0x9\text{0x9} 0x9 RST\text{RST} RST 对寄存器 S\text{S} S 和寄存器 T\text{T} T 中的值进行 XOR\text{XOR} XOR 运算,将计算结果保存到寄存器 R\text{R} R 中。 例子: 0x95F3\text{0x95F3} 0x95F3 指令会对寄存器 0xF\text{0xF} 0xF 与寄存器 0x3\text{0x3} 0x3 的值进行 XOR\text{XOR} XOR 运算,并将计算结果保存到寄存器 0x5\text{0x5} 0x5 中
0xA\text{0xA} 0xA R0X\text{R0X} R0X 对寄存器 R\text{R} R 中的值执行 X\text{X} X 次循环右移操作。 例子: 0xA403\text{0xA403} 0xA403 指令会对寄存器 0x4\text{0x4} 0x4 的值执行 33 3 次循环右移操作
0xB\text{0xB} 0xB RXY\text{RXY} RXY 如果寄存器 R\text{R} R 的值和寄存器 0x0\text{0x0} 0x0 的值相等,则跳转执行地址为 XY\text{XY} XY 的 cell\text{cell} cell 处的指令。 例子: 0xB43C\text{0xB43C} 0xB43C 指令会检查寄存器 0x4\text{0x4} 0x4 和寄存器 0x0\text{0x0} 0x0 的值是否相等。如果两者相等,则将程序计数器的值设置为 0x3C\text{0x3C} 0x3C(这样的话, 0x3C\text{0x3C} 0x3C 处的指令就会成为下一条指令)。如果两者不相等,不用做任何事情,程序会按照正常的流程继续下去
0xC\text{0xC} 0xC 000\text{000} 000 停止执行。 例子: 0xC000\text{0xC000} 0xC000 指令会让程序停止

实现基本功能

基于上述介绍,我们可以用 Python\text{Python} Python 来模拟 Vole\text{Vole} Vole 指令的执行过程。请注意,以下功能我没有实现

  • 浮点数加法: 涉及 op-code\text{op-code} op-code 为 0x6\text{0x6} 0x6 的那些指令
  • 程序计数器: 涉及 op-code\text{op-code} op-code 为 0xB\text{0xB} 0xB 的那些指令
  • 停止执行: 涉及 op-code\text{op-code} op-code 为 0xC\text{0xC} 0xC 的指令

排除掉上述 33 3 个功能后,我实现了可以模拟执行剩余指令的 Python\text{Python} Python 程序 (trae 提供了一些帮助) ⬇️

python 复制代码
class Register:
    def __init__(self):
        self.value = 0

    def load(self, value):
        if value < -128 or value > 127:
            raise ValueError("Value must be in range [-128, 127]")
        self.value = value
        
    def __repr__(self):
        return f"Register(value={self.value})"

class Memory:
    def __init__(self, size):
        self.cells = [0] * size
        self.size = size
        
    def read(self, address):
        if address < 0 or address > self.size - 1:
            raise ValueError(f"Address must be in range [0, {self.size - 1}]")
        return self.cells[address]
        
    def store(self, address, value):
        if address < 0 or address > self.size - 1:
            raise ValueError(f"Address must be in range [0, {self.size - 1}]")
        if value < -128 or value > 127:
            raise ValueError("Value must be in range [-128, 127]")
        self.cells[address] = value
        
    def __repr__(self):
        return f"Memory(size={self.size})"

class CPU:
    def __init__(self):
        self.registers = [Register() for _ in range(16)]
        self.memory = Memory(256)

    def adjust_value(self, value):
        if value > 127:
            value -= 256
        return value
    
    def to_non_negative(self, value):
        if value < 0:
            value += 256
        return value
    
    def to_bits(self, value):
        if (value < 0 or value >= 256):
            raise ValueError("Value must be in range [0, 255]")
        return [(value >> i) & 1 for i in range(8)][::-1]

    def to_int(self, bits):
        return int("".join(map(str, bits)), 2)

    def extract_opcode(self, instruction):
        return instruction >> 12

    def extract_operand1(self, instruction):
        return (instruction >> 8) & 0x0F

    def extract_operand2(self, instruction):
        return (instruction >> 4)& 0x0F

    def extract_operand3(self, instruction):
        return instruction & 0x0F

    def extract_operand2and3(self, instruction):
        return instruction & 0xFF

    def extract_R_XY(self, instruction):
        return self.extract_operand1(instruction), self.extract_operand2and3(instruction)

    def extract_R_S_T(self, instruction):
        return self.extract_operand1(instruction), self.extract_operand2(instruction), self.extract_operand3(instruction)
    
    def bitwise_operation(self, R, S, T, operator):
        s_value = self.to_non_negative(self.registers[S].value)
        t_value = self.to_non_negative(self.registers[T].value)
        self.registers[R].load(self.adjust_value(operator(s_value, t_value)))

    def run(self, instruction):
        opcode = self.extract_opcode(instruction)
        if opcode == 1:
            # 0x1: RXY
            # R=M[XY]
            R, XY = self.extract_R_XY(instruction)
            self.registers[R].load(self.memory.read(XY))
        elif opcode == 2:
            # 0x2: RXY
            # R=XY
            R, XY = self.extract_R_XY(instruction)
            self.registers[R].load(self.adjust_value(XY))
        elif opcode == 3:
            # 0x3: RXY
            # M[XY]=R
            R, XY = self.extract_R_XY(instruction)
            self.memory.store(XY, self.registers[R].value)
        elif opcode == 4:
            # 0x4: 0RS
            # R[S]=R[R]
            if self.extract_operand1(instruction) != 0:
                raise ValueError("Instruction format error!")
            R = self.extract_operand2(instruction)
            S = self.extract_operand3(instruction)
            self.registers[S].load(self.registers[R].value)
        elif opcode == 5:
            # 0x5: RST
            # R[R]=R[S]+R[T] (with 2's complement arithmetic)
            R, S, T = self.extract_R_S_T(instruction)
            result = self.registers[S].value + self.registers[T].value
            if result > 127:
                result -= 256
            if result < -128:
                result += 256
            self.registers[R].load(result)
        elif opcode == 6:
            # 0x6: RST
            # I don't know how to implement floating-point arithmetic (yet)
            raise ValueError("Not implemented yet!")
        elif opcode == 7:
            # 0x7: RST
            # R[R] = R[S] | R[T]
            R, S, T = self.extract_R_S_T(instruction)
            self.bitwise_operation(R, S, T, lambda a, b: a | b)
        elif opcode == 8:
            # 0x8: RST
            # R[R] = R[S] & R[T]
            R, S, T = self.extract_R_S_T(instruction)
            self.bitwise_operation(R, S, T, lambda a, b: a & b)
        elif opcode == 9:
            # 0x9: RST
            # R[R] = R[S] ^ R[T]
            R, S, T = self.extract_R_S_T(instruction)
            self.bitwise_operation(R, S, T, lambda a, b: a ^ b)
        elif opcode == 0xA:
            # 0xA: R0X
            # Rotate R[R] right by X bits
            if self.extract_operand2(instruction) != 0:
                raise ValueError("Instruction format error!")
            R = self.extract_operand1(instruction)
            X = self.extract_operand3(instruction)
            r_value = self.to_non_negative(self.registers[R].value)
            x_value = self.to_non_negative(X) % 8
            bits = self.to_bits(r_value)
            raw = (bits + bits)[x_value : (x_value + 8)]
            self.registers[R].load(self.adjust_value(self.to_int(raw)))
        elif opcode in [0xB, 0xC]:
            raise ValueError("Not implemented yet!")
        else:
            raise ValueError("Invalid opcode")

请将上述代码保存为 vole.py\text{vole.py} vole.py

添加图形化界面

vole.py\text{vole.py} vole.py 所提供的代码的基础上,我让 trae 帮忙生成了支持图形化界面的代码 ⬇️

python 复制代码
import pygame
from vole import CPU

# ===================== 基础配置 =====================
pygame.init()

WIDTH, HEIGHT = 1100, 750
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("VOLE CPU Simulator")

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (180, 180, 180)
LIGHT_GRAY = (230, 230, 230)
DARK_GRAY = (100, 100, 100)
BLUE = (50, 120, 200)
RED = (220, 60, 60)
GREEN = (60, 180, 60)
YELLOW = (255, 220, 80)
PURPLE = (120, 60, 200)

font = pygame.font.SysFont("Monaco", 14)
font_small = pygame.font.SysFont("Monaco", 12)
font_title = pygame.font.SysFont("Arial", 18, bold=True)

# ===================== 游戏数据 =====================
cpu = CPU()
input_text = ""
error_msg = ""
instruction_history = []
MAX_HISTORY = 10

# ===================== 辅助函数 =====================
def decode_instruction(instruction):
    opcode = instruction >> 12
    if opcode == 1:
        R = (instruction >> 8) & 0x0F
        XY = instruction & 0xFF
        return f"0x{instruction:04X}: LOAD R{R} = M[0x{XY:02X}]"
    elif opcode == 2:
        R = (instruction >> 8) & 0x0F
        XY = instruction & 0xFF
        return f"0x{instruction:04X}: MOV R{R} = 0x{XY:02X}"
    elif opcode == 3:
        R = (instruction >> 8) & 0x0F
        XY = instruction & 0xFF
        return f"0x{instruction:04X}: STORE M[0x{XY:02X}] = R{R}"
    elif opcode == 4:
        R = (instruction >> 4) & 0x0F
        S = instruction & 0x0F
        return f"0x{instruction:04X}: COPY R{S} = R{R}"
    elif opcode == 5:
        R = (instruction >> 8) & 0x0F
        S = (instruction >> 4) & 0x0F
        T = instruction & 0x0F
        return f"0x{instruction:04X}: ADD R{R} = R{S} + R{T}"
    elif opcode == 7:
        R = (instruction >> 8) & 0x0F
        S = (instruction >> 4) & 0x0F
        T = instruction & 0x0F
        return f"0x{instruction:04X}: OR R{R} = R{S} | R{T}"
    elif opcode == 8:
        R = (instruction >> 8) & 0x0F
        S = (instruction >> 4) & 0x0F
        T = instruction & 0x0F
        return f"0x{instruction:04X}: AND R{R} = R{S} & R{T}"
    elif opcode == 9:
        R = (instruction >> 8) & 0x0F
        S = (instruction >> 4) & 0x0F
        T = instruction & 0x0F
        return f"0x{instruction:04X}: XOR R{R} = R{S} ^ R{T}"
    elif opcode == 0xA:
        R = (instruction >> 8) & 0x0F
        X = instruction & 0x0F
        return f"0x{instruction:04X}: ROTATE R{R} right {X} times"
    elif opcode in [6, 0xB, 0xC]:
        return f"0x{instruction:04X}: OPCODE {opcode} - Not implemented"
    else:
        return f"0x{instruction:04X}: Invalid opcode"

def execute_instruction(hex_str):
    global error_msg, instruction_history
    if len(hex_str) == 4:
        try:
            instruction = int(hex_str, 16)
            cpu.run(instruction)
            decoded = decode_instruction(instruction)
            instruction_history.insert(0, decoded)
            if len(instruction_history) > MAX_HISTORY:
                instruction_history.pop()
            error_msg = ""
            return True
        except ValueError as e:
            error_msg = str(e)
            return False
    else:
        error_msg = "Please enter exactly 4 hex digits"
        return False

def reset_cpu():
    global cpu, input_text, error_msg, instruction_history
    cpu = CPU()
    input_text = ""
    error_msg = ""
    instruction_history = []

# ===================== 绘制函数 =====================
def draw_registers():
    x, y = 30, 50
    
    title = font_title.render("Registers (R0-R15)", True, BLACK)
    screen.blit(title, (x, y - 25))
    
    for i in range(16):
        rx = x + (i % 4) * 100
        ry = y + (i // 4) * 45
        
        val = cpu.registers[i].value
        if val >= 0:
            hex_val = f"{val:02X}"
        else:
            hex_val = f"{val & 0xFF:02X}"
        
        pygame.draw.rect(screen, LIGHT_GRAY, (rx, ry, 85, 35), border_radius=5)
        pygame.draw.rect(screen, BLACK, (rx, ry, 85, 35), 2, border_radius=5)
        
        name = font_small.render(f"R{i:02d}", True, BLUE)
        screen.blit(name, (rx + 5, ry + 5))
        
        value = font.render(f"{hex_val} ({val:4d})", True, BLACK)
        screen.blit(value, (rx + 5, ry + 18))

def draw_memory():
    x, y = 480, 50
    
    title = font_title.render("Memory (0x00-0xFF)", True, BLACK)
    screen.blit(title, (x, y - 25))
    
    pygame.draw.rect(screen, LIGHT_GRAY, (x - 40, y - 5, 610, 410), border_radius=5)
    
    for row in range(16):
        addr_label = font_small.render(f"{row*16:02X}", True, BLUE)
        screen.blit(addr_label, (x - 30, y + row * 25))
        
        for col in range(16):
            addr = row * 16 + col
            val = cpu.memory.read(addr)
            
            if val >= 0:
                hex_val = f"{val:02X}"
            else:
                hex_val = f"{val & 0xFF:02X}"
            
            mx = x + col * 35
            my = y + row * 25
            
            pygame.draw.rect(screen, WHITE, (mx, my, 30, 20), border_radius=3)
            pygame.draw.rect(screen, BLACK, (mx, my, 30, 20), 1, border_radius=3)
            
            value = font_small.render(hex_val, True, BLACK)
            screen.blit(value, (mx + 5, my + 2))

def draw_input_area():
    x, y = 30, HEIGHT - 120
    
    title = font_title.render("Instruction Input", True, BLACK)
    screen.blit(title, (x, y - 25))
    
    pygame.draw.rect(screen, WHITE, (x, y, 300, 40), border_radius=5)
    pygame.draw.rect(screen, BLACK, (x, y, 300, 40), 2, border_radius=5)
    
    if input_text:
        text = font.render(f"0x{input_text}", True, BLACK)
    else:
        text = font.render("Enter 4-digit hex instruction...", True, GRAY)
    screen.blit(text, (x + 10, y + 10))
    
    step_btn = pygame.Rect(x + 320, y, 100, 40)
    pygame.draw.rect(screen, BLUE, step_btn, border_radius=5)
    step_text = font.render("Step", True, WHITE)
    screen.blit(step_text, (step_btn.centerx - step_text.get_width()//2, step_btn.centery - step_text.get_height()//2))
    
    reset_btn = pygame.Rect(x + 430, y, 100, 40)
    pygame.draw.rect(screen, RED, reset_btn, border_radius=5)
    reset_text = font.render("Reset", True, WHITE)
    screen.blit(reset_text, (reset_btn.centerx - reset_text.get_width()//2, reset_btn.centery - reset_text.get_height()//2))
    
    if error_msg:
        error_text = font.render(error_msg, True, RED)
        screen.blit(error_text, (x, y + 50))
    
    return step_btn, reset_btn

def draw_instruction_history():
    x, y = 570, HEIGHT - 120
    
    title = font_title.render("Instruction History", True, BLACK)
    screen.blit(title, (x, y - 25))
    
    pygame.draw.rect(screen, LIGHT_GRAY, (x, y, 500, 80), border_radius=5)
    
    for i, entry in enumerate(instruction_history):
        text = font_small.render(entry, True, PURPLE)
        screen.blit(text, (x + 10, y + 5 + i * 18))

def draw():
    screen.fill(WHITE)
    
    draw_registers()
    draw_memory()
    step_btn, reset_btn = draw_input_area()
    draw_instruction_history()
    
    pygame.display.flip()
    return step_btn, reset_btn

# ===================== 主循环 =====================
running = True
while running:
    step_btn, reset_btn = draw()
    
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_BACKSPACE:
                input_text = input_text[:-1]
                error_msg = ""
            elif event.key == pygame.K_RETURN:
                execute_instruction(input_text)
            elif len(input_text) < 4 and event.unicode in "0123456789ABCDEFabcdef":
                input_text = input_text.upper() + event.unicode.upper()
                error_msg = ""
            elif event.key == pygame.K_r and pygame.key.get_mods() & pygame.KMOD_CTRL:
                reset_cpu()
        
        if event.type == pygame.MOUSEBUTTONDOWN:
            mouse_pos = pygame.mouse.get_pos()
            if step_btn.collidepoint(mouse_pos):
                execute_instruction(input_text)
            elif reset_btn.collidepoint(mouse_pos):
                reset_cpu()

pygame.quit()

请将以上代码保存为 vole_gui.py\text{vole\_gui.py} vole_gui.py。使用如下命令可以运行 vole_gui.py\text{vole\_gui.py} vole_gui.py

bash 复制代码
python3 vole_gui.py

运行效果

运行 vole_gui.py\text{vole\_gui.py} vole_gui.py 后,看到的初始界面会是这样 ⬇️

示例

以加法 5+6=115+6=11 5+6=11 为例,我们可以用以下 Vole\text{Vole} Vole 指令来完成

text 复制代码
0x2005 # 将字面值 5 保存到 0x0 寄存器中
0x2106 # 将字面值 6 保存到 0x1 寄存器中
0x5201 # 对寄存器 0x0 和寄存器 0x1 的值求和,计算结果(即,11)保存到寄存器 0x2 中
0x3200 # 将寄存器 0x2 的值(即,11)保存到地址为 0x00 的 cell 中

执行完 0x2005\text{0x2005} 0x2005 指令之后,效果如下图所示 ⬇️

执行完 0x2106\text{0x2106} 0x2106 指令之后,效果如下图所示 ⬇️

执行完 0x5201\text{0x5201} 0x5201 指令之后,效果如下图所示 ⬇️

执行完 0x3200\text{0x3200} 0x3200 指令之后,效果如下图所示 ⬇️

我们通过执行 Vole\text{Vole} Vole 指令,将 5+65+6 5+6 的和保存到了地址为 0x00\text{0x00} 0x00 的 cell\text{cell} cell 里,运行结果符合预期。

相关推荐
matlabgoodboy4 小时前
计算机毕设代做|Java Python Matlab APP 全套开发设计
java·python·课程设计
用户8356290780515 小时前
Python Word 转 PDF 和 PDF 转 Word 指南
后端·python
用户8356290780515 小时前
如何使用 Python 加密和保护 Word 文档
后端·python
Fluxproxy5 小时前
AI数据集采集、批量模型推理报错频发?AI项目网络稳定性优化实战
python·ai·ai编程
kango6 小时前
iOS 项目工程化构成与 Xcode 架构详解
ios·程序员
Fanta丶6 小时前
10.Python class类的定义和魔术方法
python
前端开发张小七6 小时前
Java 学习笔记 · 第三课:多线程与并发编程(线程、同步、死锁、Lock、乐观锁与悲观锁)
java·后端·程序员
小玮看世界7 小时前
[Python]从“脏”数据到优雅实现:一个IoT滑动窗口最大值问题的测试驱动优化实录
linux·前端·python
MC皮蛋侠客8 小时前
SQLAlchemy 系列(十一):从 1.x 到 2.x——渐进迁移与数据访问层治理
数据库·python
魔镜前的帅比8 小时前
(开源项目)x-claw (设计)
python·ai·rust·开源