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

本文会提供 Vole 的简介和相关代码
正文
基本信息
Vole 所对应的机器的体系结构如下
- 有 16 个通用寄存器( Register),编号为 0x0,0x1,⋯,0xE,0xF。每个寄存器的长度为 1 字节( 8 位)
- 机器内存( Memory)共有 256 个 cell,每个 cell 可以存储 1 字节
Vole 语言的指令长度总是 2 字节(共 16 位)。前 4 位是 op-code,后 12 位用于表示 operand。 op-code 共有 12 种 ⬇️
| op-code | operand | 作用 |
|---|---|---|
| 0x1 | RXY | 将地址为 XY 的 cell 中的内容复制到寄存器 R 中。 例子: 0x14A3 指令会将地址为 A3 的 cell 中的内容复制到寄存器 0x4 中 |
| 0x2 | RXY | 将值 XY 保存到寄存器 R 中。 例子: 0x20A3 指令会将 0xA3 保存到寄存器 0x0 里 |
| 0x3 | RXY | 将寄存器 R 中的值复制到地址为 XY 的 cell 中。 例子: 0x35B1 指令会将寄存器 0x5 的值复制到地址为 B1 的 cell 中 |
| 0x4 | 0RS | 将寄存器 R 的值复制到寄存器 S 中。 例子: 0x40A4 指令会将寄存器 0xA 的值复制到寄存器 0x4 中 |
| 0x5 | RST | 将寄存器 S 和寄存器 T 中的值相加(用 2's complement 的方式)。 例子: 0x5726 指令会将寄存器 0x2 与寄存器 0x6 的值的和保存到寄存器 0x7 中 |
| 0x6 | RST | 将寄存器 S 和寄存器 T 中的值相加(将两者的值视为浮点数)。 例子: 0x634E 指令会将寄存器 0x4 与寄存器 0xE 的值(两个值都被视为浮点数)的和保存到寄存器 0x3 中 |
| 0x7 | RST | 对寄存器 S 和寄存器 T 中的值进行 OR 运算,将计算结果保存到寄存器 R 中。 例子: 0x7CB4 指令会对寄存器 0xB 与寄存器 0x4 的值进行 OR 运算,并将计算结果保存到寄存器 0xC 中 |
| 0x8 | RST | 对寄存器 S 和寄存器 T 中的值进行 AND 运算,将计算结果保存到寄存器 R 中。 例子: 0x8045 指令会对寄存器 0x4 与寄存器 0x5 的值进行 AND 运算,并将计算结果保存到寄存器 0x0 中 |
| 0x9 | RST | 对寄存器 S 和寄存器 T 中的值进行 XOR 运算,将计算结果保存到寄存器 R 中。 例子: 0x95F3 指令会对寄存器 0xF 与寄存器 0x3 的值进行 XOR 运算,并将计算结果保存到寄存器 0x5 中 |
| 0xA | R0X | 对寄存器 R 中的值执行 X 次循环右移操作。 例子: 0xA403 指令会对寄存器 0x4 的值执行 3 次循环右移操作 |
| 0xB | RXY | 如果寄存器 R 的值和寄存器 0x0 的值相等,则跳转执行地址为 XY 的 cell 处的指令。 例子: 0xB43C 指令会检查寄存器 0x4 和寄存器 0x0 的值是否相等。如果两者相等,则将程序计数器的值设置为 0x3C(这样的话, 0x3C 处的指令就会成为下一条指令)。如果两者不相等,不用做任何事情,程序会按照正常的流程继续下去 |
| 0xC | 000 | 停止执行。 例子: 0xC000 指令会让程序停止 |
实现基本功能
基于上述介绍,我们可以用 Python 来模拟 Vole 指令的执行过程。请注意,以下功能我没有实现
- 浮点数加法: 涉及 op-code 为 0x6 的那些指令
- 程序计数器: 涉及 op-code 为 0xB 的那些指令
- 停止执行: 涉及 op-code 为 0xC 的指令
排除掉上述 3 个功能后,我实现了可以模拟执行剩余指令的 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
添加图形化界面
在 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。使用如下命令可以运行 vole_gui.py
bash
python3 vole_gui.py
运行效果
运行 vole_gui.py 后,看到的初始界面会是这样 ⬇️ 
示例
以加法 5+6=11 为例,我们可以用以下 Vole 指令来完成
text
0x2005 # 将字面值 5 保存到 0x0 寄存器中
0x2106 # 将字面值 6 保存到 0x1 寄存器中
0x5201 # 对寄存器 0x0 和寄存器 0x1 的值求和,计算结果(即,11)保存到寄存器 0x2 中
0x3200 # 将寄存器 0x2 的值(即,11)保存到地址为 0x00 的 cell 中
执行完 0x2005 指令之后,效果如下图所示 ⬇️ 
执行完 0x2106 指令之后,效果如下图所示 ⬇️

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

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

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